1use std::collections::HashSet;
2use std::fmt;
3use std::net::IpAddr;
4use std::result::Result as StdResult;
5use std::str::FromStr;
6
7use ipnet::IpNet;
8use ipnet_trie::IpnetTrie;
9use regex::{Regex, RegexSet};
10use serde_json::{Map, Value as JsonValue, json};
11
12use crate::types::{AsyncCheckFn, BoxFuture, CheckFn, DynError, MaybeSend, MaybeSync};
13use crate::{Error, JsonValueExt as _, Result, Value};
14
15pub enum Operator<Ctx: ?Sized> {
17 Equal(Value<'static>),
18 LessThan(Value<'static>),
19 LessThanOrEqual(Value<'static>),
20 GreaterThan(Value<'static>),
21 GreaterThanOrEqual(Value<'static>),
22 InSet(HashSet<Value<'static>>),
23 Regex(regex::Regex),
24 RegexSet(regex::RegexSet),
25 IpSet(IpnetTrie<()>),
26 Custom(Box<CheckFn<Ctx>>),
27 CustomAsync(Box<AsyncCheckFn<Ctx>>),
28}
29
30impl<Ctx: ?Sized> fmt::Debug for Operator<Ctx> {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 Operator::Equal(v) => write!(f, "Equal({v:?})"),
34 Operator::LessThan(v) => write!(f, "LessThan({v:?})"),
35 Operator::LessThanOrEqual(v) => write!(f, "LessThanOrEqual({v:?})"),
36 Operator::GreaterThan(v) => write!(f, "GreaterThan({v:?})"),
37 Operator::GreaterThanOrEqual(v) => write!(f, "GreaterThanOrEqual({v:?})"),
38 Operator::InSet(set) => write!(f, "InSet({set:?})",),
39 Operator::Regex(regex) => write!(f, "Regex({regex:?})"),
40 Operator::RegexSet(regex_set) => write!(f, "RegexSet({regex_set:?})"),
41 Operator::IpSet(_) => f.write_str("IpSet"),
42 Operator::Custom(_) => f.write_str("Custom"),
43 Operator::CustomAsync(_) => f.write_str("CustomAsync"),
44 }
45 }
46}
47
48impl<Ctx: ?Sized> Operator<Ctx> {
49 pub fn new<F>(func: F) -> Self
51 where
52 F: Fn(&Ctx, Value) -> StdResult<bool, DynError> + MaybeSend + MaybeSync + 'static,
53 {
54 Operator::Custom(Box::new(func))
55 }
56
57 pub fn new_async<F>(func: F) -> Self
59 where
60 F: for<'a> Fn(&'a Ctx, Value<'a>) -> BoxFuture<'a, StdResult<bool, DynError>>
61 + MaybeSend
62 + MaybeSync
63 + 'static,
64 {
65 Operator::CustomAsync(Box::new(func))
66 }
67}
68
69pub trait Matcher<Ctx: ?Sized>: MaybeSend + MaybeSync {
71 fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>>;
73
74 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
76 let _ = custom_ops;
77 json!({})
78 }
79}
80
81macro_rules! operator_error {
82 ($op:expr, $($arg:tt)*) => {
83 Err(Error::operator($op, format!($($arg)*)))
84 };
85}
86
87macro_rules! check_operator {
88 ($map:expr) => {{
89 let len = $map.len();
90 if len != 1 {
91 let msg = format!("operator object must have exactly one key (got {len})");
92 return Err(Error::json(msg));
93 }
94 $map.iter().next().unwrap()
95 }};
96}
97
98const IPV4_PATTERN: &str =
99 r"(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:/\d{1,2})?";
100const IPV6_PATTERN: &str = r"(?:[0-9a-f]{1,4}:){1,7}[0-9a-f]{0,4}|::(?:[0-9a-f:]{1,})?|[0-9a-f]{1,4}::(?:[0-9a-f:]{1,})?(?:/\d{1,3})?";
101
102pub struct DefaultMatcher;
104
105impl<Ctx: ?Sized> Matcher<Ctx> for DefaultMatcher {
106 fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
107 match value {
108 JsonValue::Null | JsonValue::Bool(_) | JsonValue::Number(_) | JsonValue::String(_) => {
109 Ok(Operator::Equal(Value::from(value).into_static()))
110 }
111 JsonValue::Array(seq) => Ok(Operator::InSet(Self::make_hashset(seq))),
112 JsonValue::Object(map) => Self::compile_op(map),
113 }
114 }
115
116 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
117 DefaultMatcher::json_schema(self, custom_ops)
118 }
119}
120
121impl DefaultMatcher {
122 fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
123 let (op, value) = check_operator!(map);
124 match (op.as_str(), value) {
125 ("<", v) => Ok(Operator::LessThan(Value::from(v).into_static())),
126 ("<=", v) => Ok(Operator::LessThanOrEqual(Value::from(v).into_static())),
127 (">", v) => Ok(Operator::GreaterThan(Value::from(v).into_static())),
128 (">=", v) => Ok(Operator::GreaterThanOrEqual(Value::from(v).into_static())),
129 ("==", v) => Ok(Operator::Equal(Value::from(v).into_static())),
130 ("in", JsonValue::Array(arr)) => Ok(Operator::InSet(Self::make_hashset(arr))),
131 ("in", _) => operator_error!(op, "expected array, got {}", value.type_name()),
132 ("re", JsonValue::String(pattern)) => {
133 let regex = Regex::new(pattern).map_err(|err| Error::operator(op, err))?;
134 Ok(Operator::Regex(regex))
135 }
136 ("re", JsonValue::Array(patterns)) => RegexMatcher::make_regex_set(patterns)
137 .map(Operator::RegexSet)
138 .map_err(|err| Error::operator(op, err)),
139 ("re", _) => operator_error!(op, "expected string or array, got {}", value.type_name()),
140 ("ip", JsonValue::Array(arr)) => IpMatcher::make_ipnet(arr)
141 .map(Operator::IpSet)
142 .map_err(|err| Error::operator(op, err)),
143 ("ip", _) => operator_error!(op, "expected array, got {}", value.type_name()),
144 _ => Err(Error::UnknownOperator(op.clone())),
145 }
146 }
147
148 fn make_hashset(arr: &[JsonValue]) -> HashSet<Value<'static>> {
150 arr.iter().map(|v| Value::from(v).into_static()).collect()
151 }
152
153 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
155 let any_schema = json!({ "type": ["null", "boolean", "number", "string"] });
157 let any_array_schema = json!({ "type": "array", "items": any_schema });
158 let string_schema = json!({ "type": "string" });
159 let string_array_schema = json!({ "type": "array", "items": string_schema });
160 let ip_schema = json!({ "type": "string", "pattern": format!(r"^(?:{IPV4_PATTERN}|(?i:{IPV6_PATTERN}))") });
161 let ip_array_schema = json!({ "type": "array", "items": ip_schema });
162
163 let mut properties = Map::new();
165 properties.insert("<".to_string(), any_schema.clone());
166 properties.insert("<=".to_string(), any_schema.clone());
167 properties.insert(">".to_string(), any_schema.clone());
168 properties.insert(">=".to_string(), any_schema.clone());
169 properties.insert("==".to_string(), any_schema.clone());
170 properties.insert("in".to_string(), any_array_schema.clone());
171 properties.insert(
172 "re".to_string(),
173 json!({ "oneOf": [string_schema, string_array_schema] }),
174 );
175 properties.insert("ip".to_string(), ip_array_schema);
176
177 for (op, schema) in custom_ops {
179 properties.insert(op.to_string(), schema.clone());
180 }
181
182 json!({
183 "oneOf": [
184 any_schema,
185 any_array_schema,
186 {
188 "type": "object",
189 "properties": properties,
190 "additionalProperties": false,
191 "minProperties": 1,
192 "maxProperties": 1
193 }
194 ]
195 })
196 }
197}
198
199pub struct StringMatcher;
203
204impl<Ctx: ?Sized> Matcher<Ctx> for StringMatcher {
205 fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
206 match value {
207 JsonValue::String(s) => Ok(Operator::Equal(Value::from(s.clone()))),
208 JsonValue::Array(seq) => Ok(Operator::InSet(Self::make_hashset(seq)?)),
209 JsonValue::Object(map) => Self::compile_op(map),
210 _ => {
211 let msg = format!("unexpected JSON {}", value.type_name());
212 Err(Error::json(msg))
213 }
214 }
215 }
216
217 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
218 StringMatcher::json_schema(self, custom_ops)
219 }
220}
221
222impl StringMatcher {
223 fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
224 let (op, value) = check_operator!(map);
225 match (op.as_str(), value) {
226 ("<", JsonValue::String(s)) => Ok(Operator::LessThan(Value::from(s).into_static())),
227 ("<=", JsonValue::String(s)) => {
228 Ok(Operator::LessThanOrEqual(Value::from(s).into_static()))
229 }
230 (">", JsonValue::String(s)) => Ok(Operator::GreaterThan(Value::from(s).into_static())),
231 (">=", JsonValue::String(s)) => {
232 Ok(Operator::GreaterThanOrEqual(Value::from(s).into_static()))
233 }
234 ("==", JsonValue::String(s)) => Ok(Operator::Equal(Value::from(s).into_static())),
235 ("<" | "<=" | ">" | ">=" | "==", _) => {
236 operator_error!(op, "expected string, got {}", value.type_name())
237 }
238 ("in", JsonValue::Array(arr)) => Self::make_hashset(arr)
239 .map(Operator::InSet)
240 .map_err(|err| Error::operator(op, err)),
241 ("in", _) => operator_error!(op, "expected array, got {}", value.type_name()),
242 ("re", JsonValue::String(pattern)) => {
243 let regex = Regex::new(pattern).map_err(|err| Error::operator(op, err))?;
244 Ok(Operator::Regex(regex))
245 }
246 ("re", JsonValue::Array(patterns)) => RegexMatcher::make_regex_set(patterns)
247 .map(Operator::RegexSet)
248 .map_err(|err| Error::operator(op, err)),
249 ("re", _) => operator_error!(op, "expected string or array, got {}", value.type_name()),
250 _ => Err(Error::UnknownOperator(op.clone())),
251 }
252 }
253
254 fn make_hashset(arr: &[JsonValue]) -> Result<HashSet<Value<'static>>> {
256 arr.iter()
257 .map(|v| match v {
258 JsonValue::String(s) => Ok(Value::from(s.clone())),
259 _ => {
260 let msg = format!("got {} in string array", v.type_name());
261 Err(Error::json(msg))
262 }
263 })
264 .collect()
265 }
266
267 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
269 let string_schema = json!({ "type": "string" });
271 let string_array_schema = json!({ "type": "array", "items": string_schema });
272
273 let mut properties = Map::new();
275 properties.insert("<".to_string(), string_schema.clone());
276 properties.insert("<=".to_string(), string_schema.clone());
277 properties.insert(">".to_string(), string_schema.clone());
278 properties.insert(">=".to_string(), string_schema.clone());
279 properties.insert("==".to_string(), string_schema.clone());
280 properties.insert("in".to_string(), string_array_schema.clone());
281 properties.insert(
282 "re".to_string(),
283 json!({ "oneOf": [string_schema, string_array_schema] }),
284 );
285
286 for (op, schema) in custom_ops {
288 properties.insert(op.to_string(), schema.clone());
289 }
290
291 json!({
292 "oneOf": [
293 string_schema,
294 string_array_schema,
295 {
297 "type": "object",
298 "properties": properties,
299 "additionalProperties": false,
300 "minProperties": 1,
301 "maxProperties": 1
302 }
303 ]
304 })
305 }
306}
307
308pub struct RegexMatcher;
312
313impl<Ctx: ?Sized> Matcher<Ctx> for RegexMatcher {
314 fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
315 match value {
316 JsonValue::String(pattern) => Ok(Operator::Regex(Regex::new(pattern)?)),
317 JsonValue::Array(patterns) => Ok(Operator::RegexSet(Self::make_regex_set(patterns)?)),
318 JsonValue::Object(map) => Self::compile_op(map),
319 _ => {
320 let msg = format!("unexpected JSON {}", value.type_name());
321 Err(Error::json(msg))
322 }
323 }
324 }
325
326 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
327 RegexMatcher::json_schema(self, custom_ops)
328 }
329}
330
331impl RegexMatcher {
332 fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
333 let (op, _value) = check_operator!(map);
334 Err(Error::UnknownOperator(op.clone()))
335 }
336
337 fn make_regex_set(patterns: &[JsonValue]) -> Result<RegexSet> {
339 let patterns = (patterns.iter())
340 .map(|v| match v {
341 JsonValue::String(s) => Ok(s),
342 _ => {
343 let msg = format!("expected string, got {} in regex array", v.type_name());
344 Err(Error::json(msg))
345 }
346 })
347 .collect::<Result<Vec<_>>>()?;
348 Ok(RegexSet::new(&patterns)?)
349 }
350
351 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
353 let string_schema = json!({ "type": "string" });
355 let string_array_schema = json!({ "type": "array", "items": string_schema });
356
357 let mut properties = Map::new();
359
360 for (op, schema) in custom_ops {
362 properties.insert(op.to_string(), schema.clone());
363 }
364
365 json!({
366 "oneOf": [
367 string_schema,
368 string_array_schema,
369 {
371 "type": "object",
372 "properties": properties,
373 "additionalProperties": false,
374 "minProperties": 1,
375 "maxProperties": 1
376 }
377 ]
378 })
379 }
380}
381
382pub struct NumberMatcher;
386
387impl<Ctx: ?Sized> Matcher<Ctx> for NumberMatcher {
388 fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
389 match value {
390 JsonValue::Number(n) => Ok(Operator::Equal(Value::Number(n.clone()))),
391 JsonValue::Array(seq) => Ok(Operator::InSet(Self::make_hashset(seq)?)),
392 JsonValue::Object(map) => Self::compile_op(map),
393 _ => {
394 let msg = format!("unexpected JSON {}", value.type_name());
395 Err(Error::json(msg))
396 }
397 }
398 }
399
400 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
401 NumberMatcher::json_schema(self, custom_ops)
402 }
403}
404
405impl NumberMatcher {
406 fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
407 let (op, value) = check_operator!(map);
408 match (op.as_str(), value) {
409 ("<", JsonValue::Number(n)) => Ok(Operator::LessThan(Value::Number(n.clone()))),
410 ("<=", JsonValue::Number(n)) => Ok(Operator::LessThanOrEqual(Value::Number(n.clone()))),
411 (">", JsonValue::Number(n)) => Ok(Operator::GreaterThan(Value::Number(n.clone()))),
412 (">=", JsonValue::Number(n)) => {
413 Ok(Operator::GreaterThanOrEqual(Value::Number(n.clone())))
414 }
415 ("==", JsonValue::Number(n)) => Ok(Operator::Equal(Value::Number(n.clone()))),
416 ("<" | "<=" | ">" | ">=" | "==", _) => {
417 operator_error!(op, "expected number, got {}", value.type_name())
418 }
419 ("in", JsonValue::Array(seq)) => Self::make_hashset(seq)
420 .map(Operator::InSet)
421 .map_err(|err| Error::operator(op, err)),
422 ("in", _) => operator_error!(op, "expected array, got {}", value.type_name()),
423 _ => Err(Error::UnknownOperator(op.clone())),
424 }
425 }
426
427 fn make_hashset(arr: &[JsonValue]) -> Result<HashSet<Value<'static>>> {
429 arr.iter()
430 .map(|v| match v {
431 JsonValue::Number(n) => Ok(Value::Number(n.clone())),
432 _ => {
433 let msg = format!("got {} in number array", v.type_name());
434 Err(Error::json(msg))
435 }
436 })
437 .collect()
438 }
439
440 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
442 let number_schema = json!({ "type": "number" });
444 let number_array_schema = json!({ "type": "array", "items": number_schema });
445
446 let mut properties = Map::new();
448 properties.insert("<".to_string(), number_schema.clone());
449 properties.insert("<=".to_string(), number_schema.clone());
450 properties.insert(">".to_string(), number_schema.clone());
451 properties.insert(">=".to_string(), number_schema.clone());
452 properties.insert("==".to_string(), number_schema.clone());
453 properties.insert("in".to_string(), number_array_schema.clone());
454
455 for (op, schema) in custom_ops {
457 properties.insert(op.to_string(), schema.clone());
458 }
459
460 json!({
461 "oneOf": [
462 number_schema,
463 number_array_schema,
464 {
466 "type": "object",
467 "properties": properties,
468 "additionalProperties": false,
469 "minProperties": 1,
470 "maxProperties": 1
471 }
472 ]
473 })
474 }
475}
476
477pub struct BoolMatcher;
481
482impl<Ctx: ?Sized> Matcher<Ctx> for BoolMatcher {
483 fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
484 match value {
485 JsonValue::Bool(b) => Ok(Operator::Equal(Value::Bool(*b))),
486 _ => {
487 let msg = format!("expected boolean, got {}", value.type_name());
488 Err(Error::json(msg))
489 }
490 }
491 }
492
493 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
494 BoolMatcher::json_schema(self, custom_ops)
495 }
496}
497
498impl BoolMatcher {
499 fn json_schema(&self, _custom_ops: &[(&str, JsonValue)]) -> JsonValue {
501 json!({ "type": "boolean" })
503 }
504}
505
506pub struct IpMatcher;
510
511impl<Ctx: ?Sized> Matcher<Ctx> for IpMatcher {
512 fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
513 match value {
514 JsonValue::String(_) => {
515 let value_slice = std::slice::from_ref(value);
516 Ok(Operator::IpSet(Self::make_ipnet(value_slice)?))
517 }
518 JsonValue::Array(addrs) => Ok(Operator::IpSet(Self::make_ipnet(addrs)?)),
519 JsonValue::Object(map) => Self::compile_op(map),
520 _ => {
521 let msg = format!("unexpected JSON {}", value.type_name());
522 Err(Error::json(msg))
523 }
524 }
525 }
526
527 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
528 IpMatcher::json_schema(self, custom_ops)
529 }
530}
531
532impl IpMatcher {
533 fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
534 let (op, _value) = check_operator!(map);
535 Err(Error::UnknownOperator(op.clone()))
536 }
537
538 fn make_ipnet(addrs: &[JsonValue]) -> Result<IpnetTrie<()>> {
540 let mut table = IpnetTrie::new();
541 for addr in addrs {
542 let addr = match addr {
543 JsonValue::String(s) => s,
544 _ => {
545 let msg = format!("got {} in ipnet array", addr.type_name());
546 return Err(Error::json(msg));
547 }
548 };
549 let net = if addr.contains('/') {
550 IpNet::from_str(addr)?
551 } else {
552 IpNet::from(IpAddr::from_str(addr)?)
553 };
554 table.insert(net, ());
555 }
556 Ok(table)
557 }
558
559 fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
561 let ip_pattern = format!(r"^(?:{IPV4_PATTERN}|(?i:{IPV6_PATTERN}))");
563
564 let ip_schema = json!({ "type": "string", "pattern": ip_pattern });
566 let ip_array_schema = json!({ "type": "array", "items": ip_schema });
567
568 let mut properties = Map::new();
570
571 for (op, schema) in custom_ops {
573 properties.insert(op.to_string(), schema.clone());
574 }
575
576 json!({
577 "oneOf": [
578 ip_schema,
579 ip_array_schema,
580 {
582 "type": "object",
583 "properties": properties,
584 "additionalProperties": false,
585 "minProperties": 1,
586 "maxProperties": 1
587 }
588 ]
589 })
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use std::any::{Any, TypeId};
596
597 use serde_json::json;
598
599 use super::*;
600
601 #[track_caller]
603 fn assert_compile_error<M>(matcher: M, value: JsonValue, expected_msg: &str)
604 where
605 M: Matcher<()>,
606 {
607 let result = matcher.compile(&value);
608 assert!(result.is_err());
609 let err = result.unwrap_err().to_string();
610 assert!(
611 err.contains(expected_msg),
612 "Expected error message to contain `{expected_msg}` but got `{err}`",
613 );
614 }
615
616 #[track_caller]
618 fn compile_op<T: Any>(matcher: impl Matcher<()>, value: JsonValue) -> (T, &'static str) {
619 let type_id = TypeId::of::<T>();
620 let op = (matcher.compile(&value)).expect("Failed to compile operator");
621 let (boxed, variant): (Box<dyn Any>, &'static str) = match op {
622 Operator::Equal(val) if type_id == val.type_id() => (Box::new(val), "Equal"),
623 Operator::LessThan(val) if type_id == val.type_id() => (Box::new(val), "LessThan"),
624 Operator::LessThanOrEqual(val) if type_id == val.type_id() => {
625 (Box::new(val), "LessThanOrEqual")
626 }
627 Operator::GreaterThan(val) if type_id == val.type_id() => {
628 (Box::new(val), "GreaterThan")
629 }
630 Operator::GreaterThanOrEqual(val) if type_id == val.type_id() => {
631 (Box::new(val), "GreaterThanOrEqual")
632 }
633 Operator::InSet(val) if type_id == val.type_id() => (Box::new(val), "InSet"),
634 Operator::Regex(val) if type_id == val.type_id() => (Box::new(val), "Regex"),
635 Operator::RegexSet(val) if type_id == val.type_id() => (Box::new(val), "RegexSet"),
636 Operator::IpSet(val) if type_id == TypeId::of::<IpnetTrie<()>>() => {
637 (Box::new(val), "IpSet")
638 }
639 op => panic!("Unexpected operator type or value type mismatch: {op:?}"),
640 };
641 (*boxed.downcast::<T>().unwrap(), variant)
643 }
644
645 #[test]
646 fn test_default_matcher() {
647 #[track_caller]
648 fn assert_default_compile_error(value: JsonValue, expected_msg: &str) {
649 assert_compile_error(DefaultMatcher, value, expected_msg);
650 }
651
652 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!(null));
656 assert_eq!(variant, "Equal");
657 assert_eq!(v, Value::None);
658
659 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!(true));
661 assert_eq!(variant, "Equal");
662 assert_eq!(v, Value::Bool(true));
663
664 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!(42));
666 assert_eq!(variant, "Equal");
667 assert_eq!(v, Value::from(42));
668
669 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!("hello"));
671 assert_eq!(variant, "Equal");
672 assert_eq!(v, Value::from("hello"));
673
674 let (set, variant) =
676 compile_op::<HashSet<Value>>(DefaultMatcher, json!([1, "hello", true]));
677 assert_eq!(variant, "InSet");
678 assert_eq!(set.len(), 3);
679 assert!(set.contains(&Value::from(1)));
680 assert!(set.contains(&Value::from("hello")));
681 assert!(set.contains(&Value::Bool(true)));
682
683 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({"<": 100}));
687 assert_eq!(variant, "LessThan");
688 assert_eq!(v, Value::from(100));
689
690 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({"<=": "hello"}));
692 assert_eq!(variant, "LessThanOrEqual");
693 assert_eq!(v, Value::from("hello"));
694
695 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({">": 100}));
697 assert_eq!(variant, "GreaterThan");
698 assert_eq!(v, Value::from(100));
699
700 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({">=": true}));
702 assert_eq!(variant, "GreaterThanOrEqual");
703 assert_eq!(v, Value::Bool(true));
704
705 let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({"==": null}));
707 assert_eq!(variant, "Equal");
708 assert_eq!(v, Value::None);
709
710 let (set, variant) =
712 compile_op::<HashSet<Value>>(DefaultMatcher, json!({"in": [1, "hello", true, null]}));
713 assert_eq!(variant, "InSet");
714 assert_eq!(set.len(), 4);
715 assert!(set.contains(&Value::from(1)));
716 assert!(set.contains(&Value::from("hello")));
717 assert!(set.contains(&Value::Bool(true)));
718 assert!(set.contains(&Value::None));
719
720 let (re, variant) = compile_op::<Regex>(DefaultMatcher, json!({"re": "^hello$"}));
722 assert_eq!(variant, "Regex");
723 assert!(re.is_match("hello"));
724 assert!(!re.is_match("hello world"));
725
726 let (re_set, variant) =
728 compile_op::<RegexSet>(DefaultMatcher, json!({"re": ["^hello$", "^world$"]}));
729 assert_eq!(variant, "RegexSet");
730 assert!(re_set.is_match("hello"));
731 assert!(re_set.is_match("world"));
732 assert!(!re_set.is_match("hello world"));
733
734 let (_, variant) = compile_op::<IpnetTrie<()>>(
736 DefaultMatcher,
737 json!({"ip": ["192.168.1.1", "10.0.0.0/8"]}),
738 );
739 assert_eq!(variant, "IpSet");
740
741 assert_default_compile_error(
743 json!({"in": true}),
744 "error in 'in' operator: expected array, got boolean",
745 );
746 assert_default_compile_error(
747 json!({"re": true}),
748 "error in 're' operator: expected string or array, got boolean",
749 );
750 assert_default_compile_error(
751 json!({"ip": true}),
752 "error in 'ip' operator: expected array, got boolean",
753 );
754 assert_default_compile_error(json!({"unknown": "value"}), "unknown operator 'unknown'");
755 }
756
757 #[test]
758 fn test_string_matcher() {
759 #[track_caller]
760 fn assert_str_compile_error(value: JsonValue, expected_msg: &str) {
761 assert_compile_error(StringMatcher, value, expected_msg);
762 }
763
764 let (s, variant) = compile_op::<Value>(StringMatcher, json!("hello"));
766 assert_eq!(variant, "Equal");
767 assert_eq!(s, Value::from("hello"));
768
769 let (set, variant) = compile_op::<HashSet<Value>>(StringMatcher, json!(["hello", "world"]));
771 assert_eq!(variant, "InSet");
772 assert_eq!(set.len(), 2);
773 assert!(set.contains(&Value::from("hello")));
774 assert!(set.contains(&Value::from("world")));
775
776 let (s, variant) = compile_op::<Value>(StringMatcher, json!({"<": "hello"}));
778 assert_eq!(variant, "LessThan");
779 assert_eq!(s, Value::from("hello"));
780
781 let (s, variant) = compile_op::<Value>(StringMatcher, json!({"<=": "hello"}));
782 assert_eq!(variant, "LessThanOrEqual");
783 assert_eq!(s, Value::from("hello"));
784
785 let (s, variant) = compile_op::<Value>(StringMatcher, json!({">": "hello"}));
786 assert_eq!(variant, "GreaterThan");
787 assert_eq!(s, Value::from("hello"));
788
789 let (s, variant) = compile_op::<Value>(StringMatcher, json!({">=": "hello"}));
790 assert_eq!(variant, "GreaterThanOrEqual");
791 assert_eq!(s, Value::from("hello"));
792
793 let (s, variant) = compile_op::<Value>(StringMatcher, json!({"==": "hello"}));
794 assert_eq!(variant, "Equal");
795 assert_eq!(s, Value::from("hello"));
796
797 let (set, variant) =
799 compile_op::<HashSet<Value>>(StringMatcher, json!({"in": ["hello", "world"]}));
800 assert_eq!(variant, "InSet");
801 assert_eq!(set.len(), 2);
802 assert!(set.contains(&Value::from("hello")));
803 assert!(set.contains(&Value::from("world")));
804
805 let (re, variant) = compile_op::<Regex>(StringMatcher, json!({"re": "^hello$"}));
807 assert_eq!(variant, "Regex");
808 assert!(re.is_match("hello"));
809
810 let (re, variant) =
812 compile_op::<RegexSet>(StringMatcher, json!({"re": ["^hello$", "^world$"]}));
813 assert_eq!(variant, "RegexSet");
814 assert!(re.is_match("hello"));
815 assert!(!re.is_match("hello world"));
816
817 assert_str_compile_error(json!(true), "unexpected JSON boolean");
819 assert_str_compile_error(json!({"in": true}), "expected array, got boolean");
820 assert_str_compile_error(
821 json!({"<": true}),
822 "error in '<' operator: expected string, got boolean",
823 );
824 assert_str_compile_error(
825 json!({"re": true}),
826 "error in 're' operator: expected string or array, got boolean",
827 );
828 assert_str_compile_error(json!({"unknown": "value"}), "unknown operator 'unknown'");
829 }
830
831 #[test]
832 fn test_regex_matcher() {
833 #[track_caller]
834 fn assert_regex_compile_error(value: JsonValue, expected_msg: &str) {
835 assert_compile_error(RegexMatcher, value, expected_msg);
836 }
837
838 let (re, variant) = compile_op::<Regex>(RegexMatcher, json!("^hello$"));
840 assert_eq!(variant, "Regex");
841 assert!(re.is_match("hello"));
842 assert!(!re.is_match("hello world"));
843
844 let (re_set, variant) = compile_op::<RegexSet>(RegexMatcher, json!(["^hello$", "^world$"]));
846 assert_eq!(variant, "RegexSet");
847 assert!(re_set.is_match("hello"));
848 assert!(re_set.is_match("world"));
849 assert!(!re_set.is_match("hello world"));
850
851 assert_regex_compile_error(json!(123), "unexpected JSON number");
853 assert_regex_compile_error(json!(true), "unexpected JSON boolean");
854 assert_regex_compile_error(json!({"invalid": "pattern"}), "unknown operator 'invalid'");
855 assert_regex_compile_error(json!("(invalid"), "regex parse error");
856 }
857
858 #[test]
859 fn test_number_matcher() {
860 #[track_caller]
861 fn assert_num_compile_error(value: JsonValue, expected_msg: &str) {
862 assert_compile_error(NumberMatcher, value, expected_msg);
863 }
864
865 let (n, variant) = compile_op::<Value>(NumberMatcher, json!(42));
867 assert_eq!(variant, "Equal");
868 assert_eq!(n, Value::from(42));
869
870 let (n, variant) = compile_op::<Value>(NumberMatcher, json!(3.14));
872 assert_eq!(variant, "Equal");
873 assert_eq!(n, Value::try_from(3.14).unwrap());
874
875 let (set, variant) = compile_op::<HashSet<Value>>(NumberMatcher, json!([1, 3]));
877 assert_eq!(variant, "InSet");
878 assert_eq!(set.len(), 2);
879 assert!(set.contains(&Value::from(1)));
880 assert!(!set.contains(&Value::from(2)));
881 assert!(set.contains(&Value::from(3)));
882
883 let (n, variant) = compile_op::<Value>(NumberMatcher, json!({"<": 100}));
885 assert_eq!(variant, "LessThan");
886 assert_eq!(n, Value::from(100));
887
888 let (n, variant) = compile_op::<Value>(NumberMatcher, json!({"<=": 100}));
889 assert_eq!(variant, "LessThanOrEqual");
890 assert_eq!(n, Value::from(100));
891
892 let (n, variant) = compile_op::<Value>(NumberMatcher, json!({">": 100}));
893 assert_eq!(variant, "GreaterThan");
894 assert_eq!(n, Value::from(100));
895
896 let (n, variant) = compile_op::<Value>(NumberMatcher, json!({">=": 100}));
897 assert_eq!(variant, "GreaterThanOrEqual");
898 assert_eq!(n, Value::from(100));
899
900 let (n, variant) = compile_op::<Value>(NumberMatcher, json!({"==": 100}));
901 assert_eq!(variant, "Equal");
902 assert_eq!(n, Value::from(100));
903
904 let (set, variant) = compile_op::<HashSet<Value>>(NumberMatcher, json!({"in": [1, 3]}));
906 assert_eq!(variant, "InSet");
907 assert_eq!(set.len(), 2);
908 assert!(set.contains(&Value::from(1)));
909 assert!(!set.contains(&Value::from(2)));
910 assert!(set.contains(&Value::from(3)));
911
912 let (set, variant) = compile_op::<HashSet<Value>>(NumberMatcher, json!({"in": [1.5, 3.5]}));
914 assert_eq!(variant, "InSet");
915 assert_eq!(set.len(), 2);
916 assert!(set.contains(&Value::try_from(1.5).unwrap()));
917 assert!(!set.contains(&Value::try_from(2.5).unwrap()));
918 assert!(set.contains(&Value::try_from(3.5).unwrap()));
919
920 assert_num_compile_error(json!("string"), "unexpected JSON string");
922 assert_num_compile_error(json!(true), "unexpected JSON boolean");
923 assert_num_compile_error(
924 json!({"<": "string"}),
925 "error in '<' operator: expected number, got string",
926 );
927 assert_num_compile_error(
928 json!({"in": true}),
929 "error in 'in' operator: expected array, got boolean",
930 );
931 assert_num_compile_error(
932 json!({"in": [1, "string"]}),
933 "error in 'in' operator: got string in number array",
934 );
935 assert_num_compile_error(json!({"unknown": 100}), "unknown operator 'unknown'");
936 }
937
938 #[test]
939 fn test_bool_matcher() {
940 let (b, variant) = compile_op::<Value>(BoolMatcher, json!(true));
942 assert_eq!(variant, "Equal");
943 assert_eq!(b, Value::Bool(true));
944
945 let (b, variant) = compile_op::<Value>(BoolMatcher, json!(false));
947 assert_eq!(variant, "Equal");
948 assert_eq!(b, Value::Bool(false));
949
950 assert_compile_error(BoolMatcher, json!("string"), "expected boolean, got string");
952 assert_compile_error(BoolMatcher, json!(123), "expected boolean, got number");
953 assert_compile_error(BoolMatcher, json!([true]), "expected boolean, got array");
954 assert_compile_error(
955 BoolMatcher,
956 json!({"==": true}),
957 "expected boolean, got object",
958 );
959 }
960
961 #[test]
962 fn test_ip_matcher() {
963 fn ip(s: &str) -> IpNet {
965 IpNet::from(IpAddr::from_str(s).unwrap())
966 }
967
968 #[track_caller]
969 fn assert_ip_compile_error(value: JsonValue, expected_msg: &str) {
970 assert_compile_error(IpMatcher, value, expected_msg);
971 }
972
973 #[track_caller]
974 fn assert_ip_matches(trie: &IpnetTrie<()>, addr: &str) {
975 let ip = ip(addr);
976 assert!(trie.longest_match(&ip).is_some(), "{addr} should match");
977 }
978
979 #[track_caller]
980 fn assert_ip_not_matches(trie: &IpnetTrie<()>, addr: &str) {
981 let ip = ip(addr);
982 assert!(trie.longest_match(&ip).is_none(), "{addr} should not match");
983 }
984
985 let (trie, variant) = compile_op::<IpnetTrie<()>>(IpMatcher, json!("192.168.1.1"));
987 assert_eq!(variant, "IpSet");
988 assert_ip_matches(&trie, "192.168.1.1");
989 assert_ip_not_matches(&trie, "192.168.1.2");
990
991 let (trie, variant) = compile_op::<IpnetTrie<()>>(IpMatcher, json!("192.168.1.0/24"));
993 assert_eq!(variant, "IpSet");
994 assert_ip_matches(&trie, "192.168.1.1");
995 assert_ip_matches(&trie, "192.168.1.254");
996 assert_ip_not_matches(&trie, "192.168.2.1");
997
998 let (trie, variant) =
1000 compile_op::<IpnetTrie<()>>(IpMatcher, json!(["192.168.1.1", "10.0.0.0/8"]));
1001 assert_eq!(variant, "IpSet");
1002 assert_ip_matches(&trie, "192.168.1.1");
1003 assert_ip_matches(&trie, "10.1.2.3");
1004 assert_ip_not_matches(&trie, "11.0.0.1");
1005
1006 let (trie, variant) =
1008 compile_op::<IpnetTrie<()>>(IpMatcher, json!(["2001:db8::1", "2001:db8::/32"]));
1009 assert_eq!(variant, "IpSet");
1010 assert_ip_matches(&trie, "2001:db8:1::1");
1011
1012 assert_ip_compile_error(json!("invalid-ip"), "invalid IP address syntax");
1014 assert_ip_compile_error(json!(123), "unexpected JSON number");
1015 assert_ip_compile_error(json!({"invalid": "pattern"}), "unknown operator 'invalid'");
1016 }
1017}