Skip to main content

cedar_policy_core/ast/
policy.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::ast::*;
18use crate::parser::Loc;
19use annotation::{Annotation, Annotations};
20use educe::Educe;
21use itertools::Itertools;
22use linked_hash_map::LinkedHashMap;
23use miette::Diagnostic;
24use nonempty::{nonempty, NonEmpty};
25use serde::{Deserialize, Serialize};
26use smol_str::SmolStr;
27use std::{
28    collections::{HashMap, HashSet},
29    str::FromStr,
30    sync::Arc,
31};
32use thiserror::Error;
33
34#[cfg(feature = "wasm")]
35extern crate tsify;
36
37macro_rules! cfg_tolerant_ast {
38    ($($item:item)*) => {
39        $(
40            #[cfg(feature = "tolerant-ast")]
41            $item
42        )*
43    };
44}
45
46cfg_tolerant_ast! {
47    use super::expr_allows_errors::AstExprErrorKind;
48    use crate::ast::expr_allows_errors::ExprWithErrsBuilder;
49    use crate::expr_builder::ExprBuilder;
50    use crate::parser::err::ParseErrors;
51    use crate::parser::err::ToASTError;
52    use crate::parser::err::ToASTErrorKind;
53
54    static DEFAULT_ANNOTATIONS: std::sync::LazyLock<Arc<Annotations>> =
55        std::sync::LazyLock::new(|| Arc::new(Annotations::default()));
56
57    static DEFAULT_PRINCIPAL_CONSTRAINT: std::sync::LazyLock<PrincipalConstraint> =
58        std::sync::LazyLock::new(PrincipalConstraint::any);
59
60    static DEFAULT_RESOURCE_CONSTRAINT: std::sync::LazyLock<ResourceConstraint> =
61        std::sync::LazyLock::new(ResourceConstraint::any);
62
63    static DEFAULT_ACTION_CONSTRAINT: std::sync::LazyLock<ActionConstraint> =
64        std::sync::LazyLock::new(ActionConstraint::any);
65
66    #[cfg_attr(feature = "tolerant-ast", expect(clippy::unwrap_used, reason = "Infallible error type - can never fail"))]
67    static DEFAULT_ERROR_EXPR: std::sync::LazyLock<Arc<Expr>> = std::sync::LazyLock::new(|| {
68        // Non scope constraint expression of an Error policy should also be an error
69        // This const represents an error expression that is part of an Error policy
70        Arc::new(
71            <ExprWithErrsBuilder as ExprBuilder>::new()
72                .error(ParseErrors::singleton(ToASTError::new(
73                    ToASTErrorKind::ASTErrorNode,
74                    Some(Loc::new(0..1, "ASTErrorNode".into())),
75                )))
76                .unwrap(),
77        )
78    });
79}
80
81/// Top level structure for a policy template.
82/// Contains both the AST for template, and the list of open slots in the template.
83///
84/// Note that this "template" may have no slots, in which case this `Template` represents a static policy
85#[derive(Clone, Hash, Eq, PartialEq, Debug)]
86pub struct Template {
87    body: TemplateBody,
88    /// INVARIANT (slot cache correctness): This Vec must contain _all_ of the open slots in `body`
89    /// This is maintained by the only public constructors: `new()`, `new_shared()`, and `link_static_policy()`
90    ///
91    /// Note that `slots` may be empty, in which case this `Template` represents a static policy
92    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    /// Checks the invariant (slot cache correctness)
103    ///
104    /// This function is a no-op in release builds, but checks the invariant (and panics if it fails) in debug builds.
105    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    /// Construct a `Template` from its components
118    #[expect(
119        clippy::too_many_arguments,
120        reason = "policies just have this many components"
121    )]
122    pub fn new(
123        id: PolicyID,
124        loc: Option<Loc>,
125        annotations: Annotations,
126        effect: Effect,
127        principal_constraint: PrincipalConstraint,
128        action_constraint: ActionConstraint,
129        resource_constraint: ResourceConstraint,
130        non_scope_constraint: Option<Expr>,
131    ) -> Self {
132        let body = TemplateBody::new(
133            id,
134            loc,
135            annotations,
136            effect,
137            principal_constraint,
138            action_constraint,
139            resource_constraint,
140            non_scope_constraint,
141        );
142        // INVARIANT (slot cache correctness)
143        // This invariant is maintained in the body of the From impl
144        Template::from(body)
145    }
146
147    #[cfg(feature = "tolerant-ast")]
148    /// Generate a template representing a policy that is unparsable
149    pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
150        let body = TemplateBody::error(id, loc);
151        Template::from(body)
152    }
153
154    /// Construct a template from an expression/annotations that are already [`std::sync::Arc`] allocated
155    #[expect(
156        clippy::too_many_arguments,
157        reason = "policies just have this many components"
158    )]
159    pub fn new_shared(
160        id: PolicyID,
161        loc: Option<Loc>,
162        annotations: Arc<Annotations>,
163        effect: Effect,
164        principal_constraint: PrincipalConstraint,
165        action_constraint: ActionConstraint,
166        resource_constraint: ResourceConstraint,
167        non_scope_constraint: Option<Arc<Expr>>,
168    ) -> Self {
169        let body = TemplateBody::new_shared(
170            id,
171            loc,
172            annotations,
173            effect,
174            principal_constraint,
175            action_constraint,
176            resource_constraint,
177            non_scope_constraint,
178        );
179        // INVARIANT (slot cache correctness)
180        // This invariant is maintained in the body of the From impl
181        Template::from(body)
182    }
183
184    /// Get the principal constraint on the body
185    pub fn principal_constraint(&self) -> &PrincipalConstraint {
186        self.body.principal_constraint()
187    }
188
189    /// Get the action constraint on the body
190    pub fn action_constraint(&self) -> &ActionConstraint {
191        self.body.action_constraint()
192    }
193
194    /// Get the resource constraint on the body
195    pub fn resource_constraint(&self) -> &ResourceConstraint {
196        self.body.resource_constraint()
197    }
198
199    /// Get the non-scope constraint on the body
200    pub fn non_scope_constraints(&self) -> Option<&Expr> {
201        self.body.non_scope_constraints()
202    }
203
204    /// Get Arc to non-scope constraint on the body
205    pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
206        self.body.non_scope_constraints_arc()
207    }
208
209    /// Get the PolicyID of this template
210    pub fn id(&self) -> &PolicyID {
211        self.body.id()
212    }
213
214    /// Clone this Policy with a new ID
215    pub fn new_id(&self, id: PolicyID) -> Self {
216        Template {
217            body: self.body.new_id(id),
218            slots: self.slots.clone(),
219        }
220    }
221
222    /// Get the location of this policy
223    pub fn loc(&self) -> Option<&Loc> {
224        self.body.loc()
225    }
226
227    /// Get the `Effect` (`Permit` or `Deny`) of this template
228    pub fn effect(&self) -> Effect {
229        self.body.effect()
230    }
231
232    /// Get data from an annotation.
233    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
234        self.body.annotation(key)
235    }
236
237    /// Get all annotation data.
238    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
239        self.body.annotations()
240    }
241
242    /// Get [`Arc`] owning the annotation data.
243    pub fn annotations_arc(&self) -> &Arc<Annotations> {
244        self.body.annotations_arc()
245    }
246
247    /// Get the condition expression of this template.
248    ///
249    /// This will be a conjunction of the template's scope constraints (on
250    /// principal, resource, and action); the template's "when" conditions; and
251    /// the negation of each of the template's "unless" conditions.
252    pub fn condition(&self) -> Expr {
253        self.body.condition()
254    }
255
256    /// List of open slots in this template
257    pub fn slots(&self) -> impl Iterator<Item = &Slot> {
258        self.slots.iter()
259    }
260
261    /// Check if this template is a static policy
262    ///
263    /// Static policies can be linked without any slots,
264    /// and all links will be identical.
265    pub fn is_static(&self) -> bool {
266        self.slots.is_empty()
267    }
268
269    /// Ensure that every slot in the template is bound by values,
270    /// and that no extra values are bound in values
271    /// This upholds invariant (values total map)
272    pub fn check_binding(
273        template: &Template,
274        values: &HashMap<SlotId, EntityUID>,
275    ) -> Result<(), LinkingError> {
276        // Verify all slots bound
277        let unbound = template
278            .slots
279            .iter()
280            .filter(|slot| !values.contains_key(&slot.id))
281            .collect::<Vec<_>>();
282
283        let extra = values
284            .iter()
285            .filter_map(|(slot, _)| {
286                if !template
287                    .slots
288                    .iter()
289                    .any(|template_slot| template_slot.id == *slot)
290                {
291                    Some(slot)
292                } else {
293                    None
294                }
295            })
296            .collect::<Vec<_>>();
297
298        if unbound.is_empty() && extra.is_empty() {
299            Ok(())
300        } else {
301            Err(LinkingError::from_unbound_and_extras(
302                unbound.into_iter().map(|slot| slot.id),
303                extra.into_iter().copied(),
304            ))
305        }
306    }
307
308    /// Attempt to create a template-linked policy from this template.
309    /// This will fail if values for all open slots are not given.
310    /// `new_instance_id` is the `PolicyId` for the created template-linked policy.
311    pub fn link(
312        template: Arc<Template>,
313        new_id: PolicyID,
314        values: HashMap<SlotId, EntityUID>,
315    ) -> Result<Policy, LinkingError> {
316        // INVARIANT (policy total map) Relies on check_binding to uphold the invariant
317        Template::check_binding(&template, &values)
318            .map(|_| Policy::new(template, Some(new_id), values))
319    }
320
321    /// Take a static policy and create a template and a template-linked policy for it.
322    /// They will share the same ID
323    pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
324        let body: TemplateBody = p.into();
325        // INVARIANT (slot cache correctness):
326        // StaticPolicy by invariant (inline policy correctness)
327        // can have no slots, so it is safe to make `slots` the empty vec
328        let t = Arc::new(Self {
329            body,
330            slots: vec![],
331        });
332        t.check_invariant();
333        let p = Policy::new(Arc::clone(&t), None, HashMap::new());
334        (t, p)
335    }
336}
337
338impl From<TemplateBody> for Template {
339    fn from(body: TemplateBody) -> Self {
340        // INVARIANT: (slot cache correctness)
341        // Pull all the slots out of the template body's condition.
342        let slots = body.condition().slots().collect::<Vec<_>>();
343        Self { body, slots }
344    }
345}
346
347impl std::fmt::Display for Template {
348    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349        write!(f, "{}", self.body)
350    }
351}
352
353/// Errors linking templates
354#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
355pub enum LinkingError {
356    /// An error with the slot arguments provided
357    // INVARIANT: `unbound_values` and `extra_values` can't both be empty
358    #[error(fmt = describe_arity_error)]
359    ArityError {
360        /// Error for when some Slots were not provided values
361        unbound_values: Vec<SlotId>,
362        /// Error for when more values than Slots are provided
363        extra_values: Vec<SlotId>,
364    },
365
366    /// The attempted linking failed as the template did not exist.
367    #[error("failed to find a template with id `{id}`")]
368    NoSuchTemplate {
369        /// [`PolicyID`] of the template we failed to find
370        id: PolicyID,
371    },
372
373    /// The new instance conflicts with an existing [`PolicyID`].
374    #[error("template-linked policy id `{id}` conflicts with an existing policy id")]
375    PolicyIdConflict {
376        /// [`PolicyID`] where the conflict exists
377        id: PolicyID,
378    },
379}
380
381impl LinkingError {
382    fn from_unbound_and_extras(
383        unbound: impl Iterator<Item = SlotId>,
384        extra: impl Iterator<Item = SlotId>,
385    ) -> Self {
386        Self::ArityError {
387            unbound_values: unbound.collect(),
388            extra_values: extra.collect(),
389        }
390    }
391}
392
393fn describe_arity_error(
394    unbound_values: &[SlotId],
395    extra_values: &[SlotId],
396    fmt: &mut std::fmt::Formatter<'_>,
397) -> std::fmt::Result {
398    match (unbound_values.len(), extra_values.len()) {
399        #[expect(clippy::unreachable, reason = "0,0 case is not an error")]
400        (0,0) => unreachable!(),
401        (_unbound, 0) => write!(fmt, "the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
402        (0, _extra) => write!(fmt, "the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
403        (_unbound, _extra) => write!(fmt, "the following slots were not provided as arguments: {}. The following slots were provided as arguments, but did not exist in the template: {}", unbound_values.iter().join(","), extra_values.iter().join(",")),
404    }
405}
406
407/// A Policy that contains:
408///   - a pointer to its template
409///   - a link ID (unless it's a static policy)
410///   - the bound values for slots in the template
411///
412/// Policies are not serializable (due to the pointer), and can be serialized
413/// by converting to/from LiteralPolicy
414#[derive(Debug, Clone, Eq, PartialEq)]
415pub struct Policy {
416    /// Reference to the template
417    template: Arc<Template>,
418    /// Id of this link
419    ///
420    /// None in the case that this is an instance of a Static Policy
421    link: Option<PolicyID>,
422    // INVARIANT (values total map)
423    // All of the slots in `template` MUST be bound by `values`
424    //
425    /// values the slots are bound to.
426    /// The constructor `new` is only visible in this module,
427    /// so it is the responsibility of callers to maintain
428    values: HashMap<SlotId, EntityUID>,
429}
430
431impl Policy {
432    /// Link a policy to its template
433    /// INVARIANT (values total map):
434    /// `values` must bind every open slot in `template`
435    fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
436        #[cfg(debug_assertions)]
437        {
438            #[expect(
439                clippy::expect_used,
440                reason = "asserts (value total map invariant) which is justified at call sites"
441            )]
442            Template::check_binding(&template, &values).expect("(values total map) does not hold!");
443        }
444        Self {
445            template,
446            link: link_id,
447            values,
448        }
449    }
450
451    /// Build a policy with a given effect, given when clause, and unconstrained scope variables
452    pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID, loc: Option<Loc>) -> Self {
453        Self::from_when_clause_annos(
454            effect,
455            Arc::new(when),
456            id,
457            loc,
458            Arc::new(Annotations::default()),
459        )
460    }
461
462    /// Build a policy with a given effect, given when clause, and unconstrained scope variables
463    pub fn from_when_clause_annos(
464        effect: Effect,
465        when: Arc<Expr>,
466        id: PolicyID,
467        loc: Option<Loc>,
468        annotations: Arc<Annotations>,
469    ) -> Self {
470        let t = Template::new_shared(
471            id,
472            loc,
473            annotations,
474            effect,
475            PrincipalConstraint::any(),
476            ActionConstraint::any(),
477            ResourceConstraint::any(),
478            Some(when),
479        );
480        Self::new(Arc::new(t), None, SlotEnv::new())
481    }
482
483    /// Get pointer to the template for this policy
484    pub fn template(&self) -> &Template {
485        &self.template
486    }
487
488    /// Get pointer to the template for this policy, as an `Arc`
489    pub(crate) fn template_arc(&self) -> Arc<Template> {
490        Arc::clone(&self.template)
491    }
492
493    /// Get the effect (forbid or permit) of this policy.
494    pub fn effect(&self) -> Effect {
495        self.template.effect()
496    }
497
498    /// Get data from an annotation.
499    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
500        self.template.annotation(key)
501    }
502
503    /// Get all annotation data.
504    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
505        self.template.annotations()
506    }
507
508    /// Get [`Arc`] owning annotation data.
509    pub fn annotations_arc(&self) -> &Arc<Annotations> {
510        self.template.annotations_arc()
511    }
512
513    /// Get the principal constraint for this policy.
514    ///
515    /// By the invariant, this principal constraint will not contain
516    /// (unresolved) slots, so you will not get `EntityReference::Slot` anywhere
517    /// in it.
518    pub fn principal_constraint(&self) -> PrincipalConstraint {
519        let constraint = self.template.principal_constraint().clone();
520        match self.values.get(&SlotId::principal()) {
521            None => constraint,
522            Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
523        }
524    }
525
526    /// Get the action constraint for this policy.
527    pub fn action_constraint(&self) -> &ActionConstraint {
528        self.template.action_constraint()
529    }
530
531    /// Get the resource constraint for this policy.
532    ///
533    /// By the invariant, this resource constraint will not contain
534    /// (unresolved) slots, so you will not get `EntityReference::Slot` anywhere
535    /// in it.
536    pub fn resource_constraint(&self) -> ResourceConstraint {
537        let constraint = self.template.resource_constraint().clone();
538        match self.values.get(&SlotId::resource()) {
539            None => constraint,
540            Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
541        }
542    }
543
544    /// Get the non-scope constraints for the policy
545    pub fn non_scope_constraints(&self) -> Option<&Expr> {
546        self.template.non_scope_constraints()
547    }
548
549    /// Get the [`Arc`] owning non-scope constraints for the policy
550    pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
551        self.template.non_scope_constraints_arc()
552    }
553
554    /// Get the expression that represents this policy.
555    pub fn condition(&self) -> Expr {
556        self.template.condition()
557    }
558
559    /// Get the mapping from SlotIds to EntityUIDs for this policy. (This will
560    /// be empty for inline policies.)
561    pub fn env(&self) -> &SlotEnv {
562        &self.values
563    }
564
565    /// Get the ID of this policy.
566    pub fn id(&self) -> &PolicyID {
567        self.link.as_ref().unwrap_or_else(|| self.template.id())
568    }
569
570    /// Clone this policy or instance with a new ID
571    pub fn new_id(&self, id: PolicyID) -> Self {
572        match self.link {
573            None => Policy {
574                template: Arc::new(self.template.new_id(id)),
575                link: None,
576                values: self.values.clone(),
577            },
578            Some(_) => Policy {
579                template: self.template.clone(),
580                link: Some(id),
581                values: self.values.clone(),
582            },
583        }
584    }
585
586    /// Clone this template instance with a new ID for the template.
587    /// Returns `None` if this is a static policy.
588    pub(crate) fn new_template_id(self, id: PolicyID) -> Option<Self> {
589        self.link.map(|link| Policy {
590            template: Arc::new(self.template.new_id(id)),
591            link: Some(link),
592            values: self.values,
593        })
594    }
595
596    /// Get the location of this policy
597    pub fn loc(&self) -> Option<&Loc> {
598        self.template.loc()
599    }
600
601    /// Returns true if this policy is an inline policy
602    pub fn is_static(&self) -> bool {
603        self.link.is_none()
604    }
605
606    /// Returns all the unknown entities in the policy during evaluation
607    pub fn unknown_entities(&self) -> HashSet<EntityUID> {
608        self.condition()
609            .unknowns()
610            .filter_map(
611                |Unknown {
612                     name,
613                     type_annotation,
614                 }| {
615                    if matches!(type_annotation, Some(Type::Entity { .. })) {
616                        EntityUID::from_str(name.as_str()).ok()
617                    } else {
618                        None
619                    }
620                },
621            )
622            .collect()
623    }
624}
625
626impl std::fmt::Display for Policy {
627    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
628        if self.is_static() {
629            write!(f, "{}", self.template())
630        } else {
631            write!(
632                f,
633                "Template Instance of {}, slots: [{}]",
634                self.template().id(),
635                display_slot_env(self.env())
636            )
637        }
638    }
639}
640
641/// Map from Slot Ids to Entity UIDs which fill the slots
642pub type SlotEnv = HashMap<SlotId, EntityUID>;
643
644/// Represents either a static policy or a template linked policy.
645///
646/// Contains less rich information than `Policy`. In particular, this form is
647/// easier to convert to/from the Protobuf representation of a `Policy`, because
648/// it simply refers to the `Template` by its Id and does not contain a
649/// reference to the `Template` itself.
650#[derive(Debug, Clone, PartialEq, Eq)]
651pub struct LiteralPolicy {
652    /// ID of the template this policy is an instance of
653    template_id: PolicyID,
654    /// ID of this link.
655    /// This is `None` for static policies, and the static policy ID is defined
656    /// as the `template_id`
657    link_id: Option<PolicyID>,
658    /// Values of the slots
659    values: SlotEnv,
660}
661
662impl LiteralPolicy {
663    /// Create a `LiteralPolicy` representing a static policy with the given ID.
664    ///
665    /// The policy set should also contain a (zero-slot) `Template` with the given ID.
666    pub fn static_policy(template_id: PolicyID) -> Self {
667        Self {
668            template_id,
669            link_id: None,
670            values: SlotEnv::new(),
671        }
672    }
673
674    /// Create a `LiteralPolicy` representing a template-linked policy.
675    ///
676    /// The policy set should also contain the associated `Template`.
677    pub fn template_linked_policy(
678        template_id: PolicyID,
679        link_id: PolicyID,
680        values: SlotEnv,
681    ) -> Self {
682        Self {
683            template_id,
684            link_id: Some(link_id),
685            values,
686        }
687    }
688
689    /// Get the `EntityUID` associated with the given `SlotId`, if it exists
690    pub fn value(&self, slot: &SlotId) -> Option<&EntityUID> {
691        self.values.get(slot)
692    }
693}
694
695// Can we verify the hash property?
696
697impl std::hash::Hash for LiteralPolicy {
698    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
699        self.template_id.hash(state);
700        // this shouldn't be a performance issue as these vectors should be small
701        let mut buf = self.values.iter().collect::<Vec<_>>();
702        buf.sort();
703        for (id, euid) in buf {
704            id.hash(state);
705            euid.hash(state);
706        }
707    }
708}
709
710// These would be great as property tests
711#[cfg(test)]
712mod hashing_tests {
713    use std::{
714        collections::hash_map::DefaultHasher,
715        hash::{Hash, Hasher},
716    };
717
718    use super::*;
719
720    fn compute_hash(ir: &LiteralPolicy) -> u64 {
721        let mut s = DefaultHasher::new();
722        ir.hash(&mut s);
723        s.finish()
724    }
725
726    fn build_template_linked_policy() -> LiteralPolicy {
727        let mut map = HashMap::new();
728        map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
729        LiteralPolicy {
730            template_id: PolicyID::from_string("template"),
731            link_id: Some(PolicyID::from_string("id")),
732            values: map,
733        }
734    }
735
736    #[test]
737    fn hash_property_instances() {
738        let a = build_template_linked_policy();
739        let b = build_template_linked_policy();
740        assert_eq!(a, b);
741        assert_eq!(compute_hash(&a), compute_hash(&b));
742    }
743}
744
745/// Errors that can happen during policy reification
746#[derive(Debug, Diagnostic, Error)]
747pub enum ReificationError {
748    /// The [`PolicyID`] linked to did not exist
749    #[error("the id linked to does not exist")]
750    NoSuchTemplate(PolicyID),
751    /// Error linking the policy
752    #[error(transparent)]
753    #[diagnostic(transparent)]
754    Linking(#[from] LinkingError),
755}
756
757impl LiteralPolicy {
758    /// Attempt to reify this template linked policy.
759    /// Ensures the linked template actually exists, replaces the id with a reference to the underlying template.
760    /// Fails if the template does not exist.
761    /// Consumes the policy.
762    pub fn reify(
763        self,
764        templates: &LinkedHashMap<PolicyID, Arc<Template>>,
765    ) -> Result<Policy, ReificationError> {
766        let template = templates
767            .get(&self.template_id)
768            .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
769        // INVARIANT (values total map)
770        Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
771        Ok(Policy::new(template.clone(), self.link_id, self.values))
772    }
773
774    /// Lookup the euid bound by a SlotId
775    pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
776        self.values.get(id)
777    }
778
779    /// Get the [`PolicyID`] of this static or template-linked policy.
780    pub fn id(&self) -> &PolicyID {
781        self.link_id.as_ref().unwrap_or(&self.template_id)
782    }
783
784    /// Get the [`PolicyID`] of the template associated with this policy.
785    ///
786    /// For static policies, this is just the static policy ID.
787    pub fn template_id(&self) -> &PolicyID {
788        &self.template_id
789    }
790
791    /// Is this a static policy
792    pub fn is_static(&self) -> bool {
793        self.link_id.is_none()
794    }
795}
796
797fn display_slot_env(env: &SlotEnv) -> String {
798    env.iter()
799        .map(|(slot, value)| format!("{slot} -> {value}"))
800        .join(",")
801}
802
803impl std::fmt::Display for LiteralPolicy {
804    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
805        if self.is_static() {
806            write!(f, "Static policy w/ ID {}", self.template_id())
807        } else {
808            write!(
809                f,
810                "Template linked policy of {}, slots: [{}]",
811                self.template_id(),
812                display_slot_env(&self.values),
813            )
814        }
815    }
816}
817
818impl From<Policy> for LiteralPolicy {
819    fn from(p: Policy) -> Self {
820        Self {
821            template_id: p.template.id().clone(),
822            link_id: p.link,
823            values: p.values,
824        }
825    }
826}
827
828/// Static Policies are policy that do not come from templates.
829/// They have the same structure as a template definition, but cannot contain slots
830// INVARIANT: (Static Policy Correctness): A Static Policy TemplateBody must have zero slots
831#[derive(Clone, Hash, Eq, PartialEq, Debug)]
832pub struct StaticPolicy(TemplateBody);
833
834impl StaticPolicy {
835    /// Get the `Id` of this policy.
836    pub fn id(&self) -> &PolicyID {
837        self.0.id()
838    }
839
840    /// Clone this policy with a new `Id`.
841    pub fn new_id(&self, id: PolicyID) -> Self {
842        StaticPolicy(self.0.new_id(id))
843    }
844
845    /// Get the location of this policy
846    pub fn loc(&self) -> Option<&Loc> {
847        self.0.loc()
848    }
849
850    /// Get the `Effect` of this policy.
851    pub fn effect(&self) -> Effect {
852        self.0.effect()
853    }
854
855    /// Get data from an annotation.
856    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
857        self.0.annotation(key)
858    }
859
860    /// Get all annotation data.
861    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
862        self.0.annotations()
863    }
864
865    /// Get the `principal` scope constraint of this policy.
866    pub fn principal_constraint(&self) -> &PrincipalConstraint {
867        self.0.principal_constraint()
868    }
869
870    /// Get the `principal` scope constraint as an expression.
871    /// This will be a boolean-valued expression: either `true` (if the policy
872    /// just has `principal,`), or an equality or hierarchy constraint
873    pub fn principal_constraint_expr(&self) -> Expr {
874        self.0.principal_constraint_expr()
875    }
876
877    /// Get the `action` scope constraint of this policy.
878    pub fn action_constraint(&self) -> &ActionConstraint {
879        self.0.action_constraint()
880    }
881
882    /// Get the `action` scope constraint of this policy as an expression.
883    /// This will be a boolean-valued expression: either `true` (if the policy
884    /// just has `action,`), or an equality or hierarchy constraint
885    pub fn action_constraint_expr(&self) -> Expr {
886        self.0.action_constraint_expr()
887    }
888
889    /// Get the `resource` scope constraint of this policy.
890    pub fn resource_constraint(&self) -> &ResourceConstraint {
891        self.0.resource_constraint()
892    }
893
894    /// Get the `resource` scope constraint of this policy as an expression.
895    /// This will be a boolean-valued expression: either `true` (if the policy
896    /// just has `resource,`), or an equality or hierarchy constraint
897    pub fn resource_constraint_expr(&self) -> Expr {
898        self.0.resource_constraint_expr()
899    }
900
901    /// Get the non-scope constraints of this policy.
902    ///
903    /// This will be a conjunction of the policy's `when` conditions and the
904    /// negation of each of the policy's `unless` conditions.
905    pub fn non_scope_constraints(&self) -> Option<&Expr> {
906        self.0.non_scope_constraints()
907    }
908
909    /// Get the condition expression of this policy.
910    ///
911    /// This will be a conjunction of the policy's scope constraints (on
912    /// principal, resource, and action); the policy's "when" conditions; and
913    /// the negation of each of the policy's "unless" conditions.
914    pub fn condition(&self) -> Expr {
915        self.0.condition()
916    }
917
918    /// Construct a `StaticPolicy` from its components
919    #[expect(
920        clippy::too_many_arguments,
921        reason = "policies just have this many components"
922    )]
923    pub fn new(
924        id: PolicyID,
925        loc: Option<Loc>,
926        annotations: Annotations,
927        effect: Effect,
928        principal_constraint: PrincipalConstraint,
929        action_constraint: ActionConstraint,
930        resource_constraint: ResourceConstraint,
931        non_scope_constraints: Option<Expr>,
932    ) -> Result<Self, UnexpectedSlotError> {
933        let body = TemplateBody::new(
934            id,
935            loc,
936            annotations,
937            effect,
938            principal_constraint,
939            action_constraint,
940            resource_constraint,
941            non_scope_constraints,
942        );
943        let first_slot = body.condition().slots().next();
944        // INVARIANT (static policy correctness), checks that no slots exists
945        match first_slot {
946            Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
947            None => Ok(Self(body)),
948        }
949    }
950}
951
952impl TryFrom<Template> for StaticPolicy {
953    type Error = UnexpectedSlotError;
954
955    fn try_from(value: Template) -> Result<Self, Self::Error> {
956        // INVARIANT (Static policy correctness): Must ensure StaticPolicy contains no slots
957        let o = value.slots().next();
958        match o {
959            Some(slot_id) => Err(Self::Error::FoundSlot(slot_id.clone())),
960            None => Ok(Self(value.body)),
961        }
962    }
963}
964
965impl From<StaticPolicy> for Policy {
966    fn from(p: StaticPolicy) -> Policy {
967        let (_, policy) = Template::link_static_policy(p);
968        policy
969    }
970}
971
972impl From<StaticPolicy> for Arc<Template> {
973    fn from(p: StaticPolicy) -> Self {
974        let (t, _) = Template::link_static_policy(p);
975        t
976    }
977}
978
979/// Policy datatype. This is used for both templates (in which case it contains
980/// slots) and static policies (in which case it contains zero slots).
981#[derive(Educe, Clone, Debug)]
982#[educe(PartialEq, Eq, Hash)]
983pub struct TemplateBodyImpl {
984    /// ID of this policy
985    id: PolicyID,
986    /// Source location spanning the entire policy
987    #[educe(PartialEq(ignore))]
988    #[educe(Hash(ignore))]
989    loc: Option<Loc>,
990    /// Annotations available for external applications, as key-value store.
991    /// Note that the keys are `AnyId`, so Cedar reserved words like `if` and `has`
992    /// are explicitly allowed as annotations.
993    annotations: Arc<Annotations>,
994    /// `Effect` of this policy
995    effect: Effect,
996    /// Scope constraint for principal. This will be a boolean-valued expression:
997    /// either `true` (if the policy just has `principal,`), or an equality or
998    /// hierarchy constraint
999    principal_constraint: PrincipalConstraint,
1000    /// Scope constraint for action. This will be a boolean-valued expression:
1001    /// either `true` (if the policy just has `action,`), or an equality or
1002    /// hierarchy constraint
1003    action_constraint: ActionConstraint,
1004    /// Scope constraint for resource. This will be a boolean-valued expression:
1005    /// either `true` (if the policy just has `resource,`), or an equality or
1006    /// hierarchy constraint
1007    resource_constraint: ResourceConstraint,
1008    /// Conjunction of all of the non-scope constraints in the policy.
1009    ///
1010    /// This will be a conjunction of the policy's `when` conditions and the
1011    /// negation of each of the policy's `unless` conditions.
1012    non_scope_constraints: Option<Arc<Expr>>,
1013}
1014
1015/// Policy datatype. This is used for both templates (in which case it contains
1016/// slots) and static policies (in which case it contains zero slots).
1017#[derive(Clone, Hash, Eq, PartialEq, Debug)]
1018pub enum TemplateBody {
1019    /// Represents a valid template body
1020    TemplateBody(TemplateBodyImpl),
1021    #[cfg(feature = "tolerant-ast")]
1022    /// Represents a policy that failed to parse
1023    TemplateBodyError(PolicyID, Option<Loc>),
1024}
1025
1026impl TemplateBody {
1027    /// Get the `Id` of this policy.
1028    pub fn id(&self) -> &PolicyID {
1029        match self {
1030            TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => id,
1031            #[cfg(feature = "tolerant-ast")]
1032            TemplateBody::TemplateBodyError(id, _) => id,
1033        }
1034    }
1035
1036    /// Get the location of this policy
1037    pub fn loc(&self) -> Option<&Loc> {
1038        match self {
1039            TemplateBody::TemplateBody(TemplateBodyImpl { loc, .. }) => loc.as_ref(),
1040            #[cfg(feature = "tolerant-ast")]
1041            TemplateBody::TemplateBodyError(_, loc) => loc.as_ref(),
1042        }
1043    }
1044
1045    /// Clone this policy with a new `Id`.
1046    pub fn new_id(&self, id: PolicyID) -> Self {
1047        match self {
1048            TemplateBody::TemplateBody(t) => {
1049                let mut new = t.clone();
1050                new.id = id;
1051                TemplateBody::TemplateBody(new)
1052            }
1053            #[cfg(feature = "tolerant-ast")]
1054            TemplateBody::TemplateBodyError(_, loc) => {
1055                TemplateBody::TemplateBodyError(id, loc.clone())
1056            }
1057        }
1058    }
1059
1060    #[cfg(feature = "tolerant-ast")]
1061    /// Create a template body representing a policy that failed to parse
1062    pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
1063        TemplateBody::TemplateBodyError(id, loc)
1064    }
1065
1066    /// Get the `Effect` of this policy.
1067    pub fn effect(&self) -> Effect {
1068        match self {
1069            TemplateBody::TemplateBody(TemplateBodyImpl { effect, .. }) => *effect,
1070            #[cfg(feature = "tolerant-ast")]
1071            TemplateBody::TemplateBodyError(_, _) => Effect::Forbid,
1072        }
1073    }
1074
1075    /// Get data from an annotation.
1076    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
1077        match self {
1078            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => {
1079                annotations.get(key)
1080            }
1081            #[cfg(feature = "tolerant-ast")]
1082            TemplateBody::TemplateBodyError(_, _) => None,
1083        }
1084    }
1085
1086    /// Get shared ref to annotations
1087    pub fn annotations_arc(&self) -> &Arc<Annotations> {
1088        match self {
1089            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations,
1090            #[cfg(feature = "tolerant-ast")]
1091            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ANNOTATIONS,
1092        }
1093    }
1094
1095    /// Get all annotation data.
1096    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1097        match self {
1098            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations.iter(),
1099            #[cfg(feature = "tolerant-ast")]
1100            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ANNOTATIONS.iter(),
1101        }
1102    }
1103
1104    /// Get the `principal` scope constraint of this policy.
1105    pub fn principal_constraint(&self) -> &PrincipalConstraint {
1106        match self {
1107            TemplateBody::TemplateBody(TemplateBodyImpl {
1108                principal_constraint,
1109                ..
1110            }) => principal_constraint,
1111            #[cfg(feature = "tolerant-ast")]
1112            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_PRINCIPAL_CONSTRAINT,
1113        }
1114
1115        // &self.principal_constraint
1116    }
1117
1118    /// Get the `principal` scope constraint as an expression.
1119    /// This will be a boolean-valued expression: either `true` (if the policy
1120    /// just has `principal,`), or an equality or hierarchy constraint
1121    pub fn principal_constraint_expr(&self) -> Expr {
1122        match self {
1123            TemplateBody::TemplateBody(TemplateBodyImpl {
1124                principal_constraint,
1125                ..
1126            }) => principal_constraint.as_expr(),
1127            #[cfg(feature = "tolerant-ast")]
1128            TemplateBody::TemplateBodyError(_, _) => DEFAULT_PRINCIPAL_CONSTRAINT.as_expr(),
1129        }
1130    }
1131
1132    /// Get the `action` scope constraint of this policy.
1133    pub fn action_constraint(&self) -> &ActionConstraint {
1134        match self {
1135            TemplateBody::TemplateBody(TemplateBodyImpl {
1136                action_constraint, ..
1137            }) => action_constraint,
1138            #[cfg(feature = "tolerant-ast")]
1139            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ACTION_CONSTRAINT,
1140        }
1141    }
1142
1143    /// Get the `action` scope constraint of this policy as an expression.
1144    /// This will be a boolean-valued expression: either `true` (if the policy
1145    /// just has `action,`), or an equality or hierarchy constraint
1146    pub fn action_constraint_expr(&self) -> Expr {
1147        match self {
1148            TemplateBody::TemplateBody(TemplateBodyImpl {
1149                action_constraint, ..
1150            }) => action_constraint.as_expr(),
1151            #[cfg(feature = "tolerant-ast")]
1152            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ACTION_CONSTRAINT.as_expr(),
1153        }
1154    }
1155
1156    /// Get the `resource` scope constraint of this policy.
1157    pub fn resource_constraint(&self) -> &ResourceConstraint {
1158        match self {
1159            TemplateBody::TemplateBody(TemplateBodyImpl {
1160                resource_constraint,
1161                ..
1162            }) => resource_constraint,
1163            #[cfg(feature = "tolerant-ast")]
1164            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_RESOURCE_CONSTRAINT,
1165        }
1166    }
1167
1168    /// Get the `resource` scope constraint of this policy as an expression.
1169    /// This will be a boolean-valued expression: either `true` (if the policy
1170    /// just has `resource,`), or an equality or hierarchy constraint
1171    pub fn resource_constraint_expr(&self) -> Expr {
1172        match self {
1173            TemplateBody::TemplateBody(TemplateBodyImpl {
1174                resource_constraint,
1175                ..
1176            }) => resource_constraint.as_expr(),
1177            #[cfg(feature = "tolerant-ast")]
1178            TemplateBody::TemplateBodyError(_, _) => DEFAULT_RESOURCE_CONSTRAINT.as_expr(),
1179        }
1180    }
1181
1182    /// Get the non-scope constraints of this policy.
1183    ///
1184    /// This will be a conjunction of the policy's `when` conditions and the
1185    /// negation of each of the policy's `unless` conditions.
1186    pub fn non_scope_constraints(&self) -> Option<&Expr> {
1187        match self {
1188            TemplateBody::TemplateBody(TemplateBodyImpl {
1189                non_scope_constraints,
1190                ..
1191            }) => non_scope_constraints.as_ref().map(|e| e.as_ref()),
1192            #[cfg(feature = "tolerant-ast")]
1193            TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1194        }
1195    }
1196
1197    /// Get the Arc owning the non scope constraints
1198    pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
1199        match self {
1200            TemplateBody::TemplateBody(TemplateBodyImpl {
1201                non_scope_constraints,
1202                ..
1203            }) => non_scope_constraints.as_ref(),
1204            #[cfg(feature = "tolerant-ast")]
1205            TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1206        }
1207    }
1208
1209    /// Get the condition expression of this policy.
1210    ///
1211    /// This will be a conjunction of the policy's scope constraints (on
1212    /// principal, resource, and action); the policy's "when" conditions; and
1213    /// the negation of each of the policy's "unless" conditions.
1214    pub fn condition(&self) -> Expr {
1215        match self {
1216            TemplateBody::TemplateBody(TemplateBodyImpl { .. }) => {
1217                let loc = self.loc().cloned();
1218                Expr::and(
1219                    self.principal_constraint_expr(),
1220                    Expr::and(
1221                        self.action_constraint_expr(),
1222                        Expr::and(
1223                            self.resource_constraint_expr(),
1224                            self.non_scope_constraints()
1225                                .cloned()
1226                                .unwrap_or_else(|| Expr::val(true)),
1227                        )
1228                        .with_maybe_source_loc(loc.clone()),
1229                    )
1230                    .with_maybe_source_loc(loc.clone()),
1231                )
1232                .with_maybe_source_loc(loc)
1233            }
1234            #[cfg(feature = "tolerant-ast")]
1235            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ERROR_EXPR.as_ref().clone(),
1236        }
1237    }
1238
1239    /// Construct a `Policy` from components that are already [`std::sync::Arc`] allocated
1240    #[expect(
1241        clippy::too_many_arguments,
1242        reason = "policies just have this many components"
1243    )]
1244    pub fn new_shared(
1245        id: PolicyID,
1246        loc: Option<Loc>,
1247        annotations: Arc<Annotations>,
1248        effect: Effect,
1249        principal_constraint: PrincipalConstraint,
1250        action_constraint: ActionConstraint,
1251        resource_constraint: ResourceConstraint,
1252        non_scope_constraints: Option<Arc<Expr>>,
1253    ) -> Self {
1254        Self::TemplateBody(TemplateBodyImpl {
1255            id,
1256            loc,
1257            annotations,
1258            effect,
1259            principal_constraint,
1260            action_constraint,
1261            resource_constraint,
1262            non_scope_constraints,
1263        })
1264    }
1265
1266    /// Construct a `Policy` from its components
1267    #[expect(
1268        clippy::too_many_arguments,
1269        reason = "policies just have this many components"
1270    )]
1271    pub fn new(
1272        id: PolicyID,
1273        loc: Option<Loc>,
1274        annotations: Annotations,
1275        effect: Effect,
1276        principal_constraint: PrincipalConstraint,
1277        action_constraint: ActionConstraint,
1278        resource_constraint: ResourceConstraint,
1279        non_scope_constraints: Option<Expr>,
1280    ) -> Self {
1281        Self::TemplateBody(TemplateBodyImpl {
1282            id,
1283            loc,
1284            annotations: Arc::new(annotations),
1285            effect,
1286            principal_constraint,
1287            action_constraint,
1288            resource_constraint,
1289            non_scope_constraints: non_scope_constraints.map(Arc::new),
1290        })
1291    }
1292}
1293
1294impl From<StaticPolicy> for TemplateBody {
1295    fn from(p: StaticPolicy) -> Self {
1296        p.0
1297    }
1298}
1299
1300impl std::fmt::Display for TemplateBody {
1301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1302        match self {
1303            TemplateBody::TemplateBody(template_body_impl) => {
1304                template_body_impl.annotations.fmt(f)?;
1305                write!(
1306                    f,
1307                    "{}(\n  {},\n  {},\n  {}\n)",
1308                    self.effect(),
1309                    self.principal_constraint(),
1310                    self.action_constraint(),
1311                    self.resource_constraint(),
1312                )?;
1313                if let Some(non_scope_constraints) = self.non_scope_constraints() {
1314                    write!(f, " when {{\n  {non_scope_constraints}\n}};")
1315                } else {
1316                    write!(f, ";")
1317                }
1318            }
1319            #[cfg(feature = "tolerant-ast")]
1320            TemplateBody::TemplateBodyError(policy_id, _) => {
1321                write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1322            }
1323        }
1324    }
1325}
1326
1327/// Template constraint on principal scope variables
1328#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1329pub struct PrincipalConstraint {
1330    pub(crate) constraint: PrincipalOrResourceConstraint,
1331}
1332
1333impl PrincipalConstraint {
1334    /// Construct a principal constraint
1335    pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1336        PrincipalConstraint { constraint }
1337    }
1338
1339    /// Get constraint as ref
1340    pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1341        &self.constraint
1342    }
1343
1344    /// Get constraint by value
1345    pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1346        self.constraint
1347    }
1348
1349    /// Get the constraint as raw AST
1350    pub fn as_expr(&self) -> Expr {
1351        self.constraint.as_expr(PrincipalOrResource::Principal)
1352    }
1353
1354    /// Unconstrained.
1355    pub fn any() -> Self {
1356        PrincipalConstraint {
1357            constraint: PrincipalOrResourceConstraint::any(),
1358        }
1359    }
1360
1361    /// Constrained to equal a specific euid.
1362    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1363        PrincipalConstraint {
1364            constraint: PrincipalOrResourceConstraint::is_eq(euid),
1365        }
1366    }
1367
1368    /// Constrained to be equal to a slot
1369    pub fn is_eq_slot() -> Self {
1370        Self {
1371            constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1372        }
1373    }
1374
1375    /// Hierarchical constraint.
1376    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1377        PrincipalConstraint {
1378            constraint: PrincipalOrResourceConstraint::is_in(euid),
1379        }
1380    }
1381
1382    /// Hierarchical constraint to Slot
1383    pub fn is_in_slot() -> Self {
1384        Self {
1385            constraint: PrincipalOrResourceConstraint::is_in_slot(),
1386        }
1387    }
1388
1389    /// Type constraint additionally constrained to be in a slot.
1390    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1391        Self {
1392            constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1393        }
1394    }
1395
1396    /// Type constraint, with a hierarchical constraint.
1397    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1398        Self {
1399            constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1400        }
1401    }
1402
1403    /// Type constraint, with no hierarchical constraint or slot.
1404    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1405        Self {
1406            constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1407        }
1408    }
1409
1410    /// Fill in the Slot, if any, with the given EUID
1411    pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1412        match self.constraint {
1413            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1414                constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1415            },
1416            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1417                constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1418            },
1419            PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1420                constraint: PrincipalOrResourceConstraint::IsIn(
1421                    entity_type,
1422                    EntityReference::EUID(euid),
1423                ),
1424            },
1425            // enumerated so it is clearer what other variants there are
1426            PrincipalOrResourceConstraint::Is(_)
1427            | PrincipalOrResourceConstraint::Any
1428            | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1429            | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1430            | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1431        }
1432    }
1433}
1434
1435impl std::fmt::Display for PrincipalConstraint {
1436    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1437        write!(
1438            f,
1439            "{}",
1440            self.constraint.display(PrincipalOrResource::Principal)
1441        )
1442    }
1443}
1444
1445/// Template constraint on resource scope variables
1446#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1447pub struct ResourceConstraint {
1448    pub(crate) constraint: PrincipalOrResourceConstraint,
1449}
1450
1451impl ResourceConstraint {
1452    /// Construct from constraint
1453    pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1454        ResourceConstraint { constraint }
1455    }
1456
1457    /// Get constraint as ref
1458    pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1459        &self.constraint
1460    }
1461
1462    /// Get constraint by value
1463    pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1464        self.constraint
1465    }
1466
1467    /// Convert into an Expression. It will be a boolean valued expression.
1468    pub fn as_expr(&self) -> Expr {
1469        self.constraint.as_expr(PrincipalOrResource::Resource)
1470    }
1471
1472    /// Unconstrained.
1473    pub fn any() -> Self {
1474        ResourceConstraint {
1475            constraint: PrincipalOrResourceConstraint::any(),
1476        }
1477    }
1478
1479    /// Constrained to equal a specific euid.
1480    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1481        ResourceConstraint {
1482            constraint: PrincipalOrResourceConstraint::is_eq(euid),
1483        }
1484    }
1485
1486    /// Constrained to equal a slot.
1487    pub fn is_eq_slot() -> Self {
1488        Self {
1489            constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1490        }
1491    }
1492
1493    /// Constrained to be in a slot
1494    pub fn is_in_slot() -> Self {
1495        Self {
1496            constraint: PrincipalOrResourceConstraint::is_in_slot(),
1497        }
1498    }
1499
1500    /// Hierarchical constraint.
1501    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1502        ResourceConstraint {
1503            constraint: PrincipalOrResourceConstraint::is_in(euid),
1504        }
1505    }
1506
1507    /// Type constraint additionally constrained to be in a slot.
1508    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1509        Self {
1510            constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1511        }
1512    }
1513
1514    /// Type constraint, with a hierarchical constraint.
1515    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1516        Self {
1517            constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1518        }
1519    }
1520
1521    /// Type constraint, with no hierarchical constraint or slot.
1522    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1523        Self {
1524            constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1525        }
1526    }
1527
1528    /// Fill in the Slot, if any, with the given EUID
1529    pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1530        match self.constraint {
1531            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1532                constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1533            },
1534            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1535                constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1536            },
1537            PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1538                constraint: PrincipalOrResourceConstraint::IsIn(
1539                    entity_type,
1540                    EntityReference::EUID(euid),
1541                ),
1542            },
1543            // enumerated so it is clearer what other variants there are
1544            PrincipalOrResourceConstraint::Is(_)
1545            | PrincipalOrResourceConstraint::Any
1546            | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1547            | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1548            | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1549        }
1550    }
1551}
1552
1553impl std::fmt::Display for ResourceConstraint {
1554    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1555        write!(
1556            f,
1557            "{}",
1558            self.as_inner().display(PrincipalOrResource::Resource)
1559        )
1560    }
1561}
1562
1563/// A reference to an EntityUID that may be a Slot
1564#[derive(Educe, Clone, Debug, Eq)]
1565#[educe(Hash, PartialEq, PartialOrd, Ord)]
1566pub enum EntityReference {
1567    /// Reference to a literal EUID
1568    EUID(Arc<EntityUID>),
1569    /// Template Slot
1570    Slot(
1571        #[educe(PartialEq(ignore))]
1572        #[educe(PartialOrd(ignore))]
1573        #[educe(Hash(ignore))]
1574        Option<Loc>,
1575    ),
1576}
1577
1578impl EntityReference {
1579    /// Create an entity reference to a specific EntityUID
1580    pub fn euid(euid: Arc<EntityUID>) -> Self {
1581        Self::EUID(euid)
1582    }
1583
1584    /// Transform into an expression AST
1585    ///
1586    /// `slot` indicates what `SlotId` would be implied by
1587    /// `EntityReference::Slot`, which is always clear from the caller's
1588    /// context.
1589    pub fn into_expr(&self, slot: SlotId) -> Expr {
1590        match self {
1591            EntityReference::EUID(euid) => Expr::val(euid.clone()),
1592            EntityReference::Slot(loc) => Expr::slot(slot).with_maybe_source_loc(loc.clone()),
1593        }
1594    }
1595}
1596
1597/// Error for unexpected slots
1598#[derive(Debug, Clone, PartialEq, Eq, Error)]
1599pub enum UnexpectedSlotError {
1600    /// Found this slot where slots are not allowed
1601    #[error("found slot `{}` where slots are not allowed", .0.id)]
1602    FoundSlot(Slot),
1603}
1604
1605impl Diagnostic for UnexpectedSlotError {
1606    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1607        match self {
1608            Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1609                let label = miette::LabeledSpan::underline(loc.span);
1610                Box::new(std::iter::once(label)) as _
1611            }),
1612        }
1613    }
1614
1615    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
1616        match self {
1617            Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|l| l as &dyn miette::SourceCode),
1618        }
1619    }
1620}
1621
1622impl From<EntityUID> for EntityReference {
1623    fn from(euid: EntityUID) -> Self {
1624        Self::EUID(Arc::new(euid))
1625    }
1626}
1627
1628/// Subset of AST variables that have the same constraint form
1629#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1630#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1631pub enum PrincipalOrResource {
1632    /// The principal of a request
1633    Principal,
1634    /// The resource of a request
1635    Resource,
1636}
1637
1638impl std::fmt::Display for PrincipalOrResource {
1639    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1640        let v = Var::from(*self);
1641        write!(f, "{v}")
1642    }
1643}
1644
1645impl TryFrom<Var> for PrincipalOrResource {
1646    type Error = Var;
1647
1648    fn try_from(value: Var) -> Result<Self, Self::Error> {
1649        match value {
1650            Var::Principal => Ok(Self::Principal),
1651            Var::Action => Err(Var::Action),
1652            Var::Resource => Ok(Self::Resource),
1653            Var::Context => Err(Var::Context),
1654        }
1655    }
1656}
1657
1658/// Represents the constraints for principals and resources.
1659/// Can either not constrain, or constrain via `==` or `in` for a single entity literal.
1660#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1661pub enum PrincipalOrResourceConstraint {
1662    /// Unconstrained
1663    Any,
1664    /// Hierarchical constraint
1665    In(EntityReference),
1666    /// Equality constraint
1667    Eq(EntityReference),
1668    /// Type constraint,
1669    Is(Arc<EntityType>),
1670    /// Type constraint with a hierarchy constraint
1671    IsIn(Arc<EntityType>, EntityReference),
1672}
1673
1674impl PrincipalOrResourceConstraint {
1675    /// Unconstrained.
1676    pub fn any() -> Self {
1677        PrincipalOrResourceConstraint::Any
1678    }
1679
1680    /// Constrained to equal a specific euid.
1681    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1682        PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1683    }
1684
1685    /// Constrained to equal a slot
1686    pub fn is_eq_slot() -> Self {
1687        PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None))
1688    }
1689
1690    /// Constrained to be in a slot
1691    pub fn is_in_slot() -> Self {
1692        PrincipalOrResourceConstraint::In(EntityReference::Slot(None))
1693    }
1694
1695    /// Hierarchical constraint.
1696    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1697        PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1698    }
1699
1700    /// Type constraint additionally constrained to be in a slot.
1701    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1702        PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None))
1703    }
1704
1705    /// Type constraint with a hierarchical constraint.
1706    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1707        PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1708    }
1709
1710    /// Type constraint, with no hierarchical constraint or slot.
1711    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1712        PrincipalOrResourceConstraint::Is(entity_type)
1713    }
1714
1715    /// Turn the constraint into an expr
1716    /// # arguments
1717    /// * `v` - The variable name to be used in the expression.
1718    pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1719        match self {
1720            PrincipalOrResourceConstraint::Any => Expr::val(true),
1721            PrincipalOrResourceConstraint::Eq(euid) => {
1722                Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1723            }
1724            PrincipalOrResourceConstraint::In(euid) => {
1725                Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1726            }
1727            PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1728                Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1729                Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1730            ),
1731            PrincipalOrResourceConstraint::Is(entity_type) => {
1732                Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1733            }
1734        }
1735    }
1736
1737    /// Pretty print the constraint
1738    /// # arguments
1739    /// * `v` - The variable name to be used in the expression.
1740    pub fn display(&self, v: PrincipalOrResource) -> String {
1741        match self {
1742            PrincipalOrResourceConstraint::In(euid) => {
1743                format!("{} in {}", v, euid.into_expr(v.into()))
1744            }
1745            PrincipalOrResourceConstraint::Eq(euid) => {
1746                format!("{} == {}", v, euid.into_expr(v.into()))
1747            }
1748            PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1749                format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1750            }
1751            PrincipalOrResourceConstraint::Is(entity_type) => {
1752                format!("{v} is {entity_type}")
1753            }
1754            PrincipalOrResourceConstraint::Any => format!("{v}"),
1755        }
1756    }
1757
1758    /// Get the entity uid in this constraint or `None` if there are no uids in the constraint
1759    pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1760        match self {
1761            PrincipalOrResourceConstraint::Any => None,
1762            PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1763            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None,
1764            PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1765            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None,
1766            PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1767            PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None,
1768            PrincipalOrResourceConstraint::Is(_) => None,
1769        }
1770    }
1771
1772    /// Get an iterator over all of the entity type names in this constraint.
1773    pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1774        self.get_euid()
1775            .into_iter()
1776            .map(|euid| euid.entity_type())
1777            .chain(match self {
1778                PrincipalOrResourceConstraint::Is(entity_type)
1779                | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1780                _ => None,
1781            })
1782    }
1783}
1784
1785/// Constraint for action scope variables.
1786/// Action variables can be constrained to be in any variable in a list.
1787#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1788pub enum ActionConstraint {
1789    /// Unconstrained
1790    Any,
1791    /// Constrained to being in a list.
1792    In(Vec<Arc<EntityUID>>),
1793    /// Constrained to equal a specific euid.
1794    Eq(Arc<EntityUID>),
1795    #[cfg(feature = "tolerant-ast")]
1796    /// Error node representing an action constraint that failed to parse
1797    ErrorConstraint,
1798}
1799
1800impl std::fmt::Display for ActionConstraint {
1801    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1802        let render_euids =
1803            |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1804        match self {
1805            ActionConstraint::Any => write!(f, "action"),
1806            ActionConstraint::In(euids) => {
1807                write!(f, "action in [{}]", render_euids(euids))
1808            }
1809            ActionConstraint::Eq(euid) => write!(f, "action == {euid}"),
1810            #[cfg(feature = "tolerant-ast")]
1811            ActionConstraint::ErrorConstraint => write!(f, "<invalid_action_constraint>"),
1812        }
1813    }
1814}
1815
1816impl ActionConstraint {
1817    /// Unconstrained action.
1818    pub fn any() -> Self {
1819        ActionConstraint::Any
1820    }
1821
1822    /// Action constrained to being in a list of euids.
1823    pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1824        ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1825    }
1826
1827    /// Action constrained to being equal to a euid.
1828    pub fn is_eq(euid: EntityUID) -> Self {
1829        ActionConstraint::Eq(Arc::new(euid))
1830    }
1831
1832    fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1833        Expr::set(euids.into_iter().map(Expr::val))
1834    }
1835
1836    /// Turn the constraint into an expression.
1837    pub fn as_expr(&self) -> Expr {
1838        match self {
1839            ActionConstraint::Any => Expr::val(true),
1840            ActionConstraint::In(euids) => Expr::is_in(
1841                Expr::var(Var::Action),
1842                ActionConstraint::euids_into_expr(euids.iter().cloned()),
1843            ),
1844            ActionConstraint::Eq(euid) => {
1845                Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1846            }
1847            #[cfg(feature = "tolerant-ast")]
1848            ActionConstraint::ErrorConstraint => Expr::new(
1849                ExprKind::Error {
1850                    error_kind: AstExprErrorKind::InvalidExpr(
1851                        "Invalid action constraint".to_string(),
1852                    ),
1853                },
1854                None,
1855                (),
1856            ),
1857        }
1858    }
1859
1860    /// Get an iterator over all of the entity uids in this constraint.
1861    pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1862        match self {
1863            ActionConstraint::Any => EntityIterator::None,
1864            ActionConstraint::In(euids) => {
1865                EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1866            }
1867            ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1868            #[cfg(feature = "tolerant-ast")]
1869            ActionConstraint::ErrorConstraint => EntityIterator::None,
1870        }
1871    }
1872
1873    /// Get an iterator over all of the entity types in this constraint.
1874    pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1875        self.iter_euids().map(|euid| euid.entity_type())
1876    }
1877
1878    /// Check that all of the EUIDs in an action constraint have the type
1879    /// `Action`, under an arbitrary namespace.
1880    pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1881        match self {
1882            ActionConstraint::Any => Ok(self),
1883            ActionConstraint::In(ref euids) => {
1884                if let Some(euids) =
1885                    NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1886                {
1887                    Err(euids)
1888                } else {
1889                    Ok(self)
1890                }
1891            }
1892            ActionConstraint::Eq(ref euid) => {
1893                if euid.is_action() {
1894                    Ok(self)
1895                } else {
1896                    Err(nonempty![euid.clone()])
1897                }
1898            }
1899            #[cfg(feature = "tolerant-ast")]
1900            ActionConstraint::ErrorConstraint => Ok(self),
1901        }
1902    }
1903}
1904
1905impl std::fmt::Display for StaticPolicy {
1906    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1907        let policy_template = &self.0;
1908        match policy_template {
1909            TemplateBody::TemplateBody(template_body_impl) => {
1910                for (k, v) in template_body_impl.annotations.iter() {
1911                    writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1912                }
1913                write!(
1914                    f,
1915                    "{}(\n  {},\n  {},\n  {}\n)",
1916                    self.effect(),
1917                    self.principal_constraint(),
1918                    self.action_constraint(),
1919                    self.resource_constraint(),
1920                )?;
1921                if let Some(non_scope_constraints) = self.non_scope_constraints() {
1922                    write!(f, " when {{\n  {non_scope_constraints}\n}};")
1923                } else {
1924                    write!(f, ";")
1925                }
1926            }
1927            #[cfg(feature = "tolerant-ast")]
1928            TemplateBody::TemplateBodyError(policy_id, _) => {
1929                write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1930            }
1931        }
1932    }
1933}
1934
1935/// A unique identifier for a policy statement
1936#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1937pub struct PolicyID(SmolStr);
1938
1939impl PolicyID {
1940    /// Create a PolicyID from a string or string-like
1941    pub fn from_string(id: impl AsRef<str>) -> Self {
1942        Self(SmolStr::from(id.as_ref()))
1943    }
1944
1945    /// Create a PolicyID from a `SmolStr`
1946    pub fn from_smolstr(id: SmolStr) -> Self {
1947        Self(id)
1948    }
1949}
1950
1951impl std::fmt::Display for PolicyID {
1952    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1953        write!(f, "{}", self.0.escape_debug())
1954    }
1955}
1956
1957impl AsRef<str> for PolicyID {
1958    fn as_ref(&self) -> &str {
1959        &self.0
1960    }
1961}
1962
1963#[cfg(feature = "arbitrary")]
1964impl arbitrary::Arbitrary<'_> for PolicyID {
1965    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1966        let s: String = u.arbitrary()?;
1967        Ok(PolicyID::from_string(s))
1968    }
1969    fn size_hint(depth: usize) -> (usize, Option<usize>) {
1970        <String as arbitrary::Arbitrary>::size_hint(depth)
1971    }
1972}
1973
1974/// the Effect of a policy
1975#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1976#[serde(rename_all = "camelCase")]
1977#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1978#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1979#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1980pub enum Effect {
1981    /// this is a Permit policy
1982    Permit,
1983    /// this is a Forbid policy
1984    Forbid,
1985}
1986
1987impl std::fmt::Display for Effect {
1988    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1989        match self {
1990            Self::Permit => write!(f, "permit"),
1991            Self::Forbid => write!(f, "forbid"),
1992        }
1993    }
1994}
1995
1996enum EntityIterator<'a> {
1997    None,
1998    One(&'a EntityUID),
1999    Bunch(Vec<&'a EntityUID>),
2000}
2001
2002impl<'a> Iterator for EntityIterator<'a> {
2003    type Item = &'a EntityUID;
2004
2005    fn next(&mut self) -> Option<Self::Item> {
2006        match self {
2007            EntityIterator::None => None,
2008            EntityIterator::One(euid) => {
2009                let eptr = *euid;
2010                let mut ptr = EntityIterator::None;
2011                std::mem::swap(self, &mut ptr);
2012                Some(eptr)
2013            }
2014            EntityIterator::Bunch(v) => v.pop(),
2015        }
2016    }
2017}
2018
2019#[cfg(test)]
2020pub(crate) mod test_generators {
2021    use super::*;
2022
2023    pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
2024        let euid = Arc::new(EntityUID::with_eid("test"));
2025        let v = vec![
2026            PrincipalOrResourceConstraint::any(),
2027            PrincipalOrResourceConstraint::is_eq(euid.clone()),
2028            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)),
2029            PrincipalOrResourceConstraint::is_in(euid),
2030            PrincipalOrResourceConstraint::In(EntityReference::Slot(None)),
2031        ];
2032
2033        v.into_iter()
2034    }
2035
2036    pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
2037        all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
2038    }
2039
2040    pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
2041        all_por_constraints().map(|constraint| ResourceConstraint { constraint })
2042    }
2043
2044    pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
2045        let euid: EntityUID = "Action::\"test\""
2046            .parse()
2047            .expect("Invalid action constraint euid");
2048        let v = vec![
2049            ActionConstraint::any(),
2050            ActionConstraint::is_eq(euid.clone()),
2051            ActionConstraint::is_in([euid.clone()]),
2052            ActionConstraint::is_in([euid.clone(), euid]),
2053        ];
2054
2055        v.into_iter()
2056    }
2057
2058    pub fn all_templates() -> impl Iterator<Item = Template> {
2059        let mut buf = vec![];
2060        let permit = PolicyID::from_string("permit");
2061        let forbid = PolicyID::from_string("forbid");
2062        for principal in all_principal_constraints() {
2063            for action in all_actions_constraints() {
2064                for resource in all_resource_constraints() {
2065                    let permit = Template::new(
2066                        permit.clone(),
2067                        None,
2068                        Annotations::new(),
2069                        Effect::Permit,
2070                        principal.clone(),
2071                        action.clone(),
2072                        resource.clone(),
2073                        None,
2074                    );
2075                    let forbid = Template::new(
2076                        forbid.clone(),
2077                        None,
2078                        Annotations::new(),
2079                        Effect::Forbid,
2080                        principal.clone(),
2081                        action.clone(),
2082                        resource.clone(),
2083                        None,
2084                    );
2085                    buf.push(permit);
2086                    buf.push(forbid);
2087                }
2088            }
2089        }
2090        buf.into_iter()
2091    }
2092}
2093
2094#[cfg(test)]
2095mod test {
2096    use cool_asserts::assert_matches;
2097    use std::collections::HashSet;
2098
2099    use super::{test_generators::*, *};
2100    use crate::{
2101        parser::{
2102            parse_policy,
2103            test_utils::{expect_exactly_one_error, expect_some_error_matches},
2104        },
2105        test_utils::ExpectedErrorMessageBuilder,
2106    };
2107
2108    #[test]
2109    fn link_templates() {
2110        for template in all_templates() {
2111            template.check_invariant();
2112            let t = Arc::new(template);
2113            let env = t
2114                .slots()
2115                .map(|slot| (slot.id, EntityUID::with_eid("eid")))
2116                .collect();
2117            let _ = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
2118        }
2119    }
2120
2121    #[test]
2122    fn test_template_rebuild() {
2123        for template in all_templates() {
2124            let id = template.id().clone();
2125            let effect = template.effect();
2126            let p = template.principal_constraint().clone();
2127            let a = template.action_constraint().clone();
2128            let r = template.resource_constraint().clone();
2129            let non_scope = template.non_scope_constraints().cloned();
2130            let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
2131            assert_eq!(template, t2);
2132        }
2133    }
2134
2135    #[test]
2136    fn test_static_policy_rebuild() {
2137        for template in all_templates() {
2138            if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
2139                let id = ip.id().clone();
2140                let e = ip.effect();
2141                let anno = ip
2142                    .annotations()
2143                    .map(|(k, v)| (k.clone(), v.clone()))
2144                    .collect();
2145                let p = ip.principal_constraint().clone();
2146                let a = ip.action_constraint().clone();
2147                let r = ip.resource_constraint().clone();
2148                let non_scope = ip.non_scope_constraints();
2149                let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope.cloned())
2150                    .expect("Policy Creation Failed");
2151                assert_eq!(ip, ip2);
2152                let (t2, inst) = Template::link_static_policy(ip2);
2153                assert!(inst.is_static());
2154                assert_eq!(&template, t2.as_ref());
2155            }
2156        }
2157    }
2158
2159    #[test]
2160    fn ir_binding_too_many() {
2161        let tid = PolicyID::from_string("tid");
2162        let iid = PolicyID::from_string("iid");
2163        let t = Arc::new(Template::new(
2164            tid,
2165            None,
2166            Annotations::new(),
2167            Effect::Forbid,
2168            PrincipalConstraint::is_eq_slot(),
2169            ActionConstraint::Any,
2170            ResourceConstraint::any(),
2171            None,
2172        ));
2173        let mut m = HashMap::new();
2174        m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
2175        assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2176            assert_eq!(unbound_values, vec![SlotId::principal()]);
2177            assert_eq!(extra_values, vec![SlotId::resource()]);
2178        });
2179    }
2180
2181    #[test]
2182    fn ir_binding_too_few() {
2183        let tid = PolicyID::from_string("tid");
2184        let iid = PolicyID::from_string("iid");
2185        let t = Arc::new(Template::new(
2186            tid,
2187            None,
2188            Annotations::new(),
2189            Effect::Forbid,
2190            PrincipalConstraint::is_eq_slot(),
2191            ActionConstraint::Any,
2192            ResourceConstraint::is_in_slot(),
2193            None,
2194        ));
2195        assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2196            assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
2197            assert_eq!(extra_values, vec![]);
2198        });
2199        let mut m = HashMap::new();
2200        m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
2201        assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2202            assert_eq!(unbound_values, vec![SlotId::resource()]);
2203            assert_eq!(extra_values, vec![]);
2204        });
2205    }
2206
2207    #[test]
2208    fn ir_binding() {
2209        let tid = PolicyID::from_string("template");
2210        let iid = PolicyID::from_string("linked");
2211        let t = Arc::new(Template::new(
2212            tid,
2213            None,
2214            Annotations::new(),
2215            Effect::Permit,
2216            PrincipalConstraint::is_in_slot(),
2217            ActionConstraint::any(),
2218            ResourceConstraint::is_eq_slot(),
2219            None,
2220        ));
2221
2222        let mut m = HashMap::new();
2223        m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2224        m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2225
2226        let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2227        assert_eq!(r.id(), &iid);
2228        assert_eq!(
2229            r.env().get(&SlotId::principal()),
2230            Some(&EntityUID::with_eid("theprincipal"))
2231        );
2232        assert_eq!(
2233            r.env().get(&SlotId::resource()),
2234            Some(&EntityUID::with_eid("theresource"))
2235        );
2236    }
2237
2238    #[test]
2239    fn isnt_template_implies_from_succeeds() {
2240        for template in all_templates() {
2241            if template.slots().count() == 0 {
2242                StaticPolicy::try_from(template).expect("Should succeed");
2243            }
2244        }
2245    }
2246
2247    #[test]
2248    fn is_template_implies_from_fails() {
2249        for template in all_templates() {
2250            if template.slots().count() != 0 {
2251                assert!(
2252                    StaticPolicy::try_from(template.clone()).is_err(),
2253                    "Following template did convert {template}"
2254                );
2255            }
2256        }
2257    }
2258
2259    #[test]
2260    fn non_template_iso() {
2261        for template in all_templates() {
2262            if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2263                let (t2, _) = Template::link_static_policy(p);
2264                assert_eq!(&template, t2.as_ref());
2265            }
2266        }
2267    }
2268
2269    #[test]
2270    fn template_into_expr() {
2271        for template in all_templates() {
2272            if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2273                let t: Template = template;
2274                assert_eq!(p.condition(), t.condition());
2275                assert_eq!(p.effect(), t.effect());
2276            }
2277        }
2278    }
2279
2280    #[test]
2281    fn template_por_iter() {
2282        let e = Arc::new(EntityUID::with_eid("eid"));
2283        assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2284        assert_eq!(
2285            PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2286            Some(&e)
2287        );
2288        assert_eq!(
2289            PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(),
2290            None
2291        );
2292        assert_eq!(
2293            PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2294            Some(&e)
2295        );
2296        assert_eq!(
2297            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(),
2298            None
2299        );
2300        assert_eq!(
2301            PrincipalOrResourceConstraint::IsIn(
2302                Arc::new("T".parse().unwrap()),
2303                EntityReference::EUID(e.clone())
2304            )
2305            .get_euid(),
2306            Some(&e)
2307        );
2308        assert_eq!(
2309            PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2310            None
2311        );
2312        assert_eq!(
2313            PrincipalOrResourceConstraint::IsIn(
2314                Arc::new("T".parse().unwrap()),
2315                EntityReference::Slot(None)
2316            )
2317            .get_euid(),
2318            None
2319        );
2320    }
2321
2322    #[test]
2323    fn action_iter() {
2324        assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2325        let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2326        let v = a.iter_euids().collect::<Vec<_>>();
2327        assert_eq!(vec![&EntityUID::with_eid("test")], v);
2328        let a =
2329            ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2330        let set = a.iter_euids().collect::<HashSet<_>>();
2331        let e1 = EntityUID::with_eid("test1");
2332        let e2 = EntityUID::with_eid("test2");
2333        let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2334        assert_eq!(set, correct);
2335    }
2336
2337    #[test]
2338    fn test_iter_none() {
2339        let mut i = EntityIterator::None;
2340        assert_eq!(i.next(), None);
2341    }
2342
2343    #[test]
2344    fn test_iter_once() {
2345        let id = EntityUID::from_components(
2346            name::Name::parse_unqualified_name("s").unwrap().into(),
2347            entity::Eid::new("eid"),
2348            None,
2349        );
2350        let mut i = EntityIterator::One(&id);
2351        assert_eq!(i.next(), Some(&id));
2352        assert_eq!(i.next(), None);
2353    }
2354
2355    #[test]
2356    fn test_iter_mult() {
2357        let id1 = EntityUID::from_components(
2358            name::Name::parse_unqualified_name("s").unwrap().into(),
2359            entity::Eid::new("eid1"),
2360            None,
2361        );
2362        let id2 = EntityUID::from_components(
2363            name::Name::parse_unqualified_name("s").unwrap().into(),
2364            entity::Eid::new("eid2"),
2365            None,
2366        );
2367        let v = vec![&id1, &id2];
2368        let mut i = EntityIterator::Bunch(v);
2369        assert_eq!(i.next(), Some(&id2));
2370        assert_eq!(i.next(), Some(&id1));
2371        assert_eq!(i.next(), None)
2372    }
2373
2374    #[test]
2375    fn euid_into_expr() {
2376        let e = EntityReference::Slot(None);
2377        assert_eq!(
2378            e.into_expr(SlotId::principal()),
2379            Expr::slot(SlotId::principal())
2380        );
2381        let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2382        assert_eq!(
2383            e.into_expr(SlotId::principal()),
2384            Expr::val(EntityUID::with_eid("eid"))
2385        );
2386    }
2387
2388    #[test]
2389    fn por_constraint_display() {
2390        let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None));
2391        let s = t.display(PrincipalOrResource::Principal);
2392        assert_eq!(s, "principal == ?principal");
2393        let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2394            EntityUID::with_eid("test"),
2395        )));
2396        let s = t.display(PrincipalOrResource::Principal);
2397        assert_eq!(s, "principal == test_entity_type::\"test\"");
2398    }
2399
2400    #[test]
2401    fn unexpected_templates() {
2402        let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2403        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2404            expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2405                "expected a static policy, got a template containing the slot ?principal"
2406                )
2407                .help("try removing the template slot(s) from this policy")
2408                .exactly_one_underline("?principal")
2409                .build()
2410            );
2411        });
2412
2413        let policy_str =
2414            r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2415        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2416            expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2417                "expected a static policy, got a template containing the slot ?principal"
2418                )
2419                .help("try removing the template slot(s) from this policy")
2420                .exactly_one_underline("?principal")
2421                .build()
2422            );
2423            assert_eq!(e.len(), 2);
2424        });
2425    }
2426
2427    #[test]
2428    fn policy_to_expr() {
2429        let policy_str = r#"permit(principal is A, action, resource is B)
2430            when { 1 == 2 }
2431            unless { 2 == 1}
2432            when { 3 == 4}
2433            unless { 4 == 3};"#;
2434        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Ok(p) => {
2435            assert_eq!(ToString::to_string(&p.condition()), "(principal is A) && (true && ((resource is B) && ((1 == 2) && ((!(2 == 1)) && ((3 == 4) && (!(4 == 3)))))))");
2436        });
2437    }
2438
2439    #[cfg(feature = "tolerant-ast")]
2440    #[test]
2441    fn template_body_error_methods() {
2442        use std::str::FromStr;
2443
2444        let policy_id = PolicyID::from_string("error_policy");
2445        let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2446        let error_body = TemplateBody::TemplateBodyError(policy_id.clone(), error_loc.clone());
2447
2448        let expected_error = <ExprWithErrsBuilder as ExprBuilder>::new()
2449            .error(ParseErrors::singleton(ToASTError::new(
2450                ToASTErrorKind::ASTErrorNode,
2451                Some(Loc::new(0..1, "ASTErrorNode".into())),
2452            )))
2453            .unwrap();
2454
2455        // Test id() method
2456        assert_eq!(error_body.id(), &policy_id);
2457
2458        // Test loc() method
2459        assert_eq!(error_body.loc(), error_loc.as_ref());
2460
2461        // Test new_id() method
2462        let new_policy_id = PolicyID::from_string("new_error_policy");
2463        let updated_error_body = error_body.new_id(new_policy_id.clone());
2464        assert_matches!(updated_error_body,
2465            TemplateBody::TemplateBodyError(id, loc) if id == new_policy_id && loc == error_loc
2466        );
2467
2468        // Test effect() method
2469        assert_eq!(error_body.effect(), Effect::Forbid);
2470
2471        // Test annotation() method
2472        assert_eq!(
2473            error_body.annotation(&AnyId::from_str("test").unwrap()),
2474            None
2475        );
2476
2477        // Test annotations() method
2478        assert!(error_body.annotations().count() == 0);
2479
2480        // Test principal_constraint() method
2481        assert_eq!(
2482            *error_body.principal_constraint(),
2483            PrincipalConstraint::any()
2484        );
2485
2486        // Test action_constraint() method
2487        assert_eq!(*error_body.action_constraint(), ActionConstraint::any());
2488
2489        // Test resource_constraint() method
2490        assert_eq!(*error_body.resource_constraint(), ResourceConstraint::any());
2491
2492        // Test non_scope_constraints() method
2493        assert_eq!(error_body.non_scope_constraints(), Some(&expected_error));
2494
2495        // Test condition() method
2496        assert_eq!(error_body.condition(), expected_error);
2497
2498        // Test Display implementation
2499        let display_str = format!("{error_body}");
2500        assert!(display_str.contains("TemplateBodyError"));
2501        assert!(display_str.contains("error_policy"));
2502    }
2503
2504    #[cfg(feature = "tolerant-ast")]
2505    #[test]
2506    fn template_error_methods() {
2507        let policy_id = PolicyID::from_string("error_policy");
2508        let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2509        let error_template = Template::error(policy_id.clone(), error_loc.clone());
2510
2511        // Check template properties
2512        assert_eq!(error_template.id(), &policy_id);
2513
2514        // Check slots are empty
2515        assert!(error_template.slots().count() == 0);
2516
2517        // Check body is an error template body
2518        assert_matches!(error_template.body,
2519            TemplateBody::TemplateBodyError(ref id, ref loc) if id == &policy_id && loc == &error_loc
2520        );
2521
2522        // Test principal_constraint() method
2523        assert_eq!(
2524            error_template.principal_constraint(),
2525            &PrincipalConstraint::any()
2526        );
2527
2528        // Test action_constraint() method
2529        assert_eq!(*error_template.action_constraint(), ActionConstraint::any());
2530
2531        // Test resource_constraint() method
2532        assert_eq!(
2533            *error_template.resource_constraint(),
2534            ResourceConstraint::any()
2535        );
2536
2537        // Verify effect is Forbid
2538        assert_eq!(error_template.effect(), Effect::Forbid);
2539
2540        // Verify condition is the default error expression
2541        assert_eq!(
2542            error_template.condition(),
2543            DEFAULT_ERROR_EXPR.as_ref().clone()
2544        );
2545
2546        // Verify location is None
2547        assert_eq!(error_template.loc(), error_loc.as_ref());
2548
2549        // Verify annotations are default
2550        assert!(error_template.annotations().count() == 0);
2551
2552        // Verify display implementation
2553        let display_str = format!("{error_template}");
2554        assert!(display_str.contains("TemplateBody::TemplateBodyError"));
2555        assert!(display_str.contains(&policy_id.to_string()));
2556    }
2557}