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