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(crate) fn new_template_id(self, id: PolicyID) -> Option<Self> {
583 self.link.map(|link| Policy {
584 template: Arc::new(self.template.new_id(id)),
585 link: Some(link),
586 values: self.values,
587 })
588 }
589
590 pub fn loc(&self) -> Option<&Loc> {
592 self.template.loc()
593 }
594
595 pub fn is_static(&self) -> bool {
597 self.link.is_none()
598 }
599
600 pub fn unknown_entities(&self) -> HashSet<EntityUID> {
602 self.condition()
603 .unknowns()
604 .filter_map(
605 |Unknown {
606 name,
607 type_annotation,
608 }| {
609 if matches!(type_annotation, Some(Type::Entity { .. })) {
610 EntityUID::from_str(name.as_str()).ok()
611 } else {
612 None
613 }
614 },
615 )
616 .collect()
617 }
618}
619
620impl std::fmt::Display for Policy {
621 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622 if self.is_static() {
623 write!(f, "{}", self.template())
624 } else {
625 write!(
626 f,
627 "Template Instance of {}, slots: [{}]",
628 self.template().id(),
629 display_slot_env(self.env())
630 )
631 }
632 }
633}
634
635pub type SlotEnv = HashMap<SlotId, EntityUID>;
637
638#[derive(Debug, Clone, PartialEq, Eq)]
645pub struct LiteralPolicy {
646 template_id: PolicyID,
648 link_id: Option<PolicyID>,
652 values: SlotEnv,
654}
655
656impl LiteralPolicy {
657 pub fn static_policy(template_id: PolicyID) -> Self {
661 Self {
662 template_id,
663 link_id: None,
664 values: SlotEnv::new(),
665 }
666 }
667
668 pub fn template_linked_policy(
672 template_id: PolicyID,
673 link_id: PolicyID,
674 values: SlotEnv,
675 ) -> Self {
676 Self {
677 template_id,
678 link_id: Some(link_id),
679 values,
680 }
681 }
682
683 pub fn value(&self, slot: &SlotId) -> Option<&EntityUID> {
685 self.values.get(slot)
686 }
687}
688
689impl std::hash::Hash for LiteralPolicy {
692 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
693 self.template_id.hash(state);
694 let mut buf = self.values.iter().collect::<Vec<_>>();
696 buf.sort();
697 for (id, euid) in buf {
698 id.hash(state);
699 euid.hash(state);
700 }
701 }
702}
703
704#[cfg(test)]
706mod hashing_tests {
707 use std::{
708 collections::hash_map::DefaultHasher,
709 hash::{Hash, Hasher},
710 };
711
712 use super::*;
713
714 fn compute_hash(ir: &LiteralPolicy) -> u64 {
715 let mut s = DefaultHasher::new();
716 ir.hash(&mut s);
717 s.finish()
718 }
719
720 fn build_template_linked_policy() -> LiteralPolicy {
721 let mut map = HashMap::new();
722 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
723 LiteralPolicy {
724 template_id: PolicyID::from_string("template"),
725 link_id: Some(PolicyID::from_string("id")),
726 values: map,
727 }
728 }
729
730 #[test]
731 fn hash_property_instances() {
732 let a = build_template_linked_policy();
733 let b = build_template_linked_policy();
734 assert_eq!(a, b);
735 assert_eq!(compute_hash(&a), compute_hash(&b));
736 }
737}
738
739#[derive(Debug, Diagnostic, Error)]
741pub enum ReificationError {
742 #[error("the id linked to does not exist")]
744 NoSuchTemplate(PolicyID),
745 #[error(transparent)]
747 #[diagnostic(transparent)]
748 Linking(#[from] LinkingError),
749}
750
751impl LiteralPolicy {
752 pub fn reify(
757 self,
758 templates: &LinkedHashMap<PolicyID, Arc<Template>>,
759 ) -> Result<Policy, ReificationError> {
760 let template = templates
761 .get(&self.template_id)
762 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
763 Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
765 Ok(Policy::new(template.clone(), self.link_id, self.values))
766 }
767
768 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
770 self.values.get(id)
771 }
772
773 pub fn id(&self) -> &PolicyID {
775 self.link_id.as_ref().unwrap_or(&self.template_id)
776 }
777
778 pub fn template_id(&self) -> &PolicyID {
782 &self.template_id
783 }
784
785 pub fn is_static(&self) -> bool {
787 self.link_id.is_none()
788 }
789}
790
791fn display_slot_env(env: &SlotEnv) -> String {
792 env.iter()
793 .map(|(slot, value)| format!("{slot} -> {value}"))
794 .join(",")
795}
796
797impl std::fmt::Display for LiteralPolicy {
798 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
799 if self.is_static() {
800 write!(f, "Static policy w/ ID {}", self.template_id())
801 } else {
802 write!(
803 f,
804 "Template linked policy of {}, slots: [{}]",
805 self.template_id(),
806 display_slot_env(&self.values),
807 )
808 }
809 }
810}
811
812impl From<Policy> for LiteralPolicy {
813 fn from(p: Policy) -> Self {
814 Self {
815 template_id: p.template.id().clone(),
816 link_id: p.link,
817 values: p.values,
818 }
819 }
820}
821
822#[derive(Clone, Hash, Eq, PartialEq, Debug)]
826pub struct StaticPolicy(TemplateBody);
827
828impl StaticPolicy {
829 pub fn id(&self) -> &PolicyID {
831 self.0.id()
832 }
833
834 pub fn new_id(&self, id: PolicyID) -> Self {
836 StaticPolicy(self.0.new_id(id))
837 }
838
839 pub fn loc(&self) -> Option<&Loc> {
841 self.0.loc()
842 }
843
844 pub fn effect(&self) -> Effect {
846 self.0.effect()
847 }
848
849 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
851 self.0.annotation(key)
852 }
853
854 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
856 self.0.annotations()
857 }
858
859 pub fn principal_constraint(&self) -> &PrincipalConstraint {
861 self.0.principal_constraint()
862 }
863
864 pub fn principal_constraint_expr(&self) -> Expr {
868 self.0.principal_constraint_expr()
869 }
870
871 pub fn action_constraint(&self) -> &ActionConstraint {
873 self.0.action_constraint()
874 }
875
876 pub fn action_constraint_expr(&self) -> Expr {
880 self.0.action_constraint_expr()
881 }
882
883 pub fn resource_constraint(&self) -> &ResourceConstraint {
885 self.0.resource_constraint()
886 }
887
888 pub fn resource_constraint_expr(&self) -> Expr {
892 self.0.resource_constraint_expr()
893 }
894
895 pub fn non_scope_constraints(&self) -> Option<&Expr> {
900 self.0.non_scope_constraints()
901 }
902
903 pub fn condition(&self) -> Expr {
909 self.0.condition()
910 }
911
912 #[allow(clippy::too_many_arguments)]
914 pub fn new(
915 id: PolicyID,
916 loc: Option<Loc>,
917 annotations: Annotations,
918 effect: Effect,
919 principal_constraint: PrincipalConstraint,
920 action_constraint: ActionConstraint,
921 resource_constraint: ResourceConstraint,
922 non_scope_constraints: Option<Expr>,
923 ) -> Result<Self, UnexpectedSlotError> {
924 let body = TemplateBody::new(
925 id,
926 loc,
927 annotations,
928 effect,
929 principal_constraint,
930 action_constraint,
931 resource_constraint,
932 non_scope_constraints,
933 );
934 let first_slot = body.condition().slots().next();
935 match first_slot {
937 Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
938 None => Ok(Self(body)),
939 }
940 }
941}
942
943impl TryFrom<Template> for StaticPolicy {
944 type Error = UnexpectedSlotError;
945
946 fn try_from(value: Template) -> Result<Self, Self::Error> {
947 let o = value.slots().next();
949 match o {
950 Some(slot_id) => Err(Self::Error::FoundSlot(slot_id.clone())),
951 None => Ok(Self(value.body)),
952 }
953 }
954}
955
956impl From<StaticPolicy> for Policy {
957 fn from(p: StaticPolicy) -> Policy {
958 let (_, policy) = Template::link_static_policy(p);
959 policy
960 }
961}
962
963impl From<StaticPolicy> for Arc<Template> {
964 fn from(p: StaticPolicy) -> Self {
965 let (t, _) = Template::link_static_policy(p);
966 t
967 }
968}
969
970#[derive(Educe, Clone, Debug)]
973#[educe(PartialEq, Eq, Hash)]
974pub struct TemplateBodyImpl {
975 id: PolicyID,
977 #[educe(PartialEq(ignore))]
979 #[educe(Hash(ignore))]
980 loc: Option<Loc>,
981 annotations: Arc<Annotations>,
985 effect: Effect,
987 principal_constraint: PrincipalConstraint,
991 action_constraint: ActionConstraint,
995 resource_constraint: ResourceConstraint,
999 non_scope_constraints: Option<Arc<Expr>>,
1004}
1005
1006#[derive(Clone, Hash, Eq, PartialEq, Debug)]
1009pub enum TemplateBody {
1010 TemplateBody(TemplateBodyImpl),
1012 #[cfg(feature = "tolerant-ast")]
1013 TemplateBodyError(PolicyID, Option<Loc>),
1015}
1016
1017impl TemplateBody {
1018 pub fn id(&self) -> &PolicyID {
1020 match self {
1021 TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => id,
1022 #[cfg(feature = "tolerant-ast")]
1023 TemplateBody::TemplateBodyError(id, _) => id,
1024 }
1025 }
1026
1027 pub fn loc(&self) -> Option<&Loc> {
1029 match self {
1030 TemplateBody::TemplateBody(TemplateBodyImpl { loc, .. }) => loc.as_ref(),
1031 #[cfg(feature = "tolerant-ast")]
1032 TemplateBody::TemplateBodyError(_, loc) => loc.as_ref(),
1033 }
1034 }
1035
1036 pub fn new_id(&self, id: PolicyID) -> Self {
1038 match self {
1039 TemplateBody::TemplateBody(t) => {
1040 let mut new = t.clone();
1041 new.id = id;
1042 TemplateBody::TemplateBody(new)
1043 }
1044 #[cfg(feature = "tolerant-ast")]
1045 TemplateBody::TemplateBodyError(_, loc) => {
1046 TemplateBody::TemplateBodyError(id, loc.clone())
1047 }
1048 }
1049 }
1050
1051 #[cfg(feature = "tolerant-ast")]
1052 pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
1054 TemplateBody::TemplateBodyError(id, loc)
1055 }
1056
1057 pub fn effect(&self) -> Effect {
1059 match self {
1060 TemplateBody::TemplateBody(TemplateBodyImpl { effect, .. }) => *effect,
1061 #[cfg(feature = "tolerant-ast")]
1062 TemplateBody::TemplateBodyError(_, _) => Effect::Forbid,
1063 }
1064 }
1065
1066 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
1068 match self {
1069 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => {
1070 annotations.get(key)
1071 }
1072 #[cfg(feature = "tolerant-ast")]
1073 TemplateBody::TemplateBodyError(_, _) => None,
1074 }
1075 }
1076
1077 pub fn annotations_arc(&self) -> &Arc<Annotations> {
1079 match self {
1080 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations,
1081 #[cfg(feature = "tolerant-ast")]
1082 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ANNOTATIONS,
1083 }
1084 }
1085
1086 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1088 match self {
1089 TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations.iter(),
1090 #[cfg(feature = "tolerant-ast")]
1091 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ANNOTATIONS.iter(),
1092 }
1093 }
1094
1095 pub fn principal_constraint(&self) -> &PrincipalConstraint {
1097 match self {
1098 TemplateBody::TemplateBody(TemplateBodyImpl {
1099 principal_constraint,
1100 ..
1101 }) => principal_constraint,
1102 #[cfg(feature = "tolerant-ast")]
1103 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_PRINCIPAL_CONSTRAINT,
1104 }
1105
1106 }
1108
1109 pub fn principal_constraint_expr(&self) -> Expr {
1113 match self {
1114 TemplateBody::TemplateBody(TemplateBodyImpl {
1115 principal_constraint,
1116 ..
1117 }) => principal_constraint.as_expr(),
1118 #[cfg(feature = "tolerant-ast")]
1119 TemplateBody::TemplateBodyError(_, _) => DEFAULT_PRINCIPAL_CONSTRAINT.as_expr(),
1120 }
1121 }
1122
1123 pub fn action_constraint(&self) -> &ActionConstraint {
1125 match self {
1126 TemplateBody::TemplateBody(TemplateBodyImpl {
1127 action_constraint, ..
1128 }) => action_constraint,
1129 #[cfg(feature = "tolerant-ast")]
1130 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ACTION_CONSTRAINT,
1131 }
1132 }
1133
1134 pub fn action_constraint_expr(&self) -> Expr {
1138 match self {
1139 TemplateBody::TemplateBody(TemplateBodyImpl {
1140 action_constraint, ..
1141 }) => action_constraint.as_expr(),
1142 #[cfg(feature = "tolerant-ast")]
1143 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ACTION_CONSTRAINT.as_expr(),
1144 }
1145 }
1146
1147 pub fn resource_constraint(&self) -> &ResourceConstraint {
1149 match self {
1150 TemplateBody::TemplateBody(TemplateBodyImpl {
1151 resource_constraint,
1152 ..
1153 }) => resource_constraint,
1154 #[cfg(feature = "tolerant-ast")]
1155 TemplateBody::TemplateBodyError(_, _) => &DEFAULT_RESOURCE_CONSTRAINT,
1156 }
1157 }
1158
1159 pub fn resource_constraint_expr(&self) -> Expr {
1163 match self {
1164 TemplateBody::TemplateBody(TemplateBodyImpl {
1165 resource_constraint,
1166 ..
1167 }) => resource_constraint.as_expr(),
1168 #[cfg(feature = "tolerant-ast")]
1169 TemplateBody::TemplateBodyError(_, _) => DEFAULT_RESOURCE_CONSTRAINT.as_expr(),
1170 }
1171 }
1172
1173 pub fn non_scope_constraints(&self) -> Option<&Expr> {
1178 match self {
1179 TemplateBody::TemplateBody(TemplateBodyImpl {
1180 non_scope_constraints,
1181 ..
1182 }) => non_scope_constraints.as_ref().map(|e| e.as_ref()),
1183 #[cfg(feature = "tolerant-ast")]
1184 TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1185 }
1186 }
1187
1188 pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
1190 match self {
1191 TemplateBody::TemplateBody(TemplateBodyImpl {
1192 non_scope_constraints,
1193 ..
1194 }) => non_scope_constraints.as_ref(),
1195 #[cfg(feature = "tolerant-ast")]
1196 TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1197 }
1198 }
1199
1200 pub fn condition(&self) -> Expr {
1206 match self {
1207 TemplateBody::TemplateBody(TemplateBodyImpl { .. }) => {
1208 let loc = self.loc().cloned();
1209 Expr::and(
1210 self.principal_constraint_expr(),
1211 Expr::and(
1212 self.action_constraint_expr(),
1213 Expr::and(
1214 self.resource_constraint_expr(),
1215 self.non_scope_constraints()
1216 .cloned()
1217 .unwrap_or_else(|| Expr::val(true)),
1218 )
1219 .with_maybe_source_loc(loc.clone()),
1220 )
1221 .with_maybe_source_loc(loc.clone()),
1222 )
1223 .with_maybe_source_loc(loc)
1224 }
1225 #[cfg(feature = "tolerant-ast")]
1226 TemplateBody::TemplateBodyError(_, _) => DEFAULT_ERROR_EXPR.as_ref().clone(),
1227 }
1228 }
1229
1230 #[allow(clippy::too_many_arguments)]
1232 pub fn new_shared(
1233 id: PolicyID,
1234 loc: Option<Loc>,
1235 annotations: Arc<Annotations>,
1236 effect: Effect,
1237 principal_constraint: PrincipalConstraint,
1238 action_constraint: ActionConstraint,
1239 resource_constraint: ResourceConstraint,
1240 non_scope_constraints: Option<Arc<Expr>>,
1241 ) -> Self {
1242 Self::TemplateBody(TemplateBodyImpl {
1243 id,
1244 loc,
1245 annotations,
1246 effect,
1247 principal_constraint,
1248 action_constraint,
1249 resource_constraint,
1250 non_scope_constraints,
1251 })
1252 }
1253
1254 #[allow(clippy::too_many_arguments)]
1256 pub fn new(
1257 id: PolicyID,
1258 loc: Option<Loc>,
1259 annotations: Annotations,
1260 effect: Effect,
1261 principal_constraint: PrincipalConstraint,
1262 action_constraint: ActionConstraint,
1263 resource_constraint: ResourceConstraint,
1264 non_scope_constraints: Option<Expr>,
1265 ) -> Self {
1266 Self::TemplateBody(TemplateBodyImpl {
1267 id,
1268 loc,
1269 annotations: Arc::new(annotations),
1270 effect,
1271 principal_constraint,
1272 action_constraint,
1273 resource_constraint,
1274 non_scope_constraints: non_scope_constraints.map(Arc::new),
1275 })
1276 }
1277}
1278
1279impl From<StaticPolicy> for TemplateBody {
1280 fn from(p: StaticPolicy) -> Self {
1281 p.0
1282 }
1283}
1284
1285impl std::fmt::Display for TemplateBody {
1286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1287 match self {
1288 TemplateBody::TemplateBody(template_body_impl) => {
1289 template_body_impl.annotations.fmt(f)?;
1290 write!(
1291 f,
1292 "{}(\n {},\n {},\n {}\n)",
1293 self.effect(),
1294 self.principal_constraint(),
1295 self.action_constraint(),
1296 self.resource_constraint(),
1297 )?;
1298 if let Some(non_scope_constraints) = self.non_scope_constraints() {
1299 write!(f, " when {{\n {non_scope_constraints}\n}};")
1300 } else {
1301 write!(f, ";")
1302 }
1303 }
1304 #[cfg(feature = "tolerant-ast")]
1305 TemplateBody::TemplateBodyError(policy_id, _) => {
1306 write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1307 }
1308 }
1309 }
1310}
1311
1312#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1314pub struct PrincipalConstraint {
1315 pub(crate) constraint: PrincipalOrResourceConstraint,
1316}
1317
1318impl PrincipalConstraint {
1319 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1321 PrincipalConstraint { constraint }
1322 }
1323
1324 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1326 &self.constraint
1327 }
1328
1329 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1331 self.constraint
1332 }
1333
1334 pub fn as_expr(&self) -> Expr {
1336 self.constraint.as_expr(PrincipalOrResource::Principal)
1337 }
1338
1339 pub fn any() -> Self {
1341 PrincipalConstraint {
1342 constraint: PrincipalOrResourceConstraint::any(),
1343 }
1344 }
1345
1346 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1348 PrincipalConstraint {
1349 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1350 }
1351 }
1352
1353 pub fn is_eq_slot() -> Self {
1355 Self {
1356 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1357 }
1358 }
1359
1360 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1362 PrincipalConstraint {
1363 constraint: PrincipalOrResourceConstraint::is_in(euid),
1364 }
1365 }
1366
1367 pub fn is_in_slot() -> Self {
1369 Self {
1370 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1371 }
1372 }
1373
1374 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1376 Self {
1377 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1378 }
1379 }
1380
1381 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1383 Self {
1384 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1385 }
1386 }
1387
1388 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1390 Self {
1391 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1392 }
1393 }
1394
1395 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1397 match self.constraint {
1398 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1399 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1400 },
1401 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1402 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1403 },
1404 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1405 constraint: PrincipalOrResourceConstraint::IsIn(
1406 entity_type,
1407 EntityReference::EUID(euid),
1408 ),
1409 },
1410 PrincipalOrResourceConstraint::Is(_)
1412 | PrincipalOrResourceConstraint::Any
1413 | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1414 | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1415 | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1416 }
1417 }
1418}
1419
1420impl std::fmt::Display for PrincipalConstraint {
1421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1422 write!(
1423 f,
1424 "{}",
1425 self.constraint.display(PrincipalOrResource::Principal)
1426 )
1427 }
1428}
1429
1430#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1432pub struct ResourceConstraint {
1433 pub(crate) constraint: PrincipalOrResourceConstraint,
1434}
1435
1436impl ResourceConstraint {
1437 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1439 ResourceConstraint { constraint }
1440 }
1441
1442 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1444 &self.constraint
1445 }
1446
1447 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1449 self.constraint
1450 }
1451
1452 pub fn as_expr(&self) -> Expr {
1454 self.constraint.as_expr(PrincipalOrResource::Resource)
1455 }
1456
1457 pub fn any() -> Self {
1459 ResourceConstraint {
1460 constraint: PrincipalOrResourceConstraint::any(),
1461 }
1462 }
1463
1464 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1466 ResourceConstraint {
1467 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1468 }
1469 }
1470
1471 pub fn is_eq_slot() -> Self {
1473 Self {
1474 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1475 }
1476 }
1477
1478 pub fn is_in_slot() -> Self {
1480 Self {
1481 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1482 }
1483 }
1484
1485 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1487 ResourceConstraint {
1488 constraint: PrincipalOrResourceConstraint::is_in(euid),
1489 }
1490 }
1491
1492 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1494 Self {
1495 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1496 }
1497 }
1498
1499 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1501 Self {
1502 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1503 }
1504 }
1505
1506 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1508 Self {
1509 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1510 }
1511 }
1512
1513 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1515 match self.constraint {
1516 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1517 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1518 },
1519 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1520 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1521 },
1522 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1523 constraint: PrincipalOrResourceConstraint::IsIn(
1524 entity_type,
1525 EntityReference::EUID(euid),
1526 ),
1527 },
1528 PrincipalOrResourceConstraint::Is(_)
1530 | PrincipalOrResourceConstraint::Any
1531 | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1532 | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1533 | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1534 }
1535 }
1536}
1537
1538impl std::fmt::Display for ResourceConstraint {
1539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1540 write!(
1541 f,
1542 "{}",
1543 self.as_inner().display(PrincipalOrResource::Resource)
1544 )
1545 }
1546}
1547
1548#[derive(Educe, Clone, Debug, Eq)]
1550#[educe(Hash, PartialEq, PartialOrd, Ord)]
1551pub enum EntityReference {
1552 EUID(Arc<EntityUID>),
1554 Slot(
1556 #[educe(PartialEq(ignore))]
1557 #[educe(PartialOrd(ignore))]
1558 #[educe(Hash(ignore))]
1559 Option<Loc>,
1560 ),
1561}
1562
1563impl EntityReference {
1564 pub fn euid(euid: Arc<EntityUID>) -> Self {
1566 Self::EUID(euid)
1567 }
1568
1569 pub fn into_expr(&self, slot: SlotId) -> Expr {
1575 match self {
1576 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1577 EntityReference::Slot(loc) => Expr::slot(slot).with_maybe_source_loc(loc.clone()),
1578 }
1579 }
1580}
1581
1582#[derive(Debug, Clone, PartialEq, Eq, Error)]
1584pub enum UnexpectedSlotError {
1585 #[error("found slot `{}` where slots are not allowed", .0.id)]
1587 FoundSlot(Slot),
1588}
1589
1590impl Diagnostic for UnexpectedSlotError {
1591 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1592 match self {
1593 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1594 let label = miette::LabeledSpan::underline(loc.span);
1595 Box::new(std::iter::once(label)) as _
1596 }),
1597 }
1598 }
1599
1600 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
1601 match self {
1602 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|l| l as &dyn miette::SourceCode),
1603 }
1604 }
1605}
1606
1607impl From<EntityUID> for EntityReference {
1608 fn from(euid: EntityUID) -> Self {
1609 Self::EUID(Arc::new(euid))
1610 }
1611}
1612
1613#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1615#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1616pub enum PrincipalOrResource {
1617 Principal,
1619 Resource,
1621}
1622
1623impl std::fmt::Display for PrincipalOrResource {
1624 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1625 let v = Var::from(*self);
1626 write!(f, "{v}")
1627 }
1628}
1629
1630impl TryFrom<Var> for PrincipalOrResource {
1631 type Error = Var;
1632
1633 fn try_from(value: Var) -> Result<Self, Self::Error> {
1634 match value {
1635 Var::Principal => Ok(Self::Principal),
1636 Var::Action => Err(Var::Action),
1637 Var::Resource => Ok(Self::Resource),
1638 Var::Context => Err(Var::Context),
1639 }
1640 }
1641}
1642
1643#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1646pub enum PrincipalOrResourceConstraint {
1647 Any,
1649 In(EntityReference),
1651 Eq(EntityReference),
1653 Is(Arc<EntityType>),
1655 IsIn(Arc<EntityType>, EntityReference),
1657}
1658
1659impl PrincipalOrResourceConstraint {
1660 pub fn any() -> Self {
1662 PrincipalOrResourceConstraint::Any
1663 }
1664
1665 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1667 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1668 }
1669
1670 pub fn is_eq_slot() -> Self {
1672 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None))
1673 }
1674
1675 pub fn is_in_slot() -> Self {
1677 PrincipalOrResourceConstraint::In(EntityReference::Slot(None))
1678 }
1679
1680 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1682 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1683 }
1684
1685 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1687 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None))
1688 }
1689
1690 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1692 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1693 }
1694
1695 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1697 PrincipalOrResourceConstraint::Is(entity_type)
1698 }
1699
1700 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1704 match self {
1705 PrincipalOrResourceConstraint::Any => Expr::val(true),
1706 PrincipalOrResourceConstraint::Eq(euid) => {
1707 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1708 }
1709 PrincipalOrResourceConstraint::In(euid) => {
1710 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1711 }
1712 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1713 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1714 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1715 ),
1716 PrincipalOrResourceConstraint::Is(entity_type) => {
1717 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1718 }
1719 }
1720 }
1721
1722 pub fn display(&self, v: PrincipalOrResource) -> String {
1726 match self {
1727 PrincipalOrResourceConstraint::In(euid) => {
1728 format!("{} in {}", v, euid.into_expr(v.into()))
1729 }
1730 PrincipalOrResourceConstraint::Eq(euid) => {
1731 format!("{} == {}", v, euid.into_expr(v.into()))
1732 }
1733 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1734 format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1735 }
1736 PrincipalOrResourceConstraint::Is(entity_type) => {
1737 format!("{v} is {entity_type}")
1738 }
1739 PrincipalOrResourceConstraint::Any => format!("{v}"),
1740 }
1741 }
1742
1743 pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1745 match self {
1746 PrincipalOrResourceConstraint::Any => None,
1747 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1748 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None,
1749 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1750 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None,
1751 PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1752 PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None,
1753 PrincipalOrResourceConstraint::Is(_) => None,
1754 }
1755 }
1756
1757 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1759 self.get_euid()
1760 .into_iter()
1761 .map(|euid| euid.entity_type())
1762 .chain(match self {
1763 PrincipalOrResourceConstraint::Is(entity_type)
1764 | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1765 _ => None,
1766 })
1767 }
1768}
1769
1770#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1773pub enum ActionConstraint {
1774 Any,
1776 In(Vec<Arc<EntityUID>>),
1778 Eq(Arc<EntityUID>),
1780 #[cfg(feature = "tolerant-ast")]
1781 ErrorConstraint,
1783}
1784
1785impl std::fmt::Display for ActionConstraint {
1786 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1787 let render_euids =
1788 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1789 match self {
1790 ActionConstraint::Any => write!(f, "action"),
1791 ActionConstraint::In(euids) => {
1792 write!(f, "action in [{}]", render_euids(euids))
1793 }
1794 ActionConstraint::Eq(euid) => write!(f, "action == {euid}"),
1795 #[cfg(feature = "tolerant-ast")]
1796 ActionConstraint::ErrorConstraint => write!(f, "<invalid_action_constraint>"),
1797 }
1798 }
1799}
1800
1801impl ActionConstraint {
1802 pub fn any() -> Self {
1804 ActionConstraint::Any
1805 }
1806
1807 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1809 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1810 }
1811
1812 pub fn is_eq(euid: EntityUID) -> Self {
1814 ActionConstraint::Eq(Arc::new(euid))
1815 }
1816
1817 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1818 Expr::set(euids.into_iter().map(Expr::val))
1819 }
1820
1821 pub fn as_expr(&self) -> Expr {
1823 match self {
1824 ActionConstraint::Any => Expr::val(true),
1825 ActionConstraint::In(euids) => Expr::is_in(
1826 Expr::var(Var::Action),
1827 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1828 ),
1829 ActionConstraint::Eq(euid) => {
1830 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1831 }
1832 #[cfg(feature = "tolerant-ast")]
1833 ActionConstraint::ErrorConstraint => Expr::new(
1834 ExprKind::Error {
1835 error_kind: AstExprErrorKind::InvalidExpr(
1836 "Invalid action constraint".to_string(),
1837 ),
1838 },
1839 None,
1840 (),
1841 ),
1842 }
1843 }
1844
1845 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1847 match self {
1848 ActionConstraint::Any => EntityIterator::None,
1849 ActionConstraint::In(euids) => {
1850 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1851 }
1852 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1853 #[cfg(feature = "tolerant-ast")]
1854 ActionConstraint::ErrorConstraint => EntityIterator::None,
1855 }
1856 }
1857
1858 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1860 self.iter_euids().map(|euid| euid.entity_type())
1861 }
1862
1863 pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1866 match self {
1867 ActionConstraint::Any => Ok(self),
1868 ActionConstraint::In(ref euids) => {
1869 if let Some(euids) =
1870 NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1871 {
1872 Err(euids)
1873 } else {
1874 Ok(self)
1875 }
1876 }
1877 ActionConstraint::Eq(ref euid) => {
1878 if euid.is_action() {
1879 Ok(self)
1880 } else {
1881 Err(nonempty![euid.clone()])
1882 }
1883 }
1884 #[cfg(feature = "tolerant-ast")]
1885 ActionConstraint::ErrorConstraint => Ok(self),
1886 }
1887 }
1888}
1889
1890impl std::fmt::Display for StaticPolicy {
1891 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1892 let policy_template = &self.0;
1893 match policy_template {
1894 TemplateBody::TemplateBody(template_body_impl) => {
1895 for (k, v) in template_body_impl.annotations.iter() {
1896 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1897 }
1898 write!(
1899 f,
1900 "{}(\n {},\n {},\n {}\n)",
1901 self.effect(),
1902 self.principal_constraint(),
1903 self.action_constraint(),
1904 self.resource_constraint(),
1905 )?;
1906 if let Some(non_scope_constraints) = self.non_scope_constraints() {
1907 write!(f, " when {{\n {non_scope_constraints}\n}};")
1908 } else {
1909 write!(f, ";")
1910 }
1911 }
1912 #[cfg(feature = "tolerant-ast")]
1913 TemplateBody::TemplateBodyError(policy_id, _) => {
1914 write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1915 }
1916 }
1917 }
1918}
1919
1920#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1922pub struct PolicyID(SmolStr);
1923
1924impl PolicyID {
1925 pub fn from_string(id: impl AsRef<str>) -> Self {
1927 Self(SmolStr::from(id.as_ref()))
1928 }
1929
1930 pub fn from_smolstr(id: SmolStr) -> Self {
1932 Self(id)
1933 }
1934}
1935
1936impl std::fmt::Display for PolicyID {
1937 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1938 write!(f, "{}", self.0.escape_debug())
1939 }
1940}
1941
1942impl AsRef<str> for PolicyID {
1943 fn as_ref(&self) -> &str {
1944 &self.0
1945 }
1946}
1947
1948#[cfg(feature = "arbitrary")]
1949impl arbitrary::Arbitrary<'_> for PolicyID {
1950 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1951 let s: String = u.arbitrary()?;
1952 Ok(PolicyID::from_string(s))
1953 }
1954 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1955 <String as arbitrary::Arbitrary>::size_hint(depth)
1956 }
1957}
1958
1959#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1961#[serde(rename_all = "camelCase")]
1962#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1963#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1964#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1965pub enum Effect {
1966 Permit,
1968 Forbid,
1970}
1971
1972impl std::fmt::Display for Effect {
1973 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1974 match self {
1975 Self::Permit => write!(f, "permit"),
1976 Self::Forbid => write!(f, "forbid"),
1977 }
1978 }
1979}
1980
1981enum EntityIterator<'a> {
1982 None,
1983 One(&'a EntityUID),
1984 Bunch(Vec<&'a EntityUID>),
1985}
1986
1987impl<'a> Iterator for EntityIterator<'a> {
1988 type Item = &'a EntityUID;
1989
1990 fn next(&mut self) -> Option<Self::Item> {
1991 match self {
1992 EntityIterator::None => None,
1993 EntityIterator::One(euid) => {
1994 let eptr = *euid;
1995 let mut ptr = EntityIterator::None;
1996 std::mem::swap(self, &mut ptr);
1997 Some(eptr)
1998 }
1999 EntityIterator::Bunch(v) => v.pop(),
2000 }
2001 }
2002}
2003
2004#[cfg(test)]
2005pub(crate) mod test_generators {
2006 use super::*;
2007
2008 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
2009 let euid = Arc::new(EntityUID::with_eid("test"));
2010 let v = vec![
2011 PrincipalOrResourceConstraint::any(),
2012 PrincipalOrResourceConstraint::is_eq(euid.clone()),
2013 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)),
2014 PrincipalOrResourceConstraint::is_in(euid),
2015 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)),
2016 ];
2017
2018 v.into_iter()
2019 }
2020
2021 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
2022 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
2023 }
2024
2025 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
2026 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
2027 }
2028
2029 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
2030 let euid: EntityUID = "Action::\"test\""
2031 .parse()
2032 .expect("Invalid action constraint euid");
2033 let v = vec![
2034 ActionConstraint::any(),
2035 ActionConstraint::is_eq(euid.clone()),
2036 ActionConstraint::is_in([euid.clone()]),
2037 ActionConstraint::is_in([euid.clone(), euid]),
2038 ];
2039
2040 v.into_iter()
2041 }
2042
2043 pub fn all_templates() -> impl Iterator<Item = Template> {
2044 let mut buf = vec![];
2045 let permit = PolicyID::from_string("permit");
2046 let forbid = PolicyID::from_string("forbid");
2047 for principal in all_principal_constraints() {
2048 for action in all_actions_constraints() {
2049 for resource in all_resource_constraints() {
2050 let permit = Template::new(
2051 permit.clone(),
2052 None,
2053 Annotations::new(),
2054 Effect::Permit,
2055 principal.clone(),
2056 action.clone(),
2057 resource.clone(),
2058 None,
2059 );
2060 let forbid = Template::new(
2061 forbid.clone(),
2062 None,
2063 Annotations::new(),
2064 Effect::Forbid,
2065 principal.clone(),
2066 action.clone(),
2067 resource.clone(),
2068 None,
2069 );
2070 buf.push(permit);
2071 buf.push(forbid);
2072 }
2073 }
2074 }
2075 buf.into_iter()
2076 }
2077}
2078
2079#[cfg(test)]
2080#[allow(clippy::indexing_slicing)]
2082#[allow(clippy::panic)]
2084mod test {
2085 use cool_asserts::assert_matches;
2086 use std::collections::HashSet;
2087
2088 use super::{test_generators::*, *};
2089 use crate::{
2090 parser::{
2091 parse_policy,
2092 test_utils::{expect_exactly_one_error, expect_some_error_matches},
2093 },
2094 test_utils::ExpectedErrorMessageBuilder,
2095 };
2096
2097 #[test]
2098 fn link_templates() {
2099 for template in all_templates() {
2100 template.check_invariant();
2101 let t = Arc::new(template);
2102 let env = t
2103 .slots()
2104 .map(|slot| (slot.id, EntityUID::with_eid("eid")))
2105 .collect();
2106 let _ = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
2107 }
2108 }
2109
2110 #[test]
2111 fn test_template_rebuild() {
2112 for template in all_templates() {
2113 let id = template.id().clone();
2114 let effect = template.effect();
2115 let p = template.principal_constraint().clone();
2116 let a = template.action_constraint().clone();
2117 let r = template.resource_constraint().clone();
2118 let non_scope = template.non_scope_constraints().cloned();
2119 let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
2120 assert_eq!(template, t2);
2121 }
2122 }
2123
2124 #[test]
2125 fn test_static_policy_rebuild() {
2126 for template in all_templates() {
2127 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
2128 let id = ip.id().clone();
2129 let e = ip.effect();
2130 let anno = ip
2131 .annotations()
2132 .map(|(k, v)| (k.clone(), v.clone()))
2133 .collect();
2134 let p = ip.principal_constraint().clone();
2135 let a = ip.action_constraint().clone();
2136 let r = ip.resource_constraint().clone();
2137 let non_scope = ip.non_scope_constraints().clone();
2138 let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope.cloned())
2139 .expect("Policy Creation Failed");
2140 assert_eq!(ip, ip2);
2141 let (t2, inst) = Template::link_static_policy(ip2);
2142 assert!(inst.is_static());
2143 assert_eq!(&template, t2.as_ref());
2144 }
2145 }
2146 }
2147
2148 #[test]
2149 fn ir_binding_too_many() {
2150 let tid = PolicyID::from_string("tid");
2151 let iid = PolicyID::from_string("iid");
2152 let t = Arc::new(Template::new(
2153 tid,
2154 None,
2155 Annotations::new(),
2156 Effect::Forbid,
2157 PrincipalConstraint::is_eq_slot(),
2158 ActionConstraint::Any,
2159 ResourceConstraint::any(),
2160 None,
2161 ));
2162 let mut m = HashMap::new();
2163 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
2164 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2165 assert_eq!(unbound_values, vec![SlotId::principal()]);
2166 assert_eq!(extra_values, vec![SlotId::resource()]);
2167 });
2168 }
2169
2170 #[test]
2171 fn ir_binding_too_few() {
2172 let tid = PolicyID::from_string("tid");
2173 let iid = PolicyID::from_string("iid");
2174 let t = Arc::new(Template::new(
2175 tid,
2176 None,
2177 Annotations::new(),
2178 Effect::Forbid,
2179 PrincipalConstraint::is_eq_slot(),
2180 ActionConstraint::Any,
2181 ResourceConstraint::is_in_slot(),
2182 None,
2183 ));
2184 assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2185 assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
2186 assert_eq!(extra_values, vec![]);
2187 });
2188 let mut m = HashMap::new();
2189 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
2190 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2191 assert_eq!(unbound_values, vec![SlotId::resource()]);
2192 assert_eq!(extra_values, vec![]);
2193 });
2194 }
2195
2196 #[test]
2197 fn ir_binding() {
2198 let tid = PolicyID::from_string("template");
2199 let iid = PolicyID::from_string("linked");
2200 let t = Arc::new(Template::new(
2201 tid,
2202 None,
2203 Annotations::new(),
2204 Effect::Permit,
2205 PrincipalConstraint::is_in_slot(),
2206 ActionConstraint::any(),
2207 ResourceConstraint::is_eq_slot(),
2208 None,
2209 ));
2210
2211 let mut m = HashMap::new();
2212 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2213 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2214
2215 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2216 assert_eq!(r.id(), &iid);
2217 assert_eq!(
2218 r.env().get(&SlotId::principal()),
2219 Some(&EntityUID::with_eid("theprincipal"))
2220 );
2221 assert_eq!(
2222 r.env().get(&SlotId::resource()),
2223 Some(&EntityUID::with_eid("theresource"))
2224 );
2225 }
2226
2227 #[test]
2228 fn isnt_template_implies_from_succeeds() {
2229 for template in all_templates() {
2230 if template.slots().count() == 0 {
2231 StaticPolicy::try_from(template).expect("Should succeed");
2232 }
2233 }
2234 }
2235
2236 #[test]
2237 fn is_template_implies_from_fails() {
2238 for template in all_templates() {
2239 if template.slots().count() != 0 {
2240 assert!(
2241 StaticPolicy::try_from(template.clone()).is_err(),
2242 "Following template did convert {template}"
2243 );
2244 }
2245 }
2246 }
2247
2248 #[test]
2249 fn non_template_iso() {
2250 for template in all_templates() {
2251 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2252 let (t2, _) = Template::link_static_policy(p);
2253 assert_eq!(&template, t2.as_ref());
2254 }
2255 }
2256 }
2257
2258 #[test]
2259 fn template_into_expr() {
2260 for template in all_templates() {
2261 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2262 let t: Template = template;
2263 assert_eq!(p.condition(), t.condition());
2264 assert_eq!(p.effect(), t.effect());
2265 }
2266 }
2267 }
2268
2269 #[test]
2270 fn template_por_iter() {
2271 let e = Arc::new(EntityUID::with_eid("eid"));
2272 assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2273 assert_eq!(
2274 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2275 Some(&e)
2276 );
2277 assert_eq!(
2278 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(),
2279 None
2280 );
2281 assert_eq!(
2282 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2283 Some(&e)
2284 );
2285 assert_eq!(
2286 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(),
2287 None
2288 );
2289 assert_eq!(
2290 PrincipalOrResourceConstraint::IsIn(
2291 Arc::new("T".parse().unwrap()),
2292 EntityReference::EUID(e.clone())
2293 )
2294 .get_euid(),
2295 Some(&e)
2296 );
2297 assert_eq!(
2298 PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2299 None
2300 );
2301 assert_eq!(
2302 PrincipalOrResourceConstraint::IsIn(
2303 Arc::new("T".parse().unwrap()),
2304 EntityReference::Slot(None)
2305 )
2306 .get_euid(),
2307 None
2308 );
2309 }
2310
2311 #[test]
2312 fn action_iter() {
2313 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2314 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2315 let v = a.iter_euids().collect::<Vec<_>>();
2316 assert_eq!(vec![&EntityUID::with_eid("test")], v);
2317 let a =
2318 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2319 let set = a.iter_euids().collect::<HashSet<_>>();
2320 let e1 = EntityUID::with_eid("test1");
2321 let e2 = EntityUID::with_eid("test2");
2322 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2323 assert_eq!(set, correct);
2324 }
2325
2326 #[test]
2327 fn test_iter_none() {
2328 let mut i = EntityIterator::None;
2329 assert_eq!(i.next(), None);
2330 }
2331
2332 #[test]
2333 fn test_iter_once() {
2334 let id = EntityUID::from_components(
2335 name::Name::parse_unqualified_name("s").unwrap().into(),
2336 entity::Eid::new("eid"),
2337 None,
2338 );
2339 let mut i = EntityIterator::One(&id);
2340 assert_eq!(i.next(), Some(&id));
2341 assert_eq!(i.next(), None);
2342 }
2343
2344 #[test]
2345 fn test_iter_mult() {
2346 let id1 = EntityUID::from_components(
2347 name::Name::parse_unqualified_name("s").unwrap().into(),
2348 entity::Eid::new("eid1"),
2349 None,
2350 );
2351 let id2 = EntityUID::from_components(
2352 name::Name::parse_unqualified_name("s").unwrap().into(),
2353 entity::Eid::new("eid2"),
2354 None,
2355 );
2356 let v = vec![&id1, &id2];
2357 let mut i = EntityIterator::Bunch(v);
2358 assert_eq!(i.next(), Some(&id2));
2359 assert_eq!(i.next(), Some(&id1));
2360 assert_eq!(i.next(), None)
2361 }
2362
2363 #[test]
2364 fn euid_into_expr() {
2365 let e = EntityReference::Slot(None);
2366 assert_eq!(
2367 e.into_expr(SlotId::principal()),
2368 Expr::slot(SlotId::principal())
2369 );
2370 let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2371 assert_eq!(
2372 e.into_expr(SlotId::principal()),
2373 Expr::val(EntityUID::with_eid("eid"))
2374 );
2375 }
2376
2377 #[test]
2378 fn por_constraint_display() {
2379 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None));
2380 let s = t.display(PrincipalOrResource::Principal);
2381 assert_eq!(s, "principal == ?principal");
2382 let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2383 EntityUID::with_eid("test"),
2384 )));
2385 let s = t.display(PrincipalOrResource::Principal);
2386 assert_eq!(s, "principal == test_entity_type::\"test\"");
2387 }
2388
2389 #[test]
2390 fn unexpected_templates() {
2391 let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2392 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2393 expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2394 "expected a static policy, got a template containing the slot ?principal"
2395 )
2396 .help("try removing the template slot(s) from this policy")
2397 .exactly_one_underline("?principal")
2398 .build()
2399 );
2400 });
2401
2402 let policy_str =
2403 r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2404 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2405 expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2406 "expected a static policy, got a template containing the slot ?principal"
2407 )
2408 .help("try removing the template slot(s) from this policy")
2409 .exactly_one_underline("?principal")
2410 .build()
2411 );
2412 assert_eq!(e.len(), 2);
2413 });
2414 }
2415
2416 #[test]
2417 fn policy_to_expr() {
2418 let policy_str = r#"permit(principal is A, action, resource is B)
2419 when { 1 == 2 }
2420 unless { 2 == 1}
2421 when { 3 == 4}
2422 unless { 4 == 3};"#;
2423 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Ok(p) => {
2424 assert_eq!(ToString::to_string(&p.condition()), "(principal is A) && (true && ((resource is B) && ((1 == 2) && ((!(2 == 1)) && ((3 == 4) && (!(4 == 3)))))))");
2425 });
2426 }
2427
2428 #[cfg(feature = "tolerant-ast")]
2429 #[test]
2430 fn template_body_error_methods() {
2431 use std::str::FromStr;
2432
2433 let policy_id = PolicyID::from_string("error_policy");
2434 let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2435 let error_body = TemplateBody::TemplateBodyError(policy_id.clone(), error_loc.clone());
2436
2437 let expected_error = <ExprWithErrsBuilder as ExprBuilder>::new()
2438 .error(ParseErrors::singleton(ToASTError::new(
2439 ToASTErrorKind::ASTErrorNode,
2440 Some(Loc::new(0..1, "ASTErrorNode".into())),
2441 )))
2442 .unwrap();
2443
2444 assert_eq!(error_body.id(), &policy_id);
2446
2447 assert_eq!(error_body.loc(), error_loc.as_ref());
2449
2450 let new_policy_id = PolicyID::from_string("new_error_policy");
2452 let updated_error_body = error_body.new_id(new_policy_id.clone());
2453 assert_matches!(updated_error_body,
2454 TemplateBody::TemplateBodyError(id, loc) if id == new_policy_id && loc == error_loc
2455 );
2456
2457 assert_eq!(error_body.effect(), Effect::Forbid);
2459
2460 assert_eq!(
2462 error_body.annotation(&AnyId::from_str("test").unwrap()),
2463 None
2464 );
2465
2466 assert!(error_body.annotations().count() == 0);
2468
2469 assert_eq!(
2471 *error_body.principal_constraint(),
2472 PrincipalConstraint::any()
2473 );
2474
2475 assert_eq!(*error_body.action_constraint(), ActionConstraint::any());
2477
2478 assert_eq!(*error_body.resource_constraint(), ResourceConstraint::any());
2480
2481 assert_eq!(error_body.non_scope_constraints(), Some(&expected_error));
2483
2484 assert_eq!(error_body.condition(), expected_error);
2486
2487 let display_str = format!("{error_body}");
2489 assert!(display_str.contains("TemplateBodyError"));
2490 assert!(display_str.contains("error_policy"));
2491 }
2492
2493 #[cfg(feature = "tolerant-ast")]
2494 #[test]
2495 fn template_error_methods() {
2496 let policy_id = PolicyID::from_string("error_policy");
2497 let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2498 let error_template = Template::error(policy_id.clone(), error_loc.clone());
2499
2500 assert_eq!(error_template.id(), &policy_id);
2502
2503 assert!(error_template.slots().count() == 0);
2505
2506 assert_matches!(error_template.body,
2508 TemplateBody::TemplateBodyError(ref id, ref loc) if id == &policy_id && loc == &error_loc
2509 );
2510
2511 assert_eq!(
2513 error_template.principal_constraint(),
2514 &PrincipalConstraint::any()
2515 );
2516
2517 assert_eq!(*error_template.action_constraint(), ActionConstraint::any());
2519
2520 assert_eq!(
2522 *error_template.resource_constraint(),
2523 ResourceConstraint::any()
2524 );
2525
2526 assert_eq!(error_template.effect(), Effect::Forbid);
2528
2529 assert_eq!(
2531 error_template.condition(),
2532 DEFAULT_ERROR_EXPR.as_ref().clone()
2533 );
2534
2535 assert_eq!(error_template.loc(), error_loc.as_ref());
2537
2538 assert!(error_template.annotations().count() == 0);
2540
2541 let display_str = format!("{error_template}");
2543 assert!(display_str.contains("TemplateBody::TemplateBodyError"));
2544 assert!(display_str.contains(&policy_id.to_string()));
2545 }
2546}