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    /// Returns an iterator over the actions that apply to this principal and
2082    /// resource type, as specified by the `appliesTo` block for the action in
2083    /// this schema.
2084    pub fn actions_for_principal_and_resource<'a: 'b, 'b>(
2085        &'a self,
2086        principal_type: &'b EntityTypeName,
2087        resource_type: &'b EntityTypeName,
2088    ) -> impl Iterator<Item = &'a EntityUid> + 'b {
2089        self.0
2090            .actions_for_principal_and_resource(&principal_type.0, &resource_type.0)
2091            .map(RefCast::ref_cast)
2092    }
2093}
2094
2095/// Contains the result of policy validation.
2096///
2097/// The result includes the list of issues found by validation and whether validation succeeds or fails.
2098/// Validation succeeds if there are no fatal errors. There may still be
2099/// non-fatal warnings present when validation passes.
2100#[derive(Debug, Clone)]
2101pub struct ValidationResult {
2102    validation_errors: Vec<ValidationError>,
2103    validation_warnings: Vec<ValidationWarning>,
2104}
2105
2106impl ValidationResult {
2107    /// True when validation passes. There are no errors, but there may be
2108    /// non-fatal warnings. Use [`ValidationResult::validation_passed_without_warnings`]
2109    /// to check that there are also no warnings.
2110    pub fn validation_passed(&self) -> bool {
2111        self.validation_errors.is_empty()
2112    }
2113
2114    /// True when validation passes (i.e., there are no errors) and there are
2115    /// additionally no non-fatal warnings.
2116    pub fn validation_passed_without_warnings(&self) -> bool {
2117        self.validation_errors.is_empty() && self.validation_warnings.is_empty()
2118    }
2119
2120    /// Get an iterator over the errors found by the validator.
2121    pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError> {
2122        self.validation_errors.iter()
2123    }
2124
2125    /// Get an iterator over the warnings found by the validator.
2126    pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning> {
2127        self.validation_warnings.iter()
2128    }
2129
2130    fn first_error_or_warning(&self) -> Option<&dyn Diagnostic> {
2131        self.validation_errors
2132            .first()
2133            .map(|e| e as &dyn Diagnostic)
2134            .or_else(|| {
2135                self.validation_warnings
2136                    .first()
2137                    .map(|w| w as &dyn Diagnostic)
2138            })
2139    }
2140
2141    pub(crate) fn into_errors_and_warnings(
2142        self,
2143    ) -> (
2144        impl Iterator<Item = ValidationError>,
2145        impl Iterator<Item = ValidationWarning>,
2146    ) {
2147        (
2148            self.validation_errors.into_iter(),
2149            self.validation_warnings.into_iter(),
2150        )
2151    }
2152}
2153
2154#[doc(hidden)]
2155impl From<cedar_policy_core::validator::ValidationResult> for ValidationResult {
2156    fn from(r: cedar_policy_core::validator::ValidationResult) -> Self {
2157        let (errors, warnings) = r.into_errors_and_warnings();
2158        Self {
2159            validation_errors: errors.map(ValidationError::from).collect(),
2160            validation_warnings: warnings.map(ValidationWarning::from).collect(),
2161        }
2162    }
2163}
2164
2165impl std::fmt::Display for ValidationResult {
2166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2167        match self.first_error_or_warning() {
2168            Some(diagnostic) => write!(f, "{diagnostic}"),
2169            None => write!(f, "no errors or warnings"),
2170        }
2171    }
2172}
2173
2174impl std::error::Error for ValidationResult {
2175    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2176        self.first_error_or_warning()
2177            .and_then(std::error::Error::source)
2178    }
2179
2180    #[allow(deprecated)]
2181    fn description(&self) -> &str {
2182        self.first_error_or_warning()
2183            .map_or("no errors or warnings", std::error::Error::description)
2184    }
2185
2186    #[allow(deprecated)]
2187    fn cause(&self) -> Option<&dyn std::error::Error> {
2188        self.first_error_or_warning()
2189            .and_then(std::error::Error::cause)
2190    }
2191}
2192
2193// Except for `.related()`, and `.severity` everything is forwarded to the first
2194// error, or to the first warning if there are no errors. This is done for the
2195// same reason as policy parse errors.
2196impl Diagnostic for ValidationResult {
2197    fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn Diagnostic> + '_>> {
2198        let mut related = self
2199            .validation_errors
2200            .iter()
2201            .map(|err| err as &dyn Diagnostic)
2202            .chain(
2203                self.validation_warnings
2204                    .iter()
2205                    .map(|warn| warn as &dyn Diagnostic),
2206            );
2207        related.next().map(move |first| match first.related() {
2208            Some(first_related) => Box::new(first_related.chain(related)),
2209            None => Box::new(related) as Box<dyn Iterator<Item = _>>,
2210        })
2211    }
2212
2213    fn severity(&self) -> Option<miette::Severity> {
2214        self.first_error_or_warning()
2215            .map_or(Some(miette::Severity::Advice), Diagnostic::severity)
2216    }
2217
2218    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
2219        self.first_error_or_warning().and_then(Diagnostic::labels)
2220    }
2221
2222    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
2223        self.first_error_or_warning()
2224            .and_then(Diagnostic::source_code)
2225    }
2226
2227    fn code(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2228        self.first_error_or_warning().and_then(Diagnostic::code)
2229    }
2230
2231    fn url(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2232        self.first_error_or_warning().and_then(Diagnostic::url)
2233    }
2234
2235    fn help(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2236        self.first_error_or_warning().and_then(Diagnostic::help)
2237    }
2238
2239    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
2240        self.first_error_or_warning()
2241            .and_then(Diagnostic::diagnostic_source)
2242    }
2243}
2244
2245/// Scan a set of policies for potentially confusing/obfuscating text.
2246///
2247/// These checks are also provided through [`Validator::validate`] which provides more
2248/// comprehensive error detection, but this function can be used to check for
2249/// confusable strings without defining a schema.
2250pub fn confusable_string_checker<'a>(
2251    templates: impl Iterator<Item = &'a Template> + 'a,
2252) -> impl Iterator<Item = ValidationWarning> + 'a {
2253    cedar_policy_core::validator::confusable_string_checks(templates.map(|t| &t.ast))
2254        .map(std::convert::Into::into)
2255}
2256
2257/// Represents a namespace.
2258///
2259/// An `EntityNamespace` can can be constructed using
2260/// [`EntityNamespace::from_str`] or by calling `parse()` on a string.
2261/// _This can fail_, so it is important to properly handle an `Err` result.
2262///
2263/// ```
2264/// # use cedar_policy::EntityNamespace;
2265/// let id : Result<EntityNamespace, _> = "My::Name::Space".parse();
2266/// # assert_eq!(id.unwrap().to_string(), "My::Name::Space".to_string());
2267/// ```
2268#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
2269pub struct EntityNamespace(pub(crate) ast::Name);
2270
2271#[doc(hidden)] // because this converts to a private/internal type
2272impl AsRef<ast::Name> for EntityNamespace {
2273    fn as_ref(&self) -> &ast::Name {
2274        &self.0
2275    }
2276}
2277
2278/// This `FromStr` implementation requires the _normalized_ representation of the
2279/// namespace. See <https://github.com/cedar-policy/rfcs/pull/9/>.
2280impl FromStr for EntityNamespace {
2281    type Err = ParseErrors;
2282
2283    fn from_str(namespace_str: &str) -> Result<Self, Self::Err> {
2284        ast::Name::from_normalized_str(namespace_str)
2285            .map(EntityNamespace)
2286            .map_err(Into::into)
2287    }
2288}
2289
2290impl std::fmt::Display for EntityNamespace {
2291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2292        write!(f, "{}", self.0)
2293    }
2294}
2295
2296#[derive(Debug, Clone, Default)]
2297/// A struct representing a `PolicySet` as a series of strings for ser/de.
2298/// A `PolicySet` that contains template-linked policies cannot be
2299/// represented as this struct.
2300pub(crate) struct StringifiedPolicySet {
2301    /// The static policies in the set
2302    pub policies: Vec<String>,
2303    /// The policy templates in the set
2304    pub policy_templates: Vec<String>,
2305}
2306
2307/// Represents a set of `Policy`s
2308#[derive(Debug, Clone, Default)]
2309pub struct PolicySet {
2310    /// AST representation. Technically partially redundant with the other fields.
2311    /// Internally, we ensure that the duplicated information remains consistent.
2312    pub(crate) ast: ast::PolicySet,
2313    /// Policies in the set (this includes both static policies and template linked-policies)
2314    policies: LinkedHashMap<PolicyId, Policy>,
2315    /// Templates in the set
2316    templates: LinkedHashMap<PolicyId, Template>,
2317}
2318
2319impl PartialEq for PolicySet {
2320    fn eq(&self, other: &Self) -> bool {
2321        // eq is based on just the `ast`
2322        self.ast.eq(&other.ast)
2323    }
2324}
2325impl Eq for PolicySet {}
2326
2327#[doc(hidden)] // because this converts to a private/internal type
2328impl AsRef<ast::PolicySet> for PolicySet {
2329    fn as_ref(&self) -> &ast::PolicySet {
2330        &self.ast
2331    }
2332}
2333
2334#[doc(hidden)]
2335impl TryFrom<ast::PolicySet> for PolicySet {
2336    type Error = PolicySetError;
2337    fn try_from(pset: ast::PolicySet) -> Result<Self, Self::Error> {
2338        Self::from_ast(pset)
2339    }
2340}
2341
2342impl FromStr for PolicySet {
2343    type Err = ParseErrors;
2344
2345    /// Create a policy set from multiple statements.
2346    ///
2347    /// Policy ids will default to "policy*" with numbers from 0.
2348    /// If you load more policies, do not use the default id, or there will be conflicts.
2349    ///
2350    /// See [`Policy`] for more.
2351    fn from_str(policies: &str) -> Result<Self, Self::Err> {
2352        let (texts, pset) = parser::parse_policyset_and_also_return_policy_text(policies)?;
2353        // 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`.
2354        #[allow(clippy::expect_used)]
2355        let policies = pset.policies().map(|p|
2356            (
2357                PolicyId::new(p.id().clone()),
2358                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() }
2359            )
2360        ).collect();
2361        // PANIC SAFETY: By the same invariant, every `PolicyId` in `pset.templates()` also occurs as a key in `text`.
2362        #[allow(clippy::expect_used)]
2363        let templates = pset.templates().map(|t|
2364            (
2365                PolicyId::new(t.id().clone()),
2366                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() }
2367            )
2368        ).collect();
2369        Ok(Self {
2370            ast: pset,
2371            policies,
2372            templates,
2373        })
2374    }
2375}
2376
2377impl PolicySet {
2378    /// Build the policy set AST from the EST
2379    fn from_est(est: &est::PolicySet) -> Result<Self, PolicySetError> {
2380        let ast: ast::PolicySet = est.clone().try_into()?;
2381        // PANIC SAFETY: Since conversion from EST to AST succeeded, every `PolicyId` in `ast.policies()` occurs in `est`
2382        #[allow(clippy::expect_used)]
2383        let policies = ast
2384            .policies()
2385            .map(|p| {
2386                (
2387                    PolicyId::new(p.id().clone()),
2388                    Policy {
2389                        lossless: LosslessPolicy::Est(est.get_policy(p.id()).expect(
2390                            "internal invariant violation: policy id exists in asts but not ests",
2391                        )),
2392                        ast: p.clone(),
2393                    },
2394                )
2395            })
2396            .collect();
2397        // PANIC SAFETY: Since conversion from EST to AST succeeded, every `PolicyId` in `ast.templates()` occurs in `est`
2398        #[allow(clippy::expect_used)]
2399        let templates = ast
2400            .templates()
2401            .map(|t| {
2402                (
2403                    PolicyId::new(t.id().clone()),
2404                    Template {
2405                        lossless: LosslessPolicy::Est(est.get_template(t.id()).expect(
2406                            "internal invariant violation: template id exists in asts but not ests",
2407                        )),
2408                        ast: t.clone(),
2409                    },
2410                )
2411            })
2412            .collect();
2413        Ok(Self {
2414            ast,
2415            policies,
2416            templates,
2417        })
2418    }
2419
2420    /// Build the [`PolicySet`] from just the AST information
2421    #[cfg_attr(not(feature = "protobufs"), allow(dead_code))]
2422    pub(crate) fn from_ast(ast: ast::PolicySet) -> Result<Self, PolicySetError> {
2423        Self::from_policies(ast.into_policies().map(Policy::from_ast))
2424    }
2425
2426    /// Deserialize the [`PolicySet`] from a JSON string
2427    pub fn from_json_str(src: impl AsRef<str>) -> Result<Self, PolicySetError> {
2428        let est: est::PolicySet = serde_json::from_str(src.as_ref())
2429            .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2430        Self::from_est(&est)
2431    }
2432
2433    /// Deserialize the [`PolicySet`] from a JSON value
2434    pub fn from_json_value(src: serde_json::Value) -> Result<Self, PolicySetError> {
2435        let est: est::PolicySet = serde_json::from_value(src)
2436            .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2437        Self::from_est(&est)
2438    }
2439
2440    /// Deserialize the [`PolicySet`] from a JSON reader
2441    pub fn from_json_file(r: impl std::io::Read) -> Result<Self, PolicySetError> {
2442        let est: est::PolicySet = serde_json::from_reader(r)
2443            .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2444        Self::from_est(&est)
2445    }
2446
2447    /// Serialize the [`PolicySet`] as a JSON value
2448    pub fn to_json(self) -> Result<serde_json::Value, PolicySetError> {
2449        let est = self.est()?;
2450        let value = serde_json::to_value(est)
2451            .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2452        Ok(value)
2453    }
2454
2455    /// Get the EST representation of the [`PolicySet`]
2456    fn est(self) -> Result<est::PolicySet, PolicyToJsonError> {
2457        let (static_policies, template_links): (Vec<_>, Vec<_>) =
2458            fold_partition(self.policies, is_static_or_link)?;
2459        let static_policies = static_policies.into_iter().collect::<LinkedHashMap<_, _>>();
2460        let templates = self
2461            .templates
2462            .into_iter()
2463            .map(|(id, template)| {
2464                template
2465                    .lossless
2466                    .est(|| template.ast.clone().into())
2467                    .map(|est| (id.into(), est))
2468            })
2469            .collect::<Result<LinkedHashMap<_, _>, _>>()?;
2470        let est = est::PolicySet {
2471            templates,
2472            static_policies,
2473            template_links,
2474        };
2475
2476        Ok(est)
2477    }
2478
2479    /// Get the human-readable Cedar syntax representation of this policy set.
2480    /// This function is primarily intended for rendering JSON policies in the
2481    /// human-readable syntax, but it will also return the original policy text
2482    /// (though possibly re-ordering policies within the policy set) when the
2483    /// policy-set contains policies parsed from the human-readable syntax.
2484    ///
2485    /// This will return `None` if there are any linked policies in the policy
2486    /// set because they cannot be directly rendered in Cedar syntax. It also
2487    /// cannot record policy ids because these cannot be specified in the Cedar
2488    /// syntax. The policies may be reordered, so parsing the resulting string
2489    /// with [`PolicySet::from_str`] is likely to yield different policy id
2490    /// assignments. For these reasons you should prefer serializing as JSON (or protobuf) and
2491    /// only using this function to obtain a representation to display to human
2492    /// users.
2493    ///
2494    /// This function does not format the policy according to any particular
2495    /// rules.  Policy formatting can be done through the Cedar policy CLI or
2496    /// the `cedar-policy-formatter` crate.
2497    pub fn to_cedar(&self) -> Option<String> {
2498        match self.stringify() {
2499            Some(StringifiedPolicySet {
2500                policies,
2501                policy_templates,
2502            }) => {
2503                let policies_as_vec = policies
2504                    .into_iter()
2505                    .chain(policy_templates)
2506                    .collect::<Vec<_>>();
2507                Some(policies_as_vec.join("\n\n"))
2508            }
2509            None => None,
2510        }
2511    }
2512
2513    /// Get the human-readable Cedar syntax representation of this policy set,
2514    /// as a vec of strings. This function is useful to break up a large cedar
2515    /// file containing many policies into individual policies.
2516    ///
2517    /// This will return `None` if there are any linked policies in the policy
2518    /// set because they cannot be directly rendered in Cedar syntax. It also
2519    /// cannot record policy ids because these cannot be specified in the Cedar
2520    /// syntax. The policies may be reordered, so parsing the resulting string
2521    /// with [`PolicySet::from_str`] is likely to yield different policy id
2522    /// assignments. For these reasons you should prefer serializing as JSON (or protobuf) and
2523    /// only using this function to obtain a compact cedar representation,
2524    /// perhaps for storage purposes.
2525    ///
2526    /// This function does not format the policy according to any particular
2527    /// rules.  Policy formatting can be done through the Cedar policy CLI or
2528    /// the `cedar-policy-formatter` crate.
2529    pub(crate) fn stringify(&self) -> Option<StringifiedPolicySet> {
2530        let policies = self
2531            .policies
2532            .values()
2533            // We'd like to print policies in a deterministic order, so we sort
2534            // before printing, hoping that the size of policy sets is fairly
2535            // small.
2536            .sorted_by_key(|p| AsRef::<str>::as_ref(p.id()))
2537            .map(Policy::to_cedar)
2538            .collect::<Option<Vec<_>>>()?;
2539        let policy_templates = self
2540            .templates
2541            .values()
2542            .sorted_by_key(|t| AsRef::<str>::as_ref(t.id()))
2543            .map(Template::to_cedar)
2544            .collect_vec();
2545
2546        Some(StringifiedPolicySet {
2547            policies,
2548            policy_templates,
2549        })
2550    }
2551
2552    /// Create a fresh empty `PolicySet`
2553    pub fn new() -> Self {
2554        Self {
2555            ast: ast::PolicySet::new(),
2556            policies: LinkedHashMap::new(),
2557            templates: LinkedHashMap::new(),
2558        }
2559    }
2560
2561    /// Create a `PolicySet` from the given policies
2562    pub fn from_policies(
2563        policies: impl IntoIterator<Item = Policy>,
2564    ) -> Result<Self, PolicySetError> {
2565        let mut set = Self::new();
2566        for policy in policies {
2567            set.add(policy)?;
2568        }
2569        Ok(set)
2570    }
2571
2572    /// Helper function for `merge_policyset`
2573    /// Merges two sets and avoids name clashes by using the provided
2574    /// renaming. The type parameter `T` allows this code to be used for
2575    /// both Templates and Policies.
2576    fn merge_sets<T>(
2577        this: &mut LinkedHashMap<PolicyId, T>,
2578        other: &LinkedHashMap<PolicyId, T>,
2579        renaming: &HashMap<PolicyId, PolicyId>,
2580    ) where
2581        T: PartialEq + Clone,
2582    {
2583        for (pid, ot) in other {
2584            match renaming.get(pid) {
2585                Some(new_pid) => {
2586                    this.insert(new_pid.clone(), ot.clone());
2587                }
2588                None => {
2589                    if this.get(pid).is_none() {
2590                        this.insert(pid.clone(), ot.clone());
2591                    }
2592                    // If pid is not in the renaming but is in both
2593                    // this and other, then by assumption
2594                    // the element at pid in this and other are equal
2595                    // i.e., the renaming is expected to track all
2596                    // conflicting pids.
2597                }
2598            }
2599        }
2600    }
2601
2602    /// Merges this `PolicySet` with another `PolicySet`.
2603    /// This `PolicySet` is modified while the other `PolicySet`
2604    /// remains unchanged.
2605    ///
2606    /// The flag `rename_duplicates` controls the expected behavior
2607    /// when a `PolicyId` in this and the other `PolicySet` conflict.
2608    ///
2609    /// When `rename_duplicates` is false, conflicting `PolicyId`s result
2610    /// in a `PolicySetError::AlreadyDefined` error.
2611    ///
2612    /// Otherwise, when `rename_duplicates` is true, conflicting `PolicyId`s from
2613    /// the other `PolicySet` are automatically renamed to avoid conflict.
2614    /// This renaming is returned as a Hashmap from the old `PolicyId` to the
2615    /// renamed `PolicyId`.
2616    pub fn merge(
2617        &mut self,
2618        other: &Self,
2619        rename_duplicates: bool,
2620    ) -> Result<HashMap<PolicyId, PolicyId>, PolicySetError> {
2621        match self.ast.merge_policyset(&other.ast, rename_duplicates) {
2622            Ok(renaming) => {
2623                let renaming: HashMap<PolicyId, PolicyId> = renaming
2624                    .into_iter()
2625                    .map(|(old_pid, new_pid)| (PolicyId::new(old_pid), PolicyId::new(new_pid)))
2626                    .collect();
2627                Self::merge_sets(&mut self.templates, &other.templates, &renaming);
2628                Self::merge_sets(&mut self.policies, &other.policies, &renaming);
2629                Ok(renaming)
2630            }
2631            Err(ast::PolicySetError::Occupied { id }) => Err(PolicySetError::AlreadyDefined(
2632                policy_set_errors::AlreadyDefined {
2633                    id: PolicyId::new(id),
2634                },
2635            )),
2636        }
2637    }
2638
2639    /// Add an static policy to the `PolicySet`. To add a template instance, use
2640    /// `link` instead. This function will return an error (and not modify
2641    /// the `PolicySet`) if a template-linked policy is passed in.
2642    pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
2643        if policy.is_static() {
2644            let id = PolicyId::new(policy.ast.id().clone());
2645            self.ast.add(policy.ast.clone())?;
2646            self.policies.insert(id, policy);
2647            Ok(())
2648        } else {
2649            Err(PolicySetError::ExpectedStatic(
2650                policy_set_errors::ExpectedStatic::new(),
2651            ))
2652        }
2653    }
2654
2655    /// Remove a static `Policy` from the `PolicySet`.
2656    ///
2657    /// This will error if the policy is not a static policy.
2658    pub fn remove_static(&mut self, policy_id: PolicyId) -> Result<Policy, PolicySetError> {
2659        let Some(policy) = self.policies.remove(&policy_id) else {
2660            return Err(PolicySetError::PolicyNonexistent(
2661                policy_set_errors::PolicyNonexistentError { policy_id },
2662            ));
2663        };
2664        if self
2665            .ast
2666            .remove_static(&ast::PolicyID::from_string(&policy_id))
2667            .is_ok()
2668        {
2669            Ok(policy)
2670        } else {
2671            //Restore self.policies
2672            self.policies.insert(policy_id.clone(), policy);
2673            Err(PolicySetError::PolicyNonexistent(
2674                policy_set_errors::PolicyNonexistentError { policy_id },
2675            ))
2676        }
2677    }
2678
2679    /// Add a `Template` to the `PolicySet`
2680    pub fn add_template(&mut self, template: Template) -> Result<(), PolicySetError> {
2681        let id = PolicyId::new(template.ast.id().clone());
2682        self.ast.add_template(template.ast.clone())?;
2683        self.templates.insert(id, template);
2684        Ok(())
2685    }
2686
2687    /// Remove a `Template` from the `PolicySet`.
2688    ///
2689    /// This will error if any policy is linked to the template.
2690    /// This will error if `policy_id` is not a template.
2691    pub fn remove_template(&mut self, template_id: PolicyId) -> Result<Template, PolicySetError> {
2692        let Some(template) = self.templates.remove(&template_id) else {
2693            return Err(PolicySetError::TemplateNonexistent(
2694                policy_set_errors::TemplateNonexistentError { template_id },
2695            ));
2696        };
2697        // If self.templates and self.ast disagree, authorization cannot be trusted.
2698        // PANIC SAFETY: We just found the policy in self.templates.
2699        #[allow(clippy::panic)]
2700        match self
2701            .ast
2702            .remove_template(&ast::PolicyID::from_string(&template_id))
2703        {
2704            Ok(_) => Ok(template),
2705            Err(ast::PolicySetTemplateRemovalError::RemoveTemplateWithLinksError(_)) => {
2706                self.templates.insert(template_id.clone(), template);
2707                Err(PolicySetError::RemoveTemplateWithActiveLinks(
2708                    policy_set_errors::RemoveTemplateWithActiveLinksError { template_id },
2709                ))
2710            }
2711            Err(ast::PolicySetTemplateRemovalError::NotTemplateError(_)) => {
2712                self.templates.insert(template_id.clone(), template);
2713                Err(PolicySetError::RemoveTemplateNotTemplate(
2714                    policy_set_errors::RemoveTemplateNotTemplateError { template_id },
2715                ))
2716            }
2717            Err(ast::PolicySetTemplateRemovalError::RemovePolicyNoTemplateError(_)) => {
2718                panic!("Found template policy in self.templates but not in self.ast");
2719            }
2720        }
2721    }
2722
2723    /// Get policies linked to a `Template` in the `PolicySet`.
2724    /// If any policy is linked to the template, this will error
2725    pub fn get_linked_policies(
2726        &self,
2727        template_id: PolicyId,
2728    ) -> Result<impl Iterator<Item = &PolicyId>, PolicySetError> {
2729        self.ast
2730            .get_linked_policies(&ast::PolicyID::from_string(&template_id))
2731            .map_or_else(
2732                |_| {
2733                    Err(PolicySetError::TemplateNonexistent(
2734                        policy_set_errors::TemplateNonexistentError { template_id },
2735                    ))
2736                },
2737                |v| Ok(v.map(PolicyId::ref_cast)),
2738            )
2739    }
2740
2741    /// Iterate over all the `Policy`s in the `PolicySet`.
2742    ///
2743    /// This will include both static and template-linked policies.
2744    pub fn policies(&self) -> impl Iterator<Item = &Policy> {
2745        self.policies.values()
2746    }
2747
2748    /// Iterate over the `Template`'s in the `PolicySet`.
2749    pub fn templates(&self) -> impl Iterator<Item = &Template> {
2750        self.templates.values()
2751    }
2752
2753    /// Get a `Template` by its `PolicyId`
2754    pub fn template(&self, id: &PolicyId) -> Option<&Template> {
2755        self.templates.get(id)
2756    }
2757
2758    /// Get a `Policy` by its `PolicyId`
2759    pub fn policy(&self, id: &PolicyId) -> Option<&Policy> {
2760        self.policies.get(id)
2761    }
2762
2763    /// Extract annotation data from a `Policy` by its `PolicyId` and annotation key.
2764    /// If the annotation is present without an explicit value (e.g., `@annotation`),
2765    /// then this function returns `Some("")`. It returns `None` only when the
2766    /// annotation is not present.
2767    pub fn annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<&str> {
2768        self.ast
2769            .get(id.as_ref())?
2770            .annotation(&key.as_ref().parse().ok()?)
2771            .map(AsRef::as_ref)
2772    }
2773
2774    /// Extract annotation data from a `Template` by its `PolicyId` and annotation key.
2775    /// If the annotation is present without an explicit value (e.g., `@annotation`),
2776    /// then this function returns `Some("")`. It returns `None` only when the
2777    /// annotation is not present.
2778    pub fn template_annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<&str> {
2779        self.ast
2780            .get_template(id.as_ref())?
2781            .annotation(&key.as_ref().parse().ok()?)
2782            .map(AsRef::as_ref)
2783    }
2784
2785    /// Returns true iff the `PolicySet` is empty
2786    pub fn is_empty(&self) -> bool {
2787        debug_assert_eq!(
2788            self.ast.is_empty(),
2789            self.policies.is_empty() && self.templates.is_empty()
2790        );
2791        self.ast.is_empty()
2792    }
2793
2794    /// Returns the number of `Policy`s in the `PolicySet`.
2795    ///
2796    /// This will include both static and template-linked policies.
2797    pub fn num_of_policies(&self) -> usize {
2798        self.policies.len()
2799    }
2800
2801    /// Returns the number of `Template`s in the `PolicySet`.
2802    pub fn num_of_templates(&self) -> usize {
2803        self.templates.len()
2804    }
2805
2806    /// Attempt to link a template and add the new template-linked policy to the policy set.
2807    /// If link fails, the `PolicySet` is not modified.
2808    /// Failure can happen for three reasons
2809    ///   1) The map passed in `vals` may not match the slots in the template
2810    ///   2) The `new_id` may conflict w/ a policy that already exists in the set
2811    ///   3) `template_id` does not correspond to a template. Either the id is
2812    ///      not in the policy set, or it is in the policy set but is either a
2813    ///      linked or static policy rather than a template
2814    #[allow(clippy::needless_pass_by_value)]
2815    pub fn link(
2816        &mut self,
2817        template_id: PolicyId,
2818        new_id: PolicyId,
2819        vals: HashMap<SlotId, EntityUid>,
2820    ) -> Result<(), PolicySetError> {
2821        let unwrapped_vals: HashMap<ast::SlotId, ast::EntityUID> = vals
2822            .into_iter()
2823            .map(|(key, value)| (key.into(), value.into()))
2824            .collect();
2825
2826        // Try to get the template with the id we're linking from.  We do this
2827        // _before_ calling `self.ast.link` because `link` mutates the policy
2828        // set by creating a new link entry in a hashmap. This happens even when
2829        // trying to link a static policy, which we want to error on here.
2830        let Some(template) = self.templates.get(&template_id) else {
2831            return Err(if self.policies.contains_key(&template_id) {
2832                policy_set_errors::ExpectedTemplate::new().into()
2833            } else {
2834                policy_set_errors::LinkingError {
2835                    inner: ast::LinkingError::NoSuchTemplate {
2836                        id: template_id.into(),
2837                    },
2838                }
2839                .into()
2840            });
2841        };
2842
2843        let linked_ast = self.ast.link(
2844            template_id.into(),
2845            new_id.clone().into(),
2846            unwrapped_vals.clone(),
2847        )?;
2848
2849        // PANIC SAFETY: `lossless.link()` will not fail after `ast.link()` succeeds
2850        #[allow(clippy::expect_used)]
2851        let linked_lossless = template
2852            .lossless
2853            .clone()
2854            .link(unwrapped_vals.iter().map(|(k, v)| (*k, v)))
2855            // The only error case for `lossless.link()` is a template with
2856            // slots which are not filled by the provided values. `ast.link()`
2857            // will have already errored if there are any unfilled slots in the
2858            // template.
2859            .expect("ast.link() didn't fail above, so this shouldn't fail");
2860        self.policies.insert(
2861            new_id,
2862            Policy {
2863                ast: linked_ast.clone(),
2864                lossless: linked_lossless,
2865            },
2866        );
2867        Ok(())
2868    }
2869
2870    /// Get all the unknown entities from the policy set
2871    #[doc = include_str!("../experimental_warning.md")]
2872    #[cfg(feature = "partial-eval")]
2873    pub fn unknown_entities(&self) -> HashSet<EntityUid> {
2874        let mut entity_uids = HashSet::new();
2875        for policy in self.policies.values() {
2876            entity_uids.extend(policy.unknown_entities());
2877        }
2878        entity_uids
2879    }
2880
2881    /// Unlink a template-linked policy from the policy set.
2882    /// Returns the policy that was unlinked.
2883    pub fn unlink(&mut self, policy_id: PolicyId) -> Result<Policy, PolicySetError> {
2884        let Some(policy) = self.policies.remove(&policy_id) else {
2885            return Err(PolicySetError::LinkNonexistent(
2886                policy_set_errors::LinkNonexistentError { policy_id },
2887            ));
2888        };
2889        // If self.policies and self.ast disagree, authorization cannot be trusted.
2890        // PANIC SAFETY: We just found the policy in self.policies.
2891        #[allow(clippy::panic)]
2892        match self.ast.unlink(&ast::PolicyID::from_string(&policy_id)) {
2893            Ok(_) => Ok(policy),
2894            Err(ast::PolicySetUnlinkError::NotLinkError(_)) => {
2895                //Restore self.policies
2896                self.policies.insert(policy_id.clone(), policy);
2897                Err(PolicySetError::UnlinkLinkNotLink(
2898                    policy_set_errors::UnlinkLinkNotLinkError { policy_id },
2899                ))
2900            }
2901            Err(ast::PolicySetUnlinkError::UnlinkingError(_)) => {
2902                panic!("Found linked policy in self.policies but not in self.ast")
2903            }
2904        }
2905    }
2906}
2907
2908impl std::fmt::Display for PolicySet {
2909    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2910        // prefer to display the lossless format
2911        let mut policies = self.policies().peekable();
2912        while let Some(policy) = policies.next() {
2913            policy.lossless.fmt(|| policy.ast.clone().into(), f)?;
2914            if policies.peek().is_some() {
2915                writeln!(f)?;
2916            }
2917        }
2918        Ok(())
2919    }
2920}
2921
2922/// Given a [`PolicyId`] and a [`Policy`], determine if the policy represents a static policy or a
2923/// link
2924fn is_static_or_link(
2925    (id, policy): (PolicyId, Policy),
2926) -> Result<Either<(ast::PolicyID, est::Policy), TemplateLink>, PolicyToJsonError> {
2927    match policy.template_id() {
2928        Some(template_id) => {
2929            let values = policy
2930                .ast
2931                .env()
2932                .iter()
2933                .map(|(id, euid)| (*id, euid.clone()))
2934                .collect();
2935            Ok(Either::Right(TemplateLink {
2936                new_id: id.into(),
2937                template_id: template_id.clone().into(),
2938                values,
2939            }))
2940        }
2941        None => policy
2942            .lossless
2943            .est(|| policy.ast.clone().into())
2944            .map(|est| Either::Left((id.into(), est))),
2945    }
2946}
2947
2948/// Like [`itertools::Itertools::partition_map`], but accepts a function that can fail.
2949/// The first invocation of `f` that fails causes the whole computation to fail
2950#[allow(clippy::redundant_pub_crate)] // can't be private because it's used in tests
2951pub(crate) fn fold_partition<T, A, B, E>(
2952    i: impl IntoIterator<Item = T>,
2953    f: impl Fn(T) -> Result<Either<A, B>, E>,
2954) -> Result<(Vec<A>, Vec<B>), E> {
2955    let mut lefts = vec![];
2956    let mut rights = vec![];
2957
2958    for item in i {
2959        match f(item)? {
2960            Either::Left(left) => lefts.push(left),
2961            Either::Right(right) => rights.push(right),
2962        }
2963    }
2964
2965    Ok((lefts, rights))
2966}
2967
2968/// The "type" of a [`Request`], i.e., the [`EntityTypeName`]s of principal
2969/// and resource, the [`EntityUid`] of action, and [`Option<EntityTypeName>`]s
2970/// of principal slot and resource slot
2971#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2972pub struct RequestEnv {
2973    pub(crate) principal: EntityTypeName,
2974    pub(crate) action: EntityUid,
2975    pub(crate) resource: EntityTypeName,
2976    pub(crate) principal_slot: Option<EntityTypeName>,
2977    pub(crate) resource_slot: Option<EntityTypeName>,
2978}
2979
2980impl RequestEnv {
2981    /// Construct a [`RequestEnv`]
2982    pub fn new(principal: EntityTypeName, action: EntityUid, resource: EntityTypeName) -> Self {
2983        Self {
2984            principal,
2985            action,
2986            resource,
2987            principal_slot: None,
2988            resource_slot: None,
2989        }
2990    }
2991
2992    /// Construct a [`RequestEnv`] that contains slots in the scope
2993    pub fn new_request_env_with_slots(
2994        principal: EntityTypeName,
2995        action: EntityUid,
2996        resource: EntityTypeName,
2997        principal_slot: Option<EntityTypeName>,
2998        resource_slot: Option<EntityTypeName>,
2999    ) -> Self {
3000        Self {
3001            principal,
3002            action,
3003            resource,
3004            principal_slot,
3005            resource_slot,
3006        }
3007    }
3008
3009    /// Get the principal type name
3010    pub fn principal(&self) -> &EntityTypeName {
3011        &self.principal
3012    }
3013
3014    /// Get the action [`EntityUid`]
3015    pub fn action(&self) -> &EntityUid {
3016        &self.action
3017    }
3018
3019    /// Get the resource type name
3020    pub fn resource(&self) -> &EntityTypeName {
3021        &self.resource
3022    }
3023
3024    /// Get the principal slot type name
3025    pub fn principal_slot(&self) -> Option<&EntityTypeName> {
3026        self.principal_slot.as_ref()
3027    }
3028
3029    /// Get the resource slot type name
3030    pub fn resource_slot(&self) -> Option<&EntityTypeName> {
3031        self.resource_slot.as_ref()
3032    }
3033}
3034
3035#[doc(hidden)]
3036impl From<cedar_policy_core::validator::types::RequestEnv<'_>> for RequestEnv {
3037    fn from(renv: cedar_policy_core::validator::types::RequestEnv<'_>) -> Self {
3038        match renv {
3039            cedar_policy_core::validator::types::RequestEnv::DeclaredAction {
3040                principal,
3041                action,
3042                resource,
3043                principal_slot,
3044                resource_slot,
3045                ..
3046            } => Self {
3047                principal: principal.clone().into(),
3048                action: action.clone().into(),
3049                resource: resource.clone().into(),
3050                principal_slot: principal_slot.map(EntityTypeName::from),
3051                resource_slot: resource_slot.map(EntityTypeName::from),
3052            },
3053            // PANIC SAFETY: partial validation is not enabled and hence `RequestEnv::UndeclaredAction` should not show up
3054            #[allow(clippy::unreachable)]
3055            cedar_policy_core::validator::types::RequestEnv::UndeclaredAction => {
3056                unreachable!("used unsupported feature")
3057            }
3058        }
3059    }
3060}
3061
3062/// Get valid request envs for an `ast::Template`
3063///
3064/// This function is called by [`Template::get_valid_request_envs`] and
3065/// [`Policy::get_valid_request_envs`]
3066fn get_valid_request_envs(ast: &ast::Template, s: &Schema) -> impl Iterator<Item = RequestEnv> {
3067    let tc = Typechecker::new(
3068        &s.0,
3069        cedar_policy_core::validator::ValidationMode::default(),
3070    );
3071    tc.typecheck_by_request_env(ast)
3072        .into_iter()
3073        .filter_map(|(env, pc)| {
3074            if matches!(pc, PolicyCheck::Success(_)) {
3075                Some(env.into())
3076            } else {
3077                None
3078            }
3079        })
3080        .collect::<BTreeSet<_>>()
3081        .into_iter()
3082}
3083
3084/// Policy template datatype
3085//
3086// NOTE: Unlike the internal type [`ast::Template`], this type only supports
3087// templates. The `Template` constructors will return an error if provided with
3088// a static policy.
3089#[derive(Debug, Clone)]
3090pub struct Template {
3091    /// AST representation of the template, used for most operations.
3092    /// In particular, the `ast` contains the authoritative `PolicyId` for the template.
3093    pub(crate) ast: ast::Template,
3094
3095    /// Some "lossless" representation of the template, whichever is most
3096    /// convenient to provide (and can be provided with the least overhead).
3097    /// This is used just for `to_json()`.
3098    /// We can't just derive this on-demand from `ast`, because the AST is lossy:
3099    /// we can't reconstruct an accurate CST/EST/policy-text from the AST, but
3100    /// we can from the EST (modulo whitespace and a few other things like the
3101    /// order of annotations).
3102    ///
3103    /// This is a `LosslessPolicy` (rather than something like `LosslessTemplate`)
3104    /// because the EST doesn't distinguish between static policies and templates.
3105    pub(crate) lossless: LosslessPolicy,
3106}
3107
3108impl PartialEq for Template {
3109    fn eq(&self, other: &Self) -> bool {
3110        // eq is based on just the `ast`
3111        self.ast.eq(&other.ast)
3112    }
3113}
3114impl Eq for Template {}
3115
3116#[doc(hidden)] // because this converts to a private/internal type
3117impl AsRef<ast::Template> for Template {
3118    fn as_ref(&self) -> &ast::Template {
3119        &self.ast
3120    }
3121}
3122
3123#[doc(hidden)]
3124impl From<ast::Template> for Template {
3125    fn from(template: ast::Template) -> Self {
3126        Self::from_ast(template)
3127    }
3128}
3129
3130impl Template {
3131    /// Attempt to parse a [`Template`] from source.
3132    /// Returns an error if the input is a static policy (i.e., has no slots).
3133    /// If `id` is Some, then the resulting template will have that `id`.
3134    /// If the `id` is None, the parser will use the default "policy0".
3135    /// The behavior around None may change in the future.
3136    pub fn parse(id: Option<PolicyId>, src: impl AsRef<str>) -> Result<Self, ParseErrors> {
3137        let ast = parser::parse_template(id.map(Into::into), src.as_ref())?;
3138        Ok(Self {
3139            ast,
3140            lossless: LosslessPolicy::policy_or_template_text(Some(src.as_ref())),
3141        })
3142    }
3143
3144    /// Get the `PolicyId` of this `Template`
3145    pub fn id(&self) -> &PolicyId {
3146        PolicyId::ref_cast(self.ast.id())
3147    }
3148
3149    /// Clone this `Template` with a new `PolicyId`
3150    #[must_use]
3151    pub fn new_id(&self, id: PolicyId) -> Self {
3152        Self {
3153            ast: self.ast.new_id(id.into()),
3154            lossless: self.lossless.clone(), // Lossless representation doesn't include the `PolicyId`
3155        }
3156    }
3157
3158    /// Get the `Effect` (`Forbid` or `Permit`) of this `Template`
3159    pub fn effect(&self) -> Effect {
3160        self.ast.effect()
3161    }
3162
3163    /// Returns `true` if this template has a `when` or `unless` clause.
3164    pub fn has_non_scope_constraint(&self) -> bool {
3165        self.ast.non_scope_constraints().is_some()
3166    }
3167
3168    /// Get an annotation value of this `Template`.
3169    /// If the annotation is present without an explicit value (e.g., `@annotation`),
3170    /// then this function returns `Some("")`. Returns `None` when the
3171    /// annotation is not present or when `key` is not a valid annotation identifier.
3172    pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
3173        self.ast
3174            .annotation(&key.as_ref().parse().ok()?)
3175            .map(AsRef::as_ref)
3176    }
3177
3178    /// Iterate through annotation data of this `Template` as key-value pairs.
3179    /// Annotations which do not have an explicit value (e.g., `@annotation`),
3180    /// are included in the iterator with the value `""`.
3181    pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
3182        self.ast
3183            .annotations()
3184            .map(|(k, v)| (k.as_ref(), v.as_ref()))
3185    }
3186
3187    /// Iterate over the open slots in this `Template`
3188    pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
3189        self.ast.slots().map(|slot| SlotId::ref_cast(&slot.id))
3190    }
3191
3192    /// Get the scope constraint on this policy's principal
3193    pub fn principal_constraint(&self) -> TemplatePrincipalConstraint {
3194        match self.ast.principal_constraint().as_inner() {
3195            ast::PrincipalOrResourceConstraint::Any => TemplatePrincipalConstraint::Any,
3196            ast::PrincipalOrResourceConstraint::In(eref) => {
3197                TemplatePrincipalConstraint::In(match eref {
3198                    ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3199                    ast::EntityReference::Slot(_) => None,
3200                })
3201            }
3202            ast::PrincipalOrResourceConstraint::Eq(eref) => {
3203                TemplatePrincipalConstraint::Eq(match eref {
3204                    ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3205                    ast::EntityReference::Slot(_) => None,
3206                })
3207            }
3208            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3209                TemplatePrincipalConstraint::Is(entity_type.as_ref().clone().into())
3210            }
3211            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3212                TemplatePrincipalConstraint::IsIn(
3213                    entity_type.as_ref().clone().into(),
3214                    match eref {
3215                        ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3216                        ast::EntityReference::Slot(_) => None,
3217                    },
3218                )
3219            }
3220        }
3221    }
3222
3223    /// Get the scope constraint on this policy's action
3224    pub fn action_constraint(&self) -> ActionConstraint {
3225        // Clone the data from Core to be consistent with the other constraints
3226        match self.ast.action_constraint() {
3227            ast::ActionConstraint::Any => ActionConstraint::Any,
3228            ast::ActionConstraint::In(ids) => {
3229                ActionConstraint::In(ids.iter().map(|id| id.as_ref().clone().into()).collect())
3230            }
3231            ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(id.as_ref().clone().into()),
3232            #[cfg(feature = "tolerant-ast")]
3233            ast::ActionConstraint::ErrorConstraint => {
3234                // We will only have an ErrorConstraint if we are using a parser that allows Error nodes
3235                // It is not recommended to evaluate an AST that allows error nodes
3236                // If somehow someone tries to evaluate an AST that includes an Action constraint error, we will
3237                // treat it as `Any`
3238                ActionConstraint::Any
3239            }
3240        }
3241    }
3242
3243    /// Get the scope constraint on this policy's resource
3244    pub fn resource_constraint(&self) -> TemplateResourceConstraint {
3245        match self.ast.resource_constraint().as_inner() {
3246            ast::PrincipalOrResourceConstraint::Any => TemplateResourceConstraint::Any,
3247            ast::PrincipalOrResourceConstraint::In(eref) => {
3248                TemplateResourceConstraint::In(match eref {
3249                    ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3250                    ast::EntityReference::Slot(_) => None,
3251                })
3252            }
3253            ast::PrincipalOrResourceConstraint::Eq(eref) => {
3254                TemplateResourceConstraint::Eq(match eref {
3255                    ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3256                    ast::EntityReference::Slot(_) => None,
3257                })
3258            }
3259            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3260                TemplateResourceConstraint::Is(entity_type.as_ref().clone().into())
3261            }
3262            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3263                TemplateResourceConstraint::IsIn(
3264                    entity_type.as_ref().clone().into(),
3265                    match eref {
3266                        ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3267                        ast::EntityReference::Slot(_) => None,
3268                    },
3269                )
3270            }
3271        }
3272    }
3273
3274    /// Create a [`Template`] from its JSON representation.
3275    /// Returns an error if the input is a static policy (i.e., has no slots).
3276    /// If `id` is Some, the policy will be given that Policy Id.
3277    /// If `id` is None, then "JSON policy" will be used.
3278    /// The behavior around None may change in the future.
3279    pub fn from_json(
3280        id: Option<PolicyId>,
3281        json: serde_json::Value,
3282    ) -> Result<Self, PolicyFromJsonError> {
3283        let est: est::Policy = serde_json::from_value(json)
3284            .map_err(|e| entities_json_errors::JsonDeserializationError::Serde(e.into()))
3285            .map_err(cedar_policy_core::est::FromJsonError::from)?;
3286        Self::from_est(id, est)
3287    }
3288
3289    fn from_est(id: Option<PolicyId>, est: est::Policy) -> Result<Self, PolicyFromJsonError> {
3290        Ok(Self {
3291            ast: est.clone().try_into_ast_template(id.map(PolicyId::into))?,
3292            lossless: LosslessPolicy::Est(est),
3293        })
3294    }
3295
3296    #[cfg_attr(not(feature = "protobufs"), allow(dead_code))]
3297    pub(crate) fn from_ast(ast: ast::Template) -> Self {
3298        Self {
3299            lossless: LosslessPolicy::Est(ast.clone().into()),
3300            ast,
3301        }
3302    }
3303
3304    /// Get the JSON representation of this `Template`.
3305    pub fn to_json(&self) -> Result<serde_json::Value, PolicyToJsonError> {
3306        let est = self.lossless.est(|| self.ast.clone().into())?;
3307        serde_json::to_value(est).map_err(Into::into)
3308    }
3309
3310    /// Get the human-readable Cedar syntax representation of this template.
3311    /// This function is primarily intended for rendering JSON policies in the
3312    /// human-readable syntax, but it will also return the original policy text
3313    /// when given a policy parsed from the human-readable syntax.
3314    ///
3315    /// It also does not format the policy according to any particular rules.
3316    /// Policy formatting can be done through the Cedar policy CLI or
3317    /// the `cedar-policy-formatter` crate.
3318    pub fn to_cedar(&self) -> String {
3319        match &self.lossless {
3320            LosslessPolicy::Empty | LosslessPolicy::Est(_) => self.ast.to_string(),
3321            LosslessPolicy::Text { text, .. } => text.clone(),
3322        }
3323    }
3324
3325    /// Get the valid [`RequestEnv`]s for this template, according to the schema.
3326    ///
3327    /// That is, all the [`RequestEnv`]s in the schema for which this template is
3328    /// not trivially false.
3329    pub fn get_valid_request_envs(&self, s: &Schema) -> impl Iterator<Item = RequestEnv> {
3330        get_valid_request_envs(&self.ast, s)
3331    }
3332}
3333
3334impl std::fmt::Display for Template {
3335    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3336        // prefer to display the lossless format
3337        self.lossless.fmt(|| self.ast.clone().into(), f)
3338    }
3339}
3340
3341impl FromStr for Template {
3342    type Err = ParseErrors;
3343
3344    fn from_str(src: &str) -> Result<Self, Self::Err> {
3345        Self::parse(None, src)
3346    }
3347}
3348
3349/// Scope constraint on policy principals.
3350#[derive(Debug, Clone, PartialEq, Eq)]
3351pub enum PrincipalConstraint {
3352    /// Un-constrained
3353    Any,
3354    /// Must be In the given [`EntityUid`]
3355    In(EntityUid),
3356    /// Must be equal to the given [`EntityUid`]
3357    Eq(EntityUid),
3358    /// Must be the given [`EntityTypeName`]
3359    Is(EntityTypeName),
3360    /// Must be the given [`EntityTypeName`], and `in` the [`EntityUid`]
3361    IsIn(EntityTypeName, EntityUid),
3362}
3363
3364/// Scope constraint on policy principals for templates.
3365#[derive(Debug, Clone, PartialEq, Eq)]
3366pub enum TemplatePrincipalConstraint {
3367    /// Un-constrained
3368    Any,
3369    /// Must be In the given [`EntityUid`].
3370    /// If [`None`], then it is a template slot.
3371    In(Option<EntityUid>),
3372    /// Must be equal to the given [`EntityUid`].
3373    /// If [`None`], then it is a template slot.
3374    Eq(Option<EntityUid>),
3375    /// Must be the given [`EntityTypeName`].
3376    Is(EntityTypeName),
3377    /// Must be the given [`EntityTypeName`], and `in` the [`EntityUid`].
3378    /// If the [`EntityUid`] is [`Option::None`], then it is a template slot.
3379    IsIn(EntityTypeName, Option<EntityUid>),
3380}
3381
3382impl TemplatePrincipalConstraint {
3383    /// Does this constraint contain a slot?
3384    pub fn has_slot(&self) -> bool {
3385        match self {
3386            Self::Any | Self::Is(_) => false,
3387            Self::In(o) | Self::Eq(o) | Self::IsIn(_, o) => o.is_none(),
3388        }
3389    }
3390}
3391
3392/// Scope constraint on policy actions.
3393#[derive(Debug, Clone, PartialEq, Eq)]
3394pub enum ActionConstraint {
3395    /// Un-constrained
3396    Any,
3397    /// Must be In the given [`EntityUid`]
3398    In(Vec<EntityUid>),
3399    /// Must be equal to the given [`EntityUid]`
3400    Eq(EntityUid),
3401}
3402
3403/// Scope constraint on policy resources.
3404#[derive(Debug, Clone, PartialEq, Eq)]
3405pub enum ResourceConstraint {
3406    /// Un-constrained
3407    Any,
3408    /// Must be In the given [`EntityUid`]
3409    In(EntityUid),
3410    /// Must be equal to the given [`EntityUid`]
3411    Eq(EntityUid),
3412    /// Must be the given [`EntityTypeName`]
3413    Is(EntityTypeName),
3414    /// Must be the given [`EntityTypeName`], and `in` the [`EntityUid`]
3415    IsIn(EntityTypeName, EntityUid),
3416}
3417
3418/// Scope constraint on policy resources for templates.
3419#[derive(Debug, Clone, PartialEq, Eq)]
3420pub enum TemplateResourceConstraint {
3421    /// Un-constrained
3422    Any,
3423    /// Must be In the given [`EntityUid`].
3424    /// If [`None`], then it is a template slot.
3425    In(Option<EntityUid>),
3426    /// Must be equal to the given [`EntityUid`].
3427    /// If [`None`], then it is a template slot.
3428    Eq(Option<EntityUid>),
3429    /// Must be the given [`EntityTypeName`].
3430    Is(EntityTypeName),
3431    /// Must be the given [`EntityTypeName`], and `in` the [`EntityUid`].
3432    /// If the [`EntityUid`] is [`Option::None`], then it is a template slot.
3433    IsIn(EntityTypeName, Option<EntityUid>),
3434}
3435
3436impl TemplateResourceConstraint {
3437    /// Does this constraint contain a slot?
3438    pub fn has_slot(&self) -> bool {
3439        match self {
3440            Self::Any | Self::Is(_) => false,
3441            Self::In(o) | Self::Eq(o) | Self::IsIn(_, o) => o.is_none(),
3442        }
3443    }
3444}
3445
3446/// Structure for a `Policy`. Includes both static policies and template-linked policies.
3447#[derive(Debug, Clone)]
3448pub struct Policy {
3449    /// AST representation of the policy, used for most operations.
3450    /// In particular, the `ast` contains the authoritative `PolicyId` for the policy.
3451    pub(crate) ast: ast::Policy,
3452    /// Some "lossless" representation of the policy, whichever is most
3453    /// convenient to provide (and can be provided with the least overhead).
3454    /// This is used just for `to_json()`.
3455    /// We can't just derive this on-demand from `ast`, because the AST is lossy:
3456    /// we can't reconstruct an accurate CST/EST/policy-text from the AST, but
3457    /// we can from the EST (modulo whitespace and a few other things like the
3458    /// order of annotations).
3459    pub(crate) lossless: LosslessPolicy,
3460}
3461
3462impl PartialEq for Policy {
3463    fn eq(&self, other: &Self) -> bool {
3464        // eq is based on just the `ast`
3465        self.ast.eq(&other.ast)
3466    }
3467}
3468impl Eq for Policy {}
3469
3470#[doc(hidden)] // because this converts to a private/internal type
3471impl AsRef<ast::Policy> for Policy {
3472    fn as_ref(&self) -> &ast::Policy {
3473        &self.ast
3474    }
3475}
3476
3477#[doc(hidden)]
3478impl From<ast::Policy> for Policy {
3479    fn from(policy: ast::Policy) -> Self {
3480        Self::from_ast(policy)
3481    }
3482}
3483
3484#[doc(hidden)]
3485impl From<ast::StaticPolicy> for Policy {
3486    fn from(policy: ast::StaticPolicy) -> Self {
3487        ast::Policy::from(policy).into()
3488    }
3489}
3490
3491impl Policy {
3492    /// Get the `PolicyId` of the `Template` this is linked to.
3493    /// If this is a static policy, this will return `None`.
3494    pub fn template_id(&self) -> Option<&PolicyId> {
3495        if self.is_static() {
3496            None
3497        } else {
3498            Some(PolicyId::ref_cast(self.ast.template().id()))
3499        }
3500    }
3501
3502    /// Get the values this `Template` is linked to, expressed as a map from `SlotId` to `EntityUid`.
3503    /// If this is a static policy, this will return `None`.
3504    pub fn template_links(&self) -> Option<HashMap<SlotId, EntityUid>> {
3505        if self.is_static() {
3506            None
3507        } else {
3508            let wrapped_vals: HashMap<SlotId, EntityUid> = self
3509                .ast
3510                .env()
3511                .iter()
3512                .map(|(key, value)| ((*key).into(), value.clone().into()))
3513                .collect();
3514            Some(wrapped_vals)
3515        }
3516    }
3517
3518    /// Get the `Effect` (`Permit` or `Forbid`) for this instance
3519    pub fn effect(&self) -> Effect {
3520        self.ast.effect()
3521    }
3522
3523    /// Returns `true` if this policy has a `when` or `unless` clause.
3524    pub fn has_non_scope_constraint(&self) -> bool {
3525        self.ast.non_scope_constraints().is_some()
3526    }
3527
3528    /// Get an annotation value of this template-linked or static policy.
3529    /// If the annotation is present without an explicit value (e.g., `@annotation`),
3530    /// then this function returns `Some("")`. Returns `None` when the
3531    /// annotation is not present or when `key` is not a valid annotations identifier.
3532    pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
3533        self.ast
3534            .annotation(&key.as_ref().parse().ok()?)
3535            .map(AsRef::as_ref)
3536    }
3537
3538    /// Iterate through annotation data of this template-linked or static policy.
3539    /// Annotations which do not have an explicit value (e.g., `@annotation`),
3540    /// are included in the iterator with the value `""`.
3541    pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
3542        self.ast
3543            .annotations()
3544            .map(|(k, v)| (k.as_ref(), v.as_ref()))
3545    }
3546
3547    /// Get the `PolicyId` for this template-linked or static policy
3548    pub fn id(&self) -> &PolicyId {
3549        PolicyId::ref_cast(self.ast.id())
3550    }
3551
3552    /// Clone this `Policy` with a new `PolicyId`
3553    #[must_use]
3554    pub fn new_id(&self, id: PolicyId) -> Self {
3555        Self {
3556            ast: self.ast.new_id(id.into()),
3557            lossless: self.lossless.clone(), // Lossless representation doesn't include the `PolicyId`
3558        }
3559    }
3560
3561    /// Returns `true` if this is a static policy, `false` otherwise.
3562    pub fn is_static(&self) -> bool {
3563        self.ast.is_static()
3564    }
3565
3566    /// Get the scope constraint on this policy's principal
3567    pub fn principal_constraint(&self) -> PrincipalConstraint {
3568        let slot_id = ast::SlotId::principal();
3569        match self.ast.template().principal_constraint().as_inner() {
3570            ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::Any,
3571            ast::PrincipalOrResourceConstraint::In(eref) => {
3572                PrincipalConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
3573            }
3574            ast::PrincipalOrResourceConstraint::Eq(eref) => {
3575                PrincipalConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
3576            }
3577            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3578                PrincipalConstraint::Is(entity_type.as_ref().clone().into())
3579            }
3580            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3581                PrincipalConstraint::IsIn(
3582                    entity_type.as_ref().clone().into(),
3583                    self.convert_entity_reference(eref, slot_id).clone(),
3584                )
3585            }
3586        }
3587    }
3588
3589    /// Get the scope constraint on this policy's action
3590    pub fn action_constraint(&self) -> ActionConstraint {
3591        // Clone the data from Core to be consistant with the other constraints
3592        match self.ast.template().action_constraint() {
3593            ast::ActionConstraint::Any => ActionConstraint::Any,
3594            ast::ActionConstraint::In(ids) => ActionConstraint::In(
3595                ids.iter()
3596                    .map(|euid| EntityUid::ref_cast(euid.as_ref()))
3597                    .cloned()
3598                    .collect(),
3599            ),
3600            ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid::ref_cast(id).clone()),
3601            #[cfg(feature = "tolerant-ast")]
3602            ast::ActionConstraint::ErrorConstraint => {
3603                // We will only have an ErrorConstraint if we are using a parser that allows Error nodes
3604                // It is not recommended to evaluate an AST that allows error nodes
3605                // If somehow someone tries to evaluate an AST that includes an Action constraint error, we will
3606                // treat it as `Any`
3607                ActionConstraint::Any
3608            }
3609        }
3610    }
3611
3612    /// Get the scope constraint on this policy's resource
3613    pub fn resource_constraint(&self) -> ResourceConstraint {
3614        let slot_id = ast::SlotId::resource();
3615        match self.ast.template().resource_constraint().as_inner() {
3616            ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::Any,
3617            ast::PrincipalOrResourceConstraint::In(eref) => {
3618                ResourceConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
3619            }
3620            ast::PrincipalOrResourceConstraint::Eq(eref) => {
3621                ResourceConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
3622            }
3623            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3624                ResourceConstraint::Is(entity_type.as_ref().clone().into())
3625            }
3626            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3627                ResourceConstraint::IsIn(
3628                    entity_type.as_ref().clone().into(),
3629                    self.convert_entity_reference(eref, slot_id).clone(),
3630                )
3631            }
3632        }
3633    }
3634
3635    /// To avoid panicking, this function may only be called when `slot` is the
3636    /// `SlotId` corresponding to the scope constraint from which the entity
3637    /// reference `r` was extracted. I.e., If `r` is taken from the principal
3638    /// scope constraint, `slot` must be `?principal`. This ensures that the
3639    /// `SlotId` exists in the policy (and therefore the slot environment map)
3640    /// whenever the `EntityReference` `r` is the Slot variant.
3641    fn convert_entity_reference<'a>(
3642        &'a self,
3643        r: &'a ast::EntityReference,
3644        slot: ast::SlotId,
3645    ) -> &'a EntityUid {
3646        match r {
3647            ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid),
3648            // PANIC SAFETY: This `unwrap` here is safe due the invariant (values total map) on policies.
3649            #[allow(clippy::unwrap_used)]
3650            ast::EntityReference::Slot(_) => {
3651                EntityUid::ref_cast(self.ast.env().get(&slot).unwrap())
3652            }
3653        }
3654    }
3655
3656    /// Parse a single policy.
3657    /// If `id` is Some, the policy will be given that Policy Id.
3658    /// If `id` is None, then "policy0" will be used.
3659    /// The behavior around None may change in the future.
3660    ///
3661    /// This can fail if the policy fails to parse.
3662    /// It can also fail if a template was passed in, as this function only accepts static
3663    /// policies
3664    pub fn parse(id: Option<PolicyId>, policy_src: impl AsRef<str>) -> Result<Self, ParseErrors> {
3665        let inline_ast = parser::parse_policy(id.map(Into::into), policy_src.as_ref())?;
3666        let (_, ast) = ast::Template::link_static_policy(inline_ast);
3667        Ok(Self {
3668            ast,
3669            lossless: LosslessPolicy::policy_or_template_text(Some(policy_src.as_ref())),
3670        })
3671    }
3672
3673    /// Create a `Policy` from its JSON representation.
3674    /// If `id` is Some, the policy will be given that Policy Id.
3675    /// If `id` is None, then "JSON policy" will be used.
3676    /// The behavior around None may change in the future.
3677    ///
3678    /// ```
3679    /// # use cedar_policy::{Policy, PolicyId};
3680    ///
3681    /// let json: serde_json::Value = serde_json::json!(
3682    ///        {
3683    ///            "effect":"permit",
3684    ///            "principal":{
3685    ///            "op":"==",
3686    ///            "entity":{
3687    ///                "type":"User",
3688    ///                "id":"bob"
3689    ///            }
3690    ///            },
3691    ///            "action":{
3692    ///            "op":"==",
3693    ///            "entity":{
3694    ///                "type":"Action",
3695    ///                "id":"view"
3696    ///            }
3697    ///            },
3698    ///            "resource":{
3699    ///            "op":"==",
3700    ///            "entity":{
3701    ///                "type":"Album",
3702    ///                "id":"trip"
3703    ///            }
3704    ///            },
3705    ///            "conditions":[
3706    ///            {
3707    ///                "kind":"when",
3708    ///                "body":{
3709    ///                   ">":{
3710    ///                        "left":{
3711    ///                        ".":{
3712    ///                            "left":{
3713    ///                                "Var":"principal"
3714    ///                            },
3715    ///                            "attr":"age"
3716    ///                        }
3717    ///                        },
3718    ///                        "right":{
3719    ///                        "Value":18
3720    ///                        }
3721    ///                    }
3722    ///                }
3723    ///            }
3724    ///            ]
3725    ///        }
3726    /// );
3727    /// let json_policy = Policy::from_json(None, json).unwrap();
3728    /// let src = r#"
3729    ///   permit(
3730    ///     principal == User::"bob",
3731    ///     action == Action::"view",
3732    ///     resource == Album::"trip"
3733    ///   )
3734    ///   when { principal.age > 18 };"#;
3735    /// let text_policy = Policy::parse(None, src).unwrap();
3736    /// assert_eq!(json_policy.to_json().unwrap(), text_policy.to_json().unwrap());
3737    /// ```
3738    pub fn from_json(
3739        id: Option<PolicyId>,
3740        json: serde_json::Value,
3741    ) -> Result<Self, PolicyFromJsonError> {
3742        let est: est::Policy = serde_json::from_value(json)
3743            .map_err(|e| entities_json_errors::JsonDeserializationError::Serde(e.into()))
3744            .map_err(cedar_policy_core::est::FromJsonError::from)?;
3745        Self::from_est(id, est)
3746    }
3747
3748    /// Get the valid [`RequestEnv`]s for this policy, according to the schema.
3749    ///
3750    /// That is, all the [`RequestEnv`]s in the schema for which this policy is
3751    /// not trivially false.
3752    pub fn get_valid_request_envs(&self, s: &Schema) -> impl Iterator<Item = RequestEnv> {
3753        get_valid_request_envs(self.ast.template(), s)
3754    }
3755
3756    /// Get all entity literals occuring in a `Policy`
3757    pub fn entity_literals(&self) -> Vec<EntityUid> {
3758        self.ast
3759            .condition()
3760            .subexpressions()
3761            .filter_map(|e| match e.expr_kind() {
3762                cedar_policy_core::ast::ExprKind::Lit(
3763                    cedar_policy_core::ast::Literal::EntityUID(euid),
3764                ) => Some(EntityUid((*euid).as_ref().clone())),
3765                _ => None,
3766            })
3767            .collect()
3768    }
3769
3770    /// Return a new policy where all occurrences of key `EntityUid`s are replaced by value `EntityUid`
3771    /// (as a single, non-sequential substitution).
3772    pub fn sub_entity_literals(
3773        &self,
3774        mapping: BTreeMap<EntityUid, EntityUid>,
3775    ) -> Result<Self, PolicyFromJsonError> {
3776        // PANIC SAFETY: This can't fail for a policy that was already constructed
3777        #[allow(clippy::expect_used)]
3778        let cloned_est = self
3779            .lossless
3780            .est(|| self.ast.clone().into())
3781            .expect("Internal error, failed to construct est.");
3782
3783        let mapping = mapping.into_iter().map(|(k, v)| (k.0, v.0)).collect();
3784
3785        // PANIC SAFETY: This can't fail for a policy that was already constructed
3786        #[allow(clippy::expect_used)]
3787        let est = cloned_est
3788            .sub_entity_literals(&mapping)
3789            .expect("Internal error, failed to sub entity literals.");
3790
3791        let ast = match est.clone().try_into_ast_policy(Some(self.ast.id().clone())) {
3792            Ok(ast) => ast,
3793            Err(e) => return Err(e.into()),
3794        };
3795
3796        Ok(Self {
3797            ast,
3798            lossless: LosslessPolicy::Est(est),
3799        })
3800    }
3801
3802    fn from_est(id: Option<PolicyId>, est: est::Policy) -> Result<Self, PolicyFromJsonError> {
3803        Ok(Self {
3804            ast: est.clone().try_into_ast_policy(id.map(PolicyId::into))?,
3805            lossless: LosslessPolicy::Est(est),
3806        })
3807    }
3808
3809    /// Get the JSON representation of this `Policy`.
3810    ///  ```
3811    /// # use cedar_policy::Policy;
3812    /// let src = r#"
3813    ///   permit(
3814    ///     principal == User::"bob",
3815    ///     action == Action::"view",
3816    ///     resource == Album::"trip"
3817    ///   )
3818    ///   when { principal.age > 18 };"#;
3819    ///
3820    /// let policy = Policy::parse(None, src).unwrap();
3821    /// println!("{}", policy);
3822    /// // convert the policy to JSON
3823    /// let json = policy.to_json().unwrap();
3824    /// println!("{}", json);
3825    /// assert_eq!(json, Policy::from_json(None, json.clone()).unwrap().to_json().unwrap());
3826    /// ```
3827    pub fn to_json(&self) -> Result<serde_json::Value, PolicyToJsonError> {
3828        let est = self.lossless.est(|| self.ast.clone().into())?;
3829        serde_json::to_value(est).map_err(Into::into)
3830    }
3831
3832    /// Get the human-readable Cedar syntax representation of this policy. This
3833    /// function is primarily intended for rendering JSON policies in the
3834    /// human-readable syntax, but it will also return the original policy text
3835    /// when given a policy parsed from the human-readable syntax.
3836    ///
3837    /// It will return `None` for linked policies because they cannot be
3838    /// directly rendered in Cedar syntax. You can instead render the unlinked
3839    /// template if you do not need to preserve links. If serializing links is
3840    /// important, then you will need to serialize the whole policy set
3841    /// containing the template and link to JSON (or protobuf).
3842    ///
3843    /// It also does not format the policy according to any particular rules.
3844    /// Policy formatting can be done through the Cedar policy CLI or
3845    /// the `cedar-policy-formatter` crate.
3846    pub fn to_cedar(&self) -> Option<String> {
3847        match &self.lossless {
3848            LosslessPolicy::Empty | LosslessPolicy::Est(_) => Some(self.ast.to_string()),
3849            LosslessPolicy::Text { text, slots } => {
3850                if slots.is_empty() {
3851                    Some(text.clone())
3852                } else {
3853                    None
3854                }
3855            }
3856        }
3857    }
3858
3859    /// Get all the unknown entities from the policy
3860    #[doc = include_str!("../experimental_warning.md")]
3861    #[cfg(feature = "partial-eval")]
3862    pub fn unknown_entities(&self) -> HashSet<EntityUid> {
3863        self.ast
3864            .unknown_entities()
3865            .into_iter()
3866            .map(Into::into)
3867            .collect()
3868    }
3869
3870    /// Create a `Policy` from its AST representation only. The `LosslessPolicy`
3871    /// will reflect the AST structure. When possible, don't use this method and
3872    /// create the `Policy` from the policy text, CST, or EST instead, as the
3873    /// conversion to AST is lossy. ESTs for policies generated by this method
3874    /// will reflect the AST and not the original policy syntax.
3875    #[cfg_attr(
3876        not(any(feature = "partial-eval", feature = "protobufs")),
3877        allow(unused)
3878    )]
3879    pub(crate) fn from_ast(ast: ast::Policy) -> Self {
3880        let text = ast.to_string(); // assume that pretty-printing is faster than `est::Policy::from(ast.clone())`; is that true?
3881        Self {
3882            ast,
3883            lossless: LosslessPolicy::policy_or_template_text(Some(text)),
3884        }
3885    }
3886}
3887
3888impl std::fmt::Display for Policy {
3889    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3890        // prefer to display the lossless format
3891        self.lossless.fmt(|| self.ast.clone().into(), f)
3892    }
3893}
3894
3895impl FromStr for Policy {
3896    type Err = ParseErrors;
3897    /// Create a policy
3898    ///
3899    /// Important note: Policies have ids, but this interface does not
3900    /// allow them to be set. It will use the default "policy0", which
3901    /// may cause id conflicts if not handled. Use `Policy::parse` to set
3902    /// the id when parsing, or `Policy::new_id` to clone a policy with
3903    /// a new id.
3904    fn from_str(policy: &str) -> Result<Self, Self::Err> {
3905        Self::parse(None, policy)
3906    }
3907}
3908
3909/// See comments on `Policy` and `Template`.
3910///
3911/// This structure can be used for static policies, linked policies, and templates.
3912#[derive(Debug, Clone)]
3913pub(crate) enum LosslessPolicy {
3914    /// An empty representation
3915    Empty,
3916    /// EST representation
3917    Est(est::Policy),
3918    /// Text representation
3919    Text {
3920        /// actual policy text, of the policy or template
3921        text: String,
3922        /// For linked policies, map of slot to UID. Only linked policies have
3923        /// this; static policies and (unlinked) templates have an empty map
3924        /// here
3925        slots: HashMap<ast::SlotId, ast::EntityUID>,
3926    },
3927}
3928
3929impl LosslessPolicy {
3930    /// Create a new `LosslessPolicy` from the text of a policy or template.
3931    fn policy_or_template_text(text: Option<impl Into<String>>) -> Self {
3932        text.map_or(Self::Empty, |text| Self::Text {
3933            text: text.into(),
3934            slots: HashMap::new(),
3935        })
3936    }
3937
3938    /// Get the EST representation of this static policy, linked policy, or template.
3939    fn est(
3940        &self,
3941        fallback_est: impl FnOnce() -> est::Policy,
3942    ) -> Result<est::Policy, PolicyToJsonError> {
3943        match self {
3944            // Fall back to the `policy` AST if the lossless representation is empty
3945            Self::Empty => Ok(fallback_est()),
3946            Self::Est(est) => Ok(est.clone()),
3947            Self::Text { text, slots } => {
3948                let est =
3949                    parser::parse_policy_or_template_to_est(text).map_err(ParseErrors::from)?;
3950                if slots.is_empty() {
3951                    Ok(est)
3952                } else {
3953                    let unwrapped_vals = slots.iter().map(|(k, v)| (*k, v.into())).collect();
3954                    Ok(est.link(&unwrapped_vals)?)
3955                }
3956            }
3957        }
3958    }
3959
3960    fn link<'a>(
3961        self,
3962        vals: impl IntoIterator<Item = (ast::SlotId, &'a ast::EntityUID)>,
3963    ) -> Result<Self, est::LinkingError> {
3964        match self {
3965            Self::Empty => Ok(Self::Empty),
3966            Self::Est(est) => {
3967                let unwrapped_est_vals: HashMap<
3968                    ast::SlotId,
3969                    cedar_policy_core::entities::EntityUidJson,
3970                > = vals.into_iter().map(|(k, v)| (k, v.into())).collect();
3971                Ok(Self::Est(est.link(&unwrapped_est_vals)?))
3972            }
3973            Self::Text { text, slots } => {
3974                debug_assert!(
3975                    slots.is_empty(),
3976                    "shouldn't call link() on an already-linked policy"
3977                );
3978                let slots = vals.into_iter().map(|(k, v)| (k, v.clone())).collect();
3979                Ok(Self::Text { text, slots })
3980            }
3981        }
3982    }
3983
3984    fn fmt(
3985        &self,
3986        fallback_est: impl FnOnce() -> est::Policy,
3987        f: &mut std::fmt::Formatter<'_>,
3988    ) -> std::fmt::Result {
3989        match self {
3990            Self::Empty => match self.est(fallback_est) {
3991                Ok(est) => write!(f, "{est}"),
3992                Err(e) => write!(f, "<invalid policy: {e}>"),
3993            },
3994            Self::Est(est) => write!(f, "{est}"),
3995            Self::Text { text, slots } => {
3996                if slots.is_empty() {
3997                    write!(f, "{text}")
3998                } else {
3999                    // need to replace placeholders according to `slots`.
4000                    // just find-and-replace wouldn't be safe/perfect, we
4001                    // want to use the actual parser; right now we reuse
4002                    // another implementation by just converting to EST and
4003                    // printing that
4004                    match self.est(fallback_est) {
4005                        Ok(est) => write!(f, "{est}"),
4006                        Err(e) => write!(f, "<invalid linked policy: {e}>"),
4007                    }
4008                }
4009            }
4010        }
4011    }
4012}
4013
4014/// Expressions to be evaluated
4015#[repr(transparent)]
4016#[derive(Debug, Clone, RefCast)]
4017pub struct Expression(pub(crate) ast::Expr);
4018
4019#[doc(hidden)] // because this converts to a private/internal type
4020impl AsRef<ast::Expr> for Expression {
4021    fn as_ref(&self) -> &ast::Expr {
4022        &self.0
4023    }
4024}
4025
4026#[doc(hidden)]
4027impl From<ast::Expr> for Expression {
4028    fn from(expr: ast::Expr) -> Self {
4029        Self(expr)
4030    }
4031}
4032
4033impl Expression {
4034    /// Create an expression representing a literal string.
4035    pub fn new_string(value: String) -> Self {
4036        Self(ast::Expr::val(value))
4037    }
4038
4039    /// Create an expression representing a literal bool.
4040    pub fn new_bool(value: bool) -> Self {
4041        Self(ast::Expr::val(value))
4042    }
4043
4044    /// Create an expression representing a literal long.
4045    pub fn new_long(value: ast::Integer) -> Self {
4046        Self(ast::Expr::val(value))
4047    }
4048
4049    /// Create an expression representing a record.
4050    ///
4051    /// Error if any key appears two or more times in `fields`.
4052    pub fn new_record(
4053        fields: impl IntoIterator<Item = (String, Self)>,
4054    ) -> Result<Self, ExpressionConstructionError> {
4055        Ok(Self(ast::Expr::record(
4056            fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
4057        )?))
4058    }
4059
4060    /// Create an expression representing a Set.
4061    pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
4062        Self(ast::Expr::set(values.into_iter().map(|v| v.0)))
4063    }
4064
4065    /// Create an expression representing an ip address.
4066    /// This function does not perform error checking on the source string,
4067    /// it creates an expression that calls the `ip` constructor.
4068    pub fn new_ip(src: impl AsRef<str>) -> Self {
4069        let src_expr = ast::Expr::val(src.as_ref());
4070        Self(ast::Expr::call_extension_fn(
4071            ip_extension_name(),
4072            vec![src_expr],
4073        ))
4074    }
4075
4076    /// Create an expression representing a fixed precision decimal number.
4077    /// This function does not perform error checking on the source string,
4078    /// it creates an expression that calls the `decimal` constructor.
4079    pub fn new_decimal(src: impl AsRef<str>) -> Self {
4080        let src_expr = ast::Expr::val(src.as_ref());
4081        Self(ast::Expr::call_extension_fn(
4082            decimal_extension_name(),
4083            vec![src_expr],
4084        ))
4085    }
4086
4087    /// Create an expression representing a particular instant of time.
4088    /// This function does not perform error checking on the source string,
4089    /// it creates an expression that calls the `datetime` constructor.
4090    pub fn new_datetime(src: impl AsRef<str>) -> Self {
4091        let src_expr = ast::Expr::val(src.as_ref());
4092        Self(ast::Expr::call_extension_fn(
4093            datetime_extension_name(),
4094            vec![src_expr],
4095        ))
4096    }
4097
4098    /// Create an expression representing a duration of time.
4099    /// This function does not perform error checking on the source string,
4100    /// it creates an expression that calls the `datetime` constructor.
4101    pub fn new_duration(src: impl AsRef<str>) -> Self {
4102        let src_expr = ast::Expr::val(src.as_ref());
4103        Self(ast::Expr::call_extension_fn(
4104            duration_extension_name(),
4105            vec![src_expr],
4106        ))
4107    }
4108}
4109
4110#[cfg(test)]
4111impl Expression {
4112    /// Deconstruct an [`Expression`] to get the internal type.
4113    /// This function is only intended to be used internally.
4114    pub(crate) fn into_inner(self) -> ast::Expr {
4115        self.0
4116    }
4117}
4118
4119impl FromStr for Expression {
4120    type Err = ParseErrors;
4121
4122    /// create an Expression using Cedar syntax
4123    fn from_str(expression: &str) -> Result<Self, Self::Err> {
4124        ast::Expr::from_str(expression)
4125            .map(Expression)
4126            .map_err(Into::into)
4127    }
4128}
4129
4130impl std::fmt::Display for Expression {
4131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4132        write!(f, "{}", &self.0)
4133    }
4134}
4135
4136/// "Restricted" expressions are used for attribute values and `context`.
4137///
4138/// Restricted expressions can contain only the following:
4139///   - bool, int, and string literals
4140///   - literal `EntityUid`s such as `User::"alice"`
4141///   - extension function calls, where the arguments must be other things
4142///     on this list
4143///   - set and record literals, where the values must be other things on
4144///     this list
4145///
4146/// That means the following are not allowed in restricted expressions:
4147///   - `principal`, `action`, `resource`, `context`
4148///   - builtin operators and functions, including `.`, `in`, `has`, `like`,
4149///     `.contains()`
4150///   - if-then-else expressions
4151#[repr(transparent)]
4152#[derive(Debug, Clone, RefCast, PartialEq, Eq)]
4153pub struct RestrictedExpression(pub(crate) ast::RestrictedExpr);
4154
4155#[doc(hidden)] // because this converts to a private/internal type
4156impl AsRef<ast::RestrictedExpr> for RestrictedExpression {
4157    fn as_ref(&self) -> &ast::RestrictedExpr {
4158        &self.0
4159    }
4160}
4161
4162impl RestrictedExpression {
4163    /// Create an expression representing a literal string.
4164    pub fn new_string(value: String) -> Self {
4165        Self(ast::RestrictedExpr::val(value))
4166    }
4167
4168    /// Create an expression representing a literal bool.
4169    pub fn new_bool(value: bool) -> Self {
4170        Self(ast::RestrictedExpr::val(value))
4171    }
4172
4173    /// Create an expression representing a literal long.
4174    pub fn new_long(value: ast::Integer) -> Self {
4175        Self(ast::RestrictedExpr::val(value))
4176    }
4177
4178    /// Create an expression representing a literal `EntityUid`.
4179    pub fn new_entity_uid(value: EntityUid) -> Self {
4180        Self(ast::RestrictedExpr::val(ast::EntityUID::from(value)))
4181    }
4182
4183    /// Create an expression representing a record.
4184    ///
4185    /// Error if any key appears two or more times in `fields`.
4186    pub fn new_record(
4187        fields: impl IntoIterator<Item = (String, Self)>,
4188    ) -> Result<Self, ExpressionConstructionError> {
4189        Ok(Self(ast::RestrictedExpr::record(
4190            fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
4191        )?))
4192    }
4193
4194    /// Create an expression representing a Set.
4195    pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
4196        Self(ast::RestrictedExpr::set(values.into_iter().map(|v| v.0)))
4197    }
4198
4199    /// Create an expression representing an ip address.
4200    /// This function does not perform error checking on the source string,
4201    /// it creates an expression that calls the `ip` constructor.
4202    pub fn new_ip(src: impl AsRef<str>) -> Self {
4203        let src_expr = ast::RestrictedExpr::val(src.as_ref());
4204        Self(ast::RestrictedExpr::call_extension_fn(
4205            ip_extension_name(),
4206            [src_expr],
4207        ))
4208    }
4209
4210    /// Create an expression representing a fixed precision decimal number.
4211    /// This function does not perform error checking on the source string,
4212    /// it creates an expression that calls the `decimal` constructor.
4213    pub fn new_decimal(src: impl AsRef<str>) -> Self {
4214        let src_expr = ast::RestrictedExpr::val(src.as_ref());
4215        Self(ast::RestrictedExpr::call_extension_fn(
4216            decimal_extension_name(),
4217            [src_expr],
4218        ))
4219    }
4220
4221    /// Create an expression representing a particular instant of time.
4222    /// This function does not perform error checking on the source string,
4223    /// it creates an expression that calls the `datetime` constructor.
4224    pub fn new_datetime(src: impl AsRef<str>) -> Self {
4225        let src_expr = ast::RestrictedExpr::val(src.as_ref());
4226        Self(ast::RestrictedExpr::call_extension_fn(
4227            datetime_extension_name(),
4228            [src_expr],
4229        ))
4230    }
4231
4232    /// Create an expression representing a duration of time.
4233    /// This function does not perform error checking on the source string,
4234    /// it creates an expression that calls the `datetime` constructor.
4235    pub fn new_duration(src: impl AsRef<str>) -> Self {
4236        let src_expr = ast::RestrictedExpr::val(src.as_ref());
4237        Self(ast::RestrictedExpr::call_extension_fn(
4238            duration_extension_name(),
4239            [src_expr],
4240        ))
4241    }
4242
4243    /// Create an unknown expression
4244    #[cfg(feature = "partial-eval")]
4245    pub fn new_unknown(name: impl AsRef<str>) -> Self {
4246        Self(ast::RestrictedExpr::unknown(ast::Unknown::new_untyped(
4247            name.as_ref(),
4248        )))
4249    }
4250}
4251
4252#[cfg(test)]
4253impl RestrictedExpression {
4254    /// Deconstruct an [`RestrictedExpression`] to get the internal type.
4255    /// This function is only intended to be used internally.
4256    pub(crate) fn into_inner(self) -> ast::RestrictedExpr {
4257        self.0
4258    }
4259}
4260
4261fn decimal_extension_name() -> ast::Name {
4262    // PANIC SAFETY: This is a constant and is known to be safe, verified by a test
4263    #[allow(clippy::unwrap_used)]
4264    ast::Name::unqualified_name("decimal".parse().unwrap())
4265}
4266
4267fn ip_extension_name() -> ast::Name {
4268    // PANIC SAFETY: This is a constant and is known to be safe, verified by a test
4269    #[allow(clippy::unwrap_used)]
4270    ast::Name::unqualified_name("ip".parse().unwrap())
4271}
4272
4273fn datetime_extension_name() -> ast::Name {
4274    // PANIC SAFETY: This is a constant and is known to be safe, verified by a test
4275    #[allow(clippy::unwrap_used)]
4276    ast::Name::unqualified_name("datetime".parse().unwrap())
4277}
4278
4279fn duration_extension_name() -> ast::Name {
4280    // PANIC SAFETY: This is a constant and is known to be safe, verified by a test
4281    #[allow(clippy::unwrap_used)]
4282    ast::Name::unqualified_name("duration".parse().unwrap())
4283}
4284
4285impl FromStr for RestrictedExpression {
4286    type Err = RestrictedExpressionParseError;
4287
4288    /// create a `RestrictedExpression` using Cedar syntax
4289    fn from_str(expression: &str) -> Result<Self, Self::Err> {
4290        ast::RestrictedExpr::from_str(expression)
4291            .map(RestrictedExpression)
4292            .map_err(Into::into)
4293    }
4294}
4295
4296/// Builder for a [`Request`]
4297///
4298/// The default for principal, action, resource, and context fields is Unknown
4299/// for partial evaluation.
4300#[doc = include_str!("../experimental_warning.md")]
4301#[cfg(feature = "partial-eval")]
4302#[derive(Debug, Clone)]
4303pub struct RequestBuilder<S> {
4304    principal: ast::EntityUIDEntry,
4305    action: ast::EntityUIDEntry,
4306    resource: ast::EntityUIDEntry,
4307    /// Here, `None` means unknown
4308    context: Option<ast::Context>,
4309    schema: S,
4310}
4311
4312/// A marker type that indicates [`Schema`] is not set for a request
4313#[doc = include_str!("../experimental_warning.md")]
4314#[cfg(feature = "partial-eval")]
4315#[derive(Debug, Clone, Copy)]
4316pub struct UnsetSchema;
4317
4318#[cfg(feature = "partial-eval")]
4319impl Default for RequestBuilder<UnsetSchema> {
4320    fn default() -> Self {
4321        Self {
4322            principal: ast::EntityUIDEntry::unknown(),
4323            action: ast::EntityUIDEntry::unknown(),
4324            resource: ast::EntityUIDEntry::unknown(),
4325            context: None,
4326            schema: UnsetSchema,
4327        }
4328    }
4329}
4330
4331#[cfg(feature = "partial-eval")]
4332impl<S> RequestBuilder<S> {
4333    /// Set the principal.
4334    ///
4335    /// Note that you can create the `EntityUid` using `.parse()` on any
4336    /// string (via the `FromStr` implementation for `EntityUid`).
4337    #[must_use]
4338    pub fn principal(self, principal: EntityUid) -> Self {
4339        Self {
4340            principal: ast::EntityUIDEntry::known(principal.into(), None),
4341            ..self
4342        }
4343    }
4344
4345    /// Set the principal to be unknown, but known to belong to a certain entity type.
4346    ///
4347    /// This information is taken into account when evaluating 'is', '==' and '!=' expressions.
4348    #[must_use]
4349    pub fn unknown_principal_with_type(self, principal_type: EntityTypeName) -> Self {
4350        Self {
4351            principal: ast::EntityUIDEntry::unknown_with_type(principal_type.0, None),
4352            ..self
4353        }
4354    }
4355
4356    /// Set the action.
4357    ///
4358    /// Note that you can create the `EntityUid` using `.parse()` on any
4359    /// string (via the `FromStr` implementation for `EntityUid`).
4360    #[must_use]
4361    pub fn action(self, action: EntityUid) -> Self {
4362        Self {
4363            action: ast::EntityUIDEntry::known(action.into(), None),
4364            ..self
4365        }
4366    }
4367
4368    /// Set the resource.
4369    ///
4370    /// Note that you can create the `EntityUid` using `.parse()` on any
4371    /// string (via the `FromStr` implementation for `EntityUid`).
4372    #[must_use]
4373    pub fn resource(self, resource: EntityUid) -> Self {
4374        Self {
4375            resource: ast::EntityUIDEntry::known(resource.into(), None),
4376            ..self
4377        }
4378    }
4379
4380    /// Set the resource to be unknown, but known to belong to a certain entity type.
4381    ///
4382    /// This information is taken into account when evaluating 'is', '==' and '!=' expressions.
4383    #[must_use]
4384    pub fn unknown_resource_with_type(self, resource_type: EntityTypeName) -> Self {
4385        Self {
4386            resource: ast::EntityUIDEntry::unknown_with_type(resource_type.0, None),
4387            ..self
4388        }
4389    }
4390
4391    /// Set the context.
4392    #[must_use]
4393    pub fn context(self, context: Context) -> Self {
4394        Self {
4395            context: Some(context.0),
4396            ..self
4397        }
4398    }
4399}
4400
4401#[cfg(feature = "partial-eval")]
4402impl RequestBuilder<UnsetSchema> {
4403    /// Set the schema. If present, this will be used for request validation.
4404    #[must_use]
4405    pub fn schema(self, schema: &Schema) -> RequestBuilder<&Schema> {
4406        RequestBuilder {
4407            principal: self.principal,
4408            action: self.action,
4409            resource: self.resource,
4410            context: self.context,
4411            schema,
4412        }
4413    }
4414
4415    /// Create the [`Request`]
4416    pub fn build(self) -> Request {
4417        Request(ast::Request::new_unchecked(
4418            self.principal,
4419            self.action,
4420            self.resource,
4421            self.context,
4422        ))
4423    }
4424}
4425
4426#[cfg(feature = "partial-eval")]
4427impl RequestBuilder<&Schema> {
4428    /// Create the [`Request`]
4429    pub fn build(self) -> Result<Request, RequestValidationError> {
4430        Ok(Request(ast::Request::new_with_unknowns(
4431            self.principal,
4432            self.action,
4433            self.resource,
4434            self.context,
4435            Some(&self.schema.0),
4436            Extensions::all_available(),
4437        )?))
4438    }
4439}
4440
4441/// An authorization request is a tuple `<P, A, R, C>` where
4442/// * P is the principal [`EntityUid`],
4443/// * A is the action [`EntityUid`],
4444/// * R is the resource [`EntityUid`], and
4445/// * C is the request [`Context`] record.
4446///
4447/// It represents an authorization request asking the question, "Can this
4448/// principal take this action on this resource in this context?"
4449#[repr(transparent)]
4450#[derive(Debug, Clone, RefCast)]
4451pub struct Request(pub(crate) ast::Request);
4452
4453#[doc(hidden)] // because this converts to a private/internal type
4454impl AsRef<ast::Request> for Request {
4455    fn as_ref(&self) -> &ast::Request {
4456        &self.0
4457    }
4458}
4459
4460#[doc(hidden)]
4461impl From<ast::Request> for Request {
4462    fn from(req: ast::Request) -> Self {
4463        Self(req)
4464    }
4465}
4466
4467impl Request {
4468    /// Create a [`RequestBuilder`]
4469    #[doc = include_str!("../experimental_warning.md")]
4470    #[cfg(feature = "partial-eval")]
4471    pub fn builder() -> RequestBuilder<UnsetSchema> {
4472        RequestBuilder::default()
4473    }
4474
4475    /// Create a Request.
4476    ///
4477    /// Note that you can create the `EntityUid`s using `.parse()` on any
4478    /// string (via the `FromStr` implementation for `EntityUid`).
4479    /// The principal, action, and resource fields are optional to support
4480    /// the case where these fields do not contribute to authorization
4481    /// decisions (e.g., because they are not used in your policies).
4482    /// If any of the fields are `None`, we will automatically generate
4483    /// a unique entity UID that is not equal to any UID in the store.
4484    ///
4485    /// If `schema` is present, this constructor will validate that the
4486    /// `Request` complies with the given `schema`.
4487    pub fn new(
4488        principal: EntityUid,
4489        action: EntityUid,
4490        resource: EntityUid,
4491        context: Context,
4492        schema: Option<&Schema>,
4493    ) -> Result<Self, RequestValidationError> {
4494        Ok(Self(ast::Request::new(
4495            (principal.into(), None),
4496            (action.into(), None),
4497            (resource.into(), None),
4498            context.0,
4499            schema.map(|schema| &schema.0),
4500            Extensions::all_available(),
4501        )?))
4502    }
4503
4504    /// Get the context component of the request. Returns `None` if the context is
4505    /// "unknown" (i.e., constructed using the partial evaluation APIs).
4506    pub fn context(&self) -> Option<&Context> {
4507        self.0.context().map(Context::ref_cast)
4508    }
4509
4510    /// Get the principal component of the request. Returns `None` if the principal is
4511    /// "unknown" (i.e., constructed using the partial evaluation APIs).
4512    pub fn principal(&self) -> Option<&EntityUid> {
4513        match self.0.principal() {
4514            ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
4515            ast::EntityUIDEntry::Unknown { .. } => None,
4516        }
4517    }
4518
4519    /// Get the action component of the request. Returns `None` if the action is
4520    /// "unknown" (i.e., constructed using the partial evaluation APIs).
4521    pub fn action(&self) -> Option<&EntityUid> {
4522        match self.0.action() {
4523            ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
4524            ast::EntityUIDEntry::Unknown { .. } => None,
4525        }
4526    }
4527
4528    /// Get the resource component of the request. Returns `None` if the resource is
4529    /// "unknown" (i.e., constructed using the partial evaluation APIs).
4530    pub fn resource(&self) -> Option<&EntityUid> {
4531        match self.0.resource() {
4532            ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
4533            ast::EntityUIDEntry::Unknown { .. } => None,
4534        }
4535    }
4536}
4537
4538/// the Context object for an authorization request
4539#[repr(transparent)]
4540#[derive(Debug, Clone, RefCast)]
4541pub struct Context(ast::Context);
4542
4543#[doc(hidden)] // because this converts to a private/internal type
4544impl AsRef<ast::Context> for Context {
4545    fn as_ref(&self) -> &ast::Context {
4546        &self.0
4547    }
4548}
4549
4550impl Context {
4551    /// Create an empty `Context`
4552    /// ```
4553    /// # use cedar_policy::Context;
4554    /// let context = Context::empty();
4555    /// ```
4556    pub fn empty() -> Self {
4557        Self(ast::Context::empty())
4558    }
4559
4560    /// Create a `Context` from a map of key to "restricted expression",
4561    /// or a Vec of `(key, restricted expression)` pairs, or any other iterator
4562    /// of `(key, restricted expression)` pairs.
4563    /// ```
4564    /// # use cedar_policy::{Context, EntityUid, RestrictedExpression, Request};
4565    /// # use std::str::FromStr;
4566    /// let context = Context::from_pairs([
4567    ///   ("key".to_string(), RestrictedExpression::from_str(r#""value""#).unwrap()),
4568    ///   ("age".to_string(), RestrictedExpression::from_str("18").unwrap()),
4569    /// ]).unwrap();
4570    /// # // create a request
4571    /// # let p = EntityUid::from_str(r#"User::"alice""#).unwrap();
4572    /// # let a = EntityUid::from_str(r#"Action::"view""#).unwrap();
4573    /// # let r = EntityUid::from_str(r#"Album::"trip""#).unwrap();
4574    /// # let request: Request = Request::new(p, a, r, context, None).unwrap();
4575    /// ```
4576    pub fn from_pairs(
4577        pairs: impl IntoIterator<Item = (String, RestrictedExpression)>,
4578    ) -> Result<Self, ContextCreationError> {
4579        Ok(Self(ast::Context::from_pairs(
4580            pairs.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
4581            Extensions::all_available(),
4582        )?))
4583    }
4584
4585    /// Retrieves a value from the Context by its key.
4586    ///
4587    /// # Arguments
4588    ///
4589    /// * `key` - The key to look up in the context
4590    ///
4591    /// # Returns
4592    ///
4593    /// * `Some(EvalResult)` - If the key exists in the context, returns its value
4594    /// * `None` - If the key doesn't exist or if the context is not a Value type
4595    ///
4596    /// # Examples
4597    ///
4598    /// ```
4599    /// # use cedar_policy::{Context, Request, EntityUid};
4600    /// # use std::str::FromStr;
4601    /// let context = Context::from_json_str(r#"{"rayId": "abc123"}"#, None).unwrap();
4602    /// if let Some(value) = context.get("rayId") {
4603    ///     // value here is an EvalResult, convertible from the internal Value type
4604    ///     println!("Found value: {:?}", value);
4605    /// }
4606    /// assert_eq!(context.get("nonexistent"), None);
4607    /// ```
4608    pub fn get(&self, key: &str) -> Option<EvalResult> {
4609        match &self.0 {
4610            ast::Context::Value(map) => map.get(key).map(|v| EvalResult::from(v.clone())),
4611            ast::Context::RestrictedResidual(_) => None,
4612        }
4613    }
4614
4615    /// Create a `Context` from a string containing JSON (which must be a JSON
4616    /// object, not any other JSON type, or you will get an error here).
4617    /// JSON here must use the `__entity` and `__extn` escapes for entity
4618    /// references, extension values, etc.
4619    ///
4620    /// If a `schema` is provided, this will inform the parsing: for instance, it
4621    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
4622    /// if attributes have the wrong types (e.g., string instead of integer).
4623    /// Since different Actions have different schemas for `Context`, you also
4624    /// must specify the `Action` for schema-based parsing.
4625    /// ```
4626    /// # use cedar_policy::{Context, EntityUid, RestrictedExpression, Request};
4627    /// # use std::str::FromStr;
4628    /// let json_data = r#"{
4629    ///     "sub": "1234",
4630    ///     "groups": {
4631    ///         "1234": {
4632    ///             "group_id": "abcd",
4633    ///             "group_name": "test-group"
4634    ///         }
4635    ///     }
4636    /// }"#;
4637    /// let context = Context::from_json_str(json_data, None).unwrap();
4638    /// # // create a request
4639    /// # let p = EntityUid::from_str(r#"User::"alice""#).unwrap();
4640    /// # let a = EntityUid::from_str(r#"Action::"view""#).unwrap();
4641    /// # let r = EntityUid::from_str(r#"Album::"trip""#).unwrap();
4642    /// # let request: Request = Request::new(p, a, r, context, None).unwrap();
4643    /// ```
4644    pub fn from_json_str(
4645        json: &str,
4646        schema: Option<(&Schema, &EntityUid)>,
4647    ) -> Result<Self, ContextJsonError> {
4648        let schema = schema
4649            .map(|(s, uid)| Self::get_context_schema(s, uid))
4650            .transpose()?;
4651        let context = cedar_policy_core::entities::ContextJsonParser::new(
4652            schema.as_ref(),
4653            Extensions::all_available(),
4654        )
4655        .from_json_str(json)?;
4656        Ok(Self(context))
4657    }
4658
4659    /// Create a `Context` from a `serde_json::Value` (which must be a JSON object,
4660    /// not any other JSON type, or you will get an error here).
4661    /// JSON here must use the `__entity` and `__extn` escapes for entity
4662    /// references, extension values, etc.
4663    ///
4664    /// If a `schema` is provided, this will inform the parsing: for instance, it
4665    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
4666    /// if attributes have the wrong types (e.g., string instead of integer).
4667    /// Since different Actions have different schemas for `Context`, you also
4668    /// must specify the `Action` for schema-based parsing.
4669    /// ```
4670    /// # use cedar_policy::{Context, EntityUid, EntityId, EntityTypeName, RestrictedExpression, Request, Schema};
4671    /// # use std::str::FromStr;
4672    /// let schema_json = serde_json::json!(
4673    ///     {
4674    ///       "": {
4675    ///         "entityTypes": {
4676    ///           "User": {},
4677    ///           "Album": {},
4678    ///         },
4679    ///         "actions": {
4680    ///           "view": {
4681    ///              "appliesTo": {
4682    ///                "principalTypes": ["User"],
4683    ///                "resourceTypes": ["Album"],
4684    ///                "context": {
4685    ///                  "type": "Record",
4686    ///                  "attributes": {
4687    ///                    "sub": { "type": "Long" }
4688    ///                  }
4689    ///                }
4690    ///              }
4691    ///           }
4692    ///         }
4693    ///       }
4694    ///     });
4695    /// let schema = Schema::from_json_value(schema_json).unwrap();
4696    ///
4697    /// let a_eid = EntityId::from_str("view").unwrap();
4698    /// let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
4699    /// let action = EntityUid::from_type_name_and_id(a_name, a_eid);
4700    /// let data = serde_json::json!({
4701    ///     "sub": 1234
4702    /// });
4703    /// let context = Context::from_json_value(data, Some((&schema, &action))).unwrap();
4704    /// # let p = EntityUid::from_str(r#"User::"alice""#).unwrap();
4705    /// # let r = EntityUid::from_str(r#"Album::"trip""#).unwrap();
4706    /// # let request: Request = Request::new(p, action, r, context, Some(&schema)).unwrap();
4707    /// ```
4708    pub fn from_json_value(
4709        json: serde_json::Value,
4710        schema: Option<(&Schema, &EntityUid)>,
4711    ) -> Result<Self, ContextJsonError> {
4712        let schema = schema
4713            .map(|(s, uid)| Self::get_context_schema(s, uid))
4714            .transpose()?;
4715        let context = cedar_policy_core::entities::ContextJsonParser::new(
4716            schema.as_ref(),
4717            Extensions::all_available(),
4718        )
4719        .from_json_value(json)?;
4720        Ok(Self(context))
4721    }
4722
4723    /// Create a `Context` from a JSON file.  The JSON file must contain a JSON
4724    /// object, not any other JSON type, or you will get an error here.
4725    /// JSON here must use the `__entity` and `__extn` escapes for entity
4726    /// references, extension values, etc.
4727    ///
4728    /// If a `schema` is provided, this will inform the parsing: for instance, it
4729    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
4730    /// if attributes have the wrong types (e.g., string instead of integer).
4731    /// Since different Actions have different schemas for `Context`, you also
4732    /// must specify the `Action` for schema-based parsing.
4733    /// ```no_run
4734    /// # use cedar_policy::{Context, RestrictedExpression};
4735    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, Request,PolicySet};
4736    /// # use std::collections::HashMap;
4737    /// # use std::str::FromStr;
4738    /// # use std::fs::File;
4739    /// let mut json = File::open("json_file.json").unwrap();
4740    /// let context = Context::from_json_file(&json, None).unwrap();
4741    /// # // create a request
4742    /// # let p_eid = EntityId::from_str("alice").unwrap();
4743    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
4744    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
4745    /// #
4746    /// # let a_eid = EntityId::from_str("view").unwrap();
4747    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
4748    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
4749    /// # let r_eid = EntityId::from_str("trip").unwrap();
4750    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
4751    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
4752    /// # let request: Request = Request::new(p, a, r, context, None).unwrap();
4753    /// ```
4754    pub fn from_json_file(
4755        json: impl std::io::Read,
4756        schema: Option<(&Schema, &EntityUid)>,
4757    ) -> Result<Self, ContextJsonError> {
4758        let schema = schema
4759            .map(|(s, uid)| Self::get_context_schema(s, uid))
4760            .transpose()?;
4761        let context = cedar_policy_core::entities::ContextJsonParser::new(
4762            schema.as_ref(),
4763            Extensions::all_available(),
4764        )
4765        .from_json_file(json)?;
4766        Ok(Self(context))
4767    }
4768
4769    /// Internal helper function to convert `(&Schema, &EntityUid)` to `impl ContextSchema`
4770    fn get_context_schema(
4771        schema: &Schema,
4772        action: &EntityUid,
4773    ) -> Result<impl ContextSchema, ContextJsonError> {
4774        cedar_policy_core::validator::context_schema_for_action(&schema.0, action.as_ref())
4775            .ok_or_else(|| ContextJsonError::missing_action(action.clone()))
4776    }
4777
4778    /// Merge this [`Context`] with another context (or iterator over
4779    /// `(String, RestrictedExpression)` pairs), returning an error if the two
4780    /// contain overlapping keys
4781    pub fn merge(
4782        self,
4783        other_context: impl IntoIterator<Item = (String, RestrictedExpression)>,
4784    ) -> Result<Self, ContextCreationError> {
4785        Self::from_pairs(self.into_iter().chain(other_context))
4786    }
4787
4788    /// Validates this context against the provided schema
4789    ///
4790    /// Returns Ok(()) if the context is valid according to the schema, or an error otherwise
4791    ///
4792    /// This validation is already handled by `Request::new`, so there is no need to separately call
4793    /// if you are validating the whole request
4794    pub fn validate(
4795        &self,
4796        schema: &crate::Schema,
4797        action: &EntityUid,
4798    ) -> std::result::Result<(), RequestValidationError> {
4799        // Call the validate_context function from coreschema.rs
4800        Ok(RequestSchema::validate_context(
4801            &schema.0,
4802            &self.0,
4803            action.as_ref(),
4804            Extensions::all_available(),
4805        )?)
4806    }
4807}
4808
4809/// Utilities for implementing `IntoIterator` for `Context`
4810mod context {
4811    use super::{ast, RestrictedExpression};
4812
4813    /// `IntoIter` iterator for `Context`
4814    #[derive(Debug)]
4815    pub struct IntoIter {
4816        pub(super) inner: <ast::Context as IntoIterator>::IntoIter,
4817    }
4818
4819    impl Iterator for IntoIter {
4820        type Item = (String, RestrictedExpression);
4821
4822        fn next(&mut self) -> Option<Self::Item> {
4823            self.inner
4824                .next()
4825                .map(|(k, v)| (k.to_string(), RestrictedExpression(v)))
4826        }
4827    }
4828}
4829
4830impl IntoIterator for Context {
4831    type Item = (String, RestrictedExpression);
4832
4833    type IntoIter = context::IntoIter;
4834
4835    fn into_iter(self) -> Self::IntoIter {
4836        Self::IntoIter {
4837            inner: self.0.into_iter(),
4838        }
4839    }
4840}
4841
4842#[doc(hidden)]
4843impl From<ast::Context> for Context {
4844    fn from(c: ast::Context) -> Self {
4845        Self(c)
4846    }
4847}
4848
4849impl std::fmt::Display for Request {
4850    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4851        write!(f, "{}", self.0)
4852    }
4853}
4854
4855impl std::fmt::Display for Context {
4856    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4857        write!(f, "{}", self.0)
4858    }
4859}
4860
4861/// Result of Evaluation
4862#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
4863pub enum EvalResult {
4864    /// Boolean value
4865    Bool(bool),
4866    /// Signed integer value
4867    Long(ast::Integer),
4868    /// String value
4869    String(String),
4870    /// Entity Uid
4871    EntityUid(EntityUid),
4872    /// A first-class set
4873    Set(Set),
4874    /// A first-class anonymous record
4875    Record(Record),
4876    /// An extension value, currently limited to String results
4877    ExtensionValue(String),
4878    // ExtensionValue(std::sync::Arc<dyn InternalExtensionValue>),
4879}
4880
4881/// Sets of Cedar values
4882#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
4883pub struct Set(BTreeSet<EvalResult>);
4884
4885impl Set {
4886    /// Iterate over the members of the set
4887    pub fn iter(&self) -> impl Iterator<Item = &EvalResult> {
4888        self.0.iter()
4889    }
4890
4891    /// Is a given element in the set
4892    pub fn contains(&self, elem: &EvalResult) -> bool {
4893        self.0.contains(elem)
4894    }
4895
4896    /// Get the number of members of the set
4897    pub fn len(&self) -> usize {
4898        self.0.len()
4899    }
4900
4901    /// Test if the set is empty
4902    pub fn is_empty(&self) -> bool {
4903        self.0.is_empty()
4904    }
4905}
4906
4907/// A record of Cedar values
4908#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
4909pub struct Record(BTreeMap<String, EvalResult>);
4910
4911impl Record {
4912    /// Iterate over the attribute/value pairs in the record
4913    pub fn iter(&self) -> impl Iterator<Item = (&String, &EvalResult)> {
4914        self.0.iter()
4915    }
4916
4917    /// Check if a given attribute is in the record
4918    pub fn contains_attribute(&self, key: impl AsRef<str>) -> bool {
4919        self.0.contains_key(key.as_ref())
4920    }
4921
4922    /// Get a given attribute from the record
4923    pub fn get(&self, key: impl AsRef<str>) -> Option<&EvalResult> {
4924        self.0.get(key.as_ref())
4925    }
4926
4927    /// Get the number of attributes in the record
4928    pub fn len(&self) -> usize {
4929        self.0.len()
4930    }
4931
4932    /// Test if the record is empty
4933    pub fn is_empty(&self) -> bool {
4934        self.0.is_empty()
4935    }
4936}
4937
4938#[doc(hidden)]
4939impl From<ast::Value> for EvalResult {
4940    fn from(v: ast::Value) -> Self {
4941        match v.value {
4942            ast::ValueKind::Lit(ast::Literal::Bool(b)) => Self::Bool(b),
4943            ast::ValueKind::Lit(ast::Literal::Long(i)) => Self::Long(i),
4944            ast::ValueKind::Lit(ast::Literal::String(s)) => Self::String(s.to_string()),
4945            ast::ValueKind::Lit(ast::Literal::EntityUID(e)) => {
4946                Self::EntityUid(ast::EntityUID::clone(&e).into())
4947            }
4948            ast::ValueKind::Set(set) => Self::Set(Set(set
4949                .authoritative
4950                .iter()
4951                .map(|v| v.clone().into())
4952                .collect())),
4953            ast::ValueKind::Record(record) => Self::Record(Record(
4954                record
4955                    .iter()
4956                    .map(|(k, v)| (k.to_string(), v.clone().into()))
4957                    .collect(),
4958            )),
4959            ast::ValueKind::ExtensionValue(ev) => {
4960                Self::ExtensionValue(RestrictedExpr::from(ev.as_ref().clone()).to_string())
4961            }
4962        }
4963    }
4964}
4965
4966#[doc(hidden)]
4967// PANIC SAFETY: see the panic safety comments below
4968#[allow(clippy::fallible_impl_from)]
4969impl From<EvalResult> for Expression {
4970    fn from(res: EvalResult) -> Self {
4971        match res {
4972            EvalResult::Bool(b) => Self::new_bool(b),
4973            EvalResult::Long(l) => Self::new_long(l),
4974            EvalResult::String(s) => Self::new_string(s),
4975            EvalResult::EntityUid(eid) => {
4976                Self::from(ast::Expr::from(ast::Value::from(ast::EntityUID::from(eid))))
4977            }
4978            EvalResult::Set(set) => Self::new_set(set.iter().cloned().map(Self::from)),
4979            EvalResult::Record(r) => {
4980                // PANIC SAFETY: record originates from EvalResult so should not panic when reconstructing as an Expression
4981                #[allow(clippy::unwrap_used)]
4982                Self::new_record(r.iter().map(|(k, v)| (k.clone(), Self::from(v.clone())))).unwrap()
4983            }
4984            EvalResult::ExtensionValue(s) => {
4985                // PANIC SAFETY: the string s is constructed using RestrictedExpr::to_string() so should not panic when being parsed back into a RestrictedExpr
4986                #[allow(clippy::unwrap_used)]
4987                let expr: ast::Expr = ast::RestrictedExpr::from_str(&s).unwrap().into();
4988                Self::from(expr)
4989            }
4990        }
4991    }
4992}
4993
4994impl std::fmt::Display for EvalResult {
4995    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4996        match self {
4997            Self::Bool(b) => write!(f, "{b}"),
4998            Self::Long(l) => write!(f, "{l}"),
4999            Self::String(s) => write!(f, "\"{}\"", s.escape_debug()),
5000            Self::EntityUid(uid) => write!(f, "{uid}"),
5001            Self::Set(s) => {
5002                write!(f, "[")?;
5003                for (i, ev) in s.iter().enumerate() {
5004                    write!(f, "{ev}")?;
5005                    if (i + 1) < s.len() {
5006                        write!(f, ", ")?;
5007                    }
5008                }
5009                write!(f, "]")?;
5010                Ok(())
5011            }
5012            Self::Record(r) => {
5013                write!(f, "{{")?;
5014                for (i, (k, v)) in r.iter().enumerate() {
5015                    write!(f, "\"{}\": {v}", k.escape_debug())?;
5016                    if (i + 1) < r.len() {
5017                        write!(f, ", ")?;
5018                    }
5019                }
5020                write!(f, "}}")?;
5021                Ok(())
5022            }
5023            Self::ExtensionValue(s) => write!(f, "{s}"),
5024        }
5025    }
5026}
5027
5028/// Evaluates an expression.
5029///
5030/// If evaluation results in an error (e.g., attempting to access a non-existent Entity or Record,
5031/// passing the wrong number of arguments to a function etc.), that error is returned as a String
5032pub fn eval_expression(
5033    request: &Request,
5034    entities: &Entities,
5035    expr: &Expression,
5036) -> Result<EvalResult, EvaluationError> {
5037    let all_ext = Extensions::all_available();
5038    let eval = Evaluator::new(request.0.clone(), &entities.0, all_ext);
5039    Ok(EvalResult::from(
5040        // Evaluate under the empty slot map, as an expression should not have slots
5041        eval.interpret(&expr.0, &ast::SlotEnv::new())?,
5042    ))
5043}
5044
5045#[cfg(feature = "tpe")]
5046pub use tpe::*;
5047
5048#[cfg(feature = "tpe")]
5049mod tpe {
5050    use std::collections::{BTreeMap, HashMap, HashSet};
5051    use std::sync::Arc;
5052
5053    use cedar_policy_core::ast::{self, PartialValueToValueError, Value};
5054    use cedar_policy_core::authorizer::Decision;
5055    use cedar_policy_core::batched_evaluator::is_authorized_batched;
5056    use cedar_policy_core::batched_evaluator::{
5057        err::BatchedEvalError, EntityLoader as EntityLoaderInternal,
5058    };
5059    use cedar_policy_core::tpe;
5060    use cedar_policy_core::{
5061        entities::conformance::EntitySchemaConformanceChecker, extensions::Extensions,
5062        validator::CoreSchema,
5063    };
5064    use itertools::Itertools;
5065    use ref_cast::RefCast;
5066    use smol_str::SmolStr;
5067
5068    use crate::Entity;
5069    #[cfg(feature = "partial-eval")]
5070    use crate::{
5071        api, tpe_err, Authorizer, Context, Entities, EntityId, EntityTypeName, EntityUid,
5072        PartialRequestCreationError, PermissionQueryError, Policy, PolicySet, Request,
5073        RequestValidationError, RestrictedExpression, Schema, TPEReauthorizationError,
5074    };
5075
5076    /// A partial [`EntityUid`].
5077    /// That is, its [`EntityId`] could be unknown
5078    #[repr(transparent)]
5079    #[derive(Debug, Clone, RefCast)]
5080    pub struct PartialEntityUid(pub(crate) tpe::request::PartialEntityUID);
5081
5082    impl PartialEntityUid {
5083        /// Construct a [`PartialEntityUid`]
5084        pub fn new(ty: EntityTypeName, id: Option<EntityId>) -> Self {
5085            Self(tpe::request::PartialEntityUID {
5086                ty: ty.0,
5087                eid: id.map(|id| <EntityId as AsRef<ast::Eid>>::as_ref(&id).clone()),
5088            })
5089        }
5090
5091        /// Construct a [`PartialEntityUid`] from a concrete [`EntityUid`].
5092        pub fn from_concrete(euid: EntityUid) -> Self {
5093            let (ty, eid) = euid.0.components();
5094            Self(tpe::request::PartialEntityUID { ty, eid: Some(eid) })
5095        }
5096    }
5097
5098    /// A partial [`Request`]
5099    /// Its principal/resource types and action must be known and its context
5100    /// must either be fully known or unknown
5101    #[repr(transparent)]
5102    #[derive(Debug, Clone, RefCast)]
5103    pub struct PartialRequest(pub(crate) tpe::request::PartialRequest);
5104
5105    impl PartialRequest {
5106        /// Construct a valid [`PartialRequest`] according to a [`Schema`]
5107        pub fn new(
5108            principal: PartialEntityUid,
5109            action: EntityUid,
5110            resource: PartialEntityUid,
5111            context: Option<Context>,
5112            schema: &Schema,
5113        ) -> Result<Self, PartialRequestCreationError> {
5114            let context = context
5115                .map(|c| match c.0 {
5116                    ast::Context::RestrictedResidual(_) => {
5117                        Err(PartialRequestCreationError::ContextContainsUnknowns)
5118                    }
5119                    ast::Context::Value(m) => Ok(m),
5120                })
5121                .transpose()?;
5122            tpe::request::PartialRequest::new(principal.0, action.0, resource.0, context, &schema.0)
5123                .map(Self)
5124                .map_err(|e| PartialRequestCreationError::Validation(e.into()))
5125        }
5126    }
5127
5128    /// Like [`PartialRequest`] but only `resource` can be unknown
5129    #[repr(transparent)]
5130    #[derive(Debug, Clone, RefCast)]
5131    pub struct ResourceQueryRequest(pub(crate) PartialRequest);
5132
5133    impl ResourceQueryRequest {
5134        /// Construct a valid [`ResourceQueryRequest`] according to a [`Schema`]
5135        pub fn new(
5136            principal: EntityUid,
5137            action: EntityUid,
5138            resource: EntityTypeName,
5139            context: Context,
5140            schema: &Schema,
5141        ) -> Result<Self, PartialRequestCreationError> {
5142            PartialRequest::new(
5143                PartialEntityUid(principal.0.into()),
5144                action,
5145                PartialEntityUid::new(resource, None),
5146                Some(context),
5147                schema,
5148            )
5149            .map(Self)
5150        }
5151
5152        /// Convert [`ResourceQueryRequest`] to a [`Request`] by providing the resource [`EntityId`]
5153        pub fn to_request(
5154            &self,
5155            resource_id: EntityId,
5156            schema: Option<&Schema>,
5157        ) -> Result<Request, RequestValidationError> {
5158            // PANIC SAFETY: various fields are validated through the constructor
5159            #[allow(clippy::unwrap_used)]
5160            Request::new(
5161                EntityUid(self.0 .0.get_principal().try_into().unwrap()),
5162                EntityUid(self.0 .0.get_action()),
5163                EntityUid::from_type_name_and_id(
5164                    EntityTypeName(self.0 .0.get_resource_type()),
5165                    resource_id,
5166                ),
5167                Context::from_pairs(
5168                    self.0
5169                         .0
5170                        .get_context_attrs()
5171                        .unwrap()
5172                        .iter()
5173                        .map(|(a, v)| (a.to_string(), RestrictedExpression(v.clone().into()))),
5174                )
5175                .unwrap(),
5176                schema,
5177            )
5178        }
5179    }
5180
5181    /// Like [`PartialRequest`] but only `principal` can be unknown
5182    #[repr(transparent)]
5183    #[derive(Debug, Clone, RefCast)]
5184    pub struct PrincipalQueryRequest(pub(crate) PartialRequest);
5185
5186    impl PrincipalQueryRequest {
5187        /// Construct a valid [`PrincipalQueryRequest`] according to a [`Schema`]
5188        pub fn new(
5189            principal: EntityTypeName,
5190            action: EntityUid,
5191            resource: EntityUid,
5192            context: Context,
5193            schema: &Schema,
5194        ) -> Result<Self, PartialRequestCreationError> {
5195            PartialRequest::new(
5196                PartialEntityUid::new(principal, None),
5197                action,
5198                PartialEntityUid(resource.0.into()),
5199                Some(context),
5200                schema,
5201            )
5202            .map(Self)
5203        }
5204
5205        /// Convert [`PrincipalQueryRequest`] to a [`Request`] by providing the principal [`EntityId`]
5206        pub fn to_request(
5207            &self,
5208            principal_id: EntityId,
5209            schema: Option<&Schema>,
5210        ) -> Result<Request, RequestValidationError> {
5211            // PANIC SAFETY: various fields are validated through the constructor
5212            #[allow(clippy::unwrap_used)]
5213            Request::new(
5214                EntityUid::from_type_name_and_id(
5215                    EntityTypeName(self.0 .0.get_principal_type()),
5216                    principal_id,
5217                ),
5218                EntityUid(self.0 .0.get_action()),
5219                EntityUid(self.0 .0.get_resource().try_into().unwrap()),
5220                Context::from_pairs(
5221                    self.0
5222                         .0
5223                        .get_context_attrs()
5224                        .unwrap()
5225                        .iter()
5226                        .map(|(a, v)| (a.to_string(), RestrictedExpression(v.clone().into()))),
5227                )
5228                .unwrap(),
5229                schema,
5230            )
5231        }
5232    }
5233
5234    /// Defines a [`PartialRequest`] which additionally leaves the action
5235    /// undefined, enabling queries listing what actions might be authorized.
5236    ///
5237    /// See [`PolicySet::query_action`] for documentation and example usage.
5238    #[derive(Debug, Clone)]
5239    pub struct ActionQueryRequest {
5240        principal: PartialEntityUid,
5241        resource: PartialEntityUid,
5242        context: Option<Arc<BTreeMap<SmolStr, Value>>>,
5243        schema: Schema,
5244    }
5245
5246    impl ActionQueryRequest {
5247        /// Construct a valid [`ActionQueryRequest`] according to a [`Schema`]
5248        pub fn new(
5249            principal: PartialEntityUid,
5250            resource: PartialEntityUid,
5251            context: Option<Context>,
5252            schema: Schema,
5253        ) -> Result<Self, PartialRequestCreationError> {
5254            let context = context
5255                .map(|c| match c.0 {
5256                    ast::Context::RestrictedResidual(_) => {
5257                        Err(PartialRequestCreationError::ContextContainsUnknowns)
5258                    }
5259                    ast::Context::Value(m) => Ok(m),
5260                })
5261                .transpose()?;
5262            Ok(Self {
5263                principal,
5264                resource,
5265                context,
5266                schema,
5267            })
5268        }
5269
5270        fn partial_request(
5271            &self,
5272            action: EntityUid,
5273        ) -> Result<PartialRequest, cedar_policy_core::validator::RequestValidationError> {
5274            tpe::request::PartialRequest::new(
5275                self.principal.0.clone(),
5276                action.0,
5277                self.resource.0.clone(),
5278                self.context.clone(),
5279                &self.schema.0,
5280            )
5281            .map(PartialRequest)
5282        }
5283    }
5284
5285    /// Partial [`Entities`]
5286    #[repr(transparent)]
5287    #[derive(Debug, Clone, RefCast)]
5288    pub struct PartialEntities(pub(crate) tpe::entities::PartialEntities);
5289
5290    impl PartialEntities {
5291        /// Construct [`PartialEntities`] from a JSON value
5292        /// The `parent`, `attrs`, `tags` field must be either fully known or
5293        /// unknown. And parent entities cannot have unknown parents.
5294        pub fn from_json_value(
5295            value: serde_json::Value,
5296            schema: &Schema,
5297        ) -> Result<Self, tpe_err::EntitiesError> {
5298            tpe::entities::PartialEntities::from_json_value(value, &schema.0).map(Self)
5299        }
5300
5301        /// Construct `[PartialEntities]` given a fully concrete `[Entities]`
5302        pub fn from_concrete(entities: Entities) -> Result<Self, PartialValueToValueError> {
5303            tpe::entities::PartialEntities::try_from(entities.0).map(Self)
5304        }
5305
5306        /// Create a `PartialEntities` with no entities
5307        pub fn empty() -> Self {
5308            Self(tpe::entities::PartialEntities::new())
5309        }
5310    }
5311
5312    /// A partial version of [`crate::Response`].
5313    #[repr(transparent)]
5314    #[derive(Debug, Clone, RefCast)]
5315    pub struct TPEResponse<'a>(pub(crate) tpe::response::Response<'a>);
5316
5317    impl TPEResponse<'_> {
5318        /// Attempt to get the authorization decision
5319        pub fn decision(&self) -> Option<Decision> {
5320            self.0.decision()
5321        }
5322
5323        /// Perform reauthorization
5324        pub fn reauthorize(
5325            &self,
5326            request: &Request,
5327            entities: &Entities,
5328        ) -> Result<api::Response, TPEReauthorizationError> {
5329            self.0
5330                .reauthorize(&request.0, &entities.0)
5331                .map(Into::into)
5332                .map_err(Into::into)
5333        }
5334    }
5335
5336    /// Entity loader trait for batched evaluation.
5337    ///
5338    /// Loads entities on demand, returning `None` for missing entities.
5339    /// The `load_entities` function must load all requested entities,
5340    /// and must compute and include all ancestors of the requested entities.
5341    /// Loading more entities than requested is allowed.
5342    pub trait EntityLoader {
5343        /// Load all entities for the given set of entity UIDs.
5344        /// Returns a map from [`EntityUid`] to [`Option<Entity>`], where `None` indicates
5345        /// the entity does not exist.
5346        fn load_entities(
5347            &mut self,
5348            uids: &HashSet<EntityUid>,
5349        ) -> HashMap<EntityUid, Option<Entity>>;
5350    }
5351
5352    /// Wrapper struct used to convert an [`EntityLoader`] to an `EntityLoaderInternal`
5353    struct EntityLoaderWrapper<'a>(&'a mut dyn EntityLoader);
5354
5355    impl EntityLoaderInternal for EntityLoaderWrapper<'_> {
5356        fn load_entities(
5357            &mut self,
5358            uids: &HashSet<ast::EntityUID>,
5359        ) -> HashMap<ast::EntityUID, Option<ast::Entity>> {
5360            let ids = uids
5361                .iter()
5362                .map(|id| EntityUid::ref_cast(id).clone())
5363                .collect();
5364            self.0
5365                .load_entities(&ids)
5366                .into_iter()
5367                .map(|(uid, entity)| (uid.0, entity.map(|e| e.0)))
5368                .collect()
5369        }
5370    }
5371
5372    /// Simple entity loader implementation that loads from a pre-existing Entities store
5373    #[derive(Debug)]
5374
5375    pub struct TestEntityLoader<'a> {
5376        entities: &'a Entities,
5377    }
5378
5379    impl<'a> TestEntityLoader<'a> {
5380        /// Create a new [`TestEntityLoader`] from an existing Entities store
5381        pub fn new(entities: &'a Entities) -> Self {
5382            Self { entities }
5383        }
5384    }
5385
5386    impl EntityLoader for TestEntityLoader<'_> {
5387        fn load_entities(
5388            &mut self,
5389            uids: &HashSet<EntityUid>,
5390        ) -> HashMap<EntityUid, Option<Entity>> {
5391            uids.iter()
5392                .map(|uid| {
5393                    let entity = self.entities.get(uid).cloned();
5394                    (uid.clone(), entity)
5395                })
5396                .collect()
5397        }
5398    }
5399
5400    impl PolicySet {
5401        /// Perform type-aware partial evaluation on this [`PolicySet`]
5402        /// If successful, the result is a [`PolicySet`] containing residual
5403        /// policies ready for re-authorization
5404        pub fn tpe<'a>(
5405            &self,
5406            request: &'a PartialRequest,
5407            entities: &'a PartialEntities,
5408            schema: &'a Schema,
5409        ) -> Result<TPEResponse<'a>, tpe_err::TPEError> {
5410            use cedar_policy_core::tpe::is_authorized;
5411            let ps = &self.ast;
5412            let res = is_authorized(ps, &request.0, &entities.0, &schema.0)?;
5413            Ok(TPEResponse(res))
5414        }
5415
5416        /// Like [`Authorizer::is_authorized`] but uses an [`EntityLoader`] to load
5417        /// entities on demand.
5418        ///
5419        /// Calls `loader` at most `max_iters` times, returning
5420        /// early if an authorization result is reached.
5421        /// Otherwise, it iterates `max_iters` times and returns
5422        /// a partial result.
5423        ///
5424        pub fn is_authorized_batched(
5425            &self,
5426            query: &Request,
5427            schema: &Schema,
5428            loader: &mut dyn EntityLoader,
5429            max_iters: u32,
5430        ) -> Result<Decision, BatchedEvalError> {
5431            is_authorized_batched(
5432                &query.0,
5433                &self.ast,
5434                &schema.0,
5435                &mut EntityLoaderWrapper(loader),
5436                max_iters,
5437            )
5438        }
5439
5440        /// Perform a permission query on the resource
5441        pub fn query_resource(
5442            &self,
5443            request: &ResourceQueryRequest,
5444            entities: &Entities,
5445            schema: &Schema,
5446        ) -> Result<impl Iterator<Item = EntityUid>, PermissionQueryError> {
5447            let partial_entities = PartialEntities(entities.clone().0.try_into()?);
5448            let core_schema = CoreSchema::new(&schema.0);
5449            let validator =
5450                EntitySchemaConformanceChecker::new(&core_schema, Extensions::all_available());
5451            // We need to type-check the entities
5452            for entity in entities.0.iter() {
5453                validator.validate_entity(entity)?;
5454            }
5455            let residuals = self.tpe(&request.0, &partial_entities, schema)?;
5456            // PANIC SAFETY: policy set construction should succeed because there shouldn't be any policy id conflicts
5457            #[allow(clippy::unwrap_used)]
5458            let policies = &Self::from_policies(
5459                residuals
5460                    .0
5461                    .residual_policies()
5462                    .into_iter()
5463                    .map(Policy::from_ast),
5464            )
5465            .unwrap();
5466            // PANIC SAFETY: request construction should succeed because each entity passes validation
5467            #[allow(clippy::unwrap_used)]
5468            match residuals.decision() {
5469                Some(Decision::Allow) => Ok(entities
5470                    .iter()
5471                    .filter(|entity| {
5472                        entity.0.uid().entity_type() == &request.0 .0.get_resource_type()
5473                    })
5474                    .map(super::Entity::uid)
5475                    .collect_vec()
5476                    .into_iter()),
5477                Some(Decision::Deny) => Ok(vec![].into_iter()),
5478                None => Ok(entities
5479                    .iter()
5480                    .filter(|entity| {
5481                        entity.0.uid().entity_type() == &request.0 .0.get_resource_type()
5482                    })
5483                    .filter(|entity| {
5484                        let authorizer = Authorizer::new();
5485                        authorizer
5486                            .is_authorized(
5487                                &request.to_request(entity.uid().id().clone(), None).unwrap(),
5488                                policies,
5489                                entities,
5490                            )
5491                            .decision
5492                            == Decision::Allow
5493                    })
5494                    .map(super::Entity::uid)
5495                    .collect_vec()
5496                    .into_iter()),
5497            }
5498        }
5499
5500        /// Perform a permission query on the principal
5501        pub fn query_principal(
5502            &self,
5503            request: &PrincipalQueryRequest,
5504            entities: &Entities,
5505            schema: &Schema,
5506        ) -> Result<impl Iterator<Item = EntityUid>, PermissionQueryError> {
5507            let partial_entities = PartialEntities(entities.clone().0.try_into()?);
5508            let core_schema = CoreSchema::new(&schema.0);
5509            let validator =
5510                EntitySchemaConformanceChecker::new(&core_schema, Extensions::all_available());
5511            // We need to type-check the entities
5512            for entity in entities.0.iter() {
5513                validator.validate_entity(entity)?;
5514            }
5515            let residuals = self.tpe(&request.0, &partial_entities, schema)?;
5516            // PANIC SAFETY: policy set construction should succeed because there shouldn't be any policy id conflicts
5517            #[allow(clippy::unwrap_used)]
5518            let policies = &Self::from_policies(
5519                residuals
5520                    .0
5521                    .residual_policies()
5522                    .into_iter()
5523                    .map(Policy::from_ast),
5524            )
5525            .unwrap();
5526            // PANIC SAFETY: request construction should succeed because each entity passes validation
5527            #[allow(clippy::unwrap_used)]
5528            match residuals.decision() {
5529                Some(Decision::Allow) => Ok(entities
5530                    .iter()
5531                    .filter(|entity| {
5532                        entity.0.uid().entity_type() == &request.0 .0.get_principal_type()
5533                    })
5534                    .map(super::Entity::uid)
5535                    .collect_vec()
5536                    .into_iter()),
5537                Some(Decision::Deny) => Ok(vec![].into_iter()),
5538                None => Ok(entities
5539                    .iter()
5540                    .filter(|entity| {
5541                        entity.0.uid().entity_type() == &request.0 .0.get_principal_type()
5542                    })
5543                    .filter(|entity| {
5544                        let authorizer = Authorizer::new();
5545                        authorizer
5546                            .is_authorized(
5547                                &request.to_request(entity.uid().id().clone(), None).unwrap(),
5548                                policies,
5549                                entities,
5550                            )
5551                            .decision
5552                            == Decision::Allow
5553                    })
5554                    .map(super::Entity::uid)
5555                    .collect_vec()
5556                    .into_iter()),
5557            }
5558        }
5559
5560        /// Given a [`ActionQueryRequest`] (a partial request without a concrete
5561        /// action) enumerate actions in the schema which might be authorized
5562        /// for that request.
5563        ///
5564        /// Each action is returned with a partial authorization decision.  If
5565        /// the action is definitely authorized, then it is `Some(Decision::Allow)`.
5566        /// If we did not reach a concrete authorization decision, then it is
5567        /// `None`. Actions which are definitely not authorized (i.e., the
5568        /// decision is `Some(Decision::Deny)`) are not returned by this
5569        /// function. It is also possible that some actions without a concrete
5570        /// authorization decision are never authorized if the residual
5571        /// expressions after partial evaluation are not satisfiable.
5572        ///
5573        /// If the partial request for a particular action is invalid (e.g., the
5574        /// action does not apply to the type of principal and resource), then
5575        /// that action is not included in the result regardless of whether a
5576        /// request with that action would be authorized.
5577        ///
5578        /// ```
5579        /// # use cedar_policy::{PolicySet, Schema, ActionQueryRequest, PartialEntities, PartialEntityUid, Decision, EntityUid, Entities};
5580        /// # use std::str::FromStr;
5581        /// # let policies = PolicySet::from_str(r#"
5582        /// #     permit(principal, action == Action::"edit", resource) when { context.should_allow };
5583        /// #     permit(principal, action == Action::"view", resource);
5584        /// # "#).unwrap();
5585        /// # let schema = Schema::from_str("
5586        /// #     entity User, Photo;
5587        /// #     action view, edit appliesTo {
5588        /// #       principal: User,
5589        /// #       resource: Photo,
5590        /// #       context: { should_allow: Bool, }
5591        /// #     };
5592        /// # ").unwrap();
5593        /// # let entities = PartialEntities::empty();
5594        ///
5595        /// // Construct a request for a concrete principal and resource, but leaving the context unknown so
5596        /// // that we can see all actions that might be authorized for some context.
5597        /// let request = ActionQueryRequest::new(
5598        ///     PartialEntityUid::from_concrete(r#"User::"alice""#.parse().unwrap()),
5599        ///     PartialEntityUid::from_concrete(r#"Photo::"vacation.jpg""#.parse().unwrap()),
5600        ///     None,
5601        ///     schema,
5602        /// ).unwrap();
5603        ///
5604        /// // All actions which might be allowed for this principal and resource.
5605        /// // The exact authorization result may depend on currently unknown
5606        /// // context and entity data.
5607        /// let possibly_allowed_actions: Vec<&EntityUid> =
5608        ///     policies.query_action(&request, &entities)
5609        ///             .unwrap()
5610        ///             .map(|(a, _)| a)
5611        ///             .collect();
5612        /// # let mut possibly_allowed_actions = possibly_allowed_actions;
5613        /// # possibly_allowed_actions.sort();
5614        /// # assert_eq!(&possibly_allowed_actions, &[&r#"Action::"edit""#.parse().unwrap(), &r#"Action::"view""#.parse().unwrap()]);
5615        ///
5616        /// // These actions are definitely allowed for this principal and resource.
5617        /// // These will be allowed for _any_ context.
5618        /// let allowed_actions: Vec<&EntityUid> =
5619        ///     policies.query_action(&request, &entities).unwrap()
5620        ///             .filter(|(_, resp)| resp == &Some(Decision::Allow))
5621        ///             .map(|(a, _)| a)
5622        ///             .collect();
5623        /// # assert_eq!(&allowed_actions, &[&r#"Action::"view""#.parse().unwrap()]);
5624        /// ```
5625        pub fn query_action<'a>(
5626            &self,
5627            request: &'a ActionQueryRequest,
5628            entities: &PartialEntities,
5629        ) -> Result<impl Iterator<Item = (&'a EntityUid, Option<Decision>)>, PermissionQueryError>
5630        {
5631            let mut authorized_actions = Vec::new();
5632            // We only consider actions that apply to the type of the requested
5633            // principal and resource. Any requests for different actions would
5634            // be invalid, so they should never be authorized. Not however that
5635            // an authorization request for _could_ return `Allow` if the caller
5636            // ignores the request validation error.
5637            for action in request
5638                .schema
5639                .0
5640                .actions_for_principal_and_resource(&request.principal.0.ty, &request.resource.0.ty)
5641            {
5642                match request.partial_request(action.clone().into()) {
5643                    Ok(partial_request) => {
5644                        let decision = self
5645                            .tpe(&partial_request, entities, &request.schema)?
5646                            .decision();
5647                        if decision != Some(Decision::Deny) {
5648                            authorized_actions.push((RefCast::ref_cast(action), decision));
5649                        }
5650                    }
5651                    // This case occurs if the partial context is not valid for
5652                    // the context type declared for this action. This action
5653                    // should never be authorized, but with the same caveats
5654                    // about invalid requests.
5655                    Err(_) => {}
5656                }
5657            }
5658            Ok(authorized_actions.into_iter())
5659        }
5660    }
5661}
5662
5663// These are the same tests in validator, just ensuring all the plumbing is done correctly
5664#[cfg(test)]
5665mod test_access {
5666    use cedar_policy_core::ast;
5667
5668    use super::*;
5669
5670    fn schema() -> Schema {
5671        let src = r#"
5672        type Task = {
5673    "id": Long,
5674    "name": String,
5675    "state": String,
5676};
5677
5678type T = String;
5679
5680type Tasks = Set<Task>;
5681entity List in [Application] = {
5682  "editors": Team,
5683  "name": String,
5684  "owner": User,
5685  "readers": Team,
5686  "tasks": Tasks,
5687};
5688entity Application;
5689entity User in [Team, Application] = {
5690  "joblevel": Long,
5691  "location": String,
5692};
5693
5694entity CoolList;
5695
5696entity Team in [Team, Application];
5697
5698action Read, Write, Create;
5699
5700action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
5701    principal: [User],
5702    resource : [List]
5703};
5704
5705action GetList in Read appliesTo {
5706    principal : [User],
5707    resource : [List, CoolList]
5708};
5709
5710action GetLists in Read appliesTo {
5711    principal : [User],
5712    resource : [Application]
5713};
5714
5715action CreateList in Create appliesTo {
5716    principal : [User],
5717    resource : [Application]
5718};
5719
5720        "#;
5721
5722        src.parse().unwrap()
5723    }
5724
5725    #[test]
5726    fn principals() {
5727        let schema = schema();
5728        let principals = schema.principals().collect::<HashSet<_>>();
5729        assert_eq!(principals.len(), 1);
5730        let user: EntityTypeName = "User".parse().unwrap();
5731        assert!(principals.contains(&user));
5732        let principals = schema.principals().collect::<Vec<_>>();
5733        assert!(principals.len() > 1);
5734        assert!(principals.iter().all(|ety| **ety == user));
5735        assert!(principals.iter().all(|ety| ety.0.loc().is_some()));
5736
5737        let et = ast::EntityType::EntityType(ast::Name::from_normalized_str("User").unwrap());
5738        let et = schema.0.get_entity_type(&et).unwrap();
5739        assert!(et.loc.is_some());
5740    }
5741
5742    #[cfg(feature = "extended-schema")]
5743    #[test]
5744    fn common_types_extended() {
5745        use cool_asserts::assert_matches;
5746
5747        use cedar_policy_core::validator::{
5748            types::{EntityRecordKind, Type},
5749            LocatedCommonType,
5750        };
5751
5752        let schema = schema();
5753        assert_eq!(schema.0.common_types().collect::<HashSet<_>>().len(), 3);
5754        let task_type = LocatedCommonType {
5755            name: "Task".into(),
5756            name_loc: None,
5757            type_loc: None,
5758        };
5759        assert!(schema.0.common_types().contains(&task_type));
5760
5761        let tasks_type = LocatedCommonType {
5762            name: "Tasks".into(),
5763            name_loc: None,
5764            type_loc: None,
5765        };
5766        assert!(schema.0.common_types().contains(&tasks_type));
5767        assert!(schema.0.common_types().all(|ct| ct.name_loc.is_some()));
5768        assert!(schema.0.common_types().all(|ct| ct.type_loc.is_some()));
5769
5770        let tasks_type = LocatedCommonType {
5771            name: "T".into(),
5772            name_loc: None,
5773            type_loc: None,
5774        };
5775        assert!(schema.0.common_types().contains(&tasks_type));
5776
5777        let et = ast::EntityType::EntityType(ast::Name::from_normalized_str("List").unwrap());
5778        let et = schema.0.get_entity_type(&et).unwrap();
5779        let attrs = et.attributes();
5780
5781        // Assert that attributes that are resolved from common types still get source locations
5782        let t = attrs.get_attr("tasks").unwrap();
5783        assert!(t.loc.is_some());
5784        assert_matches!(&t.attr_type, cedar_policy_core::validator::types::Type::Set { ref element_type } => {
5785            let el = *element_type.clone().unwrap();
5786            assert_matches!(el, Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
5787                assert!(attrs.get_attr("name").unwrap().loc.is_some());
5788                assert!(attrs.get_attr("id").unwrap().loc.is_some());
5789                assert!(attrs.get_attr("state").unwrap().loc.is_some());
5790            });
5791        });
5792    }
5793
5794    #[cfg(feature = "extended-schema")]
5795    #[test]
5796    fn namespace_extended() {
5797        let schema = schema();
5798        assert_eq!(schema.0.namespaces().collect::<HashSet<_>>().len(), 1);
5799        let default_namespace = schema.0.namespaces().last().unwrap();
5800        assert_eq!(default_namespace.name, SmolStr::from("__cedar"));
5801        assert!(default_namespace.name_loc.is_none());
5802        assert!(default_namespace.def_loc.is_none());
5803    }
5804
5805    #[test]
5806    fn empty_schema_principals_and_resources() {
5807        let empty: Schema = "".parse().unwrap();
5808        assert!(empty.principals().next().is_none());
5809        assert!(empty.resources().next().is_none());
5810    }
5811
5812    #[test]
5813    fn resources() {
5814        let schema = schema();
5815        let resources = schema.resources().cloned().collect::<HashSet<_>>();
5816        let expected: HashSet<EntityTypeName> = HashSet::from([
5817            "List".parse().unwrap(),
5818            "Application".parse().unwrap(),
5819            "CoolList".parse().unwrap(),
5820        ]);
5821        assert_eq!(resources, expected);
5822        assert!(resources.iter().all(|ety| ety.0.loc().is_some()));
5823    }
5824
5825    #[test]
5826    fn principals_for_action() {
5827        let schema = schema();
5828        let delete_list: EntityUid = r#"Action::"DeleteList""#.parse().unwrap();
5829        let delete_user: EntityUid = r#"Action::"DeleteUser""#.parse().unwrap();
5830        let got = schema
5831            .principals_for_action(&delete_list)
5832            .unwrap()
5833            .cloned()
5834            .collect::<Vec<_>>();
5835        assert_eq!(got, vec!["User".parse().unwrap()]);
5836        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5837        assert!(schema.principals_for_action(&delete_user).is_none());
5838    }
5839
5840    #[test]
5841    fn resources_for_action() {
5842        let schema = schema();
5843        let delete_list: EntityUid = r#"Action::"DeleteList""#.parse().unwrap();
5844        let delete_user: EntityUid = r#"Action::"DeleteUser""#.parse().unwrap();
5845        let create_list: EntityUid = r#"Action::"CreateList""#.parse().unwrap();
5846        let get_list: EntityUid = r#"Action::"GetList""#.parse().unwrap();
5847        let got = schema
5848            .resources_for_action(&delete_list)
5849            .unwrap()
5850            .cloned()
5851            .collect::<Vec<_>>();
5852        assert_eq!(got, vec!["List".parse().unwrap()]);
5853        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5854        let got = schema
5855            .resources_for_action(&create_list)
5856            .unwrap()
5857            .cloned()
5858            .collect::<Vec<_>>();
5859        assert_eq!(got, vec!["Application".parse().unwrap()]);
5860        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5861        let got = schema
5862            .resources_for_action(&get_list)
5863            .unwrap()
5864            .cloned()
5865            .collect::<HashSet<_>>();
5866        assert_eq!(
5867            got,
5868            HashSet::from(["List".parse().unwrap(), "CoolList".parse().unwrap()])
5869        );
5870        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5871        assert!(schema.principals_for_action(&delete_user).is_none());
5872    }
5873
5874    #[test]
5875    fn principal_parents() {
5876        let schema = schema();
5877        let user: EntityTypeName = "User".parse().unwrap();
5878        let parents = schema
5879            .ancestors(&user)
5880            .unwrap()
5881            .cloned()
5882            .collect::<HashSet<_>>();
5883        assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5884        let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
5885        assert_eq!(parents, expected);
5886        let parents = schema
5887            .ancestors(&"List".parse().unwrap())
5888            .unwrap()
5889            .cloned()
5890            .collect::<HashSet<_>>();
5891        assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5892        let expected = HashSet::from(["Application".parse().unwrap()]);
5893        assert_eq!(parents, expected);
5894        assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
5895        let parents = schema
5896            .ancestors(&"CoolList".parse().unwrap())
5897            .unwrap()
5898            .cloned()
5899            .collect::<HashSet<_>>();
5900        assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5901        let expected = HashSet::from([]);
5902        assert_eq!(parents, expected);
5903    }
5904
5905    #[test]
5906    fn action_groups() {
5907        let schema = schema();
5908        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
5909        let expected = ["Read", "Write", "Create"]
5910            .into_iter()
5911            .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5912            .collect::<HashSet<EntityUid>>();
5913        #[cfg(feature = "extended-schema")]
5914        assert!(groups.iter().all(|ety| ety.0.loc().is_some()));
5915        assert_eq!(groups, expected);
5916    }
5917
5918    #[test]
5919    fn actions() {
5920        let schema = schema();
5921        let actions = schema.actions().cloned().collect::<HashSet<_>>();
5922        let expected = [
5923            "Read",
5924            "Write",
5925            "Create",
5926            "DeleteList",
5927            "EditShare",
5928            "UpdateList",
5929            "CreateTask",
5930            "UpdateTask",
5931            "DeleteTask",
5932            "GetList",
5933            "GetLists",
5934            "CreateList",
5935        ]
5936        .into_iter()
5937        .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5938        .collect::<HashSet<EntityUid>>();
5939        assert_eq!(actions, expected);
5940        #[cfg(feature = "extended-schema")]
5941        assert!(actions.iter().all(|ety| ety.0.loc().is_some()));
5942    }
5943
5944    #[test]
5945    fn actions_for_principal_and_resource() {
5946        let schema = schema();
5947        let pty: EntityTypeName = "User".parse().unwrap();
5948        let rty: EntityTypeName = "Application".parse().unwrap();
5949        let actions = schema
5950            .actions_for_principal_and_resource(&pty, &rty)
5951            .cloned()
5952            .collect::<HashSet<EntityUid>>();
5953        let expected = ["GetLists", "CreateList"]
5954            .into_iter()
5955            .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5956            .collect::<HashSet<EntityUid>>();
5957        assert_eq!(actions, expected);
5958    }
5959
5960    #[test]
5961    fn entities() {
5962        let schema = schema();
5963        let entities = schema.entity_types().cloned().collect::<HashSet<_>>();
5964        let expected = ["List", "Application", "User", "CoolList", "Team"]
5965            .into_iter()
5966            .map(|ty| ty.parse().unwrap())
5967            .collect::<HashSet<EntityTypeName>>();
5968        assert_eq!(entities, expected);
5969    }
5970}
5971
5972#[cfg(test)]
5973mod test_access_namespace {
5974    use super::*;
5975
5976    fn schema() -> Schema {
5977        let src = r#"
5978        namespace Foo {
5979        type Task = {
5980    "id": Long,
5981    "name": String,
5982    "state": String,
5983};
5984
5985type Tasks = Set<Task>;
5986entity List in [Application] = {
5987  "editors": Team,
5988  "name": String,
5989  "owner": User,
5990  "readers": Team,
5991  "tasks": Tasks,
5992};
5993entity Application;
5994entity User in [Team, Application] = {
5995  "joblevel": Long,
5996  "location": String,
5997};
5998
5999entity CoolList;
6000
6001entity Team in [Team, Application];
6002
6003action Read, Write, Create;
6004
6005action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
6006    principal: [User],
6007    resource : [List]
6008};
6009
6010action GetList in Read appliesTo {
6011    principal : [User],
6012    resource : [List, CoolList]
6013};
6014
6015action GetLists in Read appliesTo {
6016    principal : [User],
6017    resource : [Application]
6018};
6019
6020action CreateList in Create appliesTo {
6021    principal : [User],
6022    resource : [Application]
6023};
6024    }
6025
6026        "#;
6027
6028        src.parse().unwrap()
6029    }
6030
6031    #[test]
6032    fn principals() {
6033        let schema = schema();
6034        let principals = schema.principals().collect::<HashSet<_>>();
6035        assert_eq!(principals.len(), 1);
6036        let user: EntityTypeName = "Foo::User".parse().unwrap();
6037        assert!(principals.contains(&user));
6038        let principals = schema.principals().collect::<Vec<_>>();
6039        assert!(principals.len() > 1);
6040        assert!(principals.iter().all(|ety| **ety == user));
6041        assert!(principals.iter().all(|ety| ety.0.loc().is_some()));
6042    }
6043
6044    #[test]
6045    fn empty_schema_principals_and_resources() {
6046        let empty: Schema = "".parse().unwrap();
6047        assert!(empty.principals().next().is_none());
6048        assert!(empty.resources().next().is_none());
6049    }
6050
6051    #[test]
6052    fn resources() {
6053        let schema = schema();
6054        let resources = schema.resources().cloned().collect::<HashSet<_>>();
6055        let expected: HashSet<EntityTypeName> = HashSet::from([
6056            "Foo::List".parse().unwrap(),
6057            "Foo::Application".parse().unwrap(),
6058            "Foo::CoolList".parse().unwrap(),
6059        ]);
6060        assert_eq!(resources, expected);
6061        assert!(resources.iter().all(|ety| ety.0.loc().is_some()));
6062    }
6063
6064    #[test]
6065    fn principals_for_action() {
6066        let schema = schema();
6067        let delete_list: EntityUid = r#"Foo::Action::"DeleteList""#.parse().unwrap();
6068        let delete_user: EntityUid = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
6069        let got = schema
6070            .principals_for_action(&delete_list)
6071            .unwrap()
6072            .cloned()
6073            .collect::<Vec<_>>();
6074        assert_eq!(got, vec!["Foo::User".parse().unwrap()]);
6075        assert!(schema.principals_for_action(&delete_user).is_none());
6076    }
6077
6078    #[test]
6079    fn resources_for_action() {
6080        let schema = schema();
6081        let delete_list: EntityUid = r#"Foo::Action::"DeleteList""#.parse().unwrap();
6082        let delete_user: EntityUid = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
6083        let create_list: EntityUid = r#"Foo::Action::"CreateList""#.parse().unwrap();
6084        let get_list: EntityUid = r#"Foo::Action::"GetList""#.parse().unwrap();
6085        let got = schema
6086            .resources_for_action(&delete_list)
6087            .unwrap()
6088            .cloned()
6089            .collect::<Vec<_>>();
6090        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
6091
6092        assert_eq!(got, vec!["Foo::List".parse().unwrap()]);
6093        let got = schema
6094            .resources_for_action(&create_list)
6095            .unwrap()
6096            .cloned()
6097            .collect::<Vec<_>>();
6098        assert_eq!(got, vec!["Foo::Application".parse().unwrap()]);
6099        assert!(got.iter().all(|ety| ety.0.loc().is_some()));
6100
6101        let got = schema
6102            .resources_for_action(&get_list)
6103            .unwrap()
6104            .cloned()
6105            .collect::<HashSet<_>>();
6106        assert_eq!(
6107            got,
6108            HashSet::from([
6109                "Foo::List".parse().unwrap(),
6110                "Foo::CoolList".parse().unwrap()
6111            ])
6112        );
6113        assert!(schema.principals_for_action(&delete_user).is_none());
6114    }
6115
6116    #[test]
6117    fn principal_parents() {
6118        let schema = schema();
6119        let user: EntityTypeName = "Foo::User".parse().unwrap();
6120        let parents = schema
6121            .ancestors(&user)
6122            .unwrap()
6123            .cloned()
6124            .collect::<HashSet<_>>();
6125        let expected = HashSet::from([
6126            "Foo::Team".parse().unwrap(),
6127            "Foo::Application".parse().unwrap(),
6128        ]);
6129        assert_eq!(parents, expected);
6130        let parents = schema
6131            .ancestors(&"Foo::List".parse().unwrap())
6132            .unwrap()
6133            .cloned()
6134            .collect::<HashSet<_>>();
6135        let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
6136        assert_eq!(parents, expected);
6137        assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
6138        let parents = schema
6139            .ancestors(&"Foo::CoolList".parse().unwrap())
6140            .unwrap()
6141            .cloned()
6142            .collect::<HashSet<_>>();
6143        let expected = HashSet::from([]);
6144        assert_eq!(parents, expected);
6145    }
6146
6147    #[test]
6148    fn action_groups() {
6149        let schema = schema();
6150        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
6151        let expected = ["Read", "Write", "Create"]
6152            .into_iter()
6153            .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
6154            .collect::<HashSet<EntityUid>>();
6155        assert_eq!(groups, expected);
6156    }
6157
6158    #[test]
6159    fn actions() {
6160        let schema = schema();
6161        let actions = schema.actions().cloned().collect::<HashSet<_>>();
6162        let expected = [
6163            "Read",
6164            "Write",
6165            "Create",
6166            "DeleteList",
6167            "EditShare",
6168            "UpdateList",
6169            "CreateTask",
6170            "UpdateTask",
6171            "DeleteTask",
6172            "GetList",
6173            "GetLists",
6174            "CreateList",
6175        ]
6176        .into_iter()
6177        .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
6178        .collect::<HashSet<EntityUid>>();
6179        assert_eq!(actions, expected);
6180    }
6181
6182    #[test]
6183    fn entities() {
6184        let schema = schema();
6185        let entities = schema.entity_types().cloned().collect::<HashSet<_>>();
6186        let expected = [
6187            "Foo::List",
6188            "Foo::Application",
6189            "Foo::User",
6190            "Foo::CoolList",
6191            "Foo::Team",
6192        ]
6193        .into_iter()
6194        .map(|ty| ty.parse().unwrap())
6195        .collect::<HashSet<EntityTypeName>>();
6196        assert_eq!(entities, expected);
6197    }
6198
6199    #[test]
6200    fn test_request_context() {
6201        // Create a context with some test data
6202        let context =
6203            Context::from_json_str(r#"{"testKey": "testValue", "numKey": 42}"#, None).unwrap();
6204
6205        // Create entity UIDs for the request
6206        let principal: EntityUid = "User::\"alice\"".parse().unwrap();
6207        let action: EntityUid = "Action::\"view\"".parse().unwrap();
6208        let resource: EntityUid = "Resource::\"doc123\"".parse().unwrap();
6209
6210        // Create the request
6211        let request = Request::new(
6212            principal, action, resource, context, None, // no schema validation for this test
6213        )
6214        .unwrap();
6215
6216        // Test context() method
6217        let retrieved_context = request.context().expect("Context should be present");
6218
6219        // Test get() method on the retrieved context
6220        assert!(retrieved_context.get("testKey").is_some());
6221        assert!(retrieved_context.get("numKey").is_some());
6222        assert!(retrieved_context.get("nonexistent").is_none());
6223    }
6224
6225    #[cfg(feature = "extended-schema")]
6226    #[test]
6227    fn namespace_extended() {
6228        let schema = schema();
6229        assert_eq!(schema.0.namespaces().collect::<HashSet<_>>().len(), 2);
6230        let default_namespace = schema
6231            .0
6232            .namespaces()
6233            .filter(|n| n.name == *"__cedar")
6234            .last()
6235            .unwrap();
6236        assert!(default_namespace.name_loc.is_none());
6237        assert!(default_namespace.def_loc.is_none());
6238
6239        let default_namespace = schema
6240            .0
6241            .namespaces()
6242            .filter(|n| n.name == *"Foo")
6243            .last()
6244            .unwrap();
6245        assert!(default_namespace.name_loc.is_some());
6246        assert!(default_namespace.def_loc.is_some());
6247    }
6248}
6249
6250#[cfg(test)]
6251mod test_lossless_empty {
6252    use super::{LosslessPolicy, Policy, PolicyId, Template};
6253
6254    #[test]
6255    fn test_lossless_empty_policy() {
6256        const STATIC_POLICY_TEXT: &str = "permit(principal,action,resource);";
6257        let policy0 = Policy::parse(Some(PolicyId::new("policy0")), STATIC_POLICY_TEXT)
6258            .expect("Failed to parse");
6259        let lossy_policy0 = Policy {
6260            ast: policy0.ast.clone(),
6261            lossless: LosslessPolicy::policy_or_template_text(None::<&str>),
6262        };
6263        // The `to_cedar` representation becomes lossy since we didn't provide text
6264        assert_eq!(
6265            lossy_policy0.to_cedar(),
6266            Some(String::from(
6267                "permit(\n  principal,\n  action,\n  resource\n);"
6268            ))
6269        );
6270        // The EST representation is obtained from the AST
6271        let lossy_policy0_est = lossy_policy0
6272            .lossless
6273            .est(|| policy0.ast.clone().into())
6274            .unwrap();
6275        assert_eq!(lossy_policy0_est, policy0.ast.into());
6276    }
6277
6278    #[test]
6279    fn test_lossless_empty_template() {
6280        const TEMPLATE_TEXT: &str = "permit(principal == ?principal,action,resource);";
6281        let template0 = Template::parse(Some(PolicyId::new("template0")), TEMPLATE_TEXT)
6282            .expect("Failed to parse");
6283        let lossy_template0 = Template {
6284            ast: template0.ast.clone(),
6285            lossless: LosslessPolicy::policy_or_template_text(None::<&str>),
6286        };
6287        // The `to_cedar` representation becomes lossy since we didn't provide text
6288        assert_eq!(
6289            lossy_template0.to_cedar(),
6290            String::from("permit(\n  principal == ?principal,\n  action,\n  resource\n);")
6291        );
6292        // The EST representation is obtained from the AST
6293        let lossy_template0_est = lossy_template0
6294            .lossless
6295            .est(|| template0.ast.clone().into())
6296            .unwrap();
6297        assert_eq!(lossy_template0_est, template0.ast.into());
6298    }
6299}
6300
6301/// Given a schema and policy set, compute an entity manifest.
6302///
6303/// The policies must validate against the schema in strict mode,
6304/// otherwise an error is returned.
6305/// The manifest describes the data required to answer requests
6306/// for each action.
6307#[doc = include_str!("../experimental_warning.md")]
6308#[cfg(feature = "entity-manifest")]
6309pub fn compute_entity_manifest(
6310    validator: &Validator,
6311    pset: &PolicySet,
6312) -> Result<EntityManifest, EntityManifestError> {
6313    entity_manifest::compute_entity_manifest(&validator.0, &pset.ast)
6314        .map_err(std::convert::Into::into)
6315}