Skip to main content

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