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