1use std::cmp::Ordering;
90
91use serde_json::{Number, Value};
92
93pub const MAX_EXPRESSION_LEN: usize = 512;
99
100#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
107#[error("{message}")]
108pub struct ExprError {
109 message: String,
110}
111
112impl ExprError {
113 fn new(message: impl Into<String>) -> Self {
114 Self {
115 message: message.into(),
116 }
117 }
118
119 #[must_use]
121 pub fn message(&self) -> &str {
122 &self.message
123 }
124}
125
126#[derive(Clone, Debug, PartialEq)]
132pub struct Expr {
133 root: Bool,
134}
135
136impl Expr {
137 #[must_use]
145 pub fn eval(&self, value: &Value) -> bool {
146 eval_bool(&self.root, value)
147 }
148
149 #[must_use]
163 pub fn paths(&self) -> Vec<&[Segment]> {
164 let mut found = Vec::new();
165 collect_paths(&self.root, &mut found);
166 found
167 }
168}
169
170fn collect_paths<'a>(node: &'a Bool, found: &mut Vec<&'a [Segment]>) {
172 match node {
173 Bool::Or(left, right) | Bool::And(left, right) => {
174 collect_paths(left, found);
175 collect_paths(right, found);
176 }
177 Bool::Not(inner) => collect_paths(inner, found),
178 Bool::Compare { left, right, .. } => {
179 if let Operand::Path(segments) = left {
180 found.push(segments);
181 }
182 if let Operand::Path(segments) = right {
183 found.push(segments);
184 }
185 }
186 Bool::Truthy(operand) => {
187 if let Operand::Path(segments) = operand {
188 found.push(segments);
189 }
190 }
191 }
192}
193
194#[must_use]
211pub fn compare(left: &Value, right: &Value) -> Option<Ordering> {
212 order(left, right)
213}
214
215#[derive(Clone, Debug, PartialEq)]
226pub struct Reference {
227 segments: Vec<Segment>,
228}
229
230impl Reference {
231 #[must_use]
238 pub fn resolve<'a>(&self, value: &'a Value) -> Option<&'a Value> {
239 resolve_path(&self.segments, value)
240 }
241
242 #[must_use]
250 pub fn segments(&self) -> &[Segment] {
251 &self.segments
252 }
253}
254
255pub fn parse_reference(input: &str) -> Result<Reference, ExprError> {
267 let chars: Vec<char> = input.chars().collect();
268 if chars.len() > MAX_EXPRESSION_LEN {
269 return Err(ExprError::new(format!(
270 "reference is {} characters long, which exceeds the {MAX_EXPRESSION_LEN}-character limit",
271 chars.len()
272 )));
273 }
274
275 let tokens = lex(&chars)?;
276 let mut parser = Parser {
277 tokens: &tokens,
278 pos: 0,
279 end: chars.len(),
280 };
281 let operand = parser.parse_operand()?;
282 parser.expect_end()?;
283 match operand {
284 Operand::Path(segments) => Ok(Reference { segments }),
285 Operand::Literal(_) => Err(ExprError::new(
286 "a reference must be a path into the routed value, not a literal",
287 )),
288 }
289}
290
291#[derive(Clone, Debug, PartialEq)]
293enum Bool {
294 Or(Box<Bool>, Box<Bool>),
295 And(Box<Bool>, Box<Bool>),
296 Not(Box<Bool>),
297 Compare {
298 left: Operand,
299 op: CmpOp,
300 right: Operand,
301 },
302 Truthy(Operand),
305}
306
307#[derive(Clone, Debug, PartialEq)]
310enum Operand {
311 Literal(Value),
312 Path(Vec<Segment>),
313}
314
315#[derive(Clone, Debug, PartialEq)]
321pub enum Segment {
322 Key(String),
324 Index(usize),
326}
327
328#[derive(Clone, Copy, Debug, PartialEq)]
330enum CmpOp {
331 Eq,
332 Ne,
333 Lt,
334 Le,
335 Gt,
336 Ge,
337}
338
339pub fn parse(input: &str) -> Result<Expr, ExprError> {
355 let chars: Vec<char> = input.chars().collect();
356 if chars.len() > MAX_EXPRESSION_LEN {
357 return Err(ExprError::new(format!(
358 "expression is {} characters long, which exceeds the {MAX_EXPRESSION_LEN}-character limit",
359 chars.len()
360 )));
361 }
362
363 let tokens = lex(&chars)?;
364 let mut parser = Parser {
365 tokens: &tokens,
366 pos: 0,
367 end: chars.len(),
368 };
369 let root = parser.parse_or()?;
370 parser.expect_end()?;
371 Ok(Expr { root })
372}
373
374#[derive(Clone, Debug, PartialEq)]
376struct Spanned {
377 token: Token,
378 at: usize,
379}
380
381#[derive(Clone, Debug, PartialEq)]
383enum Token {
384 Ident(String),
385 Number(Number),
386 Str(String),
387 True,
388 False,
389 Null,
390 Dot,
391 LParen,
392 RParen,
393 Bang,
394 AndAnd,
395 OrOr,
396 EqEq,
397 NotEq,
398 Lt,
399 Le,
400 Gt,
401 Ge,
402}
403
404fn is_ident_start(c: char) -> bool {
405 c.is_ascii_alphabetic() || c == '_'
406}
407
408fn is_ident_continue(c: char) -> bool {
409 c.is_ascii_alphanumeric() || c == '_'
410}
411
412fn lex(chars: &[char]) -> Result<Vec<Spanned>, ExprError> {
415 let mut tokens = Vec::new();
416 let mut i = 0;
417 let len = chars.len();
418
419 while i < len {
420 let c = chars[i];
421 let at = i;
422
423 if c.is_whitespace() {
424 i += 1;
425 continue;
426 }
427
428 match c {
429 '(' => {
430 tokens.push(Spanned {
431 token: Token::LParen,
432 at,
433 });
434 i += 1;
435 }
436 ')' => {
437 tokens.push(Spanned {
438 token: Token::RParen,
439 at,
440 });
441 i += 1;
442 }
443 '.' => {
444 tokens.push(Spanned {
445 token: Token::Dot,
446 at,
447 });
448 i += 1;
449 }
450 '!' => {
451 if i + 1 < len && chars[i + 1] == '=' {
452 tokens.push(Spanned {
453 token: Token::NotEq,
454 at,
455 });
456 i += 2;
457 } else {
458 tokens.push(Spanned {
459 token: Token::Bang,
460 at,
461 });
462 i += 1;
463 }
464 }
465 '=' => {
466 if i + 1 < len && chars[i + 1] == '=' {
467 tokens.push(Spanned {
468 token: Token::EqEq,
469 at,
470 });
471 i += 2;
472 } else {
473 return Err(at_char(at, "a lone `=`; did you mean `==`?"));
474 }
475 }
476 '<' => {
477 if i + 1 < len && chars[i + 1] == '=' {
478 tokens.push(Spanned {
479 token: Token::Le,
480 at,
481 });
482 i += 2;
483 } else {
484 tokens.push(Spanned {
485 token: Token::Lt,
486 at,
487 });
488 i += 1;
489 }
490 }
491 '>' => {
492 if i + 1 < len && chars[i + 1] == '=' {
493 tokens.push(Spanned {
494 token: Token::Ge,
495 at,
496 });
497 i += 2;
498 } else {
499 tokens.push(Spanned {
500 token: Token::Gt,
501 at,
502 });
503 i += 1;
504 }
505 }
506 '&' => {
507 if i + 1 < len && chars[i + 1] == '&' {
508 tokens.push(Spanned {
509 token: Token::AndAnd,
510 at,
511 });
512 i += 2;
513 } else {
514 return Err(at_char(at, "a lone `&`; did you mean `&&`?"));
515 }
516 }
517 '|' => {
518 if i + 1 < len && chars[i + 1] == '|' {
519 tokens.push(Spanned {
520 token: Token::OrOr,
521 at,
522 });
523 i += 2;
524 } else {
525 return Err(at_char(at, "a lone `|`; did you mean `||`?"));
526 }
527 }
528 '"' => {
529 let (token, next) = lex_string(chars, i)?;
530 tokens.push(Spanned { token, at });
531 i = next;
532 }
533 _ if c.is_ascii_digit()
534 || (c == '-' && i + 1 < len && chars[i + 1].is_ascii_digit()) =>
535 {
536 let (token, next) = lex_number(chars, i)?;
537 tokens.push(Spanned { token, at });
538 i = next;
539 }
540 _ if is_ident_start(c) => {
541 let (token, next) = lex_ident(chars, i);
542 tokens.push(Spanned { token, at });
543 i = next;
544 }
545 _ => {
546 return Err(at_char(at, format!("an unexpected character `{c}`")));
547 }
548 }
549 }
550
551 Ok(tokens)
552}
553
554fn lex_string(chars: &[char], start: usize) -> Result<(Token, usize), ExprError> {
558 let len = chars.len();
559 let mut i = start + 1;
560 let mut value = String::new();
561
562 while i < len {
563 let c = chars[i];
564 if c == '"' {
565 return Ok((Token::Str(value), i + 1));
566 }
567 if c == '\\' {
568 i += 1;
569 if i >= len {
570 break;
571 }
572 match chars[i] {
573 '"' => value.push('"'),
574 '\\' => value.push('\\'),
575 '/' => value.push('/'),
576 'n' => value.push('\n'),
577 't' => value.push('\t'),
578 'r' => value.push('\r'),
579 other => {
580 return Err(at_char(
581 i,
582 format!("an unsupported string escape `\\{other}`"),
583 ));
584 }
585 }
586 i += 1;
587 } else {
588 value.push(c);
589 i += 1;
590 }
591 }
592
593 Err(at_char(start, "an unterminated string literal"))
594}
595
596fn lex_number(chars: &[char], start: usize) -> Result<(Token, usize), ExprError> {
599 let len = chars.len();
600 let mut i = start;
601 if chars[i] == '-' {
602 i += 1;
603 }
604 while i < len && chars[i].is_ascii_digit() {
605 i += 1;
606 }
607 if i + 1 < len && chars[i] == '.' && chars[i + 1].is_ascii_digit() {
611 i += 1;
612 while i < len && chars[i].is_ascii_digit() {
613 i += 1;
614 }
615 }
616
617 let text: String = chars[start..i].iter().collect();
618 let number: Number = serde_json::from_str(&text)
619 .map_err(|_| at_char(start, format!("a malformed number `{text}`")))?;
620 Ok((Token::Number(number), i))
621}
622
623fn lex_ident(chars: &[char], start: usize) -> (Token, usize) {
626 let len = chars.len();
627 let mut i = start;
628 while i < len && is_ident_continue(chars[i]) {
629 i += 1;
630 }
631 let text: String = chars[start..i].iter().collect();
632 let token = match text.as_str() {
633 "true" => Token::True,
634 "false" => Token::False,
635 "null" => Token::Null,
636 _ => Token::Ident(text),
637 };
638 (token, i)
639}
640
641fn at_char(position: usize, what: impl std::fmt::Display) -> ExprError {
642 ExprError::new(format!("found {what} at character {position}"))
643}
644
645struct Parser<'a> {
649 tokens: &'a [Spanned],
650 pos: usize,
651 end: usize,
654}
655
656impl Parser<'_> {
657 fn peek(&self) -> Option<&Token> {
658 self.tokens.get(self.pos).map(|s| &s.token)
659 }
660
661 fn position(&self) -> usize {
662 self.tokens.get(self.pos).map_or(self.end, |s| s.at)
663 }
664
665 fn advance(&mut self) {
666 self.pos += 1;
667 }
668
669 fn expect_end(&self) -> Result<(), ExprError> {
670 match self.peek() {
671 None => Ok(()),
672 Some(_) => Err(at_char(
673 self.position(),
674 "an unexpected trailing token; the expression already ended",
675 )),
676 }
677 }
678
679 fn parse_or(&mut self) -> Result<Bool, ExprError> {
680 let mut left = self.parse_and()?;
681 while matches!(self.peek(), Some(Token::OrOr)) {
682 self.advance();
683 let right = self.parse_and()?;
684 left = Bool::Or(Box::new(left), Box::new(right));
685 }
686 Ok(left)
687 }
688
689 fn parse_and(&mut self) -> Result<Bool, ExprError> {
690 let mut left = self.parse_unary()?;
691 while matches!(self.peek(), Some(Token::AndAnd)) {
692 self.advance();
693 let right = self.parse_unary()?;
694 left = Bool::And(Box::new(left), Box::new(right));
695 }
696 Ok(left)
697 }
698
699 fn parse_unary(&mut self) -> Result<Bool, ExprError> {
700 if matches!(self.peek(), Some(Token::Bang)) {
701 self.advance();
702 let inner = self.parse_unary()?;
703 Ok(Bool::Not(Box::new(inner)))
704 } else {
705 self.parse_atom()
706 }
707 }
708
709 fn parse_atom(&mut self) -> Result<Bool, ExprError> {
710 if matches!(self.peek(), Some(Token::LParen)) {
711 self.advance();
712 let inner = self.parse_or()?;
713 match self.peek() {
714 Some(Token::RParen) => {
715 self.advance();
716 Ok(inner)
717 }
718 _ => Err(at_char(self.position(), "a missing closing `)`")),
719 }
720 } else {
721 self.parse_comparison()
722 }
723 }
724
725 fn parse_comparison(&mut self) -> Result<Bool, ExprError> {
726 let left = self.parse_operand()?;
727 let op = match self.peek() {
728 Some(Token::EqEq) => CmpOp::Eq,
729 Some(Token::NotEq) => CmpOp::Ne,
730 Some(Token::Lt) => CmpOp::Lt,
731 Some(Token::Le) => CmpOp::Le,
732 Some(Token::Gt) => CmpOp::Gt,
733 Some(Token::Ge) => CmpOp::Ge,
734 _ => return Ok(Bool::Truthy(left)),
735 };
736 self.advance();
737 let right = self.parse_operand()?;
738 Ok(Bool::Compare { left, op, right })
739 }
740
741 fn parse_operand(&mut self) -> Result<Operand, ExprError> {
742 match self.peek() {
743 Some(Token::Number(n)) => {
744 let value = Operand::Literal(Value::Number(n.clone()));
745 self.advance();
746 Ok(value)
747 }
748 Some(Token::Str(s)) => {
749 let value = Operand::Literal(Value::String(s.clone()));
750 self.advance();
751 Ok(value)
752 }
753 Some(Token::True) => {
754 self.advance();
755 Ok(Operand::Literal(Value::Bool(true)))
756 }
757 Some(Token::False) => {
758 self.advance();
759 Ok(Operand::Literal(Value::Bool(false)))
760 }
761 Some(Token::Null) => {
762 self.advance();
763 Ok(Operand::Literal(Value::Null))
764 }
765 Some(Token::Ident(name)) => {
766 let name = name.clone();
767 self.advance();
768 self.parse_path(name)
769 }
770 _ => Err(at_char(
771 self.position(),
772 "a value or path where one was required",
773 )),
774 }
775 }
776
777 fn parse_path(&mut self, first: String) -> Result<Operand, ExprError> {
778 let mut segments = vec![Segment::Key(first)];
779 while matches!(self.peek(), Some(Token::Dot)) {
780 self.advance();
781 match self.peek() {
782 Some(Token::Ident(name)) => {
783 segments.push(Segment::Key(name.clone()));
784 self.advance();
785 }
786 Some(Token::Number(n)) => {
787 let index = n
788 .as_u64()
789 .and_then(|v| usize::try_from(v).ok())
790 .ok_or_else(|| {
791 at_char(
792 self.position(),
793 "a path segment that is not a non-negative integer index",
794 )
795 })?;
796 segments.push(Segment::Index(index));
797 self.advance();
798 }
799 _ => {
800 return Err(at_char(self.position(), "a missing path segment after `.`"));
801 }
802 }
803 }
804 Ok(Operand::Path(segments))
805 }
806}
807
808fn eval_bool(node: &Bool, root: &Value) -> bool {
813 match node {
814 Bool::Or(a, b) => eval_bool(a, root) || eval_bool(b, root),
815 Bool::And(a, b) => eval_bool(a, root) && eval_bool(b, root),
816 Bool::Not(a) => !eval_bool(a, root),
817 Bool::Compare { left, op, right } => eval_compare(left, *op, right, root),
818 Bool::Truthy(operand) => matches!(resolve(operand, root), Some(Value::Bool(true))),
819 }
820}
821
822fn resolve<'a>(operand: &'a Operand, root: &'a Value) -> Option<&'a Value> {
826 match operand {
827 Operand::Literal(value) => Some(value),
828 Operand::Path(segments) => resolve_path(segments, root),
829 }
830}
831
832fn resolve_path<'a>(segments: &[Segment], root: &'a Value) -> Option<&'a Value> {
838 let mut current = root;
839 for segment in segments {
840 current = match (current, segment) {
841 (Value::Object(map), Segment::Key(key)) => map.get(key)?,
842 (Value::Array(items), Segment::Index(index)) => items.get(*index)?,
843 _ => return None,
844 };
845 }
846 Some(current)
847}
848
849fn eval_compare(left: &Operand, op: CmpOp, right: &Operand, root: &Value) -> bool {
850 let (Some(l), Some(r)) = (resolve(left, root), resolve(right, root)) else {
853 return false;
854 };
855
856 match op {
857 CmpOp::Eq => values_equal(l, r),
858 CmpOp::Ne => !values_equal(l, r),
859 CmpOp::Lt => matches!(order(l, r), Some(Ordering::Less)),
860 CmpOp::Le => matches!(order(l, r), Some(Ordering::Less | Ordering::Equal)),
861 CmpOp::Gt => matches!(order(l, r), Some(Ordering::Greater)),
862 CmpOp::Ge => matches!(order(l, r), Some(Ordering::Greater | Ordering::Equal)),
863 }
864}
865
866fn values_equal(l: &Value, r: &Value) -> bool {
869 match (l, r) {
870 (Value::Number(a), Value::Number(b)) => number_cmp(a, b) == Some(Ordering::Equal),
871 _ => l == r,
872 }
873}
874
875fn order(l: &Value, r: &Value) -> Option<Ordering> {
878 match (l, r) {
879 (Value::Number(a), Value::Number(b)) => number_cmp(a, b),
880 (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
881 _ => None,
882 }
883}
884
885fn number_cmp(a: &Number, b: &Number) -> Option<Ordering> {
888 if let (Some(x), Some(y)) = (as_i128(a), as_i128(b)) {
889 return Some(x.cmp(&y));
890 }
891 match (a.as_f64(), b.as_f64()) {
892 (Some(x), Some(y)) => x.partial_cmp(&y),
893 _ => None,
894 }
895}
896
897fn as_i128(n: &Number) -> Option<i128> {
899 n.as_u64()
900 .map(i128::from)
901 .or_else(|| n.as_i64().map(i128::from))
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907 use proptest::prelude::*;
908 use serde_json::json;
909
910 fn eval(expr: &str, value: &Value) -> bool {
911 parse(expr)
912 .unwrap_or_else(|e| panic!("`{expr}` should parse: {e}"))
913 .eval(value)
914 }
915
916 #[test]
919 fn sample_score_expression() {
920 assert!(eval("score > 0.8", &json!({"score": 0.9})));
921 assert!(!eval("score > 0.8", &json!({"score": 0.5})));
922 }
923
924 #[test]
927 fn every_comparison_operator() {
928 let v = json!({"n": 5});
929 assert!(eval("n == 5", &v));
930 assert!(!eval("n == 4", &v));
931 assert!(eval("n != 4", &v));
932 assert!(!eval("n != 5", &v));
933 assert!(eval("n < 6", &v));
934 assert!(!eval("n < 5", &v));
935 assert!(eval("n <= 5", &v));
936 assert!(eval("n > 4", &v));
937 assert!(!eval("n > 5", &v));
938 assert!(eval("n >= 5", &v));
939 }
940
941 #[test]
942 fn boolean_operators() {
943 let v = json!({"a": true, "b": false});
944 assert!(eval("a && !b", &v));
945 assert!(eval("a || b", &v));
946 assert!(!eval("!a", &v));
947 assert!(eval("!b", &v));
948 assert!(!eval("a && b", &v));
949 }
950
951 #[test]
954 fn and_binds_tighter_than_or() {
955 let v = json!({});
959 assert!(eval("true || false && false", &v));
960 assert!(!eval("(true || false) && false", &v));
961 }
962
963 #[test]
964 fn not_binds_tighter_than_and() {
965 let v = json!({"a": false, "b": true});
966 assert!(eval("!a && b", &v));
968 }
969
970 #[test]
971 fn comparison_binds_tighter_than_not() {
972 assert!(eval("!score > 0.8", &json!({"score": 0.5})));
974 assert!(!eval("!score > 0.8", &json!({"score": 0.9})));
975 }
976
977 #[test]
978 fn parentheses_group_boolean_expressions() {
979 let v = json!({"a": true, "b": false, "c": true});
980 assert!(eval("a && (b || c)", &v));
981 assert!(!eval("(a && b) || (b && c)", &v));
982 }
983
984 #[test]
985 fn comparisons_do_not_chain() {
986 assert!(parse("1 < 2 < 3").is_err());
987 }
988
989 #[test]
990 fn a_boolean_group_is_not_a_comparison_operand() {
991 assert!(parse("(a > b) > c").is_err());
992 }
993
994 #[test]
997 fn nested_and_indexed_paths() {
998 let v = json!({"output": {"score": 0.9}, "items": [{"score": 1}, {"score": 2}]});
999 assert!(eval("output.score > 0.8", &v));
1000 assert!(eval("items.0.score == 1", &v));
1001 assert!(eval("items.1.score == 2", &v));
1002 }
1003
1004 #[test]
1005 fn bare_path_is_truthy_only_for_boolean_true() {
1006 assert!(eval("flag", &json!({"flag": true})));
1007 assert!(!eval("flag", &json!({"flag": false})));
1008 assert!(!eval("flag", &json!({"flag": 1})));
1009 assert!(!eval("flag", &json!({"flag": "true"})));
1010 assert!(!eval("flag", &json!({})));
1011 }
1012
1013 #[test]
1016 fn missing_path_makes_every_comparison_false() {
1017 let v = json!({});
1018 assert!(!eval("missing == 1", &v));
1019 assert!(!eval("missing != 1", &v));
1020 assert!(!eval("missing < 1", &v));
1021 assert!(!eval("missing > 1", &v));
1022 assert!(eval("!(missing > 1)", &v));
1024 }
1025
1026 #[test]
1027 fn missing_is_distinct_from_null() {
1028 assert!(eval("x == null", &json!({"x": null})));
1030 assert!(!eval("missing == null", &json!({})));
1031 }
1032
1033 #[test]
1034 fn descending_into_a_non_container_is_missing() {
1035 let v = json!({"x": 5});
1036 assert!(!eval("x.y == 1", &v));
1037 assert!(!eval("x.0 == 1", &v));
1038 }
1039
1040 #[test]
1043 fn cross_type_equality_is_never_equal() {
1044 assert!(!eval("x == 5", &json!({"x": "5"})));
1045 assert!(eval("x != 5", &json!({"x": "5"})));
1046 assert!(!eval("x == 1", &json!({"x": true})));
1047 }
1048
1049 #[test]
1050 fn cross_type_ordering_is_false() {
1051 assert!(!eval("x < 5", &json!({"x": "5"})));
1052 assert!(!eval("x > 5", &json!({"x": "5"})));
1053 assert!(!eval("x < 5", &json!({"x": true})));
1054 assert!(!eval("x < 5", &json!({"x": null})));
1055 }
1056
1057 #[test]
1058 fn strings_order_lexicographically() {
1059 assert!(eval("x < \"b\"", &json!({"x": "a"})));
1060 assert!(!eval("x < \"a\"", &json!({"x": "b"})));
1061 assert!(eval("x == \"hi\"", &json!({"x": "hi"})));
1062 }
1063
1064 #[test]
1067 fn integer_and_float_equality() {
1068 assert!(eval("x == 1", &json!({"x": 1.0})));
1069 assert!(eval("x == 1.0", &json!({"x": 1})));
1070 assert!(eval("x >= 1", &json!({"x": 1.0})));
1071 }
1072
1073 #[test]
1074 fn large_integers_compare_exactly() {
1075 let big = json!({"a": 9_007_199_254_740_993_i64});
1077 assert!(eval("a == 9007199254740993", &big));
1078 assert!(!eval("a == 9007199254740992", &big));
1079 }
1080
1081 #[test]
1082 fn negative_and_signed_comparison() {
1083 assert!(eval("x < 0", &json!({"x": -3})));
1084 assert!(eval("x == -0.5", &json!({"x": -0.5})));
1085 assert!(eval("x > y", &json!({"x": 1, "y": -1})));
1086 }
1087
1088 #[test]
1091 fn the_length_cap_rejects_longer_input_and_names_it() {
1092 let ok = "a".repeat(MAX_EXPRESSION_LEN);
1093 assert!(parse(&ok).is_ok());
1094 let too_long = "a".repeat(MAX_EXPRESSION_LEN + 1);
1095 let err = parse(&too_long).expect_err("over the cap");
1096 assert!(
1097 err.message().contains(&MAX_EXPRESSION_LEN.to_string()),
1098 "names the cap: {err}"
1099 );
1100 }
1101
1102 #[test]
1103 fn assorted_syntax_errors() {
1104 for bad in [
1105 "",
1106 "&&",
1107 "a &&",
1108 "a && (b",
1109 "== 5",
1110 "a = 5",
1111 "a & b",
1112 "a | b",
1113 "1.2.3 == 1",
1114 "\"unterminated",
1115 "a.",
1116 "a.-1 == 1",
1117 ] {
1118 assert!(parse(bad).is_err(), "`{bad}` should be a parse error");
1119 }
1120 }
1121
1122 #[test]
1125 fn the_exported_order_is_the_one_the_operators_use() {
1126 assert_eq!(compare(&json!(1), &json!(2)), Some(Ordering::Less));
1129 assert_eq!(compare(&json!(1), &json!(1.0)), Some(Ordering::Equal));
1130 assert_eq!(
1131 compare(
1132 &json!(9_007_199_254_740_993_i64),
1133 &json!(9_007_199_254_740_992_i64)
1134 ),
1135 Some(Ordering::Greater)
1136 );
1137 assert_eq!(compare(&json!("a"), &json!("b")), Some(Ordering::Less));
1138 for value in [json!(true), json!(null), json!({"a": 1}), json!([1])] {
1141 assert_eq!(compare(&value, &value), None, "{value} must not order");
1142 }
1143 assert_eq!(compare(&json!(1), &json!("1")), None);
1144 }
1145
1146 proptest! {
1147 #[test]
1150 fn the_export_agrees_with_the_ordering_operators(
1151 left in json_strategy(),
1152 right in json_strategy(),
1153 ) {
1154 let value = json!({"l": left, "r": right});
1155 let less = parse("l < r").unwrap().eval(&value);
1156 let greater = parse("l > r").unwrap().eval(&value);
1157 let (l, r) = (&value["l"], &value["r"]);
1158 prop_assert_eq!(compare(l, r) == Some(Ordering::Less), less);
1159 prop_assert_eq!(compare(l, r) == Some(Ordering::Greater), greater);
1160 }
1161 }
1162
1163 #[test]
1166 fn a_reference_resolves_a_top_level_and_nested_array() {
1167 let top = parse_reference("items").expect("`items` parses");
1168 assert_eq!(
1169 top.resolve(&json!({"items": [1, 2, 3]})),
1170 Some(&json!([1, 2, 3]))
1171 );
1172 let nested = parse_reference("output.items").expect("`output.items` parses");
1173 assert_eq!(
1174 nested.resolve(&json!({"output": {"items": ["a"]}})),
1175 Some(&json!(["a"]))
1176 );
1177 let indexed = parse_reference("results.0.items").expect("indexed path parses");
1178 assert_eq!(
1179 indexed.resolve(&json!({"results": [{"items": [true]}]})),
1180 Some(&json!([true]))
1181 );
1182 }
1183
1184 #[test]
1185 fn a_missing_reference_resolves_to_none() {
1186 let reference = parse_reference("items").expect("parses");
1187 assert_eq!(reference.resolve(&json!({})), None);
1188 assert_eq!(reference.resolve(&json!({"other": [1]})), None);
1189 let deep = parse_reference("x.items").expect("parses");
1191 assert_eq!(deep.resolve(&json!({"x": 5})), None);
1192 }
1193
1194 #[test]
1195 fn a_reference_may_name_any_json_value_not_only_arrays() {
1196 let reference = parse_reference("value").expect("parses");
1198 assert_eq!(reference.resolve(&json!({"value": 5})), Some(&json!(5)));
1199 assert_eq!(
1200 reference.resolve(&json!({"value": {"k": 1}})),
1201 Some(&json!({"k": 1}))
1202 );
1203 }
1204
1205 #[test]
1206 fn a_literal_or_malformed_reference_is_rejected() {
1207 assert!(
1208 parse_reference("5").is_err(),
1209 "a bare literal is not a path"
1210 );
1211 assert!(
1212 parse_reference("\"x\"").is_err(),
1213 "a string literal is not a path"
1214 );
1215 assert!(parse_reference("true").is_err(), "a keyword is not a path");
1216 assert!(
1217 parse_reference("items ==").is_err(),
1218 "trailing tokens rejected"
1219 );
1220 assert!(
1221 parse_reference("items.").is_err(),
1222 "a dangling dot is rejected"
1223 );
1224 assert!(
1225 parse_reference("").is_err(),
1226 "an empty reference is rejected"
1227 );
1228 }
1229
1230 #[test]
1233 fn a_reference_reports_its_segments() {
1234 let reference = parse_reference("results.0.review.score").expect("parses");
1235 assert_eq!(
1236 reference.segments(),
1237 [
1238 Segment::Key("results".to_owned()),
1239 Segment::Index(0),
1240 Segment::Key("review".to_owned()),
1241 Segment::Key("score".to_owned()),
1242 ]
1243 );
1244 }
1245
1246 #[test]
1249 fn an_expression_reports_the_paths_it_reads() {
1250 let expr =
1251 parse("score >= 0.85 && !(review.flags.0 == \"stale\") || score < 0").expect("parses");
1252 let paths: Vec<Vec<Segment>> = expr.paths().iter().map(|p| p.to_vec()).collect();
1253 assert_eq!(
1254 paths,
1255 vec![
1256 vec![Segment::Key("score".to_owned())],
1257 vec![
1258 Segment::Key("review".to_owned()),
1259 Segment::Key("flags".to_owned()),
1260 Segment::Index(0),
1261 ],
1262 vec![Segment::Key("score".to_owned())],
1263 ]
1264 );
1265 }
1266
1267 #[test]
1270 fn a_literal_only_expression_reads_no_path() {
1271 assert!(parse("1 == 1").expect("parses").paths().is_empty());
1272 assert!(parse("true").expect("parses").paths().is_empty());
1273 }
1274
1275 proptest! {
1278 #[test]
1280 fn parsing_never_panics(input in ".*") {
1281 let _ = parse(&input);
1282 }
1283
1284 #[test]
1286 fn near_miss_parsing_never_panics(
1287 input in "[a-z0-9_. ()!&|<>=\"'.-]{0,80}"
1288 ) {
1289 let _ = parse(&input);
1290 }
1291
1292 #[test]
1295 fn cap_always_holds(input in "a{500,700}") {
1296 let result = parse(&input);
1297 if input.chars().count() > MAX_EXPRESSION_LEN {
1298 prop_assert!(result.is_err());
1299 }
1300 }
1301
1302 #[test]
1306 fn eval_never_panics(expr in expr_strategy(), value in json_strategy()) {
1307 if let Ok(parsed) = parse(&expr) {
1308 let _ = parsed.eval(&value);
1309 }
1310 }
1311 }
1312
1313 fn expr_strategy() -> impl Strategy<Value = String> {
1316 let leaf = prop_oneof![
1317 Just("score".to_string()),
1318 Just("output.score".to_string()),
1319 Just("items.0.score".to_string()),
1320 Just("flag".to_string()),
1321 Just("0.8".to_string()),
1322 Just("5".to_string()),
1323 Just("-1".to_string()),
1324 Just("true".to_string()),
1325 Just("null".to_string()),
1326 Just("\"hi\"".to_string()),
1327 ];
1328 let comparison = (leaf.clone(), "==|!=|<|<=|>|>=", leaf.clone())
1329 .prop_map(|(l, op, r)| format!("{l} {op} {r}"));
1330 let atom = prop_oneof![leaf, comparison];
1331 atom.prop_recursive(4, 32, 4, |inner| {
1332 prop_oneof![
1333 inner.clone().prop_map(|e| format!("!{e}")),
1334 inner.clone().prop_map(|e| format!("({e})")),
1335 (inner.clone(), inner.clone()).prop_map(|(a, b)| format!("{a} && {b}")),
1336 (inner.clone(), inner).prop_map(|(a, b)| format!("{a} || {b}")),
1337 ]
1338 })
1339 }
1340
1341 fn json_strategy() -> impl Strategy<Value = Value> {
1343 let leaf = prop_oneof![
1344 Just(Value::Null),
1345 any::<bool>().prop_map(Value::Bool),
1346 any::<i64>().prop_map(|n| json!(n)),
1347 any::<f64>()
1348 .prop_filter("finite", |f| f.is_finite())
1349 .prop_map(|f| json!(f)),
1350 ".*".prop_map(Value::String),
1351 ];
1352 leaf.prop_recursive(3, 16, 4, |inner| {
1353 prop_oneof![
1354 prop::collection::vec(inner.clone(), 0..4).prop_map(Value::Array),
1355 prop::collection::hash_map("[a-z]{1,5}", inner, 0..4)
1356 .prop_map(|m| Value::Object(m.into_iter().collect())),
1357 ]
1358 })
1359 }
1360}