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