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