cedar_policy/
api.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
17//! This module contains the public library api
18#![allow(
19    clippy::missing_panics_doc,
20    clippy::missing_errors_doc,
21    clippy::similar_names,
22    clippy::result_large_err, // see #878
23)]
24
25mod id;
26#[cfg(feature = "entity-manifest")]
27use cedar_policy_core::validator::entity_manifest;
28// TODO (#1157) implement wrappers for these structs before they become public
29#[cfg(feature = "entity-manifest")]
30pub use cedar_policy_core::validator::entity_manifest::{
31    AccessTrie, EntityManifest, EntityRoot, Fields, RootAccessTrie,
32};
33use cedar_policy_core::validator::json_schema;
34use cedar_policy_core::validator::typecheck::{PolicyCheck, Typechecker};
35pub use id::*;
36
37#[cfg(feature = "deprecated-schema-compat")]
38mod deprecated_schema_compat;
39
40mod err;
41pub use err::*;
42
43pub use ast::Effect;
44pub use authorizer::Decision;
45#[cfg(feature = "partial-eval")]
46use cedar_policy_core::ast::BorrowedRestrictedExpr;
47use cedar_policy_core::ast::{self, RequestSchema, RestrictedExpr};
48use cedar_policy_core::authorizer::{self};
49use cedar_policy_core::entities::{ContextSchema, Dereference};
50use cedar_policy_core::est::{self, TemplateLink};
51use cedar_policy_core::evaluator::Evaluator;
52#[cfg(feature = "partial-eval")]
53use cedar_policy_core::evaluator::RestrictedEvaluator;
54use cedar_policy_core::extensions::Extensions;
55use cedar_policy_core::parser;
56use cedar_policy_core::FromNormalizedStr;
57use itertools::{Either, Itertools};
58use linked_hash_map::LinkedHashMap;
59use miette::Diagnostic;
60use ref_cast::RefCast;
61use serde::{Deserialize, Serialize};
62use smol_str::SmolStr;
63use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
64use std::io::Read;
65use std::str::FromStr;
66use std::sync::Arc;
67
68// PANIC SAFETY: `CARGO_PKG_VERSION` should return a valid SemVer version string
69#[allow(clippy::unwrap_used)]
70pub(crate) mod version {
71    use semver::Version;
72    use std::sync::LazyLock;
73
74    // Cedar Rust SDK Semantic Versioning version
75    static SDK_VERSION: LazyLock<Version> =
76        LazyLock::new(|| env!("CARGO_PKG_VERSION").parse().unwrap());
77    // Cedar language version
78    // The patch version field may be unnecessary
79    static LANG_VERSION: LazyLock<Version> = LazyLock::new(|| Version::new(4, 4, 0));
80
81    /// Get the Cedar SDK Semantic Versioning version
82    #[allow(clippy::module_name_repetitions)]
83    pub fn get_sdk_version() -> Version {
84        SDK_VERSION.clone()
85    }
86    /// Get the Cedar language version
87    #[allow(clippy::module_name_repetitions)]
88    pub fn get_lang_version() -> Version {
89        LANG_VERSION.clone()
90    }
91}
92
93/// Entity datatype
94#[repr(transparent)]
95#[derive(Debug, Clone, PartialEq, Eq, RefCast, Hash)]
96pub struct Entity(pub(crate) ast::Entity);
97
98#[doc(hidden)] // because this converts to a private/internal type
99impl AsRef<ast::Entity> for Entity {
100    fn as_ref(&self) -> &ast::Entity {
101        &self.0
102    }
103}
104
105#[doc(hidden)]
106impl From<ast::Entity> for Entity {
107    fn from(entity: ast::Entity) -> Self {
108        Self(entity)
109    }
110}
111
112impl Entity {
113    /// Create a new `Entity` with this Uid, attributes, and parents (and no tags).
114    ///
115    /// Attribute values are specified here as "restricted expressions".
116    /// See docs on `RestrictedExpression`
117    /// ```
118    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid, RestrictedExpression};
119    /// # use std::collections::{HashMap, HashSet};
120    /// # use std::str::FromStr;
121    /// let eid = EntityId::from_str("alice").unwrap();
122    /// let type_name = EntityTypeName::from_str("User").unwrap();
123    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
124    /// let attrs = HashMap::from([
125    ///     ("age".to_string(), RestrictedExpression::from_str("21").unwrap()),
126    ///     ("department".to_string(), RestrictedExpression::from_str("\"CS\"").unwrap()),
127    /// ]);
128    /// let parent_eid = EntityId::from_str("admin").unwrap();
129    /// let parent_type_name = EntityTypeName::from_str("Group").unwrap();
130    /// let parent_euid = EntityUid::from_type_name_and_id(parent_type_name, parent_eid);
131    /// let parents = HashSet::from([parent_euid]);
132    /// let entity = Entity::new(euid, attrs, parents);
133    ///```
134    pub fn new(
135        uid: EntityUid,
136        attrs: HashMap<String, RestrictedExpression>,
137        parents: HashSet<EntityUid>,
138    ) -> Result<Self, EntityAttrEvaluationError> {
139        Self::new_with_tags(uid, attrs, parents, [])
140    }
141
142    /// Create a new `Entity` with no attributes or tags.
143    ///
144    /// Unlike [`Entity::new()`], this constructor cannot error.
145    /// (The only source of errors in `Entity::new()` are attributes.)
146    pub fn new_no_attrs(uid: EntityUid, parents: HashSet<EntityUid>) -> Self {
147        // note that we take a "parents" parameter here; we will compute TC when
148        // the `Entities` object is created
149        Self(ast::Entity::new_with_attr_partial_value(
150            uid.into(),
151            [],
152            HashSet::new(),
153            parents.into_iter().map(EntityUid::into).collect(),
154            [],
155        ))
156    }
157
158    /// Create a new `Entity` with this Uid, attributes, parents, and tags.
159    ///
160    /// Attribute and tag values are specified here as "restricted expressions".
161    /// See docs on [`RestrictedExpression`].
162    pub fn new_with_tags(
163        uid: EntityUid,
164        attrs: impl IntoIterator<Item = (String, RestrictedExpression)>,
165        parents: impl IntoIterator<Item = EntityUid>,
166        tags: impl IntoIterator<Item = (String, RestrictedExpression)>,
167    ) -> Result<Self, EntityAttrEvaluationError> {
168        // note that we take a "parents" parameter here, not "ancestors"; we
169        // will compute TC when the `Entities` object is created
170        Ok(Self(ast::Entity::new(
171            uid.into(),
172            attrs.into_iter().map(|(k, v)| (k.into(), v.0)),
173            HashSet::new(),
174            parents.into_iter().map(EntityUid::into).collect(),
175            tags.into_iter().map(|(k, v)| (k.into(), v.0)),
176            Extensions::all_available(),
177        )?))
178    }
179
180    /// Create a new `Entity` with this Uid, no attributes, and no parents.
181    /// ```
182    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
183    /// # use std::str::FromStr;
184    /// let eid = EntityId::from_str("alice").unwrap();
185    /// let type_name = EntityTypeName::from_str("User").unwrap();
186    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
187    /// let alice = Entity::with_uid(euid);
188    /// # cool_asserts::assert_matches!(alice.attr("age"), None);
189    /// ```
190    pub fn with_uid(uid: EntityUid) -> Self {
191        Self(ast::Entity::with_uid(uid.into()))
192    }
193
194    /// Test if two entities are structurally equal. That is, not only do they
195    /// have the same UID, but they also have the same attributes and ancestors.
196    ///
197    /// Note that ancestor equality is determined by examining the ancestors
198    /// entities provided when constructing these objects, without computing
199    /// their transitive closure. For accurate comparison, entities should be
200    /// constructed with the transitive closure precomputed or be drawn from an
201    /// [`Entities`] object which will perform this computation.
202    pub fn deep_eq(&self, other: &Self) -> bool {
203        self.0.deep_eq(&other.0)
204    }
205
206    /// Get the Uid of this entity
207    /// ```
208    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
209    /// # use std::str::FromStr;
210    /// # let eid = EntityId::from_str("alice").unwrap();
211    /// let type_name = EntityTypeName::from_str("User").unwrap();
212    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
213    /// let alice = Entity::with_uid(euid.clone());
214    /// assert_eq!(alice.uid(), euid);
215    /// ```
216    pub fn uid(&self) -> EntityUid {
217        self.0.uid().clone().into()
218    }
219
220    /// Get the value for the given attribute, or `None` if not present.
221    ///
222    /// This can also return Some(Err) if the attribute is not a value (i.e., is
223    /// unknown due to partial evaluation).
224    /// ```
225    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid, EvalResult, RestrictedExpression};
226    /// # use std::collections::{HashMap, HashSet};
227    /// # use std::str::FromStr;
228    /// let eid = EntityId::from_str("alice").unwrap();
229    /// let type_name = EntityTypeName::from_str("User").unwrap();
230    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
231    /// let attrs = HashMap::from([
232    ///     ("age".to_string(), RestrictedExpression::from_str("21").unwrap()),
233    ///     ("department".to_string(), RestrictedExpression::from_str("\"CS\"").unwrap()),
234    /// ]);
235    /// let entity = Entity::new(euid, attrs, HashSet::new()).unwrap();
236    /// assert_eq!(entity.attr("age").unwrap().unwrap(), EvalResult::Long(21));
237    /// assert_eq!(entity.attr("department").unwrap().unwrap(), EvalResult::String("CS".to_string()));
238    /// assert!(entity.attr("foo").is_none());
239    /// ```
240    pub fn attr(&self, attr: &str) -> Option<Result<EvalResult, PartialValueToValueError>> {
241        match ast::Value::try_from(self.0.get(attr)?.clone()) {
242            Ok(v) => Some(Ok(EvalResult::from(v))),
243            Err(e) => Some(Err(e)),
244        }
245    }
246
247    /// Get the value for the given tag, or `None` if not present.
248    ///
249    /// This can also return Some(Err) if the tag is not a value (i.e., is
250    /// unknown due to partial evaluation).
251    pub fn tag(&self, tag: &str) -> Option<Result<EvalResult, PartialValueToValueError>> {
252        match ast::Value::try_from(self.0.get_tag(tag)?.clone()) {
253            Ok(v) => Some(Ok(EvalResult::from(v))),
254            Err(e) => Some(Err(e)),
255        }
256    }
257
258    /// Consume the entity and return the entity's owned Uid, attributes and parents.
259    pub fn into_inner(
260        self,
261    ) -> (
262        EntityUid,
263        HashMap<String, RestrictedExpression>,
264        HashSet<EntityUid>,
265    ) {
266        let (uid, attrs, ancestors, mut parents, _) = self.0.into_inner();
267        parents.extend(ancestors);
268
269        let attrs = attrs
270            .into_iter()
271            .map(|(k, v)| {
272                (
273                    k.to_string(),
274                    match v {
275                        ast::PartialValue::Value(val) => {
276                            RestrictedExpression(ast::RestrictedExpr::from(val))
277                        }
278                        ast::PartialValue::Residual(exp) => {
279                            RestrictedExpression(ast::RestrictedExpr::new_unchecked(exp))
280                        }
281                    },
282                )
283            })
284            .collect();
285
286        (
287            uid.into(),
288            attrs,
289            parents.into_iter().map(Into::into).collect(),
290        )
291    }
292
293    /// Parse an entity from an in-memory JSON value
294    /// If a schema is provided, it is handled identically to [`Entities::from_json_str`]
295    pub fn from_json_value(
296        value: serde_json::Value,
297        schema: Option<&Schema>,
298    ) -> Result<Self, EntitiesError> {
299        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
300        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
301            schema.as_ref(),
302            Extensions::all_available(),
303            cedar_policy_core::entities::TCComputation::ComputeNow,
304        );
305        eparser.single_from_json_value(value).map(Self)
306    }
307
308    /// Parse an entity from a JSON string
309    /// If a schema is provided, it is handled identically to [`Entities::from_json_str`]
310    pub fn from_json_str(
311        src: impl AsRef<str>,
312        schema: Option<&Schema>,
313    ) -> Result<Self, EntitiesError> {
314        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
315        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
316            schema.as_ref(),
317            Extensions::all_available(),
318            cedar_policy_core::entities::TCComputation::ComputeNow,
319        );
320        eparser.single_from_json_str(src).map(Self)
321    }
322
323    /// Parse an entity from a JSON reader
324    /// If a schema is provided, it is handled identically to [`Entities::from_json_str`]
325    pub fn from_json_file(f: impl Read, schema: Option<&Schema>) -> Result<Self, EntitiesError> {
326        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
327        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
328            schema.as_ref(),
329            Extensions::all_available(),
330            cedar_policy_core::entities::TCComputation::ComputeNow,
331        );
332        eparser.single_from_json_file(f).map(Self)
333    }
334
335    /// Dump an `Entity` object into an entity JSON file.
336    ///
337    /// The resulting JSON will be suitable for parsing in via
338    /// `from_json_*`, and will be parse-able even with no [`Schema`].
339    ///
340    /// To read an `Entity` object from JSON , use
341    /// [`Self::from_json_file`], [`Self::from_json_value`], or [`Self::from_json_str`].
342    pub fn write_to_json(&self, f: impl std::io::Write) -> Result<(), EntitiesError> {
343        self.0.write_to_json(f)
344    }
345
346    /// Dump an `Entity` object into an in-memory JSON object.
347    ///
348    /// The resulting JSON will be suitable for parsing in via
349    /// `from_json_*`, and will be parse-able even with no `Schema`.
350    ///
351    /// To read an `Entity` object from JSON , use
352    /// [`Self::from_json_file`], [`Self::from_json_value`], or [`Self::from_json_str`].
353    pub fn to_json_value(&self) -> Result<serde_json::Value, EntitiesError> {
354        self.0.to_json_value()
355    }
356
357    /// Dump an `Entity` object into a JSON string.
358    ///
359    /// The resulting JSON will be suitable for parsing in via
360    /// `from_json_*`, and will be parse-able even with no `Schema`.
361    ///
362    /// To read an `Entity` object from JSON , use
363    /// [`Self::from_json_file`], [`Self::from_json_value`], or [`Self::from_json_str`].
364    pub fn to_json_string(&self) -> Result<String, EntitiesError> {
365        self.0.to_json_string()
366    }
367}
368
369impl std::fmt::Display for Entity {
370    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371        write!(f, "{}", self.0)
372    }
373}
374
375/// Represents an entity hierarchy, and allows looking up `Entity` objects by
376/// Uid.
377#[repr(transparent)]
378#[derive(Debug, Clone, Default, PartialEq, Eq, RefCast)]
379pub struct Entities(pub(crate) cedar_policy_core::entities::Entities);
380
381#[doc(hidden)] // because this converts to a private/internal type
382impl AsRef<cedar_policy_core::entities::Entities> for Entities {
383    fn as_ref(&self) -> &cedar_policy_core::entities::Entities {
384        &self.0
385    }
386}
387
388#[doc(hidden)]
389impl From<cedar_policy_core::entities::Entities> for Entities {
390    fn from(entities: cedar_policy_core::entities::Entities) -> Self {
391        Self(entities)
392    }
393}
394
395use entities_errors::EntitiesError;
396
397impl Entities {
398    /// Create a fresh `Entities` with no entities
399    /// ```
400    /// # use cedar_policy::Entities;
401    /// let entities = Entities::empty();
402    /// ```
403    pub fn empty() -> Self {
404        Self(cedar_policy_core::entities::Entities::new())
405    }
406
407    /// Get the `Entity` with the given Uid, if any
408    pub fn get(&self, uid: &EntityUid) -> Option<&Entity> {
409        match self.0.entity(uid.as_ref()) {
410            Dereference::Residual(_) | Dereference::NoSuchEntity => None,
411            Dereference::Data(e) => Some(Entity::ref_cast(e)),
412        }
413    }
414
415    /// Transform the store into a partial store, where
416    /// attempting to dereference a non-existent `EntityUid` results in
417    /// a residual instead of an error.
418    #[doc = include_str!("../experimental_warning.md")]
419    #[must_use]
420    #[cfg(feature = "partial-eval")]
421    pub fn partial(self) -> Self {
422        Self(self.0.partial())
423    }
424
425    /// Iterate over the `Entity`'s in the `Entities`
426    pub fn iter(&self) -> impl Iterator<Item = &Entity> {
427        self.0.iter().map(Entity::ref_cast)
428    }
429
430    /// Test if two entity hierarchies are structurally equal. The hierarchies
431    /// must contain the same set of entity ids, and the entities with each id
432    /// must be structurally equal (decided by [`Entity::deep_eq`]). Ancestor
433    /// equality between entities is always decided by comparing the transitive
434    /// closure of ancestor and not direct parents.
435    pub fn deep_eq(&self, other: &Self) -> bool {
436        self.0.deep_eq(&other.0)
437    }
438
439    /// Create an `Entities` object with the given entities.
440    ///
441    /// `schema` represents a source of `Action` entities, which will be added
442    /// to the entities provided.
443    /// (If any `Action` entities are present in the provided entities, and a
444    /// `schema` is also provided, each `Action` entity in the provided entities
445    /// must exactly match its definition in the schema or an error is
446    /// returned.)
447    ///
448    /// If a `schema` is present, this function will also ensure that the
449    /// produced entities fully conform to the `schema` -- for instance, it will
450    /// error if attributes have the wrong types (e.g., string instead of
451    /// integer), or if required attributes are missing or superfluous
452    /// attributes are provided.
453    /// ## Errors
454    /// - [`EntitiesError::Duplicate`] if there are any duplicate entities in `entities`
455    /// - [`EntitiesError::InvalidEntity`] if `schema` is not none and any entities do not conform
456    ///   to the schema
457    pub fn from_entities(
458        entities: impl IntoIterator<Item = Entity>,
459        schema: Option<&Schema>,
460    ) -> Result<Self, EntitiesError> {
461        cedar_policy_core::entities::Entities::from_entities(
462            entities.into_iter().map(|e| e.0),
463            schema
464                .map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0))
465                .as_ref(),
466            cedar_policy_core::entities::TCComputation::ComputeNow,
467            Extensions::all_available(),
468        )
469        .map(Entities)
470    }
471
472    /// Add all of the [`Entity`]s in the collection to this [`Entities`]
473    /// structure, re-computing the transitive closure.
474    ///
475    /// If a `schema` is provided, this method will ensure that the added
476    /// entities fully conform to the schema -- for instance, it will error if
477    /// attributes have the wrong types (e.g., string instead of integer), or if
478    /// required attributes are missing or superfluous attributes are provided.
479    /// (This method will not add action entities from the `schema`.)
480    ///
481    /// Re-computing the transitive closure can be expensive, so it is advised
482    /// to not call this method in a loop.
483    /// ## Errors
484    /// - [`EntitiesError::Duplicate`] if there is a pair of non-identical entities in `entities` with the same Entity UID,
485    ///   or there is an entity in `entities` with the same Entity UID as a non-identical entity in this structure
486    /// - [`EntitiesError::InvalidEntity`] if `schema` is not none and any entities do not conform
487    ///   to the schema
488    pub fn add_entities(
489        self,
490        entities: impl IntoIterator<Item = Entity>,
491        schema: Option<&Schema>,
492    ) -> Result<Self, EntitiesError> {
493        Ok(Self(
494            self.0.add_entities(
495                entities.into_iter().map(|e| Arc::new(e.0)),
496                schema
497                    .map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0))
498                    .as_ref(),
499                cedar_policy_core::entities::TCComputation::ComputeNow,
500                Extensions::all_available(),
501            )?,
502        ))
503    }
504
505    /// Removes each of the [`EntityUid`]s in the iterator
506    /// from this [`Entities`] structure, re-computing the transitive
507    /// closure after removing all edges to/from the removed entities.
508    ///
509    /// Re-computing the transitive closure can be expensive, so it is
510    /// advised to not call this method in a loop.
511    pub fn remove_entities(
512        self,
513        entity_ids: impl IntoIterator<Item = EntityUid>,
514    ) -> Result<Self, EntitiesError> {
515        Ok(Self(self.0.remove_entities(
516            entity_ids.into_iter().map(|euid| euid.0),
517            cedar_policy_core::entities::TCComputation::ComputeNow,
518        )?))
519    }
520
521    /// Updates or adds all of the [`Entity`]s in the collection to this [`Entities`]
522    /// structure, re-computing the transitive closure.
523    ///
524    /// If a `schema` is provided, this method will ensure that the added
525    /// entities fully conform to the schema -- for instance, it will error if
526    /// attributes have the wrong types (e.g., string instead of integer), or if
527    /// required attributes are missing or superfluous attributes are provided.
528    /// (This method will not add action entities from the `schema`.)
529    ///
530    /// Re-computing the transitive closure can be expensive, so it is advised
531    /// to not call this method in a loop.
532    /// ## Errors
533    /// - [`EntitiesError::InvalidEntity`] if `schema` is not none and any entities do not conform
534    ///   to the schema
535    pub fn upsert_entities(
536        self,
537        entities: impl IntoIterator<Item = Entity>,
538        schema: Option<&Schema>,
539    ) -> Result<Self, EntitiesError> {
540        Ok(Self(
541            self.0.upsert_entities(
542                entities.into_iter().map(|e| Arc::new(e.0)),
543                schema
544                    .map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0))
545                    .as_ref(),
546                cedar_policy_core::entities::TCComputation::ComputeNow,
547                Extensions::all_available(),
548            )?,
549        ))
550    }
551
552    /// Parse an entities JSON file (in [&str] form) and add them into this
553    /// [`Entities`] structure, re-computing the transitive closure
554    ///
555    /// If a `schema` is provided, this will inform the parsing: for instance, it
556    /// will allow `__entity` and `__extn` escapes to be implicit.
557    /// This method will also ensure that the added entities fully conform to the
558    /// schema -- for instance, it will error if attributes have the wrong types
559    /// (e.g., string instead of integer), or if required attributes are missing
560    /// or superfluous attributes are provided.
561    /// (This method will not add action entities from the `schema`.)
562    ///
563    /// Re-computing the transitive closure can be expensive, so it is advised
564    /// to not call this method in a loop.
565    /// ## Errors
566    /// - [`EntitiesError::Duplicate`] if there is a pair of non-identical entities in
567    ///   `entities` with the same Entity UID, or there is an entity in `entities` with the
568    ///   same Entity UID as a non-identical entity in this structure
569    /// - [`EntitiesError::InvalidEntity`] if `schema` is not none and any entities do not conform
570    ///   to the schema
571    /// - [`EntitiesError::Deserialization`] if there are errors while parsing the json
572    pub fn add_entities_from_json_str(
573        self,
574        json: &str,
575        schema: Option<&Schema>,
576    ) -> Result<Self, EntitiesError> {
577        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
578        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
579            schema.as_ref(),
580            Extensions::all_available(),
581            cedar_policy_core::entities::TCComputation::ComputeNow,
582        );
583        let new_entities = eparser.iter_from_json_str(json)?.map(Arc::new);
584        Ok(Self(self.0.add_entities(
585            new_entities,
586            schema.as_ref(),
587            cedar_policy_core::entities::TCComputation::ComputeNow,
588            Extensions::all_available(),
589        )?))
590    }
591
592    /// Parse an entities JSON file (in [`serde_json::Value`] form) and add them
593    /// into this [`Entities`] structure, re-computing the transitive closure
594    ///
595    /// If a `schema` is provided, this will inform the parsing: for instance, it
596    /// will allow `__entity` and `__extn` escapes to be implicit.
597    /// This method will also ensure that the added entities fully conform to the
598    /// schema -- for instance, it will error if attributes have the wrong types
599    /// (e.g., string instead of integer), or if required attributes are missing
600    /// or superfluous attributes are provided.
601    /// (This method will not add action entities from the `schema`.)
602    ///
603    /// Re-computing the transitive closure can be expensive, so it is advised
604    /// to not call this method in a loop.
605    /// ## Errors
606    /// - [`EntitiesError::Duplicate`] if there is a pair of non-identical entities in
607    ///   `entities` with the same Entity UID, or there is an entity in `entities` with the same
608    ///   Entity UID as a non-identical entity in this structure
609    /// - [`EntitiesError::InvalidEntity`] if `schema` is not none and any entities do not conform
610    ///   to the schema
611    /// - [`EntitiesError::Deserialization`] if there are errors while parsing the json
612    pub fn add_entities_from_json_value(
613        self,
614        json: serde_json::Value,
615        schema: Option<&Schema>,
616    ) -> Result<Self, EntitiesError> {
617        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
618        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
619            schema.as_ref(),
620            Extensions::all_available(),
621            cedar_policy_core::entities::TCComputation::ComputeNow,
622        );
623        let new_entities = eparser.iter_from_json_value(json)?.map(Arc::new);
624        Ok(Self(self.0.add_entities(
625            new_entities,
626            schema.as_ref(),
627            cedar_policy_core::entities::TCComputation::ComputeNow,
628            Extensions::all_available(),
629        )?))
630    }
631
632    /// Parse an entities JSON file (in [`std::io::Read`] form) and add them
633    /// into this [`Entities`] structure, re-computing the transitive closure
634    ///
635    /// If a `schema` is provided, this will inform the parsing: for instance, it
636    /// will allow `__entity` and `__extn` escapes to be implicit.
637    /// This method will also ensure that the added entities fully conform to the
638    /// schema -- for instance, it will error if attributes have the wrong types
639    /// (e.g., string instead of integer), or if required attributes are missing
640    /// or superfluous attributes are provided.
641    /// (This method will not add action entities from the `schema`.)
642    ///
643    /// Re-computing the transitive closure can be expensive, so it is advised
644    /// to not call this method in a loop.
645    ///
646    /// ## Errors
647    /// - [`EntitiesError::Duplicate`] if there is a pair of non-identical entities in `entities`
648    ///   with the same Entity UID, or there is an entity in `entities` with the same Entity UID as a
649    ///   non-identical entity in this structure
650    /// - [`EntitiesError::InvalidEntity`] if `schema` is not none and any entities do not conform
651    ///   to the schema
652    /// - [`EntitiesError::Deserialization`] if there are errors while parsing the json
653    pub fn add_entities_from_json_file(
654        self,
655        json: impl std::io::Read,
656        schema: Option<&Schema>,
657    ) -> Result<Self, EntitiesError> {
658        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
659        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
660            schema.as_ref(),
661            Extensions::all_available(),
662            cedar_policy_core::entities::TCComputation::ComputeNow,
663        );
664        let new_entities = eparser.iter_from_json_file(json)?.map(Arc::new);
665        Ok(Self(self.0.add_entities(
666            new_entities,
667            schema.as_ref(),
668            cedar_policy_core::entities::TCComputation::ComputeNow,
669            Extensions::all_available(),
670        )?))
671    }
672
673    /// Parse an entities JSON file (in `&str` form) into an `Entities` object
674    ///
675    /// `schema` represents a source of `Action` entities, which will be added
676    /// to the entities parsed from JSON.
677    /// (If any `Action` entities are present in the JSON, and a `schema` is
678    /// also provided, each `Action` entity in the JSON must exactly match its
679    /// definition in the schema or an error is returned.)
680    ///
681    /// If a `schema` is present, this will also inform the parsing: for
682    /// instance, it will allow `__entity` and `__extn` escapes to be implicit.
683    ///
684    /// Finally, if a `schema` is present, this function will ensure
685    /// that the produced entities fully conform to the `schema` -- for
686    /// instance, it will error if attributes have the wrong types (e.g., string
687    /// instead of integer), or if required attributes are missing or
688    /// superfluous attributes are provided.
689    ///
690    /// ## Errors
691    /// - [`EntitiesError::Duplicate`] if there are any duplicate entities in `entities`
692    /// - [`EntitiesError::InvalidEntity`] if `schema` is not none and any entities do not conform
693    ///   to the schema
694    /// - [`EntitiesError::Deserialization`] if there are errors while parsing the json
695    ///
696    /// ```
697    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, EvalResult, Request,PolicySet};
698    /// # use std::str::FromStr;
699    /// let data =r#"
700    /// [
701    /// {
702    ///   "uid": {"type":"User","id":"alice"},
703    ///   "attrs": {
704    ///     "age":19,
705    ///     "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
706    ///   },
707    ///   "parents": [{"type":"Group","id":"admin"}]
708    /// },
709    /// {
710    ///   "uid": {"type":"Group","id":"admin"},
711    ///   "attrs": {},
712    ///   "parents": []
713    /// }
714    /// ]
715    /// "#;
716    /// let entities = Entities::from_json_str(data, None).unwrap();
717    /// # let euid = EntityUid::from_str(r#"User::"alice""#).unwrap();
718    /// # let entity = entities.get(&euid).unwrap();
719    /// # assert_eq!(entity.attr("age").unwrap().unwrap(), EvalResult::Long(19));
720    /// # let ip = entity.attr("ip_addr").unwrap().unwrap();
721    /// # assert_eq!(ip, EvalResult::ExtensionValue("ip(\"10.0.1.101\")".to_string()));
722    /// ```
723    pub fn from_json_str(json: &str, schema: Option<&Schema>) -> Result<Self, EntitiesError> {
724        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
725        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
726            schema.as_ref(),
727            Extensions::all_available(),
728            cedar_policy_core::entities::TCComputation::ComputeNow,
729        );
730        eparser.from_json_str(json).map(Entities)
731    }
732
733    /// Parse an entities JSON file (in `serde_json::Value` form) into an
734    /// `Entities` object
735    ///
736    /// `schema` represents a source of `Action` entities, which will be added
737    /// to the entities parsed from JSON.
738    /// (If any `Action` entities are present in the JSON, and a `schema` is
739    /// also provided, each `Action` entity in the JSON must exactly match its
740    /// definition in the schema or an error is returned.)
741    ///
742    /// If a `schema` is present, this will also inform the parsing: for
743    /// instance, it will allow `__entity` and `__extn` escapes to be implicit.
744    ///
745    /// Finally, if a `schema` is present, this function will ensure
746    /// that the produced entities fully conform to the `schema` -- for
747    /// instance, it will error if attributes have the wrong types (e.g., string
748    /// instead of integer), or if required attributes are missing or
749    /// superfluous attributes are provided.
750    ///
751    /// ## Errors
752    /// - [`EntitiesError::Duplicate`] if there are any duplicate entities in `entities`
753    /// - [`EntitiesError::InvalidEntity`]if `schema` is not none and any entities do not conform
754    ///   to the schema
755    /// - [`EntitiesError::Deserialization`] if there are errors while parsing the json
756    ///
757    /// ```
758    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, EvalResult, Request,PolicySet};
759    /// let data =serde_json::json!(
760    /// [
761    /// {
762    ///   "uid": {"type":"User","id":"alice"},
763    ///   "attrs": {
764    ///     "age":19,
765    ///     "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
766    ///   },
767    ///   "parents": [{"type":"Group","id":"admin"}]
768    /// },
769    /// {
770    ///   "uid": {"type":"Group","id":"admin"},
771    ///   "attrs": {},
772    ///   "parents": []
773    /// }
774    /// ]
775    /// );
776    /// let entities = Entities::from_json_value(data, None).unwrap();
777    /// ```
778    pub fn from_json_value(
779        json: serde_json::Value,
780        schema: Option<&Schema>,
781    ) -> Result<Self, EntitiesError> {
782        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
783        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
784            schema.as_ref(),
785            Extensions::all_available(),
786            cedar_policy_core::entities::TCComputation::ComputeNow,
787        );
788        eparser.from_json_value(json).map(Entities)
789    }
790
791    /// Parse an entities JSON file (in `std::io::Read` form) into an `Entities`
792    /// object
793    ///
794    /// `schema` represents a source of `Action` entities, which will be added
795    /// to the entities parsed from JSON.
796    /// (If any `Action` entities are present in the JSON, and a `schema` is
797    /// also provided, each `Action` entity in the JSON must exactly match its
798    /// definition in the schema or an error is returned.)
799    ///
800    /// If a `schema` is present, this will also inform the parsing: for
801    /// instance, it will allow `__entity` and `__extn` escapes to be implicit.
802    ///
803    /// Finally, if a `schema` is present, this function will ensure
804    /// that the produced entities fully conform to the `schema` -- for
805    /// instance, it will error if attributes have the wrong types (e.g., string
806    /// instead of integer), or if required attributes are missing or
807    /// superfluous attributes are provided.
808    ///
809    /// ## Errors
810    /// - [`EntitiesError::Duplicate`] if there are any duplicate entities in `entities`
811    /// - [`EntitiesError::InvalidEntity`] if `schema` is not none and any entities do not conform
812    ///   to the schema
813    /// - [`EntitiesError::Deserialization`] if there are errors while parsing the json
814    pub fn from_json_file(
815        json: impl std::io::Read,
816        schema: Option<&Schema>,
817    ) -> Result<Self, EntitiesError> {
818        let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
819        let eparser = cedar_policy_core::entities::EntityJsonParser::new(
820            schema.as_ref(),
821            Extensions::all_available(),
822            cedar_policy_core::entities::TCComputation::ComputeNow,
823        );
824        eparser.from_json_file(json).map(Entities)
825    }
826
827    /// Is entity `a` an ancestor of entity `b`?
828    /// Same semantics as `b in a` in the Cedar language
829    pub fn is_ancestor_of(&self, a: &EntityUid, b: &EntityUid) -> bool {
830        match self.0.entity(b.as_ref()) {
831            Dereference::Data(b) => b.is_descendant_of(a.as_ref()),
832            _ => a == b, // if b doesn't exist, `b in a` is only true if `b == a`
833        }
834    }
835
836    /// Get an iterator over the ancestors of the given Euid.
837    /// Returns `None` if the given `Euid` does not exist.
838    pub fn ancestors<'a>(
839        &'a self,
840        euid: &EntityUid,
841    ) -> Option<impl Iterator<Item = &'a EntityUid>> {
842        let entity = match self.0.entity(euid.as_ref()) {
843            Dereference::Residual(_) | Dereference::NoSuchEntity => None,
844            Dereference::Data(e) => Some(e),
845        }?;
846        Some(entity.ancestors().map(EntityUid::ref_cast))
847    }
848
849    /// Returns the number of `Entity`s in the `Entities`
850    pub fn len(&self) -> usize {
851        self.0.len()
852    }
853
854    /// Returns true if the `Entities` contains no `Entity`s
855    pub fn is_empty(&self) -> bool {
856        self.0.is_empty()
857    }
858
859    /// Dump an `Entities` object into an entities JSON file.
860    ///
861    /// The resulting JSON will be suitable for parsing in via
862    /// `from_json_*`, and will be parse-able even with no `Schema`.
863    ///
864    /// To read an `Entities` object from an entities JSON file, use
865    /// `from_json_file`.
866    pub fn write_to_json(&self, f: impl std::io::Write) -> std::result::Result<(), EntitiesError> {
867        self.0.write_to_json(f)
868    }
869
870    #[doc = include_str!("../experimental_warning.md")]
871    /// Visualize an `Entities` object in the graphviz `dot`
872    /// format. Entity visualization is best-effort and not well tested.
873    /// Feel free to submit an issue if you are using this feature and would like it improved.
874    pub fn to_dot_str(&self) -> String {
875        let mut dot_str = String::new();
876        // PANIC SAFETY: Writing to the String `dot_str` cannot fail, so `to_dot_str` will not return an `Err` result.
877        #[allow(clippy::unwrap_used)]
878        self.0.to_dot_str(&mut dot_str).unwrap();
879        dot_str
880    }
881}
882
883/// Validates scope variables against the provided schema
884///
885/// Returns Ok(()) if the context is valid according to the schema, or an error otherwise
886///
887/// This validation is already handled by `Request::new`, so there is no need to separately call
888/// if you are validating the whole request
889pub fn validate_scope_variables(
890    principal: &EntityUid,
891    action: &EntityUid,
892    resource: &EntityUid,
893    schema: &Schema,
894) -> std::result::Result<(), RequestValidationError> {
895    Ok(RequestSchema::validate_scope_variables(
896        &schema.0,
897        Some(&principal.0),
898        Some(&action.0),
899        Some(&resource.0),
900    )?)
901}
902
903/// Utilities for defining `IntoIterator` over `Entities`
904pub mod entities {
905
906    /// `IntoIter` iterator for `Entities`
907    #[derive(Debug)]
908    pub struct IntoIter {
909        pub(super) inner: <cedar_policy_core::entities::Entities as IntoIterator>::IntoIter,
910    }
911
912    impl Iterator for IntoIter {
913        type Item = super::Entity;
914
915        fn next(&mut self) -> Option<Self::Item> {
916            self.inner.next().map(super::Entity)
917        }
918        fn size_hint(&self) -> (usize, Option<usize>) {
919            self.inner.size_hint()
920        }
921    }
922}
923
924impl IntoIterator for Entities {
925    type Item = Entity;
926    type IntoIter = entities::IntoIter;
927
928    fn into_iter(self) -> Self::IntoIter {
929        Self::IntoIter {
930            inner: self.0.into_iter(),
931        }
932    }
933}
934
935/// Authorizer object, which provides responses to authorization queries
936#[repr(transparent)]
937#[derive(Debug, Clone, RefCast)]
938pub struct Authorizer(authorizer::Authorizer);
939
940#[doc(hidden)] // because this converts to a private/internal type
941impl AsRef<authorizer::Authorizer> for Authorizer {
942    fn as_ref(&self) -> &authorizer::Authorizer {
943        &self.0
944    }
945}
946
947impl Default for Authorizer {
948    fn default() -> Self {
949        Self::new()
950    }
951}
952
953impl Authorizer {
954    /// Create a new `Authorizer`
955    ///
956    /// The authorizer uses the `stacker` crate to manage stack size and tries to use a sane default.
957    /// If the default is not right for you, you can try wrapping the authorizer or individual calls
958    /// to `is_authorized` in `stacker::grow`.
959    /// Note that on platforms not supported by `stacker` (e.g., Wasm, Android),
960    /// the authorizer will simply assume that the stack size is sufficient. As a result, large inputs
961    /// may result in stack overflows and crashing the process.
962    /// But on all platforms supported by `stacker` (Linux, macOS, ...), Cedar will return the
963    /// graceful error `RecursionLimit` instead of crashing.
964    /// ```
965    /// # use cedar_policy::{Authorizer, Context, Entities, EntityId, EntityTypeName,
966    /// # EntityUid, Request,PolicySet};
967    /// # use std::str::FromStr;
968    /// # // create a request
969    /// # let p_eid = EntityId::from_str("alice").unwrap();
970    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
971    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
972    /// #
973    /// # let a_eid = EntityId::from_str("view").unwrap();
974    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
975    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
976    /// #
977    /// # let r_eid = EntityId::from_str("trip").unwrap();
978    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
979    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
980    /// #
981    /// # let c = Context::empty();
982    /// #
983    /// # let request: Request = Request::new(p, a, r, c, None).unwrap();
984    /// #
985    /// # // create a policy
986    /// # let s = r#"permit(
987    /// #     principal == User::"alice",
988    /// #     action == Action::"view",
989    /// #     resource == Album::"trip"
990    /// #   )when{
991    /// #     principal.ip_addr.isIpv4()
992    /// #   };
993    /// # "#;
994    /// # let policy = PolicySet::from_str(s).expect("policy error");
995    /// # // create entities
996    /// # let e = r#"[
997    /// #     {
998    /// #         "uid": {"type":"User","id":"alice"},
999    /// #         "attrs": {
1000    /// #             "age":19,
1001    /// #             "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
1002    /// #         },
1003    /// #         "parents": []
1004    /// #     }
1005    /// # ]"#;
1006    /// # let entities = Entities::from_json_str(e, None).expect("entity error");
1007    /// let authorizer = Authorizer::new();
1008    /// let r = authorizer.is_authorized(&request, &policy, &entities);
1009    /// ```
1010    pub fn new() -> Self {
1011        Self(authorizer::Authorizer::new())
1012    }
1013
1014    /// Returns an authorization response for `r` with respect to the given
1015    /// `PolicySet` and `Entities`.
1016    ///
1017    /// The language spec and formal model give a precise definition of how this
1018    /// is computed.
1019    /// ```
1020    /// # use cedar_policy::{Authorizer,Context,Decision,Entities,EntityId,EntityTypeName, EntityUid, Request,PolicySet};
1021    /// # use std::str::FromStr;
1022    /// // create a request
1023    /// let p_eid = EntityId::from_str("alice").unwrap();
1024    /// let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
1025    /// let p = EntityUid::from_type_name_and_id(p_name, p_eid);
1026    ///
1027    /// let a_eid = EntityId::from_str("view").unwrap();
1028    /// let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
1029    /// let a = EntityUid::from_type_name_and_id(a_name, a_eid);
1030    ///
1031    /// let r_eid = EntityId::from_str("trip").unwrap();
1032    /// let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
1033    /// let r = EntityUid::from_type_name_and_id(r_name, r_eid);
1034    ///
1035    /// let c = Context::empty();
1036    ///
1037    /// let request: Request = Request::new(p, a, r, c, None).unwrap();
1038    ///
1039    /// // create a policy
1040    /// let s = r#"
1041    /// permit (
1042    ///   principal == User::"alice",
1043    ///   action == Action::"view",
1044    ///   resource == Album::"trip"
1045    /// )
1046    /// when { principal.ip_addr.isIpv4() };
1047    /// "#;
1048    /// let policy = PolicySet::from_str(s).expect("policy error");
1049    ///
1050    /// // create entities
1051    /// let e = r#"[
1052    ///     {
1053    ///         "uid": {"type":"User","id":"alice"},
1054    ///         "attrs": {
1055    ///             "age":19,
1056    ///             "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
1057    ///         },
1058    ///         "parents": []
1059    ///     }
1060    /// ]"#;
1061    /// let entities = Entities::from_json_str(e, None).expect("entity error");
1062    ///
1063    /// let authorizer = Authorizer::new();
1064    /// let response = authorizer.is_authorized(&request, &policy, &entities);
1065    /// assert_eq!(response.decision(), Decision::Allow);
1066    /// ```
1067    pub fn is_authorized(&self, r: &Request, p: &PolicySet, e: &Entities) -> Response {
1068        self.0.is_authorized(r.0.clone(), &p.ast, &e.0).into()
1069    }
1070
1071    /// A partially evaluated authorization request.
1072    /// The Authorizer will attempt to make as much progress as possible in the presence of unknowns.
1073    /// If the Authorizer can reach a response, it will return that response.
1074    /// Otherwise, it will return a list of residual policies that still need to be evaluated.
1075    #[doc = include_str!("../experimental_warning.md")]
1076    #[cfg(feature = "partial-eval")]
1077    pub fn is_authorized_partial(
1078        &self,
1079        query: &Request,
1080        policy_set: &PolicySet,
1081        entities: &Entities,
1082    ) -> PartialResponse {
1083        let response = self
1084            .0
1085            .is_authorized_core(query.0.clone(), &policy_set.ast, &entities.0);
1086        PartialResponse(response)
1087    }
1088}
1089
1090/// Authorization response returned from the `Authorizer`
1091#[derive(Debug, PartialEq, Eq, Clone)]
1092pub struct Response {
1093    /// Authorization decision
1094    pub(crate) decision: Decision,
1095    /// Diagnostics providing more information on how this decision was reached
1096    pub(crate) diagnostics: Diagnostics,
1097}
1098
1099/// A partially evaluated authorization response.
1100///
1101/// Splits the results into several categories: satisfied, false, and residual for each policy effect.
1102/// Also tracks all the errors that were encountered during evaluation.
1103#[doc = include_str!("../experimental_warning.md")]
1104#[cfg(feature = "partial-eval")]
1105#[repr(transparent)]
1106#[derive(Debug, Clone, RefCast)]
1107pub struct PartialResponse(cedar_policy_core::authorizer::PartialResponse);
1108
1109#[cfg(feature = "partial-eval")]
1110impl PartialResponse {
1111    /// Attempt to reach a partial decision; the presence of residuals may result in returning [`None`],
1112    /// indicating that a decision could not be reached given the unknowns
1113    pub fn decision(&self) -> Option<Decision> {
1114        self.0.decision()
1115    }
1116
1117    /// Convert this response into a concrete evaluation response.
1118    /// All residuals are treated as errors
1119    pub fn concretize(self) -> Response {
1120        self.0.concretize().into()
1121    }
1122
1123    /// Returns the set of [`Policy`]s that were definitely satisfied.
1124    /// This will be the set of policies (both `permit` and `forbid`) that evaluated to `true`
1125    pub fn definitely_satisfied(&self) -> impl Iterator<Item = Policy> + '_ {
1126        self.0.definitely_satisfied().map(Policy::from_ast)
1127    }
1128
1129    /// Returns the set of [`PolicyId`]s that encountered errors
1130    pub fn definitely_errored(&self) -> impl Iterator<Item = &PolicyId> {
1131        self.0.definitely_errored().map(PolicyId::ref_cast)
1132    }
1133
1134    /// Returns an over-approximation of the set of determining policies
1135    ///
1136    /// This is all policies that may be determining for any substitution of the unknowns.
1137    /// Policies not in this set will not affect the final decision, regardless of any
1138    /// substitutions.
1139    ///
1140    /// For more information on what counts as "determining" see: <https://docs.cedarpolicy.com/auth/authorization.html#request-authorization>
1141    pub fn may_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
1142        self.0.may_be_determining().map(Policy::from_ast)
1143    }
1144
1145    /// Returns an under-approximation of the set of determining policies
1146    ///
1147    /// This is all policies that must be determining for all possible substitutions of the unknowns.
1148    /// This set will include policies that evaluated to `true` and are guaranteed to be
1149    /// contributing to the final authorization decision.
1150    ///
1151    /// For more information on what counts as "determining" see: <https://docs.cedarpolicy.com/auth/authorization.html#request-authorization>
1152    pub fn must_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
1153        self.0.must_be_determining().map(Policy::from_ast)
1154    }
1155
1156    /// Returns the set of non-trivial (meaning more than just `true` or `false`) residuals expressions
1157    pub fn nontrivial_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
1158        self.0.nontrivial_residuals().map(Policy::from_ast)
1159    }
1160
1161    /// Returns every policy as a residual expression
1162    pub fn all_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
1163        self.0.all_residuals().map(Policy::from_ast)
1164    }
1165
1166    /// Returns all unknown entities during the evaluation of the response
1167    pub fn unknown_entities(&self) -> HashSet<EntityUid> {
1168        let mut entity_uids = HashSet::new();
1169        for policy in self.0.all_residuals() {
1170            entity_uids.extend(policy.unknown_entities().into_iter().map(Into::into));
1171        }
1172        entity_uids
1173    }
1174
1175    /// Return the residual for a given [`PolicyId`], if it exists in the response
1176    pub fn get(&self, id: &PolicyId) -> Option<Policy> {
1177        self.0.get(id.as_ref()).map(Policy::from_ast)
1178    }
1179
1180    /// Attempt to re-authorize this response given a mapping from unknowns to values.
1181    #[allow(clippy::needless_pass_by_value)]
1182    #[deprecated = "use reauthorize_with_bindings"]
1183    pub fn reauthorize(
1184        &self,
1185        mapping: HashMap<SmolStr, RestrictedExpression>,
1186        auth: &Authorizer,
1187        es: &Entities,
1188    ) -> Result<Self, ReauthorizationError> {
1189        self.reauthorize_with_bindings(mapping.iter().map(|(k, v)| (k.as_str(), v)), auth, es)
1190    }
1191
1192    /// Attempt to re-authorize this response given a mapping from unknowns to values, provided as an iterator.
1193    /// Exhausts the iterator, returning any evaluation errors in the restricted expressions, regardless whether there is a matching unknown.
1194    pub fn reauthorize_with_bindings<'m>(
1195        &self,
1196        mapping: impl IntoIterator<Item = (&'m str, &'m RestrictedExpression)>,
1197        auth: &Authorizer,
1198        es: &Entities,
1199    ) -> Result<Self, ReauthorizationError> {
1200        let exts = Extensions::all_available();
1201        let evaluator = RestrictedEvaluator::new(exts);
1202        let mapping = mapping
1203            .into_iter()
1204            .map(|(name, expr)| {
1205                evaluator
1206                    .interpret(BorrowedRestrictedExpr::new_unchecked(expr.0.as_ref()))
1207                    .map(|v| (name.into(), v))
1208            })
1209            .collect::<Result<HashMap<_, _>, EvaluationError>>()?;
1210        let r = self.0.reauthorize(&mapping, &auth.0, &es.0)?;
1211        Ok(Self(r))
1212    }
1213}
1214
1215#[cfg(feature = "partial-eval")]
1216#[doc(hidden)]
1217impl From<cedar_policy_core::authorizer::PartialResponse> for PartialResponse {
1218    fn from(pr: cedar_policy_core::authorizer::PartialResponse) -> Self {
1219        Self(pr)
1220    }
1221}
1222
1223/// Diagnostics providing more information on how a `Decision` was reached
1224#[derive(Debug, PartialEq, Eq, Clone)]
1225pub struct Diagnostics {
1226    /// `PolicyId`s of the policies that contributed to the decision.
1227    /// If no policies applied to the request, this set will be empty.
1228    reason: HashSet<PolicyId>,
1229    /// Errors that occurred during authorization. The errors should be
1230    /// treated as unordered, since policies may be evaluated in any order.
1231    errors: Vec<AuthorizationError>,
1232}
1233
1234#[doc(hidden)]
1235impl From<authorizer::Diagnostics> for Diagnostics {
1236    fn from(diagnostics: authorizer::Diagnostics) -> Self {
1237        Self {
1238            reason: diagnostics.reason.into_iter().map(PolicyId::new).collect(),
1239            errors: diagnostics.errors.into_iter().map(Into::into).collect(),
1240        }
1241    }
1242}
1243
1244impl Diagnostics {
1245    /// Get the `PolicyId`s of the policies that contributed to the decision.
1246    /// If no policies applied to the request, this set will be empty.
1247    /// ```
1248    /// # use cedar_policy::{Authorizer, Context, Decision, Entities, EntityId, EntityTypeName,
1249    /// # EntityUid, Request,PolicySet};
1250    /// # use std::str::FromStr;
1251    /// # // create a request
1252    /// # let p_eid = EntityId::from_str("alice").unwrap();
1253    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
1254    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
1255    /// #
1256    /// # let a_eid = EntityId::from_str("view").unwrap();
1257    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
1258    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
1259    /// #
1260    /// # let r_eid = EntityId::from_str("trip").unwrap();
1261    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
1262    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
1263    /// #
1264    /// # let c = Context::empty();
1265    /// #
1266    /// # let request: Request = Request::new(p, a, r, c, None).unwrap();
1267    /// #
1268    /// # // create a policy
1269    /// # let s = r#"permit(
1270    /// #     principal == User::"alice",
1271    /// #     action == Action::"view",
1272    /// #     resource == Album::"trip"
1273    /// #   )when{
1274    /// #     principal.ip_addr.isIpv4()
1275    /// #   };
1276    /// # "#;
1277    /// # let policy = PolicySet::from_str(s).expect("policy error");
1278    /// # // create entities
1279    /// # let e = r#"[
1280    /// #     {
1281    /// #         "uid": {"type":"User","id":"alice"},
1282    /// #         "attrs": {
1283    /// #             "age":19,
1284    /// #             "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
1285    /// #         },
1286    /// #         "parents": []
1287    /// #     }
1288    /// # ]"#;
1289    /// # let entities = Entities::from_json_str(e, None).expect("entity error");
1290    /// let authorizer = Authorizer::new();
1291    /// let response = authorizer.is_authorized(&request, &policy, &entities);
1292    /// match response.decision() {
1293    ///     Decision::Allow => println!("ALLOW"),
1294    ///     Decision::Deny => println!("DENY"),
1295    /// }
1296    /// println!("note: this decision was due to the following policies:");
1297    /// for reason in response.diagnostics().reason() {
1298    ///     println!("{}", reason);
1299    /// }
1300    /// ```
1301    pub fn reason(&self) -> impl Iterator<Item = &PolicyId> {
1302        self.reason.iter()
1303    }
1304
1305    /// Get the errors that occurred during authorization. The errors should be
1306    /// treated as unordered, since policies may be evaluated in any order.
1307    /// ```
1308    /// # use cedar_policy::{Authorizer, Context, Decision, Entities, EntityId, EntityTypeName,
1309    /// # EntityUid, Request,PolicySet};
1310    /// # use std::str::FromStr;
1311    /// # // create a request
1312    /// # let p_eid = EntityId::from_str("alice").unwrap();
1313    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
1314    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
1315    /// #
1316    /// # let a_eid = EntityId::from_str("view").unwrap();
1317    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
1318    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
1319    /// #
1320    /// # let r_eid = EntityId::from_str("trip").unwrap();
1321    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
1322    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
1323    /// #
1324    /// # let c = Context::empty();
1325    /// #
1326    /// # let request: Request = Request::new(p, a, r, c, None).unwrap();
1327    /// #
1328    /// # // create a policy
1329    /// # let s = r#"permit(
1330    /// #     principal == User::"alice",
1331    /// #     action == Action::"view",
1332    /// #     resource == Album::"trip"
1333    /// #   )when{
1334    /// #     principal.ip_addr.isIpv4()
1335    /// #   };
1336    /// # "#;
1337    /// # let policy = PolicySet::from_str(s).expect("policy error");
1338    /// # // create entities
1339    /// # let e = r#"[
1340    /// #     {
1341    /// #         "uid": {"type":"User","id":"alice"},
1342    /// #         "attrs": {
1343    /// #             "age":19,
1344    /// #             "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
1345    /// #         },
1346    /// #         "parents": []
1347    /// #     }
1348    /// # ]"#;
1349    /// # let entities = Entities::from_json_str(e, None).expect("entity error");
1350    /// let authorizer = Authorizer::new();
1351    /// let response = authorizer.is_authorized(&request, &policy, &entities);
1352    /// match response.decision() {
1353    ///     Decision::Allow => println!("ALLOW"),
1354    ///     Decision::Deny => println!("DENY"),
1355    /// }
1356    /// for err in response.diagnostics().errors() {
1357    ///     println!("{}", err);
1358    /// }
1359    /// ```
1360    pub fn errors(&self) -> impl Iterator<Item = &AuthorizationError> + '_ {
1361        self.errors.iter()
1362    }
1363
1364    /// Consume the `Diagnostics`, producing owned versions of `reason()` and `errors()`
1365    pub(crate) fn into_components(
1366        self,
1367    ) -> (
1368        impl Iterator<Item = PolicyId>,
1369        impl Iterator<Item = AuthorizationError>,
1370    ) {
1371        (self.reason.into_iter(), self.errors.into_iter())
1372    }
1373}
1374
1375impl Response {
1376    /// Create a new `Response`
1377    pub fn new(
1378        decision: Decision,
1379        reason: HashSet<PolicyId>,
1380        errors: Vec<AuthorizationError>,
1381    ) -> Self {
1382        Self {
1383            decision,
1384            diagnostics: Diagnostics { reason, errors },
1385        }
1386    }
1387
1388    /// Get the authorization decision
1389    pub fn decision(&self) -> Decision {
1390        self.decision
1391    }
1392
1393    /// Get the authorization diagnostics
1394    pub fn diagnostics(&self) -> &Diagnostics {
1395        &self.diagnostics
1396    }
1397}
1398
1399#[doc(hidden)]
1400impl From<authorizer::Response> for Response {
1401    fn from(a: authorizer::Response) -> Self {
1402        Self {
1403            decision: a.decision,
1404            diagnostics: a.diagnostics.into(),
1405        }
1406    }
1407}
1408
1409/// Used to select how a policy will be validated.
1410#[derive(Default, Eq, PartialEq, Copy, Clone, Debug, Serialize, Deserialize)]
1411#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1412#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1413#[serde(rename_all = "camelCase")]
1414#[non_exhaustive]
1415pub enum ValidationMode {
1416    /// Validate that policies do not contain any type errors, and additionally
1417    /// have a restricted form which is amenable for analysis.
1418    #[default]
1419    Strict,
1420    /// Validate that policies do not contain any type errors.
1421    #[doc = include_str!("../experimental_warning.md")]
1422    #[cfg(feature = "permissive-validate")]
1423    Permissive,
1424    /// Validate using a partial schema. Policies may contain type errors.
1425    #[doc = include_str!("../experimental_warning.md")]
1426    #[cfg(feature = "partial-validate")]
1427    Partial,
1428}
1429
1430#[doc(hidden)]
1431impl From<ValidationMode> for cedar_policy_core::validator::ValidationMode {
1432    fn from(mode: ValidationMode) -> Self {
1433        match mode {
1434            ValidationMode::Strict => Self::Strict,
1435            #[cfg(feature = "permissive-validate")]
1436            ValidationMode::Permissive => Self::Permissive,
1437            #[cfg(feature = "partial-validate")]
1438            ValidationMode::Partial => Self::Partial,
1439        }
1440    }
1441}
1442
1443/// Validator object, which provides policy validation and typechecking.
1444#[repr(transparent)]
1445#[derive(Debug, Clone, RefCast)]
1446pub struct Validator(cedar_policy_core::validator::Validator);
1447
1448#[doc(hidden)] // because this converts to a private/internal type
1449impl AsRef<cedar_policy_core::validator::Validator> for Validator {
1450    fn as_ref(&self) -> &cedar_policy_core::validator::Validator {
1451        &self.0
1452    }
1453}
1454
1455impl Validator {
1456    /// Construct a new `Validator` to validate policies using the given
1457    /// `Schema`.
1458    pub fn new(schema: Schema) -> Self {
1459        Self(cedar_policy_core::validator::Validator::new(schema.0))
1460    }
1461
1462    /// Get the `Schema` this `Validator` is using.
1463    pub fn schema(&self) -> &Schema {
1464        RefCast::ref_cast(self.0.schema())
1465    }
1466
1467    /// Validate all policies in a policy set, collecting all validation errors
1468    /// found into the returned `ValidationResult`. Each error is returned together with the
1469    /// policy id of the policy where the error was found. If a policy id
1470    /// included in the input policy set does not appear in the output iterator, then
1471    /// that policy passed the validator. If the function `validation_passed`
1472    /// returns true, then there were no validation errors found, so all
1473    /// policies in the policy set have passed the validator.
1474    pub fn validate(&self, pset: &PolicySet, mode: ValidationMode) -> ValidationResult {
1475        ValidationResult::from(self.0.validate(&pset.ast, mode.into()))
1476    }
1477
1478    /// Validate all policies in a policy set, collecting all validation errors
1479    /// found into the returned `ValidationResult`. If validation passes, run level
1480    /// validation (RFC 76). Each error is returned together with the policy id of the policy
1481    /// where the error was found. If a policy id included in the input policy set does not
1482    /// appear in the output iterator, then that policy passed the validator. If the function
1483    /// `validation_passed` returns true, then there were no validation errors found, so
1484    /// all policies in the policy set have passed the validator.
1485    pub fn validate_with_level(
1486        &self,
1487        pset: &PolicySet,
1488        mode: ValidationMode,
1489        max_deref_level: u32,
1490    ) -> ValidationResult {
1491        ValidationResult::from(
1492            self.0
1493                .validate_with_level(&pset.ast, mode.into(), max_deref_level),
1494        )
1495    }
1496}
1497
1498/// Contains all the type information used to construct a `Schema` that can be
1499/// used to validate a policy.
1500#[derive(Debug, Clone)]
1501pub struct SchemaFragment {
1502    value: cedar_policy_core::validator::ValidatorSchemaFragment<
1503        cedar_policy_core::validator::ConditionalName,
1504        cedar_policy_core::validator::ConditionalName,
1505    >,
1506    lossless:
1507        cedar_policy_core::validator::json_schema::Fragment<cedar_policy_core::validator::RawName>,
1508}
1509
1510#[doc(hidden)] // because this converts to a private/internal type
1511impl
1512    AsRef<
1513        cedar_policy_core::validator::ValidatorSchemaFragment<
1514            cedar_policy_core::validator::ConditionalName,
1515            cedar_policy_core::validator::ConditionalName,
1516        >,
1517    > for SchemaFragment
1518{
1519    fn as_ref(
1520        &self,
1521    ) -> &cedar_policy_core::validator::ValidatorSchemaFragment<
1522        cedar_policy_core::validator::ConditionalName,
1523        cedar_policy_core::validator::ConditionalName,
1524    > {
1525        &self.value
1526    }
1527}
1528
1529#[doc(hidden)] // because this converts from a private/internal type
1530impl
1531    TryFrom<
1532        cedar_policy_core::validator::json_schema::Fragment<cedar_policy_core::validator::RawName>,
1533    > for SchemaFragment
1534{
1535    type Error = SchemaError;
1536    fn try_from(
1537        json_frag: cedar_policy_core::validator::json_schema::Fragment<
1538            cedar_policy_core::validator::RawName,
1539        >,
1540    ) -> Result<Self, Self::Error> {
1541        Ok(Self {
1542            value: json_frag.clone().try_into()?,
1543            lossless: json_frag,
1544        })
1545    }
1546}
1547
1548fn get_annotation_by_key(
1549    annotations: &est::Annotations,
1550    annotation_key: impl AsRef<str>,
1551) -> Option<&str> {
1552    annotations
1553        .0
1554        .get(&annotation_key.as_ref().parse().ok()?)
1555        .map(|value| annotation_value_to_str_ref(value.as_ref()))
1556}
1557
1558fn annotation_value_to_str_ref(value: Option<&ast::Annotation>) -> &str {
1559    value.map_or("", |a| a.as_ref())
1560}
1561
1562fn annotations_to_pairs(annotations: &est::Annotations) -> impl Iterator<Item = (&str, &str)> {
1563    annotations
1564        .0
1565        .iter()
1566        .map(|(key, value)| (key.as_ref(), annotation_value_to_str_ref(value.as_ref())))
1567}
1568
1569impl SchemaFragment {
1570    /// Get annotations of a non-empty namespace.
1571    ///
1572    /// We do not allow namespace-level annotations on the empty namespace.
1573    ///
1574    /// Returns `None` if `namespace` is not found in the [`SchemaFragment`]
1575    pub fn namespace_annotations(
1576        &self,
1577        namespace: EntityNamespace,
1578    ) -> Option<impl Iterator<Item = (&str, &str)>> {
1579        self.lossless
1580            .0
1581            .get(&Some(namespace.0))
1582            .map(|ns_def| annotations_to_pairs(&ns_def.annotations))
1583    }
1584
1585    /// Get annotation value of a non-empty namespace by annotation key
1586    /// `annotation_key`
1587    ///
1588    /// We do not allow namespace-level annotations on the empty namespace.
1589    ///
1590    /// Returns `None` if `namespace` is not found in the [`SchemaFragment`]
1591    /// or `annotation_key` is not a valid annotation key
1592    /// or it does not exist
1593    pub fn namespace_annotation(
1594        &self,
1595        namespace: EntityNamespace,
1596        annotation_key: impl AsRef<str>,
1597    ) -> Option<&str> {
1598        let ns = self.lossless.0.get(&Some(namespace.0))?;
1599        get_annotation_by_key(&ns.annotations, annotation_key)
1600    }
1601
1602    /// Get annotations of a common type declaration
1603    ///
1604    /// Returns `None` if `namespace` is not found in the [`SchemaFragment`] or
1605    /// `ty` is not a valid common type ID or `ty` is not found in the
1606    /// corresponding namespace definition
1607    pub fn common_type_annotations(
1608        &self,
1609        namespace: Option<EntityNamespace>,
1610        ty: &str,
1611    ) -> Option<impl Iterator<Item = (&str, &str)>> {
1612        let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1613        let ty = json_schema::CommonTypeId::new(ast::UnreservedId::from_normalized_str(ty).ok()?)
1614            .ok()?;
1615        ns_def
1616            .common_types
1617            .get(&ty)
1618            .map(|ty| annotations_to_pairs(&ty.annotations))
1619    }
1620
1621    /// Get annotation value of a common type declaration by annotation key
1622    /// `annotation_key`
1623    ///
1624    /// Returns `None` if `namespace` is not found in the [`SchemaFragment`]
1625    /// or `ty` is not a valid common type ID
1626    /// or `ty` is not found in the corresponding namespace definition
1627    /// or `annotation_key` is not a valid annotation key
1628    /// or it does not exist
1629    pub fn common_type_annotation(
1630        &self,
1631        namespace: Option<EntityNamespace>,
1632        ty: &str,
1633        annotation_key: impl AsRef<str>,
1634    ) -> Option<&str> {
1635        let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1636        let ty = json_schema::CommonTypeId::new(ast::UnreservedId::from_normalized_str(ty).ok()?)
1637            .ok()?;
1638        get_annotation_by_key(&ns_def.common_types.get(&ty)?.annotations, annotation_key)
1639    }
1640
1641    /// Get annotations of an entity type declaration
1642    ///
1643    /// Returns `None` if `namespace` is not found in the [`SchemaFragment`] or
1644    /// `ty` is not a valid entity type name or `ty` is not found in the
1645    /// corresponding namespace definition
1646    pub fn entity_type_annotations(
1647        &self,
1648        namespace: Option<EntityNamespace>,
1649        ty: &str,
1650    ) -> Option<impl Iterator<Item = (&str, &str)>> {
1651        let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1652        let ty = ast::UnreservedId::from_normalized_str(ty).ok()?;
1653        ns_def
1654            .entity_types
1655            .get(&ty)
1656            .map(|ty| annotations_to_pairs(&ty.annotations))
1657    }
1658
1659    /// Get annotation value of an entity type declaration by annotation key
1660    /// `annotation_key`
1661    ///
1662    /// Returns `None` if `namespace` is not found in the [`SchemaFragment`]
1663    /// or `ty` is not a valid entity type name
1664    /// or `ty` is not found in the corresponding namespace definition
1665    /// or `annotation_key` is not a valid annotation key
1666    /// or it does not exist
1667    pub fn entity_type_annotation(
1668        &self,
1669        namespace: Option<EntityNamespace>,
1670        ty: &str,
1671        annotation_key: impl AsRef<str>,
1672    ) -> Option<&str> {
1673        let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1674        let ty = ast::UnreservedId::from_normalized_str(ty).ok()?;
1675        get_annotation_by_key(&ns_def.entity_types.get(&ty)?.annotations, annotation_key)
1676    }
1677
1678    /// Get annotations of an action declaration
1679    ///
1680    /// Returns `None` if `namespace` is not found in the [`SchemaFragment`] or
1681    /// `id` is not found in the corresponding namespace definition
1682    pub fn action_annotations(
1683        &self,
1684        namespace: Option<EntityNamespace>,
1685        id: &EntityId,
1686    ) -> Option<impl Iterator<Item = (&str, &str)>> {
1687        let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1688        ns_def
1689            .actions
1690            .get(id.unescaped())
1691            .map(|a| annotations_to_pairs(&a.annotations))
1692    }
1693
1694    /// Get annotation value of an action declaration by annotation key
1695    /// `annotation_key`
1696    ///
1697    /// Returns `None` if `namespace` is not found in the [`SchemaFragment`]
1698    /// or `id` is not found in the corresponding namespace definition
1699    /// or `annotation_key` is not a valid annotation key
1700    /// or it does not exist
1701    pub fn action_annotation(
1702        &self,
1703        namespace: Option<EntityNamespace>,
1704        id: &EntityId,
1705        annotation_key: impl AsRef<str>,
1706    ) -> Option<&str> {
1707        let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1708        get_annotation_by_key(
1709            &ns_def.actions.get(id.unescaped())?.annotations,
1710            annotation_key,
1711        )
1712    }
1713
1714    /// Extract namespaces defined in this [`SchemaFragment`].
1715    ///
1716    /// `None` indicates the empty namespace.
1717    pub fn namespaces(&self) -> impl Iterator<Item = Option<EntityNamespace>> + '_ {
1718        self.value.namespaces().filter_map(|ns| {
1719            match ns.map(|ns| ast::Name::try_from(ns.clone())) {
1720                Some(Ok(n)) => Some(Some(EntityNamespace(n))),
1721                None => Some(None), // empty namespace, which we want to surface to the user
1722                Some(Err(_)) => {
1723                    // if the `SchemaFragment` contains namespaces with
1724                    // reserved `__cedar` components, that's an internal
1725                    // implementation detail; hide that from the user.
1726                    // Also note that `EntityNamespace` is backed by `Name`
1727                    // which can't even contain names with reserved
1728                    // `__cedar` components.
1729                    None
1730                }
1731            }
1732        })
1733    }
1734
1735    /// Create a [`SchemaFragment`] from a string containing JSON in the
1736    /// JSON schema format.
1737    pub fn from_json_str(src: &str) -> Result<Self, SchemaError> {
1738        let lossless = cedar_policy_core::validator::json_schema::Fragment::from_json_str(src)?;
1739        Ok(Self {
1740            value: lossless.clone().try_into()?,
1741            lossless,
1742        })
1743    }
1744
1745    /// Create a [`SchemaFragment`] from a JSON value (which should be an
1746    /// object of the shape required for the JSON schema format).
1747    pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
1748        let lossless = cedar_policy_core::validator::json_schema::Fragment::from_json_value(json)?;
1749        Ok(Self {
1750            value: lossless.clone().try_into()?,
1751            lossless,
1752        })
1753    }
1754
1755    /// Parse a [`SchemaFragment`] from a reader containing the Cedar schema syntax
1756    pub fn from_cedarschema_file(
1757        r: impl std::io::Read,
1758    ) -> Result<(Self, impl Iterator<Item = SchemaWarning>), CedarSchemaError> {
1759        let (lossless, warnings) =
1760            cedar_policy_core::validator::json_schema::Fragment::from_cedarschema_file(
1761                r,
1762                Extensions::all_available(),
1763            )?;
1764        Ok((
1765            Self {
1766                value: lossless.clone().try_into()?,
1767                lossless,
1768            },
1769            warnings,
1770        ))
1771    }
1772
1773    /// Parse a [`SchemaFragment`] from a string containing the Cedar schema syntax
1774    pub fn from_cedarschema_str(
1775        src: &str,
1776    ) -> Result<(Self, impl Iterator<Item = SchemaWarning>), CedarSchemaError> {
1777        let (lossless, warnings) =
1778            cedar_policy_core::validator::json_schema::Fragment::from_cedarschema_str(
1779                src,
1780                Extensions::all_available(),
1781            )?;
1782        Ok((
1783            Self {
1784                value: lossless.clone().try_into()?,
1785                lossless,
1786            },
1787            warnings,
1788        ))
1789    }
1790
1791    /// Create a [`SchemaFragment`] directly from a JSON file (which should
1792    /// contain an object of the shape required for the JSON schema format).
1793    pub fn from_json_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
1794        let lossless = cedar_policy_core::validator::json_schema::Fragment::from_json_file(file)?;
1795        Ok(Self {
1796            value: lossless.clone().try_into()?,
1797            lossless,
1798        })
1799    }
1800
1801    /// Serialize this [`SchemaFragment`] as a JSON value
1802    pub fn to_json_value(self) -> Result<serde_json::Value, SchemaError> {
1803        serde_json::to_value(self.lossless).map_err(|e| SchemaError::JsonSerialization(e.into()))
1804    }
1805
1806    /// Serialize this [`SchemaFragment`] as a JSON string
1807    pub fn to_json_string(&self) -> Result<String, SchemaError> {
1808        serde_json::to_string(&self.lossless).map_err(|e| SchemaError::JsonSerialization(e.into()))
1809    }
1810
1811    /// Serialize this [`SchemaFragment`] into a string in the Cedar schema
1812    /// syntax
1813    pub fn to_cedarschema(&self) -> Result<String, ToCedarSchemaError> {
1814        let str = self.lossless.to_cedarschema()?;
1815        Ok(str)
1816    }
1817}
1818
1819impl TryInto<Schema> for SchemaFragment {
1820    type Error = SchemaError;
1821
1822    /// Convert [`SchemaFragment`] into a [`Schema`]. To build the [`Schema`] we
1823    /// need to have all entity types defined, so an error will be returned if
1824    /// any undeclared entity types are referenced in the schema fragment.
1825    fn try_into(self) -> Result<Schema, Self::Error> {
1826        Ok(Schema(
1827            cedar_policy_core::validator::ValidatorSchema::from_schema_fragments(
1828                [self.value],
1829                Extensions::all_available(),
1830            )?,
1831        ))
1832    }
1833}
1834
1835impl FromStr for SchemaFragment {
1836    type Err = CedarSchemaError;
1837    /// Construct [`SchemaFragment`] from a string containing a schema formatted
1838    /// in the Cedar schema format. This can fail if the string is not a valid
1839    /// schema. This function does not check for consistency in the schema
1840    /// (e.g., references to undefined entities) because this is not required
1841    /// until a `Schema` is constructed.
1842    fn from_str(src: &str) -> Result<Self, Self::Err> {
1843        Self::from_cedarschema_str(src).map(|(frag, _)| frag)
1844    }
1845}
1846
1847/// Object containing schema information used by the validator.
1848#[repr(transparent)]
1849#[derive(Debug, Clone, RefCast)]
1850pub struct Schema(pub(crate) cedar_policy_core::validator::ValidatorSchema);
1851
1852#[doc(hidden)] // because this converts to a private/internal type
1853impl AsRef<cedar_policy_core::validator::ValidatorSchema> for Schema {
1854    fn as_ref(&self) -> &cedar_policy_core::validator::ValidatorSchema {
1855        &self.0
1856    }
1857}
1858
1859#[doc(hidden)]
1860impl From<cedar_policy_core::validator::ValidatorSchema> for Schema {
1861    fn from(schema: cedar_policy_core::validator::ValidatorSchema) -> Self {
1862        Self(schema)
1863    }
1864}
1865
1866impl FromStr for Schema {
1867    type Err = CedarSchemaError;
1868
1869    /// Construct a [`Schema`] from a string containing a schema formatted in
1870    /// the Cedar schema format. This can fail if it is not possible to parse a
1871    /// schema from the string, or if errors in values in the schema are
1872    /// uncovered after parsing. For instance, when an entity attribute name is
1873    /// found to not be a valid attribute name according to the Cedar
1874    /// grammar.
1875    fn from_str(schema_src: &str) -> Result<Self, Self::Err> {
1876        Self::from_cedarschema_str(schema_src).map(|(schema, _)| schema)
1877    }
1878}
1879
1880impl Schema {
1881    /// Create a [`Schema`] from multiple [`SchemaFragment`]. The individual
1882    /// fragments may reference entity or common types that are not declared in that
1883    /// fragment, but all referenced entity and common types must be declared in some
1884    /// fragment.
1885    pub fn from_schema_fragments(
1886        fragments: impl IntoIterator<Item = SchemaFragment>,
1887    ) -> Result<Self, SchemaError> {
1888        Ok(Self(
1889            cedar_policy_core::validator::ValidatorSchema::from_schema_fragments(
1890                fragments.into_iter().map(|f| f.value),
1891                Extensions::all_available(),
1892            )?,
1893        ))
1894    }
1895
1896    /// Create a [`Schema`] from a JSON value (which should be an object of the
1897    /// shape required for the JSON schema format).
1898    pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
1899        Ok(Self(
1900            cedar_policy_core::validator::ValidatorSchema::from_json_value(
1901                json,
1902                Extensions::all_available(),
1903            )?,
1904        ))
1905    }
1906
1907    /// Create a [`Schema`] from a string containing JSON in the appropriate
1908    /// shape.
1909    pub fn from_json_str(json: &str) -> Result<Self, SchemaError> {
1910        Ok(Self(
1911            cedar_policy_core::validator::ValidatorSchema::from_json_str(
1912                json,
1913                Extensions::all_available(),
1914            )?,
1915        ))
1916    }
1917
1918    /// Create a [`Schema`] directly from a file containing JSON in the
1919    /// appropriate shape.
1920    pub fn from_json_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
1921        Ok(Self(
1922            cedar_policy_core::validator::ValidatorSchema::from_json_file(
1923                file,
1924                Extensions::all_available(),
1925            )?,
1926        ))
1927    }
1928
1929    /// Parse the schema from a reader, in the Cedar schema format.
1930    pub fn from_cedarschema_file(
1931        file: impl std::io::Read,
1932    ) -> Result<(Self, impl Iterator<Item = SchemaWarning> + 'static), CedarSchemaError> {
1933        let (schema, warnings) =
1934            cedar_policy_core::validator::ValidatorSchema::from_cedarschema_file(
1935                file,
1936                Extensions::all_available(),
1937            )?;
1938        Ok((Self(schema), warnings))
1939    }
1940
1941    /// Parse the schema from a string, in the Cedar schema format.
1942    pub fn from_cedarschema_str(
1943        src: &str,
1944    ) -> Result<(Self, impl Iterator<Item = SchemaWarning>), CedarSchemaError> {
1945        let (schema, warnings) =
1946            cedar_policy_core::validator::ValidatorSchema::from_cedarschema_str(
1947                src,
1948                Extensions::all_available(),
1949            )?;
1950        Ok((Self(schema), warnings))
1951    }
1952
1953    /// Extract from the schema an [`Entities`] containing the action entities
1954    /// declared in the schema.
1955    pub fn action_entities(&self) -> Result<Entities, EntitiesError> {
1956        Ok(Entities(self.0.action_entities()?))
1957    }
1958
1959    /// Returns an iterator over every entity type that can be a principal for any action in this schema
1960    ///
1961    /// Note: this iterator may contain duplicates.
1962    ///
1963    /// # Examples
1964    /// Here's an example of using a [`std::collections::HashSet`] to get a de-duplicated set of principals
1965    /// ```
1966    /// use std::collections::HashSet;
1967    /// use cedar_policy::Schema;
1968    /// let schema : Schema = r#"
1969    ///     entity User;
1970    ///     entity Folder;
1971    ///     action Access appliesTo {
1972    ///         principal : User,
1973    ///         resource : Folder,
1974    ///     };
1975    ///     action Delete appliesTo {
1976    ///         principal : User,
1977    ///         resource : Folder,
1978    ///     };
1979    /// "#.parse().unwrap();
1980    /// let principals = schema.principals().collect::<HashSet<_>>();
1981    /// assert_eq!(principals, HashSet::from([&"User".parse().unwrap()]));
1982    /// ```
1983    pub fn principals(&self) -> impl Iterator<Item = &EntityTypeName> {
1984        self.0.principals().map(RefCast::ref_cast)
1985    }
1986
1987    /// Returns an iterator over every entity type that can be a resource for any action in this schema
1988    ///
1989    /// Note: this iterator may contain duplicates.
1990    /// # Examples
1991    /// Here's an example of using a [`std::collections::HashSet`] to get a de-duplicated set of resources
1992    /// ```
1993    /// use std::collections::HashSet;
1994    /// use cedar_policy::Schema;
1995    /// let schema : Schema = r#"
1996    ///     entity User;
1997    ///     entity Folder;
1998    ///     action Access appliesTo {
1999    ///         principal : User,
2000    ///         resource : Folder,
2001    ///     };
2002    ///     action Delete appliesTo {
2003    ///         principal : User,
2004    ///         resource : Folder,
2005    ///     };
2006    /// "#.parse().unwrap();
2007    /// let resources = schema.resources().collect::<HashSet<_>>();
2008    /// assert_eq!(resources, HashSet::from([&"Folder".parse().unwrap()]));
2009    /// ```
2010    pub fn resources(&self) -> impl Iterator<Item = &EntityTypeName> {
2011        self.0.resources().map(RefCast::ref_cast)
2012    }
2013
2014    /// Returns an iterator over every entity type that can be a principal for `action` in this schema
2015    ///
2016    /// ## Errors
2017    ///
2018    /// Returns [`None`] if `action` is not found in the schema
2019    pub fn principals_for_action(
2020        &self,
2021        action: &EntityUid,
2022    ) -> Option<impl Iterator<Item = &EntityTypeName>> {
2023        self.0
2024            .principals_for_action(&action.0)
2025            .map(|iter| iter.map(RefCast::ref_cast))
2026    }
2027
2028    /// Returns an iterator over every entity type that can be a resource for `action` in this schema
2029    ///
2030    /// ## Errors
2031    ///
2032    /// Returns [`None`] if `action` is not found in the schema
2033    pub fn resources_for_action(
2034        &self,
2035        action: &EntityUid,
2036    ) -> Option<impl Iterator<Item = &EntityTypeName>> {
2037        self.0
2038            .resources_for_action(&action.0)
2039            .map(|iter| iter.map(RefCast::ref_cast))
2040    }
2041
2042    /// Returns an iterator over all the [`RequestEnv`]s that are valid
2043    /// according to this schema.
2044    pub fn request_envs(&self) -> impl Iterator<Item = RequestEnv> + '_ {
2045        self.0
2046            .unlinked_request_envs(cedar_policy_core::validator::ValidationMode::Strict)
2047            .map(Into::into)
2048    }
2049
2050    /// Returns an iterator over all the entity types that can be an ancestor of `ty`
2051    ///
2052    /// ## Errors
2053    ///
2054    /// Returns [`None`] if the `ty` is not found in the schema
2055    pub fn ancestors<'a>(
2056        &'a self,
2057        ty: &'a EntityTypeName,
2058    ) -> Option<impl Iterator<Item = &'a EntityTypeName> + 'a> {
2059        self.0
2060            .ancestors(&ty.0)
2061            .map(|iter| iter.map(RefCast::ref_cast))
2062    }
2063
2064    /// Returns an iterator over all the action groups defined in this schema
2065    pub fn action_groups(&self) -> impl Iterator<Item = &EntityUid> {
2066        self.0.action_groups().map(RefCast::ref_cast)
2067    }
2068
2069    /// Returns an iterator over all entity types defined in this schema
2070    pub fn entity_types(&self) -> impl Iterator<Item = &EntityTypeName> {
2071        self.0
2072            .entity_types()
2073            .map(|ety| RefCast::ref_cast(ety.name()))
2074    }
2075
2076    /// Returns an iterator over all actions defined in this schema
2077    pub fn actions(&self) -> impl Iterator<Item = &EntityUid> {
2078        self.0.actions().map(RefCast::ref_cast)
2079    }
2080}
2081
2082/// Contains the result of policy validation.
2083///
2084/// The result includes the list of issues found by validation and whether validation succeeds or fails.
2085/// Validation succeeds if there are no fatal errors. There may still be
2086/// non-fatal warnings present when validation passes.
2087#[derive(Debug, Clone)]
2088pub struct ValidationResult {
2089    validation_errors: Vec<ValidationError>,
2090    validation_warnings: Vec<ValidationWarning>,
2091}
2092
2093impl ValidationResult {
2094    /// True when validation passes. There are no errors, but there may be
2095    /// non-fatal warnings. Use [`ValidationResult::validation_passed_without_warnings`]
2096    /// to check that there are also no warnings.
2097    pub fn validation_passed(&self) -> bool {
2098        self.validation_errors.is_empty()
2099    }
2100
2101    /// True when validation passes (i.e., there are no errors) and there are
2102    /// additionally no non-fatal warnings.
2103    pub fn validation_passed_without_warnings(&self) -> bool {
2104        self.validation_errors.is_empty() && self.validation_warnings.is_empty()
2105    }
2106
2107    /// Get an iterator over the errors found by the validator.
2108    pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError> {
2109        self.validation_errors.iter()
2110    }
2111
2112    /// Get an iterator over the warnings found by the validator.
2113    pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning> {
2114        self.validation_warnings.iter()
2115    }
2116
2117    fn first_error_or_warning(&self) -> Option<&dyn Diagnostic> {
2118        self.validation_errors
2119            .first()
2120            .map(|e| e as &dyn Diagnostic)
2121            .or_else(|| {
2122                self.validation_warnings
2123                    .first()
2124                    .map(|w| w as &dyn Diagnostic)
2125            })
2126    }
2127
2128    pub(crate) fn into_errors_and_warnings(
2129        self,
2130    ) -> (
2131        impl Iterator<Item = ValidationError>,
2132        impl Iterator<Item = ValidationWarning>,
2133    ) {
2134        (
2135            self.validation_errors.into_iter(),
2136            self.validation_warnings.into_iter(),
2137        )
2138    }
2139}
2140
2141#[doc(hidden)]
2142impl From<cedar_policy_core::validator::ValidationResult> for ValidationResult {
2143    fn from(r: cedar_policy_core::validator::ValidationResult) -> Self {
2144        let (errors, warnings) = r.into_errors_and_warnings();
2145        Self {
2146            validation_errors: errors.map(ValidationError::from).collect(),
2147            validation_warnings: warnings.map(ValidationWarning::from).collect(),
2148        }
2149    }
2150}
2151
2152impl std::fmt::Display for ValidationResult {
2153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2154        match self.first_error_or_warning() {
2155            Some(diagnostic) => write!(f, "{diagnostic}"),
2156            None => write!(f, "no errors or warnings"),
2157        }
2158    }
2159}
2160
2161impl std::error::Error for ValidationResult {
2162    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2163        self.first_error_or_warning()
2164            .and_then(std::error::Error::source)
2165    }
2166
2167    #[allow(deprecated)]
2168    fn description(&self) -> &str {
2169        self.first_error_or_warning()
2170            .map_or("no errors or warnings", std::error::Error::description)
2171    }
2172
2173    #[allow(deprecated)]
2174    fn cause(&self) -> Option<&dyn std::error::Error> {
2175        self.first_error_or_warning()
2176            .and_then(std::error::Error::cause)
2177    }
2178}
2179
2180// Except for `.related()`, and `.severity` everything is forwarded to the first
2181// error, or to the first warning if there are no errors. This is done for the
2182// same reason as policy parse errors.
2183impl Diagnostic for ValidationResult {
2184    fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn Diagnostic> + '_>> {
2185        let mut related = self
2186            .validation_errors
2187            .iter()
2188            .map(|err| err as &dyn Diagnostic)
2189            .chain(
2190                self.validation_warnings
2191                    .iter()
2192                    .map(|warn| warn as &dyn Diagnostic),
2193            );
2194        related.next().map(move |first| match first.related() {
2195            Some(first_related) => Box::new(first_related.chain(related)),
2196            None => Box::new(related) as Box<dyn Iterator<Item = _>>,
2197        })
2198    }
2199
2200    fn severity(&self) -> Option<miette::Severity> {
2201        self.first_error_or_warning()
2202            .map_or(Some(miette::Severity::Advice), Diagnostic::severity)
2203    }
2204
2205    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
2206        self.first_error_or_warning().and_then(Diagnostic::labels)
2207    }
2208
2209    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
2210        self.first_error_or_warning()
2211            .and_then(Diagnostic::source_code)
2212    }
2213
2214    fn code(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2215        self.first_error_or_warning().and_then(Diagnostic::code)
2216    }
2217
2218    fn url(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2219        self.first_error_or_warning().and_then(Diagnostic::url)
2220    }
2221
2222    fn help(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2223        self.first_error_or_warning().and_then(Diagnostic::help)
2224    }
2225
2226    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
2227        self.first_error_or_warning()
2228            .and_then(Diagnostic::diagnostic_source)
2229    }
2230}
2231
2232/// Scan a set of policies for potentially confusing/obfuscating text.
2233///
2234/// These checks are also provided through [`Validator::validate`] which provides more
2235/// comprehensive error detection, but this function can be used to check for
2236/// confusable strings without defining a schema.
2237pub fn confusable_string_checker<'a>(
2238    templates: impl Iterator<Item = &'a Template> + 'a,
2239) -> impl Iterator<Item = ValidationWarning> + 'a {
2240    cedar_policy_core::validator::confusable_string_checks(templates.map(|t| &t.ast))
2241        .map(std::convert::Into::into)
2242}
2243
2244/// Represents a namespace.
2245///
2246/// An `EntityNamespace` can can be constructed using
2247/// [`EntityNamespace::from_str`] or by calling `parse()` on a string.
2248/// _This can fail_, so it is important to properly handle an `Err` result.
2249///
2250/// ```
2251/// # use cedar_policy::EntityNamespace;
2252/// let id : Result<EntityNamespace, _> = "My::Name::Space".parse();
2253/// # assert_eq!(id.unwrap().to_string(), "My::Name::Space".to_string());
2254/// ```
2255#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
2256pub struct EntityNamespace(pub(crate) ast::Name);
2257
2258#[doc(hidden)] // because this converts to a private/internal type
2259impl AsRef<ast::Name> for EntityNamespace {
2260    fn as_ref(&self) -> &ast::Name {
2261        &self.0
2262    }
2263}
2264
2265/// This `FromStr` implementation requires the _normalized_ representation of the
2266/// namespace. See <https://github.com/cedar-policy/rfcs/pull/9/>.
2267impl FromStr for EntityNamespace {
2268    type Err = ParseErrors;
2269
2270    fn from_str(namespace_str: &str) -> Result<Self, Self::Err> {
2271        ast::Name::from_normalized_str(namespace_str)
2272            .map(EntityNamespace)
2273            .map_err(Into::into)
2274    }
2275}
2276
2277impl std::fmt::Display for EntityNamespace {
2278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2279        write!(f, "{}", self.0)
2280    }
2281}
2282
2283#[derive(Debug, Clone, Default)]
2284/// A struct representing a `PolicySet` as a series of strings for ser/de.
2285/// A `PolicySet` that contains template-linked policies cannot be
2286/// represented as this struct.
2287pub(crate) struct StringifiedPolicySet {
2288    /// The static policies in the set
2289    pub policies: Vec<String>,
2290    /// The policy templates in the set
2291    pub policy_templates: Vec<String>,
2292}
2293
2294/// Represents a set of `Policy`s
2295#[derive(Debug, Clone, Default)]
2296pub struct PolicySet {
2297    /// AST representation. Technically partially redundant with the other fields.
2298    /// Internally, we ensure that the duplicated information remains consistent.
2299    pub(crate) ast: ast::PolicySet,
2300    /// Policies in the set (this includes both static policies and template linked-policies)
2301    policies: LinkedHashMap<PolicyId, Policy>,
2302    /// Templates in the set
2303    templates: LinkedHashMap<PolicyId, Template>,
2304}
2305
2306impl PartialEq for PolicySet {
2307    fn eq(&self, other: &Self) -> bool {
2308        // eq is based on just the `ast`
2309        self.ast.eq(&other.ast)
2310    }
2311}
2312impl Eq for PolicySet {}
2313
2314#[doc(hidden)] // because this converts to a private/internal type
2315impl AsRef<ast::PolicySet> for PolicySet {
2316    fn as_ref(&self) -> &ast::PolicySet {
2317        &self.ast
2318    }
2319}
2320
2321#[doc(hidden)]
2322impl TryFrom<ast::PolicySet> for PolicySet {
2323    type Error = PolicySetError;
2324    fn try_from(pset: ast::PolicySet) -> Result<Self, Self::Error> {
2325        Self::from_ast(pset)
2326    }
2327}
2328
2329impl FromStr for PolicySet {
2330    type Err = ParseErrors;
2331
2332    /// Create a policy set from multiple statements.
2333    ///
2334    /// Policy ids will default to "policy*" with numbers from 0.
2335    /// If you load more policies, do not use the default id, or there will be conflicts.
2336    ///
2337    /// See [`Policy`] for more.
2338    fn from_str(policies: &str) -> Result<Self, Self::Err> {
2339        let (texts, pset) = parser::parse_policyset_and_also_return_policy_text(policies)?;
2340        // PANIC SAFETY: By the invariant on `parse_policyset_and_also_return_policy_text(policies)`, every `PolicyId` in `pset.policies()` occurs as a key in `text`.
2341        #[allow(clippy::expect_used)]
2342        let policies = pset.policies().map(|p|
2343            (
2344                PolicyId::new(p.id().clone()),
2345                Policy { lossless: LosslessPolicy::policy_or_template_text(*texts.get(p.id()).expect("internal invariant violation: policy id exists in asts but not texts")), ast: p.clone() }
2346            )
2347        ).collect();
2348        // PANIC SAFETY: By the same invariant, every `PolicyId` in `pset.templates()` also occurs as a key in `text`.
2349        #[allow(clippy::expect_used)]
2350        let templates = pset.templates().map(|t|
2351            (
2352                PolicyId::new(t.id().clone()),
2353                Template { lossless: LosslessPolicy::policy_or_template_text(*texts.get(t.id()).expect("internal invariant violation: template id exists in asts but not ests")), ast: t.clone() }
2354            )
2355        ).collect();
2356        Ok(Self {
2357            ast: pset,
2358            policies,
2359            templates,
2360        })
2361    }
2362}
2363
2364impl PolicySet {
2365    /// Build the policy set AST from the EST
2366    fn from_est(est: &est::PolicySet) -> Result<Self, PolicySetError> {
2367        let ast: ast::PolicySet = est.clone().try_into()?;
2368        // PANIC SAFETY: Since conversion from EST to AST succeeded, every `PolicyId` in `ast.policies()` occurs in `est`
2369        #[allow(clippy::expect_used)]
2370        let policies = ast
2371            .policies()
2372            .map(|p| {
2373                (
2374                    PolicyId::new(p.id().clone()),
2375                    Policy {
2376                        lossless: LosslessPolicy::Est(est.get_policy(p.id()).expect(
2377                            "internal invariant violation: policy id exists in asts but not ests",
2378                        )),
2379                        ast: p.clone(),
2380                    },
2381                )
2382            })
2383            .collect();
2384        // PANIC SAFETY: Since conversion from EST to AST succeeded, every `PolicyId` in `ast.templates()` occurs in `est`
2385        #[allow(clippy::expect_used)]
2386        let templates = ast
2387            .templates()
2388            .map(|t| {
2389                (
2390                    PolicyId::new(t.id().clone()),
2391                    Template {
2392                        lossless: LosslessPolicy::Est(est.get_template(t.id()).expect(
2393                            "internal invariant violation: template id exists in asts but not ests",
2394                        )),
2395                        ast: t.clone(),
2396                    },
2397                )
2398            })
2399            .collect();
2400        Ok(Self {
2401            ast,
2402            policies,
2403            templates,
2404        })
2405    }
2406
2407    /// Build the [`PolicySet`] from just the AST information
2408    #[cfg_attr(not(feature = "protobufs"), allow(dead_code))]
2409    pub(crate) fn from_ast(ast: ast::PolicySet) -> Result<Self, PolicySetError> {
2410        Self::from_policies(ast.into_policies().map(Policy::from_ast))
2411    }
2412
2413    /// Deserialize the [`PolicySet`] from a JSON string
2414    pub fn from_json_str(src: impl AsRef<str>) -> Result<Self, PolicySetError> {
2415        let est: est::PolicySet = serde_json::from_str(src.as_ref())
2416            .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2417        Self::from_est(&est)
2418    }
2419
2420    /// Deserialize the [`PolicySet`] from a JSON value
2421    pub fn from_json_value(src: serde_json::Value) -> Result<Self, PolicySetError> {
2422        let est: est::PolicySet = serde_json::from_value(src)
2423            .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2424        Self::from_est(&est)
2425    }
2426
2427    /// Deserialize the [`PolicySet`] from a JSON reader
2428    pub fn from_json_file(r: impl std::io::Read) -> Result<Self, PolicySetError> {
2429        let est: est::PolicySet = serde_json::from_reader(r)
2430            .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2431        Self::from_est(&est)
2432    }
2433
2434    /// Serialize the [`PolicySet`] as a JSON value
2435    pub fn to_json(self) -> Result<serde_json::Value, PolicySetError> {
2436        let est = self.est()?;
2437        let value = serde_json::to_value(est)
2438            .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2439        Ok(value)
2440    }
2441
2442    /// Get the EST representation of the [`PolicySet`]
2443    fn est(self) -> Result<est::PolicySet, PolicyToJsonError> {
2444        let (static_policies, template_links): (Vec<_>, Vec<_>) =
2445            fold_partition(self.policies, is_static_or_link)?;
2446        let static_policies = static_policies.into_iter().collect::<LinkedHashMap<_, _>>();
2447        let templates = self
2448            .templates
2449            .into_iter()
2450            .map(|(id, template)| {
2451                template
2452                    .lossless
2453                    .est(|| template.ast.clone().into())
2454                    .map(|est| (id.into(), est))
2455            })
2456            .collect::<Result<LinkedHashMap<_, _>, _>>()?;
2457        let est = est::PolicySet {
2458            templates,
2459            static_policies,
2460            template_links,
2461        };
2462
2463        Ok(est)
2464    }
2465
2466    /// Get the human-readable Cedar syntax representation of this policy set.
2467    /// This function is primarily intended for rendering JSON policies in the
2468    /// human-readable syntax, but it will also return the original policy text
2469    /// (though possibly re-ordering policies within the policy set) when the
2470    /// policy-set contains policies parsed from the human-readable syntax.
2471    ///
2472    /// This will return `None` if there are any linked policies in the policy
2473    /// set because they cannot be directly rendered in Cedar syntax. It also
2474    /// cannot record policy ids because these cannot be specified in the Cedar
2475    /// syntax. The policies may be reordered, so parsing the resulting string
2476    /// with [`PolicySet::from_str`] is likely to yield different policy id
2477    /// assignments. For these reasons you should prefer serializing as JSON (or protobuf) and
2478    /// only using this function to obtain a representation to display to human
2479    /// users.
2480    ///
2481    /// This function does not format the policy according to any particular
2482    /// rules.  Policy formatting can be done through the Cedar policy CLI or
2483    /// the `cedar-policy-formatter` crate.
2484    pub fn to_cedar(&self) -> Option<String> {
2485        match self.stringify() {
2486            Some(StringifiedPolicySet {
2487                policies,
2488                policy_templates,
2489            }) => {
2490                let policies_as_vec = policies
2491                    .into_iter()
2492                    .chain(policy_templates)
2493                    .collect::<Vec<_>>();
2494                Some(policies_as_vec.join("\n\n"))
2495            }
2496            None => None,
2497        }
2498    }
2499
2500    /// Get the human-readable Cedar syntax representation of this policy set,
2501    /// as a vec of strings. This function is useful to break up a large cedar
2502    /// file containing many policies into individual policies.
2503    ///
2504    /// This will return `None` if there are any linked policies in the policy
2505    /// set because they cannot be directly rendered in Cedar syntax. It also
2506    /// cannot record policy ids because these cannot be specified in the Cedar
2507    /// syntax. The policies may be reordered, so parsing the resulting string
2508    /// with [`PolicySet::from_str`] is likely to yield different policy id
2509    /// assignments. For these reasons you should prefer serializing as JSON (or protobuf) and
2510    /// only using this function to obtain a compact cedar representation,
2511    /// perhaps for storage purposes.
2512    ///
2513    /// This function does not format the policy according to any particular
2514    /// rules.  Policy formatting can be done through the Cedar policy CLI or
2515    /// the `cedar-policy-formatter` crate.
2516    pub(crate) fn stringify(&self) -> Option<StringifiedPolicySet> {
2517        let policies = self
2518            .policies
2519            .values()
2520            // We'd like to print policies in a deterministic order, so we sort
2521            // before printing, hoping that the size of policy sets is fairly
2522            // small.
2523            .sorted_by_key(|p| AsRef::<str>::as_ref(p.id()))
2524            .map(Policy::to_cedar)
2525            .collect::<Option<Vec<_>>>()?;
2526        let policy_templates = self
2527            .templates
2528            .values()
2529            .sorted_by_key(|t| AsRef::<str>::as_ref(t.id()))
2530            .map(Template::to_cedar)
2531            .collect_vec();
2532
2533        Some(StringifiedPolicySet {
2534            policies,
2535            policy_templates,
2536        })
2537    }
2538
2539    /// Create a fresh empty `PolicySet`
2540    pub fn new() -> Self {
2541        Self {
2542            ast: ast::PolicySet::new(),
2543            policies: LinkedHashMap::new(),
2544            templates: LinkedHashMap::new(),
2545        }
2546    }
2547
2548    /// Create a `PolicySet` from the given policies
2549    pub fn from_policies(
2550        policies: impl IntoIterator<Item = Policy>,
2551    ) -> Result<Self, PolicySetError> {
2552        let mut set = Self::new();
2553        for policy in policies {
2554            set.add(policy)?;
2555        }
2556        Ok(set)
2557    }
2558
2559    /// Helper function for `merge_policyset`
2560    /// Merges two sets and avoids name clashes by using the provided
2561    /// renaming. The type parameter `T` allows this code to be used for
2562    /// both Templates and Policies.
2563    fn merge_sets<T>(
2564        this: &mut LinkedHashMap<PolicyId, T>,
2565        other: &LinkedHashMap<PolicyId, T>,
2566        renaming: &HashMap<PolicyId, PolicyId>,
2567    ) where
2568        T: PartialEq + Clone,
2569    {
2570        for (pid, ot) in other {
2571            match renaming.get(pid) {
2572                Some(new_pid) => {
2573                    this.insert(new_pid.clone(), ot.clone());
2574                }
2575                None => {
2576                    if this.get(pid).is_none() {
2577                        this.insert(pid.clone(), ot.clone());
2578                    }
2579                    // If pid is not in the renaming but is in both
2580                    // this and other, then by assumption
2581                    // the element at pid in this and other are equal
2582                    // i.e., the renaming is expected to track all
2583                    // conflicting pids.
2584                }
2585            }
2586        }
2587    }
2588
2589    /// Merges this `PolicySet` with another `PolicySet`.
2590    /// This `PolicySet` is modified while the other `PolicySet`
2591    /// remains unchanged.
2592    ///
2593    /// The flag `rename_duplicates` controls the expected behavior
2594    /// when a `PolicyId` in this and the other `PolicySet` conflict.
2595    ///
2596    /// When `rename_duplicates` is false, conflicting `PolicyId`s result
2597    /// in a `PolicySetError::AlreadyDefined` error.
2598    ///
2599    /// Otherwise, when `rename_duplicates` is true, conflicting `PolicyId`s from
2600    /// the other `PolicySet` are automatically renamed to avoid conflict.
2601    /// This renaming is returned as a Hashmap from the old `PolicyId` to the
2602    /// renamed `PolicyId`.
2603    pub fn merge(
2604        &mut self,
2605        other: &Self,
2606        rename_duplicates: bool,
2607    ) -> Result<HashMap<PolicyId, PolicyId>, PolicySetError> {
2608        match self.ast.merge_policyset(&other.ast, rename_duplicates) {
2609            Ok(renaming) => {
2610                let renaming: HashMap<PolicyId, PolicyId> = renaming
2611                    .into_iter()
2612                    .map(|(old_pid, new_pid)| (PolicyId::new(old_pid), PolicyId::new(new_pid)))
2613                    .collect();
2614                Self::merge_sets(&mut self.templates, &other.templates, &renaming);
2615                Self::merge_sets(&mut self.policies, &other.policies, &renaming);
2616                Ok(renaming)
2617            }
2618            Err(ast::PolicySetError::Occupied { id }) => Err(PolicySetError::AlreadyDefined(
2619                policy_set_errors::AlreadyDefined {
2620                    id: PolicyId::new(id),
2621                },
2622            )),
2623        }
2624    }
2625
2626    /// Add an static policy to the `PolicySet`. To add a template instance, use
2627    /// `link` instead. This function will return an error (and not modify
2628    /// the `PolicySet`) if a template-linked policy is passed in.
2629    pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
2630        if policy.is_static() {
2631            let id = PolicyId::new(policy.ast.id().clone());
2632            self.ast.add(policy.ast.clone())?;
2633            self.policies.insert(id, policy);
2634            Ok(())
2635        } else {
2636            Err(PolicySetError::ExpectedStatic(
2637                policy_set_errors::ExpectedStatic::new(),
2638            ))
2639        }
2640    }
2641
2642    /// Remove a static `Policy` from the `PolicySet`.
2643    ///
2644    /// This will error if the policy is not a static policy.
2645    pub fn remove_static(&mut self, policy_id: PolicyId) -> Result<Policy, PolicySetError> {
2646        let Some(policy) = self.policies.remove(&policy_id) else {
2647            return Err(PolicySetError::PolicyNonexistent(
2648                policy_set_errors::PolicyNonexistentError { policy_id },
2649            ));
2650        };
2651        if self
2652            .ast
2653            .remove_static(&ast::PolicyID::from_string(&policy_id))
2654            .is_ok()
2655        {
2656            Ok(policy)
2657        } else {
2658            //Restore self.policies
2659            self.policies.insert(policy_id.clone(), policy);
2660            Err(PolicySetError::PolicyNonexistent(
2661                policy_set_errors::PolicyNonexistentError { policy_id },
2662            ))
2663        }
2664    }
2665
2666    /// Add a `Template` to the `PolicySet`
2667    pub fn add_template(&mut self, template: Template) -> Result<(), PolicySetError> {
2668        let id = PolicyId::new(template.ast.id().clone());
2669        self.ast.add_template(template.ast.clone())?;
2670        self.templates.insert(id, template);
2671        Ok(())
2672    }
2673
2674    /// Remove a `Template` from the `PolicySet`.
2675    ///
2676    /// This will error if any policy is linked to the template.
2677    /// This will error if `policy_id` is not a template.
2678    pub fn remove_template(&mut self, template_id: PolicyId) -> Result<Template, PolicySetError> {
2679        let Some(template) = self.templates.remove(&template_id) else {
2680            return Err(PolicySetError::TemplateNonexistent(
2681                policy_set_errors::TemplateNonexistentError { template_id },
2682            ));
2683        };
2684        // If self.templates and self.ast disagree, authorization cannot be trusted.
2685        // PANIC SAFETY: We just found the policy in self.templates.
2686        #[allow(clippy::panic)]
2687        match self
2688            .ast
2689            .remove_template(&ast::PolicyID::from_string(&template_id))
2690        {
2691            Ok(_) => Ok(template),
2692            Err(ast::PolicySetTemplateRemovalError::RemoveTemplateWithLinksError(_)) => {
2693                self.templates.insert(template_id.clone(), template);
2694                Err(PolicySetError::RemoveTemplateWithActiveLinks(
2695                    policy_set_errors::RemoveTemplateWithActiveLinksError { template_id },
2696                ))
2697            }
2698            Err(ast::PolicySetTemplateRemovalError::NotTemplateError(_)) => {
2699                self.templates.insert(template_id.clone(), template);
2700                Err(PolicySetError::RemoveTemplateNotTemplate(
2701                    policy_set_errors::RemoveTemplateNotTemplateError { template_id },
2702                ))
2703            }
2704            Err(ast::PolicySetTemplateRemovalError::RemovePolicyNoTemplateError(_)) => {
2705                panic!("Found template policy in self.templates but not in self.ast");
2706            }
2707        }
2708    }
2709
2710    /// Get policies linked to a `Template` in the `PolicySet`.
2711    /// If any policy is linked to the template, this will error
2712    pub fn get_linked_policies(
2713        &self,
2714        template_id: PolicyId,
2715    ) -> Result<impl Iterator<Item = &PolicyId>, PolicySetError> {
2716        self.ast
2717            .get_linked_policies(&ast::PolicyID::from_string(&template_id))
2718            .map_or_else(
2719                |_| {
2720                    Err(PolicySetError::TemplateNonexistent(
2721                        policy_set_errors::TemplateNonexistentError { template_id },
2722                    ))
2723                },
2724                |v| Ok(v.map(PolicyId::ref_cast)),
2725            )
2726    }
2727
2728    /// Iterate over all the `Policy`s in the `PolicySet`.
2729    ///
2730    /// This will include both static and template-linked policies.
2731    pub fn policies(&self) -> impl Iterator<Item = &Policy> {
2732        self.policies.values()
2733    }
2734
2735    /// Iterate over the `Template`'s in the `PolicySet`.
2736    pub fn templates(&self) -> impl Iterator<Item = &Template> {
2737        self.templates.values()
2738    }
2739
2740    /// Get a `Template` by its `PolicyId`
2741    pub fn template(&self, id: &PolicyId) -> Option<&Template> {
2742        self.templates.get(id)
2743    }
2744
2745    /// Get a `Policy` by its `PolicyId`
2746    pub fn policy(&self, id: &PolicyId) -> Option<&Policy> {
2747        self.policies.get(id)
2748    }
2749
2750    /// Extract annotation data from a `Policy` by its `PolicyId` and annotation key.
2751    /// If the annotation is present without an explicit value (e.g., `@annotation`),
2752    /// then this function returns `Some("")`. It returns `None` only when the
2753    /// annotation is not present.
2754    pub fn annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<&str> {
2755        self.ast
2756            .get(id.as_ref())?
2757            .annotation(&key.as_ref().parse().ok()?)
2758            .map(AsRef::as_ref)
2759    }
2760
2761    /// Extract annotation data from a `Template` by its `PolicyId` and annotation key.
2762    /// If the annotation is present without an explicit value (e.g., `@annotation`),
2763    /// then this function returns `Some("")`. It returns `None` only when the
2764    /// annotation is not present.
2765    pub fn template_annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<&str> {
2766        self.ast
2767            .get_template(id.as_ref())?
2768            .annotation(&key.as_ref().parse().ok()?)
2769            .map(AsRef::as_ref)
2770    }
2771
2772    /// Returns true iff the `PolicySet` is empty
2773    pub fn is_empty(&self) -> bool {
2774        debug_assert_eq!(
2775            self.ast.is_empty(),
2776            self.policies.is_empty() && self.templates.is_empty()
2777        );
2778        self.ast.is_empty()
2779    }
2780
2781    /// Returns the number of `Policy`s in the `PolicySet`.
2782    ///
2783    /// This will include both static and template-linked policies.
2784    pub fn num_of_policies(&self) -> usize {
2785        self.policies.len()
2786    }
2787
2788    /// Returns the number of `Template`s in the `PolicySet`.
2789    pub fn num_of_templates(&self) -> usize {
2790        self.templates.len()
2791    }
2792
2793    /// Attempt to link a template and add the new template-linked policy to the policy set.
2794    /// If link fails, the `PolicySet` is not modified.
2795    /// Failure can happen for three reasons
2796    ///   1) The map passed in `vals` may not match the slots in the template
2797    ///   2) The `new_id` may conflict w/ a policy that already exists in the set
2798    ///   3) `template_id` does not correspond to a template. Either the id is
2799    ///      not in the policy set, or it is in the policy set but is either a
2800    ///      linked or static policy rather than a template
2801    #[allow(clippy::needless_pass_by_value)]
2802    pub fn link(
2803        &mut self,
2804        template_id: PolicyId,
2805        new_id: PolicyId,
2806        vals: HashMap<SlotId, EntityUid>,
2807    ) -> Result<(), PolicySetError> {
2808        let unwrapped_vals: HashMap<ast::SlotId, ast::EntityUID> = vals
2809            .into_iter()
2810            .map(|(key, value)| (key.into(), value.into()))
2811            .collect();
2812
2813        // Try to get the template with the id we're linking from.  We do this
2814        // _before_ calling `self.ast.link` because `link` mutates the policy
2815        // set by creating a new link entry in a hashmap. This happens even when
2816        // trying to link a static policy, which we want to error on here.
2817        let Some(template) = self.templates.get(&template_id) else {
2818            return Err(if self.policies.contains_key(&template_id) {
2819                policy_set_errors::ExpectedTemplate::new().into()
2820            } else {
2821                policy_set_errors::LinkingError {
2822                    inner: ast::LinkingError::NoSuchTemplate {
2823                        id: template_id.into(),
2824                    },
2825                }
2826                .into()
2827            });
2828        };
2829
2830        let linked_ast = self.ast.link(
2831            template_id.into(),
2832            new_id.clone().into(),
2833            unwrapped_vals.clone(),
2834        )?;
2835
2836        // PANIC SAFETY: `lossless.link()` will not fail after `ast.link()` succeeds
2837        #[allow(clippy::expect_used)]
2838        let linked_lossless = template
2839            .lossless
2840            .clone()
2841            .link(unwrapped_vals.iter().map(|(k, v)| (*k, v)))
2842            // The only error case for `lossless.link()` is a template with
2843            // slots which are not filled by the provided values. `ast.link()`
2844            // will have already errored if there are any unfilled slots in the
2845            // template.
2846            .expect("ast.link() didn't fail above, so this shouldn't fail");
2847        self.policies.insert(
2848            new_id,
2849            Policy {
2850                ast: linked_ast.clone(),
2851                lossless: linked_lossless,
2852            },
2853        );
2854        Ok(())
2855    }
2856
2857    /// Get all the unknown entities from the policy set
2858    #[doc = include_str!("../experimental_warning.md")]
2859    #[cfg(feature = "partial-eval")]
2860    pub fn unknown_entities(&self) -> HashSet<EntityUid> {
2861        let mut entity_uids = HashSet::new();
2862        for policy in self.policies.values() {
2863            entity_uids.extend(policy.unknown_entities());
2864        }
2865        entity_uids
2866    }
2867
2868    /// Unlink a template-linked policy from the policy set.
2869    /// Returns the policy that was unlinked.
2870    pub fn unlink(&mut self, policy_id: PolicyId) -> Result<Policy, PolicySetError> {
2871        let Some(policy) = self.policies.remove(&policy_id) else {
2872            return Err(PolicySetError::LinkNonexistent(
2873                policy_set_errors::LinkNonexistentError { policy_id },
2874            ));
2875        };
2876        // If self.policies and self.ast disagree, authorization cannot be trusted.
2877        // PANIC SAFETY: We just found the policy in self.policies.
2878        #[allow(clippy::panic)]
2879        match self.ast.unlink(&ast::PolicyID::from_string(&policy_id)) {
2880            Ok(_) => Ok(policy),
2881            Err(ast::PolicySetUnlinkError::NotLinkError(_)) => {
2882                //Restore self.policies
2883                self.policies.insert(policy_id.clone(), policy);
2884                Err(PolicySetError::UnlinkLinkNotLink(
2885                    policy_set_errors::UnlinkLinkNotLinkError { policy_id },
2886                ))
2887            }
2888            Err(ast::PolicySetUnlinkError::UnlinkingError(_)) => {
2889                panic!("Found linked policy in self.policies but not in self.ast")
2890            }
2891        }
2892    }
2893}
2894
2895impl std::fmt::Display for PolicySet {
2896    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2897        // prefer to display the lossless format
2898        let mut policies = self.policies().peekable();
2899        while let Some(policy) = policies.next() {
2900            policy.lossless.fmt(|| policy.ast.clone().into(), f)?;
2901            if policies.peek().is_some() {
2902                writeln!(f)?;
2903            }
2904        }
2905        Ok(())
2906    }
2907}
2908
2909/// Given a [`PolicyId`] and a [`Policy`], determine if the policy represents a static policy or a
2910/// link
2911fn is_static_or_link(
2912    (id, policy): (PolicyId, Policy),
2913) -> Result<Either<(ast::PolicyID, est::Policy), TemplateLink>, PolicyToJsonError> {
2914    match policy.template_id() {
2915        Some(template_id) => {
2916            let values = policy
2917                .ast
2918                .env()
2919                .iter()
2920                .map(|(id, euid)| (*id, euid.clone()))
2921                .collect();
2922            Ok(Either::Right(TemplateLink {
2923                new_id: id.into(),
2924                template_id: template_id.clone().into(),
2925                values,
2926            }))
2927        }
2928        None => policy
2929            .lossless
2930            .est(|| policy.ast.clone().into())
2931            .map(|est| Either::Left((id.into(), est))),
2932    }
2933}
2934
2935/// Like [`itertools::Itertools::partition_map`], but accepts a function that can fail.
2936/// The first invocation of `f` that fails causes the whole computation to fail
2937#[allow(clippy::redundant_pub_crate)] // can't be private because it's used in tests
2938pub(crate) fn fold_partition<T, A, B, E>(
2939    i: impl IntoIterator<Item = T>,
2940    f: impl Fn(T) -> Result<Either<A, B>, E>,
2941) -> Result<(Vec<A>, Vec<B>), E> {
2942    let mut lefts = vec![];
2943    let mut rights = vec![];
2944
2945    for item in i {
2946        match f(item)? {
2947            Either::Left(left) => lefts.push(left),
2948            Either::Right(right) => rights.push(right),
2949        }
2950    }
2951
2952    Ok((lefts, rights))
2953}
2954
2955/// The "type" of a [`Request`], i.e., the [`EntityTypeName`]s of principal
2956/// and resource, the [`EntityUid`] of action, and [`Option<EntityTypeName>`]s
2957/// of principal slot and resource slot
2958#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2959pub struct RequestEnv {
2960    pub(crate) principal: EntityTypeName,
2961    pub(crate) action: EntityUid,
2962    pub(crate) resource: EntityTypeName,
2963    pub(crate) principal_slot: Option<EntityTypeName>,
2964    pub(crate) resource_slot: Option<EntityTypeName>,
2965}
2966
2967impl RequestEnv {
2968    /// Construct a [`RequestEnv`]
2969    pub fn new(principal: EntityTypeName, action: EntityUid, resource: EntityTypeName) -> Self {
2970        Self {
2971            principal,
2972            action,
2973            resource,
2974            principal_slot: None,
2975            resource_slot: None,
2976        }
2977    }
2978
2979    /// Construct a [`RequestEnv`] that contains slots in the scope
2980    pub fn new_request_env_with_slots(
2981        principal: EntityTypeName,
2982        action: EntityUid,
2983        resource: EntityTypeName,
2984        principal_slot: Option<EntityTypeName>,
2985        resource_slot: Option<EntityTypeName>,
2986    ) -> Self {
2987        Self {
2988            principal,
2989            action,
2990            resource,
2991            principal_slot,
2992            resource_slot,
2993        }
2994    }
2995
2996    /// Get the principal type name
2997    pub fn principal(&self) -> &EntityTypeName {
2998        &self.principal
2999    }
3000
3001    /// Get the action [`EntityUid`]
3002    pub fn action(&self) -> &EntityUid {
3003        &self.action
3004    }
3005
3006    /// Get the resource type name
3007    pub fn resource(&self) -> &EntityTypeName {
3008        &self.resource
3009    }
3010
3011    /// Get the principal slot type name
3012    pub fn principal_slot(&self) -> Option<&EntityTypeName> {
3013        self.principal_slot.as_ref()
3014    }
3015
3016    /// Get the resource slot type name
3017    pub fn resource_slot(&self) -> Option<&EntityTypeName> {
3018        self.resource_slot.as_ref()
3019    }
3020}
3021
3022#[doc(hidden)]
3023impl From<cedar_policy_core::validator::types::RequestEnv<'_>> for RequestEnv {
3024    fn from(renv: cedar_policy_core::validator::types::RequestEnv<'_>) -> Self {
3025        match renv {
3026            cedar_policy_core::validator::types::RequestEnv::DeclaredAction {
3027                principal,
3028                action,
3029                resource,
3030                principal_slot,
3031                resource_slot,
3032                ..
3033            } => Self {
3034                principal: principal.clone().into(),
3035                action: action.clone().into(),
3036                resource: resource.clone().into(),
3037                principal_slot: principal_slot.map(EntityTypeName::from),
3038                resource_slot: resource_slot.map(EntityTypeName::from),
3039            },
3040            // PANIC SAFETY: partial validation is not enabled and hence `RequestEnv::UndeclaredAction` should not show up
3041            #[allow(clippy::unreachable)]
3042            cedar_policy_core::validator::types::RequestEnv::UndeclaredAction => {
3043                unreachable!("used unsupported feature")
3044            }
3045        }
3046    }
3047}
3048
3049/// Get valid request envs for an `ast::Template`
3050///
3051/// This function is called by [`Template::get_valid_request_envs`] and
3052/// [`Policy::get_valid_request_envs`]
3053fn get_valid_request_envs(ast: &ast::Template, s: &Schema) -> impl Iterator<Item = RequestEnv> {
3054    let tc = Typechecker::new(
3055        &s.0,
3056        cedar_policy_core::validator::ValidationMode::default(),
3057    );
3058    tc.typecheck_by_request_env(ast)
3059        .into_iter()
3060        .filter_map(|(env, pc)| {
3061            if matches!(pc, PolicyCheck::Success(_)) {
3062                Some(env.into())
3063            } else {
3064                None
3065            }
3066        })
3067        .collect::<BTreeSet<_>>()
3068        .into_iter()
3069}
3070
3071/// Policy template datatype
3072//
3073// NOTE: Unlike the internal type [`ast::Template`], this type only supports
3074// templates. The `Template` constructors will return an error if provided with
3075// a static policy.
3076#[derive(Debug, Clone)]
3077pub struct Template {
3078    /// AST representation of the template, used for most operations.
3079    /// In particular, the `ast` contains the authoritative `PolicyId` for the template.
3080    pub(crate) ast: ast::Template,
3081
3082    /// Some "lossless" representation of the template, whichever is most
3083    /// convenient to provide (and can be provided with the least overhead).
3084    /// This is used just for `to_json()`.
3085    /// We can't just derive this on-demand from `ast`, because the AST is lossy:
3086    /// we can't reconstruct an accurate CST/EST/policy-text from the AST, but
3087    /// we can from the EST (modulo whitespace and a few other things like the
3088    /// order of annotations).
3089    ///
3090    /// This is a `LosslessPolicy` (rather than something like `LosslessTemplate`)
3091    /// because the EST doesn't distinguish between static policies and templates.
3092    pub(crate) lossless: LosslessPolicy,
3093}
3094
3095impl PartialEq for Template {
3096    fn eq(&self, other: &Self) -> bool {
3097        // eq is based on just the `ast`
3098        self.ast.eq(&other.ast)
3099    }
3100}
3101impl Eq for Template {}
3102
3103#[doc(hidden)] // because this converts to a private/internal type
3104impl AsRef<ast::Template> for Template {
3105    fn as_ref(&self) -> &ast::Template {
3106        &self.ast
3107    }
3108}
3109
3110#[doc(hidden)]
3111impl From<ast::Template> for Template {
3112    fn from(template: ast::Template) -> Self {
3113        Self::from_ast(template)
3114    }
3115}
3116
3117impl Template {
3118    /// Attempt to parse a [`Template`] from source.
3119    /// Returns an error if the input is a static policy (i.e., has no slots).
3120    /// If `id` is Some, then the resulting template will have that `id`.
3121    /// If the `id` is None, the parser will use the default "policy0".
3122    /// The behavior around None may change in the future.
3123    pub fn parse(id: Option<PolicyId>, src: impl AsRef<str>) -> Result<Self, ParseErrors> {
3124        let ast = parser::parse_template(id.map(Into::into), src.as_ref())?;
3125        Ok(Self {
3126            ast,
3127            lossless: LosslessPolicy::policy_or_template_text(Some(src.as_ref())),
3128        })
3129    }
3130
3131    /// Get the `PolicyId` of this `Template`
3132    pub fn id(&self) -> &PolicyId {
3133        PolicyId::ref_cast(self.ast.id())
3134    }
3135
3136    /// Clone this `Template` with a new `PolicyId`
3137    #[must_use]
3138    pub fn new_id(&self, id: PolicyId) -> Self {
3139        Self {
3140            ast: self.ast.new_id(id.into()),
3141            lossless: self.lossless.clone(), // Lossless representation doesn't include the `PolicyId`
3142        }
3143    }
3144
3145    /// Get the `Effect` (`Forbid` or `Permit`) of this `Template`
3146    pub fn effect(&self) -> Effect {
3147        self.ast.effect()
3148    }
3149
3150    /// Returns `true` if this template has a `when` or `unless` clause.
3151    pub fn has_non_scope_constraint(&self) -> bool {
3152        self.ast.non_scope_constraints().is_some()
3153    }
3154
3155    /// Get an annotation value of this `Template`.
3156    /// If the annotation is present without an explicit value (e.g., `@annotation`),
3157    /// then this function returns `Some("")`. Returns `None` when the
3158    /// annotation is not present or when `key` is not a valid annotation identifier.
3159    pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
3160        self.ast
3161            .annotation(&key.as_ref().parse().ok()?)
3162            .map(AsRef::as_ref)
3163    }
3164
3165    /// Iterate through annotation data of this `Template` as key-value pairs.
3166    /// Annotations which do not have an explicit value (e.g., `@annotation`),
3167    /// are included in the iterator with the value `""`.
3168    pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
3169        self.ast
3170            .annotations()
3171            .map(|(k, v)| (k.as_ref(), v.as_ref()))
3172    }
3173
3174    /// Iterate over the open slots in this `Template`
3175    pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
3176        self.ast.slots().map(|slot| SlotId::ref_cast(&slot.id))
3177    }
3178
3179    /// Get the scope constraint on this policy's principal
3180    pub fn principal_constraint(&self) -> TemplatePrincipalConstraint {
3181        match self.ast.principal_constraint().as_inner() {
3182            ast::PrincipalOrResourceConstraint::Any => TemplatePrincipalConstraint::Any,
3183            ast::PrincipalOrResourceConstraint::In(eref) => {
3184                TemplatePrincipalConstraint::In(match eref {
3185                    ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3186                    ast::EntityReference::Slot(_) => None,
3187                })
3188            }
3189            ast::PrincipalOrResourceConstraint::Eq(eref) => {
3190                TemplatePrincipalConstraint::Eq(match eref {
3191                    ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3192                    ast::EntityReference::Slot(_) => None,
3193                })
3194            }
3195            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3196                TemplatePrincipalConstraint::Is(entity_type.as_ref().clone().into())
3197            }
3198            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3199                TemplatePrincipalConstraint::IsIn(
3200                    entity_type.as_ref().clone().into(),
3201                    match eref {
3202                        ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3203                        ast::EntityReference::Slot(_) => None,
3204                    },
3205                )
3206            }
3207        }
3208    }
3209
3210    /// Get the scope constraint on this policy's action
3211    pub fn action_constraint(&self) -> ActionConstraint {
3212        // Clone the data from Core to be consistent with the other constraints
3213        match self.ast.action_constraint() {
3214            ast::ActionConstraint::Any => ActionConstraint::Any,
3215            ast::ActionConstraint::In(ids) => {
3216                ActionConstraint::In(ids.iter().map(|id| id.as_ref().clone().into()).collect())
3217            }
3218            ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(id.as_ref().clone().into()),
3219            #[cfg(feature = "tolerant-ast")]
3220            ast::ActionConstraint::ErrorConstraint => {
3221                // We will only have an ErrorConstraint if we are using a parser that allows Error nodes
3222                // It is not recommended to evaluate an AST that allows error nodes
3223                // If somehow someone tries to evaluate an AST that includes an Action constraint error, we will
3224                // treat it as `Any`
3225                ActionConstraint::Any
3226            }
3227        }
3228    }
3229
3230    /// Get the scope constraint on this policy's resource
3231    pub fn resource_constraint(&self) -> TemplateResourceConstraint {
3232        match self.ast.resource_constraint().as_inner() {
3233            ast::PrincipalOrResourceConstraint::Any => TemplateResourceConstraint::Any,
3234            ast::PrincipalOrResourceConstraint::In(eref) => {
3235                TemplateResourceConstraint::In(match eref {
3236                    ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3237                    ast::EntityReference::Slot(_) => None,
3238                })
3239            }
3240            ast::PrincipalOrResourceConstraint::Eq(eref) => {
3241                TemplateResourceConstraint::Eq(match eref {
3242                    ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3243                    ast::EntityReference::Slot(_) => None,
3244                })
3245            }
3246            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3247                TemplateResourceConstraint::Is(entity_type.as_ref().clone().into())
3248            }
3249            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3250                TemplateResourceConstraint::IsIn(
3251                    entity_type.as_ref().clone().into(),
3252                    match eref {
3253                        ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3254                        ast::EntityReference::Slot(_) => None,
3255                    },
3256                )
3257            }
3258        }
3259    }
3260
3261    /// Create a [`Template`] from its JSON representation.
3262    /// Returns an error if the input is a static policy (i.e., has no slots).
3263    /// If `id` is Some, the policy will be given that Policy Id.
3264    /// If `id` is None, then "JSON policy" will be used.
3265    /// The behavior around None may change in the future.
3266    pub fn from_json(
3267        id: Option<PolicyId>,
3268        json: serde_json::Value,
3269    ) -> Result<Self, PolicyFromJsonError> {
3270        let est: est::Policy = serde_json::from_value(json)
3271            .map_err(|e| entities_json_errors::JsonDeserializationError::Serde(e.into()))
3272            .map_err(cedar_policy_core::est::FromJsonError::from)?;
3273        Self::from_est(id, est)
3274    }
3275
3276    fn from_est(id: Option<PolicyId>, est: est::Policy) -> Result<Self, PolicyFromJsonError> {
3277        Ok(Self {
3278            ast: est.clone().try_into_ast_template(id.map(PolicyId::into))?,
3279            lossless: LosslessPolicy::Est(est),
3280        })
3281    }
3282
3283    #[cfg_attr(not(feature = "protobufs"), allow(dead_code))]
3284    pub(crate) fn from_ast(ast: ast::Template) -> Self {
3285        Self {
3286            lossless: LosslessPolicy::Est(ast.clone().into()),
3287            ast,
3288        }
3289    }
3290
3291    /// Get the JSON representation of this `Template`.
3292    pub fn to_json(&self) -> Result<serde_json::Value, PolicyToJsonError> {
3293        let est = self.lossless.est(|| self.ast.clone().into())?;
3294        serde_json::to_value(est).map_err(Into::into)
3295    }
3296
3297    /// Get the human-readable Cedar syntax representation of this template.
3298    /// This function is primarily intended for rendering JSON policies in the
3299    /// human-readable syntax, but it will also return the original policy text
3300    /// when given a policy parsed from the human-readable syntax.
3301    ///
3302    /// It also does not format the policy according to any particular rules.
3303    /// Policy formatting can be done through the Cedar policy CLI or
3304    /// the `cedar-policy-formatter` crate.
3305    pub fn to_cedar(&self) -> String {
3306        match &self.lossless {
3307            LosslessPolicy::Empty | LosslessPolicy::Est(_) => self.ast.to_string(),
3308            LosslessPolicy::Text { text, .. } => text.clone(),
3309        }
3310    }
3311
3312    /// Get the valid [`RequestEnv`]s for this template, according to the schema.
3313    ///
3314    /// That is, all the [`RequestEnv`]s in the schema for which this template is
3315    /// not trivially false.
3316    pub fn get_valid_request_envs(&self, s: &Schema) -> impl Iterator<Item = RequestEnv> {
3317        get_valid_request_envs(&self.ast, s)
3318    }
3319}
3320
3321impl std::fmt::Display for Template {
3322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3323        // prefer to display the lossless format
3324        self.lossless.fmt(|| self.ast.clone().into(), f)
3325    }
3326}
3327
3328impl FromStr for Template {
3329    type Err = ParseErrors;
3330
3331    fn from_str(src: &str) -> Result<Self, Self::Err> {
3332        Self::parse(None, src)
3333    }
3334}
3335
3336/// Scope constraint on policy principals.
3337#[derive(Debug, Clone, PartialEq, Eq)]
3338pub enum PrincipalConstraint {
3339    /// Un-constrained
3340    Any,
3341    /// Must be In the given [`EntityUid`]
3342    In(EntityUid),
3343    /// Must be equal to the given [`EntityUid`]
3344    Eq(EntityUid),
3345    /// Must be the given [`EntityTypeName`]
3346    Is(EntityTypeName),
3347    /// Must be the given [`EntityTypeName`], and `in` the [`EntityUid`]
3348    IsIn(EntityTypeName, EntityUid),
3349}
3350
3351/// Scope constraint on policy principals for templates.
3352#[derive(Debug, Clone, PartialEq, Eq)]
3353pub enum TemplatePrincipalConstraint {
3354    /// Un-constrained
3355    Any,
3356    /// Must be In the given [`EntityUid`].
3357    /// If [`None`], then it is a template slot.
3358    In(Option<EntityUid>),
3359    /// Must be equal to the given [`EntityUid`].
3360    /// If [`None`], then it is a template slot.
3361    Eq(Option<EntityUid>),
3362    /// Must be the given [`EntityTypeName`].
3363    Is(EntityTypeName),
3364    /// Must be the given [`EntityTypeName`], and `in` the [`EntityUid`].
3365    /// If the [`EntityUid`] is [`Option::None`], then it is a template slot.
3366    IsIn(EntityTypeName, Option<EntityUid>),
3367}
3368
3369impl TemplatePrincipalConstraint {
3370    /// Does this constraint contain a slot?
3371    pub fn has_slot(&self) -> bool {
3372        match self {
3373            Self::Any | Self::Is(_) => false,
3374            Self::In(o) | Self::Eq(o) | Self::IsIn(_, o) => o.is_none(),
3375        }
3376    }
3377}
3378
3379/// Scope constraint on policy actions.
3380#[derive(Debug, Clone, PartialEq, Eq)]
3381pub enum ActionConstraint {
3382    /// Un-constrained
3383    Any,
3384    /// Must be In the given [`EntityUid`]
3385    In(Vec<EntityUid>),
3386    /// Must be equal to the given [`EntityUid]`
3387    Eq(EntityUid),
3388}
3389
3390/// Scope constraint on policy resources.
3391#[derive(Debug, Clone, PartialEq, Eq)]
3392pub enum ResourceConstraint {
3393    /// Un-constrained
3394    Any,
3395    /// Must be In the given [`EntityUid`]
3396    In(EntityUid),
3397    /// Must be equal to the given [`EntityUid`]
3398    Eq(EntityUid),
3399    /// Must be the given [`EntityTypeName`]
3400    Is(EntityTypeName),
3401    /// Must be the given [`EntityTypeName`], and `in` the [`EntityUid`]
3402    IsIn(EntityTypeName, EntityUid),
3403}
3404
3405/// Scope constraint on policy resources for templates.
3406#[derive(Debug, Clone, PartialEq, Eq)]
3407pub enum TemplateResourceConstraint {
3408    /// Un-constrained
3409    Any,
3410    /// Must be In the given [`EntityUid`].
3411    /// If [`None`], then it is a template slot.
3412    In(Option<EntityUid>),
3413    /// Must be equal to the given [`EntityUid`].
3414    /// If [`None`], then it is a template slot.
3415    Eq(Option<EntityUid>),
3416    /// Must be the given [`EntityTypeName`].
3417    Is(EntityTypeName),
3418    /// Must be the given [`EntityTypeName`], and `in` the [`EntityUid`].
3419    /// If the [`EntityUid`] is [`Option::None`], then it is a template slot.
3420    IsIn(EntityTypeName, Option<EntityUid>),
3421}
3422
3423impl TemplateResourceConstraint {
3424    /// Does this constraint contain a slot?
3425    pub fn has_slot(&self) -> bool {
3426        match self {
3427            Self::Any | Self::Is(_) => false,
3428            Self::In(o) | Self::Eq(o) | Self::IsIn(_, o) => o.is_none(),
3429        }
3430    }
3431}
3432
3433/// Structure for a `Policy`. Includes both static policies and template-linked policies.
3434#[derive(Debug, Clone)]
3435pub struct Policy {
3436    /// AST representation of the policy, used for most operations.
3437    /// In particular, the `ast` contains the authoritative `PolicyId` for the policy.
3438    pub(crate) ast: ast::Policy,
3439    /// Some "lossless" representation of the policy, whichever is most
3440    /// convenient to provide (and can be provided with the least overhead).
3441    /// This is used just for `to_json()`.
3442    /// We can't just derive this on-demand from `ast`, because the AST is lossy:
3443    /// we can't reconstruct an accurate CST/EST/policy-text from the AST, but
3444    /// we can from the EST (modulo whitespace and a few other things like the
3445    /// order of annotations).
3446    pub(crate) lossless: LosslessPolicy,
3447}
3448
3449impl PartialEq for Policy {
3450    fn eq(&self, other: &Self) -> bool {
3451        // eq is based on just the `ast`
3452        self.ast.eq(&other.ast)
3453    }
3454}
3455impl Eq for Policy {}
3456
3457#[doc(hidden)] // because this converts to a private/internal type
3458impl AsRef<ast::Policy> for Policy {
3459    fn as_ref(&self) -> &ast::Policy {
3460        &self.ast
3461    }
3462}
3463
3464#[doc(hidden)]
3465impl From<ast::Policy> for Policy {
3466    fn from(policy: ast::Policy) -> Self {
3467        Self::from_ast(policy)
3468    }
3469}
3470
3471#[doc(hidden)]
3472impl From<ast::StaticPolicy> for Policy {
3473    fn from(policy: ast::StaticPolicy) -> Self {
3474        ast::Policy::from(policy).into()
3475    }
3476}
3477
3478impl Policy {
3479    /// Get the `PolicyId` of the `Template` this is linked to.
3480    /// If this is a static policy, this will return `None`.
3481    pub fn template_id(&self) -> Option<&PolicyId> {
3482        if self.is_static() {
3483            None
3484        } else {
3485            Some(PolicyId::ref_cast(self.ast.template().id()))
3486        }
3487    }
3488
3489    /// Get the values this `Template` is linked to, expressed as a map from `SlotId` to `EntityUid`.
3490    /// If this is a static policy, this will return `None`.
3491    pub fn template_links(&self) -> Option<HashMap<SlotId, EntityUid>> {
3492        if self.is_static() {
3493            None
3494        } else {
3495            let wrapped_vals: HashMap<SlotId, EntityUid> = self
3496                .ast
3497                .env()
3498                .iter()
3499                .map(|(key, value)| ((*key).into(), value.clone().into()))
3500                .collect();
3501            Some(wrapped_vals)
3502        }
3503    }
3504
3505    /// Get the `Effect` (`Permit` or `Forbid`) for this instance
3506    pub fn effect(&self) -> Effect {
3507        self.ast.effect()
3508    }
3509
3510    /// Returns `true` if this policy has a `when` or `unless` clause.
3511    pub fn has_non_scope_constraint(&self) -> bool {
3512        self.ast.non_scope_constraints().is_some()
3513    }
3514
3515    /// Get an annotation value of this template-linked or static policy.
3516    /// If the annotation is present without an explicit value (e.g., `@annotation`),
3517    /// then this function returns `Some("")`. Returns `None` when the
3518    /// annotation is not present or when `key` is not a valid annotations identifier.
3519    pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
3520        self.ast
3521            .annotation(&key.as_ref().parse().ok()?)
3522            .map(AsRef::as_ref)
3523    }
3524
3525    /// Iterate through annotation data of this template-linked or static policy.
3526    /// Annotations which do not have an explicit value (e.g., `@annotation`),
3527    /// are included in the iterator with the value `""`.
3528    pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
3529        self.ast
3530            .annotations()
3531            .map(|(k, v)| (k.as_ref(), v.as_ref()))
3532    }
3533
3534    /// Get the `PolicyId` for this template-linked or static policy
3535    pub fn id(&self) -> &PolicyId {
3536        PolicyId::ref_cast(self.ast.id())
3537    }
3538
3539    /// Clone this `Policy` with a new `PolicyId`
3540    #[must_use]
3541    pub fn new_id(&self, id: PolicyId) -> Self {
3542        Self {
3543            ast: self.ast.new_id(id.into()),
3544            lossless: self.lossless.clone(), // Lossless representation doesn't include the `PolicyId`
3545        }
3546    }
3547
3548    /// Returns `true` if this is a static policy, `false` otherwise.
3549    pub fn is_static(&self) -> bool {
3550        self.ast.is_static()
3551    }
3552
3553    /// Get the scope constraint on this policy's principal
3554    pub fn principal_constraint(&self) -> PrincipalConstraint {
3555        let slot_id = ast::SlotId::principal();
3556        match self.ast.template().principal_constraint().as_inner() {
3557            ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::Any,
3558            ast::PrincipalOrResourceConstraint::In(eref) => {
3559                PrincipalConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
3560            }
3561            ast::PrincipalOrResourceConstraint::Eq(eref) => {
3562                PrincipalConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
3563            }
3564            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3565                PrincipalConstraint::Is(entity_type.as_ref().clone().into())
3566            }
3567            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3568                PrincipalConstraint::IsIn(
3569                    entity_type.as_ref().clone().into(),
3570                    self.convert_entity_reference(eref, slot_id).clone(),
3571                )
3572            }
3573        }
3574    }
3575
3576    /// Get the scope constraint on this policy's action
3577    pub fn action_constraint(&self) -> ActionConstraint {
3578        // Clone the data from Core to be consistant with the other constraints
3579        match self.ast.template().action_constraint() {
3580            ast::ActionConstraint::Any => ActionConstraint::Any,
3581            ast::ActionConstraint::In(ids) => ActionConstraint::In(
3582                ids.iter()
3583                    .map(|euid| EntityUid::ref_cast(euid.as_ref()))
3584                    .cloned()
3585                    .collect(),
3586            ),
3587            ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid::ref_cast(id).clone()),
3588            #[cfg(feature = "tolerant-ast")]
3589            ast::ActionConstraint::ErrorConstraint => {
3590                // We will only have an ErrorConstraint if we are using a parser that allows Error nodes
3591                // It is not recommended to evaluate an AST that allows error nodes
3592                // If somehow someone tries to evaluate an AST that includes an Action constraint error, we will
3593                // treat it as `Any`
3594                ActionConstraint::Any
3595            }
3596        }
3597    }
3598
3599    /// Get the scope constraint on this policy's resource
3600    pub fn resource_constraint(&self) -> ResourceConstraint {
3601        let slot_id = ast::SlotId::resource();
3602        match self.ast.template().resource_constraint().as_inner() {
3603            ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::Any,
3604            ast::PrincipalOrResourceConstraint::In(eref) => {
3605                ResourceConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
3606            }
3607            ast::PrincipalOrResourceConstraint::Eq(eref) => {
3608                ResourceConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
3609            }
3610            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3611                ResourceConstraint::Is(entity_type.as_ref().clone().into())
3612            }
3613            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3614                ResourceConstraint::IsIn(
3615                    entity_type.as_ref().clone().into(),
3616                    self.convert_entity_reference(eref, slot_id).clone(),
3617                )
3618            }
3619        }
3620    }
3621
3622    /// To avoid panicking, this function may only be called when `slot` is the
3623    /// `SlotId` corresponding to the scope constraint from which the entity
3624    /// reference `r` was extracted. I.e., If `r` is taken from the principal
3625    /// scope constraint, `slot` must be `?principal`. This ensures that the
3626    /// `SlotId` exists in the policy (and therefore the slot environment map)
3627    /// whenever the `EntityReference` `r` is the Slot variant.
3628    fn convert_entity_reference<'a>(
3629        &'a self,
3630        r: &'a ast::EntityReference,
3631        slot: ast::SlotId,
3632    ) -> &'a EntityUid {
3633        match r {
3634            ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid),
3635            // PANIC SAFETY: This `unwrap` here is safe due the invariant (values total map) on policies.
3636            #[allow(clippy::unwrap_used)]
3637            ast::EntityReference::Slot(_) => {
3638                EntityUid::ref_cast(self.ast.env().get(&slot).unwrap())
3639            }
3640        }
3641    }
3642
3643    /// Parse a single policy.
3644    /// If `id` is Some, the policy will be given that Policy Id.
3645    /// If `id` is None, then "policy0" will be used.
3646    /// The behavior around None may change in the future.
3647    ///
3648    /// This can fail if the policy fails to parse.
3649    /// It can also fail if a template was passed in, as this function only accepts static
3650    /// policies
3651    pub fn parse(id: Option<PolicyId>, policy_src: impl AsRef<str>) -> Result<Self, ParseErrors> {
3652        let inline_ast = parser::parse_policy(id.map(Into::into), policy_src.as_ref())?;
3653        let (_, ast) = ast::Template::link_static_policy(inline_ast);
3654        Ok(Self {
3655            ast,
3656            lossless: LosslessPolicy::policy_or_template_text(Some(policy_src.as_ref())),
3657        })
3658    }
3659
3660    /// Create a `Policy` from its JSON representation.
3661    /// If `id` is Some, the policy will be given that Policy Id.
3662    /// If `id` is None, then "JSON policy" will be used.
3663    /// The behavior around None may change in the future.
3664    ///
3665    /// ```
3666    /// # use cedar_policy::{Policy, PolicyId};
3667    ///
3668    /// let json: serde_json::Value = serde_json::json!(
3669    ///        {
3670    ///            "effect":"permit",
3671    ///            "principal":{
3672    ///            "op":"==",
3673    ///            "entity":{
3674    ///                "type":"User",
3675    ///                "id":"bob"
3676    ///            }
3677    ///            },
3678    ///            "action":{
3679    ///            "op":"==",
3680    ///            "entity":{
3681    ///                "type":"Action",
3682    ///                "id":"view"
3683    ///            }
3684    ///            },
3685    ///            "resource":{
3686    ///            "op":"==",
3687    ///            "entity":{
3688    ///                "type":"Album",
3689    ///                "id":"trip"
3690    ///            }
3691    ///            },
3692    ///            "conditions":[
3693    ///            {
3694    ///                "kind":"when",
3695    ///                "body":{
3696    ///                   ">":{
3697    ///                        "left":{
3698    ///                        ".":{
3699    ///                            "left":{
3700    ///                                "Var":"principal"
3701    ///                            },
3702    ///                            "attr":"age"
3703    ///                        }
3704    ///                        },
3705    ///                        "right":{
3706    ///                        "Value":18
3707    ///                        }
3708    ///                    }
3709    ///                }
3710    ///            }
3711    ///            ]
3712    ///        }
3713    /// );
3714    /// let json_policy = Policy::from_json(None, json).unwrap();
3715    /// let src = r#"
3716    ///   permit(
3717    ///     principal == User::"bob",
3718    ///     action == Action::"view",
3719    ///     resource == Album::"trip"
3720    ///   )
3721    ///   when { principal.age > 18 };"#;
3722    /// let text_policy = Policy::parse(None, src).unwrap();
3723    /// assert_eq!(json_policy.to_json().unwrap(), text_policy.to_json().unwrap());
3724    /// ```
3725    pub fn from_json(
3726        id: Option<PolicyId>,
3727        json: serde_json::Value,
3728    ) -> Result<Self, PolicyFromJsonError> {
3729        let est: est::Policy = serde_json::from_value(json)
3730            .map_err(|e| entities_json_errors::JsonDeserializationError::Serde(e.into()))
3731            .map_err(cedar_policy_core::est::FromJsonError::from)?;
3732        Self::from_est(id, est)
3733    }
3734
3735    /// Get the valid [`RequestEnv`]s for this policy, according to the schema.
3736    ///
3737    /// That is, all the [`RequestEnv`]s in the schema for which this policy is
3738    /// not trivially false.
3739    pub fn get_valid_request_envs(&self, s: &Schema) -> impl Iterator<Item = RequestEnv> {
3740        get_valid_request_envs(self.ast.template(), s)
3741    }
3742
3743    /// Get all entity literals occuring in a `Policy`
3744    pub fn entity_literals(&self) -> Vec<EntityUid> {
3745        self.ast
3746            .condition()
3747            .subexpressions()
3748            .filter_map(|e| match e.expr_kind() {
3749                cedar_policy_core::ast::ExprKind::Lit(
3750                    cedar_policy_core::ast::Literal::EntityUID(euid),
3751                ) => Some(EntityUid((*euid).as_ref().clone())),
3752                _ => None,
3753            })
3754            .collect()
3755    }
3756
3757    /// Return a new policy where all occurrences of key `EntityUid`s are replaced by value `EntityUid`
3758    /// (as a single, non-sequential substitution).
3759    pub fn sub_entity_literals(
3760        &self,
3761        mapping: BTreeMap<EntityUid, EntityUid>,
3762    ) -> Result<Self, PolicyFromJsonError> {
3763        // PANIC SAFETY: This can't fail for a policy that was already constructed
3764        #[allow(clippy::expect_used)]
3765        let cloned_est = self
3766            .lossless
3767            .est(|| self.ast.clone().into())
3768            .expect("Internal error, failed to construct est.");
3769
3770        let mapping = mapping.into_iter().map(|(k, v)| (k.0, v.0)).collect();
3771
3772        // PANIC SAFETY: This can't fail for a policy that was already constructed
3773        #[allow(clippy::expect_used)]
3774        let est = cloned_est
3775            .sub_entity_literals(&mapping)
3776            .expect("Internal error, failed to sub entity literals.");
3777
3778        let ast = match est.clone().try_into_ast_policy(Some(self.ast.id().clone())) {
3779            Ok(ast) => ast,
3780            Err(e) => return Err(e.into()),
3781        };
3782
3783        Ok(Self {
3784            ast,
3785            lossless: LosslessPolicy::Est(est),
3786        })
3787    }
3788
3789    fn from_est(id: Option<PolicyId>, est: est::Policy) -> Result<Self, PolicyFromJsonError> {
3790        Ok(Self {
3791            ast: est.clone().try_into_ast_policy(id.map(PolicyId::into))?,
3792            lossless: LosslessPolicy::Est(est),
3793        })
3794    }
3795
3796    /// Get the JSON representation of this `Policy`.
3797    ///  ```
3798    /// # use cedar_policy::Policy;
3799    /// let src = r#"
3800    ///   permit(
3801    ///     principal == User::"bob",
3802    ///     action == Action::"view",
3803    ///     resource == Album::"trip"
3804    ///   )
3805    ///   when { principal.age > 18 };"#;
3806    ///
3807    /// let policy = Policy::parse(None, src).unwrap();
3808    /// println!("{}", policy);
3809    /// // convert the policy to JSON
3810    /// let json = policy.to_json().unwrap();
3811    /// println!("{}", json);
3812    /// assert_eq!(json, Policy::from_json(None, json.clone()).unwrap().to_json().unwrap());
3813    /// ```
3814    pub fn to_json(&self) -> Result<serde_json::Value, PolicyToJsonError> {
3815        let est = self.lossless.est(|| self.ast.clone().into())?;
3816        serde_json::to_value(est).map_err(Into::into)
3817    }
3818
3819    /// Get the human-readable Cedar syntax representation of this policy. This
3820    /// function is primarily intended for rendering JSON policies in the
3821    /// human-readable syntax, but it will also return the original policy text
3822    /// when given a policy parsed from the human-readable syntax.
3823    ///
3824    /// It will return `None` for linked policies because they cannot be
3825    /// directly rendered in Cedar syntax. You can instead render the unlinked
3826    /// template if you do not need to preserve links. If serializing links is
3827    /// important, then you will need to serialize the whole policy set
3828    /// containing the template and link to JSON (or protobuf).
3829    ///
3830    /// It also does not format the policy according to any particular rules.
3831    /// Policy formatting can be done through the Cedar policy CLI or
3832    /// the `cedar-policy-formatter` crate.
3833    pub fn to_cedar(&self) -> Option<String> {
3834        match &self.lossless {
3835            LosslessPolicy::Empty | LosslessPolicy::Est(_) => Some(self.ast.to_string()),
3836            LosslessPolicy::Text { text, slots } => {
3837                if slots.is_empty() {
3838                    Some(text.clone())
3839                } else {
3840                    None
3841                }
3842            }
3843        }
3844    }
3845
3846    /// Get all the unknown entities from the policy
3847    #[doc = include_str!("../experimental_warning.md")]
3848    #[cfg(feature = "partial-eval")]
3849    pub fn unknown_entities(&self) -> HashSet<EntityUid> {
3850        self.ast
3851            .unknown_entities()
3852            .into_iter()
3853            .map(Into::into)
3854            .collect()
3855    }
3856
3857    /// Create a `Policy` from its AST representation only. The `LosslessPolicy`
3858    /// will reflect the AST structure. When possible, don't use this method and
3859    /// create the `Policy` from the policy text, CST, or EST instead, as the
3860    /// conversion to AST is lossy. ESTs for policies generated by this method
3861    /// will reflect the AST and not the original policy syntax.
3862    #[cfg_attr(
3863        not(any(feature = "partial-eval", feature = "protobufs")),
3864        allow(unused)
3865    )]
3866    pub(crate) fn from_ast(ast: ast::Policy) -> Self {
3867        let text = ast.to_string(); // assume that pretty-printing is faster than `est::Policy::from(ast.clone())`; is that true?
3868        Self {
3869            ast,
3870            lossless: LosslessPolicy::policy_or_template_text(Some(text)),
3871        }
3872    }
3873}
3874
3875impl std::fmt::Display for Policy {
3876    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3877        // prefer to display the lossless format
3878        self.lossless.fmt(|| self.ast.clone().into(), f)
3879    }
3880}
3881
3882impl FromStr for Policy {
3883    type Err = ParseErrors;
3884    /// Create a policy
3885    ///
3886    /// Important note: Policies have ids, but this interface does not
3887    /// allow them to be set. It will use the default "policy0", which
3888    /// may cause id conflicts if not handled. Use `Policy::parse` to set
3889    /// the id when parsing, or `Policy::new_id` to clone a policy with
3890    /// a new id.
3891    fn from_str(policy: &str) -> Result<Self, Self::Err> {
3892        Self::parse(None, policy)
3893    }
3894}
3895
3896/// See comments on `Policy` and `Template`.
3897///
3898/// This structure can be used for static policies, linked policies, and templates.
3899#[derive(Debug, Clone)]
3900pub(crate) enum LosslessPolicy {
3901    /// An empty representation
3902    Empty,
3903    /// EST representation
3904    Est(est::Policy),
3905    /// Text representation
3906    Text {
3907        /// actual policy text, of the policy or template
3908        text: String,
3909        /// For linked policies, map of slot to UID. Only linked policies have
3910        /// this; static policies and (unlinked) templates have an empty map
3911        /// here
3912        slots: HashMap<ast::SlotId, ast::EntityUID>,
3913    },
3914}
3915
3916impl LosslessPolicy {
3917    /// Create a new `LosslessPolicy` from the text of a policy or template.
3918    fn policy_or_template_text(text: Option<impl Into<String>>) -> Self {
3919        text.map_or(Self::Empty, |text| Self::Text {
3920            text: text.into(),
3921            slots: HashMap::new(),
3922        })
3923    }
3924
3925    /// Get the EST representation of this static policy, linked policy, or template.
3926    fn est(
3927        &self,
3928        fallback_est: impl FnOnce() -> est::Policy,
3929    ) -> Result<est::Policy, PolicyToJsonError> {
3930        match self {
3931            // Fall back to the `policy` AST if the lossless representation is empty
3932            Self::Empty => Ok(fallback_est()),
3933            Self::Est(est) => Ok(est.clone()),
3934            Self::Text { text, slots } => {
3935                let est =
3936                    parser::parse_policy_or_template_to_est(text).map_err(ParseErrors::from)?;
3937                if slots.is_empty() {
3938                    Ok(est)
3939                } else {
3940                    let unwrapped_vals = slots.iter().map(|(k, v)| (*k, v.into())).collect();
3941                    Ok(est.link(&unwrapped_vals)?)
3942                }
3943            }
3944        }
3945    }
3946
3947    fn link<'a>(
3948        self,
3949        vals: impl IntoIterator<Item = (ast::SlotId, &'a ast::EntityUID)>,
3950    ) -> Result<Self, est::LinkingError> {
3951        match self {
3952            Self::Empty => Ok(Self::Empty),
3953            Self::Est(est) => {
3954                let unwrapped_est_vals: HashMap<
3955                    ast::SlotId,
3956                    cedar_policy_core::entities::EntityUidJson,
3957                > = vals.into_iter().map(|(k, v)| (k, v.into())).collect();
3958                Ok(Self::Est(est.link(&unwrapped_est_vals)?))
3959            }
3960            Self::Text { text, slots } => {
3961                debug_assert!(
3962                    slots.is_empty(),
3963                    "shouldn't call link() on an already-linked policy"
3964                );
3965                let slots = vals.into_iter().map(|(k, v)| (k, v.clone())).collect();
3966                Ok(Self::Text { text, slots })
3967            }
3968        }
3969    }
3970
3971    fn fmt(
3972        &self,
3973        fallback_est: impl FnOnce() -> est::Policy,
3974        f: &mut std::fmt::Formatter<'_>,
3975    ) -> std::fmt::Result {
3976        match self {
3977            Self::Empty => match self.est(fallback_est) {
3978                Ok(est) => write!(f, "{est}"),
3979                Err(e) => write!(f, "<invalid policy: {e}>"),
3980            },
3981            Self::Est(est) => write!(f, "{est}"),
3982            Self::Text { text, slots } => {
3983                if slots.is_empty() {
3984                    write!(f, "{text}")
3985                } else {
3986                    // need to replace placeholders according to `slots`.
3987                    // just find-and-replace wouldn't be safe/perfect, we
3988                    // want to use the actual parser; right now we reuse
3989                    // another implementation by just converting to EST and
3990                    // printing that
3991                    match self.est(fallback_est) {
3992                        Ok(est) => write!(f, "{est}"),
3993                        Err(e) => write!(f, "<invalid linked policy: {e}>"),
3994                    }
3995                }
3996            }
3997        }
3998    }
3999}
4000
4001/// Expressions to be evaluated
4002#[repr(transparent)]
4003#[derive(Debug, Clone, RefCast)]
4004pub struct Expression(pub(crate) ast::Expr);
4005
4006#[doc(hidden)] // because this converts to a private/internal type
4007impl AsRef<ast::Expr> for Expression {
4008    fn as_ref(&self) -> &ast::Expr {
4009        &self.0
4010    }
4011}
4012
4013#[doc(hidden)]
4014impl From<ast::Expr> for Expression {
4015    fn from(expr: ast::Expr) -> Self {
4016        Self(expr)
4017    }
4018}
4019
4020impl Expression {
4021    /// Create an expression representing a literal string.
4022    pub fn new_string(value: String) -> Self {
4023        Self(ast::Expr::val(value))
4024    }
4025
4026    /// Create an expression representing a literal bool.
4027    pub fn new_bool(value: bool) -> Self {
4028        Self(ast::Expr::val(value))
4029    }
4030
4031    /// Create an expression representing a literal long.
4032    pub fn new_long(value: ast::Integer) -> Self {
4033        Self(ast::Expr::val(value))
4034    }
4035
4036    /// Create an expression representing a record.
4037    ///
4038    /// Error if any key appears two or more times in `fields`.
4039    pub fn new_record(
4040        fields: impl IntoIterator<Item = (String, Self)>,
4041    ) -> Result<Self, ExpressionConstructionError> {
4042        Ok(Self(ast::Expr::record(
4043            fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
4044        )?))
4045    }
4046
4047    /// Create an expression representing a Set.
4048    pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
4049        Self(ast::Expr::set(values.into_iter().map(|v| v.0)))
4050    }
4051
4052    /// Create an expression representing an ip address.
4053    /// This function does not perform error checking on the source string,
4054    /// it creates an expression that calls the `ip` constructor.
4055    pub fn new_ip(src: impl AsRef<str>) -> Self {
4056        let src_expr = ast::Expr::val(src.as_ref());
4057        Self(ast::Expr::call_extension_fn(
4058            ip_extension_name(),
4059            vec![src_expr],
4060        ))
4061    }
4062
4063    /// Create an expression representing a fixed precision decimal number.
4064    /// This function does not perform error checking on the source string,
4065    /// it creates an expression that calls the `decimal` constructor.
4066    pub fn new_decimal(src: impl AsRef<str>) -> Self {
4067        let src_expr = ast::Expr::val(src.as_ref());
4068        Self(ast::Expr::call_extension_fn(
4069            decimal_extension_name(),
4070            vec![src_expr],
4071        ))
4072    }
4073
4074    /// Create an expression representing a particular instant of time.
4075    /// This function does not perform error checking on the source string,
4076    /// it creates an expression that calls the `datetime` constructor.
4077    pub fn new_datetime(src: impl AsRef<str>) -> Self {
4078        let src_expr = ast::Expr::val(src.as_ref());
4079        Self(ast::Expr::call_extension_fn(
4080            datetime_extension_name(),
4081            vec![src_expr],
4082        ))
4083    }
4084
4085    /// Create an expression representing a duration of time.
4086    /// This function does not perform error checking on the source string,
4087    /// it creates an expression that calls the `datetime` constructor.
4088    pub fn new_duration(src: impl AsRef<str>) -> Self {
4089        let src_expr = ast::Expr::val(src.as_ref());
4090        Self(ast::Expr::call_extension_fn(
4091            duration_extension_name(),
4092            vec![src_expr],
4093        ))
4094    }
4095}
4096
4097#[cfg(test)]
4098impl Expression {
4099    /// Deconstruct an [`Expression`] to get the internal type.
4100    /// This function is only intended to be used internally.
4101    pub(crate) fn into_inner(self) -> ast::Expr {
4102        self.0
4103    }
4104}
4105
4106impl FromStr for Expression {
4107    type Err = ParseErrors;
4108
4109    /// create an Expression using Cedar syntax
4110    fn from_str(expression: &str) -> Result<Self, Self::Err> {
4111        ast::Expr::from_str(expression)
4112            .map(Expression)
4113            .map_err(Into::into)
4114    }
4115}
4116
4117impl std::fmt::Display for Expression {
4118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4119        write!(f, "{}", &self.0)
4120    }
4121}
4122
4123/// "Restricted" expressions are used for attribute values and `context`.
4124///
4125/// Restricted expressions can contain only the following:
4126///   - bool, int, and string literals
4127///   - literal `EntityUid`s such as `User::"alice"`
4128///   - extension function calls, where the arguments must be other things
4129///     on this list
4130///   - set and record literals, where the values must be other things on
4131///     this list
4132///
4133/// That means the following are not allowed in restricted expressions:
4134///   - `principal`, `action`, `resource`, `context`
4135///   - builtin operators and functions, including `.`, `in`, `has`, `like`,
4136///     `.contains()`
4137///   - if-then-else expressions
4138#[repr(transparent)]
4139#[derive(Debug, Clone, RefCast, PartialEq, Eq)]
4140pub struct RestrictedExpression(pub(crate) ast::RestrictedExpr);
4141
4142#[doc(hidden)] // because this converts to a private/internal type
4143impl AsRef<ast::RestrictedExpr> for RestrictedExpression {
4144    fn as_ref(&self) -> &ast::RestrictedExpr {
4145        &self.0
4146    }
4147}
4148
4149impl RestrictedExpression {
4150    /// Create an expression representing a literal string.
4151    pub fn new_string(value: String) -> Self {
4152        Self(ast::RestrictedExpr::val(value))
4153    }
4154
4155    /// Create an expression representing a literal bool.
4156    pub fn new_bool(value: bool) -> Self {
4157        Self(ast::RestrictedExpr::val(value))
4158    }
4159
4160    /// Create an expression representing a literal long.
4161    pub fn new_long(value: ast::Integer) -> Self {
4162        Self(ast::RestrictedExpr::val(value))
4163    }
4164
4165    /// Create an expression representing a literal `EntityUid`.
4166    pub fn new_entity_uid(value: EntityUid) -> Self {
4167        Self(ast::RestrictedExpr::val(ast::EntityUID::from(value)))
4168    }
4169
4170    /// Create an expression representing a record.
4171    ///
4172    /// Error if any key appears two or more times in `fields`.
4173    pub fn new_record(
4174        fields: impl IntoIterator<Item = (String, Self)>,
4175    ) -> Result<Self, ExpressionConstructionError> {
4176        Ok(Self(ast::RestrictedExpr::record(
4177            fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
4178        )?))
4179    }
4180
4181    /// Create an expression representing a Set.
4182    pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
4183        Self(ast::RestrictedExpr::set(values.into_iter().map(|v| v.0)))
4184    }
4185
4186    /// Create an expression representing an ip address.
4187    /// This function does not perform error checking on the source string,
4188    /// it creates an expression that calls the `ip` constructor.
4189    pub fn new_ip(src: impl AsRef<str>) -> Self {
4190        let src_expr = ast::RestrictedExpr::val(src.as_ref());
4191        Self(ast::RestrictedExpr::call_extension_fn(
4192            ip_extension_name(),
4193            [src_expr],
4194        ))
4195    }
4196
4197    /// Create an expression representing a fixed precision decimal number.
4198    /// This function does not perform error checking on the source string,
4199    /// it creates an expression that calls the `decimal` constructor.
4200    pub fn new_decimal(src: impl AsRef<str>) -> Self {
4201        let src_expr = ast::RestrictedExpr::val(src.as_ref());
4202        Self(ast::RestrictedExpr::call_extension_fn(
4203            decimal_extension_name(),
4204            [src_expr],
4205        ))
4206    }
4207
4208    /// Create an expression representing a particular instant of time.
4209    /// This function does not perform error checking on the source string,
4210    /// it creates an expression that calls the `datetime` constructor.
4211    pub fn new_datetime(src: impl AsRef<str>) -> Self {
4212        let src_expr = ast::RestrictedExpr::val(src.as_ref());
4213        Self(ast::RestrictedExpr::call_extension_fn(
4214            datetime_extension_name(),
4215            [src_expr],
4216        ))
4217    }
4218
4219    /// Create an expression representing a duration of time.
4220    /// This function does not perform error checking on the source string,
4221    /// it creates an expression that calls the `datetime` constructor.
4222    pub fn new_duration(src: impl AsRef<str>) -> Self {
4223        let src_expr = ast::RestrictedExpr::val(src.as_ref());
4224        Self(ast::RestrictedExpr::call_extension_fn(
4225            duration_extension_name(),
4226            [src_expr],
4227        ))
4228    }
4229
4230    /// Create an unknown expression
4231    #[cfg(feature = "partial-eval")]
4232    pub fn new_unknown(name: impl AsRef<str>) -> Self {
4233        Self(ast::RestrictedExpr::unknown(ast::Unknown::new_untyped(
4234            name.as_ref(),
4235        )))
4236    }
4237}
4238
4239#[cfg(test)]
4240impl RestrictedExpression {
4241    /// Deconstruct an [`RestrictedExpression`] to get the internal type.
4242    /// This function is only intended to be used internally.
4243    pub(crate) fn into_inner(self) -> ast::RestrictedExpr {
4244        self.0
4245    }
4246}
4247
4248fn decimal_extension_name() -> ast::Name {
4249    // PANIC SAFETY: This is a constant and is known to be safe, verified by a test
4250    #[allow(clippy::unwrap_used)]
4251    ast::Name::unqualified_name("decimal".parse().unwrap())
4252}
4253
4254fn ip_extension_name() -> ast::Name {
4255    // PANIC SAFETY: This is a constant and is known to be safe, verified by a test
4256    #[allow(clippy::unwrap_used)]
4257    ast::Name::unqualified_name("ip".parse().unwrap())
4258}
4259
4260fn datetime_extension_name() -> ast::Name {
4261    // PANIC SAFETY: This is a constant and is known to be safe, verified by a test
4262    #[allow(clippy::unwrap_used)]
4263    ast::Name::unqualified_name("datetime".parse().unwrap())
4264}
4265
4266fn duration_extension_name() -> ast::Name {
4267    // PANIC SAFETY: This is a constant and is known to be safe, verified by a test
4268    #[allow(clippy::unwrap_used)]
4269    ast::Name::unqualified_name("duration".parse().unwrap())
4270}
4271
4272impl FromStr for RestrictedExpression {
4273    type Err = RestrictedExpressionParseError;
4274
4275    /// create a `RestrictedExpression` using Cedar syntax
4276    fn from_str(expression: &str) -> Result<Self, Self::Err> {
4277        ast::RestrictedExpr::from_str(expression)
4278            .map(RestrictedExpression)
4279            .map_err(Into::into)
4280    }
4281}
4282
4283/// Builder for a [`Request`]
4284///
4285/// The default for principal, action, resource, and context fields is Unknown
4286/// for partial evaluation.
4287#[doc = include_str!("../experimental_warning.md")]
4288#[cfg(feature = "partial-eval")]
4289#[derive(Debug, Clone)]
4290pub struct RequestBuilder<S> {
4291    principal: ast::EntityUIDEntry,
4292    action: ast::EntityUIDEntry,
4293    resource: ast::EntityUIDEntry,
4294    /// Here, `None` means unknown
4295    context: Option<ast::Context>,
4296    schema: S,
4297}
4298
4299/// A marker type that indicates [`Schema`] is not set for a request
4300#[doc = include_str!("../experimental_warning.md")]
4301#[cfg(feature = "partial-eval")]
4302#[derive(Debug, Clone, Copy)]
4303pub struct UnsetSchema;
4304
4305#[cfg(feature = "partial-eval")]
4306impl Default for RequestBuilder<UnsetSchema> {
4307    fn default() -> Self {
4308        Self {
4309            principal: ast::EntityUIDEntry::unknown(),
4310            action: ast::EntityUIDEntry::unknown(),
4311            resource: ast::EntityUIDEntry::unknown(),
4312            context: None,
4313            schema: UnsetSchema,
4314        }
4315    }
4316}
4317
4318#[cfg(feature = "partial-eval")]
4319impl<S> RequestBuilder<S> {
4320    /// Set the principal.
4321    ///
4322    /// Note that you can create the `EntityUid` using `.parse()` on any
4323    /// string (via the `FromStr` implementation for `EntityUid`).
4324    #[must_use]
4325    pub fn principal(self, principal: EntityUid) -> Self {
4326        Self {
4327            principal: ast::EntityUIDEntry::known(principal.into(), None),
4328            ..self
4329        }
4330    }
4331
4332    /// Set the principal to be unknown, but known to belong to a certain entity type.
4333    ///
4334    /// This information is taken into account when evaluating 'is', '==' and '!=' expressions.
4335    #[must_use]
4336    pub fn unknown_principal_with_type(self, principal_type: EntityTypeName) -> Self {
4337        Self {
4338            principal: ast::EntityUIDEntry::unknown_with_type(principal_type.0, None),
4339            ..self
4340        }
4341    }
4342
4343    /// Set the action.
4344    ///
4345    /// Note that you can create the `EntityUid` using `.parse()` on any
4346    /// string (via the `FromStr` implementation for `EntityUid`).
4347    #[must_use]
4348    pub fn action(self, action: EntityUid) -> Self {
4349        Self {
4350            action: ast::EntityUIDEntry::known(action.into(), None),
4351            ..self
4352        }
4353    }
4354
4355    /// Set the resource.
4356    ///
4357    /// Note that you can create the `EntityUid` using `.parse()` on any
4358    /// string (via the `FromStr` implementation for `EntityUid`).
4359    #[must_use]
4360    pub fn resource(self, resource: EntityUid) -> Self {
4361        Self {
4362            resource: ast::EntityUIDEntry::known(resource.into(), None),
4363            ..self
4364        }
4365    }
4366
4367    /// Set the resource to be unknown, but known to belong to a certain entity type.
4368    ///
4369    /// This information is taken into account when evaluating 'is', '==' and '!=' expressions.
4370    #[must_use]
4371    pub fn unknown_resource_with_type(self, resource_type: EntityTypeName) -> Self {
4372        Self {
4373            resource: ast::EntityUIDEntry::unknown_with_type(resource_type.0, None),
4374            ..self
4375        }
4376    }
4377
4378    /// Set the context.
4379    #[must_use]
4380    pub fn context(self, context: Context) -> Self {
4381        Self {
4382            context: Some(context.0),
4383            ..self
4384        }
4385    }
4386}
4387
4388#[cfg(feature = "partial-eval")]
4389impl RequestBuilder<UnsetSchema> {
4390    /// Set the schema. If present, this will be used for request validation.
4391    #[must_use]
4392    pub fn schema(self, schema: &Schema) -> RequestBuilder<&Schema> {
4393        RequestBuilder {
4394            principal: self.principal,
4395            action: self.action,
4396            resource: self.resource,
4397            context: self.context,
4398            schema,
4399        }
4400    }
4401
4402    /// Create the [`Request`]
4403    pub fn build(self) -> Request {
4404        Request(ast::Request::new_unchecked(
4405            self.principal,
4406            self.action,
4407            self.resource,
4408            self.context,
4409        ))
4410    }
4411}
4412
4413#[cfg(feature = "partial-eval")]
4414impl RequestBuilder<&Schema> {
4415    /// Create the [`Request`]
4416    pub fn build(self) -> Result<Request, RequestValidationError> {
4417        Ok(Request(ast::Request::new_with_unknowns(
4418            self.principal,
4419            self.action,
4420            self.resource,
4421            self.context,
4422            Some(&self.schema.0),
4423            Extensions::all_available(),
4424        )?))
4425    }
4426}
4427
4428/// An authorization request is a tuple `<P, A, R, C>` where
4429/// * P is the principal [`EntityUid`],
4430/// * A is the action [`EntityUid`],
4431/// * R is the resource [`EntityUid`], and
4432/// * C is the request [`Context`] record.
4433///
4434/// It represents an authorization request asking the question, "Can this
4435/// principal take this action on this resource in this context?"
4436#[repr(transparent)]
4437#[derive(Debug, Clone, RefCast)]
4438pub struct Request(pub(crate) ast::Request);
4439
4440#[doc(hidden)] // because this converts to a private/internal type
4441impl AsRef<ast::Request> for Request {
4442    fn as_ref(&self) -> &ast::Request {
4443        &self.0
4444    }
4445}
4446
4447#[doc(hidden)]
4448impl From<ast::Request> for Request {
4449    fn from(req: ast::Request) -> Self {
4450        Self(req)
4451    }
4452}
4453
4454impl Request {
4455    /// Create a [`RequestBuilder`]
4456    #[doc = include_str!("../experimental_warning.md")]
4457    #[cfg(feature = "partial-eval")]
4458    pub fn builder() -> RequestBuilder<UnsetSchema> {
4459        RequestBuilder::default()
4460    }
4461
4462    /// Create a Request.
4463    ///
4464    /// Note that you can create the `EntityUid`s using `.parse()` on any
4465    /// string (via the `FromStr` implementation for `EntityUid`).
4466    /// The principal, action, and resource fields are optional to support
4467    /// the case where these fields do not contribute to authorization
4468    /// decisions (e.g., because they are not used in your policies).
4469    /// If any of the fields are `None`, we will automatically generate
4470    /// a unique entity UID that is not equal to any UID in the store.
4471    ///
4472    /// If `schema` is present, this constructor will validate that the
4473    /// `Request` complies with the given `schema`.
4474    pub fn new(
4475        principal: EntityUid,
4476        action: EntityUid,
4477        resource: EntityUid,
4478        context: Context,
4479        schema: Option<&Schema>,
4480    ) -> Result<Self, RequestValidationError> {
4481        Ok(Self(ast::Request::new(
4482            (principal.into(), None),
4483            (action.into(), None),
4484            (resource.into(), None),
4485            context.0,
4486            schema.map(|schema| &schema.0),
4487            Extensions::all_available(),
4488        )?))
4489    }
4490
4491    /// Get the context component of the request. Returns `None` if the context is
4492    /// "unknown" (i.e., constructed using the partial evaluation APIs).
4493    pub fn context(&self) -> Option<&Context> {
4494        self.0.context().map(Context::ref_cast)
4495    }
4496
4497    /// Get the principal component of the request. Returns `None` if the principal is
4498    /// "unknown" (i.e., constructed using the partial evaluation APIs).
4499    pub fn principal(&self) -> Option<&EntityUid> {
4500        match self.0.principal() {
4501            ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
4502            ast::EntityUIDEntry::Unknown { .. } => None,
4503        }
4504    }
4505
4506    /// Get the action component of the request. Returns `None` if the action is
4507    /// "unknown" (i.e., constructed using the partial evaluation APIs).
4508    pub fn action(&self) -> Option<&EntityUid> {
4509        match self.0.action() {
4510            ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
4511            ast::EntityUIDEntry::Unknown { .. } => None,
4512        }
4513    }
4514
4515    /// Get the resource component of the request. Returns `None` if the resource is
4516    /// "unknown" (i.e., constructed using the partial evaluation APIs).
4517    pub fn resource(&self) -> Option<&EntityUid> {
4518        match self.0.resource() {
4519            ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
4520            ast::EntityUIDEntry::Unknown { .. } => None,
4521        }
4522    }
4523}
4524
4525/// the Context object for an authorization request
4526#[repr(transparent)]
4527#[derive(Debug, Clone, RefCast)]
4528pub struct Context(ast::Context);
4529
4530#[doc(hidden)] // because this converts to a private/internal type
4531impl AsRef<ast::Context> for Context {
4532    fn as_ref(&self) -> &ast::Context {
4533        &self.0
4534    }
4535}
4536
4537impl Context {
4538    /// Create an empty `Context`
4539    /// ```
4540    /// # use cedar_policy::Context;
4541    /// let context = Context::empty();
4542    /// ```
4543    pub fn empty() -> Self {
4544        Self(ast::Context::empty())
4545    }
4546
4547    /// Create a `Context` from a map of key to "restricted expression",
4548    /// or a Vec of `(key, restricted expression)` pairs, or any other iterator
4549    /// of `(key, restricted expression)` pairs.
4550    /// ```
4551    /// # use cedar_policy::{Context, EntityUid, RestrictedExpression, Request};
4552    /// # use std::str::FromStr;
4553    /// let context = Context::from_pairs([
4554    ///   ("key".to_string(), RestrictedExpression::from_str(r#""value""#).unwrap()),
4555    ///   ("age".to_string(), RestrictedExpression::from_str("18").unwrap()),
4556    /// ]).unwrap();
4557    /// # // create a request
4558    /// # let p = EntityUid::from_str(r#"User::"alice""#).unwrap();
4559    /// # let a = EntityUid::from_str(r#"Action::"view""#).unwrap();
4560    /// # let r = EntityUid::from_str(r#"Album::"trip""#).unwrap();
4561    /// # let request: Request = Request::new(p, a, r, context, None).unwrap();
4562    /// ```
4563    pub fn from_pairs(
4564        pairs: impl IntoIterator<Item = (String, RestrictedExpression)>,
4565    ) -> Result<Self, ContextCreationError> {
4566        Ok(Self(ast::Context::from_pairs(
4567            pairs.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
4568            Extensions::all_available(),
4569        )?))
4570    }
4571
4572    /// Retrieves a value from the Context by its key.
4573    ///
4574    /// # Arguments
4575    ///
4576    /// * `key` - The key to look up in the context
4577    ///
4578    /// # Returns
4579    ///
4580    /// * `Some(EvalResult)` - If the key exists in the context, returns its value
4581    /// * `None` - If the key doesn't exist or if the context is not a Value type
4582    ///
4583    /// # Examples
4584    ///
4585    /// ```
4586    /// # use cedar_policy::{Context, Request, EntityUid};
4587    /// # use std::str::FromStr;
4588    /// let context = Context::from_json_str(r#"{"rayId": "abc123"}"#, None).unwrap();
4589    /// if let Some(value) = context.get("rayId") {
4590    ///     // value here is an EvalResult, convertible from the internal Value type
4591    ///     println!("Found value: {:?}", value);
4592    /// }
4593    /// assert_eq!(context.get("nonexistent"), None);
4594    /// ```
4595    pub fn get(&self, key: &str) -> Option<EvalResult> {
4596        match &self.0 {
4597            ast::Context::Value(map) => map.get(key).map(|v| EvalResult::from(v.clone())),
4598            ast::Context::RestrictedResidual(_) => None,
4599        }
4600    }
4601
4602    /// Create a `Context` from a string containing JSON (which must be a JSON
4603    /// object, not any other JSON type, or you will get an error here).
4604    /// JSON here must use the `__entity` and `__extn` escapes for entity
4605    /// references, extension values, etc.
4606    ///
4607    /// If a `schema` is provided, this will inform the parsing: for instance, it
4608    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
4609    /// if attributes have the wrong types (e.g., string instead of integer).
4610    /// Since different Actions have different schemas for `Context`, you also
4611    /// must specify the `Action` for schema-based parsing.
4612    /// ```
4613    /// # use cedar_policy::{Context, EntityUid, RestrictedExpression, Request};
4614    /// # use std::str::FromStr;
4615    /// let json_data = r#"{
4616    ///     "sub": "1234",
4617    ///     "groups": {
4618    ///         "1234": {
4619    ///             "group_id": "abcd",
4620    ///             "group_name": "test-group"
4621    ///         }
4622    ///     }
4623    /// }"#;
4624    /// let context = Context::from_json_str(json_data, None).unwrap();
4625    /// # // create a request
4626    /// # let p = EntityUid::from_str(r#"User::"alice""#).unwrap();
4627    /// # let a = EntityUid::from_str(r#"Action::"view""#).unwrap();
4628    /// # let r = EntityUid::from_str(r#"Album::"trip""#).unwrap();
4629    /// # let request: Request = Request::new(p, a, r, context, None).unwrap();
4630    /// ```
4631    pub fn from_json_str(
4632        json: &str,
4633        schema: Option<(&Schema, &EntityUid)>,
4634    ) -> Result<Self, ContextJsonError> {
4635        let schema = schema
4636            .map(|(s, uid)| Self::get_context_schema(s, uid))
4637            .transpose()?;
4638        let context = cedar_policy_core::entities::ContextJsonParser::new(
4639            schema.as_ref(),
4640            Extensions::all_available(),
4641        )
4642        .from_json_str(json)?;
4643        Ok(Self(context))
4644    }
4645
4646    /// Create a `Context` from a `serde_json::Value` (which must be a JSON object,
4647    /// not any other JSON type, or you will get an error here).
4648    /// JSON here must use the `__entity` and `__extn` escapes for entity
4649    /// references, extension values, etc.
4650    ///
4651    /// If a `schema` is provided, this will inform the parsing: for instance, it
4652    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
4653    /// if attributes have the wrong types (e.g., string instead of integer).
4654    /// Since different Actions have different schemas for `Context`, you also
4655    /// must specify the `Action` for schema-based parsing.
4656    /// ```
4657    /// # use cedar_policy::{Context, EntityUid, EntityId, EntityTypeName, RestrictedExpression, Request, Schema};
4658    /// # use std::str::FromStr;
4659    /// let schema_json = serde_json::json!(
4660    ///     {
4661    ///       "": {
4662    ///         "entityTypes": {
4663    ///           "User": {},
4664    ///           "Album": {},
4665    ///         },
4666    ///         "actions": {
4667    ///           "view": {
4668    ///              "appliesTo": {
4669    ///                "principalTypes": ["User"],
4670    ///                "resourceTypes": ["Album"],
4671    ///                "context": {
4672    ///                  "type": "Record",
4673    ///                  "attributes": {
4674    ///                    "sub": { "type": "Long" }
4675    ///                  }
4676    ///                }
4677    ///              }
4678    ///           }
4679    ///         }
4680    ///       }
4681    ///     });
4682    /// let schema = Schema::from_json_value(schema_json).unwrap();
4683    ///
4684    /// let a_eid = EntityId::from_str("view").unwrap();
4685    /// let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
4686    /// let action = EntityUid::from_type_name_and_id(a_name, a_eid);
4687    /// let data = serde_json::json!({
4688    ///     "sub": 1234
4689    /// });
4690    /// let context = Context::from_json_value(data, Some((&schema, &action))).unwrap();
4691    /// # let p = EntityUid::from_str(r#"User::"alice""#).unwrap();
4692    /// # let r = EntityUid::from_str(r#"Album::"trip""#).unwrap();
4693    /// # let request: Request = Request::new(p, action, r, context, Some(&schema)).unwrap();
4694    /// ```
4695    pub fn from_json_value(
4696        json: serde_json::Value,
4697        schema: Option<(&Schema, &EntityUid)>,
4698    ) -> Result<Self, ContextJsonError> {
4699        let schema = schema
4700            .map(|(s, uid)| Self::get_context_schema(s, uid))
4701            .transpose()?;
4702        let context = cedar_policy_core::entities::ContextJsonParser::new(
4703            schema.as_ref(),
4704            Extensions::all_available(),
4705        )
4706        .from_json_value(json)?;
4707        Ok(Self(context))
4708    }
4709
4710    /// Create a `Context` from a JSON file.  The JSON file must contain a JSON
4711    /// object, not any other JSON type, or you will get an error here.
4712    /// JSON here must use the `__entity` and `__extn` escapes for entity
4713    /// references, extension values, etc.
4714    ///
4715    /// If a `schema` is provided, this will inform the parsing: for instance, it
4716    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
4717    /// if attributes have the wrong types (e.g., string instead of integer).
4718    /// Since different Actions have different schemas for `Context`, you also
4719    /// must specify the `Action` for schema-based parsing.
4720    /// ```no_run
4721    /// # use cedar_policy::{Context, RestrictedExpression};
4722    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, Request,PolicySet};
4723    /// # use std::collections::HashMap;
4724    /// # use std::str::FromStr;
4725    /// # use std::fs::File;
4726    /// let mut json = File::open("json_file.json").unwrap();
4727    /// let context = Context::from_json_file(&json, None).unwrap();
4728    /// # // create a request
4729    /// # let p_eid = EntityId::from_str("alice").unwrap();
4730    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
4731    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
4732    /// #
4733    /// # let a_eid = EntityId::from_str("view").unwrap();
4734    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
4735    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
4736    /// # let r_eid = EntityId::from_str("trip").unwrap();
4737    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
4738    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
4739    /// # let request: Request = Request::new(p, a, r, context, None).unwrap();
4740    /// ```
4741    pub fn from_json_file(
4742        json: impl std::io::Read,
4743        schema: Option<(&Schema, &EntityUid)>,
4744    ) -> Result<Self, ContextJsonError> {
4745        let schema = schema
4746            .map(|(s, uid)| Self::get_context_schema(s, uid))
4747            .transpose()?;
4748        let context = cedar_policy_core::entities::ContextJsonParser::new(
4749            schema.as_ref(),
4750            Extensions::all_available(),
4751        )
4752        .from_json_file(json)?;
4753        Ok(Self(context))
4754    }
4755
4756    /// Internal helper function to convert `(&Schema, &EntityUid)` to `impl ContextSchema`
4757    fn get_context_schema(
4758        schema: &Schema,
4759        action: &EntityUid,
4760    ) -> Result<impl ContextSchema, ContextJsonError> {
4761        cedar_policy_core::validator::context_schema_for_action(&schema.0, action.as_ref())
4762            .ok_or_else(|| ContextJsonError::missing_action(action.clone()))
4763    }
4764
4765    /// Merge this [`Context`] with another context (or iterator over
4766    /// `(String, RestrictedExpression)` pairs), returning an error if the two
4767    /// contain overlapping keys
4768    pub fn merge(
4769        self,
4770        other_context: impl IntoIterator<Item = (String, RestrictedExpression)>,
4771    ) -> Result<Self, ContextCreationError> {
4772        Self::from_pairs(self.into_iter().chain(other_context))
4773    }
4774
4775    /// Validates this context against the provided schema
4776    ///
4777    /// Returns Ok(()) if the context is valid according to the schema, or an error otherwise
4778    ///
4779    /// This validation is already handled by `Request::new`, so there is no need to separately call
4780    /// if you are validating the whole request
4781    pub fn validate(
4782        &self,
4783        schema: &crate::Schema,
4784        action: &EntityUid,
4785    ) -> std::result::Result<(), RequestValidationError> {
4786        // Call the validate_context function from coreschema.rs
4787        Ok(RequestSchema::validate_context(
4788            &schema.0,
4789            &self.0,
4790            action.as_ref(),
4791            Extensions::all_available(),
4792        )?)
4793    }
4794}
4795
4796/// Utilities for implementing `IntoIterator` for `Context`
4797mod context {
4798    use super::{ast, RestrictedExpression};
4799
4800    /// `IntoIter` iterator for `Context`
4801    #[derive(Debug)]
4802    pub struct IntoIter {
4803        pub(super) inner: <ast::Context as IntoIterator>::IntoIter,
4804    }
4805
4806    impl Iterator for IntoIter {
4807        type Item = (String, RestrictedExpression);
4808
4809        fn next(&mut self) -> Option<Self::Item> {
4810            self.inner
4811                .next()
4812                .map(|(k, v)| (k.to_string(), RestrictedExpression(v)))
4813        }
4814    }
4815}
4816
4817impl IntoIterator for Context {
4818    type Item = (String, RestrictedExpression);
4819
4820    type IntoIter = context::IntoIter;
4821
4822    fn into_iter(self) -> Self::IntoIter {
4823        Self::IntoIter {
4824            inner: self.0.into_iter(),
4825        }
4826    }
4827}
4828
4829#[doc(hidden)]
4830impl From<ast::Context> for Context {
4831    fn from(c: ast::Context) -> Self {
4832        Self(c)
4833    }
4834}
4835
4836impl std::fmt::Display for Request {
4837    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4838        write!(f, "{}", self.0)
4839    }
4840}
4841
4842impl std::fmt::Display for Context {
4843    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4844        write!(f, "{}", self.0)
4845    }
4846}
4847
4848/// Result of Evaluation
4849#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
4850pub enum EvalResult {
4851    /// Boolean value
4852    Bool(bool),
4853    /// Signed integer value
4854    Long(ast::Integer),
4855    /// String value
4856    String(String),
4857    /// Entity Uid
4858    EntityUid(EntityUid),
4859    /// A first-class set
4860    Set(Set),
4861    /// A first-class anonymous record
4862    Record(Record),
4863    /// An extension value, currently limited to String results
4864    ExtensionValue(String),
4865    // ExtensionValue(std::sync::Arc<dyn InternalExtensionValue>),
4866}
4867
4868/// Sets of Cedar values
4869#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
4870pub struct Set(BTreeSet<EvalResult>);
4871
4872impl Set {
4873    /// Iterate over the members of the set
4874    pub fn iter(&self) -> impl Iterator<Item = &EvalResult> {
4875        self.0.iter()
4876    }
4877
4878    /// Is a given element in the set
4879    pub fn contains(&self, elem: &EvalResult) -> bool {
4880        self.0.contains(elem)
4881    }
4882
4883    /// Get the number of members of the set
4884    pub fn len(&self) -> usize {
4885        self.0.len()
4886    }
4887
4888    /// Test if the set is empty
4889    pub fn is_empty(&self) -> bool {
4890        self.0.is_empty()
4891    }
4892}
4893
4894/// A record of Cedar values
4895#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
4896pub struct Record(BTreeMap<String, EvalResult>);
4897
4898impl Record {
4899    /// Iterate over the attribute/value pairs in the record
4900    pub fn iter(&self) -> impl Iterator<Item = (&String, &EvalResult)> {
4901        self.0.iter()
4902    }
4903
4904    /// Check if a given attribute is in the record
4905    pub fn contains_attribute(&self, key: impl AsRef<str>) -> bool {
4906        self.0.contains_key(key.as_ref())
4907    }
4908
4909    /// Get a given attribute from the record
4910    pub fn get(&self, key: impl AsRef<str>) -> Option<&EvalResult> {
4911        self.0.get(key.as_ref())
4912    }
4913
4914    /// Get the number of attributes in the record
4915    pub fn len(&self) -> usize {
4916        self.0.len()
4917    }
4918
4919    /// Test if the record is empty
4920    pub fn is_empty(&self) -> bool {
4921        self.0.is_empty()
4922    }
4923}
4924
4925#[doc(hidden)]
4926impl From<ast::Value> for EvalResult {
4927    fn from(v: ast::Value) -> Self {
4928        match v.value {
4929            ast::ValueKind::Lit(ast::Literal::Bool(b)) => Self::Bool(b),
4930            ast::ValueKind::Lit(ast::Literal::Long(i)) => Self::Long(i),
4931            ast::ValueKind::Lit(ast::Literal::String(s)) => Self::String(s.to_string()),
4932            ast::ValueKind::Lit(ast::Literal::EntityUID(e)) => {
4933                Self::EntityUid(ast::EntityUID::clone(&e).into())
4934            }
4935            ast::ValueKind::Set(set) => Self::Set(Set(set
4936                .authoritative
4937                .iter()
4938                .map(|v| v.clone().into())
4939                .collect())),
4940            ast::ValueKind::Record(record) => Self::Record(Record(
4941                record
4942                    .iter()
4943                    .map(|(k, v)| (k.to_string(), v.clone().into()))
4944                    .collect(),
4945            )),
4946            ast::ValueKind::ExtensionValue(ev) => {
4947                Self::ExtensionValue(RestrictedExpr::from(ev.as_ref().clone()).to_string())
4948            }
4949        }
4950    }
4951}
4952
4953#[doc(hidden)]
4954// PANIC SAFETY: see the panic safety comments below
4955#[allow(clippy::fallible_impl_from)]
4956impl From<EvalResult> for Expression {
4957    fn from(res: EvalResult) -> Self {
4958        match res {
4959            EvalResult::Bool(b) => Self::new_bool(b),
4960            EvalResult::Long(l) => Self::new_long(l),
4961            EvalResult::String(s) => Self::new_string(s),
4962            EvalResult::EntityUid(eid) => {
4963                Self::from(ast::Expr::from(ast::Value::from(ast::EntityUID::from(eid))))
4964            }
4965            EvalResult::Set(set) => Self::new_set(set.iter().cloned().map(Self::from)),
4966            EvalResult::Record(r) => {
4967                // PANIC SAFETY: record originates from EvalResult so should not panic when reconstructing as an Expression
4968                #[allow(clippy::unwrap_used)]
4969                Self::new_record(r.iter().map(|(k, v)| (k.clone(), Self::from(v.clone())))).unwrap()
4970            }
4971            EvalResult::ExtensionValue(s) => {
4972                // PANIC SAFETY: the string s is constructed using RestrictedExpr::to_string() so should not panic when being parsed back into a RestrictedExpr
4973                #[allow(clippy::unwrap_used)]
4974                let expr: ast::Expr = ast::RestrictedExpr::from_str(&s).unwrap().into();
4975                Self::from(expr)
4976            }
4977        }
4978    }
4979}
4980
4981impl std::fmt::Display for EvalResult {
4982    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4983        match self {
4984            Self::Bool(b) => write!(f, "{b}"),
4985            Self::Long(l) => write!(f, "{l}"),
4986            Self::String(s) => write!(f, "\"{}\"", s.escape_debug()),
4987            Self::EntityUid(uid) => write!(f, "{uid}"),
4988            Self::Set(s) => {
4989                write!(f, "[")?;
4990                for (i, ev) in s.iter().enumerate() {
4991                    write!(f, "{ev}")?;
4992                    if (i + 1) < s.len() {
4993                        write!(f, ", ")?;
4994                    }
4995                }
4996                write!(f, "]")?;
4997                Ok(())
4998            }
4999            Self::Record(r) => {
5000                write!(f, "{{")?;
5001                for (i, (k, v)) in r.iter().enumerate() {
5002                    write!(f, "\"{}\": {v}", k.escape_debug())?;
5003                    if (i + 1) < r.len() {
5004                        write!(f, ", ")?;
5005                    }
5006                }
5007                write!(f, "}}")?;
5008                Ok(())
5009            }
5010            Self::ExtensionValue(s) => write!(f, "{s}"),
5011        }
5012    }
5013}
5014
5015/// Evaluates an expression.
5016///
5017/// If evaluation results in an error (e.g., attempting to access a non-existent Entity or Record,
5018/// passing the wrong number of arguments to a function etc.), that error is returned as a String
5019pub fn eval_expression(
5020    request: &Request,
5021    entities: &Entities,
5022    expr: &Expression,
5023) -> Result<EvalResult, EvaluationError> {
5024    let all_ext = Extensions::all_available();
5025    let eval = Evaluator::new(request.0.clone(), &entities.0, all_ext);
5026    Ok(EvalResult::from(
5027        // Evaluate under the empty slot map, as an expression should not have slots
5028        eval.interpret(&expr.0, &ast::SlotEnv::new())?,
5029    ))
5030}
5031
5032#[cfg(feature = "tpe")]
5033pub use tpe::*;
5034
5035#[cfg(feature = "tpe")]
5036mod tpe {
5037    use std::collections::{HashMap, HashSet};
5038
5039    use cedar_policy_core::ast::{self, PartialValueToValueError};
5040    use cedar_policy_core::authorizer::Decision;
5041    use cedar_policy_core::batched_evaluator::is_authorized_batched;
5042    use cedar_policy_core::batched_evaluator::{
5043        err::BatchedEvalError, EntityLoader as EntityLoaderInternal,
5044    };
5045    use cedar_policy_core::tpe;
5046    use cedar_policy_core::{
5047        entities::conformance::EntitySchemaConformanceChecker, extensions::Extensions,
5048        validator::CoreSchema,
5049    };
5050    use itertools::Itertools;
5051    use ref_cast::RefCast;
5052
5053    use crate::Entity;
5054    #[cfg(feature = "partial-eval")]
5055    use crate::{
5056        api, tpe_err, Authorizer, Context, Entities, EntityId, EntityTypeName, EntityUid,
5057        PartialRequestCreationError, PermissionQueryError, Policy, PolicySet, Request,
5058        RequestValidationError, RestrictedExpression, Schema, TPEReauthorizationError,
5059    };
5060
5061    /// A partial [`EntityUid`].
5062    /// That is, its [`EntityId`] could be unknown
5063    #[repr(transparent)]
5064    #[derive(Debug, Clone, RefCast)]
5065    pub struct PartialEntityUid(pub(crate) tpe::request::PartialEntityUID);
5066
5067    impl PartialEntityUid {
5068        /// Construct a [`PartialEntityUid`]
5069        pub fn new(ty: EntityTypeName, id: Option<EntityId>) -> Self {
5070            Self(tpe::request::PartialEntityUID {
5071                ty: ty.0,
5072                eid: id.map(|id| <EntityId as AsRef<ast::Eid>>::as_ref(&id).clone()),
5073            })
5074        }
5075
5076        /// Construct a [`PartialEntityUid`] from a concrete [`EntityUid`].
5077        pub fn from_concrete(euid: EntityUid) -> Self {
5078            let (ty, eid) = euid.0.components();
5079            Self(tpe::request::PartialEntityUID { ty, eid: Some(eid) })
5080        }
5081    }
5082
5083    /// A partial [`Request`]
5084    /// Its principal/resource types and action must be known and its context
5085    /// must either be fully known or unknown
5086    #[repr(transparent)]
5087    #[derive(Debug, Clone, RefCast)]
5088    pub struct PartialRequest(pub(crate) tpe::request::PartialRequest);
5089
5090    impl PartialRequest {
5091        /// Construct a valid [`PartialRequest`] according to a [`Schema`]
5092        pub fn new(
5093            principal: PartialEntityUid,
5094            action: EntityUid,
5095            resource: PartialEntityUid,
5096            context: Option<Context>,
5097            schema: &Schema,
5098        ) -> Result<Self, PartialRequestCreationError> {
5099            let context = context
5100                .map(|c| match c.0 {
5101                    ast::Context::RestrictedResidual(_) => {
5102                        Err(PartialRequestCreationError::ContextContainsUnknowns)
5103                    }
5104                    ast::Context::Value(m) => Ok(m),
5105                })
5106                .transpose()?;
5107            tpe::request::PartialRequest::new(principal.0, action.0, resource.0, context, &schema.0)
5108                .map(Self)
5109                .map_err(|e| PartialRequestCreationError::Validation(e.into()))
5110        }
5111    }
5112
5113    /// Like [`PartialRequest`] but only `resource` can be unknown
5114    #[repr(transparent)]
5115    #[derive(Debug, Clone, RefCast)]
5116    pub struct ResourceQueryRequest(pub(crate) PartialRequest);
5117
5118    impl ResourceQueryRequest {
5119        /// Construct a valid [`ResourceQueryRequest`] according to a [`Schema`]
5120        pub fn new(
5121            principal: EntityUid,
5122            action: EntityUid,
5123            resource: EntityTypeName,
5124            context: Context,
5125            schema: &Schema,
5126        ) -> Result<Self, PartialRequestCreationError> {
5127            PartialRequest::new(
5128                PartialEntityUid(principal.0.into()),
5129                action,
5130                PartialEntityUid::new(resource, None),
5131                Some(context),
5132                schema,
5133            )
5134            .map(Self)
5135        }
5136
5137        /// Convert [`ResourceQueryRequest`] to a [`Request`] by providing the resource [`EntityId`]
5138        pub fn to_request(
5139            &self,
5140            resource_id: EntityId,
5141            schema: Option<&Schema>,
5142        ) -> Result<Request, RequestValidationError> {
5143            // PANIC SAFETY: various fields are validated through the constructor
5144            #[allow(clippy::unwrap_used)]
5145            Request::new(
5146                EntityUid(self.0 .0.get_principal().try_into().unwrap()),
5147                EntityUid(self.0 .0.get_action()),
5148                EntityUid::from_type_name_and_id(
5149                    EntityTypeName(self.0 .0.get_resource_type()),
5150                    resource_id,
5151                ),
5152                Context::from_pairs(
5153                    self.0
5154                         .0
5155                        .get_context_attrs()
5156                        .unwrap()
5157                        .iter()
5158                        .map(|(a, v)| (a.to_string(), RestrictedExpression(v.clone().into()))),
5159                )
5160                .unwrap(),
5161                schema,
5162            )
5163        }
5164    }
5165
5166    /// Like [`PartialRequest`] but only `principal` can be unknown
5167    #[repr(transparent)]
5168    #[derive(Debug, Clone, RefCast)]
5169    pub struct PrincipalQueryRequest(pub(crate) PartialRequest);
5170
5171    impl PrincipalQueryRequest {
5172        /// Construct a valid [`PrincipalQueryRequest`] according to a [`Schema`]
5173        pub fn new(
5174            principal: EntityTypeName,
5175            action: EntityUid,
5176            resource: EntityUid,
5177            context: Context,
5178            schema: &Schema,
5179        ) -> Result<Self, PartialRequestCreationError> {
5180            PartialRequest::new(
5181                PartialEntityUid::new(principal, None),
5182                action,
5183                PartialEntityUid(resource.0.into()),
5184                Some(context),
5185                schema,
5186            )
5187            .map(Self)
5188        }
5189
5190        /// Convert [`PrincipalQueryRequest`] to a [`Request`] by providing the principal [`EntityId`]
5191        pub fn to_request(
5192            &self,
5193            principal_id: EntityId,
5194            schema: Option<&Schema>,
5195        ) -> Result<Request, RequestValidationError> {
5196            // PANIC SAFETY: various fields are validated through the constructor
5197            #[allow(clippy::unwrap_used)]
5198            Request::new(
5199                EntityUid::from_type_name_and_id(
5200                    EntityTypeName(self.0 .0.get_principal_type()),
5201                    principal_id,
5202                ),
5203                EntityUid(self.0 .0.get_action()),
5204                EntityUid(self.0 .0.get_resource().try_into().unwrap()),
5205                Context::from_pairs(
5206                    self.0
5207                         .0
5208                        .get_context_attrs()
5209                        .unwrap()
5210                        .iter()
5211                        .map(|(a, v)| (a.to_string(), RestrictedExpression(v.clone().into()))),
5212                )
5213                .unwrap(),
5214                schema,
5215            )
5216        }
5217    }
5218
5219    /// Partial [`Entities`]
5220    #[repr(transparent)]
5221    #[derive(Debug, Clone, RefCast)]
5222    pub struct PartialEntities(pub(crate) tpe::entities::PartialEntities);
5223
5224    impl PartialEntities {
5225        /// Construct [`PartialEntities`] from a JSON value
5226        /// The `parent`, `attrs`, `tags` field must be either fully known or
5227        /// unknown. And parent entities cannot have unknown parents.
5228        pub fn from_json_value(
5229            value: serde_json::Value,
5230            schema: &Schema,
5231        ) -> Result<Self, tpe_err::EntitiesError> {
5232            tpe::entities::PartialEntities::from_json_value(value, &schema.0).map(Self)
5233        }
5234
5235        /// Construct `[PartialEntities]` given a fully concrete `[Entities]`
5236        pub fn from_concrete(entities: Entities) -> Result<Self, PartialValueToValueError> {
5237            tpe::entities::PartialEntities::try_from(entities.0).map(Self)
5238        }
5239    }
5240
5241    /// A partial version of [`crate::Response`].
5242    #[repr(transparent)]
5243    #[derive(Debug, Clone, RefCast)]
5244    pub struct TPEResponse<'a>(pub(crate) tpe::response::Response<'a>);
5245
5246    impl TPEResponse<'_> {
5247        /// Attempt to get the authorization decision
5248        pub fn decision(&self) -> Option<Decision> {
5249            self.0.decision()
5250        }
5251
5252        /// Perform reauthorization
5253        pub fn reauthorize(
5254            &self,
5255            request: &Request,
5256            entities: &Entities,
5257        ) -> Result<api::Response, TPEReauthorizationError> {
5258            self.0
5259                .reauthorize(&request.0, &entities.0)
5260                .map(Into::into)
5261                .map_err(Into::into)
5262        }
5263    }
5264
5265    /// Entity loader trait for batched evaluation.
5266    ///
5267    /// Loads entities on demand, returning `None` for missing entities.
5268    /// The `load_entities` function must load all requested entities,
5269    /// and must compute and include all ancestors of the requested entities.
5270    /// Loading more entities than requested is allowed.
5271    pub trait EntityLoader {
5272        /// Load all entities for the given set of entity UIDs.
5273        /// Returns a map from [`EntityUid`] to [`Option<Entity>`], where `None` indicates
5274        /// the entity does not exist.
5275        fn load_entities(
5276            &mut self,
5277            uids: &HashSet<EntityUid>,
5278        ) -> HashMap<EntityUid, Option<Entity>>;
5279    }
5280
5281    /// Wrapper struct used to convert an [`EntityLoader`] to an `EntityLoaderInternal`
5282    struct EntityLoaderWrapper<'a>(&'a mut dyn EntityLoader);
5283
5284    impl EntityLoaderInternal for EntityLoaderWrapper<'_> {
5285        fn load_entities(
5286            &mut self,
5287            uids: &HashSet<ast::EntityUID>,
5288        ) -> HashMap<ast::EntityUID, Option<ast::Entity>> {
5289            let ids = uids
5290                .iter()
5291                .map(|id| EntityUid::ref_cast(id).clone())
5292                .collect();
5293            self.0
5294                .load_entities(&ids)
5295                .into_iter()
5296                .map(|(uid, entity)| (uid.0, entity.map(|e| e.0)))
5297                .collect()
5298        }
5299    }
5300
5301    /// Simple entity loader implementation that loads from a pre-existing Entities store
5302    #[derive(Debug)]
5303
5304    pub struct TestEntityLoader<'a> {
5305        entities: &'a Entities,
5306    }
5307
5308    impl<'a> TestEntityLoader<'a> {
5309        /// Create a new [`TestEntityLoader`] from an existing Entities store
5310        pub fn new(entities: &'a Entities) -> Self {
5311            Self { entities }
5312        }
5313    }
5314
5315    impl EntityLoader for TestEntityLoader<'_> {
5316        fn load_entities(
5317            &mut self,
5318            uids: &HashSet<EntityUid>,
5319        ) -> HashMap<EntityUid, Option<Entity>> {
5320            uids.iter()
5321                .map(|uid| {
5322                    let entity = self.entities.get(uid).cloned();
5323                    (uid.clone(), entity)
5324                })
5325                .collect()
5326        }
5327    }
5328
5329    impl PolicySet {
5330        /// Perform type-aware partial evaluation on this [`PolicySet`]
5331        /// If successful, the result is a [`PolicySet`] containing residual
5332        /// policies ready for re-authorization
5333        pub fn tpe<'a>(
5334            &self,
5335            request: &'a PartialRequest,
5336            entities: &'a PartialEntities,
5337            schema: &'a Schema,
5338        ) -> Result<TPEResponse<'a>, tpe_err::TPEError> {
5339            use cedar_policy_core::tpe::is_authorized;
5340            let ps = &self.ast;
5341            let res = is_authorized(ps, &request.0, &entities.0, &schema.0)?;
5342            Ok(TPEResponse(res))
5343        }
5344
5345        /// Like [`Authorizer::is_authorized`] but uses an [`EntityLoader`] to load
5346        /// entities on demand.
5347        ///
5348        /// Calls `loader` at most `max_iters` times, returning
5349        /// early if an authorization result is reached.
5350        /// Otherwise, it iterates `max_iters` times and returns
5351        /// a partial result.
5352        ///
5353        pub fn is_authorized_batched(
5354            &self,
5355            query: &Request,
5356            schema: &Schema,
5357            loader: &mut dyn EntityLoader,
5358            max_iters: u32,
5359        ) -> Result<Decision, BatchedEvalError> {
5360            is_authorized_batched(
5361                &query.0,
5362                &self.ast,
5363                &schema.0,
5364                &mut EntityLoaderWrapper(loader),
5365                max_iters,
5366            )
5367        }
5368
5369        /// Perform a permission query on the resource
5370        pub fn query_resource(
5371            &self,
5372            request: &ResourceQueryRequest,
5373            entities: &Entities,
5374            schema: &Schema,
5375        ) -> Result<impl Iterator<Item = EntityUid>, PermissionQueryError> {
5376            let partial_entities = PartialEntities(entities.clone().0.try_into()?);
5377            let core_schema = CoreSchema::new(&schema.0);
5378            let validator =
5379                EntitySchemaConformanceChecker::new(&core_schema, Extensions::all_available());
5380            // We need to type-check the entities
5381            for entity in entities.0.iter() {
5382                validator.validate_entity(entity)?;
5383            }
5384            let residuals = self.tpe(&request.0, &partial_entities, schema)?;
5385            // PANIC SAFETY: policy set construction should succeed because there shouldn't be any policy id conflicts
5386            #[allow(clippy::unwrap_used)]
5387            let policies = &Self::from_policies(
5388                residuals
5389                    .0
5390                    .residual_policies()
5391                    .into_iter()
5392                    .map(Policy::from_ast),
5393            )
5394            .unwrap();
5395            // PANIC SAFETY: request construction should succeed because each entity passes validation
5396            #[allow(clippy::unwrap_used)]
5397            match residuals.decision() {
5398                Some(Decision::Allow) => Ok(entities
5399                    .iter()
5400                    .filter(|entity| {
5401                        entity.0.uid().entity_type() == &request.0 .0.get_resource_type()
5402                    })
5403                    .map(super::Entity::uid)
5404                    .collect_vec()
5405                    .into_iter()),
5406                Some(Decision::Deny) => Ok(vec![].into_iter()),
5407                None => Ok(entities
5408                    .iter()
5409                    .filter(|entity| {
5410                        entity.0.uid().entity_type() == &request.0 .0.get_resource_type()
5411                    })
5412                    .filter(|entity| {
5413                        let authorizer = Authorizer::new();
5414                        authorizer
5415                            .is_authorized(
5416                                &request.to_request(entity.uid().id().clone(), None).unwrap(),
5417                                policies,
5418                                entities,
5419                            )
5420                            .decision
5421                            == Decision::Allow
5422                    })
5423                    .map(super::Entity::uid)
5424                    .collect_vec()
5425                    .into_iter()),
5426            }
5427        }
5428
5429        /// Perform a permission query on the principal
5430        pub fn query_principal(
5431            &self,
5432            request: &PrincipalQueryRequest,
5433            entities: &Entities,
5434            schema: &Schema,
5435        ) -> Result<impl Iterator<Item = EntityUid>, PermissionQueryError> {
5436            let partial_entities = PartialEntities(entities.clone().0.try_into()?);
5437            let core_schema = CoreSchema::new(&schema.0);
5438            let validator =
5439                EntitySchemaConformanceChecker::new(&core_schema, Extensions::all_available());
5440            // We need to type-check the entities
5441            for entity in entities.0.iter() {
5442                validator.validate_entity(entity)?;
5443            }
5444            let residuals = self.tpe(&request.0, &partial_entities, schema)?;
5445            // PANIC SAFETY: policy set construction should succeed because there shouldn't be any policy id conflicts
5446            #[allow(clippy::unwrap_used)]
5447            let policies = &Self::from_policies(
5448                residuals
5449                    .0
5450                    .residual_policies()
5451                    .into_iter()
5452                    .map(Policy::from_ast),
5453            )
5454            .unwrap();
5455            // PANIC SAFETY: request construction should succeed because each entity passes validation
5456            #[allow(clippy::unwrap_used)]
5457            match residuals.decision() {
5458                Some(Decision::Allow) => Ok(entities
5459                    .iter()
5460                    .filter(|entity| {
5461                        entity.0.uid().entity_type() == &request.0 .0.get_principal_type()
5462                    })
5463                    .map(super::Entity::uid)
5464                    .collect_vec()
5465                    .into_iter()),
5466                Some(Decision::Deny) => Ok(vec![].into_iter()),
5467                None => Ok(entities
5468                    .iter()
5469                    .filter(|entity| {
5470                        entity.0.uid().entity_type() == &request.0 .0.get_principal_type()
5471                    })
5472                    .filter(|entity| {
5473                        let authorizer = Authorizer::new();
5474                        authorizer
5475                            .is_authorized(
5476                                &request.to_request(entity.uid().id().clone(), None).unwrap(),
5477                                policies,
5478                                entities,
5479                            )
5480                            .decision
5481                            == Decision::Allow
5482                    })
5483                    .map(super::Entity::uid)
5484                    .collect_vec()
5485                    .into_iter()),
5486            }
5487        }
5488    }
5489}
5490
5491// These are the same tests in validator, just ensuring all the plumbing is done correctly
5492#[cfg(test)]
5493mod test_access {
5494    use cedar_policy_core::ast;
5495
5496    use super::*;
5497
5498    fn schema() -> Schema {
5499        let src = r#"
5500        type Task = {
5501    "id": Long,
5502    "name": String,
5503    "state": String,
5504};
5505
5506type T = String;
5507
5508type Tasks = Set<Task>;
5509entity List in [Application] = {
5510  "editors": Team,
5511  "name": String,
5512  "owner": User,
5513  "readers": Team,
5514  "tasks": Tasks,
5515};
5516entity Application;
5517entity User in [Team, Application] = {
5518  "joblevel": Long,
5519  "location": String,
5520};
5521
5522entity CoolList;
5523
5524entity Team in [Team, Application];
5525
5526action Read, Write, Create;
5527
5528action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
5529    principal: [User],
5530    resource : [List]
5531};
5532
5533action GetList in Read appliesTo {
5534    principal : [User],
5535    resource : [List, CoolList]
5536};
5537
5538action GetLists in Read appliesTo {
5539    principal : [User],
5540    resource : [Application]
5541};
5542
5543action CreateList in Create appliesTo {
5544    principal : [User],
5545    resource : [Application]
5546};
5547
5548        "#;
5549
5550        src.parse().unwrap()
5551    }
5552
5553    #[test]
5554    fn principals() {
5555        let schema = schema();
5556        let principals = schema.principals().collect::<HashSet<_>>();
5557        assert_eq!(principals.len(), 1);
5558        let user: EntityTypeName = "User".parse().unwrap();
5559        assert!(principals.contains(&user));
5560        let principals = schema.principals().collect::<Vec<_>>();
5561        assert!(principals.len() > 1);
5562        assert!(principals.iter().all(|ety| **ety == user));
5563        assert!(principals.iter().all(|ety| ety.0.loc().is_some()));
5564
5565        let et = ast::EntityType::EntityType(ast::Name::from_normalized_str("User").unwrap());
5566        let et = schema.0.get_entity_type(&et).unwrap();
5567        assert!(et.loc.is_some());
5568    }
5569
5570    #[cfg(feature = "extended-schema")]
5571    #[test]
5572    fn common_types_extended() {
5573        use cool_asserts::assert_matches;
5574
5575        use cedar_policy_core::validator::{
5576            types::{EntityRecordKind, Type},
5577            LocatedCommonType,
5578        };
5579
5580        let schema = schema();
5581        assert_eq!(schema.0.common_types().collect::<HashSet<_>>().len(), 3);
5582        let task_type = LocatedCommonType {
5583            name: "Task".into(),
5584            name_loc: None,
5585            type_loc: None,
5586        };
5587        assert!(schema.0.common_types().contains(&task_type));
5588
5589        let tasks_type = LocatedCommonType {
5590            name: "Tasks".into(),
5591            name_loc: None,
5592            type_loc: None,
5593        };
5594        assert!(schema.0.common_types().contains(&tasks_type));
5595        assert!(schema.0.common_types().all(|ct| ct.name_loc.is_some()));
5596        assert!(schema.0.common_types().all(|ct| ct.type_loc.is_some()));
5597
5598        let tasks_type = LocatedCommonType {
5599            name: "T".into(),
5600            name_loc: None,
5601            type_loc: None,
5602        };
5603        assert!(schema.0.common_types().contains(&tasks_type));
5604
5605        let et = ast::EntityType::EntityType(ast::Name::from_normalized_str("List").unwrap());
5606        let et = schema.0.get_entity_type(&et).unwrap();
5607        let attrs = et.attributes();
5608
5609        // Assert that attributes that are resolved from common types still get source locations
5610        let t = attrs.get_attr("tasks").unwrap();
5611        assert!(t.loc.is_some());
5612        assert_matches!(&t.attr_type, cedar_policy_core::validator::types::Type::Set { ref element_type } => {
5613            let el = *element_type.clone().unwrap();
5614            assert_matches!(el, Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
5615                assert!(attrs.get_attr("name").unwrap().loc.is_some());
5616                assert!(attrs.get_attr("id").unwrap().loc.is_some());
5617                assert!(attrs.get_attr("state").unwrap().loc.is_some());
5618            });
5619        });
5620    }
5621
5622    #[cfg(feature = "extended-schema")]
5623    #[test]
5624    fn namespace_extended() {
5625        let schema = schema();
5626        assert_eq!(schema.0.namespaces().collect::<HashSet<_>>().len(), 1);
5627        let default_namespace = schema.0.namespaces().last().unwrap();
5628        assert_eq!(default_namespace.name, SmolStr::from("__cedar"));
5629        assert!(default_namespace.name_loc.is_none());
5630        assert!(default_namespace.def_loc.is_none());
5631    }
5632
5633    #[test]
5634    fn empty_schema_principals_and_resources() {
5635        let empty: Schema = "".parse().unwrap();
5636        assert!(empty.principals().next().is_none());
5637        assert!(empty.resources().next().is_none());
5638    }
5639
5640    #[test]
5641    fn resources() {
5642        let schema = schema();
5643        let resources = schema.resources().cloned().collect::<HashSet<_>>();
5644        let expected: HashSet<EntityTypeName> = HashSet::from([
5645            "List".parse().unwrap(),
5646            "Application".parse().unwrap(),
5647            "CoolList".parse().unwrap(),
5648        ]);
5649        assert_eq!(resources, expected);
5650        assert!(resources.iter().all(|ety| ety.0.loc().is_some()));
5651    }
5652
5653    #[test]
5654    fn principals_for_action() {
5655        let schema = schema();
5656        let delete_list: EntityUid = r#"Action::"DeleteList""#.parse().unwrap();
5657        let delete_user: EntityUid = r#"Action::"DeleteUser""#.parse().unwrap();
5658        let got = schema
5659            .principals_for_action(&delete_list)
5660            .unwrap()
5661            .cloned()
5662            .collect::<Vec<_>>();
5663        assert_eq!(got, vec!["User".parse().unwrap()]);
5664        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5665        assert!(schema.principals_for_action(&delete_user).is_none());
5666    }
5667
5668    #[test]
5669    fn resources_for_action() {
5670        let schema = schema();
5671        let delete_list: EntityUid = r#"Action::"DeleteList""#.parse().unwrap();
5672        let delete_user: EntityUid = r#"Action::"DeleteUser""#.parse().unwrap();
5673        let create_list: EntityUid = r#"Action::"CreateList""#.parse().unwrap();
5674        let get_list: EntityUid = r#"Action::"GetList""#.parse().unwrap();
5675        let got = schema
5676            .resources_for_action(&delete_list)
5677            .unwrap()
5678            .cloned()
5679            .collect::<Vec<_>>();
5680        assert_eq!(got, vec!["List".parse().unwrap()]);
5681        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5682        let got = schema
5683            .resources_for_action(&create_list)
5684            .unwrap()
5685            .cloned()
5686            .collect::<Vec<_>>();
5687        assert_eq!(got, vec!["Application".parse().unwrap()]);
5688        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5689        let got = schema
5690            .resources_for_action(&get_list)
5691            .unwrap()
5692            .cloned()
5693            .collect::<HashSet<_>>();
5694        assert_eq!(
5695            got,
5696            HashSet::from(["List".parse().unwrap(), "CoolList".parse().unwrap()])
5697        );
5698        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5699        assert!(schema.principals_for_action(&delete_user).is_none());
5700    }
5701
5702    #[test]
5703    fn principal_parents() {
5704        let schema = schema();
5705        let user: EntityTypeName = "User".parse().unwrap();
5706        let parents = schema
5707            .ancestors(&user)
5708            .unwrap()
5709            .cloned()
5710            .collect::<HashSet<_>>();
5711        assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5712        let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
5713        assert_eq!(parents, expected);
5714        let parents = schema
5715            .ancestors(&"List".parse().unwrap())
5716            .unwrap()
5717            .cloned()
5718            .collect::<HashSet<_>>();
5719        assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5720        let expected = HashSet::from(["Application".parse().unwrap()]);
5721        assert_eq!(parents, expected);
5722        assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
5723        let parents = schema
5724            .ancestors(&"CoolList".parse().unwrap())
5725            .unwrap()
5726            .cloned()
5727            .collect::<HashSet<_>>();
5728        assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5729        let expected = HashSet::from([]);
5730        assert_eq!(parents, expected);
5731    }
5732
5733    #[test]
5734    fn action_groups() {
5735        let schema = schema();
5736        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
5737        let expected = ["Read", "Write", "Create"]
5738            .into_iter()
5739            .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5740            .collect::<HashSet<EntityUid>>();
5741        #[cfg(feature = "extended-schema")]
5742        assert!(groups.iter().all(|ety| ety.0.loc().is_some()));
5743        assert_eq!(groups, expected);
5744    }
5745
5746    #[test]
5747    fn actions() {
5748        let schema = schema();
5749        let actions = schema.actions().cloned().collect::<HashSet<_>>();
5750        let expected = [
5751            "Read",
5752            "Write",
5753            "Create",
5754            "DeleteList",
5755            "EditShare",
5756            "UpdateList",
5757            "CreateTask",
5758            "UpdateTask",
5759            "DeleteTask",
5760            "GetList",
5761            "GetLists",
5762            "CreateList",
5763        ]
5764        .into_iter()
5765        .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5766        .collect::<HashSet<EntityUid>>();
5767        assert_eq!(actions, expected);
5768        #[cfg(feature = "extended-schema")]
5769        assert!(actions.iter().all(|ety| ety.0.loc().is_some()));
5770    }
5771
5772    #[test]
5773    fn entities() {
5774        let schema = schema();
5775        let entities = schema.entity_types().cloned().collect::<HashSet<_>>();
5776        let expected = ["List", "Application", "User", "CoolList", "Team"]
5777            .into_iter()
5778            .map(|ty| ty.parse().unwrap())
5779            .collect::<HashSet<EntityTypeName>>();
5780        assert_eq!(entities, expected);
5781    }
5782}
5783
5784#[cfg(test)]
5785mod test_access_namespace {
5786    use super::*;
5787
5788    fn schema() -> Schema {
5789        let src = r#"
5790        namespace Foo {
5791        type Task = {
5792    "id": Long,
5793    "name": String,
5794    "state": String,
5795};
5796
5797type Tasks = Set<Task>;
5798entity List in [Application] = {
5799  "editors": Team,
5800  "name": String,
5801  "owner": User,
5802  "readers": Team,
5803  "tasks": Tasks,
5804};
5805entity Application;
5806entity User in [Team, Application] = {
5807  "joblevel": Long,
5808  "location": String,
5809};
5810
5811entity CoolList;
5812
5813entity Team in [Team, Application];
5814
5815action Read, Write, Create;
5816
5817action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
5818    principal: [User],
5819    resource : [List]
5820};
5821
5822action GetList in Read appliesTo {
5823    principal : [User],
5824    resource : [List, CoolList]
5825};
5826
5827action GetLists in Read appliesTo {
5828    principal : [User],
5829    resource : [Application]
5830};
5831
5832action CreateList in Create appliesTo {
5833    principal : [User],
5834    resource : [Application]
5835};
5836    }
5837
5838        "#;
5839
5840        src.parse().unwrap()
5841    }
5842
5843    #[test]
5844    fn principals() {
5845        let schema = schema();
5846        let principals = schema.principals().collect::<HashSet<_>>();
5847        assert_eq!(principals.len(), 1);
5848        let user: EntityTypeName = "Foo::User".parse().unwrap();
5849        assert!(principals.contains(&user));
5850        let principals = schema.principals().collect::<Vec<_>>();
5851        assert!(principals.len() > 1);
5852        assert!(principals.iter().all(|ety| **ety == user));
5853        assert!(principals.iter().all(|ety| ety.0.loc().is_some()));
5854    }
5855
5856    #[test]
5857    fn empty_schema_principals_and_resources() {
5858        let empty: Schema = "".parse().unwrap();
5859        assert!(empty.principals().next().is_none());
5860        assert!(empty.resources().next().is_none());
5861    }
5862
5863    #[test]
5864    fn resources() {
5865        let schema = schema();
5866        let resources = schema.resources().cloned().collect::<HashSet<_>>();
5867        let expected: HashSet<EntityTypeName> = HashSet::from([
5868            "Foo::List".parse().unwrap(),
5869            "Foo::Application".parse().unwrap(),
5870            "Foo::CoolList".parse().unwrap(),
5871        ]);
5872        assert_eq!(resources, expected);
5873        assert!(resources.iter().all(|ety| ety.0.loc().is_some()));
5874    }
5875
5876    #[test]
5877    fn principals_for_action() {
5878        let schema = schema();
5879        let delete_list: EntityUid = r#"Foo::Action::"DeleteList""#.parse().unwrap();
5880        let delete_user: EntityUid = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
5881        let got = schema
5882            .principals_for_action(&delete_list)
5883            .unwrap()
5884            .cloned()
5885            .collect::<Vec<_>>();
5886        assert_eq!(got, vec!["Foo::User".parse().unwrap()]);
5887        assert!(schema.principals_for_action(&delete_user).is_none());
5888    }
5889
5890    #[test]
5891    fn resources_for_action() {
5892        let schema = schema();
5893        let delete_list: EntityUid = r#"Foo::Action::"DeleteList""#.parse().unwrap();
5894        let delete_user: EntityUid = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
5895        let create_list: EntityUid = r#"Foo::Action::"CreateList""#.parse().unwrap();
5896        let get_list: EntityUid = r#"Foo::Action::"GetList""#.parse().unwrap();
5897        let got = schema
5898            .resources_for_action(&delete_list)
5899            .unwrap()
5900            .cloned()
5901            .collect::<Vec<_>>();
5902        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5903
5904        assert_eq!(got, vec!["Foo::List".parse().unwrap()]);
5905        let got = schema
5906            .resources_for_action(&create_list)
5907            .unwrap()
5908            .cloned()
5909            .collect::<Vec<_>>();
5910        assert_eq!(got, vec!["Foo::Application".parse().unwrap()]);
5911        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5912
5913        let got = schema
5914            .resources_for_action(&get_list)
5915            .unwrap()
5916            .cloned()
5917            .collect::<HashSet<_>>();
5918        assert_eq!(
5919            got,
5920            HashSet::from([
5921                "Foo::List".parse().unwrap(),
5922                "Foo::CoolList".parse().unwrap()
5923            ])
5924        );
5925        assert!(schema.principals_for_action(&delete_user).is_none());
5926    }
5927
5928    #[test]
5929    fn principal_parents() {
5930        let schema = schema();
5931        let user: EntityTypeName = "Foo::User".parse().unwrap();
5932        let parents = schema
5933            .ancestors(&user)
5934            .unwrap()
5935            .cloned()
5936            .collect::<HashSet<_>>();
5937        let expected = HashSet::from([
5938            "Foo::Team".parse().unwrap(),
5939            "Foo::Application".parse().unwrap(),
5940        ]);
5941        assert_eq!(parents, expected);
5942        let parents = schema
5943            .ancestors(&"Foo::List".parse().unwrap())
5944            .unwrap()
5945            .cloned()
5946            .collect::<HashSet<_>>();
5947        let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
5948        assert_eq!(parents, expected);
5949        assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
5950        let parents = schema
5951            .ancestors(&"Foo::CoolList".parse().unwrap())
5952            .unwrap()
5953            .cloned()
5954            .collect::<HashSet<_>>();
5955        let expected = HashSet::from([]);
5956        assert_eq!(parents, expected);
5957    }
5958
5959    #[test]
5960    fn action_groups() {
5961        let schema = schema();
5962        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
5963        let expected = ["Read", "Write", "Create"]
5964            .into_iter()
5965            .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5966            .collect::<HashSet<EntityUid>>();
5967        assert_eq!(groups, expected);
5968    }
5969
5970    #[test]
5971    fn actions() {
5972        let schema = schema();
5973        let actions = schema.actions().cloned().collect::<HashSet<_>>();
5974        let expected = [
5975            "Read",
5976            "Write",
5977            "Create",
5978            "DeleteList",
5979            "EditShare",
5980            "UpdateList",
5981            "CreateTask",
5982            "UpdateTask",
5983            "DeleteTask",
5984            "GetList",
5985            "GetLists",
5986            "CreateList",
5987        ]
5988        .into_iter()
5989        .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5990        .collect::<HashSet<EntityUid>>();
5991        assert_eq!(actions, expected);
5992    }
5993
5994    #[test]
5995    fn entities() {
5996        let schema = schema();
5997        let entities = schema.entity_types().cloned().collect::<HashSet<_>>();
5998        let expected = [
5999            "Foo::List",
6000            "Foo::Application",
6001            "Foo::User",
6002            "Foo::CoolList",
6003            "Foo::Team",
6004        ]
6005        .into_iter()
6006        .map(|ty| ty.parse().unwrap())
6007        .collect::<HashSet<EntityTypeName>>();
6008        assert_eq!(entities, expected);
6009    }
6010
6011    #[test]
6012    fn test_request_context() {
6013        // Create a context with some test data
6014        let context =
6015            Context::from_json_str(r#"{"testKey": "testValue", "numKey": 42}"#, None).unwrap();
6016
6017        // Create entity UIDs for the request
6018        let principal: EntityUid = "User::\"alice\"".parse().unwrap();
6019        let action: EntityUid = "Action::\"view\"".parse().unwrap();
6020        let resource: EntityUid = "Resource::\"doc123\"".parse().unwrap();
6021
6022        // Create the request
6023        let request = Request::new(
6024            principal, action, resource, context, None, // no schema validation for this test
6025        )
6026        .unwrap();
6027
6028        // Test context() method
6029        let retrieved_context = request.context().expect("Context should be present");
6030
6031        // Test get() method on the retrieved context
6032        assert!(retrieved_context.get("testKey").is_some());
6033        assert!(retrieved_context.get("numKey").is_some());
6034        assert!(retrieved_context.get("nonexistent").is_none());
6035    }
6036
6037    #[cfg(feature = "extended-schema")]
6038    #[test]
6039    fn namespace_extended() {
6040        let schema = schema();
6041        assert_eq!(schema.0.namespaces().collect::<HashSet<_>>().len(), 2);
6042        let default_namespace = schema
6043            .0
6044            .namespaces()
6045            .filter(|n| n.name == *"__cedar")
6046            .last()
6047            .unwrap();
6048        assert!(default_namespace.name_loc.is_none());
6049        assert!(default_namespace.def_loc.is_none());
6050
6051        let default_namespace = schema
6052            .0
6053            .namespaces()
6054            .filter(|n| n.name == *"Foo")
6055            .last()
6056            .unwrap();
6057        assert!(default_namespace.name_loc.is_some());
6058        assert!(default_namespace.def_loc.is_some());
6059    }
6060}
6061
6062#[cfg(test)]
6063mod test_lossless_empty {
6064    use super::{LosslessPolicy, Policy, PolicyId, Template};
6065
6066    #[test]
6067    fn test_lossless_empty_policy() {
6068        const STATIC_POLICY_TEXT: &str = "permit(principal,action,resource);";
6069        let policy0 = Policy::parse(Some(PolicyId::new("policy0")), STATIC_POLICY_TEXT)
6070            .expect("Failed to parse");
6071        let lossy_policy0 = Policy {
6072            ast: policy0.ast.clone(),
6073            lossless: LosslessPolicy::policy_or_template_text(None::<&str>),
6074        };
6075        // The `to_cedar` representation becomes lossy since we didn't provide text
6076        assert_eq!(
6077            lossy_policy0.to_cedar(),
6078            Some(String::from(
6079                "permit(\n  principal,\n  action,\n  resource\n);"
6080            ))
6081        );
6082        // The EST representation is obtained from the AST
6083        let lossy_policy0_est = lossy_policy0
6084            .lossless
6085            .est(|| policy0.ast.clone().into())
6086            .unwrap();
6087        assert_eq!(lossy_policy0_est, policy0.ast.into());
6088    }
6089
6090    #[test]
6091    fn test_lossless_empty_template() {
6092        const TEMPLATE_TEXT: &str = "permit(principal == ?principal,action,resource);";
6093        let template0 = Template::parse(Some(PolicyId::new("template0")), TEMPLATE_TEXT)
6094            .expect("Failed to parse");
6095        let lossy_template0 = Template {
6096            ast: template0.ast.clone(),
6097            lossless: LosslessPolicy::policy_or_template_text(None::<&str>),
6098        };
6099        // The `to_cedar` representation becomes lossy since we didn't provide text
6100        assert_eq!(
6101            lossy_template0.to_cedar(),
6102            String::from("permit(\n  principal == ?principal,\n  action,\n  resource\n);")
6103        );
6104        // The EST representation is obtained from the AST
6105        let lossy_template0_est = lossy_template0
6106            .lossless
6107            .est(|| template0.ast.clone().into())
6108            .unwrap();
6109        assert_eq!(lossy_template0_est, template0.ast.into());
6110    }
6111}
6112
6113/// Given a schema and policy set, compute an entity manifest.
6114///
6115/// The policies must validate against the schema in strict mode,
6116/// otherwise an error is returned.
6117/// The manifest describes the data required to answer requests
6118/// for each action.
6119#[doc = include_str!("../experimental_warning.md")]
6120#[cfg(feature = "entity-manifest")]
6121pub fn compute_entity_manifest(
6122    validator: &Validator,
6123    pset: &PolicySet,
6124) -> Result<EntityManifest, EntityManifestError> {
6125    entity_manifest::compute_entity_manifest(&validator.0, &pset.ast)
6126        .map_err(std::convert::Into::into)
6127}