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 #[allow(clippy::unwrap_used)]
71 Arc::new(
72 <ExprWithErrsBuilder as ExprBuilder>::new()
73 .error(ParseErrors::singleton(ToASTError::new(
74 ToASTErrorKind::ASTErrorNode,
75 Some(Loc::new(0..1, "ASTErrorNode".into())),
76 )))
77 .unwrap(),
78 )
79 });
80}
81
82#[derive(Clone, Hash, Eq, PartialEq, Debug)]
87pub struct Template {
88 body: TemplateBody,
89 slots: Vec<Slot>,
94}
95
96impl From<Template> for TemplateBody {
97 fn from(val: Template) -> Self {
98 val.body
99 }
100}
101
102impl Template {
103 pub fn check_invariant(&self) {
107 #[cfg(debug_assertions)]
108 {
109 for slot in self.body.condition().slots() {
110 assert!(self.slots.contains(&slot));
111 }
112 for slot in self.slots() {
113 assert!(self.body.condition().slots().contains(slot));
114 }
115 }
116 }
117
118 #[allow(clippy::too_many_arguments)]
120 pub fn new(
121 id: PolicyID,
122 loc: Option<Loc>,
123 annotations: Annotations,
124 effect: Effect,
125 principal_constraint: PrincipalConstraint,
126 action_constraint: ActionConstraint,
127 resource_constraint: ResourceConstraint,
128 non_scope_constraint: Option<Expr>,
129 ) -> Self {
130 let body = TemplateBody::new(
131 id,
132 loc,
133 annotations,
134 effect,
135 principal_constraint,
136 action_constraint,
137 resource_constraint,
138 non_scope_constraint,
139 );
140 Template::from(body)
143 }
144
145 #[cfg(feature = "tolerant-ast")]
146 pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
148 let body = TemplateBody::error(id, loc);
149 Template::from(body)
150 }
151
152 #[allow(clippy::too_many_arguments)]
154 pub fn new_shared(
155 id: PolicyID,
156 loc: Option<Loc>,
157 annotations: Arc<Annotations>,
158 effect: Effect,
159 principal_constraint: PrincipalConstraint,
160 action_constraint: ActionConstraint,
161 resource_constraint: ResourceConstraint,
162 non_scope_constraint: Option<Arc<Expr>>,
163 ) -> Self {
164 let body = TemplateBody::new_shared(
165 id,
166 loc,
167 annotations,
168 effect,
169 principal_constraint,
170 action_constraint,
171 resource_constraint,
172 non_scope_constraint,
173 );
174 Template::from(body)
177 }
178
179 pub fn principal_constraint(&self) -> &PrincipalConstraint {
181 self.body.principal_constraint()
182 }
183
184 pub fn action_constraint(&self) -> &ActionConstraint {
186 self.body.action_constraint()
187 }
188
189 pub fn resource_constraint(&self) -> &ResourceConstraint {
191 self.body.resource_constraint()
192 }
193
194 pub fn non_scope_constraints(&self) -> Option<&Expr> {
196 self.body.non_scope_constraints()
197 }
198
199 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
201 self.body.non_scope_constraints_arc()
202 }
203
204 pub fn id(&self) -> &PolicyID {
206 self.body.id()
207 }
208
209 pub fn new_id(&self, id: PolicyID) -> Self {
211 Template {
212 body: self.body.new_id(id),
213 slots: self.slots.clone(),
214 }
215 }
216
217 pub fn loc(&self) -> Option<&Loc> {
219 self.body.loc()
220 }
221
222 pub fn effect(&self) -> Effect {
224 self.body.effect()
225 }
226
227 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
229 self.body.annotation(key)
230 }
231
232 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
234 self.body.annotations()
235 }
236
237 pub fn annotations_arc(&self) -> &Arc<Annotations> {
239 self.body.annotations_arc()
240 }
241
242 pub fn condition(&self) -> Expr {
248 self.body.condition()
249 }
250
251 pub fn slots(&self) -> impl Iterator<Item = &Slot> {
253 self.slots.iter()
254 }
255
256 pub fn is_static(&self) -> bool {
261 self.slots.is_empty()
262 }
263
264 pub fn check_binding(
268 template: &Template,
269 values: &HashMap<SlotId, EntityUID>,
270 ) -> Result<(), LinkingError> {
271 let unbound = template
273 .slots
274 .iter()
275 .filter(|slot| !values.contains_key(&slot.id))
276 .collect::<Vec<_>>();
277
278 let extra = values
279 .iter()
280 .filter_map(|(slot, _)| {
281 if !template
282 .slots
283 .iter()
284 .any(|template_slot| template_slot.id == *slot)
285 {
286 Some(slot)
287 } else {
288 None
289 }
290 })
291 .collect::<Vec<_>>();
292
293 if unbound.is_empty() && extra.is_empty() {
294 Ok(())
295 } else {
296 Err(LinkingError::from_unbound_and_extras(
297 unbound.into_iter().map(|slot| slot.id),
298 extra.into_iter().copied(),
299 ))
300 }
301 }
302
303 pub fn link(
307 template: Arc<Template>,
308 new_id: PolicyID,
309 values: HashMap<SlotId, EntityUID>,
310 ) -> Result<Policy, LinkingError> {
311 Template::check_binding(&template, &values)
313 .map(|_| Policy::new(template, Some(new_id), values))
314 }
315
316 pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
319 let body: TemplateBody = p.into();
320 let t = Arc::new(Self {
324 body,
325 slots: vec![],
326 });
327 t.check_invariant();
328 let p = Policy::new(Arc::clone(&t), None, HashMap::new());
329 (t, p)
330 }
331}
332
333impl From<TemplateBody> for Template {
334 fn from(body: TemplateBody) -> Self {
335 let slots = body.condition().slots().collect::<Vec<_>>();
338 Self { body, slots }
339 }
340}
341
342impl std::fmt::Display for Template {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 write!(f, "{}", self.body)
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
350pub enum LinkingError {
351 #[error(fmt = describe_arity_error)]
354 ArityError {
355 unbound_values: Vec<SlotId>,
357 extra_values: Vec<SlotId>,
359 },
360
361 #[error("failed to find a template with id `{id}`")]
363 NoSuchTemplate {
364 id: PolicyID,
366 },
367
368 #[error("template-linked policy id `{id}` conflicts with an existing policy id")]
370 PolicyIdConflict {
371 id: PolicyID,
373 },
374}
375
376impl LinkingError {
377 fn from_unbound_and_extras(
378 unbound: impl Iterator<Item = SlotId>,
379 extra: impl Iterator<Item = SlotId>,
380 ) -> Self {
381 Self::ArityError {
382 unbound_values: unbound.collect(),
383 extra_values: extra.collect(),
384 }
385 }
386}
387
388fn describe_arity_error(
389 unbound_values: &[SlotId],
390 extra_values: &[SlotId],
391 fmt: &mut std::fmt::Formatter<'_>,
392) -> std::fmt::Result {
393 match (unbound_values.len(), extra_values.len()) {
394 #[allow(clippy::unreachable)]
396 (0,0) => unreachable!(),
397 (_unbound, 0) => write!(fmt, "the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
398 (0, _extra) => write!(fmt, "the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
399 (_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(",")),
400 }
401}
402
403#[derive(Debug, Clone, Eq, PartialEq)]
411pub struct Policy {
412 template: Arc<Template>,
414 link: Option<PolicyID>,
418 values: HashMap<SlotId, EntityUID>,
425}
426
427impl Policy {
428 fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
432 #[cfg(debug_assertions)]
433 {
434 #[allow(clippy::expect_used)]
436 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
437 }
438 Self {
439 template,
440 link: link_id,
441 values,
442 }
443 }
444
445 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID, loc: Option<Loc>) -> Self {
447 Self::from_when_clause_annos(
448 effect,
449 Arc::new(when),
450 id,
451 loc,
452 Arc::new(Annotations::default()),
453 )
454 }
455
456 pub fn from_when_clause_annos(
458 effect: Effect,
459 when: Arc<Expr>,
460 id: PolicyID,
461 loc: Option<Loc>,
462 annotations: Arc<Annotations>,
463 ) -> Self {
464 let t = Template::new_shared(
465 id,
466 loc,
467 annotations,
468 effect,
469 PrincipalConstraint::any(),
470 ActionConstraint::any(),
471 ResourceConstraint::any(),
472 Some(when),
473 );
474 Self::new(Arc::new(t), None, SlotEnv::new())
475 }
476
477 pub fn template(&self) -> &Template {
479 &self.template
480 }
481
482 pub(crate) fn template_arc(&self) -> Arc<Template> {
484 Arc::clone(&self.template)
485 }
486
487 pub fn effect(&self) -> Effect {
489 self.template.effect()
490 }
491
492 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
494 self.template.annotation(key)
495 }
496
497 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
499 self.template.annotations()
500 }
501
502 pub fn annotations_arc(&self) -> &Arc<Annotations> {
504 self.template.annotations_arc()
505 }
506
507 pub fn principal_constraint(&self) -> PrincipalConstraint {
513 let constraint = self.template.principal_constraint().clone();
514 match self.values.get(&SlotId::principal()) {
515 None => constraint,
516 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
517 }
518 }
519
520 pub fn action_constraint(&self) -> &ActionConstraint {
522 self.template.action_constraint()
523 }
524
525 pub fn resource_constraint(&self) -> ResourceConstraint {
531 let constraint = self.template.resource_constraint().clone();
532 match self.values.get(&SlotId::resource()) {
533 None => constraint,
534 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
535 }
536 }
537
538 pub fn non_scope_constraints(&self) -> Option<&Expr> {
540 self.template.non_scope_constraints()
541 }
542
543 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
545 self.template.non_scope_constraints_arc()
546 }
547
548 pub fn condition(&self) -> Expr {
550 self.template.condition()
551 }
552
553 pub fn env(&self) -> &SlotEnv {
556 &self.values
557 }
558
559 pub fn id(&self) -> &PolicyID {
561 self.link.as_ref().unwrap_or_else(|| self.template.id())
562 }
563
564 pub fn new_id(&self, id: PolicyID) -> Self {
566 match self.link {
567 None => Policy {
568 template: Arc::new(self.template.new_id(id)),
569 link: None,
570 values: self.values.clone(),
571 },
572 Some(_) => Policy {
573 template: self.template.clone(),
574 link: Some(id),
575 values: self.values.clone(),
576 },
577 }
578 }
579
580 pub fn loc(&self) -> Option<&Loc> {
582 self.template.loc()
583 }
584
585 pub fn is_static(&self) -> bool {
587 self.link.is_none()
588 }
589
590 pub fn unknown_entities(&self) -> HashSet<EntityUID> {
592 self.condition()
593 .unknowns()
594 .filter_map(
595 |Unknown {
596 name,
597 type_annotation,
598 }| {
599 if matches!(type_annotation, Some(Type::Entity { .. })) {
600 EntityUID::from_str(name.as_str()).ok()
601 } else {
602 None
603 }
604 },
605 )
606 .collect()
607 }
608}
609
610impl std::fmt::Display for Policy {
611 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
612 if self.is_static() {
613 write!(f, "{}", self.template())
614 } else {
615 write!(
616 f,
617 "Template Instance of {}, slots: [{}]",
618 self.template().id(),
619 display_slot_env(self.env())
620 )
621 }
622 }
623}
624
625pub type SlotEnv = HashMap<SlotId, EntityUID>;
627
628#[derive(Debug, Clone, PartialEq, Eq)]
635pub struct LiteralPolicy {
636 template_id: PolicyID,
638 link_id: Option<PolicyID>,
642 values: SlotEnv,
644}
645
646impl LiteralPolicy {
647 pub fn static_policy(template_id: PolicyID) -> Self {
651 Self {
652 template_id,
653 link_id: None,
654 values: SlotEnv::new(),
655 }
656 }
657
658 pub fn template_linked_policy(
662 template_id: PolicyID,
663 link_id: PolicyID,
664 values: SlotEnv,
665 ) -> Self {
666 Self {
667 template_id,
668 link_id: Some(link_id),
669 values,
670 }
671 }
672
673 pub fn value(&self, slot: &SlotId) -> Option<&EntityUID> {
675 self.values.get(slot)
676 }
677}
678
679impl std::hash::Hash for LiteralPolicy {
682 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
683 self.template_id.hash(state);
684 let mut buf = self.values.iter().collect::<Vec<_>>();
686 buf.sort();
687 for (id, euid) in buf {
688 id.hash(state);
689 euid.hash(state);
690 }
691 }
692}
693
694#[cfg(test)]
696mod hashing_tests {
697 use std::{
698 collections::hash_map::DefaultHasher,
699 hash::{Hash, Hasher},
700 };
701
702 use super::*;
703
704 fn compute_hash(ir: &LiteralPolicy) -> u64 {
705 let mut s = DefaultHasher::new();
706 ir.hash(&mut s);
707 s.finish()
708 }
709
710 fn build_template_linked_policy() -> LiteralPolicy {
711 let mut map = HashMap::new();
712 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
713 LiteralPolicy {
714 template_id: PolicyID::from_string("template"),
715 link_id: Some(PolicyID::from_string("id")),
716 values: map,
717 }
718 }
719
720 #[test]
721 fn hash_property_instances() {
722 let a = build_template_linked_policy();
723 let b = build_template_linked_policy();
724 assert_eq!(a, b);
725 assert_eq!(compute_hash(&a), compute_hash(&b));
726 }
727}
728
729#[derive(Debug, Diagnostic, Error)]
731pub enum ReificationError {
732 #[error("the id linked to does not exist")]
734 NoSuchTemplate(PolicyID),
735 #[error(transparent)]
737 #[diagnostic(transparent)]
738 Linking(#[from] LinkingError),
739}
740
741impl LiteralPolicy {
742 pub fn reify(
747 self,
748 templates: &LinkedHashMap<PolicyID, Arc<Template>>,
749 ) -> Result<Policy, ReificationError> {
750 let template = templates
751 .get(&self.template_id)
752 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
753 Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
755 Ok(Policy::new(template.clone(), self.link_id, self.values))
756 }
757
758 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
760 self.values.get(id)
761 }
762
763 pub fn id(&self) -> &PolicyID {
765 self.link_id.as_ref().unwrap_or(&self.template_id)
766 }
767
768 pub fn template_id(&self) -> &PolicyID {
772 &self.template_id
773 }
774
775 pub fn is_static(&self) -> bool {
777 self.link_id.is_none()
778 }
779}
780
781fn display_slot_env(env: &SlotEnv) -> String {
782 env.iter()
783 .map(|(slot, value)| format!("{slot} -> {value}"))
784 .join(",")
785}
786
787impl std::fmt::Display for LiteralPolicy {
788 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
789 if self.is_static() {
790 write!(f, "Static policy w/ ID {}", self.template_id())
791 } else {
792 write!(
793 f,
794 "Template linked policy of {}, slots: [{}]",
795 self.template_id(),
796 display_slot_env(&self.values),
797 )
798 }
799 }
800}
801
802impl From<Policy> for LiteralPolicy {
803 fn from(p: Policy) -> Self {
804 Self {
805 template_id: p.template.id().clone(),
806 link_id: p.link,
807 values: p.values,
808 }
809 }
810}
811
812#[derive(Clone, Hash, Eq, PartialEq, Debug)]
816pub struct StaticPolicy(TemplateBody);
817
818impl StaticPolicy {
819 pub fn id(&self) -> &PolicyID {
821 self.0.id()
822 }
823
824 pub fn new_id(&self, id: PolicyID) -> Self {
826 StaticPolicy(self.0.new_id(id))
827 }
828
829 pub fn loc(&self) -> Option<&Loc> {
831 self.0.loc()
832 }
833
834 pub fn effect(&self) -> Effect {
836 self.0.effect()
837 }
838
839 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
841 self.0.annotation(key)
842 }
843
844 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
846 self.0.annotations()
847 }
848
849 pub fn principal_constraint(&self) -> &PrincipalConstraint {
851 self.0.principal_constraint()
852 }
853
854 pub fn principal_constraint_expr(&self) -> Expr {
858 self.0.principal_constraint_expr()
859 }
860
861 pub fn action_constraint(&self) -> &ActionConstraint {
863 self.0.action_constraint()
864 }
865
866 pub fn action_constraint_expr(&self) -> Expr {
870 self.0.action_constraint_expr()
871 }
872
873 pub fn resource_constraint(&self) -> &ResourceConstraint {
875 self.0.resource_constraint()
876 }
877
878 pub fn resource_constraint_expr(&self) -> Expr {
882 self.0.resource_constraint_expr()
883 }
884
885 pub fn non_scope_constraints(&self) -> Option<&Expr> {
890 self.0.non_scope_constraints()
891 }
892
893 pub fn condition(&self) -> Expr {
899 self.0.condition()
900 }
901
902 #[allow(clippy::too_many_arguments)]
904 pub fn new(
905 id: PolicyID,
906 loc: Option<Loc>,
907 annotations: Annotations,
908 effect: Effect,
909 principal_constraint: PrincipalConstraint,
910 action_constraint: ActionConstraint,
911 resource_constraint: ResourceConstraint,
912 non_scope_constraints: Option<Expr>,
913 ) -> Result<Self, UnexpectedSlotError> {
914 let body = TemplateBody::new(
915 id,
916 loc,
917 annotations,
918 effect,
919 principal_constraint,
920 action_constraint,
921 resource_constraint,
922 non_scope_constraints,
923 );
924 let first_slot = body.condition().slots().next();
925 match first_slot {
927 Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
928 None => Ok(Self(body)),
929 }
930 }
931}
932
933impl TryFrom<Template> for StaticPolicy {
934 type Error = UnexpectedSlotError;
935
936 fn try_from(value: Template) -> Result<Self, Self::Error> {
937 let o = value.slots().next();
939 match o {
940 Some(slot_id) => Err(Self::Error::FoundSlot(slot_id.clone())),
941 None => Ok(Self(value.body)),
942 }
943 }
944}
945
946impl From<StaticPolicy> for Policy {
947 fn from(p: StaticPolicy) -> Policy {
948 let (_, policy) = Template::link_static_policy(p);
949 policy
950 }
951}
952
953impl From<StaticPolicy> for Arc<Template> {
954 fn from(p: StaticPolicy) -> Self {
955 let (t, _) = Template::link_static_policy(p);
956 t
957 }
958}
959
960#[derive(Educe, Clone, Debug)]
963#[educe(PartialEq, Eq, Hash)]
964pub struct TemplateBodyImpl {
965 id: PolicyID,
967 #[educe(PartialEq(ignore))]
969 #[educe(Hash(ignore))]
970 loc: Option<Loc>,
971 annotations: Arc<Annotations>,
975 effect: Effect,
977 principal_constraint: PrincipalConstraint,
981 action_constraint: ActionConstraint,
985 resource_constraint: ResourceConstraint,
989 non_scope_constraints: Option<Arc<Expr>>,
994}
995
996#[derive(Clone, Hash, Eq, PartialEq, Debug)]
999pub enum TemplateBody {
1000 TemplateBody(TemplateBodyImpl),
1002 #[cfg(feature = "tolerant-ast")]
1003 TemplateBodyError(PolicyID, Option<Loc>),
1005}
1006
1007impl TemplateBody {
1008 pub fn id(&self) -> &PolicyID {
1010 match self {
1011 TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => id,
1012 #[cfg(feature = "tolerant-ast")]
1013 TemplateBody::TemplateBodyError(id, _) => id,
1014 }
1015 }
1016
1017 pub fn loc(&self) -> Option<&Loc> {
1019 match self {
1020 TemplateBody::TemplateBody(TemplateBodyImpl { loc, .. }) => loc.as_ref(),
1021 #[cfg(feature = "tolerant-ast")]
1022 TemplateBody::TemplateBodyError(_, loc) => loc.as_ref(),
1023 }
1024 }
1025
1026 pub fn new_id(&self, id: PolicyID) -> Self {
1028 match self {
1029 TemplateBody::TemplateBody(t) => {
1030 let mut new = t.clone();
1031 new.id = id;
1032 TemplateBody::TemplateBody(new)
1033 }
1034 #[cfg(feature = "tolerant-ast")]
1035 TemplateBody::TemplateBodyError(_, loc) => {
1036 TemplateBody::TemplateBodyError(id, loc.clone())
1037 }
1038 }
1039 }
1040
1041 #[cfg(feature = "tolerant-ast")]
1042 pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
1044 TemplateBody::TemplateBodyError(id, loc)
1045 }
1046
1047 pub fn effect(&self) -> Effect {
1049 match self {
1050 TemplateBody::TemplateBody(TemplateBodyImpl { effect, .. }) => *effect,
1051 #[cfg(feature = "tolerant-ast")]
1052 TemplateBody::TemplateBodyError(_, _) => Effect::Forbid,
1053 }
1054 }
1055
1056 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
1058 match self {
1059 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => {
1060 annotations.get(key)
1061 }
1062 #[cfg(feature = "tolerant-ast")]
1063 TemplateBody::TemplateBodyError(_, _) => None,
1064 }
1065 }
1066
1067 pub fn annotations_arc(&self) -> &Arc<Annotations> {
1069 match self {
1070 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations,
1071 #[cfg(feature = "tolerant-ast")]
1072 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ANNOTATIONS,
1073 }
1074 }
1075
1076 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1078 match self {
1079 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations.iter(),
1080 #[cfg(feature = "tolerant-ast")]
1081 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ANNOTATIONS.iter(),
1082 }
1083 }
1084
1085 pub fn principal_constraint(&self) -> &PrincipalConstraint {
1087 match self {
1088 TemplateBody::TemplateBody(TemplateBodyImpl {
1089 principal_constraint,
1090 ..
1091 }) => principal_constraint,
1092 #[cfg(feature = "tolerant-ast")]
1093 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_PRINCIPAL_CONSTRAINT,
1094 }
1095
1096 }
1098
1099 pub fn principal_constraint_expr(&self) -> Expr {
1103 match self {
1104 TemplateBody::TemplateBody(TemplateBodyImpl {
1105 principal_constraint,
1106 ..
1107 }) => principal_constraint.as_expr(),
1108 #[cfg(feature = "tolerant-ast")]
1109 TemplateBody::TemplateBodyError(_, _) => DEFAULT_PRINCIPAL_CONSTRAINT.as_expr(),
1110 }
1111 }
1112
1113 pub fn action_constraint(&self) -> &ActionConstraint {
1115 match self {
1116 TemplateBody::TemplateBody(TemplateBodyImpl {
1117 action_constraint, ..
1118 }) => action_constraint,
1119 #[cfg(feature = "tolerant-ast")]
1120 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ACTION_CONSTRAINT,
1121 }
1122 }
1123
1124 pub fn action_constraint_expr(&self) -> Expr {
1128 match self {
1129 TemplateBody::TemplateBody(TemplateBodyImpl {
1130 action_constraint, ..
1131 }) => action_constraint.as_expr(),
1132 #[cfg(feature = "tolerant-ast")]
1133 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ACTION_CONSTRAINT.as_expr(),
1134 }
1135 }
1136
1137 pub fn resource_constraint(&self) -> &ResourceConstraint {
1139 match self {
1140 TemplateBody::TemplateBody(TemplateBodyImpl {
1141 resource_constraint,
1142 ..
1143 }) => resource_constraint,
1144 #[cfg(feature = "tolerant-ast")]
1145 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_RESOURCE_CONSTRAINT,
1146 }
1147 }
1148
1149 pub fn resource_constraint_expr(&self) -> Expr {
1153 match self {
1154 TemplateBody::TemplateBody(TemplateBodyImpl {
1155 resource_constraint,
1156 ..
1157 }) => resource_constraint.as_expr(),
1158 #[cfg(feature = "tolerant-ast")]
1159 TemplateBody::TemplateBodyError(_, _) => DEFAULT_RESOURCE_CONSTRAINT.as_expr(),
1160 }
1161 }
1162
1163 pub fn non_scope_constraints(&self) -> Option<&Expr> {
1168 match self {
1169 TemplateBody::TemplateBody(TemplateBodyImpl {
1170 non_scope_constraints,
1171 ..
1172 }) => non_scope_constraints.as_ref().map(|e| e.as_ref()),
1173 #[cfg(feature = "tolerant-ast")]
1174 TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1175 }
1176 }
1177
1178 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
1180 match self {
1181 TemplateBody::TemplateBody(TemplateBodyImpl {
1182 non_scope_constraints,
1183 ..
1184 }) => non_scope_constraints.as_ref(),
1185 #[cfg(feature = "tolerant-ast")]
1186 TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1187 }
1188 }
1189
1190 pub fn condition(&self) -> Expr {
1196 match self {
1197 TemplateBody::TemplateBody(TemplateBodyImpl { .. }) => {
1198 let loc = self.loc().cloned();
1199 Expr::and(
1200 self.principal_constraint_expr(),
1201 Expr::and(
1202 self.action_constraint_expr(),
1203 Expr::and(
1204 self.resource_constraint_expr(),
1205 self.non_scope_constraints()
1206 .cloned()
1207 .unwrap_or_else(|| Expr::val(true)),
1208 )
1209 .with_maybe_source_loc(loc.clone()),
1210 )
1211 .with_maybe_source_loc(loc.clone()),
1212 )
1213 .with_maybe_source_loc(loc)
1214 }
1215 #[cfg(feature = "tolerant-ast")]
1216 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ERROR_EXPR.as_ref().clone(),
1217 }
1218 }
1219
1220 #[allow(clippy::too_many_arguments)]
1222 pub fn new_shared(
1223 id: PolicyID,
1224 loc: Option<Loc>,
1225 annotations: Arc<Annotations>,
1226 effect: Effect,
1227 principal_constraint: PrincipalConstraint,
1228 action_constraint: ActionConstraint,
1229 resource_constraint: ResourceConstraint,
1230 non_scope_constraints: Option<Arc<Expr>>,
1231 ) -> Self {
1232 Self::TemplateBody(TemplateBodyImpl {
1233 id,
1234 loc,
1235 annotations,
1236 effect,
1237 principal_constraint,
1238 action_constraint,
1239 resource_constraint,
1240 non_scope_constraints,
1241 })
1242 }
1243
1244 #[allow(clippy::too_many_arguments)]
1246 pub fn new(
1247 id: PolicyID,
1248 loc: Option<Loc>,
1249 annotations: Annotations,
1250 effect: Effect,
1251 principal_constraint: PrincipalConstraint,
1252 action_constraint: ActionConstraint,
1253 resource_constraint: ResourceConstraint,
1254 non_scope_constraints: Option<Expr>,
1255 ) -> Self {
1256 Self::TemplateBody(TemplateBodyImpl {
1257 id,
1258 loc,
1259 annotations: Arc::new(annotations),
1260 effect,
1261 principal_constraint,
1262 action_constraint,
1263 resource_constraint,
1264 non_scope_constraints: non_scope_constraints.map(Arc::new),
1265 })
1266 }
1267}
1268
1269impl From<StaticPolicy> for TemplateBody {
1270 fn from(p: StaticPolicy) -> Self {
1271 p.0
1272 }
1273}
1274
1275impl std::fmt::Display for TemplateBody {
1276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1277 match self {
1278 TemplateBody::TemplateBody(template_body_impl) => {
1279 template_body_impl.annotations.fmt(f)?;
1280 write!(
1281 f,
1282 "{}(\n {},\n {},\n {}\n)",
1283 self.effect(),
1284 self.principal_constraint(),
1285 self.action_constraint(),
1286 self.resource_constraint(),
1287 )?;
1288 if let Some(non_scope_constraints) = self.non_scope_constraints() {
1289 write!(f, " when {{\n {non_scope_constraints}\n}};")
1290 } else {
1291 write!(f, ";")
1292 }
1293 }
1294 #[cfg(feature = "tolerant-ast")]
1295 TemplateBody::TemplateBodyError(policy_id, _) => {
1296 write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1297 }
1298 }
1299 }
1300}
1301
1302#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1304pub struct PrincipalConstraint {
1305 pub(crate) constraint: PrincipalOrResourceConstraint,
1306}
1307
1308impl PrincipalConstraint {
1309 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1311 PrincipalConstraint { constraint }
1312 }
1313
1314 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1316 &self.constraint
1317 }
1318
1319 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1321 self.constraint
1322 }
1323
1324 pub fn as_expr(&self) -> Expr {
1326 self.constraint.as_expr(PrincipalOrResource::Principal)
1327 }
1328
1329 pub fn any() -> Self {
1331 PrincipalConstraint {
1332 constraint: PrincipalOrResourceConstraint::any(),
1333 }
1334 }
1335
1336 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1338 PrincipalConstraint {
1339 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1340 }
1341 }
1342
1343 pub fn is_eq_slot() -> Self {
1345 Self {
1346 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1347 }
1348 }
1349
1350 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1352 PrincipalConstraint {
1353 constraint: PrincipalOrResourceConstraint::is_in(euid),
1354 }
1355 }
1356
1357 pub fn is_in_slot() -> Self {
1359 Self {
1360 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1361 }
1362 }
1363
1364 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1366 Self {
1367 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1368 }
1369 }
1370
1371 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1373 Self {
1374 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1375 }
1376 }
1377
1378 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1380 Self {
1381 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1382 }
1383 }
1384
1385 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1387 match self.constraint {
1388 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1389 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1390 },
1391 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1392 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1393 },
1394 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1395 constraint: PrincipalOrResourceConstraint::IsIn(
1396 entity_type,
1397 EntityReference::EUID(euid),
1398 ),
1399 },
1400 PrincipalOrResourceConstraint::Is(_)
1402 | PrincipalOrResourceConstraint::Any
1403 | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1404 | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1405 | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1406 }
1407 }
1408}
1409
1410impl std::fmt::Display for PrincipalConstraint {
1411 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1412 write!(
1413 f,
1414 "{}",
1415 self.constraint.display(PrincipalOrResource::Principal)
1416 )
1417 }
1418}
1419
1420#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1422pub struct ResourceConstraint {
1423 pub(crate) constraint: PrincipalOrResourceConstraint,
1424}
1425
1426impl ResourceConstraint {
1427 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1429 ResourceConstraint { constraint }
1430 }
1431
1432 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1434 &self.constraint
1435 }
1436
1437 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1439 self.constraint
1440 }
1441
1442 pub fn as_expr(&self) -> Expr {
1444 self.constraint.as_expr(PrincipalOrResource::Resource)
1445 }
1446
1447 pub fn any() -> Self {
1449 ResourceConstraint {
1450 constraint: PrincipalOrResourceConstraint::any(),
1451 }
1452 }
1453
1454 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1456 ResourceConstraint {
1457 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1458 }
1459 }
1460
1461 pub fn is_eq_slot() -> Self {
1463 Self {
1464 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1465 }
1466 }
1467
1468 pub fn is_in_slot() -> Self {
1470 Self {
1471 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1472 }
1473 }
1474
1475 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1477 ResourceConstraint {
1478 constraint: PrincipalOrResourceConstraint::is_in(euid),
1479 }
1480 }
1481
1482 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1484 Self {
1485 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1486 }
1487 }
1488
1489 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1491 Self {
1492 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1493 }
1494 }
1495
1496 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1498 Self {
1499 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1500 }
1501 }
1502
1503 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1505 match self.constraint {
1506 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1507 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1508 },
1509 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1510 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1511 },
1512 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1513 constraint: PrincipalOrResourceConstraint::IsIn(
1514 entity_type,
1515 EntityReference::EUID(euid),
1516 ),
1517 },
1518 PrincipalOrResourceConstraint::Is(_)
1520 | PrincipalOrResourceConstraint::Any
1521 | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1522 | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1523 | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1524 }
1525 }
1526}
1527
1528impl std::fmt::Display for ResourceConstraint {
1529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1530 write!(
1531 f,
1532 "{}",
1533 self.as_inner().display(PrincipalOrResource::Resource)
1534 )
1535 }
1536}
1537
1538#[derive(Educe, Clone, Debug, Eq)]
1540#[educe(Hash, PartialEq, PartialOrd, Ord)]
1541pub enum EntityReference {
1542 EUID(Arc<EntityUID>),
1544 Slot(
1546 #[educe(PartialEq(ignore))]
1547 #[educe(PartialOrd(ignore))]
1548 #[educe(Hash(ignore))]
1549 Option<Loc>,
1550 ),
1551}
1552
1553impl EntityReference {
1554 pub fn euid(euid: Arc<EntityUID>) -> Self {
1556 Self::EUID(euid)
1557 }
1558
1559 pub fn into_expr(&self, slot: SlotId) -> Expr {
1565 match self {
1566 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1567 EntityReference::Slot(loc) => Expr::slot(slot).with_maybe_source_loc(loc.clone()),
1568 }
1569 }
1570}
1571
1572#[derive(Debug, Clone, PartialEq, Eq, Error)]
1574pub enum UnexpectedSlotError {
1575 #[error("found slot `{}` where slots are not allowed", .0.id)]
1577 FoundSlot(Slot),
1578}
1579
1580impl Diagnostic for UnexpectedSlotError {
1581 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1582 match self {
1583 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1584 let label = miette::LabeledSpan::underline(loc.span);
1585 Box::new(std::iter::once(label)) as _
1586 }),
1587 }
1588 }
1589
1590 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
1591 match self {
1592 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|l| l as &dyn miette::SourceCode),
1593 }
1594 }
1595}
1596
1597impl From<EntityUID> for EntityReference {
1598 fn from(euid: EntityUID) -> Self {
1599 Self::EUID(Arc::new(euid))
1600 }
1601}
1602
1603#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1605#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1606pub enum PrincipalOrResource {
1607 Principal,
1609 Resource,
1611}
1612
1613impl std::fmt::Display for PrincipalOrResource {
1614 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1615 let v = Var::from(*self);
1616 write!(f, "{v}")
1617 }
1618}
1619
1620impl TryFrom<Var> for PrincipalOrResource {
1621 type Error = Var;
1622
1623 fn try_from(value: Var) -> Result<Self, Self::Error> {
1624 match value {
1625 Var::Principal => Ok(Self::Principal),
1626 Var::Action => Err(Var::Action),
1627 Var::Resource => Ok(Self::Resource),
1628 Var::Context => Err(Var::Context),
1629 }
1630 }
1631}
1632
1633#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1636pub enum PrincipalOrResourceConstraint {
1637 Any,
1639 In(EntityReference),
1641 Eq(EntityReference),
1643 Is(Arc<EntityType>),
1645 IsIn(Arc<EntityType>, EntityReference),
1647}
1648
1649impl PrincipalOrResourceConstraint {
1650 pub fn any() -> Self {
1652 PrincipalOrResourceConstraint::Any
1653 }
1654
1655 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1657 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1658 }
1659
1660 pub fn is_eq_slot() -> Self {
1662 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None))
1663 }
1664
1665 pub fn is_in_slot() -> Self {
1667 PrincipalOrResourceConstraint::In(EntityReference::Slot(None))
1668 }
1669
1670 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1672 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1673 }
1674
1675 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1677 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None))
1678 }
1679
1680 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1682 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1683 }
1684
1685 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1687 PrincipalOrResourceConstraint::Is(entity_type)
1688 }
1689
1690 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1694 match self {
1695 PrincipalOrResourceConstraint::Any => Expr::val(true),
1696 PrincipalOrResourceConstraint::Eq(euid) => {
1697 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1698 }
1699 PrincipalOrResourceConstraint::In(euid) => {
1700 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1701 }
1702 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1703 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1704 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1705 ),
1706 PrincipalOrResourceConstraint::Is(entity_type) => {
1707 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1708 }
1709 }
1710 }
1711
1712 pub fn display(&self, v: PrincipalOrResource) -> String {
1716 match self {
1717 PrincipalOrResourceConstraint::In(euid) => {
1718 format!("{} in {}", v, euid.into_expr(v.into()))
1719 }
1720 PrincipalOrResourceConstraint::Eq(euid) => {
1721 format!("{} == {}", v, euid.into_expr(v.into()))
1722 }
1723 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1724 format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1725 }
1726 PrincipalOrResourceConstraint::Is(entity_type) => {
1727 format!("{v} is {entity_type}")
1728 }
1729 PrincipalOrResourceConstraint::Any => format!("{v}"),
1730 }
1731 }
1732
1733 pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1735 match self {
1736 PrincipalOrResourceConstraint::Any => None,
1737 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1738 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None,
1739 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1740 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None,
1741 PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1742 PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None,
1743 PrincipalOrResourceConstraint::Is(_) => None,
1744 }
1745 }
1746
1747 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1749 self.get_euid()
1750 .into_iter()
1751 .map(|euid| euid.entity_type())
1752 .chain(match self {
1753 PrincipalOrResourceConstraint::Is(entity_type)
1754 | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1755 _ => None,
1756 })
1757 }
1758}
1759
1760#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1763pub enum ActionConstraint {
1764 Any,
1766 In(Vec<Arc<EntityUID>>),
1768 Eq(Arc<EntityUID>),
1770 #[cfg(feature = "tolerant-ast")]
1771 ErrorConstraint,
1773}
1774
1775impl std::fmt::Display for ActionConstraint {
1776 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1777 let render_euids =
1778 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1779 match self {
1780 ActionConstraint::Any => write!(f, "action"),
1781 ActionConstraint::In(euids) => {
1782 write!(f, "action in [{}]", render_euids(euids))
1783 }
1784 ActionConstraint::Eq(euid) => write!(f, "action == {euid}"),
1785 #[cfg(feature = "tolerant-ast")]
1786 ActionConstraint::ErrorConstraint => write!(f, "<invalid_action_constraint>"),
1787 }
1788 }
1789}
1790
1791impl ActionConstraint {
1792 pub fn any() -> Self {
1794 ActionConstraint::Any
1795 }
1796
1797 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1799 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1800 }
1801
1802 pub fn is_eq(euid: EntityUID) -> Self {
1804 ActionConstraint::Eq(Arc::new(euid))
1805 }
1806
1807 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1808 Expr::set(euids.into_iter().map(Expr::val))
1809 }
1810
1811 pub fn as_expr(&self) -> Expr {
1813 match self {
1814 ActionConstraint::Any => Expr::val(true),
1815 ActionConstraint::In(euids) => Expr::is_in(
1816 Expr::var(Var::Action),
1817 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1818 ),
1819 ActionConstraint::Eq(euid) => {
1820 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1821 }
1822 #[cfg(feature = "tolerant-ast")]
1823 ActionConstraint::ErrorConstraint => Expr::new(
1824 ExprKind::Error {
1825 error_kind: AstExprErrorKind::InvalidExpr(
1826 "Invalid action constraint".to_string(),
1827 ),
1828 },
1829 None,
1830 (),
1831 ),
1832 }
1833 }
1834
1835 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1837 match self {
1838 ActionConstraint::Any => EntityIterator::None,
1839 ActionConstraint::In(euids) => {
1840 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1841 }
1842 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1843 #[cfg(feature = "tolerant-ast")]
1844 ActionConstraint::ErrorConstraint => EntityIterator::None,
1845 }
1846 }
1847
1848 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1850 self.iter_euids().map(|euid| euid.entity_type())
1851 }
1852
1853 pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1856 match self {
1857 ActionConstraint::Any => Ok(self),
1858 ActionConstraint::In(ref euids) => {
1859 if let Some(euids) =
1860 NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1861 {
1862 Err(euids)
1863 } else {
1864 Ok(self)
1865 }
1866 }
1867 ActionConstraint::Eq(ref euid) => {
1868 if euid.is_action() {
1869 Ok(self)
1870 } else {
1871 Err(nonempty![euid.clone()])
1872 }
1873 }
1874 #[cfg(feature = "tolerant-ast")]
1875 ActionConstraint::ErrorConstraint => Ok(self),
1876 }
1877 }
1878}
1879
1880impl std::fmt::Display for StaticPolicy {
1881 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1882 let policy_template = &self.0;
1883 match policy_template {
1884 TemplateBody::TemplateBody(template_body_impl) => {
1885 for (k, v) in template_body_impl.annotations.iter() {
1886 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1887 }
1888 write!(
1889 f,
1890 "{}(\n {},\n {},\n {}\n)",
1891 self.effect(),
1892 self.principal_constraint(),
1893 self.action_constraint(),
1894 self.resource_constraint(),
1895 )?;
1896 if let Some(non_scope_constraints) = self.non_scope_constraints() {
1897 write!(f, " when {{\n {non_scope_constraints}\n}};")
1898 } else {
1899 write!(f, ";")
1900 }
1901 }
1902 #[cfg(feature = "tolerant-ast")]
1903 TemplateBody::TemplateBodyError(policy_id, _) => {
1904 write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1905 }
1906 }
1907 }
1908}
1909
1910#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1912pub struct PolicyID(SmolStr);
1913
1914impl PolicyID {
1915 pub fn from_string(id: impl AsRef<str>) -> Self {
1917 Self(SmolStr::from(id.as_ref()))
1918 }
1919
1920 pub fn from_smolstr(id: SmolStr) -> Self {
1922 Self(id)
1923 }
1924}
1925
1926impl std::fmt::Display for PolicyID {
1927 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1928 write!(f, "{}", self.0.escape_debug())
1929 }
1930}
1931
1932impl AsRef<str> for PolicyID {
1933 fn as_ref(&self) -> &str {
1934 &self.0
1935 }
1936}
1937
1938#[cfg(feature = "arbitrary")]
1939impl arbitrary::Arbitrary<'_> for PolicyID {
1940 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1941 let s: String = u.arbitrary()?;
1942 Ok(PolicyID::from_string(s))
1943 }
1944 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1945 <String as arbitrary::Arbitrary>::size_hint(depth)
1946 }
1947}
1948
1949#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1951#[serde(rename_all = "camelCase")]
1952#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1953#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1954#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1955pub enum Effect {
1956 Permit,
1958 Forbid,
1960}
1961
1962impl std::fmt::Display for Effect {
1963 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1964 match self {
1965 Self::Permit => write!(f, "permit"),
1966 Self::Forbid => write!(f, "forbid"),
1967 }
1968 }
1969}
1970
1971enum EntityIterator<'a> {
1972 None,
1973 One(&'a EntityUID),
1974 Bunch(Vec<&'a EntityUID>),
1975}
1976
1977impl<'a> Iterator for EntityIterator<'a> {
1978 type Item = &'a EntityUID;
1979
1980 fn next(&mut self) -> Option<Self::Item> {
1981 match self {
1982 EntityIterator::None => None,
1983 EntityIterator::One(euid) => {
1984 let eptr = *euid;
1985 let mut ptr = EntityIterator::None;
1986 std::mem::swap(self, &mut ptr);
1987 Some(eptr)
1988 }
1989 EntityIterator::Bunch(v) => v.pop(),
1990 }
1991 }
1992}
1993
1994#[cfg(test)]
1995pub(crate) mod test_generators {
1996 use super::*;
1997
1998 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
1999 let euid = Arc::new(EntityUID::with_eid("test"));
2000 let v = vec![
2001 PrincipalOrResourceConstraint::any(),
2002 PrincipalOrResourceConstraint::is_eq(euid.clone()),
2003 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)),
2004 PrincipalOrResourceConstraint::is_in(euid),
2005 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)),
2006 ];
2007
2008 v.into_iter()
2009 }
2010
2011 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
2012 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
2013 }
2014
2015 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
2016 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
2017 }
2018
2019 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
2020 let euid: EntityUID = "Action::\"test\""
2021 .parse()
2022 .expect("Invalid action constraint euid");
2023 let v = vec![
2024 ActionConstraint::any(),
2025 ActionConstraint::is_eq(euid.clone()),
2026 ActionConstraint::is_in([euid.clone()]),
2027 ActionConstraint::is_in([euid.clone(), euid]),
2028 ];
2029
2030 v.into_iter()
2031 }
2032
2033 pub fn all_templates() -> impl Iterator<Item = Template> {
2034 let mut buf = vec![];
2035 let permit = PolicyID::from_string("permit");
2036 let forbid = PolicyID::from_string("forbid");
2037 for principal in all_principal_constraints() {
2038 for action in all_actions_constraints() {
2039 for resource in all_resource_constraints() {
2040 let permit = Template::new(
2041 permit.clone(),
2042 None,
2043 Annotations::new(),
2044 Effect::Permit,
2045 principal.clone(),
2046 action.clone(),
2047 resource.clone(),
2048 None,
2049 );
2050 let forbid = Template::new(
2051 forbid.clone(),
2052 None,
2053 Annotations::new(),
2054 Effect::Forbid,
2055 principal.clone(),
2056 action.clone(),
2057 resource.clone(),
2058 None,
2059 );
2060 buf.push(permit);
2061 buf.push(forbid);
2062 }
2063 }
2064 }
2065 buf.into_iter()
2066 }
2067}
2068
2069#[cfg(test)]
2070#[allow(clippy::indexing_slicing)]
2072#[allow(clippy::panic)]
2074mod test {
2075 use cool_asserts::assert_matches;
2076 use std::collections::HashSet;
2077
2078 use super::{test_generators::*, *};
2079 use crate::{
2080 parser::{
2081 parse_policy,
2082 test_utils::{expect_exactly_one_error, expect_some_error_matches},
2083 },
2084 test_utils::ExpectedErrorMessageBuilder,
2085 };
2086
2087 #[test]
2088 fn link_templates() {
2089 for template in all_templates() {
2090 template.check_invariant();
2091 let t = Arc::new(template);
2092 let env = t
2093 .slots()
2094 .map(|slot| (slot.id, EntityUID::with_eid("eid")))
2095 .collect();
2096 let _ = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
2097 }
2098 }
2099
2100 #[test]
2101 fn test_template_rebuild() {
2102 for template in all_templates() {
2103 let id = template.id().clone();
2104 let effect = template.effect();
2105 let p = template.principal_constraint().clone();
2106 let a = template.action_constraint().clone();
2107 let r = template.resource_constraint().clone();
2108 let non_scope = template.non_scope_constraints().cloned();
2109 let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
2110 assert_eq!(template, t2);
2111 }
2112 }
2113
2114 #[test]
2115 fn test_static_policy_rebuild() {
2116 for template in all_templates() {
2117 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
2118 let id = ip.id().clone();
2119 let e = ip.effect();
2120 let anno = ip
2121 .annotations()
2122 .map(|(k, v)| (k.clone(), v.clone()))
2123 .collect();
2124 let p = ip.principal_constraint().clone();
2125 let a = ip.action_constraint().clone();
2126 let r = ip.resource_constraint().clone();
2127 let non_scope = ip.non_scope_constraints().clone();
2128 let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope.cloned())
2129 .expect("Policy Creation Failed");
2130 assert_eq!(ip, ip2);
2131 let (t2, inst) = Template::link_static_policy(ip2);
2132 assert!(inst.is_static());
2133 assert_eq!(&template, t2.as_ref());
2134 }
2135 }
2136 }
2137
2138 #[test]
2139 fn ir_binding_too_many() {
2140 let tid = PolicyID::from_string("tid");
2141 let iid = PolicyID::from_string("iid");
2142 let t = Arc::new(Template::new(
2143 tid,
2144 None,
2145 Annotations::new(),
2146 Effect::Forbid,
2147 PrincipalConstraint::is_eq_slot(),
2148 ActionConstraint::Any,
2149 ResourceConstraint::any(),
2150 None,
2151 ));
2152 let mut m = HashMap::new();
2153 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
2154 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2155 assert_eq!(unbound_values, vec![SlotId::principal()]);
2156 assert_eq!(extra_values, vec![SlotId::resource()]);
2157 });
2158 }
2159
2160 #[test]
2161 fn ir_binding_too_few() {
2162 let tid = PolicyID::from_string("tid");
2163 let iid = PolicyID::from_string("iid");
2164 let t = Arc::new(Template::new(
2165 tid,
2166 None,
2167 Annotations::new(),
2168 Effect::Forbid,
2169 PrincipalConstraint::is_eq_slot(),
2170 ActionConstraint::Any,
2171 ResourceConstraint::is_in_slot(),
2172 None,
2173 ));
2174 assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2175 assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
2176 assert_eq!(extra_values, vec![]);
2177 });
2178 let mut m = HashMap::new();
2179 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
2180 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2181 assert_eq!(unbound_values, vec![SlotId::resource()]);
2182 assert_eq!(extra_values, vec![]);
2183 });
2184 }
2185
2186 #[test]
2187 fn ir_binding() {
2188 let tid = PolicyID::from_string("template");
2189 let iid = PolicyID::from_string("linked");
2190 let t = Arc::new(Template::new(
2191 tid,
2192 None,
2193 Annotations::new(),
2194 Effect::Permit,
2195 PrincipalConstraint::is_in_slot(),
2196 ActionConstraint::any(),
2197 ResourceConstraint::is_eq_slot(),
2198 None,
2199 ));
2200
2201 let mut m = HashMap::new();
2202 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2203 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2204
2205 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2206 assert_eq!(r.id(), &iid);
2207 assert_eq!(
2208 r.env().get(&SlotId::principal()),
2209 Some(&EntityUID::with_eid("theprincipal"))
2210 );
2211 assert_eq!(
2212 r.env().get(&SlotId::resource()),
2213 Some(&EntityUID::with_eid("theresource"))
2214 );
2215 }
2216
2217 #[test]
2218 fn isnt_template_implies_from_succeeds() {
2219 for template in all_templates() {
2220 if template.slots().count() == 0 {
2221 StaticPolicy::try_from(template).expect("Should succeed");
2222 }
2223 }
2224 }
2225
2226 #[test]
2227 fn is_template_implies_from_fails() {
2228 for template in all_templates() {
2229 if template.slots().count() != 0 {
2230 assert!(
2231 StaticPolicy::try_from(template.clone()).is_err(),
2232 "Following template did convert {template}"
2233 );
2234 }
2235 }
2236 }
2237
2238 #[test]
2239 fn non_template_iso() {
2240 for template in all_templates() {
2241 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2242 let (t2, _) = Template::link_static_policy(p);
2243 assert_eq!(&template, t2.as_ref());
2244 }
2245 }
2246 }
2247
2248 #[test]
2249 fn template_into_expr() {
2250 for template in all_templates() {
2251 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2252 let t: Template = template;
2253 assert_eq!(p.condition(), t.condition());
2254 assert_eq!(p.effect(), t.effect());
2255 }
2256 }
2257 }
2258
2259 #[test]
2260 fn template_por_iter() {
2261 let e = Arc::new(EntityUID::with_eid("eid"));
2262 assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2263 assert_eq!(
2264 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2265 Some(&e)
2266 );
2267 assert_eq!(
2268 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(),
2269 None
2270 );
2271 assert_eq!(
2272 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2273 Some(&e)
2274 );
2275 assert_eq!(
2276 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(),
2277 None
2278 );
2279 assert_eq!(
2280 PrincipalOrResourceConstraint::IsIn(
2281 Arc::new("T".parse().unwrap()),
2282 EntityReference::EUID(e.clone())
2283 )
2284 .get_euid(),
2285 Some(&e)
2286 );
2287 assert_eq!(
2288 PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2289 None
2290 );
2291 assert_eq!(
2292 PrincipalOrResourceConstraint::IsIn(
2293 Arc::new("T".parse().unwrap()),
2294 EntityReference::Slot(None)
2295 )
2296 .get_euid(),
2297 None
2298 );
2299 }
2300
2301 #[test]
2302 fn action_iter() {
2303 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2304 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2305 let v = a.iter_euids().collect::<Vec<_>>();
2306 assert_eq!(vec![&EntityUID::with_eid("test")], v);
2307 let a =
2308 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2309 let set = a.iter_euids().collect::<HashSet<_>>();
2310 let e1 = EntityUID::with_eid("test1");
2311 let e2 = EntityUID::with_eid("test2");
2312 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2313 assert_eq!(set, correct);
2314 }
2315
2316 #[test]
2317 fn test_iter_none() {
2318 let mut i = EntityIterator::None;
2319 assert_eq!(i.next(), None);
2320 }
2321
2322 #[test]
2323 fn test_iter_once() {
2324 let id = EntityUID::from_components(
2325 name::Name::parse_unqualified_name("s").unwrap().into(),
2326 entity::Eid::new("eid"),
2327 None,
2328 );
2329 let mut i = EntityIterator::One(&id);
2330 assert_eq!(i.next(), Some(&id));
2331 assert_eq!(i.next(), None);
2332 }
2333
2334 #[test]
2335 fn test_iter_mult() {
2336 let id1 = EntityUID::from_components(
2337 name::Name::parse_unqualified_name("s").unwrap().into(),
2338 entity::Eid::new("eid1"),
2339 None,
2340 );
2341 let id2 = EntityUID::from_components(
2342 name::Name::parse_unqualified_name("s").unwrap().into(),
2343 entity::Eid::new("eid2"),
2344 None,
2345 );
2346 let v = vec![&id1, &id2];
2347 let mut i = EntityIterator::Bunch(v);
2348 assert_eq!(i.next(), Some(&id2));
2349 assert_eq!(i.next(), Some(&id1));
2350 assert_eq!(i.next(), None)
2351 }
2352
2353 #[test]
2354 fn euid_into_expr() {
2355 let e = EntityReference::Slot(None);
2356 assert_eq!(
2357 e.into_expr(SlotId::principal()),
2358 Expr::slot(SlotId::principal())
2359 );
2360 let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2361 assert_eq!(
2362 e.into_expr(SlotId::principal()),
2363 Expr::val(EntityUID::with_eid("eid"))
2364 );
2365 }
2366
2367 #[test]
2368 fn por_constraint_display() {
2369 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None));
2370 let s = t.display(PrincipalOrResource::Principal);
2371 assert_eq!(s, "principal == ?principal");
2372 let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2373 EntityUID::with_eid("test"),
2374 )));
2375 let s = t.display(PrincipalOrResource::Principal);
2376 assert_eq!(s, "principal == test_entity_type::\"test\"");
2377 }
2378
2379 #[test]
2380 fn unexpected_templates() {
2381 let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2382 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2383 expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2384 "expected a static policy, got a template containing the slot ?principal"
2385 )
2386 .help("try removing the template slot(s) from this policy")
2387 .exactly_one_underline("?principal")
2388 .build()
2389 );
2390 });
2391
2392 let policy_str =
2393 r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2394 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2395 expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2396 "expected a static policy, got a template containing the slot ?principal"
2397 )
2398 .help("try removing the template slot(s) from this policy")
2399 .exactly_one_underline("?principal")
2400 .build()
2401 );
2402 assert_eq!(e.len(), 2);
2403 });
2404 }
2405
2406 #[test]
2407 fn policy_to_expr() {
2408 let policy_str = r#"permit(principal is A, action, resource is B)
2409 when { 1 == 2 }
2410 unless { 2 == 1}
2411 when { 3 == 4}
2412 unless { 4 == 3};"#;
2413 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Ok(p) => {
2414 assert_eq!(ToString::to_string(&p.condition()), "(principal is A) && (true && ((resource is B) && ((1 == 2) && ((!(2 == 1)) && ((3 == 4) && (!(4 == 3)))))))");
2415 });
2416 }
2417
2418 #[cfg(feature = "tolerant-ast")]
2419 #[test]
2420 fn template_body_error_methods() {
2421 use std::str::FromStr;
2422
2423 let policy_id = PolicyID::from_string("error_policy");
2424 let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2425 let error_body = TemplateBody::TemplateBodyError(policy_id.clone(), error_loc.clone());
2426
2427 let expected_error = <ExprWithErrsBuilder as ExprBuilder>::new()
2428 .error(ParseErrors::singleton(ToASTError::new(
2429 ToASTErrorKind::ASTErrorNode,
2430 Some(Loc::new(0..1, "ASTErrorNode".into())),
2431 )))
2432 .unwrap();
2433
2434 assert_eq!(error_body.id(), &policy_id);
2436
2437 assert_eq!(error_body.loc(), error_loc.as_ref());
2439
2440 let new_policy_id = PolicyID::from_string("new_error_policy");
2442 let updated_error_body = error_body.new_id(new_policy_id.clone());
2443 assert_matches!(updated_error_body,
2444 TemplateBody::TemplateBodyError(id, loc) if id == new_policy_id && loc == error_loc
2445 );
2446
2447 assert_eq!(error_body.effect(), Effect::Forbid);
2449
2450 assert_eq!(
2452 error_body.annotation(&AnyId::from_str("test").unwrap()),
2453 None
2454 );
2455
2456 assert!(error_body.annotations().count() == 0);
2458
2459 assert_eq!(
2461 *error_body.principal_constraint(),
2462 PrincipalConstraint::any()
2463 );
2464
2465 assert_eq!(*error_body.action_constraint(), ActionConstraint::any());
2467
2468 assert_eq!(*error_body.resource_constraint(), ResourceConstraint::any());
2470
2471 assert_eq!(error_body.non_scope_constraints(), Some(&expected_error));
2473
2474 assert_eq!(error_body.condition(), expected_error);
2476
2477 let display_str = format!("{error_body}");
2479 assert!(display_str.contains("TemplateBodyError"));
2480 assert!(display_str.contains("error_policy"));
2481 }
2482
2483 #[cfg(feature = "tolerant-ast")]
2484 #[test]
2485 fn template_error_methods() {
2486 let policy_id = PolicyID::from_string("error_policy");
2487 let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2488 let error_template = Template::error(policy_id.clone(), error_loc.clone());
2489
2490 assert_eq!(error_template.id(), &policy_id);
2492
2493 assert!(error_template.slots().count() == 0);
2495
2496 assert_matches!(error_template.body,
2498 TemplateBody::TemplateBodyError(ref id, ref loc) if id == &policy_id && loc == &error_loc
2499 );
2500
2501 assert_eq!(
2503 error_template.principal_constraint(),
2504 &PrincipalConstraint::any()
2505 );
2506
2507 assert_eq!(*error_template.action_constraint(), ActionConstraint::any());
2509
2510 assert_eq!(
2512 *error_template.resource_constraint(),
2513 ResourceConstraint::any()
2514 );
2515
2516 assert_eq!(error_template.effect(), Effect::Forbid);
2518
2519 assert_eq!(
2521 error_template.condition(),
2522 DEFAULT_ERROR_EXPR.as_ref().clone()
2523 );
2524
2525 assert_eq!(error_template.loc(), error_loc.as_ref());
2527
2528 assert!(error_template.annotations().count() == 0);
2530
2531 let display_str = format!("{error_template}");
2533 assert!(display_str.contains("TemplateBody::TemplateBodyError"));
2534 assert!(display_str.contains(&policy_id.to_string()));
2535 }
2536}