§DNF Query Library
Build and evaluate DNF (Disjunctive Normal Form) queries against Rust structs.
DNF queries are OR-ed ANDs: (a AND b) OR (c AND d) OR ...
§Quick Start
use dnf::{DnfEvaluable, DnfQuery, Op};
#[derive(DnfEvaluable)]
struct User { age: u32, premium: bool }
let query = DnfQuery::builder()
.or(|c| c.and("age", Op::GTE, 18))
.or(|c| c.and("premium", Op::EQ, true))
.build();
let user = User { age: 25, premium: false };
assert!(query.evaluate(&user));
§Validation
The builder API doesn’t check field names at compile time — typos silently return false.
Use validate() to catch mistakes early:
let query = DnfQuery::builder()
.or(|c| c.and("age", Op::GT, 18))
.validate::<User>() .unwrap()
.build();
Or use the parser — it validates automatically:
ⓘlet query = DnfQuery::parse::<User>("age > 18")?;
§Features
| Feature | What it does |
derive | #[derive(DnfEvaluable)] macro (default) |
serde | Serialization support |
parser | Parse queries from strings |
§Operators
| Category | Operators |
| Comparison | EQ NE GT GTE LT LTE |
| String | CONTAINS STARTS_WITH ENDS_WITH (+ NOT variants) |
| Collection | ANY_OF ALL_OF (+ NOT variants) |
| Range | BETWEEN NOT_BETWEEN |
| Custom | Op::custom("NAME") |
§Collections & Range
use dnf::{DnfEvaluable, DnfQuery, Op, Value};
#[derive(DnfEvaluable)]
struct User { tags: Vec<String>, score: f64 }
let q = DnfQuery::builder()
.or(|c| c.and("tags", Op::CONTAINS, "rust"))
.or(|c| c.and("score", Op::BETWEEN, vec![60.0, 100.0]))
.build();
§Custom Operators
use dnf::{DnfQuery, Op, Value};
let q = DnfQuery::builder()
.with_custom_op("IS_ADULT", true, |f, _| matches!(f, Value::Uint(n) if *n >= 18))
.or(|c| c.and("age", Op::custom("IS_ADULT"), Value::None))
.build();
§Nested Structs
| Field type | Attribute | Query syntax |
| Single struct | #[dnf(nested)] required | "address.city" |
Vec<T> | Auto-detected | "offices.city" (any match) |
HashMap | N/A | Value::at_key("k", "v") |
use dnf::{DnfEvaluable, DnfQuery, Op};
#[derive(DnfEvaluable)]
struct Address { city: String }
#[derive(DnfEvaluable)]
struct User {
#[dnf(nested)] address: Address,
offices: Vec<Address>, }
§Derive Attributes
| Attribute | What it does |
#[dnf(rename = "x")] | Use different name in queries |
#[dnf(skip)] | Exclude field from queries |
#[dnf(nested)] | Enable dot notation for nested struct |
§Map Queries
use dnf::{DnfEvaluable, DnfQuery, Op, Value};
use std::collections::HashMap;
#[derive(DnfEvaluable)]
struct Doc { meta: HashMap<String, String> }
let q = DnfQuery::builder()
.or(|c| c.and("meta", Op::EQ, Value::at_key("author", "Alice")))
.build();
| Operation | Code |
| Key’s value | Value::at_key("key", value) |
| Key exists | Value::keys("key") |
| Value exists | Value::values(value) |
§Supported Types
| Category | Types |
| Integers | i8–i64, isize, u8–u64, usize |
| Floats | f32, f64 |
| Strings | String, &str, Box<str>, Cow<str> |
| Other | bool |
| Collections | Vec<T>, HashSet<T> |
| Maps | HashMap<String, V>, BTreeMap<String, V> |
| Wrappers | Option<T> |
§Manual Implementation
For computed fields or custom logic:
use dnf::{DnfEvaluable, DnfField, FieldInfo, Op, Value};
struct Doc { title: String, tags: Vec<String> }
impl DnfEvaluable for Doc {
fn evaluate_field(&self, field: &str, op: &Op, value: &Value) -> bool {
match field {
"title" => self.title.evaluate(op, value),
"tag_count" => self.tags.len().evaluate(op, value), _ => false,
}
}
fn fields() -> impl Iterator<Item = FieldInfo> {
[FieldInfo::new("title", "String"), FieldInfo::new("tag_count", "usize")].into_iter()
}
}