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