#[cfg(feature = "parser")]
use dnf::QueryBuilder;
use dnf::{DnfEvaluable, DnfQuery, FieldKind, Op, Value};
use std::collections::HashMap;
#[derive(DnfEvaluable, Debug)]
struct Document {
title: String,
metadata: HashMap<String, String>,
}
#[derive(DnfEvaluable, Debug)]
struct Config {
name: String,
settings: HashMap<String, i64>,
}
#[test]
fn test_hashmap_at_key_equals() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
metadata.insert("version".to_string(), "1.0".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::EQ, Value::at_key("author", "Alice")))
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_at_key_not_equals() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::NE, Value::at_key("author", "Bob")))
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_at_key_missing() {
let metadata = HashMap::new();
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::EQ, Value::at_key("author", "Alice")))
.build();
assert!(!query.evaluate(&doc));
}
#[test]
fn test_hashmap_at_key_missing_not_equals() {
let metadata = HashMap::new();
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::NE, Value::at_key("author", "Alice")))
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_at_key_numeric() {
let mut settings = HashMap::new();
settings.insert("timeout".to_string(), 30);
settings.insert("retries".to_string(), 3);
let config = Config {
name: "default".to_string(),
settings,
};
let query = DnfQuery::builder()
.or(|c| c.and("settings", Op::GT, Value::at_key("timeout", 20)))
.build();
assert!(query.evaluate(&config));
}
#[test]
fn test_hashmap_keys_contains() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
metadata.insert("version".to_string(), "1.0".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::CONTAINS, Value::keys("author")))
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_keys_not_contains() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::NOT_CONTAINS, Value::keys("missing_key")))
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_keys_all_of() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
metadata.insert("version".to_string(), "1.0".to_string());
metadata.insert("date".to_string(), "2024-01-01".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| {
c.and(
"metadata",
Op::ALL_OF,
Value::keys(vec!["author", "version"]),
)
})
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_keys_any_of() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| {
c.and(
"metadata",
Op::ANY_OF,
Value::keys(vec!["author", "editor", "reviewer"]),
)
})
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_values_contains() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
metadata.insert("editor".to_string(), "Bob".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::CONTAINS, Value::values("Alice")))
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_values_any_of() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| {
c.and(
"metadata",
Op::ANY_OF,
Value::values(vec!["Alice", "Charlie"]),
)
})
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_values_numeric() {
let mut settings = HashMap::new();
settings.insert("timeout".to_string(), 30);
settings.insert("retries".to_string(), 3);
let config = Config {
name: "default".to_string(),
settings,
};
let query = DnfQuery::builder()
.or(|c| c.and("settings", Op::CONTAINS, Value::values(30i64)))
.build();
assert!(query.evaluate(&config));
}
#[test]
fn test_hashmap_field_kind() {
let fields: Vec<_> = Document::fields().collect();
let title_field = fields.iter().find(|f| f.name == "title").unwrap();
let metadata_field = fields.iter().find(|f| f.name == "metadata").unwrap();
assert_eq!(title_field.kind, FieldKind::Scalar);
assert_eq!(metadata_field.kind, FieldKind::Map);
}
#[test]
fn test_hashmap_empty_keys_contains() {
let doc = Document {
title: "Empty".to_string(),
metadata: HashMap::new(),
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::CONTAINS, Value::keys("author")))
.build();
assert!(!query.evaluate(&doc));
}
#[test]
fn test_hashmap_empty_keys_not_contains() {
let doc = Document {
title: "Empty".to_string(),
metadata: HashMap::new(),
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::NOT_CONTAINS, Value::keys("author")))
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_combined_with_scalar_fields() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| {
c.and("title", Op::EQ, "README").and(
"metadata",
Op::EQ,
Value::at_key("author", "Alice"),
)
})
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_hashmap_or_with_keys_and_values() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::CONTAINS, Value::keys("author")))
.or(|c| c.and("metadata", Op::CONTAINS, Value::values("Bob")))
.build();
assert!(query.evaluate(&doc));
}
#[test]
fn test_value_display() {
let at_key = Value::at_key("author", "Alice");
let keys = Value::keys("author");
let values = Value::values("Alice");
assert!(at_key.to_string().contains("author"));
assert!(keys.to_string().contains("keys"));
assert!(values.to_string().contains("values"));
}
#[cfg(feature = "serde")]
#[test]
fn test_value_serde_roundtrip() {
let values = vec![
Value::at_key("author", "Alice"),
Value::keys("version"),
Value::values(vec!["v1", "v2"]),
];
for value in values {
let json = serde_json::to_string(&value).unwrap();
let parsed: Value = serde_json::from_str(&json).unwrap();
assert_eq!(value, parsed, "Roundtrip failed for: {:?}", value);
}
}
#[cfg(feature = "parser")]
mod parser_tests {
use super::*;
#[test]
fn test_parse_at_key_equals() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = QueryBuilder::from_query::<Document>(r#"metadata["author"] == "Alice""#)
.expect("should parse");
assert!(query.evaluate(&doc));
}
#[test]
fn test_parse_at_key_not_equals() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = QueryBuilder::from_query::<Document>(r#"metadata["author"] != "Bob""#)
.expect("should parse");
assert!(query.evaluate(&doc));
}
#[test]
fn test_parse_keys_contains() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = QueryBuilder::from_query::<Document>(r#"metadata.@keys CONTAINS "author""#)
.expect("should parse");
assert!(query.evaluate(&doc));
}
#[test]
fn test_parse_keys_not_contains() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query =
QueryBuilder::from_query::<Document>(r#"metadata.@keys NOT CONTAINS "missing""#)
.expect("should parse");
assert!(query.evaluate(&doc));
}
#[test]
fn test_parse_values_contains() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = QueryBuilder::from_query::<Document>(r#"metadata.@values CONTAINS "Alice""#)
.expect("should parse");
assert!(query.evaluate(&doc));
}
#[test]
fn test_parse_combined_query() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query = QueryBuilder::from_query::<Document>(
r#"title == "README" AND metadata["author"] == "Alice""#,
)
.expect("should parse");
assert!(query.evaluate(&doc));
}
#[test]
fn test_parse_keys_any_of() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
let doc = Document {
title: "README".to_string(),
metadata,
};
let query =
QueryBuilder::from_query::<Document>(r#"metadata.@keys IN ["author", "editor"]"#)
.expect("should parse");
assert!(query.evaluate(&doc));
}
#[test]
fn test_parse_error_non_map_with_bracket() {
let result = QueryBuilder::from_query::<Document>(r#"title["key"] == "value""#);
assert!(result.is_err());
}
#[test]
fn test_parse_error_non_map_with_at_keys() {
let result = QueryBuilder::from_query::<Document>(r#"title.@keys CONTAINS "x""#);
assert!(result.is_err());
}
}
#[test]
fn test_validate_map_field_with_map_value() {
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::EQ, Value::at_key("author", "Alice")))
.build();
let result = query.validate::<Document>();
assert!(result.is_ok());
}
#[test]
fn test_validate_map_field_with_keys() {
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::CONTAINS, Value::keys("author")))
.build();
let result = query.validate::<Document>();
assert!(result.is_ok());
}
#[test]
fn test_validate_map_field_with_values() {
let query = DnfQuery::builder()
.or(|c| c.and("metadata", Op::CONTAINS, Value::values("Alice")))
.build();
let result = query.validate::<Document>();
assert!(result.is_ok());
}
#[test]
fn test_validate_non_map_field_with_map_value_fails() {
let query = DnfQuery::builder()
.or(|c| c.and("title", Op::EQ, Value::at_key("key", "value")))
.build();
let result = query.validate::<Document>();
assert!(result.is_err());
let err = result.unwrap_err();
match err {
dnf::DnfError::InvalidMapTarget {
field_name,
field_kind,
} => {
assert_eq!(&*field_name, "title");
assert_eq!(field_kind, FieldKind::Scalar);
}
_ => panic!("Expected InvalidMapTarget error, got {:?}", err),
}
}
#[test]
fn test_validate_non_map_field_with_keys_fails() {
let query = DnfQuery::builder()
.or(|c| c.and("title", Op::CONTAINS, Value::keys("x")))
.build();
let result = query.validate::<Document>();
assert!(result.is_err());
}
#[test]
fn test_validate_scalar_field_ok() {
let query = DnfQuery::builder()
.or(|c| c.and("title", Op::EQ, "README"))
.build();
let result = query.validate::<Document>();
assert!(result.is_ok());
}
#[test]
fn test_validate_unknown_field_fails() {
let query = DnfQuery::builder()
.or(|c| c.and("unknown_field", Op::EQ, "value"))
.build();
let result = query.validate::<Document>();
assert!(result.is_err());
let err = result.unwrap_err();
match err {
dnf::DnfError::UnknownField { field_name, .. } => {
assert_eq!(&*field_name, "unknown_field");
}
_ => panic!("Expected UnknownField error, got {:?}", err),
}
}
#[derive(DnfEvaluable, Debug)]
struct Address {
city: String,
zip: String,
}
#[derive(DnfEvaluable, Debug)]
struct Person {
name: String,
#[dnf(nested)]
address: Address,
}
#[test]
fn test_validate_field_names() {
let test_cases: Vec<(&str, bool, &str)> = vec![
("name", true, "valid root field"),
("address.city", true, "valid nested field"),
("address.zip", true, "valid nested field zip"),
("unknown", false, "unknown root field"),
("unknown.city", false, "unknown nested root"),
("address.unknown", true, "unknown nested leaf (root valid)"),
];
for (field, expected_ok, desc) in test_cases {
let query = DnfQuery::builder()
.or(|c| c.and(field, Op::EQ, "test"))
.build();
let result = query.validate::<Person>();
assert_eq!(result.is_ok(), expected_ok, "Failed: {}", desc);
}
}
#[test]
fn test_validate_custom_ops() {
let test_cases: Vec<(bool, bool, &str)> = vec![
(true, true, "registered custom op"),
(false, false, "unregistered custom op"),
];
for (has_registration, expected_ok, desc) in test_cases {
let mut builder = DnfQuery::builder();
if has_registration {
builder = builder.with_custom_op(
"IS_VALID",
true,
|field, _| matches!(field, Value::String(s) if !s.is_empty()),
);
}
let query = builder
.or(|c| c.and("title", Op::custom("IS_VALID"), Value::None))
.build();
let result = query.validate::<Document>();
assert_eq!(result.is_ok(), expected_ok, "Failed: {}", desc);
}
}
#[test]
fn test_validate_error_types() {
enum ExpectedError {
UnknownField(&'static str),
UnregisteredCustomOp(&'static str),
InvalidMapTarget(&'static str),
}
let test_cases: Vec<(DnfQuery, ExpectedError, &str)> = vec![
(
DnfQuery::builder()
.or(|c| c.and("unknown", Op::EQ, "x"))
.build(),
ExpectedError::UnknownField("unknown"),
"unknown field error",
),
(
DnfQuery::builder()
.or(|c| c.and("title", Op::custom("MISSING"), Value::None))
.build(),
ExpectedError::UnregisteredCustomOp("MISSING"),
"unregistered custom op error",
),
(
DnfQuery::builder()
.or(|c| c.and("title", Op::EQ, Value::at_key("k", "v")))
.build(),
ExpectedError::InvalidMapTarget("title"),
"map target on non-map field",
),
];
for (query, expected_error, desc) in test_cases {
let result = query.validate::<Document>();
assert!(result.is_err(), "Expected error for: {}", desc);
let err = result.unwrap_err();
match (&expected_error, &err) {
(ExpectedError::UnknownField(name), dnf::DnfError::UnknownField { field_name, .. }) => {
assert_eq!(&**field_name, *name, "Wrong field name for: {}", desc);
}
(
ExpectedError::UnregisteredCustomOp(name),
dnf::DnfError::UnregisteredCustomOp { operator_name },
) => {
assert_eq!(&**operator_name, *name, "Wrong op name for: {}", desc);
}
(
ExpectedError::InvalidMapTarget(name),
dnf::DnfError::InvalidMapTarget { field_name, .. },
) => {
assert_eq!(&**field_name, *name, "Wrong field for: {}", desc);
}
_ => panic!("Wrong error type for {}: got {:?}", desc, err),
}
}
}
#[test]
fn test_validate_chained_api() {
let query = DnfQuery::builder()
.or(|c| c.and("title", Op::EQ, "README"))
.build()
.validate::<Document>()
.expect("should validate");
assert!(query.uses_field("title"));
}