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