1use super::basic::BasicPredicate;
11use super::field_predicate::{FieldPredicate, LookupOp};
12use crate::cacheable::Field;
13use crate::jsahibon::{JObject, JSahibON, compare_jsahibon_numbers};
14use std::any::Any;
15use std::cmp::Ordering;
16use std::marker::PhantomData;
17use std::sync::Arc;
18
19#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct JPath(Arc<[String]>);
28
29impl JPath {
30 pub fn root() -> Self {
32 Self(Arc::from([]))
33 }
34
35 pub fn from_segments<I, S>(segments: I) -> Self
39 where
40 I: IntoIterator<Item = S>,
41 S: Into<String>,
42 {
43 Self(
44 segments
45 .into_iter()
46 .map(Into::into)
47 .collect::<Vec<_>>()
48 .into(),
49 )
50 }
51
52 pub fn parse_dotted(path: &'static str) -> Self {
66 let segments = path.split('.').collect::<Vec<_>>();
67 assert!(
68 segments.iter().all(|segment| valid_plain_segment(segment)),
69 "JSahibON dotted paths require non-empty ASCII identifier segments of at most 63 bytes"
70 );
71 Self::from_segments(segments)
72 }
73
74 pub fn segments(&self) -> &[String] {
76 &self.0
77 }
78
79 fn push(&self, key: String) -> Self {
80 let mut segments = Vec::with_capacity(self.0.len() + 1);
81 segments.extend(self.0.iter().cloned());
82 segments.push(key);
83 Self(segments.into())
84 }
85
86 fn extend<I, S>(&self, segments: I) -> Self
87 where
88 I: IntoIterator<Item = S>,
89 S: Into<String>,
90 {
91 let mut next = Vec::from(self.0.as_ref());
92 next.extend(segments.into_iter().map(Into::into));
93 Self(next.into())
94 }
95}
96
97impl Default for JPath {
98 fn default() -> Self {
99 Self::root()
100 }
101}
102
103impl FromIterator<String> for JPath {
104 fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
105 Self::from_segments(iter)
106 }
107}
108
109impl<'a> FromIterator<&'a str> for JPath {
110 fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
111 Self::from_segments(iter)
112 }
113}
114
115fn valid_plain_segment(segment: &str) -> bool {
116 let mut bytes = segment.bytes();
117 let Some(first) = bytes.next() else {
118 return false;
119 };
120 if segment.len() > 63 || !(first.is_ascii_alphabetic() || first == b'_') {
121 return false;
122 }
123 bytes.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum JScalarKind {
133 I64,
135 U64,
137 F64,
139 String,
141 Bool,
143}
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150#[non_exhaustive]
151pub enum JTypeKind {
152 Null,
154 Bool,
156 Number,
158 String,
160 Array,
162 Object,
164}
165
166#[derive(Clone, Debug)]
171#[non_exhaustive]
172pub enum JScalarValue {
173 I64(i64),
175 U64(u64),
177 F64(crate::JFiniteF64),
179 String(String),
181 Bool(bool),
183}
184
185impl PartialEq for JScalarValue {
186 fn eq(&self, other: &Self) -> bool {
187 scalar_to_jsahibon(self) == scalar_to_jsahibon(other)
188 }
189}
190
191#[derive(Clone, Copy, Debug, PartialEq, Eq)]
193#[non_exhaustive]
194pub enum JCompareOp {
195 Eq,
197 Neq,
199 Gt,
201 Gte,
203 Lt,
205 Lte,
207}
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq)]
211#[non_exhaustive]
212pub enum JInPolarity {
213 In,
215 NotIn,
217}
218
219#[derive(Clone, Debug, PartialEq)]
227#[non_exhaustive]
228pub enum JSahibONPredicateBody {
229 Exists {
232 path: JPath,
234 },
235 Missing {
237 path: JPath,
239 },
240 IsJsonNull {
242 path: JPath,
244 },
245 IsNotJsonNull {
247 path: JPath,
249 },
250 Type {
252 path: JPath,
254 kind: JTypeKind,
256 },
257 HasKey {
259 path: JPath,
261 key: Arc<String>,
263 },
264 HasAnyKey {
267 path: JPath,
269 keys: Arc<[String]>,
271 },
272 HasAllKeys {
275 path: JPath,
277 keys: Arc<[String]>,
279 },
280 ScalarCompare {
283 path: JPath,
285 op: JCompareOp,
287 scalar_kind: JScalarKind,
290 operand: JScalarValue,
292 },
293 ScalarIn {
295 path: JPath,
297 scalar_kind: JScalarKind,
299 operands: Arc<[JScalarValue]>,
301 polarity: JInPolarity,
303 },
304 ScalarBetween {
307 path: JPath,
309 scalar_kind: JScalarKind,
311 low: JScalarValue,
313 high: JScalarValue,
315 },
316 JsonEq {
319 path: JPath,
321 value: JSahibON,
323 },
324 JsonNeq {
327 path: JPath,
329 value: JSahibON,
331 },
332 ArrayContains {
335 path: JPath,
337 element: JSahibON,
339 },
340 ArrayLen {
342 path: JPath,
344 op: JCompareOp,
346 len: u64,
349 },
350}
351
352pub trait JScalar: private::Sealed + Send + Sync + 'static {
358 const KIND: JScalarKind;
360
361 fn into_scalar_value(self) -> JScalarValue;
371}
372
373pub trait JOrderedScalar: JScalar {}
379
380mod private {
381 pub trait Sealed {}
382}
383
384impl private::Sealed for i64 {}
385impl JScalar for i64 {
386 const KIND: JScalarKind = JScalarKind::I64;
387 fn into_scalar_value(self) -> JScalarValue {
388 JScalarValue::I64(self)
389 }
390}
391impl JOrderedScalar for i64 {}
392
393impl private::Sealed for u64 {}
394impl JScalar for u64 {
395 const KIND: JScalarKind = JScalarKind::U64;
396 fn into_scalar_value(self) -> JScalarValue {
397 JScalarValue::U64(self)
398 }
399}
400impl JOrderedScalar for u64 {}
401
402impl private::Sealed for f64 {}
403impl JScalar for f64 {
404 const KIND: JScalarKind = JScalarKind::F64;
405
406 fn into_scalar_value(self) -> JScalarValue {
417 JScalarValue::F64(
418 crate::JFiniteF64::try_new(self)
419 .expect("JSahibON scalar predicates only accept finite f64 operands"),
420 )
421 }
422}
423impl JOrderedScalar for f64 {}
424
425impl private::Sealed for String {}
426impl JScalar for String {
427 const KIND: JScalarKind = JScalarKind::String;
428 fn into_scalar_value(self) -> JScalarValue {
429 JScalarValue::String(self)
430 }
431}
432
433impl private::Sealed for bool {}
434impl JScalar for bool {
435 const KIND: JScalarKind = JScalarKind::Bool;
436 fn into_scalar_value(self) -> JScalarValue {
437 JScalarValue::Bool(self)
438 }
439}
440
441enum JRoot<T> {
442 Required(fn(&T) -> &JSahibON),
443 Optional(fn(&T) -> &Option<JSahibON>),
444}
445
446impl<T> Copy for JRoot<T> {}
447
448impl<T> Clone for JRoot<T> {
449 fn clone(&self) -> Self {
450 *self
451 }
452}
453
454impl<T> JRoot<T> {
455 fn resolve(self, value: &T) -> Option<&JSahibON> {
456 match self {
457 Self::Required(extract) => Some(extract(value)),
458 Self::Optional(extract) => extract(value).as_ref(),
459 }
460 }
461}
462
463pub struct JSahibONPathRef<T> {
469 field_name: &'static str,
470 root: JRoot<T>,
471 path: JPath,
472}
473
474pub struct JSahibONFieldRef<T> {
480 inner: JSahibONPathRef<T>,
481}
482
483pub struct JSahibONOptionFieldRef<T> {
489 inner: JSahibONPathRef<T>,
490}
491
492pub struct JSahibONValueRef<T, V> {
499 inner: JSahibONPathRef<T>,
500 _marker: PhantomData<V>,
501}
502
503impl<T> Clone for JSahibONPathRef<T> {
504 fn clone(&self) -> Self {
505 Self {
506 field_name: self.field_name,
507 root: self.root,
508 path: self.path.clone(),
509 }
510 }
511}
512
513impl<T> Clone for JSahibONFieldRef<T> {
514 fn clone(&self) -> Self {
515 Self {
516 inner: self.inner.clone(),
517 }
518 }
519}
520
521impl<T> Clone for JSahibONOptionFieldRef<T> {
522 fn clone(&self) -> Self {
523 Self {
524 inner: self.inner.clone(),
525 }
526 }
527}
528
529impl<T, V> Clone for JSahibONValueRef<T, V> {
530 fn clone(&self) -> Self {
531 Self {
532 inner: self.inner.clone(),
533 _marker: PhantomData,
534 }
535 }
536}
537
538impl<T: 'static> Field<T, JSahibON> {
539 pub fn jsahibon(&self) -> JSahibONFieldRef<T> {
546 JSahibONFieldRef {
547 inner: JSahibONPathRef {
548 field_name: self.name,
549 root: JRoot::Required(self.extract),
550 path: JPath::root(),
551 },
552 }
553 }
554}
555
556impl<T: 'static> Field<T, Option<JSahibON>> {
557 pub fn jsahibon(&self) -> JSahibONOptionFieldRef<T> {
562 JSahibONOptionFieldRef {
563 inner: JSahibONPathRef {
564 field_name: self.name,
565 root: JRoot::Optional(self.extract),
566 path: JPath::root(),
567 },
568 }
569 }
570}
571
572macro_rules! delegate_json_ref_methods {
573 ($ty:ident) => {
574 impl<T: 'static> $ty<T> {
575 pub fn root(&self) -> JSahibONPathRef<T> {
577 let mut inner = self.inner.clone();
578 inner.path = JPath::root();
579 inner
580 }
581
582 pub fn path(&self, dotted_plain_idents: &'static str) -> JSahibONPathRef<T> {
587 let mut inner = self.inner.clone();
588 inner.path = JPath::parse_dotted(dotted_plain_idents);
589 inner
590 }
591
592 pub fn key(&self, key: impl Into<String>) -> JSahibONPathRef<T> {
594 let mut inner = self.inner.clone();
595 inner.path = inner.path.push(key.into());
596 inner
597 }
598
599 pub fn path_segments<I, S>(&self, segments: I) -> JSahibONPathRef<T>
601 where
602 I: IntoIterator<Item = S>,
603 S: Into<String>,
604 {
605 let mut inner = self.inner.clone();
606 inner.path = JPath::from_segments(segments);
607 inner
608 }
609
610 pub fn exists(&self) -> BasicPredicate<T> {
612 self.inner.exists()
613 }
614
615 pub fn missing(&self) -> BasicPredicate<T> {
617 self.inner.missing()
618 }
619
620 pub fn is_json_null(&self) -> BasicPredicate<T> {
622 self.inner.is_json_null()
623 }
624
625 pub fn is_not_json_null(&self) -> BasicPredicate<T> {
627 self.inner.is_not_json_null()
628 }
629
630 pub fn is_type(&self, kind: JTypeKind) -> BasicPredicate<T> {
632 self.inner.is_type(kind)
633 }
634
635 pub fn is_bool(&self) -> BasicPredicate<T> {
637 self.inner.is_bool()
638 }
639
640 pub fn is_number(&self) -> BasicPredicate<T> {
642 self.inner.is_number()
643 }
644
645 pub fn is_string(&self) -> BasicPredicate<T> {
647 self.inner.is_string()
648 }
649
650 pub fn is_array(&self) -> BasicPredicate<T> {
652 self.inner.is_array()
653 }
654
655 pub fn is_object(&self) -> BasicPredicate<T> {
657 self.inner.is_object()
658 }
659
660 pub fn has_key(&self, key: impl Into<String>) -> BasicPredicate<T> {
662 self.inner.has_key(key)
663 }
664
665 pub fn has_any_key<I, S>(&self, keys: I) -> BasicPredicate<T>
667 where
668 I: IntoIterator<Item = S>,
669 S: Into<String>,
670 {
671 self.inner.has_any_key(keys)
672 }
673
674 pub fn has_all_keys<I, S>(&self, keys: I) -> BasicPredicate<T>
676 where
677 I: IntoIterator<Item = S>,
678 S: Into<String>,
679 {
680 self.inner.has_all_keys(keys)
681 }
682
683 pub fn value<V: JScalar>(&self) -> JSahibONValueRef<T, V> {
685 self.inner.value()
686 }
687
688 pub fn eq_json(&self, value: JSahibON) -> BasicPredicate<T> {
690 self.inner.eq_json(value)
691 }
692
693 pub fn neq_json(&self, value: JSahibON) -> BasicPredicate<T> {
695 self.inner.neq_json(value)
696 }
697
698 pub fn array_contains(&self, element: JSahibON) -> BasicPredicate<T> {
700 self.inner.array_contains(element)
701 }
702
703 pub fn array_len_eq(&self, len: usize) -> BasicPredicate<T> {
705 self.inner.array_len_eq(len)
706 }
707
708 pub fn array_len_gt(&self, len: usize) -> BasicPredicate<T> {
710 self.inner.array_len_gt(len)
711 }
712
713 pub fn array_len_gte(&self, len: usize) -> BasicPredicate<T> {
715 self.inner.array_len_gte(len)
716 }
717
718 pub fn array_len_lt(&self, len: usize) -> BasicPredicate<T> {
720 self.inner.array_len_lt(len)
721 }
722
723 pub fn array_len_lte(&self, len: usize) -> BasicPredicate<T> {
725 self.inner.array_len_lte(len)
726 }
727 }
728 };
729}
730
731delegate_json_ref_methods!(JSahibONFieldRef);
732delegate_json_ref_methods!(JSahibONOptionFieldRef);
733
734impl<T: 'static> JSahibONPathRef<T> {
735 pub fn key(self, key: impl Into<String>) -> Self {
740 Self {
741 path: self.path.push(key.into()),
742 ..self
743 }
744 }
745
746 pub fn path_segments<I, S>(self, segments: I) -> Self
748 where
749 I: IntoIterator<Item = S>,
750 S: Into<String>,
751 {
752 Self {
753 path: self.path.extend(segments),
754 ..self
755 }
756 }
757
758 pub fn exists(&self) -> BasicPredicate<T> {
761 self.predicate(JSahibONPredicateBody::Exists {
762 path: self.path.clone(),
763 })
764 }
765
766 pub fn missing(&self) -> BasicPredicate<T> {
768 self.predicate(JSahibONPredicateBody::Missing {
769 path: self.path.clone(),
770 })
771 }
772
773 pub fn is_json_null(&self) -> BasicPredicate<T> {
776 self.predicate(JSahibONPredicateBody::IsJsonNull {
777 path: self.path.clone(),
778 })
779 }
780
781 pub fn is_not_json_null(&self) -> BasicPredicate<T> {
784 self.predicate(JSahibONPredicateBody::IsNotJsonNull {
785 path: self.path.clone(),
786 })
787 }
788
789 pub fn is_type(&self, kind: JTypeKind) -> BasicPredicate<T> {
791 self.predicate(JSahibONPredicateBody::Type {
792 path: self.path.clone(),
793 kind,
794 })
795 }
796
797 pub fn is_bool(&self) -> BasicPredicate<T> {
799 self.is_type(JTypeKind::Bool)
800 }
801
802 pub fn is_number(&self) -> BasicPredicate<T> {
805 self.is_type(JTypeKind::Number)
806 }
807
808 pub fn is_string(&self) -> BasicPredicate<T> {
810 self.is_type(JTypeKind::String)
811 }
812
813 pub fn is_array(&self) -> BasicPredicate<T> {
815 self.is_type(JTypeKind::Array)
816 }
817
818 pub fn is_object(&self) -> BasicPredicate<T> {
820 self.is_type(JTypeKind::Object)
821 }
822
823 pub fn has_key(&self, key: impl Into<String>) -> BasicPredicate<T> {
826 self.predicate(JSahibONPredicateBody::HasKey {
827 path: self.path.clone(),
828 key: Arc::new(key.into()),
829 })
830 }
831
832 pub fn has_any_key<I, S>(&self, keys: I) -> BasicPredicate<T>
835 where
836 I: IntoIterator<Item = S>,
837 S: Into<String>,
838 {
839 self.predicate(JSahibONPredicateBody::HasAnyKey {
840 path: self.path.clone(),
841 keys: keys.into_iter().map(Into::into).collect::<Vec<_>>().into(),
842 })
843 }
844
845 pub fn has_all_keys<I, S>(&self, keys: I) -> BasicPredicate<T>
848 where
849 I: IntoIterator<Item = S>,
850 S: Into<String>,
851 {
852 self.predicate(JSahibONPredicateBody::HasAllKeys {
853 path: self.path.clone(),
854 keys: keys.into_iter().map(Into::into).collect::<Vec<_>>().into(),
855 })
856 }
857
858 pub fn value<V: JScalar>(&self) -> JSahibONValueRef<T, V> {
865 JSahibONValueRef {
866 inner: self.clone(),
867 _marker: PhantomData,
868 }
869 }
870
871 pub fn eq_json(&self, value: JSahibON) -> BasicPredicate<T> {
875 self.predicate(JSahibONPredicateBody::JsonEq {
876 path: self.path.clone(),
877 value,
878 })
879 }
880
881 pub fn neq_json(&self, value: JSahibON) -> BasicPredicate<T> {
884 self.predicate(JSahibONPredicateBody::JsonNeq {
885 path: self.path.clone(),
886 value,
887 })
888 }
889
890 pub fn array_contains(&self, element: JSahibON) -> BasicPredicate<T> {
893 self.predicate(JSahibONPredicateBody::ArrayContains {
894 path: self.path.clone(),
895 element,
896 })
897 }
898
899 pub fn array_len_eq(&self, len: usize) -> BasicPredicate<T> {
901 self.array_len(JCompareOp::Eq, len)
902 }
903
904 pub fn array_len_gt(&self, len: usize) -> BasicPredicate<T> {
907 self.array_len(JCompareOp::Gt, len)
908 }
909
910 pub fn array_len_gte(&self, len: usize) -> BasicPredicate<T> {
913 self.array_len(JCompareOp::Gte, len)
914 }
915
916 pub fn array_len_lt(&self, len: usize) -> BasicPredicate<T> {
919 self.array_len(JCompareOp::Lt, len)
920 }
921
922 pub fn array_len_lte(&self, len: usize) -> BasicPredicate<T> {
925 self.array_len(JCompareOp::Lte, len)
926 }
927
928 fn array_len(&self, op: JCompareOp, len: usize) -> BasicPredicate<T> {
929 let len = u64::try_from(len).expect("array length predicate exceeds u64");
930 self.predicate(JSahibONPredicateBody::ArrayLen {
931 path: self.path.clone(),
932 op,
933 len,
934 })
935 }
936
937 fn predicate(&self, body: JSahibONPredicateBody) -> BasicPredicate<T> {
938 let root = self.root;
939 let body: Arc<JSahibONPredicateBody> = Arc::new(body);
940 let body_for_eval = body.clone();
941 let value: Arc<dyn Any + Send + Sync> = body;
942 BasicPredicate::Field(FieldPredicate::new(
943 self.field_name,
944 LookupOp::Json,
945 value,
946 move |entry| evaluate_jsahibon_predicate(root.resolve(entry), body_for_eval.as_ref()),
947 ))
948 }
949}
950
951impl<T: 'static, V: JScalar> JSahibONValueRef<T, V> {
952 pub fn eq(&self, value: V) -> BasicPredicate<T> {
960 self.compare(JCompareOp::Eq, value)
961 }
962
963 pub fn neq(&self, value: V) -> BasicPredicate<T> {
970 self.compare(JCompareOp::Neq, value)
971 }
972
973 pub fn in_(&self, values: Vec<V>) -> BasicPredicate<T> {
981 self.in_predicate(values, JInPolarity::In)
982 }
983
984 pub fn not_in(&self, values: Vec<V>) -> BasicPredicate<T> {
992 self.in_predicate(values, JInPolarity::NotIn)
993 }
994
995 fn compare(&self, op: JCompareOp, value: V) -> BasicPredicate<T> {
996 self.inner.predicate(JSahibONPredicateBody::ScalarCompare {
997 path: self.inner.path.clone(),
998 op,
999 scalar_kind: V::KIND,
1000 operand: value.into_scalar_value(),
1001 })
1002 }
1003
1004 fn in_predicate(&self, values: Vec<V>, polarity: JInPolarity) -> BasicPredicate<T> {
1005 self.inner.predicate(JSahibONPredicateBody::ScalarIn {
1006 path: self.inner.path.clone(),
1007 scalar_kind: V::KIND,
1008 operands: values
1009 .into_iter()
1010 .map(JScalar::into_scalar_value)
1011 .collect::<Vec<_>>()
1012 .into(),
1013 polarity,
1014 })
1015 }
1016}
1017
1018impl<T: 'static, V: JOrderedScalar> JSahibONValueRef<T, V> {
1019 pub fn gt(&self, value: V) -> BasicPredicate<T> {
1025 self.compare(JCompareOp::Gt, value)
1026 }
1027
1028 pub fn gte(&self, value: V) -> BasicPredicate<T> {
1034 self.compare(JCompareOp::Gte, value)
1035 }
1036
1037 pub fn lt(&self, value: V) -> BasicPredicate<T> {
1043 self.compare(JCompareOp::Lt, value)
1044 }
1045
1046 pub fn lte(&self, value: V) -> BasicPredicate<T> {
1052 self.compare(JCompareOp::Lte, value)
1053 }
1054
1055 pub fn between(&self, low: V, high: V) -> BasicPredicate<T> {
1061 self.inner.predicate(JSahibONPredicateBody::ScalarBetween {
1062 path: self.inner.path.clone(),
1063 scalar_kind: V::KIND,
1064 low: low.into_scalar_value(),
1065 high: high.into_scalar_value(),
1066 })
1067 }
1068}
1069
1070pub fn evaluate_jsahibon_predicate(root: Option<&JSahibON>, body: &JSahibONPredicateBody) -> bool {
1081 match body {
1082 JSahibONPredicateBody::Exists { path } => resolve_path(root, path).is_some(),
1083 JSahibONPredicateBody::Missing { path } => resolve_path(root, path).is_none(),
1084 JSahibONPredicateBody::IsJsonNull { path } => {
1085 matches!(resolve_path(root, path), Some(JSahibON::Null))
1086 }
1087 JSahibONPredicateBody::IsNotJsonNull { path } => {
1088 resolve_path(root, path).is_some_and(|value| !matches!(value, JSahibON::Null))
1089 }
1090 JSahibONPredicateBody::Type { path, kind } => {
1091 resolve_path(root, path).is_some_and(|value| matches_type(value, *kind))
1092 }
1093 JSahibONPredicateBody::HasKey { path, key } => {
1094 object_at(root, path).is_some_and(|object| object.get(key.as_str()).is_some())
1095 }
1096 JSahibONPredicateBody::HasAnyKey { path, keys } => object_at(root, path)
1097 .is_some_and(|object| keys.iter().any(|key| object.get(key.as_str()).is_some())),
1098 JSahibONPredicateBody::HasAllKeys { path, keys } => object_at(root, path)
1099 .is_some_and(|object| keys.iter().all(|key| object.get(key.as_str()).is_some())),
1100 JSahibONPredicateBody::ScalarCompare {
1101 path,
1102 op,
1103 scalar_kind,
1104 operand,
1105 } => scalar_at(root, path, *scalar_kind)
1106 .is_some_and(|left| compare_scalar(&left, *op, operand)),
1107 JSahibONPredicateBody::ScalarIn {
1108 path,
1109 scalar_kind,
1110 operands,
1111 polarity,
1112 } => scalar_at(root, path, *scalar_kind).is_some_and(|left| {
1113 let contains = operands.iter().any(|right| &left == right);
1114 match polarity {
1115 JInPolarity::In => contains,
1116 JInPolarity::NotIn => !contains,
1117 }
1118 }),
1119 JSahibONPredicateBody::ScalarBetween {
1120 path,
1121 scalar_kind,
1122 low,
1123 high,
1124 } => scalar_at(root, path, *scalar_kind).is_some_and(|left| {
1125 compare_scalar_order(&left, low).is_some_and(|ordering| ordering != Ordering::Less)
1126 && compare_scalar_order(&left, high)
1127 .is_some_and(|ordering| ordering != Ordering::Greater)
1128 }),
1129 JSahibONPredicateBody::JsonEq { path, value } => {
1130 resolve_path(root, path).is_some_and(|left| left == value)
1131 }
1132 JSahibONPredicateBody::JsonNeq { path, value } => {
1133 resolve_path(root, path).is_some_and(|left| left != value)
1134 }
1135 JSahibONPredicateBody::ArrayContains { path, element } => match resolve_path(root, path) {
1136 Some(JSahibON::Array(values)) => values.iter().any(|value| value == element),
1137 _ => false,
1138 },
1139 JSahibONPredicateBody::ArrayLen { path, op, len } => match resolve_path(root, path) {
1140 Some(JSahibON::Array(values)) => {
1141 compare_u64(u64::try_from(values.len()).unwrap_or(u64::MAX), *op, *len)
1142 }
1143 _ => false,
1144 },
1145 }
1146}
1147
1148fn matches_type(value: &JSahibON, kind: JTypeKind) -> bool {
1149 matches!(
1150 (value, kind),
1151 (JSahibON::Null, JTypeKind::Null)
1152 | (JSahibON::Bool(_), JTypeKind::Bool)
1153 | (
1154 JSahibON::I64(_) | JSahibON::U64(_) | JSahibON::F64(_),
1155 JTypeKind::Number
1156 )
1157 | (JSahibON::String(_), JTypeKind::String)
1158 | (JSahibON::Array(_), JTypeKind::Array)
1159 | (JSahibON::Object(_), JTypeKind::Object)
1160 )
1161}
1162
1163fn resolve_path<'a>(root: Option<&'a JSahibON>, path: &JPath) -> Option<&'a JSahibON> {
1164 let mut current = root?;
1165 for segment in path.segments() {
1166 let JSahibON::Object(object) = current else {
1167 return None;
1168 };
1169 current = object.get(segment)?;
1170 }
1171 Some(current)
1172}
1173
1174fn object_at<'a>(root: Option<&'a JSahibON>, path: &JPath) -> Option<&'a JObject> {
1175 match resolve_path(root, path) {
1176 Some(JSahibON::Object(object)) => Some(object),
1177 _ => None,
1178 }
1179}
1180
1181fn scalar_at(root: Option<&JSahibON>, path: &JPath, kind: JScalarKind) -> Option<JScalarValue> {
1182 let value = resolve_path(root, path)?;
1183 match (kind, value) {
1184 (JScalarKind::I64 | JScalarKind::U64 | JScalarKind::F64, JSahibON::I64(value)) => {
1185 Some(JScalarValue::I64(*value))
1186 }
1187 (JScalarKind::I64 | JScalarKind::U64 | JScalarKind::F64, JSahibON::U64(value)) => {
1188 Some(JScalarValue::U64(*value))
1189 }
1190 (JScalarKind::I64 | JScalarKind::U64 | JScalarKind::F64, JSahibON::F64(value)) => {
1191 Some(JScalarValue::F64(*value))
1192 }
1193 (JScalarKind::String, JSahibON::String(value)) => Some(JScalarValue::String(value.clone())),
1194 (JScalarKind::Bool, JSahibON::Bool(value)) => Some(JScalarValue::Bool(*value)),
1195 _ => None,
1196 }
1197}
1198
1199fn compare_scalar(left: &JScalarValue, op: JCompareOp, right: &JScalarValue) -> bool {
1200 match op {
1201 JCompareOp::Eq => left == right,
1202 JCompareOp::Neq => left != right,
1203 JCompareOp::Gt => {
1204 compare_scalar_order(left, right).is_some_and(|ordering| ordering == Ordering::Greater)
1205 }
1206 JCompareOp::Gte => {
1207 compare_scalar_order(left, right).is_some_and(|ordering| ordering != Ordering::Less)
1208 }
1209 JCompareOp::Lt => {
1210 compare_scalar_order(left, right).is_some_and(|ordering| ordering == Ordering::Less)
1211 }
1212 JCompareOp::Lte => {
1213 compare_scalar_order(left, right).is_some_and(|ordering| ordering != Ordering::Greater)
1214 }
1215 }
1216}
1217
1218fn compare_scalar_order(left: &JScalarValue, right: &JScalarValue) -> Option<Ordering> {
1219 compare_jsahibon_numbers(&scalar_to_jsahibon(left), &scalar_to_jsahibon(right))
1220}
1221
1222fn compare_u64(left: u64, op: JCompareOp, right: u64) -> bool {
1223 match op {
1224 JCompareOp::Eq => left == right,
1225 JCompareOp::Neq => left != right,
1226 JCompareOp::Gt => left > right,
1227 JCompareOp::Gte => left >= right,
1228 JCompareOp::Lt => left < right,
1229 JCompareOp::Lte => left <= right,
1230 }
1231}
1232
1233fn scalar_to_jsahibon(value: &JScalarValue) -> JSahibON {
1234 match value {
1235 JScalarValue::I64(value) => JSahibON::I64(*value),
1236 JScalarValue::U64(value) => JSahibON::U64(*value),
1237 JScalarValue::F64(value) => JSahibON::F64(*value),
1238 JScalarValue::String(value) => JSahibON::String(value.clone()),
1239 JScalarValue::Bool(value) => JSahibON::Bool(*value),
1240 }
1241}