Sebastian's Gopherspace (HTTP Gateway)

Introduction
------------------------------------------------------------
 
zeef is a DSL for filter expressions in Clojure. It provides
a unified and declarative way to define conditions, logical
expressions, and nested queries on structured data.
 
Filter expressions in zeef are structured Clojure data that
use special field references and operators, making them easy
to serialize, store, and manipulate programmatically.
 
Use zeef when you want to define filter logic once and apply
it across multiple data sources - from in-memory collections
to databases, search engines, and APIs - without rewriting
your filtering logic for each backend.
 
zeef comes with a built-in evaluation module that compiles
filter expressions into predicate functions, enabling
seamless integration with standard Clojure collection
functions like filter, remove, and some.
 
 
Installation
------------------------------------------------------------
 
zeef is available via Clojars [1].
 
 
Quick Start
------------------------------------------------------------
 
Here's a simple example to get you started:
 
  (require '[zeef.core :as zf]
           '[zeef.eval :as ze])
 
  ;; Define a filter expression
  (def adult-filter (zf/z->= (zf/field :age) 18))
 
  ;; Compile to a predicate function
  (def adult? (ze/compile-expression adult-filter))
 
  ;; Use with data
  (adult? {:name "Alice" :age 25}) ; => true
  (adult? {:name "Bob" :age 16})   ; => false
 
  ;; Use with collections
  (def people [{:name "Alice" :age 25} {:name "Bob" :age 16}])
  (filter adult? people) ; => ({:name "Alice", :age 25})
 
 
Building Filter Expressions
------------------------------------------------------------
 
Filter expressions in zeef can be:
 
  * Conditions - Simple tests on fields or values
  * Logical Expressions - Combine conditions with AND, OR, NOT
  * Nested Queries - Test properties of nested objects or collections
 
Once defined, filter expressions can be compiled into
executable predicate functions using the zeef.eval module.
You can also create custom evaluation modules to translate
filter expressions for different data sources (databases,
search engines, APIs, etc.), enabling the same filter
expressions to work across multiple backends.
 
 
Fields
------------------------------------------------------------
 
Use field to reference data fields:
 
  (require '[zeef.core :as zf])
 
  (zf/field :name) ; references the :name field
  (zf/field :age)  ; references the :age field
 
 
Conditions
------------------------------------------------------------
 
Nil Test
--------
 
z-nil? - Tests if a field is nil
 
  (zf/z-nil? (zf/field :name))
 
 
Comparison Operations
---------------------
 
  * z-< - Less than
  * z-<= - Less than or equal to
  * z-= - Equal to
  * z-> - Greater than
  * z->= - Greater than or equal to
 
  (zf/z-< (zf/field :age) 30)          ; age < 30
  (zf/z->= (zf/field :score) 80)       ; score >= 80
  (zf/z-= (zf/field :status) "active") ; status = "active"
 
 
String Operations
-----------------
 
  * z-starts-with? - Tests if a string starts with a prefix
  * z-ends-with? - Tests if a string ends with a suffix
  * z-includes? - Tests if a string contains a substring
 
  (zf/z-starts-with? (zf/field :name) "A")        ; name starts with "A"
  (zf/z-ends-with? (zf/field :email) ".com")      ; email ends with ".com"
  (zf/z-includes? (zf/field :description) "test") ; description contains "test"
 
 
Collection Operations
---------------------
 
z-in? - Tests if a value is in a collection
 
  (zf/z-in? (zf/field :category) ["tech" "science" "news"]) ; category in collection
 
 
Range Operations
----------------
 
z-between? - Tests if a value is within a range (inclusive)
 
  (zf/z-between? (zf/field :age) 18 65) ; 18 <= age <= 65
 
 
Logical Expressions
------------------------------------------------------------
 
Combine conditions with logical operators:
 
  * z-not - Logical negation
  * z-and - Logical AND (accepts multiple arguments)
  * z-or - Logical OR (accepts multiple arguments)
 
  (zf/z-not (zf/z-nil? (zf/field :name))) ; name is not nil
 
  (zf/z-and                               ; age < 30 AND status = "active"
    (zf/z-< (zf/field :age) 30)
    (zf/z-= (zf/field :status) "active"))
 
  (zf/z-or                                ; age < 18 OR age > 65
    (zf/z-< (zf/field :age) 18)
    (zf/z-> (zf/field :age) 65))
 
 
Nested Queries
------------------------------------------------------------
 
Test properties of nested objects or collections:
 
  * z-satisfies? - Tests if a nested object satisfies an expression
  * z-some? - Tests if any element in a collection satisfies an expression
 
  ;; Test if nested address has a specific city
  (zf/z-satisfies? (zf/field :address) 
                   (zf/z-= (zf/field :city) "Berlin"))
 
  ;; Test if any order has a value > 100
  (zf/z-some? (zf/field :orders)
              (zf/z-> (zf/field :value) 100))
 
 
Compilation and Evaluation
------------------------------------------------------------
 
The zeef.eval module provides the compile-expression function
that transforms declarative filter expressions into predicate
functions:
 
  (require '[zeef.core :as zf]
           '[zeef.eval :as ze])
 
  ;; Define a filter expression
  (def age-filter (zf/z->= (zf/field :age) 18))
 
  ;; Compile it to a predicate function
  (def age-predicate (ze/compile-expression age-filter))
 
  ;; Use the predicate function
  (age-predicate {:name "Alice" :age 25}) ; => true
  (age-predicate {:name "Bob" :age 16})   ; => false
 
 
Custom Resolvers
----------------
 
By default, compile-expression uses Clojure's get function to
resolve field values. You can provide a custom resolver
function:
 
  ;; Custom resolver for nested field access
  (defn nested-resolver [data field-name]
    (get-in data [:user field-name]))
 
  (def custom-predicate 
    (ze/compile-expression 
      (zf/z-= (zf/field :status) "active")
      nested-resolver))
 
  ;; Works with nested data structure
  (custom-predicate {:user {:status "active" :name "Alice"}}) ; => true
 
 
Serialization and Deserialization
------------------------------------------------------------
 
zeef filter expressions can be serialized to and from EDN
format, making them easy to store in databases, transmit over
networks, or persist to files. This is particularly useful
when you need to save user-defined filters or share filter
logic between systems.
 
Use pr-str to serialize any zeef filter expression to an EDN
string:
 
  (require '[zeef.core :as zf]
           '[zeef.types :as zt])
 
  ;; Create a complex filter expression
  (def user-filter
    (zf/z-and
      (zf/z->= (zf/field :age) 18)
      (zf/z-= (zf/field :status) "active")
      (zf/z-some? (zf/field :orders)
                  (zf/z-> (zf/field :amount) 100))))
 
  ;; Serialize to EDN string
  (def serialized-filter (pr-str user-filter))
 
  ;; Deserialize back to filter expression
  (def deserialized-filter (zt/read-string serialized-filter))
 
  ;; The deserialized expression works exactly like the original
  (def pred (ze/compile-expression deserialized-filter))
  (pred {:age 25 :status "active" :orders [{:amount 150}]}) ; => true
 
Important: Always use zeef.types/read-string instead of
clojure.edn/read-string when deserializing, as it includes
the necessary reader for the #zeef/field tagged literal.
 
 
Examples
------------------------------------------------------------
 
All examples below demonstrate how filter expressions are
compiled into predicate functions using the zeef.eval module.
 
 
Basic Filtering
---------------
 
  (require '[zeef.core :as zf]
           '[zeef.eval :as ze])
 
  ;; Define a filter expression for young adults in tech
  (def young-tech-filter
    (zf/z-and
      (zf/z-between? (zf/field :age) 18 35)
      (zf/z-= (zf/field :industry) "tech")))
 
  ;; Compile the expression to a predicate function
  (def predicate (ze/compile-expression young-tech-filter))
 
  ;; Sample data
  (def people
    [{:name "Alice" :age 25 :industry "tech"}
     {:name "Bob" :age 45 :industry "finance"}
     {:name "Charlie" :age 30 :industry "tech"}
     {:name "Diana" :age 22 :industry "healthcare"}])
 
  ;; Apply the compiled predicate function to filter data
  (filter predicate people)
  ;; => ({:name "Alice", :age 25, :industry "tech"}
  ;;     {:name "Charlie", :age 30, :industry "tech"})
 
 
Complex Filtering with Nested Data
-----------------------------------
 
  ;; Define a complex filter expression with nested queries
  (def premium-user-filter
    (zf/z-or
      ;; Has active premium subscription
      (zf/z-satisfies? (zf/field :subscription)
                       (zf/z-and
                         (zf/z-= (zf/field :type) "premium")
                         (zf/z-= (zf/field :status) "active")))
      ;; Has any order > $500
      (zf/z-some? (zf/field :orders)
                  (zf/z-> (zf/field :amount) 500))))
 
  (def users
    [{:name "Alice"
      :subscription {:type "premium" :status "active"}
      :orders [{:amount 50} {:amount 200}]}
     {:name "Bob" 
      :subscription {:type "basic" :status "active"}
      :orders [{:amount 600} {:amount 100}]}
     {:name "Charlie"
      :subscription {:type "premium" :status "expired"}
      :orders [{:amount 25}]}])
 
  ;; Compile the expression to a predicate function
  (def premium-predicate (ze/compile-expression premium-user-filter))
 
  ;; Apply the predicate to filter users
  (filter premium-predicate users)
  ;; => Both Alice (premium subscription) and Bob (high-value order)
 
 
Function Reference
------------------------------------------------------------
 
The following shows which argument types are supported for
each zeef expression builder function:
 
 
Nil Test
--------
 
z-nil?     Field     -           -         Tests if field is nil
 
 
Comparison Operations
---------------------
 
z-<        Field/Value  Field/Value  -      Less than
z-<=       Field/Value  Field/Value  -      Less than or equal
z-=        Field/Value  Field/Value  -      Equal to
z->        Field/Value  Field/Value  -      Greater than
z->=       Field/Value  Field/Value  -      Greater than or equal
 
 
String Operations
-----------------
 
z-starts-with? Field/String Field/String -  String starts with prefix
z-ends-with?   Field/String Field/String -  String ends with suffix
z-includes?    Field/String Field/String -  String contains substring
 
 
Collection Operations
---------------------
 
z-in?      Field/Value  Collection   -      Value is in collection
 
 
Range Operations
----------------
 
z-between? Field/Value  Field/Value  Field/Value  Value is within range (inclusive)
 
 
Logical Operations
------------------
 
z-not      Expression   -            -      Logical negation
z-and      Expression   Expression+  -      Logical AND (variadic)
z-or       Expression   Expression+  -      Logical OR (variadic)
 
 
Nested Queries
--------------
 
z-satisfies?  Field     Expression   -      Nested object satisfies expression
z-some?       Field     Expression   -      Any collection element satisfies expression
 
 
Argument Types
------------------------------------------------------------
 
  * Field: Created with (zf/field :keyword) - references a data field
  * Value: String, Integer, Float, Boolean, UUID, Inst, Keyword
  * String: String values specifically
  * Collection: Vector, list, or set of values
  * Expression: Any valid zeef filter expression
  * Expression+: One or more expressions (variadic)
 
 
References
------------------------------------------------------------
[1]: Clojars
 
 
WWW
------------------------------------------------------------
master.zip
GitHub