cedar_policy/
api.rs

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