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 #[cfg_attr(feature = "tolerant-ast", expect(clippy::unwrap_used, reason = "Infallible error type - can never fail"))]
67 static DEFAULT_ERROR_EXPR: std::sync::LazyLock<Arc<Expr>> = std::sync::LazyLock::new(|| {
68 Arc::new(
71 <ExprWithErrsBuilder as ExprBuilder>::new()
72 .error(ParseErrors::singleton(ToASTError::new(
73 ToASTErrorKind::ASTErrorNode,
74 Some(Loc::new(0..1, "ASTErrorNode".into())),
75 )))
76 .unwrap(),
77 )
78 });
79}
80
81#[derive(Clone, Hash, Eq, PartialEq, Debug)]
86pub struct Template {
87 body: TemplateBody,
88 slots: Vec<Slot>,
93}
94
95impl From<Template> for TemplateBody {
96 fn from(val: Template) -> Self {
97 val.body
98 }
99}
100
101impl Template {
102 pub fn check_invariant(&self) {
106 #[cfg(debug_assertions)]
107 {
108 for slot in self.body.condition().slots() {
109 assert!(self.slots.contains(&slot));
110 }
111 for slot in self.slots() {
112 assert!(self.body.condition().slots().contains(slot));
113 }
114 }
115 }
116
117 #[expect(
119 clippy::too_many_arguments,
120 reason = "policies just have this many components"
121 )]
122 pub fn new(
123 id: PolicyID,
124 loc: Option<Loc>,
125 annotations: Annotations,
126 effect: Effect,
127 principal_constraint: PrincipalConstraint,
128 action_constraint: ActionConstraint,
129 resource_constraint: ResourceConstraint,
130 non_scope_constraint: Option<Expr>,
131 ) -> Self {
132 let body = TemplateBody::new(
133 id,
134 loc,
135 annotations,
136 effect,
137 principal_constraint,
138 action_constraint,
139 resource_constraint,
140 non_scope_constraint,
141 );
142 Template::from(body)
145 }
146
147 #[cfg(feature = "tolerant-ast")]
148 pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
150 let body = TemplateBody::error(id, loc);
151 Template::from(body)
152 }
153
154 #[expect(
156 clippy::too_many_arguments,
157 reason = "policies just have this many components"
158 )]
159 pub fn new_shared(
160 id: PolicyID,
161 loc: Option<Loc>,
162 annotations: Arc<Annotations>,
163 effect: Effect,
164 principal_constraint: PrincipalConstraint,
165 action_constraint: ActionConstraint,
166 resource_constraint: ResourceConstraint,
167 non_scope_constraint: Option<Arc<Expr>>,
168 ) -> Self {
169 let body = TemplateBody::new_shared(
170 id,
171 loc,
172 annotations,
173 effect,
174 principal_constraint,
175 action_constraint,
176 resource_constraint,
177 non_scope_constraint,
178 );
179 Template::from(body)
182 }
183
184 pub fn principal_constraint(&self) -> &PrincipalConstraint {
186 self.body.principal_constraint()
187 }
188
189 pub fn action_constraint(&self) -> &ActionConstraint {
191 self.body.action_constraint()
192 }
193
194 pub fn resource_constraint(&self) -> &ResourceConstraint {
196 self.body.resource_constraint()
197 }
198
199 pub fn non_scope_constraints(&self) -> Option<&Expr> {
201 self.body.non_scope_constraints()
202 }
203
204 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
206 self.body.non_scope_constraints_arc()
207 }
208
209 pub fn id(&self) -> &PolicyID {
211 self.body.id()
212 }
213
214 pub fn new_id(&self, id: PolicyID) -> Self {
216 Template {
217 body: self.body.new_id(id),
218 slots: self.slots.clone(),
219 }
220 }
221
222 pub fn loc(&self) -> Option<&Loc> {
224 self.body.loc()
225 }
226
227 pub fn effect(&self) -> Effect {
229 self.body.effect()
230 }
231
232 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
234 self.body.annotation(key)
235 }
236
237 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
239 self.body.annotations()
240 }
241
242 pub fn annotations_arc(&self) -> &Arc<Annotations> {
244 self.body.annotations_arc()
245 }
246
247 pub fn condition(&self) -> Expr {
253 self.body.condition()
254 }
255
256 pub fn slots(&self) -> impl Iterator<Item = &Slot> {
258 self.slots.iter()
259 }
260
261 pub fn is_static(&self) -> bool {
266 self.slots.is_empty()
267 }
268
269 pub fn check_binding(
273 template: &Template,
274 values: &HashMap<SlotId, EntityUID>,
275 ) -> Result<(), LinkingError> {
276 let unbound = template
278 .slots
279 .iter()
280 .filter(|slot| !values.contains_key(&slot.id))
281 .collect::<Vec<_>>();
282
283 let extra = values
284 .iter()
285 .filter_map(|(slot, _)| {
286 if !template
287 .slots
288 .iter()
289 .any(|template_slot| template_slot.id == *slot)
290 {
291 Some(slot)
292 } else {
293 None
294 }
295 })
296 .collect::<Vec<_>>();
297
298 if unbound.is_empty() && extra.is_empty() {
299 Ok(())
300 } else {
301 Err(LinkingError::from_unbound_and_extras(
302 unbound.into_iter().map(|slot| slot.id),
303 extra.into_iter().copied(),
304 ))
305 }
306 }
307
308 pub fn link(
312 template: Arc<Template>,
313 new_id: PolicyID,
314 values: HashMap<SlotId, EntityUID>,
315 ) -> Result<Policy, LinkingError> {
316 Template::check_binding(&template, &values)
318 .map(|_| Policy::new(template, Some(new_id), values))
319 }
320
321 pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
324 let body: TemplateBody = p.into();
325 let t = Arc::new(Self {
329 body,
330 slots: vec![],
331 });
332 t.check_invariant();
333 let p = Policy::new(Arc::clone(&t), None, HashMap::new());
334 (t, p)
335 }
336}
337
338impl From<TemplateBody> for Template {
339 fn from(body: TemplateBody) -> Self {
340 let slots = body.condition().slots().collect::<Vec<_>>();
343 Self { body, slots }
344 }
345}
346
347impl std::fmt::Display for Template {
348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 write!(f, "{}", self.body)
350 }
351}
352
353#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
355pub enum LinkingError {
356 #[error(fmt = describe_arity_error)]
359 ArityError {
360 unbound_values: Vec<SlotId>,
362 extra_values: Vec<SlotId>,
364 },
365
366 #[error("failed to find a template with id `{id}`")]
368 NoSuchTemplate {
369 id: PolicyID,
371 },
372
373 #[error("template-linked policy id `{id}` conflicts with an existing policy id")]
375 PolicyIdConflict {
376 id: PolicyID,
378 },
379}
380
381impl LinkingError {
382 fn from_unbound_and_extras(
383 unbound: impl Iterator<Item = SlotId>,
384 extra: impl Iterator<Item = SlotId>,
385 ) -> Self {
386 Self::ArityError {
387 unbound_values: unbound.collect(),
388 extra_values: extra.collect(),
389 }
390 }
391}
392
393fn describe_arity_error(
394 unbound_values: &[SlotId],
395 extra_values: &[SlotId],
396 fmt: &mut std::fmt::Formatter<'_>,
397) -> std::fmt::Result {
398 match (unbound_values.len(), extra_values.len()) {
399 #[expect(clippy::unreachable, reason = "0,0 case is not an error")]
400 (0,0) => unreachable!(),
401 (_unbound, 0) => write!(fmt, "the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
402 (0, _extra) => write!(fmt, "the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
403 (_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(",")),
404 }
405}
406
407#[derive(Debug, Clone, Eq, PartialEq)]
415pub struct Policy {
416 template: Arc<Template>,
418 link: Option<PolicyID>,
422 values: HashMap<SlotId, EntityUID>,
429}
430
431impl Policy {
432 fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
436 #[cfg(debug_assertions)]
437 {
438 #[expect(
439 clippy::expect_used,
440 reason = "asserts (value total map invariant) which is justified at call sites"
441 )]
442 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
443 }
444 Self {
445 template,
446 link: link_id,
447 values,
448 }
449 }
450
451 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID, loc: Option<Loc>) -> Self {
453 Self::from_when_clause_annos(
454 effect,
455 Arc::new(when),
456 id,
457 loc,
458 Arc::new(Annotations::default()),
459 )
460 }
461
462 pub fn from_when_clause_annos(
464 effect: Effect,
465 when: Arc<Expr>,
466 id: PolicyID,
467 loc: Option<Loc>,
468 annotations: Arc<Annotations>,
469 ) -> Self {
470 let t = Template::new_shared(
471 id,
472 loc,
473 annotations,
474 effect,
475 PrincipalConstraint::any(),
476 ActionConstraint::any(),
477 ResourceConstraint::any(),
478 Some(when),
479 );
480 Self::new(Arc::new(t), None, SlotEnv::new())
481 }
482
483 pub fn template(&self) -> &Template {
485 &self.template
486 }
487
488 pub(crate) fn template_arc(&self) -> Arc<Template> {
490 Arc::clone(&self.template)
491 }
492
493 pub fn effect(&self) -> Effect {
495 self.template.effect()
496 }
497
498 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
500 self.template.annotation(key)
501 }
502
503 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
505 self.template.annotations()
506 }
507
508 pub fn annotations_arc(&self) -> &Arc<Annotations> {
510 self.template.annotations_arc()
511 }
512
513 pub fn principal_constraint(&self) -> PrincipalConstraint {
519 let constraint = self.template.principal_constraint().clone();
520 match self.values.get(&SlotId::principal()) {
521 None => constraint,
522 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
523 }
524 }
525
526 pub fn action_constraint(&self) -> &ActionConstraint {
528 self.template.action_constraint()
529 }
530
531 pub fn resource_constraint(&self) -> ResourceConstraint {
537 let constraint = self.template.resource_constraint().clone();
538 match self.values.get(&SlotId::resource()) {
539 None => constraint,
540 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
541 }
542 }
543
544 pub fn non_scope_constraints(&self) -> Option<&Expr> {
546 self.template.non_scope_constraints()
547 }
548
549 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
551 self.template.non_scope_constraints_arc()
552 }
553
554 pub fn condition(&self) -> Expr {
556 self.template.condition()
557 }
558
559 pub fn env(&self) -> &SlotEnv {
562 &self.values
563 }
564
565 pub fn id(&self) -> &PolicyID {
567 self.link.as_ref().unwrap_or_else(|| self.template.id())
568 }
569
570 pub fn new_id(&self, id: PolicyID) -> Self {
572 match self.link {
573 None => Policy {
574 template: Arc::new(self.template.new_id(id)),
575 link: None,
576 values: self.values.clone(),
577 },
578 Some(_) => Policy {
579 template: self.template.clone(),
580 link: Some(id),
581 values: self.values.clone(),
582 },
583 }
584 }
585
586 pub(crate) fn new_template_id(self, id: PolicyID) -> Option<Self> {
589 self.link.map(|link| Policy {
590 template: Arc::new(self.template.new_id(id)),
591 link: Some(link),
592 values: self.values,
593 })
594 }
595
596 pub fn loc(&self) -> Option<&Loc> {
598 self.template.loc()
599 }
600
601 pub fn is_static(&self) -> bool {
603 self.link.is_none()
604 }
605
606 pub fn unknown_entities(&self) -> HashSet<EntityUID> {
608 self.condition()
609 .unknowns()
610 .filter_map(
611 |Unknown {
612 name,
613 type_annotation,
614 }| {
615 if matches!(type_annotation, Some(Type::Entity { .. })) {
616 EntityUID::from_str(name.as_str()).ok()
617 } else {
618 None
619 }
620 },
621 )
622 .collect()
623 }
624}
625
626impl std::fmt::Display for Policy {
627 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
628 if self.is_static() {
629 write!(f, "{}", self.template())
630 } else {
631 write!(
632 f,
633 "Template Instance of {}, slots: [{}]",
634 self.template().id(),
635 display_slot_env(self.env())
636 )
637 }
638 }
639}
640
641pub type SlotEnv = HashMap<SlotId, EntityUID>;
643
644#[derive(Debug, Clone, PartialEq, Eq)]
651pub struct LiteralPolicy {
652 template_id: PolicyID,
654 link_id: Option<PolicyID>,
658 values: SlotEnv,
660}
661
662impl LiteralPolicy {
663 pub fn static_policy(template_id: PolicyID) -> Self {
667 Self {
668 template_id,
669 link_id: None,
670 values: SlotEnv::new(),
671 }
672 }
673
674 pub fn template_linked_policy(
678 template_id: PolicyID,
679 link_id: PolicyID,
680 values: SlotEnv,
681 ) -> Self {
682 Self {
683 template_id,
684 link_id: Some(link_id),
685 values,
686 }
687 }
688
689 pub fn value(&self, slot: &SlotId) -> Option<&EntityUID> {
691 self.values.get(slot)
692 }
693}
694
695impl std::hash::Hash for LiteralPolicy {
698 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
699 self.template_id.hash(state);
700 let mut buf = self.values.iter().collect::<Vec<_>>();
702 buf.sort();
703 for (id, euid) in buf {
704 id.hash(state);
705 euid.hash(state);
706 }
707 }
708}
709
710#[cfg(test)]
712mod hashing_tests {
713 use std::{
714 collections::hash_map::DefaultHasher,
715 hash::{Hash, Hasher},
716 };
717
718 use super::*;
719
720 fn compute_hash(ir: &LiteralPolicy) -> u64 {
721 let mut s = DefaultHasher::new();
722 ir.hash(&mut s);
723 s.finish()
724 }
725
726 fn build_template_linked_policy() -> LiteralPolicy {
727 let mut map = HashMap::new();
728 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
729 LiteralPolicy {
730 template_id: PolicyID::from_string("template"),
731 link_id: Some(PolicyID::from_string("id")),
732 values: map,
733 }
734 }
735
736 #[test]
737 fn hash_property_instances() {
738 let a = build_template_linked_policy();
739 let b = build_template_linked_policy();
740 assert_eq!(a, b);
741 assert_eq!(compute_hash(&a), compute_hash(&b));
742 }
743}
744
745#[derive(Debug, Diagnostic, Error)]
747pub enum ReificationError {
748 #[error("the id linked to does not exist")]
750 NoSuchTemplate(PolicyID),
751 #[error(transparent)]
753 #[diagnostic(transparent)]
754 Linking(#[from] LinkingError),
755}
756
757impl LiteralPolicy {
758 pub fn reify(
763 self,
764 templates: &LinkedHashMap<PolicyID, Arc<Template>>,
765 ) -> Result<Policy, ReificationError> {
766 let template = templates
767 .get(&self.template_id)
768 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
769 Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
771 Ok(Policy::new(template.clone(), self.link_id, self.values))
772 }
773
774 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
776 self.values.get(id)
777 }
778
779 pub fn id(&self) -> &PolicyID {
781 self.link_id.as_ref().unwrap_or(&self.template_id)
782 }
783
784 pub fn template_id(&self) -> &PolicyID {
788 &self.template_id
789 }
790
791 pub fn is_static(&self) -> bool {
793 self.link_id.is_none()
794 }
795}
796
797fn display_slot_env(env: &SlotEnv) -> String {
798 env.iter()
799 .map(|(slot, value)| format!("{slot} -> {value}"))
800 .join(",")
801}
802
803impl std::fmt::Display for LiteralPolicy {
804 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
805 if self.is_static() {
806 write!(f, "Static policy w/ ID {}", self.template_id())
807 } else {
808 write!(
809 f,
810 "Template linked policy of {}, slots: [{}]",
811 self.template_id(),
812 display_slot_env(&self.values),
813 )
814 }
815 }
816}
817
818impl From<Policy> for LiteralPolicy {
819 fn from(p: Policy) -> Self {
820 Self {
821 template_id: p.template.id().clone(),
822 link_id: p.link,
823 values: p.values,
824 }
825 }
826}
827
828#[derive(Clone, Hash, Eq, PartialEq, Debug)]
832pub struct StaticPolicy(TemplateBody);
833
834impl StaticPolicy {
835 pub fn id(&self) -> &PolicyID {
837 self.0.id()
838 }
839
840 pub fn new_id(&self, id: PolicyID) -> Self {
842 StaticPolicy(self.0.new_id(id))
843 }
844
845 pub fn loc(&self) -> Option<&Loc> {
847 self.0.loc()
848 }
849
850 pub fn effect(&self) -> Effect {
852 self.0.effect()
853 }
854
855 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
857 self.0.annotation(key)
858 }
859
860 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
862 self.0.annotations()
863 }
864
865 pub fn principal_constraint(&self) -> &PrincipalConstraint {
867 self.0.principal_constraint()
868 }
869
870 pub fn principal_constraint_expr(&self) -> Expr {
874 self.0.principal_constraint_expr()
875 }
876
877 pub fn action_constraint(&self) -> &ActionConstraint {
879 self.0.action_constraint()
880 }
881
882 pub fn action_constraint_expr(&self) -> Expr {
886 self.0.action_constraint_expr()
887 }
888
889 pub fn resource_constraint(&self) -> &ResourceConstraint {
891 self.0.resource_constraint()
892 }
893
894 pub fn resource_constraint_expr(&self) -> Expr {
898 self.0.resource_constraint_expr()
899 }
900
901 pub fn non_scope_constraints(&self) -> Option<&Expr> {
906 self.0.non_scope_constraints()
907 }
908
909 pub fn condition(&self) -> Expr {
915 self.0.condition()
916 }
917
918 #[expect(
920 clippy::too_many_arguments,
921 reason = "policies just have this many components"
922 )]
923 pub fn new(
924 id: PolicyID,
925 loc: Option<Loc>,
926 annotations: Annotations,
927 effect: Effect,
928 principal_constraint: PrincipalConstraint,
929 action_constraint: ActionConstraint,
930 resource_constraint: ResourceConstraint,
931 non_scope_constraints: Option<Expr>,
932 ) -> Result<Self, UnexpectedSlotError> {
933 let body = TemplateBody::new(
934 id,
935 loc,
936 annotations,
937 effect,
938 principal_constraint,
939 action_constraint,
940 resource_constraint,
941 non_scope_constraints,
942 );
943 let first_slot = body.condition().slots().next();
944 match first_slot {
946 Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
947 None => Ok(Self(body)),
948 }
949 }
950}
951
952impl TryFrom<Template> for StaticPolicy {
953 type Error = UnexpectedSlotError;
954
955 fn try_from(value: Template) -> Result<Self, Self::Error> {
956 let o = value.slots().next();
958 match o {
959 Some(slot_id) => Err(Self::Error::FoundSlot(slot_id.clone())),
960 None => Ok(Self(value.body)),
961 }
962 }
963}
964
965impl From<StaticPolicy> for Policy {
966 fn from(p: StaticPolicy) -> Policy {
967 let (_, policy) = Template::link_static_policy(p);
968 policy
969 }
970}
971
972impl From<StaticPolicy> for Arc<Template> {
973 fn from(p: StaticPolicy) -> Self {
974 let (t, _) = Template::link_static_policy(p);
975 t
976 }
977}
978
979#[derive(Educe, Clone, Debug)]
982#[educe(PartialEq, Eq, Hash)]
983pub struct TemplateBodyImpl {
984 id: PolicyID,
986 #[educe(PartialEq(ignore))]
988 #[educe(Hash(ignore))]
989 loc: Option<Loc>,
990 annotations: Arc<Annotations>,
994 effect: Effect,
996 principal_constraint: PrincipalConstraint,
1000 action_constraint: ActionConstraint,
1004 resource_constraint: ResourceConstraint,
1008 non_scope_constraints: Option<Arc<Expr>>,
1013}
1014
1015#[derive(Clone, Hash, Eq, PartialEq, Debug)]
1018pub enum TemplateBody {
1019 TemplateBody(TemplateBodyImpl),
1021 #[cfg(feature = "tolerant-ast")]
1022 TemplateBodyError(PolicyID, Option<Loc>),
1024}
1025
1026impl TemplateBody {
1027 pub fn id(&self) -> &PolicyID {
1029 match self {
1030 TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => id,
1031 #[cfg(feature = "tolerant-ast")]
1032 TemplateBody::TemplateBodyError(id, _) => id,
1033 }
1034 }
1035
1036 pub fn loc(&self) -> Option<&Loc> {
1038 match self {
1039 TemplateBody::TemplateBody(TemplateBodyImpl { loc, .. }) => loc.as_ref(),
1040 #[cfg(feature = "tolerant-ast")]
1041 TemplateBody::TemplateBodyError(_, loc) => loc.as_ref(),
1042 }
1043 }
1044
1045 pub fn new_id(&self, id: PolicyID) -> Self {
1047 match self {
1048 TemplateBody::TemplateBody(t) => {
1049 let mut new = t.clone();
1050 new.id = id;
1051 TemplateBody::TemplateBody(new)
1052 }
1053 #[cfg(feature = "tolerant-ast")]
1054 TemplateBody::TemplateBodyError(_, loc) => {
1055 TemplateBody::TemplateBodyError(id, loc.clone())
1056 }
1057 }
1058 }
1059
1060 #[cfg(feature = "tolerant-ast")]
1061 pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
1063 TemplateBody::TemplateBodyError(id, loc)
1064 }
1065
1066 pub fn effect(&self) -> Effect {
1068 match self {
1069 TemplateBody::TemplateBody(TemplateBodyImpl { effect, .. }) => *effect,
1070 #[cfg(feature = "tolerant-ast")]
1071 TemplateBody::TemplateBodyError(_, _) => Effect::Forbid,
1072 }
1073 }
1074
1075 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
1077 match self {
1078 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => {
1079 annotations.get(key)
1080 }
1081 #[cfg(feature = "tolerant-ast")]
1082 TemplateBody::TemplateBodyError(_, _) => None,
1083 }
1084 }
1085
1086 pub fn annotations_arc(&self) -> &Arc<Annotations> {
1088 match self {
1089 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations,
1090 #[cfg(feature = "tolerant-ast")]
1091 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ANNOTATIONS,
1092 }
1093 }
1094
1095 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1097 match self {
1098 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations.iter(),
1099 #[cfg(feature = "tolerant-ast")]
1100 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ANNOTATIONS.iter(),
1101 }
1102 }
1103
1104 pub fn principal_constraint(&self) -> &PrincipalConstraint {
1106 match self {
1107 TemplateBody::TemplateBody(TemplateBodyImpl {
1108 principal_constraint,
1109 ..
1110 }) => principal_constraint,
1111 #[cfg(feature = "tolerant-ast")]
1112 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_PRINCIPAL_CONSTRAINT,
1113 }
1114
1115 }
1117
1118 pub fn principal_constraint_expr(&self) -> Expr {
1122 match self {
1123 TemplateBody::TemplateBody(TemplateBodyImpl {
1124 principal_constraint,
1125 ..
1126 }) => principal_constraint.as_expr(),
1127 #[cfg(feature = "tolerant-ast")]
1128 TemplateBody::TemplateBodyError(_, _) => DEFAULT_PRINCIPAL_CONSTRAINT.as_expr(),
1129 }
1130 }
1131
1132 pub fn action_constraint(&self) -> &ActionConstraint {
1134 match self {
1135 TemplateBody::TemplateBody(TemplateBodyImpl {
1136 action_constraint, ..
1137 }) => action_constraint,
1138 #[cfg(feature = "tolerant-ast")]
1139 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ACTION_CONSTRAINT,
1140 }
1141 }
1142
1143 pub fn action_constraint_expr(&self) -> Expr {
1147 match self {
1148 TemplateBody::TemplateBody(TemplateBodyImpl {
1149 action_constraint, ..
1150 }) => action_constraint.as_expr(),
1151 #[cfg(feature = "tolerant-ast")]
1152 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ACTION_CONSTRAINT.as_expr(),
1153 }
1154 }
1155
1156 pub fn resource_constraint(&self) -> &ResourceConstraint {
1158 match self {
1159 TemplateBody::TemplateBody(TemplateBodyImpl {
1160 resource_constraint,
1161 ..
1162 }) => resource_constraint,
1163 #[cfg(feature = "tolerant-ast")]
1164 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_RESOURCE_CONSTRAINT,
1165 }
1166 }
1167
1168 pub fn resource_constraint_expr(&self) -> Expr {
1172 match self {
1173 TemplateBody::TemplateBody(TemplateBodyImpl {
1174 resource_constraint,
1175 ..
1176 }) => resource_constraint.as_expr(),
1177 #[cfg(feature = "tolerant-ast")]
1178 TemplateBody::TemplateBodyError(_, _) => DEFAULT_RESOURCE_CONSTRAINT.as_expr(),
1179 }
1180 }
1181
1182 pub fn non_scope_constraints(&self) -> Option<&Expr> {
1187 match self {
1188 TemplateBody::TemplateBody(TemplateBodyImpl {
1189 non_scope_constraints,
1190 ..
1191 }) => non_scope_constraints.as_ref().map(|e| e.as_ref()),
1192 #[cfg(feature = "tolerant-ast")]
1193 TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1194 }
1195 }
1196
1197 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
1199 match self {
1200 TemplateBody::TemplateBody(TemplateBodyImpl {
1201 non_scope_constraints,
1202 ..
1203 }) => non_scope_constraints.as_ref(),
1204 #[cfg(feature = "tolerant-ast")]
1205 TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1206 }
1207 }
1208
1209 pub fn condition(&self) -> Expr {
1215 match self {
1216 TemplateBody::TemplateBody(TemplateBodyImpl { .. }) => {
1217 let loc = self.loc().cloned();
1218 Expr::and(
1219 self.principal_constraint_expr(),
1220 Expr::and(
1221 self.action_constraint_expr(),
1222 Expr::and(
1223 self.resource_constraint_expr(),
1224 self.non_scope_constraints()
1225 .cloned()
1226 .unwrap_or_else(|| Expr::val(true)),
1227 )
1228 .with_maybe_source_loc(loc.clone()),
1229 )
1230 .with_maybe_source_loc(loc.clone()),
1231 )
1232 .with_maybe_source_loc(loc)
1233 }
1234 #[cfg(feature = "tolerant-ast")]
1235 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ERROR_EXPR.as_ref().clone(),
1236 }
1237 }
1238
1239 #[expect(
1241 clippy::too_many_arguments,
1242 reason = "policies just have this many components"
1243 )]
1244 pub fn new_shared(
1245 id: PolicyID,
1246 loc: Option<Loc>,
1247 annotations: Arc<Annotations>,
1248 effect: Effect,
1249 principal_constraint: PrincipalConstraint,
1250 action_constraint: ActionConstraint,
1251 resource_constraint: ResourceConstraint,
1252 non_scope_constraints: Option<Arc<Expr>>,
1253 ) -> Self {
1254 Self::TemplateBody(TemplateBodyImpl {
1255 id,
1256 loc,
1257 annotations,
1258 effect,
1259 principal_constraint,
1260 action_constraint,
1261 resource_constraint,
1262 non_scope_constraints,
1263 })
1264 }
1265
1266 #[expect(
1268 clippy::too_many_arguments,
1269 reason = "policies just have this many components"
1270 )]
1271 pub fn new(
1272 id: PolicyID,
1273 loc: Option<Loc>,
1274 annotations: Annotations,
1275 effect: Effect,
1276 principal_constraint: PrincipalConstraint,
1277 action_constraint: ActionConstraint,
1278 resource_constraint: ResourceConstraint,
1279 non_scope_constraints: Option<Expr>,
1280 ) -> Self {
1281 Self::TemplateBody(TemplateBodyImpl {
1282 id,
1283 loc,
1284 annotations: Arc::new(annotations),
1285 effect,
1286 principal_constraint,
1287 action_constraint,
1288 resource_constraint,
1289 non_scope_constraints: non_scope_constraints.map(Arc::new),
1290 })
1291 }
1292}
1293
1294impl From<StaticPolicy> for TemplateBody {
1295 fn from(p: StaticPolicy) -> Self {
1296 p.0
1297 }
1298}
1299
1300impl std::fmt::Display for TemplateBody {
1301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1302 match self {
1303 TemplateBody::TemplateBody(template_body_impl) => {
1304 template_body_impl.annotations.fmt(f)?;
1305 write!(
1306 f,
1307 "{}(\n {},\n {},\n {}\n)",
1308 self.effect(),
1309 self.principal_constraint(),
1310 self.action_constraint(),
1311 self.resource_constraint(),
1312 )?;
1313 if let Some(non_scope_constraints) = self.non_scope_constraints() {
1314 write!(f, " when {{\n {non_scope_constraints}\n}};")
1315 } else {
1316 write!(f, ";")
1317 }
1318 }
1319 #[cfg(feature = "tolerant-ast")]
1320 TemplateBody::TemplateBodyError(policy_id, _) => {
1321 write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1322 }
1323 }
1324 }
1325}
1326
1327#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1329pub struct PrincipalConstraint {
1330 pub(crate) constraint: PrincipalOrResourceConstraint,
1331}
1332
1333impl PrincipalConstraint {
1334 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1336 PrincipalConstraint { constraint }
1337 }
1338
1339 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1341 &self.constraint
1342 }
1343
1344 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1346 self.constraint
1347 }
1348
1349 pub fn as_expr(&self) -> Expr {
1351 self.constraint.as_expr(PrincipalOrResource::Principal)
1352 }
1353
1354 pub fn any() -> Self {
1356 PrincipalConstraint {
1357 constraint: PrincipalOrResourceConstraint::any(),
1358 }
1359 }
1360
1361 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1363 PrincipalConstraint {
1364 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1365 }
1366 }
1367
1368 pub fn is_eq_slot() -> Self {
1370 Self {
1371 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1372 }
1373 }
1374
1375 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1377 PrincipalConstraint {
1378 constraint: PrincipalOrResourceConstraint::is_in(euid),
1379 }
1380 }
1381
1382 pub fn is_in_slot() -> Self {
1384 Self {
1385 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1386 }
1387 }
1388
1389 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1391 Self {
1392 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1393 }
1394 }
1395
1396 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1398 Self {
1399 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1400 }
1401 }
1402
1403 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1405 Self {
1406 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1407 }
1408 }
1409
1410 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1412 match self.constraint {
1413 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1414 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1415 },
1416 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1417 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1418 },
1419 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1420 constraint: PrincipalOrResourceConstraint::IsIn(
1421 entity_type,
1422 EntityReference::EUID(euid),
1423 ),
1424 },
1425 PrincipalOrResourceConstraint::Is(_)
1427 | PrincipalOrResourceConstraint::Any
1428 | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1429 | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1430 | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1431 }
1432 }
1433}
1434
1435impl std::fmt::Display for PrincipalConstraint {
1436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1437 write!(
1438 f,
1439 "{}",
1440 self.constraint.display(PrincipalOrResource::Principal)
1441 )
1442 }
1443}
1444
1445#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1447pub struct ResourceConstraint {
1448 pub(crate) constraint: PrincipalOrResourceConstraint,
1449}
1450
1451impl ResourceConstraint {
1452 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1454 ResourceConstraint { constraint }
1455 }
1456
1457 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1459 &self.constraint
1460 }
1461
1462 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1464 self.constraint
1465 }
1466
1467 pub fn as_expr(&self) -> Expr {
1469 self.constraint.as_expr(PrincipalOrResource::Resource)
1470 }
1471
1472 pub fn any() -> Self {
1474 ResourceConstraint {
1475 constraint: PrincipalOrResourceConstraint::any(),
1476 }
1477 }
1478
1479 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1481 ResourceConstraint {
1482 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1483 }
1484 }
1485
1486 pub fn is_eq_slot() -> Self {
1488 Self {
1489 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1490 }
1491 }
1492
1493 pub fn is_in_slot() -> Self {
1495 Self {
1496 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1497 }
1498 }
1499
1500 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1502 ResourceConstraint {
1503 constraint: PrincipalOrResourceConstraint::is_in(euid),
1504 }
1505 }
1506
1507 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1509 Self {
1510 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1511 }
1512 }
1513
1514 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1516 Self {
1517 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1518 }
1519 }
1520
1521 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1523 Self {
1524 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1525 }
1526 }
1527
1528 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1530 match self.constraint {
1531 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1532 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1533 },
1534 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1535 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1536 },
1537 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1538 constraint: PrincipalOrResourceConstraint::IsIn(
1539 entity_type,
1540 EntityReference::EUID(euid),
1541 ),
1542 },
1543 PrincipalOrResourceConstraint::Is(_)
1545 | PrincipalOrResourceConstraint::Any
1546 | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1547 | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1548 | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1549 }
1550 }
1551}
1552
1553impl std::fmt::Display for ResourceConstraint {
1554 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1555 write!(
1556 f,
1557 "{}",
1558 self.as_inner().display(PrincipalOrResource::Resource)
1559 )
1560 }
1561}
1562
1563#[derive(Educe, Clone, Debug, Eq)]
1565#[educe(Hash, PartialEq, PartialOrd, Ord)]
1566pub enum EntityReference {
1567 EUID(Arc<EntityUID>),
1569 Slot(
1571 #[educe(PartialEq(ignore))]
1572 #[educe(PartialOrd(ignore))]
1573 #[educe(Hash(ignore))]
1574 Option<Loc>,
1575 ),
1576}
1577
1578impl EntityReference {
1579 pub fn euid(euid: Arc<EntityUID>) -> Self {
1581 Self::EUID(euid)
1582 }
1583
1584 pub fn into_expr(&self, slot: SlotId) -> Expr {
1590 match self {
1591 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1592 EntityReference::Slot(loc) => Expr::slot(slot).with_maybe_source_loc(loc.clone()),
1593 }
1594 }
1595}
1596
1597#[derive(Debug, Clone, PartialEq, Eq, Error)]
1599pub enum UnexpectedSlotError {
1600 #[error("found slot `{}` where slots are not allowed", .0.id)]
1602 FoundSlot(Slot),
1603}
1604
1605impl Diagnostic for UnexpectedSlotError {
1606 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1607 match self {
1608 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1609 let label = miette::LabeledSpan::underline(loc.span);
1610 Box::new(std::iter::once(label)) as _
1611 }),
1612 }
1613 }
1614
1615 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
1616 match self {
1617 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|l| l as &dyn miette::SourceCode),
1618 }
1619 }
1620}
1621
1622impl From<EntityUID> for EntityReference {
1623 fn from(euid: EntityUID) -> Self {
1624 Self::EUID(Arc::new(euid))
1625 }
1626}
1627
1628#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1630#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1631pub enum PrincipalOrResource {
1632 Principal,
1634 Resource,
1636}
1637
1638impl std::fmt::Display for PrincipalOrResource {
1639 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1640 let v = Var::from(*self);
1641 write!(f, "{v}")
1642 }
1643}
1644
1645impl TryFrom<Var> for PrincipalOrResource {
1646 type Error = Var;
1647
1648 fn try_from(value: Var) -> Result<Self, Self::Error> {
1649 match value {
1650 Var::Principal => Ok(Self::Principal),
1651 Var::Action => Err(Var::Action),
1652 Var::Resource => Ok(Self::Resource),
1653 Var::Context => Err(Var::Context),
1654 }
1655 }
1656}
1657
1658#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1661pub enum PrincipalOrResourceConstraint {
1662 Any,
1664 In(EntityReference),
1666 Eq(EntityReference),
1668 Is(Arc<EntityType>),
1670 IsIn(Arc<EntityType>, EntityReference),
1672}
1673
1674impl PrincipalOrResourceConstraint {
1675 pub fn any() -> Self {
1677 PrincipalOrResourceConstraint::Any
1678 }
1679
1680 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1682 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1683 }
1684
1685 pub fn is_eq_slot() -> Self {
1687 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None))
1688 }
1689
1690 pub fn is_in_slot() -> Self {
1692 PrincipalOrResourceConstraint::In(EntityReference::Slot(None))
1693 }
1694
1695 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1697 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1698 }
1699
1700 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1702 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None))
1703 }
1704
1705 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1707 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1708 }
1709
1710 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1712 PrincipalOrResourceConstraint::Is(entity_type)
1713 }
1714
1715 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1719 match self {
1720 PrincipalOrResourceConstraint::Any => Expr::val(true),
1721 PrincipalOrResourceConstraint::Eq(euid) => {
1722 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1723 }
1724 PrincipalOrResourceConstraint::In(euid) => {
1725 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1726 }
1727 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1728 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1729 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1730 ),
1731 PrincipalOrResourceConstraint::Is(entity_type) => {
1732 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1733 }
1734 }
1735 }
1736
1737 pub fn display(&self, v: PrincipalOrResource) -> String {
1741 match self {
1742 PrincipalOrResourceConstraint::In(euid) => {
1743 format!("{} in {}", v, euid.into_expr(v.into()))
1744 }
1745 PrincipalOrResourceConstraint::Eq(euid) => {
1746 format!("{} == {}", v, euid.into_expr(v.into()))
1747 }
1748 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1749 format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1750 }
1751 PrincipalOrResourceConstraint::Is(entity_type) => {
1752 format!("{v} is {entity_type}")
1753 }
1754 PrincipalOrResourceConstraint::Any => format!("{v}"),
1755 }
1756 }
1757
1758 pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1760 match self {
1761 PrincipalOrResourceConstraint::Any => None,
1762 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1763 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None,
1764 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1765 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None,
1766 PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1767 PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None,
1768 PrincipalOrResourceConstraint::Is(_) => None,
1769 }
1770 }
1771
1772 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1774 self.get_euid()
1775 .into_iter()
1776 .map(|euid| euid.entity_type())
1777 .chain(match self {
1778 PrincipalOrResourceConstraint::Is(entity_type)
1779 | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1780 _ => None,
1781 })
1782 }
1783}
1784
1785#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1788pub enum ActionConstraint {
1789 Any,
1791 In(Vec<Arc<EntityUID>>),
1793 Eq(Arc<EntityUID>),
1795 #[cfg(feature = "tolerant-ast")]
1796 ErrorConstraint,
1798}
1799
1800impl std::fmt::Display for ActionConstraint {
1801 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1802 let render_euids =
1803 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1804 match self {
1805 ActionConstraint::Any => write!(f, "action"),
1806 ActionConstraint::In(euids) => {
1807 write!(f, "action in [{}]", render_euids(euids))
1808 }
1809 ActionConstraint::Eq(euid) => write!(f, "action == {euid}"),
1810 #[cfg(feature = "tolerant-ast")]
1811 ActionConstraint::ErrorConstraint => write!(f, "<invalid_action_constraint>"),
1812 }
1813 }
1814}
1815
1816impl ActionConstraint {
1817 pub fn any() -> Self {
1819 ActionConstraint::Any
1820 }
1821
1822 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1824 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1825 }
1826
1827 pub fn is_eq(euid: EntityUID) -> Self {
1829 ActionConstraint::Eq(Arc::new(euid))
1830 }
1831
1832 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1833 Expr::set(euids.into_iter().map(Expr::val))
1834 }
1835
1836 pub fn as_expr(&self) -> Expr {
1838 match self {
1839 ActionConstraint::Any => Expr::val(true),
1840 ActionConstraint::In(euids) => Expr::is_in(
1841 Expr::var(Var::Action),
1842 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1843 ),
1844 ActionConstraint::Eq(euid) => {
1845 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1846 }
1847 #[cfg(feature = "tolerant-ast")]
1848 ActionConstraint::ErrorConstraint => Expr::new(
1849 ExprKind::Error {
1850 error_kind: AstExprErrorKind::InvalidExpr(
1851 "Invalid action constraint".to_string(),
1852 ),
1853 },
1854 None,
1855 (),
1856 ),
1857 }
1858 }
1859
1860 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1862 match self {
1863 ActionConstraint::Any => EntityIterator::None,
1864 ActionConstraint::In(euids) => {
1865 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1866 }
1867 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1868 #[cfg(feature = "tolerant-ast")]
1869 ActionConstraint::ErrorConstraint => EntityIterator::None,
1870 }
1871 }
1872
1873 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1875 self.iter_euids().map(|euid| euid.entity_type())
1876 }
1877
1878 pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1881 match self {
1882 ActionConstraint::Any => Ok(self),
1883 ActionConstraint::In(ref euids) => {
1884 if let Some(euids) =
1885 NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1886 {
1887 Err(euids)
1888 } else {
1889 Ok(self)
1890 }
1891 }
1892 ActionConstraint::Eq(ref euid) => {
1893 if euid.is_action() {
1894 Ok(self)
1895 } else {
1896 Err(nonempty![euid.clone()])
1897 }
1898 }
1899 #[cfg(feature = "tolerant-ast")]
1900 ActionConstraint::ErrorConstraint => Ok(self),
1901 }
1902 }
1903}
1904
1905impl std::fmt::Display for StaticPolicy {
1906 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1907 let policy_template = &self.0;
1908 match policy_template {
1909 TemplateBody::TemplateBody(template_body_impl) => {
1910 for (k, v) in template_body_impl.annotations.iter() {
1911 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1912 }
1913 write!(
1914 f,
1915 "{}(\n {},\n {},\n {}\n)",
1916 self.effect(),
1917 self.principal_constraint(),
1918 self.action_constraint(),
1919 self.resource_constraint(),
1920 )?;
1921 if let Some(non_scope_constraints) = self.non_scope_constraints() {
1922 write!(f, " when {{\n {non_scope_constraints}\n}};")
1923 } else {
1924 write!(f, ";")
1925 }
1926 }
1927 #[cfg(feature = "tolerant-ast")]
1928 TemplateBody::TemplateBodyError(policy_id, _) => {
1929 write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1930 }
1931 }
1932 }
1933}
1934
1935#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1937pub struct PolicyID(SmolStr);
1938
1939impl PolicyID {
1940 pub fn from_string(id: impl AsRef<str>) -> Self {
1942 Self(SmolStr::from(id.as_ref()))
1943 }
1944
1945 pub fn from_smolstr(id: SmolStr) -> Self {
1947 Self(id)
1948 }
1949}
1950
1951impl std::fmt::Display for PolicyID {
1952 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1953 write!(f, "{}", self.0.escape_debug())
1954 }
1955}
1956
1957impl AsRef<str> for PolicyID {
1958 fn as_ref(&self) -> &str {
1959 &self.0
1960 }
1961}
1962
1963#[cfg(feature = "arbitrary")]
1964impl arbitrary::Arbitrary<'_> for PolicyID {
1965 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1966 let s: String = u.arbitrary()?;
1967 Ok(PolicyID::from_string(s))
1968 }
1969 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1970 <String as arbitrary::Arbitrary>::size_hint(depth)
1971 }
1972}
1973
1974#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1976#[serde(rename_all = "camelCase")]
1977#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1978#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1979#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1980pub enum Effect {
1981 Permit,
1983 Forbid,
1985}
1986
1987impl std::fmt::Display for Effect {
1988 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1989 match self {
1990 Self::Permit => write!(f, "permit"),
1991 Self::Forbid => write!(f, "forbid"),
1992 }
1993 }
1994}
1995
1996enum EntityIterator<'a> {
1997 None,
1998 One(&'a EntityUID),
1999 Bunch(Vec<&'a EntityUID>),
2000}
2001
2002impl<'a> Iterator for EntityIterator<'a> {
2003 type Item = &'a EntityUID;
2004
2005 fn next(&mut self) -> Option<Self::Item> {
2006 match self {
2007 EntityIterator::None => None,
2008 EntityIterator::One(euid) => {
2009 let eptr = *euid;
2010 let mut ptr = EntityIterator::None;
2011 std::mem::swap(self, &mut ptr);
2012 Some(eptr)
2013 }
2014 EntityIterator::Bunch(v) => v.pop(),
2015 }
2016 }
2017}
2018
2019#[cfg(test)]
2020pub(crate) mod test_generators {
2021 use super::*;
2022
2023 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
2024 let euid = Arc::new(EntityUID::with_eid("test"));
2025 let v = vec![
2026 PrincipalOrResourceConstraint::any(),
2027 PrincipalOrResourceConstraint::is_eq(euid.clone()),
2028 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)),
2029 PrincipalOrResourceConstraint::is_in(euid),
2030 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)),
2031 ];
2032
2033 v.into_iter()
2034 }
2035
2036 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
2037 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
2038 }
2039
2040 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
2041 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
2042 }
2043
2044 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
2045 let euid: EntityUID = "Action::\"test\""
2046 .parse()
2047 .expect("Invalid action constraint euid");
2048 let v = vec![
2049 ActionConstraint::any(),
2050 ActionConstraint::is_eq(euid.clone()),
2051 ActionConstraint::is_in([euid.clone()]),
2052 ActionConstraint::is_in([euid.clone(), euid]),
2053 ];
2054
2055 v.into_iter()
2056 }
2057
2058 pub fn all_templates() -> impl Iterator<Item = Template> {
2059 let mut buf = vec![];
2060 let permit = PolicyID::from_string("permit");
2061 let forbid = PolicyID::from_string("forbid");
2062 for principal in all_principal_constraints() {
2063 for action in all_actions_constraints() {
2064 for resource in all_resource_constraints() {
2065 let permit = Template::new(
2066 permit.clone(),
2067 None,
2068 Annotations::new(),
2069 Effect::Permit,
2070 principal.clone(),
2071 action.clone(),
2072 resource.clone(),
2073 None,
2074 );
2075 let forbid = Template::new(
2076 forbid.clone(),
2077 None,
2078 Annotations::new(),
2079 Effect::Forbid,
2080 principal.clone(),
2081 action.clone(),
2082 resource.clone(),
2083 None,
2084 );
2085 buf.push(permit);
2086 buf.push(forbid);
2087 }
2088 }
2089 }
2090 buf.into_iter()
2091 }
2092}
2093
2094#[cfg(test)]
2095mod test {
2096 use cool_asserts::assert_matches;
2097 use std::collections::HashSet;
2098
2099 use super::{test_generators::*, *};
2100 use crate::{
2101 parser::{
2102 parse_policy,
2103 test_utils::{expect_exactly_one_error, expect_some_error_matches},
2104 },
2105 test_utils::ExpectedErrorMessageBuilder,
2106 };
2107
2108 #[test]
2109 fn link_templates() {
2110 for template in all_templates() {
2111 template.check_invariant();
2112 let t = Arc::new(template);
2113 let env = t
2114 .slots()
2115 .map(|slot| (slot.id, EntityUID::with_eid("eid")))
2116 .collect();
2117 let _ = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
2118 }
2119 }
2120
2121 #[test]
2122 fn test_template_rebuild() {
2123 for template in all_templates() {
2124 let id = template.id().clone();
2125 let effect = template.effect();
2126 let p = template.principal_constraint().clone();
2127 let a = template.action_constraint().clone();
2128 let r = template.resource_constraint().clone();
2129 let non_scope = template.non_scope_constraints().cloned();
2130 let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
2131 assert_eq!(template, t2);
2132 }
2133 }
2134
2135 #[test]
2136 fn test_static_policy_rebuild() {
2137 for template in all_templates() {
2138 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
2139 let id = ip.id().clone();
2140 let e = ip.effect();
2141 let anno = ip
2142 .annotations()
2143 .map(|(k, v)| (k.clone(), v.clone()))
2144 .collect();
2145 let p = ip.principal_constraint().clone();
2146 let a = ip.action_constraint().clone();
2147 let r = ip.resource_constraint().clone();
2148 let non_scope = ip.non_scope_constraints();
2149 let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope.cloned())
2150 .expect("Policy Creation Failed");
2151 assert_eq!(ip, ip2);
2152 let (t2, inst) = Template::link_static_policy(ip2);
2153 assert!(inst.is_static());
2154 assert_eq!(&template, t2.as_ref());
2155 }
2156 }
2157 }
2158
2159 #[test]
2160 fn ir_binding_too_many() {
2161 let tid = PolicyID::from_string("tid");
2162 let iid = PolicyID::from_string("iid");
2163 let t = Arc::new(Template::new(
2164 tid,
2165 None,
2166 Annotations::new(),
2167 Effect::Forbid,
2168 PrincipalConstraint::is_eq_slot(),
2169 ActionConstraint::Any,
2170 ResourceConstraint::any(),
2171 None,
2172 ));
2173 let mut m = HashMap::new();
2174 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
2175 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2176 assert_eq!(unbound_values, vec![SlotId::principal()]);
2177 assert_eq!(extra_values, vec![SlotId::resource()]);
2178 });
2179 }
2180
2181 #[test]
2182 fn ir_binding_too_few() {
2183 let tid = PolicyID::from_string("tid");
2184 let iid = PolicyID::from_string("iid");
2185 let t = Arc::new(Template::new(
2186 tid,
2187 None,
2188 Annotations::new(),
2189 Effect::Forbid,
2190 PrincipalConstraint::is_eq_slot(),
2191 ActionConstraint::Any,
2192 ResourceConstraint::is_in_slot(),
2193 None,
2194 ));
2195 assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2196 assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
2197 assert_eq!(extra_values, vec![]);
2198 });
2199 let mut m = HashMap::new();
2200 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
2201 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2202 assert_eq!(unbound_values, vec![SlotId::resource()]);
2203 assert_eq!(extra_values, vec![]);
2204 });
2205 }
2206
2207 #[test]
2208 fn ir_binding() {
2209 let tid = PolicyID::from_string("template");
2210 let iid = PolicyID::from_string("linked");
2211 let t = Arc::new(Template::new(
2212 tid,
2213 None,
2214 Annotations::new(),
2215 Effect::Permit,
2216 PrincipalConstraint::is_in_slot(),
2217 ActionConstraint::any(),
2218 ResourceConstraint::is_eq_slot(),
2219 None,
2220 ));
2221
2222 let mut m = HashMap::new();
2223 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2224 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2225
2226 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2227 assert_eq!(r.id(), &iid);
2228 assert_eq!(
2229 r.env().get(&SlotId::principal()),
2230 Some(&EntityUID::with_eid("theprincipal"))
2231 );
2232 assert_eq!(
2233 r.env().get(&SlotId::resource()),
2234 Some(&EntityUID::with_eid("theresource"))
2235 );
2236 }
2237
2238 #[test]
2239 fn isnt_template_implies_from_succeeds() {
2240 for template in all_templates() {
2241 if template.slots().count() == 0 {
2242 StaticPolicy::try_from(template).expect("Should succeed");
2243 }
2244 }
2245 }
2246
2247 #[test]
2248 fn is_template_implies_from_fails() {
2249 for template in all_templates() {
2250 if template.slots().count() != 0 {
2251 assert!(
2252 StaticPolicy::try_from(template.clone()).is_err(),
2253 "Following template did convert {template}"
2254 );
2255 }
2256 }
2257 }
2258
2259 #[test]
2260 fn non_template_iso() {
2261 for template in all_templates() {
2262 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2263 let (t2, _) = Template::link_static_policy(p);
2264 assert_eq!(&template, t2.as_ref());
2265 }
2266 }
2267 }
2268
2269 #[test]
2270 fn template_into_expr() {
2271 for template in all_templates() {
2272 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2273 let t: Template = template;
2274 assert_eq!(p.condition(), t.condition());
2275 assert_eq!(p.effect(), t.effect());
2276 }
2277 }
2278 }
2279
2280 #[test]
2281 fn template_por_iter() {
2282 let e = Arc::new(EntityUID::with_eid("eid"));
2283 assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2284 assert_eq!(
2285 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2286 Some(&e)
2287 );
2288 assert_eq!(
2289 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(),
2290 None
2291 );
2292 assert_eq!(
2293 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2294 Some(&e)
2295 );
2296 assert_eq!(
2297 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(),
2298 None
2299 );
2300 assert_eq!(
2301 PrincipalOrResourceConstraint::IsIn(
2302 Arc::new("T".parse().unwrap()),
2303 EntityReference::EUID(e.clone())
2304 )
2305 .get_euid(),
2306 Some(&e)
2307 );
2308 assert_eq!(
2309 PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2310 None
2311 );
2312 assert_eq!(
2313 PrincipalOrResourceConstraint::IsIn(
2314 Arc::new("T".parse().unwrap()),
2315 EntityReference::Slot(None)
2316 )
2317 .get_euid(),
2318 None
2319 );
2320 }
2321
2322 #[test]
2323 fn action_iter() {
2324 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2325 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2326 let v = a.iter_euids().collect::<Vec<_>>();
2327 assert_eq!(vec![&EntityUID::with_eid("test")], v);
2328 let a =
2329 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2330 let set = a.iter_euids().collect::<HashSet<_>>();
2331 let e1 = EntityUID::with_eid("test1");
2332 let e2 = EntityUID::with_eid("test2");
2333 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2334 assert_eq!(set, correct);
2335 }
2336
2337 #[test]
2338 fn test_iter_none() {
2339 let mut i = EntityIterator::None;
2340 assert_eq!(i.next(), None);
2341 }
2342
2343 #[test]
2344 fn test_iter_once() {
2345 let id = EntityUID::from_components(
2346 name::Name::parse_unqualified_name("s").unwrap().into(),
2347 entity::Eid::new("eid"),
2348 None,
2349 );
2350 let mut i = EntityIterator::One(&id);
2351 assert_eq!(i.next(), Some(&id));
2352 assert_eq!(i.next(), None);
2353 }
2354
2355 #[test]
2356 fn test_iter_mult() {
2357 let id1 = EntityUID::from_components(
2358 name::Name::parse_unqualified_name("s").unwrap().into(),
2359 entity::Eid::new("eid1"),
2360 None,
2361 );
2362 let id2 = EntityUID::from_components(
2363 name::Name::parse_unqualified_name("s").unwrap().into(),
2364 entity::Eid::new("eid2"),
2365 None,
2366 );
2367 let v = vec![&id1, &id2];
2368 let mut i = EntityIterator::Bunch(v);
2369 assert_eq!(i.next(), Some(&id2));
2370 assert_eq!(i.next(), Some(&id1));
2371 assert_eq!(i.next(), None)
2372 }
2373
2374 #[test]
2375 fn euid_into_expr() {
2376 let e = EntityReference::Slot(None);
2377 assert_eq!(
2378 e.into_expr(SlotId::principal()),
2379 Expr::slot(SlotId::principal())
2380 );
2381 let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2382 assert_eq!(
2383 e.into_expr(SlotId::principal()),
2384 Expr::val(EntityUID::with_eid("eid"))
2385 );
2386 }
2387
2388 #[test]
2389 fn por_constraint_display() {
2390 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None));
2391 let s = t.display(PrincipalOrResource::Principal);
2392 assert_eq!(s, "principal == ?principal");
2393 let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2394 EntityUID::with_eid("test"),
2395 )));
2396 let s = t.display(PrincipalOrResource::Principal);
2397 assert_eq!(s, "principal == test_entity_type::\"test\"");
2398 }
2399
2400 #[test]
2401 fn unexpected_templates() {
2402 let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2403 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2404 expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2405 "expected a static policy, got a template containing the slot ?principal"
2406 )
2407 .help("try removing the template slot(s) from this policy")
2408 .exactly_one_underline("?principal")
2409 .build()
2410 );
2411 });
2412
2413 let policy_str =
2414 r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2415 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2416 expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2417 "expected a static policy, got a template containing the slot ?principal"
2418 )
2419 .help("try removing the template slot(s) from this policy")
2420 .exactly_one_underline("?principal")
2421 .build()
2422 );
2423 assert_eq!(e.len(), 2);
2424 });
2425 }
2426
2427 #[test]
2428 fn policy_to_expr() {
2429 let policy_str = r#"permit(principal is A, action, resource is B)
2430 when { 1 == 2 }
2431 unless { 2 == 1}
2432 when { 3 == 4}
2433 unless { 4 == 3};"#;
2434 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Ok(p) => {
2435 assert_eq!(ToString::to_string(&p.condition()), "(principal is A) && (true && ((resource is B) && ((1 == 2) && ((!(2 == 1)) && ((3 == 4) && (!(4 == 3)))))))");
2436 });
2437 }
2438
2439 #[cfg(feature = "tolerant-ast")]
2440 #[test]
2441 fn template_body_error_methods() {
2442 use std::str::FromStr;
2443
2444 let policy_id = PolicyID::from_string("error_policy");
2445 let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2446 let error_body = TemplateBody::TemplateBodyError(policy_id.clone(), error_loc.clone());
2447
2448 let expected_error = <ExprWithErrsBuilder as ExprBuilder>::new()
2449 .error(ParseErrors::singleton(ToASTError::new(
2450 ToASTErrorKind::ASTErrorNode,
2451 Some(Loc::new(0..1, "ASTErrorNode".into())),
2452 )))
2453 .unwrap();
2454
2455 assert_eq!(error_body.id(), &policy_id);
2457
2458 assert_eq!(error_body.loc(), error_loc.as_ref());
2460
2461 let new_policy_id = PolicyID::from_string("new_error_policy");
2463 let updated_error_body = error_body.new_id(new_policy_id.clone());
2464 assert_matches!(updated_error_body,
2465 TemplateBody::TemplateBodyError(id, loc) if id == new_policy_id && loc == error_loc
2466 );
2467
2468 assert_eq!(error_body.effect(), Effect::Forbid);
2470
2471 assert_eq!(
2473 error_body.annotation(&AnyId::from_str("test").unwrap()),
2474 None
2475 );
2476
2477 assert!(error_body.annotations().count() == 0);
2479
2480 assert_eq!(
2482 *error_body.principal_constraint(),
2483 PrincipalConstraint::any()
2484 );
2485
2486 assert_eq!(*error_body.action_constraint(), ActionConstraint::any());
2488
2489 assert_eq!(*error_body.resource_constraint(), ResourceConstraint::any());
2491
2492 assert_eq!(error_body.non_scope_constraints(), Some(&expected_error));
2494
2495 assert_eq!(error_body.condition(), expected_error);
2497
2498 let display_str = format!("{error_body}");
2500 assert!(display_str.contains("TemplateBodyError"));
2501 assert!(display_str.contains("error_policy"));
2502 }
2503
2504 #[cfg(feature = "tolerant-ast")]
2505 #[test]
2506 fn template_error_methods() {
2507 let policy_id = PolicyID::from_string("error_policy");
2508 let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2509 let error_template = Template::error(policy_id.clone(), error_loc.clone());
2510
2511 assert_eq!(error_template.id(), &policy_id);
2513
2514 assert!(error_template.slots().count() == 0);
2516
2517 assert_matches!(error_template.body,
2519 TemplateBody::TemplateBodyError(ref id, ref loc) if id == &policy_id && loc == &error_loc
2520 );
2521
2522 assert_eq!(
2524 error_template.principal_constraint(),
2525 &PrincipalConstraint::any()
2526 );
2527
2528 assert_eq!(*error_template.action_constraint(), ActionConstraint::any());
2530
2531 assert_eq!(
2533 *error_template.resource_constraint(),
2534 ResourceConstraint::any()
2535 );
2536
2537 assert_eq!(error_template.effect(), Effect::Forbid);
2539
2540 assert_eq!(
2542 error_template.condition(),
2543 DEFAULT_ERROR_EXPR.as_ref().clone()
2544 );
2545
2546 assert_eq!(error_template.loc(), error_loc.as_ref());
2548
2549 assert!(error_template.annotations().count() == 0);
2551
2552 let display_str = format!("{error_template}");
2554 assert!(display_str.contains("TemplateBody::TemplateBodyError"));
2555 assert!(display_str.contains(&policy_id.to_string()));
2556 }
2557}