1use crate::ast::*;
18use crate::parser::Loc;
19use annotation::{Annotation, Annotations};
20use educe::Educe;
21use itertools::Itertools;
22use linked_hash_map::LinkedHashMap;
23use miette::Diagnostic;
24use nonempty::{nonempty, NonEmpty};
25use serde::{Deserialize, Serialize};
26use smol_str::SmolStr;
27use std::{
28 collections::{HashMap, HashSet},
29 str::FromStr,
30 sync::Arc,
31};
32use thiserror::Error;
33
34#[cfg(feature = "wasm")]
35extern crate tsify;
36
37macro_rules! cfg_tolerant_ast {
38 ($($item:item)*) => {
39 $(
40 #[cfg(feature = "tolerant-ast")]
41 $item
42 )*
43 };
44}
45
46cfg_tolerant_ast! {
47 use super::expr_allows_errors::AstExprErrorKind;
48 use crate::ast::expr_allows_errors::ExprWithErrsBuilder;
49 use crate::expr_builder::ExprBuilder;
50 use crate::parser::err::ParseErrors;
51 use crate::parser::err::ToASTError;
52 use crate::parser::err::ToASTErrorKind;
53
54 static DEFAULT_ANNOTATIONS: std::sync::LazyLock<Arc<Annotations>> =
55 std::sync::LazyLock::new(|| Arc::new(Annotations::default()));
56
57 static DEFAULT_PRINCIPAL_CONSTRAINT: std::sync::LazyLock<PrincipalConstraint> =
58 std::sync::LazyLock::new(PrincipalConstraint::any);
59
60 static DEFAULT_RESOURCE_CONSTRAINT: std::sync::LazyLock<ResourceConstraint> =
61 std::sync::LazyLock::new(ResourceConstraint::any);
62
63 static DEFAULT_ACTION_CONSTRAINT: std::sync::LazyLock<ActionConstraint> =
64 std::sync::LazyLock::new(ActionConstraint::any);
65
66 static DEFAULT_ERROR_EXPR: std::sync::LazyLock<Arc<Expr>> = std::sync::LazyLock::new(|| {
67 Arc::new(
70 <ExprWithErrsBuilder as ExprBuilder>::new()
71 .error(ParseErrors::singleton(ToASTError::new(
72 ToASTErrorKind::ASTErrorNode,
73 Some(Loc::new(0..1, "ASTErrorNode".into())),
74 )))
75 .unwrap_infallible(),
76 )
77 });
78}
79
80#[derive(Clone, Hash, Eq, PartialEq, Debug)]
85pub struct Template {
86 body: TemplateBody,
87 slots: Vec<Slot>,
92}
93
94impl From<Template> for TemplateBody {
95 fn from(val: Template) -> Self {
96 val.body
97 }
98}
99
100impl Template {
101 pub fn check_invariant(&self) {
105 #[cfg(debug_assertions)]
106 {
107 for slot in self.body.condition().slots() {
108 assert!(self.slots.contains(&slot));
109 }
110 for slot in self.slots() {
111 assert!(self.body.condition().slots().contains(slot));
112 }
113 }
114 }
115
116 #[expect(
118 clippy::too_many_arguments,
119 reason = "policies just have this many components"
120 )]
121 pub fn new(
122 id: PolicyID,
123 loc: Option<Loc>,
124 annotations: Annotations,
125 effect: Effect,
126 principal_constraint: PrincipalConstraint,
127 action_constraint: ActionConstraint,
128 resource_constraint: ResourceConstraint,
129 non_scope_constraint: Option<Expr>,
130 ) -> Self {
131 let body = TemplateBody::new(
132 id,
133 loc,
134 annotations,
135 effect,
136 principal_constraint,
137 action_constraint,
138 resource_constraint,
139 non_scope_constraint,
140 );
141 Template::from(body)
144 }
145
146 #[cfg(feature = "tolerant-ast")]
147 pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
149 let body = TemplateBody::error(id, loc);
150 Template::from(body)
151 }
152
153 #[expect(
155 clippy::too_many_arguments,
156 reason = "policies just have this many components"
157 )]
158 pub fn new_shared(
159 id: PolicyID,
160 loc: Option<Loc>,
161 annotations: Arc<Annotations>,
162 effect: Effect,
163 principal_constraint: PrincipalConstraint,
164 action_constraint: ActionConstraint,
165 resource_constraint: ResourceConstraint,
166 non_scope_constraint: Option<Arc<Expr>>,
167 ) -> Self {
168 let body = TemplateBody::new_shared(
169 id,
170 loc,
171 annotations,
172 effect,
173 principal_constraint,
174 action_constraint,
175 resource_constraint,
176 non_scope_constraint,
177 );
178 Template::from(body)
181 }
182
183 #[expect(clippy::type_complexity, reason = "policies just have many components")]
188 pub(crate) fn into_template_components_opt(
189 self,
190 ) -> Option<(
191 PolicyID,
192 Arc<Annotations>,
193 Effect,
194 PrincipalConstraint,
195 ActionConstraint,
196 ResourceConstraint,
197 Option<Arc<Expr>>,
198 )> {
199 self.body.into_components_opt()
200 }
201
202 pub fn principal_constraint(&self) -> &PrincipalConstraint {
204 self.body.principal_constraint()
205 }
206
207 pub fn action_constraint(&self) -> &ActionConstraint {
209 self.body.action_constraint()
210 }
211
212 pub fn resource_constraint(&self) -> &ResourceConstraint {
214 self.body.resource_constraint()
215 }
216
217 pub fn non_scope_constraints(&self) -> Option<&Expr> {
219 self.body.non_scope_constraints()
220 }
221
222 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
224 self.body.non_scope_constraints_arc()
225 }
226
227 pub fn id(&self) -> &PolicyID {
229 self.body.id()
230 }
231
232 pub fn new_id(&self, id: PolicyID) -> Self {
234 Template {
235 body: self.body.new_id(id),
236 slots: self.slots.clone(),
237 }
238 }
239
240 pub fn loc(&self) -> Option<&Loc> {
242 self.body.loc()
243 }
244
245 pub fn effect(&self) -> Effect {
247 self.body.effect()
248 }
249
250 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
252 self.body.annotation(key)
253 }
254
255 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
257 self.body.annotations()
258 }
259
260 pub fn annotations_arc(&self) -> &Arc<Annotations> {
262 self.body.annotations_arc()
263 }
264
265 pub fn condition(&self) -> Expr {
271 self.body.condition()
272 }
273
274 pub fn slots(&self) -> impl Iterator<Item = &Slot> {
276 self.slots.iter()
277 }
278
279 pub fn is_static(&self) -> bool {
284 self.slots.is_empty()
285 }
286
287 pub fn check_binding(
291 template: &Template,
292 values: &HashMap<SlotId, EntityUID>,
293 ) -> Result<(), LinkingError> {
294 let unbound = template
296 .slots
297 .iter()
298 .filter(|slot| !values.contains_key(&slot.id))
299 .collect::<Vec<_>>();
300
301 let extra = values
302 .iter()
303 .filter_map(|(slot, _)| {
304 if !template
305 .slots
306 .iter()
307 .any(|template_slot| template_slot.id == *slot)
308 {
309 Some(slot)
310 } else {
311 None
312 }
313 })
314 .collect::<Vec<_>>();
315
316 if unbound.is_empty() && extra.is_empty() {
317 Ok(())
318 } else {
319 Err(LinkingError::from_unbound_and_extras(
320 unbound.into_iter().map(|slot| slot.id),
321 extra.into_iter().copied(),
322 ))
323 }
324 }
325
326 pub fn link(
330 template: Arc<Template>,
331 new_id: PolicyID,
332 values: HashMap<SlotId, EntityUID>,
333 ) -> Result<Policy, LinkingError> {
334 Template::check_binding(&template, &values)
336 .map(|_| Policy::new(template, Some(new_id), values))
337 }
338
339 pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
342 let body: TemplateBody = p.into();
343 let t = Arc::new(Self {
347 body,
348 slots: vec![],
349 });
350 t.check_invariant();
351 let p = Policy::new(Arc::clone(&t), None, HashMap::new());
352 (t, p)
353 }
354}
355
356impl From<TemplateBody> for Template {
357 fn from(body: TemplateBody) -> Self {
358 let slots = body.condition().slots().collect::<Vec<_>>();
361 Self { body, slots }
362 }
363}
364
365impl std::fmt::Display for Template {
366 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367 write!(f, "{}", self.body)
368 }
369}
370
371#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
373pub enum LinkingError {
374 #[error(fmt = describe_arity_error)]
377 ArityError {
378 unbound_values: Vec<SlotId>,
380 extra_values: Vec<SlotId>,
382 },
383
384 #[error("failed to find a template with id `{id}`")]
386 NoSuchTemplate {
387 id: PolicyID,
389 },
390
391 #[error("template-linked policy id `{id}` conflicts with an existing policy id")]
393 PolicyIdConflict {
394 id: PolicyID,
396 },
397}
398
399impl LinkingError {
400 fn from_unbound_and_extras(
401 unbound: impl Iterator<Item = SlotId>,
402 extra: impl Iterator<Item = SlotId>,
403 ) -> Self {
404 Self::ArityError {
405 unbound_values: unbound.collect(),
406 extra_values: extra.collect(),
407 }
408 }
409}
410
411fn describe_arity_error(
412 unbound_values: &[SlotId],
413 extra_values: &[SlotId],
414 fmt: &mut std::fmt::Formatter<'_>,
415) -> std::fmt::Result {
416 match (unbound_values.len(), extra_values.len()) {
417 #[expect(clippy::unreachable, reason = "0,0 case is not an error")]
418 (0,0) => unreachable!(),
419 (_unbound, 0) => write!(fmt, "the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
420 (0, _extra) => write!(fmt, "the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
421 (_unbound, _extra) => write!(fmt, "the following slots were not provided as arguments: {}. The following slots were provided as arguments, but did not exist in the template: {}", unbound_values.iter().join(","), extra_values.iter().join(",")),
422 }
423}
424
425#[derive(Debug, Clone, Eq, PartialEq)]
433pub struct Policy {
434 template: Arc<Template>,
436 link: Option<PolicyID>,
440 values: HashMap<SlotId, EntityUID>,
447}
448
449impl Policy {
450 pub(crate) fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
454 #[cfg(debug_assertions)]
455 {
456 #[expect(
457 clippy::expect_used,
458 reason = "asserts (value total map invariant) which is justified at call sites"
459 )]
460 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
461 }
462 Self {
463 template,
464 link: link_id,
465 values,
466 }
467 }
468
469 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID, loc: Option<Loc>) -> Self {
471 Self::from_when_clause_annos(
472 effect,
473 Arc::new(when),
474 id,
475 loc,
476 Arc::new(Annotations::default()),
477 )
478 }
479
480 pub fn from_when_clause_annos(
482 effect: Effect,
483 when: Arc<Expr>,
484 id: PolicyID,
485 loc: Option<Loc>,
486 annotations: Arc<Annotations>,
487 ) -> Self {
488 let t = Template::new_shared(
489 id,
490 loc,
491 annotations,
492 effect,
493 PrincipalConstraint::any(),
494 ActionConstraint::any(),
495 ResourceConstraint::any(),
496 Some(when),
497 );
498 Self::new(Arc::new(t), None, SlotEnv::new())
499 }
500
501 pub(crate) fn into_components(self) -> (Arc<Template>, Option<PolicyID>, SlotEnv) {
504 (self.template, self.link, self.values)
505 }
506
507 pub fn template(&self) -> &Template {
509 &self.template
510 }
511
512 pub(crate) fn template_arc(&self) -> Arc<Template> {
514 Arc::clone(&self.template)
515 }
516
517 pub fn effect(&self) -> Effect {
519 self.template.effect()
520 }
521
522 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
524 self.template.annotation(key)
525 }
526
527 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
529 self.template.annotations()
530 }
531
532 pub fn annotations_arc(&self) -> &Arc<Annotations> {
534 self.template.annotations_arc()
535 }
536
537 pub fn principal_constraint(&self) -> PrincipalConstraint {
543 let constraint = self.template.principal_constraint().clone();
544 match self.values.get(&SlotId::principal()) {
545 None => constraint,
546 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
547 }
548 }
549
550 pub fn action_constraint(&self) -> &ActionConstraint {
552 self.template.action_constraint()
553 }
554
555 pub fn resource_constraint(&self) -> ResourceConstraint {
561 let constraint = self.template.resource_constraint().clone();
562 match self.values.get(&SlotId::resource()) {
563 None => constraint,
564 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
565 }
566 }
567
568 pub fn non_scope_constraints(&self) -> Option<&Expr> {
570 self.template.non_scope_constraints()
571 }
572
573 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
575 self.template.non_scope_constraints_arc()
576 }
577
578 pub fn condition(&self) -> Expr {
580 self.template.condition()
581 }
582
583 pub fn env(&self) -> &SlotEnv {
586 &self.values
587 }
588
589 pub fn id(&self) -> &PolicyID {
591 self.link.as_ref().unwrap_or_else(|| self.template.id())
592 }
593
594 pub fn new_id(&self, id: PolicyID) -> Self {
596 match self.link {
597 None => Policy {
598 template: Arc::new(self.template.new_id(id)),
599 link: None,
600 values: self.values.clone(),
601 },
602 Some(_) => Policy {
603 template: self.template.clone(),
604 link: Some(id),
605 values: self.values.clone(),
606 },
607 }
608 }
609
610 pub(crate) fn new_template_id(self, id: PolicyID) -> Option<Self> {
613 self.link.map(|link| Policy {
614 template: Arc::new(self.template.new_id(id)),
615 link: Some(link),
616 values: self.values,
617 })
618 }
619
620 pub fn loc(&self) -> Option<&Loc> {
622 self.template.loc()
623 }
624
625 pub fn is_static(&self) -> bool {
627 self.link.is_none()
628 }
629
630 pub fn unknown_entities(&self) -> HashSet<EntityUID> {
632 self.condition()
633 .unknowns()
634 .filter_map(
635 |Unknown {
636 name,
637 type_annotation,
638 }| {
639 if matches!(type_annotation, Some(Type::Entity { .. })) {
640 EntityUID::from_str(name.as_str()).ok()
641 } else {
642 None
643 }
644 },
645 )
646 .collect()
647 }
648}
649
650impl std::fmt::Display for Policy {
651 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
652 if self.is_static() {
653 write!(f, "{}", self.template())
654 } else {
655 write!(
656 f,
657 "Template Instance of {}, slots: [{}]",
658 self.template().id(),
659 display_slot_env(self.env())
660 )
661 }
662 }
663}
664
665pub type SlotEnv = HashMap<SlotId, EntityUID>;
667
668#[derive(Debug, Clone, PartialEq, Eq)]
675pub struct LiteralPolicy {
676 template_id: PolicyID,
678 link_id: Option<PolicyID>,
682 values: SlotEnv,
684}
685
686impl LiteralPolicy {
687 pub fn static_policy(template_id: PolicyID) -> Self {
691 Self {
692 template_id,
693 link_id: None,
694 values: SlotEnv::new(),
695 }
696 }
697
698 pub fn template_linked_policy(
702 template_id: PolicyID,
703 link_id: PolicyID,
704 values: SlotEnv,
705 ) -> Self {
706 Self {
707 template_id,
708 link_id: Some(link_id),
709 values,
710 }
711 }
712
713 pub fn value(&self, slot: &SlotId) -> Option<&EntityUID> {
715 self.values.get(slot)
716 }
717}
718
719impl std::hash::Hash for LiteralPolicy {
722 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
723 self.template_id.hash(state);
724 let mut buf = self.values.iter().collect::<Vec<_>>();
726 buf.sort();
727 for (id, euid) in buf {
728 id.hash(state);
729 euid.hash(state);
730 }
731 }
732}
733
734#[cfg(test)]
736mod hashing_tests {
737 use std::{
738 collections::hash_map::DefaultHasher,
739 hash::{Hash, Hasher},
740 };
741
742 use super::*;
743
744 fn compute_hash(ir: &LiteralPolicy) -> u64 {
745 let mut s = DefaultHasher::new();
746 ir.hash(&mut s);
747 s.finish()
748 }
749
750 fn build_template_linked_policy() -> LiteralPolicy {
751 let mut map = HashMap::new();
752 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
753 LiteralPolicy {
754 template_id: PolicyID::from_string("template"),
755 link_id: Some(PolicyID::from_string("id")),
756 values: map,
757 }
758 }
759
760 #[test]
761 fn hash_property_instances() {
762 let a = build_template_linked_policy();
763 let b = build_template_linked_policy();
764 assert_eq!(a, b);
765 assert_eq!(compute_hash(&a), compute_hash(&b));
766 }
767}
768
769#[derive(Debug, Diagnostic, Error)]
771pub enum ReificationError {
772 #[error("the id linked to does not exist")]
774 NoSuchTemplate(PolicyID),
775 #[error(transparent)]
777 #[diagnostic(transparent)]
778 Linking(#[from] LinkingError),
779}
780
781impl LiteralPolicy {
782 pub fn reify(
787 self,
788 templates: &LinkedHashMap<PolicyID, Arc<Template>>,
789 ) -> Result<Policy, ReificationError> {
790 let template = templates
791 .get(&self.template_id)
792 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
793 Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
795 Ok(Policy::new(template.clone(), self.link_id, self.values))
796 }
797
798 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
800 self.values.get(id)
801 }
802
803 pub fn id(&self) -> &PolicyID {
805 self.link_id.as_ref().unwrap_or(&self.template_id)
806 }
807
808 pub fn template_id(&self) -> &PolicyID {
812 &self.template_id
813 }
814
815 pub fn is_static(&self) -> bool {
817 self.link_id.is_none()
818 }
819}
820
821fn display_slot_env(env: &SlotEnv) -> String {
822 env.iter()
823 .map(|(slot, value)| format!("{slot} -> {value}"))
824 .join(",")
825}
826
827impl std::fmt::Display for LiteralPolicy {
828 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
829 if self.is_static() {
830 write!(f, "Static policy w/ ID {}", self.template_id())
831 } else {
832 write!(
833 f,
834 "Template linked policy of {}, slots: [{}]",
835 self.template_id(),
836 display_slot_env(&self.values),
837 )
838 }
839 }
840}
841
842impl From<Policy> for LiteralPolicy {
843 fn from(p: Policy) -> Self {
844 Self {
845 template_id: p.template.id().clone(),
846 link_id: p.link,
847 values: p.values,
848 }
849 }
850}
851
852#[derive(Clone, Hash, Eq, PartialEq, Debug)]
856pub struct StaticPolicy(TemplateBody);
857
858impl StaticPolicy {
859 pub fn id(&self) -> &PolicyID {
861 self.0.id()
862 }
863
864 pub fn new_id(&self, id: PolicyID) -> Self {
866 StaticPolicy(self.0.new_id(id))
867 }
868
869 pub fn loc(&self) -> Option<&Loc> {
871 self.0.loc()
872 }
873
874 pub fn effect(&self) -> Effect {
876 self.0.effect()
877 }
878
879 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
881 self.0.annotation(key)
882 }
883
884 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
886 self.0.annotations()
887 }
888
889 pub fn principal_constraint(&self) -> &PrincipalConstraint {
891 self.0.principal_constraint()
892 }
893
894 pub fn principal_constraint_expr(&self) -> Expr {
898 self.0.principal_constraint_expr()
899 }
900
901 pub fn action_constraint(&self) -> &ActionConstraint {
903 self.0.action_constraint()
904 }
905
906 pub fn action_constraint_expr(&self) -> Expr {
910 self.0.action_constraint_expr()
911 }
912
913 pub fn resource_constraint(&self) -> &ResourceConstraint {
915 self.0.resource_constraint()
916 }
917
918 pub fn resource_constraint_expr(&self) -> Expr {
922 self.0.resource_constraint_expr()
923 }
924
925 pub fn non_scope_constraints(&self) -> Option<&Expr> {
930 self.0.non_scope_constraints()
931 }
932
933 pub fn condition(&self) -> Expr {
939 self.0.condition()
940 }
941
942 #[expect(
944 clippy::too_many_arguments,
945 reason = "policies just have this many components"
946 )]
947 pub fn new(
948 id: PolicyID,
949 loc: Option<Loc>,
950 annotations: Annotations,
951 effect: Effect,
952 principal_constraint: PrincipalConstraint,
953 action_constraint: ActionConstraint,
954 resource_constraint: ResourceConstraint,
955 non_scope_constraints: Option<Expr>,
956 ) -> Result<Self, UnexpectedSlotError> {
957 let body = TemplateBody::new(
958 id,
959 loc,
960 annotations,
961 effect,
962 principal_constraint,
963 action_constraint,
964 resource_constraint,
965 non_scope_constraints,
966 );
967 let first_slot = body.condition().slots().next();
968 match first_slot {
970 Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
971 None => Ok(Self(body)),
972 }
973 }
974}
975
976impl TryFrom<Template> for StaticPolicy {
977 type Error = UnexpectedSlotError;
978
979 fn try_from(value: Template) -> Result<Self, Self::Error> {
980 let o = value.slots().next();
982 match o {
983 Some(slot_id) => Err(Self::Error::FoundSlot(slot_id.clone())),
984 None => Ok(Self(value.body)),
985 }
986 }
987}
988
989impl From<StaticPolicy> for Policy {
990 fn from(p: StaticPolicy) -> Policy {
991 let (_, policy) = Template::link_static_policy(p);
992 policy
993 }
994}
995
996impl From<StaticPolicy> for Arc<Template> {
997 fn from(p: StaticPolicy) -> Self {
998 let (t, _) = Template::link_static_policy(p);
999 t
1000 }
1001}
1002
1003#[derive(Educe, Clone, Debug)]
1006#[educe(PartialEq, Eq, Hash)]
1007pub struct TemplateBodyImpl {
1008 id: PolicyID,
1010 #[educe(PartialEq(ignore))]
1012 #[educe(Hash(ignore))]
1013 loc: Option<Loc>,
1014 annotations: Arc<Annotations>,
1018 effect: Effect,
1020 principal_constraint: PrincipalConstraint,
1024 action_constraint: ActionConstraint,
1028 resource_constraint: ResourceConstraint,
1032 non_scope_constraints: Option<Arc<Expr>>,
1037}
1038
1039#[derive(Clone, Hash, Eq, PartialEq, Debug)]
1042pub enum TemplateBody {
1043 TemplateBody(TemplateBodyImpl),
1045 #[cfg(feature = "tolerant-ast")]
1046 TemplateBodyError(PolicyID, Option<Loc>),
1048}
1049
1050impl TemplateBody {
1051 pub fn id(&self) -> &PolicyID {
1053 match self {
1054 TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => id,
1055 #[cfg(feature = "tolerant-ast")]
1056 TemplateBody::TemplateBodyError(id, _) => id,
1057 }
1058 }
1059
1060 pub fn loc(&self) -> Option<&Loc> {
1062 match self {
1063 TemplateBody::TemplateBody(TemplateBodyImpl { loc, .. }) => loc.as_ref(),
1064 #[cfg(feature = "tolerant-ast")]
1065 TemplateBody::TemplateBodyError(_, loc) => loc.as_ref(),
1066 }
1067 }
1068
1069 pub fn new_id(&self, id: PolicyID) -> Self {
1071 match self {
1072 TemplateBody::TemplateBody(t) => {
1073 let mut new = t.clone();
1074 new.id = id;
1075 TemplateBody::TemplateBody(new)
1076 }
1077 #[cfg(feature = "tolerant-ast")]
1078 TemplateBody::TemplateBodyError(_, loc) => {
1079 TemplateBody::TemplateBodyError(id, loc.clone())
1080 }
1081 }
1082 }
1083
1084 #[cfg(feature = "tolerant-ast")]
1085 pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
1087 TemplateBody::TemplateBodyError(id, loc)
1088 }
1089
1090 pub fn effect(&self) -> Effect {
1092 match self {
1093 TemplateBody::TemplateBody(TemplateBodyImpl { effect, .. }) => *effect,
1094 #[cfg(feature = "tolerant-ast")]
1095 TemplateBody::TemplateBodyError(_, _) => Effect::Forbid,
1096 }
1097 }
1098
1099 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
1101 match self {
1102 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => {
1103 annotations.get(key)
1104 }
1105 #[cfg(feature = "tolerant-ast")]
1106 TemplateBody::TemplateBodyError(_, _) => None,
1107 }
1108 }
1109
1110 pub fn annotations_arc(&self) -> &Arc<Annotations> {
1112 match self {
1113 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations,
1114 #[cfg(feature = "tolerant-ast")]
1115 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ANNOTATIONS,
1116 }
1117 }
1118
1119 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1121 match self {
1122 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations.iter(),
1123 #[cfg(feature = "tolerant-ast")]
1124 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ANNOTATIONS.iter(),
1125 }
1126 }
1127
1128 pub fn principal_constraint(&self) -> &PrincipalConstraint {
1130 match self {
1131 TemplateBody::TemplateBody(TemplateBodyImpl {
1132 principal_constraint,
1133 ..
1134 }) => principal_constraint,
1135 #[cfg(feature = "tolerant-ast")]
1136 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_PRINCIPAL_CONSTRAINT,
1137 }
1138
1139 }
1141
1142 pub fn principal_constraint_expr(&self) -> Expr {
1146 match self {
1147 TemplateBody::TemplateBody(TemplateBodyImpl {
1148 principal_constraint,
1149 ..
1150 }) => principal_constraint.as_expr(),
1151 #[cfg(feature = "tolerant-ast")]
1152 TemplateBody::TemplateBodyError(_, _) => DEFAULT_PRINCIPAL_CONSTRAINT.as_expr(),
1153 }
1154 }
1155
1156 pub fn action_constraint(&self) -> &ActionConstraint {
1158 match self {
1159 TemplateBody::TemplateBody(TemplateBodyImpl {
1160 action_constraint, ..
1161 }) => action_constraint,
1162 #[cfg(feature = "tolerant-ast")]
1163 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ACTION_CONSTRAINT,
1164 }
1165 }
1166
1167 pub fn action_constraint_expr(&self) -> Expr {
1171 match self {
1172 TemplateBody::TemplateBody(TemplateBodyImpl {
1173 action_constraint, ..
1174 }) => action_constraint.as_expr(),
1175 #[cfg(feature = "tolerant-ast")]
1176 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ACTION_CONSTRAINT.as_expr(),
1177 }
1178 }
1179
1180 pub fn resource_constraint(&self) -> &ResourceConstraint {
1182 match self {
1183 TemplateBody::TemplateBody(TemplateBodyImpl {
1184 resource_constraint,
1185 ..
1186 }) => resource_constraint,
1187 #[cfg(feature = "tolerant-ast")]
1188 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_RESOURCE_CONSTRAINT,
1189 }
1190 }
1191
1192 pub fn resource_constraint_expr(&self) -> Expr {
1196 match self {
1197 TemplateBody::TemplateBody(TemplateBodyImpl {
1198 resource_constraint,
1199 ..
1200 }) => resource_constraint.as_expr(),
1201 #[cfg(feature = "tolerant-ast")]
1202 TemplateBody::TemplateBodyError(_, _) => DEFAULT_RESOURCE_CONSTRAINT.as_expr(),
1203 }
1204 }
1205
1206 pub fn non_scope_constraints(&self) -> Option<&Expr> {
1211 match self {
1212 TemplateBody::TemplateBody(TemplateBodyImpl {
1213 non_scope_constraints,
1214 ..
1215 }) => non_scope_constraints.as_ref().map(|e| e.as_ref()),
1216 #[cfg(feature = "tolerant-ast")]
1217 TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1218 }
1219 }
1220
1221 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
1223 match self {
1224 TemplateBody::TemplateBody(TemplateBodyImpl {
1225 non_scope_constraints,
1226 ..
1227 }) => non_scope_constraints.as_ref(),
1228 #[cfg(feature = "tolerant-ast")]
1229 TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1230 }
1231 }
1232
1233 #[expect(clippy::type_complexity, reason = "policies just have many components")]
1236 pub(crate) fn into_components_opt(
1237 self,
1238 ) -> Option<(
1239 PolicyID,
1240 Arc<Annotations>,
1241 Effect,
1242 PrincipalConstraint,
1243 ActionConstraint,
1244 ResourceConstraint,
1245 Option<Arc<Expr>>,
1246 )> {
1247 match self {
1248 TemplateBody::TemplateBody(TemplateBodyImpl {
1249 id,
1250 loc: _,
1251 annotations,
1252 effect,
1253 principal_constraint,
1254 action_constraint,
1255 resource_constraint,
1256 non_scope_constraints,
1257 }) => Some((
1258 id,
1259 annotations,
1260 effect,
1261 principal_constraint,
1262 action_constraint,
1263 resource_constraint,
1264 non_scope_constraints,
1265 )),
1266 #[cfg(feature = "tolerant-ast")]
1267 TemplateBody::TemplateBodyError(_, _) => None,
1268 }
1269 }
1270
1271 pub fn condition(&self) -> Expr {
1277 match self {
1278 TemplateBody::TemplateBody(TemplateBodyImpl { .. }) => {
1279 let loc = self.loc().cloned();
1280 Expr::and(
1281 self.principal_constraint_expr(),
1282 Expr::and(
1283 self.action_constraint_expr(),
1284 Expr::and(
1285 self.resource_constraint_expr(),
1286 self.non_scope_constraints()
1287 .cloned()
1288 .unwrap_or_else(|| Expr::val(true)),
1289 )
1290 .with_maybe_source_loc(loc.clone()),
1291 )
1292 .with_maybe_source_loc(loc.clone()),
1293 )
1294 .with_maybe_source_loc(loc)
1295 }
1296 #[cfg(feature = "tolerant-ast")]
1297 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ERROR_EXPR.as_ref().clone(),
1298 }
1299 }
1300
1301 #[expect(
1303 clippy::too_many_arguments,
1304 reason = "policies just have this many components"
1305 )]
1306 pub fn new_shared(
1307 id: PolicyID,
1308 loc: Option<Loc>,
1309 annotations: Arc<Annotations>,
1310 effect: Effect,
1311 principal_constraint: PrincipalConstraint,
1312 action_constraint: ActionConstraint,
1313 resource_constraint: ResourceConstraint,
1314 non_scope_constraints: Option<Arc<Expr>>,
1315 ) -> Self {
1316 Self::TemplateBody(TemplateBodyImpl {
1317 id,
1318 loc,
1319 annotations,
1320 effect,
1321 principal_constraint,
1322 action_constraint,
1323 resource_constraint,
1324 non_scope_constraints,
1325 })
1326 }
1327
1328 #[expect(
1330 clippy::too_many_arguments,
1331 reason = "policies just have this many components"
1332 )]
1333 pub fn new(
1334 id: PolicyID,
1335 loc: Option<Loc>,
1336 annotations: Annotations,
1337 effect: Effect,
1338 principal_constraint: PrincipalConstraint,
1339 action_constraint: ActionConstraint,
1340 resource_constraint: ResourceConstraint,
1341 non_scope_constraints: Option<Expr>,
1342 ) -> Self {
1343 Self::TemplateBody(TemplateBodyImpl {
1344 id,
1345 loc,
1346 annotations: Arc::new(annotations),
1347 effect,
1348 principal_constraint,
1349 action_constraint,
1350 resource_constraint,
1351 non_scope_constraints: non_scope_constraints.map(Arc::new),
1352 })
1353 }
1354}
1355
1356impl From<StaticPolicy> for TemplateBody {
1357 fn from(p: StaticPolicy) -> Self {
1358 p.0
1359 }
1360}
1361
1362impl std::fmt::Display for TemplateBody {
1363 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1364 match self {
1365 TemplateBody::TemplateBody(template_body_impl) => {
1366 template_body_impl.annotations.fmt(f)?;
1367 write!(
1368 f,
1369 "{}(\n {},\n {},\n {}\n)",
1370 self.effect(),
1371 self.principal_constraint(),
1372 self.action_constraint(),
1373 self.resource_constraint(),
1374 )?;
1375 if let Some(non_scope_constraints) = self.non_scope_constraints() {
1376 write!(f, " when {{\n {non_scope_constraints}\n}};")
1377 } else {
1378 write!(f, ";")
1379 }
1380 }
1381 #[cfg(feature = "tolerant-ast")]
1382 TemplateBody::TemplateBodyError(policy_id, _) => {
1383 write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1384 }
1385 }
1386 }
1387}
1388
1389#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1391pub struct PrincipalConstraint {
1392 pub(crate) constraint: PrincipalOrResourceConstraint,
1393}
1394
1395impl PrincipalConstraint {
1396 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1398 PrincipalConstraint { constraint }
1399 }
1400
1401 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1403 &self.constraint
1404 }
1405
1406 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1408 self.constraint
1409 }
1410
1411 pub fn as_expr(&self) -> Expr {
1413 self.constraint.as_expr(PrincipalOrResource::Principal)
1414 }
1415
1416 pub fn any() -> Self {
1418 PrincipalConstraint {
1419 constraint: PrincipalOrResourceConstraint::any(),
1420 }
1421 }
1422
1423 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1425 PrincipalConstraint {
1426 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1427 }
1428 }
1429
1430 pub fn is_eq_slot() -> Self {
1432 Self {
1433 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1434 }
1435 }
1436
1437 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1439 PrincipalConstraint {
1440 constraint: PrincipalOrResourceConstraint::is_in(euid),
1441 }
1442 }
1443
1444 pub fn is_in_slot() -> Self {
1446 Self {
1447 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1448 }
1449 }
1450
1451 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1453 Self {
1454 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1455 }
1456 }
1457
1458 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1460 Self {
1461 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1462 }
1463 }
1464
1465 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1467 Self {
1468 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1469 }
1470 }
1471
1472 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1474 match self.constraint {
1475 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1476 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1477 },
1478 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1479 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1480 },
1481 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1482 constraint: PrincipalOrResourceConstraint::IsIn(
1483 entity_type,
1484 EntityReference::EUID(euid),
1485 ),
1486 },
1487 PrincipalOrResourceConstraint::Is(_)
1489 | PrincipalOrResourceConstraint::Any
1490 | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1491 | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1492 | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1493 }
1494 }
1495}
1496
1497impl std::fmt::Display for PrincipalConstraint {
1498 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1499 write!(
1500 f,
1501 "{}",
1502 self.constraint.display(PrincipalOrResource::Principal)
1503 )
1504 }
1505}
1506
1507#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1509pub struct ResourceConstraint {
1510 pub(crate) constraint: PrincipalOrResourceConstraint,
1511}
1512
1513impl ResourceConstraint {
1514 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1516 ResourceConstraint { constraint }
1517 }
1518
1519 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1521 &self.constraint
1522 }
1523
1524 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1526 self.constraint
1527 }
1528
1529 pub fn as_expr(&self) -> Expr {
1531 self.constraint.as_expr(PrincipalOrResource::Resource)
1532 }
1533
1534 pub fn any() -> Self {
1536 ResourceConstraint {
1537 constraint: PrincipalOrResourceConstraint::any(),
1538 }
1539 }
1540
1541 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1543 ResourceConstraint {
1544 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1545 }
1546 }
1547
1548 pub fn is_eq_slot() -> Self {
1550 Self {
1551 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1552 }
1553 }
1554
1555 pub fn is_in_slot() -> Self {
1557 Self {
1558 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1559 }
1560 }
1561
1562 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1564 ResourceConstraint {
1565 constraint: PrincipalOrResourceConstraint::is_in(euid),
1566 }
1567 }
1568
1569 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1571 Self {
1572 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1573 }
1574 }
1575
1576 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1578 Self {
1579 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1580 }
1581 }
1582
1583 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1585 Self {
1586 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1587 }
1588 }
1589
1590 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1592 match self.constraint {
1593 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1594 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1595 },
1596 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1597 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1598 },
1599 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1600 constraint: PrincipalOrResourceConstraint::IsIn(
1601 entity_type,
1602 EntityReference::EUID(euid),
1603 ),
1604 },
1605 PrincipalOrResourceConstraint::Is(_)
1607 | PrincipalOrResourceConstraint::Any
1608 | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1609 | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1610 | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1611 }
1612 }
1613}
1614
1615impl std::fmt::Display for ResourceConstraint {
1616 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1617 write!(
1618 f,
1619 "{}",
1620 self.as_inner().display(PrincipalOrResource::Resource)
1621 )
1622 }
1623}
1624
1625#[derive(Educe, Clone, Debug, Eq)]
1627#[educe(Hash, PartialEq, PartialOrd, Ord)]
1628pub enum EntityReference {
1629 EUID(Arc<EntityUID>),
1631 Slot(
1633 #[educe(PartialEq(ignore))]
1634 #[educe(PartialOrd(ignore))]
1635 #[educe(Hash(ignore))]
1636 Option<Loc>,
1637 ),
1638}
1639
1640impl EntityReference {
1641 pub fn euid(euid: Arc<EntityUID>) -> Self {
1643 Self::EUID(euid)
1644 }
1645
1646 pub fn into_expr(&self, slot: SlotId) -> Expr {
1652 match self {
1653 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1654 EntityReference::Slot(loc) => Expr::slot(slot).with_maybe_source_loc(loc.clone()),
1655 }
1656 }
1657}
1658
1659#[derive(Debug, Clone, PartialEq, Eq, Error)]
1661pub enum UnexpectedSlotError {
1662 #[error("found slot `{}` where slots are not allowed", .0.id)]
1664 FoundSlot(Slot),
1665}
1666
1667impl Diagnostic for UnexpectedSlotError {
1668 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1669 match self {
1670 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1671 let label = miette::LabeledSpan::underline(loc.span);
1672 Box::new(std::iter::once(label)) as _
1673 }),
1674 }
1675 }
1676
1677 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
1678 match self {
1679 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|l| l as &dyn miette::SourceCode),
1680 }
1681 }
1682}
1683
1684impl From<EntityUID> for EntityReference {
1685 fn from(euid: EntityUID) -> Self {
1686 Self::EUID(Arc::new(euid))
1687 }
1688}
1689
1690#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1692#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1693pub enum PrincipalOrResource {
1694 Principal,
1696 Resource,
1698}
1699
1700impl std::fmt::Display for PrincipalOrResource {
1701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1702 let v = Var::from(*self);
1703 write!(f, "{v}")
1704 }
1705}
1706
1707impl TryFrom<Var> for PrincipalOrResource {
1708 type Error = Var;
1709
1710 fn try_from(value: Var) -> Result<Self, Self::Error> {
1711 match value {
1712 Var::Principal => Ok(Self::Principal),
1713 Var::Action => Err(Var::Action),
1714 Var::Resource => Ok(Self::Resource),
1715 Var::Context => Err(Var::Context),
1716 }
1717 }
1718}
1719
1720#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1723pub enum PrincipalOrResourceConstraint {
1724 Any,
1726 In(EntityReference),
1728 Eq(EntityReference),
1730 Is(Arc<EntityType>),
1732 IsIn(Arc<EntityType>, EntityReference),
1734}
1735
1736impl PrincipalOrResourceConstraint {
1737 pub fn any() -> Self {
1739 PrincipalOrResourceConstraint::Any
1740 }
1741
1742 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1744 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1745 }
1746
1747 pub fn is_eq_slot() -> Self {
1749 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None))
1750 }
1751
1752 pub fn is_in_slot() -> Self {
1754 PrincipalOrResourceConstraint::In(EntityReference::Slot(None))
1755 }
1756
1757 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1759 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1760 }
1761
1762 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1764 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None))
1765 }
1766
1767 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1769 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1770 }
1771
1772 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1774 PrincipalOrResourceConstraint::Is(entity_type)
1775 }
1776
1777 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1781 match self {
1782 PrincipalOrResourceConstraint::Any => Expr::val(true),
1783 PrincipalOrResourceConstraint::Eq(euid) => {
1784 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1785 }
1786 PrincipalOrResourceConstraint::In(euid) => {
1787 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1788 }
1789 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1790 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1791 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1792 ),
1793 PrincipalOrResourceConstraint::Is(entity_type) => {
1794 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1795 }
1796 }
1797 }
1798
1799 pub fn display(&self, v: PrincipalOrResource) -> String {
1803 match self {
1804 PrincipalOrResourceConstraint::In(euid) => {
1805 format!("{} in {}", v, euid.into_expr(v.into()))
1806 }
1807 PrincipalOrResourceConstraint::Eq(euid) => {
1808 format!("{} == {}", v, euid.into_expr(v.into()))
1809 }
1810 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1811 format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1812 }
1813 PrincipalOrResourceConstraint::Is(entity_type) => {
1814 format!("{v} is {entity_type}")
1815 }
1816 PrincipalOrResourceConstraint::Any => format!("{v}"),
1817 }
1818 }
1819
1820 pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1822 match self {
1823 PrincipalOrResourceConstraint::Any => None,
1824 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1825 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None,
1826 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1827 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None,
1828 PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1829 PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None,
1830 PrincipalOrResourceConstraint::Is(_) => None,
1831 }
1832 }
1833
1834 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1836 self.get_euid()
1837 .into_iter()
1838 .map(|euid| euid.entity_type())
1839 .chain(match self {
1840 PrincipalOrResourceConstraint::Is(entity_type)
1841 | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1842 _ => None,
1843 })
1844 }
1845}
1846
1847#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1850pub enum ActionConstraint {
1851 Any,
1853 In(Vec<Arc<EntityUID>>),
1855 Eq(Arc<EntityUID>),
1857 #[cfg(feature = "tolerant-ast")]
1858 ErrorConstraint,
1860}
1861
1862impl std::fmt::Display for ActionConstraint {
1863 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1864 let render_euids =
1865 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1866 match self {
1867 ActionConstraint::Any => write!(f, "action"),
1868 ActionConstraint::In(euids) => {
1869 write!(f, "action in [{}]", render_euids(euids))
1870 }
1871 ActionConstraint::Eq(euid) => write!(f, "action == {euid}"),
1872 #[cfg(feature = "tolerant-ast")]
1873 ActionConstraint::ErrorConstraint => write!(f, "<invalid_action_constraint>"),
1874 }
1875 }
1876}
1877
1878impl ActionConstraint {
1879 pub fn any() -> Self {
1881 ActionConstraint::Any
1882 }
1883
1884 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1886 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1887 }
1888
1889 pub fn is_eq(euid: EntityUID) -> Self {
1891 ActionConstraint::Eq(Arc::new(euid))
1892 }
1893
1894 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1895 Expr::set(euids.into_iter().map(Expr::val))
1896 }
1897
1898 pub fn as_expr(&self) -> Expr {
1900 match self {
1901 ActionConstraint::Any => Expr::val(true),
1902 ActionConstraint::In(euids) => Expr::is_in(
1903 Expr::var(Var::Action),
1904 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1905 ),
1906 ActionConstraint::Eq(euid) => {
1907 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1908 }
1909 #[cfg(feature = "tolerant-ast")]
1910 ActionConstraint::ErrorConstraint => Expr::new(
1911 ExprKind::Error {
1912 error_kind: AstExprErrorKind::InvalidExpr(
1913 "Invalid action constraint".to_string(),
1914 ),
1915 },
1916 None,
1917 (),
1918 ),
1919 }
1920 }
1921
1922 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1924 match self {
1925 ActionConstraint::Any => EntityIterator::None,
1926 ActionConstraint::In(euids) => {
1927 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1928 }
1929 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1930 #[cfg(feature = "tolerant-ast")]
1931 ActionConstraint::ErrorConstraint => EntityIterator::None,
1932 }
1933 }
1934
1935 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1937 self.iter_euids().map(|euid| euid.entity_type())
1938 }
1939
1940 pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1943 match self {
1944 ActionConstraint::Any => Ok(self),
1945 ActionConstraint::In(ref euids) => {
1946 if let Some(euids) =
1947 NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1948 {
1949 Err(euids)
1950 } else {
1951 Ok(self)
1952 }
1953 }
1954 ActionConstraint::Eq(ref euid) => {
1955 if euid.is_action() {
1956 Ok(self)
1957 } else {
1958 Err(nonempty![euid.clone()])
1959 }
1960 }
1961 #[cfg(feature = "tolerant-ast")]
1962 ActionConstraint::ErrorConstraint => Ok(self),
1963 }
1964 }
1965}
1966
1967impl std::fmt::Display for StaticPolicy {
1968 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1969 let policy_template = &self.0;
1970 match policy_template {
1971 TemplateBody::TemplateBody(template_body_impl) => {
1972 for (k, v) in template_body_impl.annotations.iter() {
1973 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1974 }
1975 write!(
1976 f,
1977 "{}(\n {},\n {},\n {}\n)",
1978 self.effect(),
1979 self.principal_constraint(),
1980 self.action_constraint(),
1981 self.resource_constraint(),
1982 )?;
1983 if let Some(non_scope_constraints) = self.non_scope_constraints() {
1984 write!(f, " when {{\n {non_scope_constraints}\n}};")
1985 } else {
1986 write!(f, ";")
1987 }
1988 }
1989 #[cfg(feature = "tolerant-ast")]
1990 TemplateBody::TemplateBodyError(policy_id, _) => {
1991 write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1992 }
1993 }
1994 }
1995}
1996
1997#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1999pub struct PolicyID(SmolStr);
2000
2001impl PolicyID {
2002 pub fn from_string(id: impl AsRef<str>) -> Self {
2004 Self(SmolStr::from(id.as_ref()))
2005 }
2006
2007 pub fn from_smolstr(id: SmolStr) -> Self {
2009 Self(id)
2010 }
2011
2012 pub fn into_smolstr(self) -> SmolStr {
2014 self.0
2015 }
2016}
2017
2018impl std::fmt::Display for PolicyID {
2019 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2020 write!(f, "{}", self.0.escape_debug())
2021 }
2022}
2023
2024impl AsRef<str> for PolicyID {
2025 fn as_ref(&self) -> &str {
2026 &self.0
2027 }
2028}
2029
2030#[cfg(feature = "arbitrary")]
2031impl arbitrary::Arbitrary<'_> for PolicyID {
2032 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
2033 let s: String = u.arbitrary()?;
2034 Ok(PolicyID::from_string(s))
2035 }
2036 fn size_hint(depth: usize) -> (usize, Option<usize>) {
2037 <String as arbitrary::Arbitrary>::size_hint(depth)
2038 }
2039}
2040
2041#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
2043#[serde(rename_all = "camelCase")]
2044#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2045#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
2046#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
2047pub enum Effect {
2048 Permit,
2050 Forbid,
2052}
2053
2054impl std::fmt::Display for Effect {
2055 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2056 match self {
2057 Self::Permit => write!(f, "permit"),
2058 Self::Forbid => write!(f, "forbid"),
2059 }
2060 }
2061}
2062
2063enum EntityIterator<'a> {
2064 None,
2065 One(&'a EntityUID),
2066 Bunch(Vec<&'a EntityUID>),
2067}
2068
2069impl<'a> Iterator for EntityIterator<'a> {
2070 type Item = &'a EntityUID;
2071
2072 fn next(&mut self) -> Option<Self::Item> {
2073 match self {
2074 EntityIterator::None => None,
2075 EntityIterator::One(euid) => {
2076 let eptr = *euid;
2077 let mut ptr = EntityIterator::None;
2078 std::mem::swap(self, &mut ptr);
2079 Some(eptr)
2080 }
2081 EntityIterator::Bunch(v) => v.pop(),
2082 }
2083 }
2084}
2085
2086#[cfg(test)]
2087pub(crate) mod test_generators {
2088 use super::*;
2089
2090 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
2091 let euid = Arc::new(EntityUID::with_eid("test"));
2092 let v = vec![
2093 PrincipalOrResourceConstraint::any(),
2094 PrincipalOrResourceConstraint::is_eq(euid.clone()),
2095 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)),
2096 PrincipalOrResourceConstraint::is_in(euid),
2097 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)),
2098 ];
2099
2100 v.into_iter()
2101 }
2102
2103 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
2104 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
2105 }
2106
2107 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
2108 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
2109 }
2110
2111 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
2112 let euid: EntityUID = "Action::\"test\""
2113 .parse()
2114 .expect("Invalid action constraint euid");
2115 let v = vec![
2116 ActionConstraint::any(),
2117 ActionConstraint::is_eq(euid.clone()),
2118 ActionConstraint::is_in([euid.clone()]),
2119 ActionConstraint::is_in([euid.clone(), euid]),
2120 ];
2121
2122 v.into_iter()
2123 }
2124
2125 pub fn all_templates() -> impl Iterator<Item = Template> {
2126 let mut buf = vec![];
2127 let permit = PolicyID::from_string("permit");
2128 let forbid = PolicyID::from_string("forbid");
2129 for principal in all_principal_constraints() {
2130 for action in all_actions_constraints() {
2131 for resource in all_resource_constraints() {
2132 let permit = Template::new(
2133 permit.clone(),
2134 None,
2135 Annotations::new(),
2136 Effect::Permit,
2137 principal.clone(),
2138 action.clone(),
2139 resource.clone(),
2140 None,
2141 );
2142 let forbid = Template::new(
2143 forbid.clone(),
2144 None,
2145 Annotations::new(),
2146 Effect::Forbid,
2147 principal.clone(),
2148 action.clone(),
2149 resource.clone(),
2150 None,
2151 );
2152 buf.push(permit);
2153 buf.push(forbid);
2154 }
2155 }
2156 }
2157 buf.into_iter()
2158 }
2159}
2160
2161#[cfg(test)]
2162mod test {
2163 use cool_asserts::assert_matches;
2164 use std::collections::HashSet;
2165
2166 use super::{test_generators::*, *};
2167 use crate::{
2168 parser::{
2169 parse_policy,
2170 test_utils::{expect_exactly_one_error, expect_some_error_matches},
2171 },
2172 test_utils::ExpectedErrorMessageBuilder,
2173 };
2174
2175 #[test]
2176 fn link_templates() {
2177 for template in all_templates() {
2178 template.check_invariant();
2179 let t = Arc::new(template);
2180 let env = t
2181 .slots()
2182 .map(|slot| (slot.id, EntityUID::with_eid("eid")))
2183 .collect();
2184 let _ = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
2185 }
2186 }
2187
2188 #[test]
2189 fn test_template_rebuild() {
2190 for template in all_templates() {
2191 let id = template.id().clone();
2192 let effect = template.effect();
2193 let p = template.principal_constraint().clone();
2194 let a = template.action_constraint().clone();
2195 let r = template.resource_constraint().clone();
2196 let non_scope = template.non_scope_constraints().cloned();
2197 let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
2198 assert_eq!(template, t2);
2199 }
2200 }
2201
2202 #[test]
2203 fn test_static_policy_rebuild() {
2204 for template in all_templates() {
2205 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
2206 let id = ip.id().clone();
2207 let e = ip.effect();
2208 let anno = ip
2209 .annotations()
2210 .map(|(k, v)| (k.clone(), v.clone()))
2211 .collect();
2212 let p = ip.principal_constraint().clone();
2213 let a = ip.action_constraint().clone();
2214 let r = ip.resource_constraint().clone();
2215 let non_scope = ip.non_scope_constraints();
2216 let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope.cloned())
2217 .expect("Policy Creation Failed");
2218 assert_eq!(ip, ip2);
2219 let (t2, inst) = Template::link_static_policy(ip2);
2220 assert!(inst.is_static());
2221 assert_eq!(&template, t2.as_ref());
2222 }
2223 }
2224 }
2225
2226 #[test]
2227 fn ir_binding_too_many() {
2228 let tid = PolicyID::from_string("tid");
2229 let iid = PolicyID::from_string("iid");
2230 let t = Arc::new(Template::new(
2231 tid,
2232 None,
2233 Annotations::new(),
2234 Effect::Forbid,
2235 PrincipalConstraint::is_eq_slot(),
2236 ActionConstraint::Any,
2237 ResourceConstraint::any(),
2238 None,
2239 ));
2240 let mut m = HashMap::new();
2241 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
2242 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2243 assert_eq!(unbound_values, vec![SlotId::principal()]);
2244 assert_eq!(extra_values, vec![SlotId::resource()]);
2245 });
2246 }
2247
2248 #[test]
2249 fn ir_binding_too_few() {
2250 let tid = PolicyID::from_string("tid");
2251 let iid = PolicyID::from_string("iid");
2252 let t = Arc::new(Template::new(
2253 tid,
2254 None,
2255 Annotations::new(),
2256 Effect::Forbid,
2257 PrincipalConstraint::is_eq_slot(),
2258 ActionConstraint::Any,
2259 ResourceConstraint::is_in_slot(),
2260 None,
2261 ));
2262 assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2263 assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
2264 assert_eq!(extra_values, vec![]);
2265 });
2266 let mut m = HashMap::new();
2267 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
2268 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2269 assert_eq!(unbound_values, vec![SlotId::resource()]);
2270 assert_eq!(extra_values, vec![]);
2271 });
2272 }
2273
2274 #[test]
2275 fn ir_binding() {
2276 let tid = PolicyID::from_string("template");
2277 let iid = PolicyID::from_string("linked");
2278 let t = Arc::new(Template::new(
2279 tid,
2280 None,
2281 Annotations::new(),
2282 Effect::Permit,
2283 PrincipalConstraint::is_in_slot(),
2284 ActionConstraint::any(),
2285 ResourceConstraint::is_eq_slot(),
2286 None,
2287 ));
2288
2289 let mut m = HashMap::new();
2290 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2291 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2292
2293 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2294 assert_eq!(r.id(), &iid);
2295 assert_eq!(
2296 r.env().get(&SlotId::principal()),
2297 Some(&EntityUID::with_eid("theprincipal"))
2298 );
2299 assert_eq!(
2300 r.env().get(&SlotId::resource()),
2301 Some(&EntityUID::with_eid("theresource"))
2302 );
2303 }
2304
2305 #[test]
2306 fn isnt_template_implies_from_succeeds() {
2307 for template in all_templates() {
2308 if template.slots().count() == 0 {
2309 StaticPolicy::try_from(template).expect("Should succeed");
2310 }
2311 }
2312 }
2313
2314 #[test]
2315 fn is_template_implies_from_fails() {
2316 for template in all_templates() {
2317 if template.slots().count() != 0 {
2318 assert!(
2319 StaticPolicy::try_from(template.clone()).is_err(),
2320 "Following template did convert {template}"
2321 );
2322 }
2323 }
2324 }
2325
2326 #[test]
2327 fn non_template_iso() {
2328 for template in all_templates() {
2329 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2330 let (t2, _) = Template::link_static_policy(p);
2331 assert_eq!(&template, t2.as_ref());
2332 }
2333 }
2334 }
2335
2336 #[test]
2337 fn template_into_expr() {
2338 for template in all_templates() {
2339 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2340 let t: Template = template;
2341 assert_eq!(p.condition(), t.condition());
2342 assert_eq!(p.effect(), t.effect());
2343 }
2344 }
2345 }
2346
2347 #[test]
2348 fn template_por_iter() {
2349 let e = Arc::new(EntityUID::with_eid("eid"));
2350 assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2351 assert_eq!(
2352 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2353 Some(&e)
2354 );
2355 assert_eq!(
2356 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(),
2357 None
2358 );
2359 assert_eq!(
2360 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2361 Some(&e)
2362 );
2363 assert_eq!(
2364 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(),
2365 None
2366 );
2367 assert_eq!(
2368 PrincipalOrResourceConstraint::IsIn(
2369 Arc::new("T".parse().unwrap()),
2370 EntityReference::EUID(e.clone())
2371 )
2372 .get_euid(),
2373 Some(&e)
2374 );
2375 assert_eq!(
2376 PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2377 None
2378 );
2379 assert_eq!(
2380 PrincipalOrResourceConstraint::IsIn(
2381 Arc::new("T".parse().unwrap()),
2382 EntityReference::Slot(None)
2383 )
2384 .get_euid(),
2385 None
2386 );
2387 }
2388
2389 #[test]
2390 fn action_iter() {
2391 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2392 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2393 let v = a.iter_euids().collect::<Vec<_>>();
2394 assert_eq!(vec![&EntityUID::with_eid("test")], v);
2395 let a =
2396 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2397 let set = a.iter_euids().collect::<HashSet<_>>();
2398 let e1 = EntityUID::with_eid("test1");
2399 let e2 = EntityUID::with_eid("test2");
2400 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2401 assert_eq!(set, correct);
2402 }
2403
2404 #[test]
2405 fn test_iter_none() {
2406 let mut i = EntityIterator::None;
2407 assert_eq!(i.next(), None);
2408 }
2409
2410 #[test]
2411 fn test_iter_once() {
2412 let id = EntityUID::from_components(
2413 name::Name::parse_unqualified_name("s").unwrap().into(),
2414 entity::Eid::new("eid"),
2415 None,
2416 );
2417 let mut i = EntityIterator::One(&id);
2418 assert_eq!(i.next(), Some(&id));
2419 assert_eq!(i.next(), None);
2420 }
2421
2422 #[test]
2423 fn test_iter_mult() {
2424 let id1 = EntityUID::from_components(
2425 name::Name::parse_unqualified_name("s").unwrap().into(),
2426 entity::Eid::new("eid1"),
2427 None,
2428 );
2429 let id2 = EntityUID::from_components(
2430 name::Name::parse_unqualified_name("s").unwrap().into(),
2431 entity::Eid::new("eid2"),
2432 None,
2433 );
2434 let v = vec![&id1, &id2];
2435 let mut i = EntityIterator::Bunch(v);
2436 assert_eq!(i.next(), Some(&id2));
2437 assert_eq!(i.next(), Some(&id1));
2438 assert_eq!(i.next(), None)
2439 }
2440
2441 #[test]
2442 fn euid_into_expr() {
2443 let e = EntityReference::Slot(None);
2444 assert_eq!(
2445 e.into_expr(SlotId::principal()),
2446 Expr::slot(SlotId::principal())
2447 );
2448 let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2449 assert_eq!(
2450 e.into_expr(SlotId::principal()),
2451 Expr::val(EntityUID::with_eid("eid"))
2452 );
2453 }
2454
2455 #[test]
2456 fn por_constraint_display() {
2457 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None));
2458 let s = t.display(PrincipalOrResource::Principal);
2459 assert_eq!(s, "principal == ?principal");
2460 let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2461 EntityUID::with_eid("test"),
2462 )));
2463 let s = t.display(PrincipalOrResource::Principal);
2464 assert_eq!(s, "principal == test_entity_type::\"test\"");
2465 }
2466
2467 #[test]
2468 fn unexpected_templates() {
2469 let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2470 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2471 expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2472 "expected a static policy, got a template containing the slot ?principal"
2473 )
2474 .help("try removing the template slot(s) from this policy")
2475 .exactly_one_underline("?principal")
2476 .build()
2477 );
2478 });
2479
2480 let policy_str =
2481 r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2482 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2483 expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2484 "expected a static policy, got a template containing the slot ?principal"
2485 )
2486 .help("try removing the template slot(s) from this policy")
2487 .exactly_one_underline("?principal")
2488 .build()
2489 );
2490 assert_eq!(e.len(), 2);
2491 });
2492 }
2493
2494 #[test]
2495 fn policy_to_expr() {
2496 let policy_str = r#"permit(principal is A, action, resource is B)
2497 when { 1 == 2 }
2498 unless { 2 == 1}
2499 when { 3 == 4}
2500 unless { 4 == 3};"#;
2501 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Ok(p) => {
2502 assert_eq!(ToString::to_string(&p.condition()), "(principal is A) && (true && ((resource is B) && ((1 == 2) && ((!(2 == 1)) && ((3 == 4) && (!(4 == 3)))))))");
2503 });
2504 }
2505
2506 #[cfg(feature = "tolerant-ast")]
2507 #[test]
2508 fn template_body_error_methods() {
2509 use std::str::FromStr;
2510
2511 let policy_id = PolicyID::from_string("error_policy");
2512 let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2513 let error_body = TemplateBody::TemplateBodyError(policy_id.clone(), error_loc.clone());
2514
2515 let expected_error = <ExprWithErrsBuilder as ExprBuilder>::new()
2516 .error(ParseErrors::singleton(ToASTError::new(
2517 ToASTErrorKind::ASTErrorNode,
2518 Some(Loc::new(0..1, "ASTErrorNode".into())),
2519 )))
2520 .unwrap();
2521
2522 assert_eq!(error_body.id(), &policy_id);
2524
2525 assert_eq!(error_body.loc(), error_loc.as_ref());
2527
2528 let new_policy_id = PolicyID::from_string("new_error_policy");
2530 let updated_error_body = error_body.new_id(new_policy_id.clone());
2531 assert_matches!(updated_error_body,
2532 TemplateBody::TemplateBodyError(id, loc) if id == new_policy_id && loc == error_loc
2533 );
2534
2535 assert_eq!(error_body.effect(), Effect::Forbid);
2537
2538 assert_eq!(
2540 error_body.annotation(&AnyId::from_str("test").unwrap()),
2541 None
2542 );
2543
2544 assert!(error_body.annotations().count() == 0);
2546
2547 assert_eq!(
2549 *error_body.principal_constraint(),
2550 PrincipalConstraint::any()
2551 );
2552
2553 assert_eq!(*error_body.action_constraint(), ActionConstraint::any());
2555
2556 assert_eq!(*error_body.resource_constraint(), ResourceConstraint::any());
2558
2559 assert_eq!(error_body.non_scope_constraints(), Some(&expected_error));
2561
2562 assert_eq!(error_body.condition(), expected_error);
2564
2565 let display_str = format!("{error_body}");
2567 assert!(display_str.contains("TemplateBodyError"));
2568 assert!(display_str.contains("error_policy"));
2569 }
2570
2571 #[cfg(feature = "tolerant-ast")]
2572 #[test]
2573 fn template_error_methods() {
2574 let policy_id = PolicyID::from_string("error_policy");
2575 let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2576 let error_template = Template::error(policy_id.clone(), error_loc.clone());
2577
2578 assert_eq!(error_template.id(), &policy_id);
2580
2581 assert!(error_template.slots().count() == 0);
2583
2584 assert_matches!(error_template.body,
2586 TemplateBody::TemplateBodyError(ref id, ref loc) if id == &policy_id && loc == &error_loc
2587 );
2588
2589 assert_eq!(
2591 error_template.principal_constraint(),
2592 &PrincipalConstraint::any()
2593 );
2594
2595 assert_eq!(*error_template.action_constraint(), ActionConstraint::any());
2597
2598 assert_eq!(
2600 *error_template.resource_constraint(),
2601 ResourceConstraint::any()
2602 );
2603
2604 assert_eq!(error_template.effect(), Effect::Forbid);
2606
2607 assert_eq!(
2609 error_template.condition(),
2610 DEFAULT_ERROR_EXPR.as_ref().clone()
2611 );
2612
2613 assert_eq!(error_template.loc(), error_loc.as_ref());
2615
2616 assert!(error_template.annotations().count() == 0);
2618
2619 let display_str = format!("{error_template}");
2621 assert!(display_str.contains("TemplateBody::TemplateBodyError"));
2622 assert!(display_str.contains(&policy_id.to_string()));
2623 }
2624}