1use super::err::{
23 error_body::{self},
24 PstConstructionError,
25};
26use crate::ast;
27use crate::expr_builder::ExprBuilder;
28use crate::extensions::Extensions;
29use smol_str::{SmolStr, ToSmolStr};
30use std::collections::{BTreeMap, HashSet};
31use std::fmt::Display;
32use std::str::FromStr;
33use std::sync::Arc;
34
35mod constants {
37 pub static NOT_EQ_STR: &str = "!=";
39 pub static GREATER_STR: &str = ">";
40 pub static GREATER_EQ_STR: &str = ">=";
41 pub static AND_STR: &str = "&&";
42 pub static OR_STR: &str = "||";
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
66pub struct Id(SmolStr);
67
68impl Id {
69 pub fn new(s: impl AsRef<str>) -> Result<Self, PstConstructionError> {
71 let ast_id = ast::Id::from_str(s.as_ref())?;
72 Ok(Self(ast_id.into_smolstr()))
73 }
74
75 pub fn as_str(&self) -> &str {
77 &self.0
78 }
79
80 pub fn into_smolstr(self) -> SmolStr {
82 self.0
83 }
84}
85
86impl AsRef<str> for Id {
87 fn as_ref(&self) -> &str {
88 &self.0
89 }
90}
91
92impl Display for Id {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 write!(f, "{}", &self.0)
95 }
96}
97
98impl From<ast::Id> for Id {
100 fn from(id: ast::Id) -> Self {
101 Id(id.into_smolstr())
102 }
103}
104
105#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
120#[non_exhaustive]
121pub enum SlotId {
122 Principal,
124 Resource,
126}
127
128impl Display for SlotId {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 let b: ast::SlotId = (*self).into();
131 write!(f, "{}", b)
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Hash)]
150pub struct Name {
151 pub id: Id,
153 pub namespace: Arc<Vec<Id>>,
155}
156
157impl Name {
158 pub fn unqualified(id: impl AsRef<str>) -> Result<Self, PstConstructionError> {
164 Ok(Name {
165 id: Id::new(id)?,
166 namespace: Arc::new(vec![]),
167 })
168 }
169
170 pub fn qualified<I, T>(namespace: I, id: impl AsRef<str>) -> Result<Self, PstConstructionError>
175 where
176 I: IntoIterator<Item = T>,
177 T: AsRef<str>,
178 {
179 let ns: Result<Vec<Id>, _> = namespace.into_iter().map(|s| Id::new(s)).collect();
180 Ok(Name {
181 id: Id::new(id)?,
182 namespace: Arc::new(ns?),
183 })
184 }
185}
186
187impl Display for Name {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 for elem in self.namespace.as_ref() {
190 write!(f, "{elem}::")?;
191 }
192 write!(f, "{}", self.id)?;
193 Ok(())
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Hash)]
206pub struct EntityType(pub Name);
207
208impl EntityType {
209 pub fn from_name(name: impl Into<Name>) -> Self {
211 EntityType(name.into())
212 }
213}
214
215impl Display for EntityType {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 let ast_et: ast::EntityType = self.clone().into();
218 write!(f, "{}", ast_et)
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, Hash)]
232pub struct EntityUID {
233 pub ty: EntityType,
235 pub eid: SmolStr,
237}
238
239impl Display for EntityUID {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 write!(f, "{}::\"{}\"", self.ty, self.eid.as_str().escape_default())
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, Eq, Hash)]
256pub enum Var {
257 Principal,
259 Action,
261 Resource,
263 Context,
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
301#[non_exhaustive]
302pub enum UnaryOp {
303 Not,
305 Neg,
307 IsEmpty,
309 Datetime,
311 Decimal,
313 Duration,
315 Ip,
317 IsIPv4,
319 IsIPV6,
321 IsLoopback,
323 IsMulticast,
325 ToDate,
327 ToTime,
329 ToMilliseconds,
331 ToSeconds,
333 ToMinutes,
335 ToHours,
337 ToDays,
339}
340
341impl UnaryOp {
342 pub(crate) fn to_name(self) -> Option<&'static ast::Name> {
343 use crate::extensions;
346 match self {
347 UnaryOp::IsEmpty | UnaryOp::Neg | UnaryOp::Not => None,
348 UnaryOp::Datetime => Some(&extensions::datetime::constants::DATETIME_CONSTRUCTOR_NAME),
349 UnaryOp::Decimal => Some(&extensions::decimal::constants::DECIMAL_FROM_STR_NAME),
350 UnaryOp::Duration => Some(&extensions::datetime::constants::DURATION_CONSTRUCTOR_NAME),
351 UnaryOp::Ip => Some(&extensions::ipaddr::names::IP_FROM_STR_NAME),
352 UnaryOp::IsIPv4 => Some(&extensions::ipaddr::names::IS_IPV4),
353 UnaryOp::IsIPV6 => Some(&extensions::ipaddr::names::IS_IPV6),
354 UnaryOp::IsLoopback => Some(&extensions::ipaddr::names::IS_LOOPBACK),
355 UnaryOp::IsMulticast => Some(&extensions::ipaddr::names::IS_MULTICAST),
356 UnaryOp::ToDate => Some(&extensions::datetime::constants::TO_DATE_NAME),
357 UnaryOp::ToTime => Some(&extensions::datetime::constants::TO_TIME_NAME),
358 UnaryOp::ToMilliseconds => Some(&extensions::datetime::constants::TO_MILLISECONDS_NAME),
359 UnaryOp::ToSeconds => Some(&extensions::datetime::constants::TO_SECONDS_NAME),
360 UnaryOp::ToMinutes => Some(&extensions::datetime::constants::TO_MINUTES_NAME),
361 UnaryOp::ToHours => Some(&extensions::datetime::constants::TO_HOURS_NAME),
362 UnaryOp::ToDays => Some(&extensions::datetime::constants::TO_DAYS_NAME),
363 }
364 }
365
366 pub(crate) fn from_function_name(name: &str) -> Option<Self> {
368 match name {
369 "decimal" => Some(UnaryOp::Decimal),
370 "datetime" => Some(UnaryOp::Datetime),
371 "duration" => Some(UnaryOp::Duration),
372 "ip" => Some(UnaryOp::Ip),
373 "isIpv4" => Some(UnaryOp::IsIPv4),
374 "isIpv6" => Some(UnaryOp::IsIPV6),
375 "isLoopback" => Some(UnaryOp::IsLoopback),
376 "isMulticast" => Some(UnaryOp::IsMulticast),
377 "toDate" => Some(UnaryOp::ToDate),
378 "toTime" => Some(UnaryOp::ToTime),
379 "toMilliseconds" => Some(UnaryOp::ToMilliseconds),
380 "toSeconds" => Some(UnaryOp::ToSeconds),
381 "toMinutes" => Some(UnaryOp::ToMinutes),
382 "toHours" => Some(UnaryOp::ToHours),
383 "toDays" => Some(UnaryOp::ToDays),
384 _ => None,
385 }
386 }
387}
388
389impl Display for UnaryOp {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 match self {
392 UnaryOp::Not => write!(f, "{}", ast::UnaryOp::Not),
393 UnaryOp::Neg => write!(f, "{}", ast::UnaryOp::Neg),
394 UnaryOp::IsEmpty => write!(f, "{}", ast::UnaryOp::IsEmpty),
395 _ => match self.to_name() {
397 Some(name) => write!(f, "{}", name),
398 None => write!(f, "<impossible operator>"),
399 },
400 }
401 }
402}
403
404#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
446#[non_exhaustive]
447pub enum BinaryOp {
448 Eq,
450 NotEq,
452 Less,
454 LessEq,
456 Greater,
458 GreaterEq,
460 And,
462 Or,
464 Add,
466 Sub,
468 Mul,
470 In,
472 Contains,
474 ContainsAll,
476 ContainsAny,
478 GetTag,
480 HasTag,
482 IsInRange,
484 Offset,
486 DurationSince,
488 DecimalLessThan,
490 DecimalLessEq,
492 DecimalGreater,
494 DecimalGreaterEq,
496}
497
498impl BinaryOp {
499 pub(crate) fn to_name(self) -> Option<&'static ast::Name> {
500 use crate::extensions;
501 match self {
502 BinaryOp::IsInRange => Some(&extensions::ipaddr::names::IS_IN_RANGE),
503 BinaryOp::Offset => Some(&extensions::datetime::constants::OFFSET_METHOD_NAME),
504 BinaryOp::DurationSince => Some(&extensions::datetime::constants::DURATION_SINCE_NAME),
505 BinaryOp::DecimalLessThan => Some(&extensions::decimal::constants::LESS_THAN),
506 BinaryOp::DecimalLessEq => Some(&extensions::decimal::constants::LESS_THAN_OR_EQUAL),
507 BinaryOp::DecimalGreater => Some(&extensions::decimal::constants::GREATER_THAN),
508 BinaryOp::DecimalGreaterEq => {
509 Some(&extensions::decimal::constants::GREATER_THAN_OR_EQUAL)
510 }
511 BinaryOp::Eq
513 | BinaryOp::NotEq
514 | BinaryOp::And
515 | BinaryOp::Or
516 | BinaryOp::Less
517 | BinaryOp::LessEq
518 | BinaryOp::Greater
519 | BinaryOp::GreaterEq
520 | BinaryOp::Add
521 | BinaryOp::Sub
522 | BinaryOp::Mul
523 | BinaryOp::In
524 | BinaryOp::Contains
525 | BinaryOp::ContainsAll
526 | BinaryOp::ContainsAny
527 | BinaryOp::GetTag
528 | BinaryOp::HasTag => None,
529 }
530 }
531
532 pub(crate) fn from_function_name(name: &str) -> Option<Self> {
534 match name {
535 "lessThan" => Some(BinaryOp::DecimalLessThan),
536 "lessThanOrEqual" => Some(BinaryOp::DecimalLessEq),
537 "greaterThan" => Some(BinaryOp::DecimalGreater),
538 "greaterThanOrEqual" => Some(BinaryOp::DecimalGreaterEq),
539 "isInRange" => Some(BinaryOp::IsInRange),
540 "offset" => Some(BinaryOp::Offset),
541 "durationSince" => Some(BinaryOp::DurationSince),
542 _ => None,
543 }
544 }
545}
546
547impl Display for BinaryOp {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 match self {
550 BinaryOp::Eq => write!(f, "{}", ast::BinaryOp::Eq),
551 BinaryOp::NotEq => write!(f, "{}", &constants::NOT_EQ_STR),
552 BinaryOp::Less => write!(f, "{}", ast::BinaryOp::Less),
553 BinaryOp::LessEq => write!(f, "{}", ast::BinaryOp::LessEq),
554 BinaryOp::Greater => write!(f, "{}", &constants::GREATER_STR),
555 BinaryOp::GreaterEq => write!(f, "{}", &constants::GREATER_EQ_STR),
556 BinaryOp::And => write!(f, "{}", &constants::AND_STR),
557 BinaryOp::Or => write!(f, "{}", &constants::OR_STR),
558 BinaryOp::Add => write!(f, "{}", ast::BinaryOp::Add),
559 BinaryOp::Sub => write!(f, "{}", ast::BinaryOp::Sub),
560 BinaryOp::Mul => write!(f, "{}", ast::BinaryOp::Mul),
561 BinaryOp::In => write!(f, "{}", ast::BinaryOp::In),
562 BinaryOp::Contains => write!(f, "{}", ast::BinaryOp::Contains),
563 BinaryOp::ContainsAll => write!(f, "{}", ast::BinaryOp::ContainsAll),
564 BinaryOp::ContainsAny => write!(f, "{}", ast::BinaryOp::ContainsAny),
565 BinaryOp::GetTag => write!(f, "{}", ast::BinaryOp::GetTag),
566 BinaryOp::HasTag => write!(f, "{}", ast::BinaryOp::HasTag),
567 _ => match self.to_name() {
569 Some(name) => write!(f, "{}", name),
570 None => write!(f, "<impossible operator>"),
571 },
572 }
573 }
574}
575
576#[cfg(feature = "variadic-is-in-range")]
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
583#[non_exhaustive]
584pub enum VariadicOp {
585 IsInRange,
587}
588
589#[cfg(feature = "variadic-is-in-range")]
590impl VariadicOp {
591 pub(crate) fn to_name(self) -> Option<&'static ast::Name> {
592 self.to_binop().to_name()
593 }
594
595 pub(crate) fn from_function_name(name: &str) -> Option<Self> {
597 match name {
598 "isInRange" => Some(VariadicOp::IsInRange),
599 _ => None,
600 }
601 }
602
603 pub(crate) fn to_binop(self) -> BinaryOp {
605 match self {
606 VariadicOp::IsInRange => BinaryOp::IsInRange,
607 }
608 }
609}
610
611#[cfg(feature = "variadic-is-in-range")]
612impl Display for VariadicOp {
613 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
614 write!(f, "{}", self.to_binop())
615 }
616}
617
618#[derive(Debug, Clone, PartialEq, Eq, Hash)]
629#[non_exhaustive]
630pub enum Literal {
631 Bool(bool),
633 Long(i64),
635 String(SmolStr),
637 EntityUID(EntityUID),
639}
640
641#[derive(Debug, Clone, PartialEq, Eq, Hash)]
650pub enum PatternElem {
651 Char(char),
653 Wildcard,
655}
656
657#[derive(Debug, Clone, PartialEq, Eq)]
664#[non_exhaustive]
665pub enum Expr {
666 Literal(Literal),
668 Var(Var),
670 Slot(SlotId),
672 UnaryOp {
681 op: UnaryOp,
683 expr: Arc<Expr>,
685 },
686 BinaryOp {
694 op: BinaryOp,
696 left: Arc<Expr>,
698 right: Arc<Expr>,
700 },
701 GetAttr {
708 expr: Arc<Expr>,
710 attr: SmolStr,
712 },
713 HasAttr {
722 expr: Arc<Expr>,
724 attrs: nonempty::NonEmpty<SmolStr>,
726 },
727 Like {
733 expr: Arc<Expr>,
735 pattern: Vec<PatternElem>,
737 },
738 Is {
745 expr: Arc<Expr>,
747 entity_type: EntityType,
749 in_expr: Option<Arc<Expr>>,
751 },
752 IfThenElse {
758 cond: Arc<Expr>,
760 then_expr: Arc<Expr>,
762 else_expr: Arc<Expr>,
764 },
765 Set(Vec<Arc<Expr>>),
772 Record(BTreeMap<String, Arc<Expr>>),
778 Unknown {
780 name: SmolStr,
782 },
783 #[cfg(feature = "tpe")]
790 ResidualError,
791 #[cfg(feature = "variadic-is-in-range")]
793 VariadicOp {
794 op: VariadicOp,
796 left: Arc<Expr>,
798 rights: nonempty::NonEmpty<Arc<Expr>>,
800 },
801}
802
803impl Expr {
804 pub(crate) fn from_function_ast_name_and_args(
807 name: &ast::Name,
808 args: Vec<Arc<Expr>>,
809 ) -> Result<Expr, PstConstructionError> {
810 Self::from_function_names_and_args(name.to_smolstr(), name, args)
811 }
812
813 fn from_function_names_and_args(
817 name: SmolStr,
818 ast_name: &ast::Name,
819 args: Vec<Arc<Expr>>,
820 ) -> Result<Expr, PstConstructionError> {
821 #[cfg(feature = "tpe")]
824 if *ast_name == *crate::tpe::residual::ERROR_NAME {
825 return Ok(Expr::ResidualError);
826 }
827
828 let extension = Extensions::all_available().func(ast_name)?;
829
830 let expected = extension.arg_types().len();
831 let got = args.len();
832
833 #[cfg(feature = "variadic-is-in-range")]
834 let arity_ok = if extension.is_variadic() {
835 got >= expected
836 } else {
837 got == expected
838 };
839 #[cfg(not(feature = "variadic-is-in-range"))]
840 let arity_ok = got == expected;
841
842 if !arity_ok {
843 return Err(error_body::WrongArityError::new(name.into(), expected, got).into());
844 }
845 Ok(match args.len() {
846 1 => {
847 #[expect(clippy::unwrap_used, reason = "length = 1 checked in arm")]
848 let expr = args.into_iter().next().unwrap();
849 if ast_name.to_string() == "unknown" {
851 return Ok(Expr::Unknown {
852 name: format!("{}", expr).into(),
853 });
854 }
855 let op = UnaryOp::from_function_name(&ast_name.to_string())
856 .ok_or_else(|| error_body::UnknownFunctionError::new(name.clone()))?;
857 Expr::UnaryOp { op, expr }
858 }
859 2 => {
860 let op = BinaryOp::from_function_name(&ast_name.to_string())
861 .ok_or_else(|| error_body::UnknownFunctionError::new(name.clone()))?;
862 let mut iter = args.into_iter();
863 Expr::BinaryOp {
864 op,
865 #[expect(clippy::unwrap_used, reason = "length = 2 checked in match arm")]
866 left: iter.next().unwrap(),
867 #[expect(clippy::unwrap_used, reason = "length = 2 checked in match arm")]
868 right: iter.next().unwrap(),
869 }
870 }
871 #[cfg(feature = "variadic-is-in-range")]
872 _ => {
873 let op = VariadicOp::from_function_name(&ast_name.to_string())
874 .ok_or_else(|| error_body::UnknownFunctionError::new(name.clone()))?;
875 let mut iter = args.into_iter();
876 #[expect(clippy::unwrap_used, reason = "length >= 3 checked in match arm")]
877 let left = iter.next().unwrap();
878 #[expect(clippy::unwrap_used, reason = "length >= 3 checked in match arm")]
879 let first_right = iter.next().unwrap();
880 let rights = nonempty::NonEmpty {
881 head: first_right,
882 tail: iter.collect(),
883 };
884 Expr::VariadicOp { op, left, rights }
885 }
886 #[cfg(not(feature = "variadic-is-in-range"))]
887 _ => return Err(error_body::UnknownFunctionError::new(name).into()),
888 })
889 }
890
891 pub fn reduce<T: Clone + Sized>(
900 &self,
901 f: &dyn Fn(&Self) -> Option<T>,
902 op: &dyn Fn(T, T) -> T,
903 zero: T,
904 ) -> T {
905 if let Some(t) = f(self) {
906 return t;
907 }
908 let recurse = |e: &Arc<Self>| e.reduce(f, op, zero.clone());
909 match self {
910 Expr::Literal(_) | Expr::Var(_) | Expr::Slot(_) | Expr::Unknown { .. } => zero,
911 #[cfg(feature = "tpe")]
912 Expr::ResidualError => zero,
913 Expr::UnaryOp { expr, .. }
914 | Expr::GetAttr { expr, .. }
915 | Expr::HasAttr { expr, .. }
916 | Expr::Like { expr, .. } => recurse(expr),
917 Expr::BinaryOp { left, right, .. } => op(recurse(left), recurse(right)),
918 Expr::Is { expr, in_expr, .. } => match in_expr {
919 Some(e) => op(recurse(expr), recurse(e)),
920 None => recurse(expr),
921 },
922 Expr::IfThenElse {
923 cond,
924 then_expr,
925 else_expr,
926 } => op(op(recurse(cond), recurse(then_expr)), recurse(else_expr)),
927 Expr::Set(exprs) => {
928 let mut iter = exprs.iter();
929 match iter.next() {
930 None => zero,
931 Some(first) => iter.fold(recurse(first), |acc, e| op(acc, recurse(e))),
932 }
933 }
934 Expr::Record(map) => {
935 let mut iter = map.values();
936 match iter.next() {
937 None => zero,
938 Some(first) => iter.fold(recurse(first), |acc, e| op(acc, recurse(e))),
939 }
940 }
941 #[cfg(feature = "variadic-is-in-range")]
942 Expr::VariadicOp { left, rights, .. } => rights
943 .iter()
944 .fold(recurse(left), |acc, e| op(acc, recurse(e))),
945 }
946 }
947
948 pub fn has_slots(&self) -> bool {
950 self.reduce::<bool>(
951 &|e| match e {
952 Expr::Slot(_) => Some(true),
953 _ => None,
954 },
955 &|a, b| a || b,
956 false,
957 )
958 }
959
960 pub fn has_unknowns(&self) -> bool {
962 self.reduce::<bool>(
963 &|e| match e {
964 Expr::Unknown { .. } => Some(true),
965 _ => None,
966 },
967 &|a, b| a || b,
968 false,
969 )
970 }
971
972 pub fn slots(&self) -> HashSet<SlotId> {
974 self.reduce::<HashSet<SlotId>>(
975 &|e| match e {
976 Expr::Slot(id) => Some(HashSet::from([*id])),
977 _ => None,
978 },
979 &|a, b| a.union(&b).copied().collect(),
980 HashSet::new(),
981 )
982 }
983
984 #[cfg(feature = "tpe")]
993 pub fn has_error(&self) -> bool {
994 self.reduce::<bool>(
995 &|e| match e {
996 Expr::ResidualError => Some(true),
997 _ => None,
998 },
999 &|a, b| a || b,
1000 false,
1001 )
1002 }
1003}
1004
1005#[derive(Clone, Debug)]
1009pub(crate) struct PstBuilder;
1010
1011impl ExprBuilder for PstBuilder {
1012 type Expr = Expr;
1013 type Data = ();
1014 type BuildError = PstConstructionError;
1015
1016 #[cfg(feature = "tolerant-ast")]
1017 type ErrorType = crate::parser::err::ParseErrors;
1018
1019 fn with_data(_data: Self::Data) -> Self {
1020 Self
1021 }
1022
1023 fn with_maybe_source_loc(self, _: Option<&crate::parser::Loc>) -> Self {
1024 self
1026 }
1027
1028 fn loc(&self) -> Option<&crate::parser::Loc> {
1029 None
1030 }
1031
1032 fn data(&self) -> &Self::Data {
1033 &()
1034 }
1035
1036 fn val(self, lit: impl Into<ast::Literal>) -> Expr {
1037 Expr::Literal(From::<ast::Literal>::from(lit.into()))
1038 }
1039
1040 fn var(self, var: ast::Var) -> Expr {
1041 Expr::Var(var.into())
1042 }
1043
1044 fn unknown(self, u: ast::Unknown) -> Expr {
1045 Expr::Unknown { name: u.name }
1046 }
1047
1048 fn slot(self, s: ast::SlotId) -> Expr {
1049 Expr::Slot(s.into())
1050 }
1051
1052 fn ite_arc(self, cond: Arc<Expr>, then_expr: Arc<Expr>, else_expr: Arc<Expr>) -> Expr {
1053 Expr::IfThenElse {
1054 cond,
1055 then_expr,
1056 else_expr,
1057 }
1058 }
1059
1060 fn not(self, e: Expr) -> Expr {
1061 Expr::UnaryOp {
1062 op: UnaryOp::Not,
1063 expr: Arc::new(e),
1064 }
1065 }
1066
1067 fn is_eq(self, e1: Expr, e2: Expr) -> Expr {
1068 Expr::BinaryOp {
1069 op: BinaryOp::Eq,
1070 left: Arc::new(e1),
1071 right: Arc::new(e2),
1072 }
1073 }
1074
1075 fn noteq(self, e1: Expr, e2: Expr) -> Expr {
1076 Expr::BinaryOp {
1077 op: BinaryOp::NotEq,
1078 left: Arc::new(e1),
1079 right: Arc::new(e2),
1080 }
1081 }
1082
1083 fn and(self, e1: Expr, e2: Expr) -> Expr {
1084 Expr::BinaryOp {
1085 op: BinaryOp::And,
1086 left: Arc::new(e1),
1087 right: Arc::new(e2),
1088 }
1089 }
1090
1091 fn or(self, e1: Expr, e2: Expr) -> Expr {
1092 Expr::BinaryOp {
1093 op: BinaryOp::Or,
1094 left: Arc::new(e1),
1095 right: Arc::new(e2),
1096 }
1097 }
1098
1099 fn less(self, e1: Expr, e2: Expr) -> Expr {
1100 Expr::BinaryOp {
1101 op: BinaryOp::Less,
1102 left: Arc::new(e1),
1103 right: Arc::new(e2),
1104 }
1105 }
1106
1107 fn lesseq(self, e1: Expr, e2: Expr) -> Expr {
1108 Expr::BinaryOp {
1109 op: BinaryOp::LessEq,
1110 left: Arc::new(e1),
1111 right: Arc::new(e2),
1112 }
1113 }
1114
1115 fn greater(self, e1: Expr, e2: Expr) -> Expr {
1116 Expr::BinaryOp {
1117 op: BinaryOp::Greater,
1118 left: Arc::new(e1),
1119 right: Arc::new(e2),
1120 }
1121 }
1122
1123 fn greatereq(self, e1: Expr, e2: Expr) -> Expr {
1124 Expr::BinaryOp {
1125 op: BinaryOp::GreaterEq,
1126 left: Arc::new(e1),
1127 right: Arc::new(e2),
1128 }
1129 }
1130
1131 fn add(self, e1: Expr, e2: Expr) -> Expr {
1132 Expr::BinaryOp {
1133 op: BinaryOp::Add,
1134 left: Arc::new(e1),
1135 right: Arc::new(e2),
1136 }
1137 }
1138
1139 fn sub(self, e1: Expr, e2: Expr) -> Expr {
1140 Expr::BinaryOp {
1141 op: BinaryOp::Sub,
1142 left: Arc::new(e1),
1143 right: Arc::new(e2),
1144 }
1145 }
1146
1147 fn mul(self, e1: Expr, e2: Expr) -> Expr {
1148 Expr::BinaryOp {
1149 op: BinaryOp::Mul,
1150 left: Arc::new(e1),
1151 right: Arc::new(e2),
1152 }
1153 }
1154
1155 fn neg(self, e: Expr) -> Expr {
1156 Expr::UnaryOp {
1157 op: UnaryOp::Neg,
1158 expr: Arc::new(e),
1159 }
1160 }
1161
1162 fn is_in_arc(self, left: Arc<Expr>, right: Arc<Expr>) -> Expr {
1163 Expr::BinaryOp {
1164 op: BinaryOp::In,
1165 left,
1166 right,
1167 }
1168 }
1169
1170 fn contains(self, e1: Expr, e2: Expr) -> Expr {
1171 Expr::BinaryOp {
1172 op: BinaryOp::Contains,
1173 left: Arc::new(e1),
1174 right: Arc::new(e2),
1175 }
1176 }
1177
1178 fn contains_all(self, e1: Expr, e2: Expr) -> Expr {
1179 Expr::BinaryOp {
1180 op: BinaryOp::ContainsAll,
1181 left: Arc::new(e1),
1182 right: Arc::new(e2),
1183 }
1184 }
1185
1186 fn contains_any(self, e1: Expr, e2: Expr) -> Expr {
1187 Expr::BinaryOp {
1188 op: BinaryOp::ContainsAny,
1189 left: Arc::new(e1),
1190 right: Arc::new(e2),
1191 }
1192 }
1193
1194 fn is_empty(self, expr: Expr) -> Expr {
1195 Expr::UnaryOp {
1196 op: UnaryOp::IsEmpty,
1197 expr: Arc::new(expr),
1198 }
1199 }
1200
1201 fn get_tag(self, expr: Expr, tag: Expr) -> Expr {
1202 Expr::BinaryOp {
1203 op: BinaryOp::GetTag,
1204 left: Arc::new(expr),
1205 right: Arc::new(tag),
1206 }
1207 }
1208
1209 fn has_tag(self, expr: Expr, tag: Expr) -> Expr {
1210 Expr::BinaryOp {
1211 op: BinaryOp::HasTag,
1212 left: Arc::new(expr),
1213 right: Arc::new(tag),
1214 }
1215 }
1216
1217 fn set(self, exprs: impl IntoIterator<Item = Expr>) -> Expr {
1218 Expr::Set(exprs.into_iter().map(Arc::new).collect())
1219 }
1220
1221 fn record(
1222 self,
1223 pairs: impl IntoIterator<Item = (SmolStr, Expr)>,
1224 ) -> Result<Expr, ast::ExpressionConstructionError> {
1225 let mut map = BTreeMap::new();
1226 for (k, v) in pairs {
1227 if map.insert(k.to_string(), Arc::new(v)).is_some() {
1228 return Err(ast::expression_construction_errors::DuplicateKeyError {
1229 key: k,
1230 context: "in record literal",
1231 }
1232 .into());
1233 }
1234 }
1235 Ok(Expr::Record(map))
1236 }
1237
1238 fn call_extension_fn(
1239 self,
1240 fn_name: ast::Name,
1241 args: impl IntoIterator<Item = Expr>,
1242 ) -> Result<Expr, PstConstructionError> {
1243 Expr::from_function_ast_name_and_args(&fn_name, args.into_iter().map(Arc::new).collect())
1244 }
1245
1246 fn get_attr_arc(self, expr: Arc<Expr>, attr: SmolStr) -> Expr {
1247 Expr::GetAttr { expr, attr }
1248 }
1249
1250 fn has_attr_arc(self, expr: Arc<Expr>, attr: SmolStr) -> Expr {
1251 Expr::HasAttr {
1252 expr,
1253 attrs: nonempty::nonempty![attr],
1254 }
1255 }
1256
1257 fn extended_has_attr_arc(self, expr: Arc<Expr>, attrs: nonempty::NonEmpty<SmolStr>) -> Expr {
1258 Expr::HasAttr { expr, attrs }
1259 }
1260
1261 fn like(self, expr: Expr, pattern: ast::Pattern) -> Expr {
1262 Expr::Like {
1263 expr: Arc::new(expr),
1264 pattern: pattern.into(),
1265 }
1266 }
1267
1268 fn is_entity_type_arc(self, expr: Arc<Expr>, entity_type: ast::EntityType) -> Expr {
1269 Expr::Is {
1270 expr,
1271 entity_type: entity_type.into(),
1272 in_expr: None,
1273 }
1274 }
1275
1276 fn is_in_entity_type(self, e1: Expr, entity_type: ast::EntityType, e2: Expr) -> Expr {
1277 Expr::Is {
1278 expr: Arc::new(e1),
1279 entity_type: entity_type.into(),
1280 in_expr: Some(Arc::new(e2)),
1281 }
1282 }
1283
1284 #[cfg(feature = "tolerant-ast")]
1285 fn error(
1286 self,
1287 parse_errors: crate::parser::err::ParseErrors,
1288 ) -> Result<Self::Expr, Self::ErrorType> {
1289 Err(parse_errors)
1291 }
1292}
1293
1294impl std::fmt::Display for Expr {
1295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1296 let est: crate::est::Expr = self.clone().into();
1297 write!(f, "{est}")
1298 }
1299}
1300
1301#[expect(
1302 clippy::fallible_impl_from,
1303 reason = "AST records cannot have duplicate keys, so builder.record() cannot fail"
1304)]
1305#[cfg(test)]
1306mod tests {
1307 use cool_asserts::{assert_matches, assertion_failure};
1308
1309 use super::*;
1310 use std::str::FromStr;
1311
1312 #[test]
1315 fn test_id_valid_identifiers() {
1316 assert!(Id::new("x").is_ok());
1318 assert!(Id::new("userName").is_ok());
1319 assert!(Id::new("_private").is_ok());
1320 assert!(Id::new("a1").is_ok());
1321 assert!(Id::new("ABC").is_ok());
1322 }
1323
1324 #[test]
1325 fn test_id_reserved_keywords_rejected() {
1326 for kw in [
1327 "if", "then", "else", "true", "false", "in", "is", "like", "has",
1328 ] {
1329 assert!(Id::new(kw).is_err(), "keyword `{kw}` should be rejected");
1330 }
1331 }
1332
1333 #[test]
1334 fn test_id_invalid_strings_rejected() {
1335 assert!(Id::new("").is_err());
1336 assert!(Id::new("1abc").is_err()); assert!(Id::new("a b").is_err()); assert!(Id::new("a+b").is_err()); assert!(Id::new("::").is_err());
1340 }
1341
1342 #[test]
1343 fn test_id_accessors() {
1344 let id = Id::new("hello").unwrap();
1345 assert_eq!(id.as_str(), "hello");
1346 assert_eq!(id.as_ref(), "hello");
1347 assert_eq!(id.to_string(), "hello");
1348 assert_eq!(id.clone().into_smolstr(), SmolStr::from("hello"));
1349 }
1350
1351 #[test]
1352 fn test_id_equality_and_ordering() {
1353 let a = Id::new("aaa").unwrap();
1354 let b = Id::new("bbb").unwrap();
1355 let a2 = Id::new("aaa").unwrap();
1356 assert_eq!(a, a2);
1357 assert_ne!(a, b);
1358 assert!(a < b);
1359 }
1360
1361 #[test]
1362 fn test_id_from_ast_id() {
1363 let ast_id = crate::ast::Id::from_str("myIdent").unwrap();
1364 let pst_id = Id::from(ast_id);
1365 assert_eq!(pst_id.as_str(), "myIdent");
1366 }
1367
1368 #[test]
1371 fn test_name_unqualified() {
1372 let name = Name::unqualified("User").unwrap();
1373 assert_eq!(name.id.as_str(), "User");
1374 assert!(name.namespace.is_empty());
1375 assert_eq!(name.to_string(), "User");
1376 }
1377
1378 #[test]
1379 fn test_name_qualified() {
1380 let name = Name::qualified(["MyApp", "Auth"], "User").unwrap();
1381 assert_eq!(name.id.as_str(), "User");
1382 assert_eq!(name.namespace.len(), 2);
1383 assert_eq!(name.namespace[0].as_str(), "MyApp");
1384 assert_eq!(name.namespace[1].as_str(), "Auth");
1385 assert_eq!(name.to_string(), "MyApp::Auth::User");
1386 }
1387
1388 #[test]
1389 fn test_name_rejects_invalid_basename() {
1390 assert!(Name::unqualified("if").is_err());
1391 assert!(Name::unqualified("1bad").is_err());
1392 assert!(Name::qualified(["Good"], "if").is_err());
1393 }
1394
1395 #[test]
1396 fn test_name_rejects_invalid_namespace_component() {
1397 assert!(Name::qualified(["true"], "User").is_err());
1398 assert!(Name::qualified(["ok", "1bad"], "User").is_err());
1399 }
1400
1401 #[test]
1402 fn test_name_roundtrip_through_ast() {
1403 let pst_name = Name::qualified(["NS"], "Foo").unwrap();
1404 let ast_name: crate::ast::Name = pst_name.clone().into();
1405 let back: Name = ast_name.into();
1406 assert_eq!(pst_name, back);
1407 }
1408
1409 #[test]
1412 fn test_entity_type_display_with_valid_name() {
1413 let et = EntityType::from_name(Name::unqualified("User").unwrap());
1414 assert_eq!(et.to_string(), "User");
1415 let et = EntityType::from_name(Name::qualified(["App"], "Photo").unwrap());
1416 assert_eq!(et.to_string(), "App::Photo");
1417 }
1418
1419 #[test]
1420 fn test_entity_uid_roundtrip_through_ast() {
1421 let uid = EntityUID {
1422 ty: EntityType::from_name(Name::qualified(["NS"], "Type").unwrap()),
1423 eid: SmolStr::from("eid123"),
1424 };
1425 let ast_uid: crate::ast::EntityUID = uid.clone().into();
1426 let back: EntityUID = ast_uid.into();
1427 assert_eq!(uid, back);
1428 }
1429
1430 #[test]
1431 fn test_has_slots() {
1432 assert!(!Expr::Literal(Literal::Long(1)).has_slots());
1434 assert!(!Expr::Var(Var::Principal).has_slots());
1436 assert!(Expr::Slot(SlotId::Principal).has_slots());
1438 assert!(Expr::Slot(SlotId::Resource).has_slots());
1439 let slot = Arc::new(Expr::Slot(SlotId::Principal));
1441 let lit = Arc::new(Expr::Literal(Literal::Long(42)));
1442 let binop = Expr::BinaryOp {
1443 op: BinaryOp::Eq,
1444 left: slot,
1445 right: lit.clone(),
1446 };
1447 assert!(binop.has_slots());
1448 let binop_no_slot = Expr::BinaryOp {
1450 op: BinaryOp::Eq,
1451 left: lit.clone(),
1452 right: lit.clone(),
1453 };
1454 assert!(!binop_no_slot.has_slots());
1455 let set_with_slot = Expr::Set(vec![lit.clone(), Arc::new(Expr::Slot(SlotId::Resource))]);
1457 assert!(set_with_slot.has_slots());
1458 assert!(!Expr::Set(vec![]).has_slots());
1460 let ite = Expr::IfThenElse {
1462 cond: lit.clone(),
1463 then_expr: lit.clone(),
1464 else_expr: Arc::new(Expr::Slot(SlotId::Principal)),
1465 };
1466 assert!(ite.has_slots());
1467 #[cfg(feature = "variadic-is-in-range")]
1469 {
1470 let variadic_no_slot = Expr::VariadicOp {
1471 op: super::VariadicOp::IsInRange,
1472 left: lit.clone(),
1473 rights: nonempty::nonempty![lit.clone(), lit.clone()],
1474 };
1475 assert!(!variadic_no_slot.has_slots());
1476 let variadic_with_slot = Expr::VariadicOp {
1477 op: super::VariadicOp::IsInRange,
1478 left: lit.clone(),
1479 rights: nonempty::nonempty![Arc::new(Expr::Slot(SlotId::Principal))],
1480 };
1481 assert!(variadic_with_slot.has_slots());
1482 }
1483 }
1484
1485 #[test]
1486 fn test_from_function_unknown_function() {
1487 let name = ast::Name::parse_unqualified_name("unknownFunc").unwrap();
1488 let args = vec![Arc::new(Expr::Literal(Literal::Long(1)))];
1489
1490 let result = Expr::from_function_ast_name_and_args(&name, args);
1491 assert!(matches!(
1492 result,
1493 Err(PstConstructionError::UnknownFunction(..))
1494 ));
1495 }
1496
1497 #[test]
1498 fn test_from_function_wrong_arity() {
1499 let name = ast::Name::parse_unqualified_name("decimal").unwrap();
1500 let args = vec![
1501 Arc::new(Expr::Literal(Literal::Long(1))),
1502 Arc::new(Expr::Literal(Literal::Long(2))),
1503 ];
1504
1505 let result = Expr::from_function_ast_name_and_args(&name, args);
1506 assert_matches!(result, Err(PstConstructionError::WrongArity(..)));
1507 }
1508
1509 #[test]
1510 fn test_all_extension_functions_are_supported() {
1511 let extensions = Extensions::all_available();
1514
1515 for func in extensions.all_funcs() {
1516 let name = func.name().clone();
1517 let arity = func.arg_types().len();
1518
1519 let args: Vec<Arc<Expr>> = (0..arity)
1521 .map(|_| Arc::new(Expr::Literal(Literal::Long(0))))
1522 .collect();
1523
1524 let result = Expr::from_function_ast_name_and_args(&name, args);
1525 assert!(
1526 result.is_ok(),
1527 "Function {} should be supported but got error: {:?}",
1528 name,
1529 result.err()
1530 );
1531 let actual = result.unwrap();
1532 print!("Expression: {}", actual);
1533 match arity {
1534 1 => {
1535 if &name.to_string() == "unknown" {
1536 assert!(
1537 matches!(actual, Expr::Unknown { .. }),
1538 "Expected unary unknown function to be Unknown expr",
1539 );
1540 } else {
1541 match actual {
1542 Expr::UnaryOp { op, .. } => {
1543 let op_name = op.to_name();
1544 assert!(
1545 op_name.is_some(),
1546 "UnaryOp from extension {} should have known ast::Name",
1547 name
1548 );
1549 assert_eq!(
1550 UnaryOp::from_function_name(&name.as_ref().to_string()),
1551 Some(op)
1552 );
1553 }
1554 _ => {
1555 assertion_failure!("Unary function should produce BinaryOp", name:name)
1556 }
1557 }
1558 }
1559 }
1560 2 => match actual {
1561 Expr::BinaryOp { op, .. } => {
1562 let op_name = op.to_name();
1563 assert!(
1564 op_name.is_some(),
1565 "BinaryOp from extension {} should have known ast::Name",
1566 name
1567 );
1568 assert_eq!(
1569 BinaryOp::from_function_name(&name.as_ref().to_string()),
1570 Some(op)
1571 );
1572 }
1573 _ => assertion_failure!("Binary function should produce BinaryOp", name:name),
1574 },
1575 _ => (),
1576 }
1577 }
1578 }
1579
1580 #[test]
1581 fn test_expr_construction_error_display() {
1582 let err: PstConstructionError =
1583 error_body::UnknownFunctionError::new("foo".to_smolstr()).into();
1584 assert!(err.to_string().contains("foo"));
1585
1586 let err: PstConstructionError =
1587 error_body::WrongArityError::new("bar".to_string(), 2, 1).into();
1588 assert!(err.to_string().contains("bar"));
1589 assert!(err.to_string().contains("2"));
1590 assert!(err.to_string().contains("1"));
1591 }
1592
1593 #[test]
1594 fn test_builder_additional_methods() {
1595 let expr = PstBuilder::new().unknown(ast::Unknown::new_untyped("test"));
1597 assert_matches!(expr, Expr::Unknown { .. });
1598
1599 let base = PstBuilder::new().val("test");
1601 let pattern = ast::Pattern::from(vec![ast::PatternElem::Char('a')]);
1602 let expr = PstBuilder::new().like(base, pattern);
1603 assert_matches!(expr, Expr::Like { .. });
1604
1605 let base = PstBuilder::new().var(ast::Var::Principal);
1607 let entity_type = EntityType::from_name(ast::Name::parse_unqualified_name("User").unwrap());
1608 let uid = ast::EntityUID::from_components(
1609 ast::EntityType::from(ast::Name::parse_unqualified_name("User").unwrap()),
1610 ast::Eid::new("alice"),
1611 None,
1612 );
1613 let in_expr = PstBuilder::new().val(uid);
1614 let expr = PstBuilder::new().is_in_entity_type(
1615 base,
1616 entity_type.clone().try_into().unwrap(),
1617 in_expr,
1618 );
1619 if let Expr::Is {
1620 entity_type: et,
1621 in_expr: Some(_),
1622 ..
1623 } = expr
1624 {
1625 assert_eq!(et, entity_type);
1626 } else {
1627 panic!("Expected Is with in_expr");
1628 }
1629 }
1630
1631 #[test]
1632 fn test_builder_record_duplicate_keys() {
1633 let pairs = vec![
1634 (SmolStr::new("key"), PstBuilder::new().val(1i64)),
1635 (SmolStr::new("key"), PstBuilder::new().val(2i64)),
1636 ];
1637 let result = PstBuilder::new().record(pairs);
1638 assert!(matches!(
1639 result,
1640 Err(ast::ExpressionConstructionError::DuplicateKey { .. })
1641 ));
1642 }
1643
1644 mod display_tests {
1645 use super::*;
1646 use smol_str::SmolStr;
1647
1648 #[test]
1649 fn invalid_name_rejected_at_construction() {
1650 let name = "!__Cedar!";
1651 assert!(Name::unqualified(name).is_err());
1652 }
1653
1654 fn builder() -> PstBuilder {
1664 PstBuilder::new()
1665 }
1666
1667 #[test]
1668 fn test_builder_display() {
1669 let cases = vec![
1670 (builder().val(true), "true"),
1672 (builder().val(false), "false"),
1673 (builder().val(42i64), "42"),
1674 (builder().val(-123i64), "(-123)"),
1675 (builder().val("hello"), "\"hello\""),
1676 (
1677 builder().val(ast::EntityUID::from_components(
1678 ast::Name::from_str("Photo").unwrap().into(),
1679 ast::Eid::new("abc123"),
1680 None,
1681 )),
1682 "Photo::\"abc123\"",
1683 ),
1684 (builder().var(ast::Var::Principal), "principal"),
1686 (builder().var(ast::Var::Action), "action"),
1687 (builder().var(ast::Var::Resource), "resource"),
1688 (builder().var(ast::Var::Context), "context"),
1689 (builder().slot(ast::SlotId::principal()), "?principal"),
1691 (builder().slot(ast::SlotId::resource()), "?resource"),
1692 (builder().not(builder().val(true)), "!true"),
1694 (builder().neg(builder().val(42i64)), "-(42)"),
1695 (
1697 builder().is_eq(builder().val(1i64), builder().val(2i64)),
1698 "1 == 2",
1699 ),
1700 (
1701 builder().noteq(builder().val(1i64), builder().val(2i64)),
1702 "1 != 2",
1703 ),
1704 (
1705 builder().less(builder().val(1i64), builder().val(2i64)),
1706 "1 < 2",
1707 ),
1708 (
1709 builder().lesseq(builder().val(1i64), builder().val(2i64)),
1710 "1 <= 2",
1711 ),
1712 (
1713 builder().greater(builder().val(1i64), builder().val(2i64)),
1714 "1 > 2",
1715 ),
1716 (
1717 builder().greatereq(builder().val(1i64), builder().val(2i64)),
1718 "1 >= 2",
1719 ),
1720 (
1722 builder().and(builder().val(true), builder().val(false)),
1723 "true && false",
1724 ),
1725 (
1726 builder().or(builder().val(true), builder().val(false)),
1727 "true || false",
1728 ),
1729 (
1731 builder().add(builder().val(1i64), builder().val(2i64)),
1732 "1 + 2",
1733 ),
1734 (
1735 builder().sub(builder().val(5i64), builder().val(3i64)),
1736 "5 - 3",
1737 ),
1738 (
1739 builder().mul(builder().val(2i64), builder().val(3i64)),
1740 "2 * 3",
1741 ),
1742 (
1744 builder().is_in(
1745 builder().var(ast::Var::Principal),
1746 builder().var(ast::Var::Resource),
1747 ),
1748 "principal in resource",
1749 ),
1750 (
1751 builder().contains(builder().set([builder().val(1i64)]), builder().val(1i64)),
1752 "[1].contains(1)",
1753 ),
1754 (
1755 builder().contains_all(
1756 builder().set([builder().val(1i64)]),
1757 builder().set([builder().val(1i64)]),
1758 ),
1759 "[1].containsAll([1])",
1760 ),
1761 (
1762 builder().contains_any(
1763 builder().set([builder().val(1i64)]),
1764 builder().set([builder().val(1i64)]),
1765 ),
1766 "[1].containsAny([1])",
1767 ),
1768 (
1770 builder().get_attr(builder().var(ast::Var::Principal), SmolStr::from("name")),
1771 "principal.name",
1772 ),
1773 (
1774 builder().has_attr(builder().var(ast::Var::Principal), SmolStr::from("name")),
1775 "principal has name",
1776 ),
1777 (
1778 builder().is_entity_type(
1779 builder().var(ast::Var::Resource),
1780 ast::Name::from_str("Photo").unwrap().into(),
1781 ),
1782 "resource is Photo",
1783 ),
1784 (
1786 builder().ite(
1787 builder().val(true),
1788 builder().val(1i64),
1789 builder().val(2i64),
1790 ),
1791 "if true then 1 else 2",
1792 ),
1793 (builder().set([]), "[]"),
1795 (builder().set([builder().val(1i64)]), "[1]"),
1796 (
1797 builder().set([
1798 builder().val(1i64),
1799 builder().val(2i64),
1800 builder().val(3i64),
1801 ]),
1802 "[1, 2, 3]",
1803 ),
1804 (builder().record([]).unwrap(), "{}"),
1806 (
1807 builder()
1808 .record([(SmolStr::from("a"), builder().val(1i64))])
1809 .unwrap(),
1810 "{a: 1}",
1811 ),
1812 (
1813 builder()
1814 .record([
1815 (SmolStr::from("a"), builder().val(1i64)),
1816 (SmolStr::from("b"), builder().val(2i64)),
1817 ])
1818 .unwrap(),
1819 "{a: 1, b: 2}",
1820 ),
1821 (
1823 builder().has_tag(builder().var(ast::Var::Action), builder().val("tag")),
1824 "action.hasTag(\"tag\")",
1825 ),
1826 (
1827 builder().get_tag(builder().var(ast::Var::Action), builder().val("tag")),
1828 "action.getTag(\"tag\")",
1829 ),
1830 (
1832 builder().like(
1833 builder().val("hello"),
1834 ast::Pattern::from(vec![
1835 ast::PatternElem::Char('h'),
1836 ast::PatternElem::Wildcard,
1837 ]),
1838 ),
1839 "\"hello\" like \"h*\"",
1840 ),
1841 (
1843 builder()
1844 .call_extension_fn(
1845 Name::unqualified("decimal").unwrap().into(),
1846 vec![builder().val("1.23")],
1847 )
1848 .unwrap(),
1849 "decimal(\"1.23\")",
1850 ),
1851 ];
1852
1853 for (expr, expected) in cases {
1854 assert_eq!(expr.to_string(), expected, "Failed for: {}", expected);
1855 }
1856
1857 let fail_func = builder().call_extension_fn(
1858 Name::unqualified("notAFunc").unwrap().into(),
1859 vec![builder().val("12.3")],
1860 );
1861 assert!(fail_func.is_err());
1862 }
1863
1864 #[test]
1865 fn test_complex_expressions() {
1866 let nested = builder().is_eq(
1868 builder().add(builder().val(1i64), builder().val(2i64)),
1869 builder().val(3i64),
1870 );
1871 assert_eq!(nested.to_string(), "(1 + 2) == 3");
1872
1873 let complex = builder().ite(
1875 builder().greater(
1876 builder().get_attr(builder().var(ast::Var::Principal), SmolStr::from("age")),
1877 builder().val(18i64),
1878 ),
1879 builder().get_attr(builder().var(ast::Var::Principal), SmolStr::from("name")),
1880 builder().val("unknown"),
1881 );
1882 assert_eq!(
1883 complex.to_string(),
1884 "if ((principal.age) > 18) then (principal.name) else \"unknown\""
1885 );
1886
1887 let is_empty = builder().is_empty(builder().set([]));
1889 assert_eq!(is_empty.to_string(), "[].isEmpty()");
1890 }
1891
1892 #[test]
1893 fn test_unary_op_display_no_impossible_operator() {
1894 let ops = [
1896 UnaryOp::Not,
1897 UnaryOp::Neg,
1898 UnaryOp::IsEmpty,
1899 UnaryOp::Datetime,
1900 UnaryOp::Decimal,
1901 UnaryOp::Duration,
1902 UnaryOp::Ip,
1903 UnaryOp::IsIPv4,
1904 UnaryOp::IsIPV6,
1905 UnaryOp::IsLoopback,
1906 UnaryOp::IsMulticast,
1907 UnaryOp::ToDate,
1908 UnaryOp::ToTime,
1909 UnaryOp::ToMilliseconds,
1910 UnaryOp::ToSeconds,
1911 UnaryOp::ToMinutes,
1912 UnaryOp::ToHours,
1913 UnaryOp::ToDays,
1914 ];
1915
1916 for op in ops {
1917 let display = op.to_string();
1918 assert_ne!(
1919 display, "<impossible operator>",
1920 "UnaryOp::{:?} should not display as impossible operator",
1921 op
1922 );
1923 }
1924 }
1925
1926 #[test]
1927 fn test_binary_op_display_no_impossible_operator() {
1928 let ops = [
1930 BinaryOp::Eq,
1931 BinaryOp::NotEq,
1932 BinaryOp::Less,
1933 BinaryOp::LessEq,
1934 BinaryOp::Greater,
1935 BinaryOp::GreaterEq,
1936 BinaryOp::And,
1937 BinaryOp::Or,
1938 BinaryOp::Add,
1939 BinaryOp::Sub,
1940 BinaryOp::Mul,
1941 BinaryOp::In,
1942 BinaryOp::Contains,
1943 BinaryOp::ContainsAll,
1944 BinaryOp::ContainsAny,
1945 BinaryOp::GetTag,
1946 BinaryOp::HasTag,
1947 BinaryOp::IsInRange,
1948 BinaryOp::Offset,
1949 BinaryOp::DurationSince,
1950 ];
1951
1952 for op in ops {
1953 let display = op.to_string();
1954 assert_ne!(
1955 display, "<impossible operator>",
1956 "BinaryOp::{:?} should not display as impossible operator",
1957 op
1958 );
1959 }
1960 }
1961 }
1962}