cedar_policy/
api.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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)]
23pub use ast::Effect;
24pub use authorizer::Decision;
25use cedar_policy_core::ast;
26use cedar_policy_core::ast::{
27    ContextCreationError, ExprConstructionError, RestrictedExprParseError,
28}; // `ContextCreationError` is unsuitable for `pub use` because it contains internal types like `RestrictedExpr`
29use cedar_policy_core::authorizer;
30pub use cedar_policy_core::authorizer::AuthorizationError;
31use cedar_policy_core::entities::{
32    self, ContextJsonDeserializationError, ContextSchema, Dereference, JsonDeserializationError,
33    JsonDeserializationErrorContext,
34};
35use cedar_policy_core::est;
36use cedar_policy_core::evaluator::Evaluator;
37pub use cedar_policy_core::evaluator::{EvaluationError, EvaluationErrorKind};
38pub use cedar_policy_core::extensions;
39use cedar_policy_core::extensions::Extensions;
40use cedar_policy_core::parser;
41pub use cedar_policy_core::parser::err::ParseErrors;
42use cedar_policy_core::parser::SourceInfo;
43use cedar_policy_core::FromNormalizedStr;
44use cedar_policy_validator::RequestValidationError; // this type is unsuitable for `pub use` because it contains internal types like `EntityUID` and `EntityType`
45pub use cedar_policy_validator::{
46    TypeErrorKind, UnsupportedFeature, ValidationErrorKind, ValidationWarningKind,
47};
48use itertools::Itertools;
49use ref_cast::RefCast;
50use serde::{Deserialize, Serialize};
51use smol_str::SmolStr;
52use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
53use std::convert::Infallible;
54use std::str::FromStr;
55use thiserror::Error;
56
57/// Identifier for a Template slot
58#[repr(transparent)]
59#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, RefCast)]
60pub struct SlotId(ast::SlotId);
61
62impl SlotId {
63    /// Get the slot for `principal`
64    pub fn principal() -> Self {
65        Self(ast::SlotId::principal())
66    }
67
68    /// Get the slot for `resource`
69    pub fn resource() -> Self {
70        Self(ast::SlotId::resource())
71    }
72}
73
74impl std::fmt::Display for SlotId {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        write!(f, "{}", self.0)
77    }
78}
79
80impl From<ast::SlotId> for SlotId {
81    fn from(a: ast::SlotId) -> Self {
82        Self(a)
83    }
84}
85
86impl From<SlotId> for ast::SlotId {
87    fn from(s: SlotId) -> Self {
88        s.0
89    }
90}
91
92/// Entity datatype
93// INVARIANT(UidOfEntityNotUnspecified): The `EntityUid` of an `Entity` cannot be unspecified
94#[repr(transparent)]
95#[derive(Debug, Clone, PartialEq, Eq, RefCast)]
96pub struct Entity(ast::Entity);
97
98impl Entity {
99    /// Create a new `Entity` with this Uid, attributes, and parents.
100    ///
101    /// Attribute values are specified here as "restricted expressions".
102    /// See docs on `RestrictedExpression`
103    /// ```
104    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid, RestrictedExpression};
105    /// # use std::collections::{HashMap, HashSet};
106    /// # use std::str::FromStr;
107    /// let eid = EntityId::from_str("alice").unwrap();
108    /// let type_name = EntityTypeName::from_str("User").unwrap();
109    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
110    /// let attrs = HashMap::from([
111    ///     ("age".to_string(), RestrictedExpression::from_str("21").unwrap()),
112    ///     ("department".to_string(), RestrictedExpression::from_str("\"CS\"").unwrap()),
113    /// ]);
114    /// let parent_eid = EntityId::from_str("admin").unwrap();
115    /// let parent_type_name = EntityTypeName::from_str("Group").unwrap();
116    /// let parent_euid = EntityUid::from_type_name_and_id(parent_type_name, parent_eid);
117    /// let parents = HashSet::from([parent_euid]);
118    /// let entity = Entity::new(euid, attrs, parents);
119    ///```
120    pub fn new(
121        uid: EntityUid,
122        attrs: HashMap<String, RestrictedExpression>,
123        parents: HashSet<EntityUid>,
124    ) -> Result<Self, EntityAttrEvaluationError> {
125        // note that we take a "parents" parameter here; we will compute TC when
126        // the `Entities` object is created
127        // INVARIANT(UidOfEntityNotUnspecified): by invariant on `EntityUid`
128        Ok(Self(ast::Entity::new(
129            uid.0,
130            attrs
131                .into_iter()
132                .map(|(k, v)| (SmolStr::from(k), v.0))
133                .collect(),
134            parents.into_iter().map(|uid| uid.0).collect(),
135            &Extensions::all_available(),
136        )?))
137    }
138
139    /// Create a new `Entity` with no attributes.
140    ///
141    /// Unlike [`Entity::new()`], this constructor cannot error.
142    /// (The only source of errors in `Entity::new()` are attributes.)
143    pub fn new_no_attrs(uid: EntityUid, parents: HashSet<EntityUid>) -> Self {
144        // note that we take a "parents" parameter here; we will compute TC when
145        // the `Entities` object is created
146        // INVARIANT(UidOfEntityNotUnspecified): by invariant on `EntityUid`
147        Self(ast::Entity::new_with_attr_partial_value(
148            uid.0,
149            HashMap::new(),
150            parents.into_iter().map(|uid| uid.0).collect(),
151        ))
152    }
153
154    /// Create a new `Entity` with this Uid, no attributes, and no parents.
155    /// ```
156    /// use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
157    /// # use std::str::FromStr;
158    /// let eid = EntityId::from_str("alice").unwrap();
159    /// let type_name = EntityTypeName::from_str("User").unwrap();
160    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
161    /// let alice = Entity::with_uid(euid);
162    /// # cool_asserts::assert_matches!(alice.attr("age"), None);
163    /// ```
164    pub fn with_uid(uid: EntityUid) -> Self {
165        // INVARIANT(UidOfEntityNotUnspecified): by invariant on `EntityUid`
166        Self(ast::Entity::with_uid(uid.0))
167    }
168
169    /// Get the Uid of this entity
170    /// ```
171    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
172    /// # use std::str::FromStr;
173    /// # let eid = EntityId::from_str("alice").unwrap();
174    /// let type_name = EntityTypeName::from_str("User").unwrap();
175    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
176    /// let alice = Entity::with_uid(euid.clone());
177    /// assert_eq!(alice.uid(), euid);
178    /// ```
179    pub fn uid(&self) -> EntityUid {
180        // INVARIANT: By invariant on self and `EntityUid`: Our Uid can't be unspecified
181        EntityUid(self.0.uid())
182    }
183
184    /// Get the value for the given attribute, or `None` if not present.
185    ///
186    /// This can also return Some(Err) if the attribute is not a value (i.e., is
187    /// unknown due to partial evaluation).
188    /// ```
189    /// use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid, EvalResult,
190    ///     RestrictedExpression};
191    /// use std::collections::{HashMap, HashSet};
192    /// use std::str::FromStr;
193    /// let eid = EntityId::from_str("alice").unwrap();
194    /// let type_name = EntityTypeName::from_str("User").unwrap();
195    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
196    /// let attrs = HashMap::from([
197    ///     ("age".to_string(), RestrictedExpression::from_str("21").unwrap()),
198    ///     ("department".to_string(), RestrictedExpression::from_str("\"CS\"").unwrap()),
199    /// ]);
200    /// let entity = Entity::new(euid, attrs, HashSet::new()).unwrap();
201    /// assert_eq!(entity.attr("age").unwrap().unwrap(), EvalResult::Long(21));
202    /// assert_eq!(entity.attr("department").unwrap().unwrap(), EvalResult::String("CS".to_string()));
203    /// assert!(entity.attr("foo").is_none());
204    /// ```
205    pub fn attr(&self, attr: &str) -> Option<Result<EvalResult, impl std::error::Error>> {
206        let v = match ast::Value::try_from(self.0.get(attr)?.clone()) {
207            Ok(v) => v,
208            Err(e) => return Some(Err(e)),
209        };
210        Some(Ok(EvalResult::from(v)))
211    }
212}
213
214impl std::fmt::Display for Entity {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        write!(f, "{}", self.0)
217    }
218}
219
220/// Represents an entity hierarchy, and allows looking up `Entity` objects by
221/// Uid.
222#[repr(transparent)]
223#[derive(Debug, Clone, Default, PartialEq, Eq, RefCast)]
224pub struct Entities(pub(crate) entities::Entities);
225
226pub use entities::EntitiesError;
227
228impl Entities {
229    /// Create a fresh `Entities` with no entities
230    /// ```
231    /// use cedar_policy::Entities;
232    /// let entities = Entities::empty();
233    /// ```
234    pub fn empty() -> Self {
235        Self(entities::Entities::new())
236    }
237
238    /// Get the `Entity` with the given Uid, if any
239    pub fn get(&self, uid: &EntityUid) -> Option<&Entity> {
240        match self.0.entity(&uid.0) {
241            Dereference::Residual(_) | Dereference::NoSuchEntity => None,
242            Dereference::Data(e) => Some(Entity::ref_cast(e)),
243        }
244    }
245
246    /// Transform the store into a partial store, where
247    /// attempting to dereference a non-existent `EntityUID` results in
248    /// a residual instead of an error.
249    #[must_use]
250    #[cfg(feature = "partial-eval")]
251    pub fn partial(self) -> Self {
252        Self(self.0.partial())
253    }
254
255    /// Iterate over the `Entity`'s in the `Entities`
256    pub fn iter(&self) -> impl Iterator<Item = &Entity> {
257        self.0.iter().map(Entity::ref_cast)
258    }
259
260    /// Create an `Entities` object with the given entities.
261    ///
262    /// `schema` represents a source of `Action` entities, which will be added
263    /// to the entities provided.
264    /// (If any `Action` entities are present in the provided entities, and a
265    /// `schema` is also provided, each `Action` entity in the provided entities
266    /// must exactly match its definition in the schema or an error is
267    /// returned.)
268    ///
269    /// If a `schema` is present, this function will also ensure that the
270    /// produced entities fully conform to the `schema` -- for instance, it will
271    /// error if attributes have the wrong types (e.g., string instead of
272    /// integer), or if required attributes are missing or superfluous
273    /// attributes are provided.
274    pub fn from_entities(
275        entities: impl IntoIterator<Item = Entity>,
276        schema: Option<&Schema>,
277    ) -> Result<Self, entities::EntitiesError> {
278        entities::Entities::from_entities(
279            entities.into_iter().map(|e| e.0),
280            schema
281                .map(|s| cedar_policy_validator::CoreSchema::new(&s.0))
282                .as_ref(),
283            entities::TCComputation::ComputeNow,
284            Extensions::all_available(),
285        )
286        .map(Entities)
287    }
288
289    /// Add all of the [`Entity`]s in the collection to this [`Entities`]
290    /// structure, re-computing the transitive closure.
291    ///
292    /// If a `schema` is provided, this method will ensure that the added
293    /// entities fully conform to the schema -- for instance, it will error if
294    /// attributes have the wrong types (e.g., string instead of integer), or if
295    /// required attributes are missing or superfluous attributes are provided.
296    /// (This method will not add action entities from the `schema`.)
297    ///
298    /// Re-computing the transitive closure can be expensive, so it is advised
299    /// to not call this method in a loop.
300    pub fn add_entities(
301        self,
302        entities: impl IntoIterator<Item = Entity>,
303        schema: Option<&Schema>,
304    ) -> Result<Self, EntitiesError> {
305        Ok(Self(
306            self.0.add_entities(
307                entities.into_iter().map(|e| e.0),
308                schema
309                    .map(|s| cedar_policy_validator::CoreSchema::new(&s.0))
310                    .as_ref(),
311                entities::TCComputation::ComputeNow,
312                Extensions::all_available(),
313            )?,
314        ))
315    }
316
317    /// Parse an entities JSON file (in [&str] form) and add them into this
318    /// [`Entities`] structure, re-computing the transitive closure
319    ///
320    /// If a `schema` is provided, this will inform the parsing: for instance, it
321    /// will allow `__entity` and `__extn` escapes to be implicit.
322    /// This method will also ensure that the added entities fully conform to the
323    /// schema -- for instance, it will error if attributes have the wrong types
324    /// (e.g., string instead of integer), or if required attributes are missing
325    /// or superfluous attributes are provided.
326    /// (This method will not add action entities from the `schema`.)
327    ///
328    /// Re-computing the transitive closure can be expensive, so it is advised
329    /// to not call this method in a loop.
330    pub fn add_entities_from_json_str(
331        self,
332        json: &str,
333        schema: Option<&Schema>,
334    ) -> Result<Self, EntitiesError> {
335        let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
336        let eparser = entities::EntityJsonParser::new(
337            schema.as_ref(),
338            Extensions::all_available(),
339            entities::TCComputation::ComputeNow,
340        );
341        let new_entities = eparser.iter_from_json_str(json)?;
342        Ok(Self(self.0.add_entities(
343            new_entities,
344            schema.as_ref(),
345            entities::TCComputation::ComputeNow,
346            Extensions::all_available(),
347        )?))
348    }
349
350    /// Parse an entities JSON file (in [`serde_json::Value`] form) and add them
351    /// into this [`Entities`] structure, re-computing the transitive closure
352    ///
353    /// If a `schema` is provided, this will inform the parsing: for instance, it
354    /// will allow `__entity` and `__extn` escapes to be implicit.
355    /// This method will also ensure that the added entities fully conform to the
356    /// schema -- for instance, it will error if attributes have the wrong types
357    /// (e.g., string instead of integer), or if required attributes are missing
358    /// or superfluous attributes are provided.
359    /// (This method will not add action entities from the `schema`.)
360    ///
361    /// Re-computing the transitive closure can be expensive, so it is advised
362    /// to not call this method in a loop.
363    pub fn add_entities_from_json_value(
364        self,
365        json: serde_json::Value,
366        schema: Option<&Schema>,
367    ) -> Result<Self, EntitiesError> {
368        let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
369        let eparser = entities::EntityJsonParser::new(
370            schema.as_ref(),
371            Extensions::all_available(),
372            entities::TCComputation::ComputeNow,
373        );
374        let new_entities = eparser.iter_from_json_value(json)?;
375        Ok(Self(self.0.add_entities(
376            new_entities,
377            schema.as_ref(),
378            entities::TCComputation::ComputeNow,
379            Extensions::all_available(),
380        )?))
381    }
382
383    /// Parse an entities JSON file (in [`std::io::Read`] form) and add them
384    /// into this [`Entities`] structure, re-computing the transitive closure
385    ///
386    /// If a `schema` is provided, this will inform the parsing: for instance, it
387    /// will allow `__entity` and `__extn` escapes to be implicit.
388    /// This method will also ensure that the added entities fully conform to the
389    /// schema -- for instance, it will error if attributes have the wrong types
390    /// (e.g., string instead of integer), or if required attributes are missing
391    /// or superfluous attributes are provided.
392    /// (This method will not add action entities from the `schema`.)
393    ///
394    /// Re-computing the transitive closure can be expensive, so it is advised
395    /// to not call this method in a loop.
396    pub fn add_entities_from_json_file(
397        self,
398        json: impl std::io::Read,
399        schema: Option<&Schema>,
400    ) -> Result<Self, EntitiesError> {
401        let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
402        let eparser = entities::EntityJsonParser::new(
403            schema.as_ref(),
404            Extensions::all_available(),
405            entities::TCComputation::ComputeNow,
406        );
407        let new_entities = eparser.iter_from_json_file(json)?;
408        Ok(Self(self.0.add_entities(
409            new_entities,
410            schema.as_ref(),
411            entities::TCComputation::ComputeNow,
412            Extensions::all_available(),
413        )?))
414    }
415
416    /// Parse an entities JSON file (in `&str` form) into an `Entities` object
417    ///
418    /// `schema` represents a source of `Action` entities, which will be added
419    /// to the entities parsed from JSON.
420    /// (If any `Action` entities are present in the JSON, and a `schema` is
421    /// also provided, each `Action` entity in the JSON must exactly match its
422    /// definition in the schema or an error is returned.)
423    ///
424    /// If a `schema` is present, this will also inform the parsing: for
425    /// instance, it will allow `__entity` and `__extn` escapes to be implicit.
426    ///
427    /// Finally, if a `schema` is present, this function will ensure
428    /// that the produced entities fully conform to the `schema` -- for
429    /// instance, it will error if attributes have the wrong types (e.g., string
430    /// instead of integer), or if required attributes are missing or
431    /// superfluous attributes are provided.
432    ///
433    /// ```
434    /// use std::collections::HashMap;
435    /// use std::str::FromStr;
436    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, EvalResult, Request,PolicySet};
437    /// let data =r#"
438    /// [
439    /// {
440    ///   "uid": {"type":"User","id":"alice"},
441    ///   "attrs": {
442    ///     "age":19,
443    ///     "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
444    ///   },
445    ///   "parents": [{"type":"Group","id":"admin"}]
446    /// },
447    /// {
448    ///   "uid": {"type":"Groupd","id":"admin"},
449    ///   "attrs": {},
450    ///   "parents": []
451    /// }
452    /// ]
453    /// "#;
454    /// let entities = Entities::from_json_str(data, None).unwrap();
455    /// let eid = EntityId::from_str("alice").unwrap();
456    /// let type_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
457    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
458    /// let entity = entities.get(&euid).unwrap();
459    /// assert_eq!(entity.attr("age").unwrap().unwrap(), EvalResult::Long(19));
460    /// let ip = entity.attr("ip_addr").unwrap().unwrap();
461    /// assert_eq!(ip, EvalResult::ExtensionValue("10.0.1.101/32".to_string()));
462    /// ```
463    pub fn from_json_str(
464        json: &str,
465        schema: Option<&Schema>,
466    ) -> Result<Self, entities::EntitiesError> {
467        let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
468        let eparser = entities::EntityJsonParser::new(
469            schema.as_ref(),
470            Extensions::all_available(),
471            entities::TCComputation::ComputeNow,
472        );
473        eparser.from_json_str(json).map(Entities)
474    }
475
476    /// Parse an entities JSON file (in `serde_json::Value` form) into an
477    /// `Entities` object
478    ///
479    /// `schema` represents a source of `Action` entities, which will be added
480    /// to the entities parsed from JSON.
481    /// (If any `Action` entities are present in the JSON, and a `schema` is
482    /// also provided, each `Action` entity in the JSON must exactly match its
483    /// definition in the schema or an error is returned.)
484    ///
485    /// If a `schema` is present, this will also inform the parsing: for
486    /// instance, it will allow `__entity` and `__extn` escapes to be implicit.
487    ///
488    /// Finally, if a `schema` is present, this function will ensure
489    /// that the produced entities fully conform to the `schema` -- for
490    /// instance, it will error if attributes have the wrong types (e.g., string
491    /// instead of integer), or if required attributes are missing or
492    /// superfluous attributes are provided.
493    ///
494    /// ```
495    /// use std::collections::HashMap;
496    /// use std::str::FromStr;
497    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, EvalResult, Request,PolicySet};
498    /// let data =serde_json::json!(
499    /// [
500    /// {
501    ///   "uid": {"type":"User","id":"alice"},
502    ///   "attrs": {
503    ///     "age":19,
504    ///     "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
505    ///   },
506    ///   "parents": [{"type":"Group","id":"admin"}]
507    /// },
508    /// {
509    ///   "uid": {"type":"Groupd","id":"admin"},
510    ///   "attrs": {},
511    ///   "parents": []
512    /// }
513    /// ]
514    /// );
515    /// let entities = Entities::from_json_value(data, None).unwrap();
516    /// ```
517    pub fn from_json_value(
518        json: serde_json::Value,
519        schema: Option<&Schema>,
520    ) -> Result<Self, entities::EntitiesError> {
521        let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
522        let eparser = entities::EntityJsonParser::new(
523            schema.as_ref(),
524            Extensions::all_available(),
525            entities::TCComputation::ComputeNow,
526        );
527        eparser.from_json_value(json).map(Entities)
528    }
529
530    /// Parse an entities JSON file (in `std::io::Read` form) into an `Entities`
531    /// object
532    ///
533    /// `schema` represents a source of `Action` entities, which will be added
534    /// to the entities parsed from JSON.
535    /// (If any `Action` entities are present in the JSON, and a `schema` is
536    /// also provided, each `Action` entity in the JSON must exactly match its
537    /// definition in the schema or an error is returned.)
538    ///
539    /// If a `schema` is present, this will also inform the parsing: for
540    /// instance, it will allow `__entity` and `__extn` escapes to be implicit.
541    ///
542    /// Finally, if a `schema` is present, this function will ensure
543    /// that the produced entities fully conform to the `schema` -- for
544    /// instance, it will error if attributes have the wrong types (e.g., string
545    /// instead of integer), or if required attributes are missing or
546    /// superfluous attributes are provided.
547    pub fn from_json_file(
548        json: impl std::io::Read,
549        schema: Option<&Schema>,
550    ) -> Result<Self, entities::EntitiesError> {
551        let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
552        let eparser = entities::EntityJsonParser::new(
553            schema.as_ref(),
554            Extensions::all_available(),
555            entities::TCComputation::ComputeNow,
556        );
557        eparser.from_json_file(json).map(Entities)
558    }
559
560    /// Is entity `a` an ancestor of entity `b`?
561    /// Same semantics as `b in a` in the Cedar language
562    pub fn is_ancestor_of(&self, a: &EntityUid, b: &EntityUid) -> bool {
563        match self.0.entity(&b.0) {
564            Dereference::Data(b) => b.is_descendant_of(&a.0),
565            _ => a == b, // if b doesn't exist, `b in a` is only true if `b == a`
566        }
567    }
568
569    /// Get an iterator over the ancestors of the given Euid.
570    /// Returns `None` if the given `Euid` does not exist.
571    pub fn ancestors<'a>(
572        &'a self,
573        euid: &EntityUid,
574    ) -> Option<impl Iterator<Item = &'a EntityUid>> {
575        let entity = match self.0.entity(&euid.0) {
576            Dereference::Residual(_) | Dereference::NoSuchEntity => None,
577            Dereference::Data(e) => Some(e),
578        }?;
579        // Invariant: No way to write down the unspecified EntityUid, so no way to have ancestors that are unspecified
580        Some(entity.ancestors().map(EntityUid::ref_cast))
581    }
582
583    /// Dump an `Entities` object into an entities JSON file.
584    ///
585    /// The resulting JSON will be suitable for parsing in via
586    /// `from_json_*`, and will be parse-able even with no `Schema`.
587    ///
588    /// To read an `Entities` object from an entities JSON file, use
589    /// `from_json_file`.
590    pub fn write_to_json(
591        &self,
592        f: impl std::io::Write,
593    ) -> std::result::Result<(), entities::EntitiesError> {
594        self.0.write_to_json(f)
595    }
596}
597
598/// Authorizer object, which provides responses to authorization queries
599#[repr(transparent)]
600#[derive(Debug, RefCast)]
601pub struct Authorizer(authorizer::Authorizer);
602
603impl Default for Authorizer {
604    fn default() -> Self {
605        Self::new()
606    }
607}
608
609impl Authorizer {
610    /// Create a new `Authorizer`
611    ///
612    /// The authorizer uses the `stacker` crate to manage stack size and tries to use a sane default.
613    /// If the default is not right for you, you can try wrapping the authorizer or individual calls
614    /// to `is_authorized` in `stacker::grow`.
615    /// ```
616    /// # use cedar_policy::{Authorizer, Context, Entities, EntityId, EntityTypeName,
617    /// # EntityUid, Request,PolicySet};
618    /// # use std::str::FromStr;
619    /// # // create a request
620    /// # let p_eid = EntityId::from_str("alice").unwrap();
621    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
622    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
623    /// #
624    /// # let a_eid = EntityId::from_str("view").unwrap();
625    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
626    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
627    /// #
628    /// # let r_eid = EntityId::from_str("trip").unwrap();
629    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
630    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
631    /// #
632    /// # let c = Context::empty();
633    /// #
634    /// # let request: Request = Request::new(Some(p), Some(a), Some(r), c, None).unwrap();
635    /// #
636    /// # // create a policy
637    /// # let s = r#"permit(
638    /// #     principal == User::"alice",
639    /// #     action == Action::"view",
640    /// #     resource == Album::"trip"
641    /// #   )when{
642    /// #     principal.ip_addr.isIpv4()
643    /// #   };
644    /// # "#;
645    /// # let policy = PolicySet::from_str(s).expect("policy error");
646    /// # // create entities
647    /// # let e = r#"[
648    /// #     {
649    /// #         "uid": {"type":"User","id":"alice"},
650    /// #         "attrs": {
651    /// #             "age":19,
652    /// #             "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
653    /// #         },
654    /// #         "parents": []
655    /// #     }
656    /// # ]"#;
657    /// # let entities = Entities::from_json_str(e, None).expect("entity error");
658    /// let authorizer = Authorizer::new();
659    /// let r = authorizer.is_authorized(&request, &policy, &entities);
660    /// ```
661    pub fn new() -> Self {
662        Self(authorizer::Authorizer::new())
663    }
664
665    /// Returns an authorization response for `r` with respect to the given
666    /// `PolicySet` and `Entities`.
667    ///
668    /// The language spec and Dafny model give a precise definition of how this
669    /// is computed.
670    /// ```
671    /// use cedar_policy::{Authorizer,Context,Entities,EntityId,EntityTypeName,
672    /// EntityUid, Request,PolicySet};
673    /// use std::str::FromStr;
674    ///
675    /// // create a request
676    /// let p_eid = EntityId::from_str("alice").unwrap();
677    /// let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
678    /// let p = EntityUid::from_type_name_and_id(p_name, p_eid);
679    ///
680    /// let a_eid = EntityId::from_str("view").unwrap();
681    /// let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
682    /// let a = EntityUid::from_type_name_and_id(a_name, a_eid);
683    ///
684    /// let r_eid = EntityId::from_str("trip").unwrap();
685    /// let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
686    /// let r = EntityUid::from_type_name_and_id(r_name, r_eid);
687    ///
688    /// let c = Context::empty();
689    ///
690    /// let request: Request = Request::new(Some(p), Some(a), Some(r), c, None).unwrap();
691    ///
692    /// // create a policy
693    /// let s = r#"
694    /// permit (
695    ///   principal == User::"alice",
696    ///   action == Action::"view",
697    ///   resource == Album::"trip"
698    /// )
699    /// when { principal.ip_addr.isIpv4() };
700    /// "#;
701    /// let policy = PolicySet::from_str(s).expect("policy error");
702
703    /// // create entities
704    /// let e = r#"[
705    ///     {
706    ///         "uid": {"type":"User","id":"alice"},
707    ///         "attrs": {
708    ///             "age":19,
709    ///             "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
710    ///         },
711    ///         "parents": []
712    ///     }
713    /// ]"#;
714    /// let entities = Entities::from_json_str(e, None).expect("entity error");
715    ///
716    /// let authorizer = Authorizer::new();
717    /// let r = authorizer.is_authorized(&request, &policy, &entities);
718    /// println!("{:?}", r);
719    /// ```
720    pub fn is_authorized(&self, r: &Request, p: &PolicySet, e: &Entities) -> Response {
721        self.0.is_authorized(r.0.clone(), &p.ast, &e.0).into()
722    }
723
724    /// A partially evaluated authorization request.
725    /// The Authorizer will attempt to make as much progress as possible in the presence of unknowns.
726    /// If the Authorizer can reach a response, it will return that response.
727    /// Otherwise, it will return a list of residual policies that still need to be evaluated.
728    #[cfg(feature = "partial-eval")]
729    pub fn is_authorized_partial(
730        &self,
731        query: &Request,
732        policy_set: &PolicySet,
733        entities: &Entities,
734    ) -> PartialResponse {
735        let response = self
736            .0
737            .is_authorized_core(query.0.clone(), &policy_set.ast, &entities.0);
738        match response {
739            authorizer::ResponseKind::FullyEvaluated(a) => PartialResponse::Concrete(a.into()),
740            authorizer::ResponseKind::Partial(p) => PartialResponse::Residual(p.into()),
741        }
742    }
743}
744
745/// Authorization response returned from the `Authorizer`
746#[derive(Debug, PartialEq, Eq, Clone)]
747pub struct Response {
748    /// Authorization decision
749    decision: Decision,
750    /// Diagnostics providing more information on how this decision was reached
751    diagnostics: Diagnostics,
752}
753
754/// Authorization response returned from `is_authorized_partial`.
755/// It can either be a full concrete response, or a residual response.
756#[cfg(feature = "partial-eval")]
757#[derive(Debug, PartialEq, Eq, Clone)]
758pub enum PartialResponse {
759    /// A full, concrete response.
760    Concrete(Response),
761    /// A residual response. Determining the concrete response requires further processing.
762    Residual(ResidualResponse),
763}
764
765/// A residual response obtained from `is_authorized_partial`.
766#[cfg(feature = "partial-eval")]
767#[derive(Debug, PartialEq, Eq, Clone)]
768pub struct ResidualResponse {
769    /// Residual policies
770    residuals: PolicySet,
771    /// Diagnostics
772    diagnostics: Diagnostics,
773}
774
775/// Diagnostics providing more information on how a `Decision` was reached
776#[derive(Debug, PartialEq, Eq, Clone)]
777pub struct Diagnostics {
778    /// `PolicyId`s of the policies that contributed to the decision.
779    /// If no policies applied to the request, this set will be empty.
780    reason: HashSet<PolicyId>,
781    /// Errors that occurred during authorization. The errors should be
782    /// treated as unordered, since policies may be evaluated in any order.
783    errors: Vec<AuthorizationError>,
784}
785
786impl From<authorizer::Diagnostics> for Diagnostics {
787    fn from(diagnostics: authorizer::Diagnostics) -> Self {
788        Self {
789            reason: diagnostics.reason.into_iter().map(PolicyId).collect(),
790            errors: diagnostics.errors,
791        }
792    }
793}
794
795impl Diagnostics {
796    /// Get the policies that contributed to the decision
797    /// ```
798    /// # use cedar_policy::{Authorizer, Context, Decision, Entities, EntityId, EntityTypeName,
799    /// # EntityUid, Request,PolicySet};
800    /// # use std::str::FromStr;
801    /// # // create a request
802    /// # let p_eid = EntityId::from_str("alice").unwrap();
803    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
804    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
805    /// #
806    /// # let a_eid = EntityId::from_str("view").unwrap();
807    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
808    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
809    /// #
810    /// # let r_eid = EntityId::from_str("trip").unwrap();
811    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
812    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
813    /// #
814    /// # let c = Context::empty();
815    /// #
816    /// # let request: Request = Request::new(Some(p), Some(a), Some(r), c, None).unwrap();
817    /// #
818    /// # // create a policy
819    /// # let s = r#"permit(
820    /// #     principal == User::"alice",
821    /// #     action == Action::"view",
822    /// #     resource == Album::"trip"
823    /// #   )when{
824    /// #     principal.ip_addr.isIpv4()
825    /// #   };
826    /// # "#;
827    /// # let policy = PolicySet::from_str(s).expect("policy error");
828    /// # // create entities
829    /// # let e = r#"[
830    /// #     {
831    /// #         "uid": {"type":"User","id":"alice"},
832    /// #         "attrs": {
833    /// #             "age":19,
834    /// #             "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
835    /// #         },
836    /// #         "parents": []
837    /// #     }
838    /// # ]"#;
839    /// # let entities = Entities::from_json_str(e, None).expect("entity error");
840    /// let authorizer = Authorizer::new();
841    /// let response = authorizer.is_authorized(&request, &policy, &entities);
842    /// match response.decision() {
843    ///     Decision::Allow => println!("ALLOW"),
844    ///     Decision::Deny => println!("DENY"),
845    /// }
846    /// println!("note: this decision was due to the following policies:");
847    /// for reason in response.diagnostics().reason() {
848    ///     println!("{}", reason);
849    /// }
850    /// ```
851    pub fn reason(&self) -> impl Iterator<Item = &PolicyId> {
852        self.reason.iter()
853    }
854
855    /// Get the errors
856    /// ```
857    /// # use cedar_policy::{Authorizer, Context, Decision, Entities, EntityId, EntityTypeName,
858    /// # EntityUid, Request,PolicySet};
859    /// # use std::str::FromStr;
860    /// # // create a request
861    /// # let p_eid = EntityId::from_str("alice").unwrap();
862    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
863    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
864    /// #
865    /// # let a_eid = EntityId::from_str("view").unwrap();
866    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
867    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
868    /// #
869    /// # let r_eid = EntityId::from_str("trip").unwrap();
870    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
871    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
872    /// #
873    /// # let c = Context::empty();
874    /// #
875    /// # let request: Request = Request::new(Some(p), Some(a), Some(r), c, None).unwrap();
876    /// #
877    /// # // create a policy
878    /// # let s = r#"permit(
879    /// #     principal == User::"alice",
880    /// #     action == Action::"view",
881    /// #     resource == Album::"trip"
882    /// #   )when{
883    /// #     principal.ip_addr.isIpv4()
884    /// #   };
885    /// # "#;
886    /// # let policy = PolicySet::from_str(s).expect("policy error");
887    /// # // create entities
888    /// # let e = r#"[
889    /// #     {
890    /// #         "uid": {"type":"User","id":"alice"},
891    /// #         "attrs": {
892    /// #             "age":19,
893    /// #             "ip_addr":{"__extn":{"fn":"ip", "arg":"10.0.1.101"}}
894    /// #         },
895    /// #         "parents": []
896    /// #     }
897    /// # ]"#;
898    /// # let entities = Entities::from_json_str(e, None).expect("entity error");
899    /// let authorizer = Authorizer::new();
900    /// let response = authorizer.is_authorized(&request, &policy, &entities);
901    /// match response.decision() {
902    ///     Decision::Allow => println!("ALLOW"),
903    ///     Decision::Deny => println!("DENY"),
904    /// }
905    /// for err in response.diagnostics().errors() {
906    ///     println!("{}", err);
907    /// }
908    /// ```
909    pub fn errors(&self) -> impl Iterator<Item = &AuthorizationError> + '_ {
910        self.errors.iter()
911    }
912}
913
914impl Response {
915    /// Create a new `Response`
916    pub fn new(
917        decision: Decision,
918        reason: HashSet<PolicyId>,
919        errors: Vec<AuthorizationError>,
920    ) -> Self {
921        Self {
922            decision,
923            diagnostics: Diagnostics { reason, errors },
924        }
925    }
926
927    /// Get the authorization decision
928    pub fn decision(&self) -> Decision {
929        self.decision
930    }
931
932    /// Get the authorization diagnostics
933    pub fn diagnostics(&self) -> &Diagnostics {
934        &self.diagnostics
935    }
936}
937
938impl From<authorizer::Response> for Response {
939    fn from(a: authorizer::Response) -> Self {
940        Self {
941            decision: a.decision,
942            diagnostics: a.diagnostics.into(),
943        }
944    }
945}
946
947#[cfg(feature = "partial-eval")]
948impl ResidualResponse {
949    /// Create a new `ResidualResponse`
950    pub fn new(
951        residuals: PolicySet,
952        reason: HashSet<PolicyId>,
953        errors: Vec<AuthorizationError>,
954    ) -> Self {
955        Self {
956            residuals,
957            diagnostics: Diagnostics { reason, errors },
958        }
959    }
960
961    /// Get the residual policies needed to reach an authorization decision.
962    pub fn residuals(&self) -> &PolicySet {
963        &self.residuals
964    }
965
966    /// Get the authorization diagnostics
967    pub fn diagnostics(&self) -> &Diagnostics {
968        &self.diagnostics
969    }
970}
971
972#[cfg(feature = "partial-eval")]
973impl From<authorizer::PartialResponse> for ResidualResponse {
974    fn from(p: authorizer::PartialResponse) -> Self {
975        Self {
976            residuals: PolicySet::from_ast(p.residuals),
977            diagnostics: p.diagnostics.into(),
978        }
979    }
980}
981
982/// Used to select how a policy will be validated.
983#[derive(Default, Eq, PartialEq, Copy, Clone, Debug)]
984#[non_exhaustive]
985pub enum ValidationMode {
986    /// Validate that policies do not contain any type errors, and additionally
987    /// have a restricted form which is amenable for analysis.
988    #[default]
989    Strict,
990    #[cfg(feature = "permissive-validate")]
991    /// Validate that policies do not contain any type errors.
992    Permissive,
993    /// Validate using a partial schema. Policies may contain type errors.
994    #[cfg(feature = "partial-validate")]
995    Partial,
996}
997
998impl From<ValidationMode> for cedar_policy_validator::ValidationMode {
999    fn from(mode: ValidationMode) -> Self {
1000        match mode {
1001            ValidationMode::Strict => Self::Strict,
1002            #[cfg(feature = "permissive-validate")]
1003            ValidationMode::Permissive => Self::Permissive,
1004            #[cfg(feature = "partial-validate")]
1005            ValidationMode::Partial => Self::Partial,
1006        }
1007    }
1008}
1009
1010/// Validator object, which provides policy validation and typechecking.
1011#[repr(transparent)]
1012#[derive(Debug, RefCast)]
1013pub struct Validator(cedar_policy_validator::Validator);
1014
1015impl Validator {
1016    /// Construct a new `Validator` to validate policies using the given
1017    /// `Schema`.
1018    pub fn new(schema: Schema) -> Self {
1019        Self(cedar_policy_validator::Validator::new(schema.0))
1020    }
1021
1022    /// Validate all policies in a policy set, collecting all validation errors
1023    /// found into the returned `ValidationResult`. Each error is returned together with the
1024    /// policy id of the policy where the error was found. If a policy id
1025    /// included in the input policy set does not appear in the output iterator, then
1026    /// that policy passed the validator. If the function `validation_passed`
1027    /// returns true, then there were no validation errors found, so all
1028    /// policies in the policy set have passed the validator.
1029    pub fn validate<'a>(
1030        &'a self,
1031        pset: &'a PolicySet,
1032        mode: ValidationMode,
1033    ) -> ValidationResult<'a> {
1034        ValidationResult::from(self.0.validate(&pset.ast, mode.into()))
1035    }
1036}
1037
1038/// Contains all the type information used to construct a `Schema` that can be
1039/// used to validate a policy.
1040#[derive(Debug)]
1041pub struct SchemaFragment(cedar_policy_validator::ValidatorSchemaFragment);
1042
1043impl SchemaFragment {
1044    /// Extract namespaces defined in this `SchemaFragment`. Each namespace
1045    /// entry defines the name of the namespace and the entity types and actions
1046    /// that exist in the namespace.
1047    pub fn namespaces(&self) -> impl Iterator<Item = Option<EntityNamespace>> + '_ {
1048        self.0
1049            .namespaces()
1050            .map(|ns| ns.as_ref().map(|ns| EntityNamespace(ns.clone())))
1051    }
1052
1053    /// Create an `SchemaFragment` from a JSON value (which should be an
1054    /// object of the shape required for Cedar schemas).
1055    pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
1056        Ok(Self(
1057            cedar_policy_validator::SchemaFragment::from_json_value(json)?.try_into()?,
1058        ))
1059    }
1060
1061    /// Create a `SchemaFragment` directly from a file.
1062    pub fn from_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
1063        Ok(Self(
1064            cedar_policy_validator::SchemaFragment::from_file(file)?.try_into()?,
1065        ))
1066    }
1067}
1068
1069impl TryInto<Schema> for SchemaFragment {
1070    type Error = SchemaError;
1071
1072    /// Convert `SchemaFragment` into a `Schema`. To build the `Schema` we
1073    /// need to have all entity types defined, so an error will be returned if
1074    /// any undeclared entity types are referenced in the schema fragment.
1075    fn try_into(self) -> Result<Schema, Self::Error> {
1076        Ok(Schema(
1077            cedar_policy_validator::ValidatorSchema::from_schema_fragments([self.0])?,
1078        ))
1079    }
1080}
1081
1082impl FromStr for SchemaFragment {
1083    type Err = SchemaError;
1084    /// Construct `SchemaFragment` from a string containing a schema formatted
1085    /// in the cedar schema format. This can fail if the string is not valid
1086    /// JSON, or if the JSON structure does not form a valid schema. This
1087    /// function does not check for consistency in the schema (e.g., references
1088    /// to undefined entities) because this is not required until a `Schema` is
1089    /// constructed.
1090    fn from_str(src: &str) -> Result<Self, Self::Err> {
1091        Ok(Self(
1092            serde_json::from_str::<cedar_policy_validator::SchemaFragment>(src)
1093                .map_err(cedar_policy_validator::SchemaError::from)?
1094                .try_into()?,
1095        ))
1096    }
1097}
1098
1099/// Object containing schema information used by the validator.
1100#[repr(transparent)]
1101#[derive(Debug, Clone, RefCast)]
1102pub struct Schema(pub(crate) cedar_policy_validator::ValidatorSchema);
1103
1104impl FromStr for Schema {
1105    type Err = SchemaError;
1106
1107    /// Construct a schema from a string containing a schema formatted in the
1108    /// Cedar schema format. This can fail if it is not possible to parse a
1109    /// schema from the strings, or if errors in values in the schema are
1110    /// uncovered after parsing. For instance, when an entity attribute name is
1111    /// found to not be a valid attribute name according to the Cedar
1112    /// grammar.
1113    fn from_str(schema_src: &str) -> Result<Self, Self::Err> {
1114        Ok(Self(schema_src.parse()?))
1115    }
1116}
1117
1118impl Schema {
1119    /// Create a `Schema` from multiple `SchemaFragment`. The individual
1120    /// fragments may references entity types that are not declared in that
1121    /// fragment, but all referenced entity types must be declared in some
1122    /// fragment.
1123    pub fn from_schema_fragments(
1124        fragments: impl IntoIterator<Item = SchemaFragment>,
1125    ) -> Result<Self, SchemaError> {
1126        Ok(Self(
1127            cedar_policy_validator::ValidatorSchema::from_schema_fragments(
1128                fragments.into_iter().map(|f| f.0),
1129            )?,
1130        ))
1131    }
1132
1133    /// Create a `Schema` from a JSON value (which should be an object of the
1134    /// shape required for Cedar schemas).
1135    pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
1136        Ok(Self(
1137            cedar_policy_validator::ValidatorSchema::from_json_value(
1138                json,
1139                Extensions::all_available(),
1140            )?,
1141        ))
1142    }
1143
1144    /// Create a `Schema` directly from a file.
1145    pub fn from_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
1146        Ok(Self(cedar_policy_validator::ValidatorSchema::from_file(
1147            file,
1148            Extensions::all_available(),
1149        )?))
1150    }
1151
1152    /// Extract from the schema an `Entities` containing the action entities
1153    /// declared in the schema.
1154    pub fn action_entities(&self) -> Result<Entities, EntitiesError> {
1155        Ok(Entities(self.0.action_entities()?))
1156    }
1157}
1158
1159/// Errors encountered during construction of a Validation Schema
1160#[derive(Debug, Error)]
1161pub enum SchemaError {
1162    /// Error thrown by the `serde_json` crate during deserialization
1163    #[error("failed to parse schema: {0}")]
1164    Serde(#[from] serde_json::Error),
1165    /// Errors occurring while computing or enforcing transitive closure on
1166    /// action hierarchy.
1167    #[error("transitive closure computation/enforcement error on action hierarchy: {0}")]
1168    ActionTransitiveClosure(String),
1169    /// Errors occurring while computing or enforcing transitive closure on
1170    /// entity type hierarchy.
1171    #[error("transitive closure computation/enforcement error on entity type hierarchy: {0}")]
1172    EntityTypeTransitiveClosure(String),
1173    /// Error generated when processing a schema file that uses unsupported features
1174    #[error("unsupported feature used in schema: {0}")]
1175    UnsupportedFeature(String),
1176    /// Undeclared entity type(s) used in the `memberOf` field of an entity
1177    /// type, the `appliesTo` fields of an action, or an attribute type in a
1178    /// context or entity attribute record. Entity types in the error message
1179    /// are fully qualified, including any implicit or explicit namespaces.
1180    #[error("undeclared entity type(s): {0:?}")]
1181    UndeclaredEntityTypes(HashSet<String>),
1182    /// Undeclared action(s) used in the `memberOf` field of an action.
1183    #[error("undeclared action(s): {0:?}")]
1184    UndeclaredActions(HashSet<String>),
1185    /// Undeclared common type(s) used in entity or context attributes.
1186    #[error("undeclared common type(s): {0:?}")]
1187    UndeclaredCommonTypes(HashSet<String>),
1188    /// Duplicate specifications for an entity type. Argument is the name of
1189    /// the duplicate entity type.
1190    #[error("duplicate entity type `{0}`")]
1191    DuplicateEntityType(String),
1192    /// Duplicate specifications for an action. Argument is the name of the
1193    /// duplicate action.
1194    #[error("duplicate action `{0}`")]
1195    DuplicateAction(String),
1196    /// Duplicate specification for a reusable type declaration.
1197    #[error("duplicate common type `{0}`")]
1198    DuplicateCommonType(String),
1199    /// Cycle in the schema's action hierarchy.
1200    #[error("cycle in action hierarchy containing `{0}`")]
1201    CycleInActionHierarchy(EntityUid),
1202    /// Parse errors occurring while parsing an entity type.
1203    #[error("parse error in entity type: {0}")]
1204    ParseEntityType(ParseErrors),
1205    /// Parse errors occurring while parsing a namespace identifier.
1206    #[error("parse error in namespace identifier: {0}")]
1207    ParseNamespace(ParseErrors),
1208    /// Parse errors occurring while parsing an extension type.
1209    #[error("parse error in extension type: {0}")]
1210    ParseExtensionType(ParseErrors),
1211    /// Parse errors occurring while parsing the name of a reusable
1212    /// declared type.
1213    #[error("parse error in common type identifier: {0}")]
1214    ParseCommonType(ParseErrors),
1215    /// The schema file included an entity type `Action` in the entity type
1216    /// list. The `Action` entity type is always implicitly declared, and it
1217    /// cannot currently have attributes or be in any groups, so there is no
1218    /// purposes in adding an explicit entry.
1219    #[error("entity type `Action` declared in `entityTypes` list")]
1220    ActionEntityTypeDeclared,
1221    /// `context` or `shape` fields are not records
1222    #[error("{0} is declared with a type other than `Record`")]
1223    ContextOrShapeNotRecord(ContextOrShape),
1224    /// An action entity (transitively) has an attribute that is an empty set.
1225    /// The validator cannot assign a type to an empty set.
1226    /// This error variant should only be used when `PermitAttributes` is enabled.
1227    #[error("action `{0}` has an attribute that is an empty set")]
1228    ActionAttributesContainEmptySet(EntityUid),
1229    /// An action entity (transitively) has an attribute of unsupported type (`ExprEscape`, `EntityEscape` or `ExtnEscape`).
1230    /// This error variant should only be used when `PermitAttributes` is enabled.
1231    #[error("action `{0}` has an attribute with unsupported JSON representation: {1}")]
1232    UnsupportedActionAttribute(EntityUid, String),
1233    /// Error when evaluating an action attribute
1234    #[error(transparent)]
1235    ActionAttrEval(EntityAttrEvaluationError),
1236    /// Error thrown when the schema contains the `__expr` escape.
1237    /// Support for this escape form has been dropped.
1238    #[error("schema contained the non-supported `__expr` escape.")]
1239    ExprEscapeUsed,
1240}
1241
1242/// Error when evaluating an entity attribute
1243#[derive(Debug, Error)]
1244#[error("in attribute `{attr}` of `{uid}`: {err}")]
1245pub struct EntityAttrEvaluationError {
1246    /// Action that had the attribute with the error
1247    pub uid: EntityUid,
1248    /// Attribute that had the error
1249    pub attr: SmolStr,
1250    /// Underlying evaluation error
1251    pub err: EvaluationError,
1252}
1253
1254impl From<ast::EntityAttrEvaluationError> for EntityAttrEvaluationError {
1255    fn from(err: ast::EntityAttrEvaluationError) -> Self {
1256        Self {
1257            uid: EntityUid(err.uid),
1258            attr: err.attr,
1259            err: err.err,
1260        }
1261    }
1262}
1263
1264/// Describes in what action context or entity type shape a schema parsing error
1265/// occurred.
1266#[derive(Debug)]
1267pub enum ContextOrShape {
1268    /// An error occurred when parsing the context for the action with this
1269    /// `EntityUid`.
1270    ActionContext(EntityUid),
1271    /// An error occurred when parsing the shape for the entity type with this
1272    /// `EntityTypeName`.
1273    EntityTypeShape(EntityTypeName),
1274}
1275
1276impl std::fmt::Display for ContextOrShape {
1277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1278        match self {
1279            Self::ActionContext(action) => write!(f, "Context for action {action}"),
1280            Self::EntityTypeShape(entity_type) => {
1281                write!(f, "Shape for entity type {entity_type}")
1282            }
1283        }
1284    }
1285}
1286
1287impl From<cedar_policy_validator::ContextOrShape> for ContextOrShape {
1288    fn from(value: cedar_policy_validator::ContextOrShape) -> Self {
1289        match value {
1290            cedar_policy_validator::ContextOrShape::ActionContext(euid) => {
1291                Self::ActionContext(EntityUid(euid))
1292            }
1293            cedar_policy_validator::ContextOrShape::EntityTypeShape(name) => {
1294                Self::EntityTypeShape(EntityTypeName(name))
1295            }
1296        }
1297    }
1298}
1299
1300#[doc(hidden)]
1301impl From<cedar_policy_validator::SchemaError> for SchemaError {
1302    fn from(value: cedar_policy_validator::SchemaError) -> Self {
1303        match value {
1304            cedar_policy_validator::SchemaError::Serde(e) => Self::Serde(e),
1305            cedar_policy_validator::SchemaError::ActionTransitiveClosure(e) => {
1306                Self::ActionTransitiveClosure(e.to_string())
1307            }
1308            cedar_policy_validator::SchemaError::EntityTypeTransitiveClosure(e) => {
1309                Self::EntityTypeTransitiveClosure(e.to_string())
1310            }
1311            cedar_policy_validator::SchemaError::UnsupportedFeature(e) => {
1312                Self::UnsupportedFeature(e.to_string())
1313            }
1314            cedar_policy_validator::SchemaError::UndeclaredEntityTypes(e) => {
1315                Self::UndeclaredEntityTypes(e)
1316            }
1317            cedar_policy_validator::SchemaError::UndeclaredActions(e) => Self::UndeclaredActions(e),
1318            cedar_policy_validator::SchemaError::UndeclaredCommonTypes(c) => {
1319                Self::UndeclaredCommonTypes(c)
1320            }
1321            cedar_policy_validator::SchemaError::DuplicateEntityType(e) => {
1322                Self::DuplicateEntityType(e)
1323            }
1324            cedar_policy_validator::SchemaError::DuplicateAction(e) => Self::DuplicateAction(e),
1325            cedar_policy_validator::SchemaError::DuplicateCommonType(c) => {
1326                Self::DuplicateCommonType(c)
1327            }
1328            cedar_policy_validator::SchemaError::CycleInActionHierarchy(e) => {
1329                Self::CycleInActionHierarchy(EntityUid(e))
1330            }
1331            cedar_policy_validator::SchemaError::ParseEntityType(e) => Self::ParseEntityType(e),
1332            cedar_policy_validator::SchemaError::ParseNamespace(e) => Self::ParseNamespace(e),
1333            cedar_policy_validator::SchemaError::ParseCommonType(e) => Self::ParseCommonType(e),
1334            cedar_policy_validator::SchemaError::ParseExtensionType(e) => {
1335                Self::ParseExtensionType(e)
1336            }
1337            cedar_policy_validator::SchemaError::ActionEntityTypeDeclared => {
1338                Self::ActionEntityTypeDeclared
1339            }
1340            cedar_policy_validator::SchemaError::ContextOrShapeNotRecord(context_or_shape) => {
1341                Self::ContextOrShapeNotRecord(context_or_shape.into())
1342            }
1343            cedar_policy_validator::SchemaError::ActionAttributesContainEmptySet(uid) => {
1344                Self::ActionAttributesContainEmptySet(EntityUid(uid))
1345            }
1346            cedar_policy_validator::SchemaError::UnsupportedActionAttribute(uid, escape_type) => {
1347                Self::UnsupportedActionAttribute(EntityUid(uid), escape_type)
1348            }
1349            cedar_policy_validator::SchemaError::ActionAttrEval(err) => {
1350                Self::ActionAttrEval(err.into())
1351            }
1352            cedar_policy_validator::SchemaError::ExprEscapeUsed => Self::ExprEscapeUsed,
1353        }
1354    }
1355}
1356
1357/// Contains the result of policy validation. The result includes the list of
1358/// issues found by validation and whether validation succeeds or fails.
1359/// Validation succeeds if there are no fatal errors. There may still be
1360/// non-fatal warnings present when validation passes.
1361#[derive(Debug)]
1362pub struct ValidationResult<'a> {
1363    validation_errors: Vec<ValidationError<'a>>,
1364    validation_warnings: Vec<ValidationWarning<'a>>,
1365}
1366
1367impl<'a> ValidationResult<'a> {
1368    /// True when validation passes. There are no errors, but there may be
1369    /// non-fatal warnings. Use [`ValidationResult::validation_passed_without_warnings`]
1370    /// to check that there are also no warnings.
1371    pub fn validation_passed(&self) -> bool {
1372        self.validation_errors.is_empty()
1373    }
1374
1375    /// True when validation passes (i.e., there are no errors) and there are
1376    /// additionally no non-fatal warnings.
1377    pub fn validation_passed_without_warnings(&self) -> bool {
1378        self.validation_errors.is_empty() && self.validation_warnings.is_empty()
1379    }
1380
1381    /// Get an iterator over the errors found by the validator.
1382    pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError<'a>> {
1383        self.validation_errors.iter()
1384    }
1385
1386    /// Get an iterator over the warnings found by the validator.
1387    pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning<'a>> {
1388        self.validation_warnings.iter()
1389    }
1390}
1391
1392impl<'a> From<cedar_policy_validator::ValidationResult<'a>> for ValidationResult<'a> {
1393    fn from(r: cedar_policy_validator::ValidationResult<'a>) -> Self {
1394        let (errors, warnings) = r.into_errors_and_warnings();
1395        Self {
1396            validation_errors: errors.map(ValidationError::from).collect(),
1397            validation_warnings: warnings.map(ValidationWarning::from).collect(),
1398        }
1399    }
1400}
1401
1402/// An error generated by the validator when it finds a potential problem in a
1403/// policy. The error contains a enumeration that specifies the kind of problem,
1404/// and provides details specific to that kind of problem. The error also records
1405/// where the problem was encountered.
1406#[derive(Debug, Error)]
1407#[error("validation error on `{}`: {}", self.location, self.error_kind())]
1408pub struct ValidationError<'a> {
1409    location: SourceLocation<'a>,
1410    error_kind: ValidationErrorKind,
1411}
1412
1413impl<'a> ValidationError<'a> {
1414    /// Extract details about the exact issue detected by the validator.
1415    pub fn error_kind(&self) -> &ValidationErrorKind {
1416        &self.error_kind
1417    }
1418
1419    /// Extract the location where the validator found the issue.
1420    pub fn location(&self) -> &SourceLocation<'a> {
1421        &self.location
1422    }
1423}
1424
1425impl<'a> From<cedar_policy_validator::ValidationError<'a>> for ValidationError<'a> {
1426    fn from(err: cedar_policy_validator::ValidationError<'a>) -> Self {
1427        let (location, error_kind) = err.into_location_and_error_kind();
1428        Self {
1429            location: SourceLocation::from(location),
1430            error_kind,
1431        }
1432    }
1433}
1434
1435/// Represents a location in Cedar policy source.
1436#[derive(Debug, Clone, Eq, PartialEq)]
1437pub struct SourceLocation<'a> {
1438    policy_id: &'a PolicyId,
1439    source_range: Option<SourceInfo>,
1440}
1441
1442impl<'a> SourceLocation<'a> {
1443    /// Get the `PolicyId` for the policy at this source location.
1444    pub fn policy_id(&self) -> &'a PolicyId {
1445        self.policy_id
1446    }
1447
1448    /// Get the start of the location. Returns `None` if this location does not
1449    /// have a range.
1450    pub fn range_start(&self) -> Option<usize> {
1451        self.source_range.as_ref().map(SourceInfo::range_start)
1452    }
1453
1454    /// Get the end of the location. Returns `None` if this location does not
1455    /// have a range.
1456    pub fn range_end(&self) -> Option<usize> {
1457        self.source_range.as_ref().map(SourceInfo::range_end)
1458    }
1459}
1460
1461impl<'a> std::fmt::Display for SourceLocation<'a> {
1462    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1463        write!(f, "policy `{}`", self.policy_id)?;
1464        if let Some(source_range) = &self.source_range {
1465            write!(
1466                f,
1467                " at offset {}-{}",
1468                source_range.range_start(),
1469                source_range.range_end()
1470            )?;
1471        }
1472        Ok(())
1473    }
1474}
1475
1476impl<'a> From<cedar_policy_validator::SourceLocation<'a>> for SourceLocation<'a> {
1477    fn from(loc: cedar_policy_validator::SourceLocation<'a>) -> SourceLocation<'a> {
1478        let policy_id: &'a PolicyId = PolicyId::ref_cast(loc.policy_id());
1479        let source_range = loc.into_source_info();
1480        Self {
1481            policy_id,
1482            source_range,
1483        }
1484    }
1485}
1486
1487/// Scan a set of policies for potentially confusing/obfuscating text. These
1488/// checks are also provided through [`Validator::validate`] which provides more
1489/// comprehensive error detection, but this function can be used to check for
1490/// confusable strings without defining a schema.
1491pub fn confusable_string_checker<'a>(
1492    templates: impl Iterator<Item = &'a Template>,
1493) -> impl Iterator<Item = ValidationWarning<'a>> {
1494    cedar_policy_validator::confusable_string_checks(templates.map(|t| &t.ast))
1495        .map(std::convert::Into::into)
1496}
1497
1498#[derive(Debug, Error)]
1499#[error("validation warning on `{}`: {}", .location, .kind)]
1500/// Warnings found in Cedar policies
1501pub struct ValidationWarning<'a> {
1502    location: SourceLocation<'a>,
1503    kind: ValidationWarningKind,
1504}
1505
1506impl<'a> ValidationWarning<'a> {
1507    /// Extract details about the exact issue detected by the validator.
1508    pub fn warning_kind(&self) -> &ValidationWarningKind {
1509        &self.kind
1510    }
1511
1512    /// Extract the location where the validator found the issue.
1513    pub fn location(&self) -> &SourceLocation<'a> {
1514        &self.location
1515    }
1516}
1517
1518#[doc(hidden)]
1519impl<'a> From<cedar_policy_validator::ValidationWarning<'a>> for ValidationWarning<'a> {
1520    fn from(w: cedar_policy_validator::ValidationWarning<'a>) -> Self {
1521        let (loc, kind) = w.to_kind_and_location();
1522        ValidationWarning {
1523            location: loc.into(),
1524            kind,
1525        }
1526    }
1527}
1528
1529/// unique identifier portion of the `EntityUid` type
1530#[repr(transparent)]
1531#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
1532pub struct EntityId(ast::Eid);
1533
1534impl FromStr for EntityId {
1535    type Err = Infallible;
1536    fn from_str(eid_str: &str) -> Result<Self, Self::Err> {
1537        Ok(Self(ast::Eid::new(eid_str)))
1538    }
1539}
1540
1541impl AsRef<str> for EntityId {
1542    fn as_ref(&self) -> &str {
1543        self.0.as_ref()
1544    }
1545}
1546
1547// Note that this Display formatter will format the EntityId as it would be expected
1548// in the EntityUid string form. For instance, the `"alice"` in `User::"alice"`.
1549// This means it adds quotes and potentially performs some escaping.
1550impl std::fmt::Display for EntityId {
1551    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1552        write!(f, "{}", self.0)
1553    }
1554}
1555
1556/// Represents a concatenation of Namespaces and `TypeName`
1557#[repr(transparent)]
1558#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
1559pub struct EntityTypeName(ast::Name);
1560
1561impl EntityTypeName {
1562    /// Get the basename of the `EntityTypeName` (ie, with namespaces stripped).
1563    /// ```
1564    /// use cedar_policy::EntityTypeName;
1565    /// use std::str::FromStr;
1566    /// let type_name = EntityTypeName::from_str("MySpace::User").unwrap();
1567    /// assert_eq!(type_name.basename(), "User");
1568    /// ```
1569    pub fn basename(&self) -> &str {
1570        self.0.basename().as_ref()
1571    }
1572
1573    /// Get the namespace of the `EntityTypeName`, as components
1574    /// ```
1575    /// use cedar_policy::EntityTypeName;
1576    /// use std::str::FromStr;
1577    /// let type_name = EntityTypeName::from_str("Namespace::MySpace::User").unwrap();
1578    /// let mut components = type_name.namespace_components();
1579    /// assert_eq!(components.next(), Some("Namespace"));
1580    /// assert_eq!(components.next(), Some("MySpace"));
1581    /// assert_eq!(components.next(), None);
1582    /// ```
1583    pub fn namespace_components(&self) -> impl Iterator<Item = &str> {
1584        self.0.namespace_components().map(AsRef::as_ref)
1585    }
1586
1587    /// Get the full namespace of the `EntityTypeName`, as a single string.
1588    /// ```
1589    /// use cedar_policy::EntityTypeName;
1590    /// use std::str::FromStr;
1591    /// let type_name = EntityTypeName::from_str("Namespace::MySpace::User").unwrap();
1592    /// let components = type_name.namespace();
1593    /// assert_eq!(components,"Namespace::MySpace");
1594    /// ```
1595    pub fn namespace(&self) -> String {
1596        self.0.namespace()
1597    }
1598}
1599
1600// This FromStr implementation requires the _normalized_ representation of the
1601// type name. See https://github.com/cedar-policy/rfcs/pull/9/.
1602impl FromStr for EntityTypeName {
1603    type Err = ParseErrors;
1604
1605    fn from_str(namespace_type_str: &str) -> Result<Self, Self::Err> {
1606        ast::Name::from_normalized_str(namespace_type_str).map(EntityTypeName)
1607    }
1608}
1609
1610impl std::fmt::Display for EntityTypeName {
1611    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1612        write!(f, "{}", self.0)
1613    }
1614}
1615
1616/// Represents a namespace
1617#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
1618pub struct EntityNamespace(ast::Name);
1619
1620// This FromStr implementation requires the _normalized_ representation of the
1621// namespace. See https://github.com/cedar-policy/rfcs/pull/9/.
1622impl FromStr for EntityNamespace {
1623    type Err = ParseErrors;
1624
1625    fn from_str(namespace_str: &str) -> Result<Self, Self::Err> {
1626        ast::Name::from_normalized_str(namespace_str).map(EntityNamespace)
1627    }
1628}
1629
1630impl std::fmt::Display for EntityNamespace {
1631    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1632        write!(f, "{}", self.0)
1633    }
1634}
1635
1636/// Unique Id for an entity, such as `User::"alice"`
1637// INVARIANT: this can never be an `ast::EntityType::Unspecified`
1638#[repr(transparent)]
1639#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
1640pub struct EntityUid(ast::EntityUID);
1641
1642impl EntityUid {
1643    /// Returns the portion of the Euid that represents namespace and entity type
1644    /// ```
1645    /// use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
1646    /// use std::str::FromStr;
1647    /// let json_data = serde_json::json!({ "__entity": { "type": "User", "id": "alice" } });
1648    /// let euid = EntityUid::from_json(json_data).unwrap();
1649    /// assert_eq!(euid.type_name(), &EntityTypeName::from_str("User").unwrap());
1650    /// ```
1651    pub fn type_name(&self) -> &EntityTypeName {
1652        // PANIC SAFETY by invariant on struct
1653        #[allow(clippy::panic)]
1654        match self.0.entity_type() {
1655            ast::EntityType::Unspecified => panic!("Impossible to have an unspecified entity"),
1656            ast::EntityType::Specified(name) => EntityTypeName::ref_cast(name),
1657        }
1658    }
1659
1660    /// Returns the id portion of the Euid
1661    /// ```
1662    /// use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
1663    /// use std::str::FromStr;
1664    /// let json_data = serde_json::json!({ "__entity": { "type": "User", "id": "alice" } });
1665    /// let euid = EntityUid::from_json(json_data).unwrap();
1666    /// assert_eq!(euid.id(), &EntityId::from_str("alice").unwrap());
1667    /// ```
1668    pub fn id(&self) -> &EntityId {
1669        EntityId::ref_cast(self.0.eid())
1670    }
1671
1672    /// Creates `EntityUid` from `EntityTypeName` and `EntityId`
1673    ///```
1674    /// use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
1675    /// use std::str::FromStr;
1676    /// let eid = EntityId::from_str("alice").unwrap();
1677    /// let type_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
1678    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
1679    /// assert_eq!(euid.type_name(), &EntityTypeName::from_str("User").unwrap());
1680    /// assert_eq!(euid.id(), &EntityId::from_str("alice").unwrap());
1681    ///
1682    /// ```
1683    pub fn from_type_name_and_id(name: EntityTypeName, id: EntityId) -> Self {
1684        // INVARIANT: `from_components` always constructs a Concrete id
1685        Self(ast::EntityUID::from_components(name.0, id.0))
1686    }
1687
1688    /// Creates `EntityUid` from a JSON value, which should have
1689    /// either the implicit or explicit `__entity` form.
1690    /// ```
1691    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
1692    /// # use std::str::FromStr;
1693    /// let json_data = serde_json::json!({ "__entity": { "type": "User", "id": "123abc" } });
1694    /// let euid = EntityUid::from_json(json_data).unwrap();
1695    /// assert_eq!(euid.type_name(), &EntityTypeName::from_str("User").unwrap());
1696    /// ```
1697    pub fn from_json(json: serde_json::Value) -> Result<Self, impl std::error::Error> {
1698        let parsed: entities::EntityUidJson = serde_json::from_value(json)?;
1699        // INVARIANT: There is no way to write down the unspecified entityuid
1700        Ok::<Self, entities::JsonDeserializationError>(Self(
1701            parsed.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
1702        ))
1703    }
1704
1705    /// Testing utility for creating `EntityUids` a bit easier
1706    #[cfg(test)]
1707    pub(crate) fn from_strs(typename: &str, id: &str) -> Self {
1708        Self::from_type_name_and_id(
1709            EntityTypeName::from_str(typename).unwrap(),
1710            EntityId::from_str(id).unwrap(),
1711        )
1712    }
1713}
1714
1715impl FromStr for EntityUid {
1716    type Err = ParseErrors;
1717
1718    /// Parse an [`EntityUid`].
1719    ///
1720    /// An [`EntityUid`] consists of an [`EntityTypeName`] followed by a quoted [`EntityId`].
1721    /// The two are joined by a `::`.
1722    /// For the formal grammar, see <https://docs.cedarpolicy.com/policies/syntax-grammar.html#entity>
1723    ///
1724    /// Examples:
1725    /// ```
1726    ///  use cedar_policy::EntityUid;
1727    ///  let euid: EntityUid = r#"Foo::Bar::"george""#.parse().unwrap();
1728    ///  // Get the type of this euid (`Foo::Bar`)
1729    ///  euid.type_name();
1730    ///  // Or the id
1731    ///  euid.id();
1732    /// ```
1733    ///
1734    /// This [`FromStr`] implementation requires the _normalized_ representation of the
1735    /// UID. See <https://github.com/cedar-policy/rfcs/pull/9/>.
1736    ///
1737    /// A note on safety:
1738    ///
1739    /// __DO NOT__ create [`EntityUid`]'s via string concatenation.
1740    /// If you have separate components of an [`EntityUid`], use [`EntityUid::from_type_name_and_id`]
1741    fn from_str(uid_str: &str) -> Result<Self, Self::Err> {
1742        // INVARIANT there is no way to write down the unspecified entity
1743        ast::EntityUID::from_normalized_str(uid_str).map(EntityUid)
1744    }
1745}
1746
1747impl std::fmt::Display for EntityUid {
1748    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1749        write!(f, "{}", self.0)
1750    }
1751}
1752
1753/// Potential errors when adding to a `PolicySet`.
1754#[derive(Error, Debug)]
1755#[non_exhaustive]
1756pub enum PolicySetError {
1757    /// There was a duplicate [`PolicyId`] encountered in either the set of
1758    /// templates or the set of policies.
1759    #[error("duplicate template or policy id `{id}`")]
1760    AlreadyDefined {
1761        /// [`PolicyId`] that was duplicate
1762        id: PolicyId,
1763    },
1764    /// Error when linking a template
1765    #[error("unable to link template: {0}")]
1766    LinkingError(#[from] ast::LinkingError),
1767    /// Expected a static policy, but a template-linked policy was provided
1768    #[error("expected a static policy, but a template-linked policy was provided")]
1769    ExpectedStatic,
1770    /// Expected a template, but a static policy was provided.
1771    #[error("expected a template, but a static policy was provided")]
1772    ExpectedTemplate,
1773    /// Error when removing a static policy
1774    #[error("unable to remove static policy `{0}` because it does not exist")]
1775    PolicyNonexistentError(PolicyId),
1776    /// Error when removing a template that doesn't exist
1777    #[error("unable to remove policy template `{0}` because it does not exist")]
1778    TemplateNonexistentError(PolicyId),
1779    /// Error when removing a template with active links
1780    #[error("unable to remove policy template `{0}` because it has active links")]
1781    RemoveTemplateWithActiveLinksError(PolicyId),
1782    /// Error when removing a template that is not a template
1783    #[error("unable to remove policy template `{0}` because it is not a template")]
1784    RemoveTemplateNotTemplateError(PolicyId),
1785    /// Error when unlinking a template
1786    #[error("unable to unlink policy template `{0}` because it does not exist")]
1787    LinkNonexistentError(PolicyId),
1788    /// Error when removing a link that is not a link
1789    #[error("unable to unlink `{0}` because it is not a link")]
1790    UnlinkLinkNotLinkError(PolicyId),
1791}
1792
1793impl From<ast::PolicySetError> for PolicySetError {
1794    fn from(e: ast::PolicySetError) -> Self {
1795        match e {
1796            ast::PolicySetError::Occupied { id } => Self::AlreadyDefined { id: PolicyId(id) },
1797        }
1798    }
1799}
1800
1801impl From<ast::UnexpectedSlotError> for PolicySetError {
1802    fn from(_: ast::UnexpectedSlotError) -> Self {
1803        Self::ExpectedStatic
1804    }
1805}
1806
1807/// Represents a set of `Policy`s
1808#[derive(Debug, Clone, Default)]
1809pub struct PolicySet {
1810    /// AST representation. Technically partially redundant with the other fields.
1811    /// Internally, we ensure that the duplicated information remains consistent.
1812    pub(crate) ast: ast::PolicySet,
1813    /// Policies in the set (this includes both static policies and template linked-policies)
1814    policies: HashMap<PolicyId, Policy>,
1815    /// Templates in the set
1816    templates: HashMap<PolicyId, Template>,
1817}
1818
1819impl PartialEq for PolicySet {
1820    fn eq(&self, other: &Self) -> bool {
1821        // eq is based on just the `ast`
1822        self.ast.eq(&other.ast)
1823    }
1824}
1825impl Eq for PolicySet {}
1826
1827impl FromStr for PolicySet {
1828    type Err = ParseErrors;
1829
1830    /// Create a policy set from multiple statements.
1831    ///
1832    /// Policy ids will default to "policy*" with numbers from 0.
1833    /// If you load more policies, do not use the default id, or there will be conflicts.
1834    ///
1835    /// See [`Policy`] for more.
1836    fn from_str(policies: &str) -> Result<Self, Self::Err> {
1837        let (texts, pset) = parser::parse_policyset_and_also_return_policy_text(policies)?;
1838        // 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`.
1839        #[allow(clippy::expect_used)]
1840        let policies = pset.policies().map(|p|
1841            (
1842                PolicyId(p.id().clone()),
1843                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() }
1844            )
1845        ).collect();
1846        // PANIC SAFETY: By the same invariant, every `PolicyId` in `pset.templates()` also occurs as a key in `text`.
1847        #[allow(clippy::expect_used)]
1848        let templates = pset.templates().map(|t|
1849            (
1850                PolicyId(t.id().clone()),
1851                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() }
1852            )
1853        ).collect();
1854        Ok(Self {
1855            ast: pset,
1856            policies,
1857            templates,
1858        })
1859    }
1860}
1861
1862impl PolicySet {
1863    /// Create a fresh empty `PolicySet`
1864    pub fn new() -> Self {
1865        Self {
1866            ast: ast::PolicySet::new(),
1867            policies: HashMap::new(),
1868            templates: HashMap::new(),
1869        }
1870    }
1871
1872    /// Create a `PolicySet` from the given policies
1873    pub fn from_policies(
1874        policies: impl IntoIterator<Item = Policy>,
1875    ) -> Result<Self, PolicySetError> {
1876        let mut set = Self::new();
1877        for policy in policies {
1878            set.add(policy)?;
1879        }
1880        Ok(set)
1881    }
1882
1883    /// Add an static policy to the `PolicySet`. To add a template instance, use
1884    /// `link` instead. This function will return an error (and not modify
1885    /// the `PolicySet`) if a template-linked policy is passed in.
1886    pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
1887        if policy.is_static() {
1888            let id = PolicyId(policy.ast.id().clone());
1889            self.ast.add(policy.ast.clone())?;
1890            self.policies.insert(id, policy);
1891            Ok(())
1892        } else {
1893            Err(PolicySetError::ExpectedStatic)
1894        }
1895    }
1896
1897    /// Remove a static `Policy` from the `PolicySet`.
1898    ///
1899    /// This will error if the policy is not a static policy.
1900    pub fn remove_static(&mut self, policy_id: PolicyId) -> Result<Policy, PolicySetError> {
1901        let Some(policy) = self.policies.remove(&policy_id) else {
1902            return Err(PolicySetError::PolicyNonexistentError(policy_id));
1903        };
1904        match self
1905            .ast
1906            .remove_static(&ast::PolicyID::from_string(policy_id.to_string()))
1907        {
1908            Ok(_) => Ok(policy),
1909            Err(_) => {
1910                //Restore self.policies
1911                self.policies.insert(policy_id.clone(), policy);
1912                Err(PolicySetError::PolicyNonexistentError(policy_id.clone()))
1913            }
1914        }
1915    }
1916
1917    /// Add a `Template` to the `PolicySet`
1918    pub fn add_template(&mut self, template: Template) -> Result<(), PolicySetError> {
1919        let id = PolicyId(template.ast.id().clone());
1920        self.ast.add_template(template.ast.clone())?;
1921        self.templates.insert(id, template);
1922        Ok(())
1923    }
1924
1925    /// Remove a `Template` from the `PolicySet`.
1926    ///
1927    /// This will error if any policy is linked to the template.
1928    /// This will error if `policy_id` is not a template.
1929    pub fn remove_template(&mut self, template_id: PolicyId) -> Result<Template, PolicySetError> {
1930        let Some(template) = self.templates.remove(&template_id) else {
1931            return Err(PolicySetError::TemplateNonexistentError(template_id));
1932        };
1933        // If self.templates and self.ast disagree, authorization cannot be trusted.
1934        // PANIC SAFETY: We just found the policy in self.templates.
1935        #[allow(clippy::panic)]
1936        match self
1937            .ast
1938            .remove_template(&ast::PolicyID::from_string(template_id.to_string()))
1939        {
1940            Ok(_) => Ok(template),
1941            Err(ast::PolicySetTemplateRemovalError::RemoveTemplateWithLinksError(_)) => {
1942                self.templates.insert(template_id.clone(), template);
1943                Err(PolicySetError::RemoveTemplateWithActiveLinksError(
1944                    template_id,
1945                ))
1946            }
1947            Err(ast::PolicySetTemplateRemovalError::NotTemplateError(_)) => {
1948                self.templates.insert(template_id.clone(), template);
1949                Err(PolicySetError::RemoveTemplateNotTemplateError(template_id))
1950            }
1951            Err(ast::PolicySetTemplateRemovalError::RemovePolicyNoTemplateError(_)) => {
1952                panic!("Found template policy in self.templates but not in self.ast");
1953            }
1954        }
1955    }
1956
1957    /// Get policies linked to a `Template` in the `PolicySet`.
1958    /// If any policy is linked to the template, this will error
1959    pub fn get_linked_policies(
1960        &self,
1961        template_id: PolicyId,
1962    ) -> Result<impl Iterator<Item = &PolicyId>, PolicySetError> {
1963        match self
1964            .ast
1965            .get_linked_policies(&ast::PolicyID::from_string(template_id.to_string()))
1966        {
1967            Ok(v) => Ok(v.map(PolicyId::ref_cast)),
1968            Err(_) => Err(PolicySetError::TemplateNonexistentError(template_id)),
1969        }
1970    }
1971
1972    /// Iterate over all the `Policy`s in the `PolicySet`.
1973    ///
1974    /// This will include both static and template-linked policies.
1975    pub fn policies(&self) -> impl Iterator<Item = &Policy> {
1976        self.policies.values()
1977    }
1978
1979    /// Iterate over the `Template`'s in the `PolicySet`.
1980    pub fn templates(&self) -> impl Iterator<Item = &Template> {
1981        self.templates.values()
1982    }
1983
1984    /// Get a `Template` by its `PolicyId`
1985    pub fn template(&self, id: &PolicyId) -> Option<&Template> {
1986        self.templates.get(id)
1987    }
1988
1989    /// Get a `Policy` by its `PolicyId`
1990    pub fn policy(&self, id: &PolicyId) -> Option<&Policy> {
1991        self.policies.get(id)
1992    }
1993
1994    /// Extract annotation data from a `Policy` by its `PolicyId` and annotation key
1995    pub fn annotation<'a>(&'a self, id: &PolicyId, key: impl AsRef<str>) -> Option<&'a str> {
1996        self.ast
1997            .get(&id.0)?
1998            .annotation(&key.as_ref().parse().ok()?)
1999            .map(smol_str::SmolStr::as_str)
2000    }
2001
2002    /// Extract annotation data from a `Template` by its `PolicyId` and annotation key.
2003    pub fn template_annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<String> {
2004        self.ast
2005            .get_template(&id.0)?
2006            .annotation(&key.as_ref().parse().ok()?)
2007            .map(smol_str::SmolStr::to_string)
2008    }
2009
2010    /// Returns true iff the `PolicySet` is empty
2011    pub fn is_empty(&self) -> bool {
2012        debug_assert_eq!(
2013            self.ast.is_empty(),
2014            self.policies.is_empty() && self.templates.is_empty()
2015        );
2016        self.ast.is_empty()
2017    }
2018
2019    /// Attempt to link a template and add the new template-linked policy to the policy set.
2020    /// If link fails, the `PolicySet` is not modified.
2021    /// Failure can happen for three reasons
2022    ///   1) The map passed in `vals` may not match the slots in the template
2023    ///   2) The `new_id` may conflict w/ a policy that already exists in the set
2024    ///   3) `template_id` does not correspond to a template. Either the id is
2025    ///   not in the policy set, or it is in the policy set but is either a
2026    ///   linked or static policy rather than a template
2027    #[allow(clippy::needless_pass_by_value)]
2028    pub fn link(
2029        &mut self,
2030        template_id: PolicyId,
2031        new_id: PolicyId,
2032        vals: HashMap<SlotId, EntityUid>,
2033    ) -> Result<(), PolicySetError> {
2034        let unwrapped_vals: HashMap<ast::SlotId, ast::EntityUID> = vals
2035            .into_iter()
2036            .map(|(key, value)| (key.into(), value.0))
2037            .collect();
2038
2039        // Try to get the template with the id we're linking from.  We do this
2040        // _before_ calling `self.ast.link` because `link` mutates the policy
2041        // set by creating a new link entry in a hashmap. This happens even when
2042        // trying to link a static policy, which we want to error on here.
2043        let Some(template) = self.templates.get(&template_id) else {
2044            return Err(if self.policies.contains_key(&template_id) {
2045                PolicySetError::ExpectedTemplate
2046            } else {
2047                PolicySetError::LinkingError(ast::LinkingError::NoSuchTemplate {
2048                    id: template_id.0,
2049                })
2050            });
2051        };
2052
2053        let linked_ast = self
2054            .ast
2055            .link(
2056                template_id.0.clone(),
2057                new_id.0.clone(),
2058                unwrapped_vals.clone(),
2059            )
2060            .map_err(PolicySetError::LinkingError)?;
2061
2062        // PANIC SAFETY: `lossless.link()` will not fail after `ast.link()` succeeds
2063        #[allow(clippy::expect_used)]
2064        let linked_lossless = template
2065            .lossless
2066            .clone()
2067            .link(unwrapped_vals.iter().map(|(k, v)| (*k, v)))
2068            // The only error case for `lossless.link()` is a template with
2069            // slots which are not filled by the provided values. `ast.link()`
2070            // will have already errored if there are any unfilled slots in the
2071            // template.
2072            .expect("ast.link() didn't fail above, so this shouldn't fail");
2073        self.policies.insert(
2074            new_id,
2075            Policy {
2076                ast: linked_ast.clone(),
2077                lossless: linked_lossless,
2078            },
2079        );
2080        Ok(())
2081    }
2082
2083    /// Get all the unknown entities from the policy set
2084    #[cfg(feature = "partial-eval")]
2085    pub fn unknown_entities(&self) -> HashSet<EntityUid> {
2086        let mut entity_uids = HashSet::new();
2087        for policy in self.policies.values() {
2088            let ids: Vec<EntityUid> = policy
2089                .ast
2090                .condition()
2091                .unknowns()
2092                .filter_map(
2093                    |ast::Unknown {
2094                         name,
2095                         type_annotation,
2096                     }| {
2097                        if matches!(type_annotation, Some(ast::Type::Entity { .. })) {
2098                            EntityUid::from_str(name.as_str()).ok()
2099                        } else {
2100                            None
2101                        }
2102                    },
2103                )
2104                .collect();
2105            entity_uids.extend(ids);
2106        }
2107        entity_uids
2108    }
2109
2110    /// Unlink a template link from the policy set.
2111    /// Returns the policy that was unlinked.
2112    pub fn unlink(&mut self, policy_id: PolicyId) -> Result<Policy, PolicySetError> {
2113        let Some(policy) = self.policies.remove(&policy_id) else {
2114            return Err(PolicySetError::LinkNonexistentError(policy_id));
2115        };
2116        // If self.policies and self.ast disagree, authorization cannot be trusted.
2117        // PANIC SAFETY: We just found the policy in self.policies.
2118        #[allow(clippy::panic)]
2119        match self
2120            .ast
2121            .unlink(&ast::PolicyID::from_string(policy_id.to_string()))
2122        {
2123            Ok(_) => Ok(policy),
2124            Err(ast::PolicySetUnlinkError::NotLinkError(_)) => {
2125                //Restore self.policies
2126                self.policies.insert(policy_id.clone(), policy);
2127                Err(PolicySetError::UnlinkLinkNotLinkError(policy_id))
2128            }
2129            Err(ast::PolicySetUnlinkError::UnlinkingError(_)) => {
2130                panic!("Found linked policy in self.policies but not in self.ast")
2131            }
2132        }
2133    }
2134
2135    /// Create a `PolicySet` from its AST representation only. The EST will
2136    /// reflect the AST structure. When possible, don't use this method and
2137    /// create the ESTs from the policy text or CST instead, as the conversion
2138    /// to AST is lossy. ESTs generated by this method will reflect the AST and
2139    /// not the original policy syntax.
2140    #[cfg_attr(not(feature = "partial-eval"), allow(unused))]
2141    fn from_ast(ast: ast::PolicySet) -> Self {
2142        let policies = ast
2143            .policies()
2144            .map(|p| (PolicyId(p.id().clone()), Policy::from_ast(p.clone())))
2145            .collect();
2146        let templates = ast
2147            .templates()
2148            .map(|t| (PolicyId(t.id().clone()), Template::from_ast(t.clone())))
2149            .collect();
2150        Self {
2151            ast,
2152            policies,
2153            templates,
2154        }
2155    }
2156}
2157
2158impl std::fmt::Display for PolicySet {
2159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2160        // prefer to display the lossless format
2161        write!(f, "{}", self.policies().map(|p| &p.lossless).join("\n"))
2162    }
2163}
2164
2165/// Policy template datatype
2166#[derive(Debug, Clone)]
2167pub struct Template {
2168    /// AST representation of the template, used for most operations.
2169    /// In particular, the `ast` contains the authoritative `PolicyId` for the template.
2170    ast: ast::Template,
2171
2172    /// Some "lossless" representation of the template, whichever is most
2173    /// convenient to provide (and can be provided with the least overhead).
2174    /// This is used just for `to_json()`.
2175    /// We can't just derive this on-demand from `ast`, because the AST is lossy:
2176    /// we can't reconstruct an accurate CST/EST/policy-text from the AST, but
2177    /// we can from the EST (modulo whitespace and a few other things like the
2178    /// order of annotations).
2179    ///
2180    /// This is a `LosslessPolicy` (rather than something like `LosslessTemplate`)
2181    /// because the EST doesn't distinguish between static policies and templates.
2182    lossless: LosslessPolicy,
2183}
2184
2185impl PartialEq for Template {
2186    fn eq(&self, other: &Self) -> bool {
2187        // eq is based on just the `ast`
2188        self.ast.eq(&other.ast)
2189    }
2190}
2191impl Eq for Template {}
2192
2193impl Template {
2194    /// Attempt to parse a `Template` from source.
2195    /// If `id` is Some, then the resulting template will have that `id`.
2196    /// If the `id` is None, the parser will use the default "policy0".
2197    /// The behavior around None may change in the future.
2198    pub fn parse(id: Option<String>, src: impl AsRef<str>) -> Result<Self, ParseErrors> {
2199        let ast = parser::parse_policy_template(id, src.as_ref())?;
2200        Ok(Self {
2201            ast,
2202            lossless: LosslessPolicy::policy_or_template_text(src.as_ref()),
2203        })
2204    }
2205
2206    /// Get the `PolicyId` of this `Template`
2207    pub fn id(&self) -> &PolicyId {
2208        PolicyId::ref_cast(self.ast.id())
2209    }
2210
2211    /// Clone this `Template` with a new `PolicyId`
2212    #[must_use]
2213    pub fn new_id(&self, id: PolicyId) -> Self {
2214        Self {
2215            ast: self.ast.new_id(id.0),
2216            lossless: self.lossless.clone(), // Lossless representation doesn't include the `PolicyId`
2217        }
2218    }
2219
2220    /// Get the `Effect` (`Forbid` or `Permit`) of this `Template`
2221    pub fn effect(&self) -> Effect {
2222        self.ast.effect()
2223    }
2224
2225    /// Get an annotation value of this `Template`
2226    pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
2227        self.ast
2228            .annotation(&key.as_ref().parse().ok()?)
2229            .map(smol_str::SmolStr::as_str)
2230    }
2231
2232    /// Iterate through annotation data of this `Template` as key-value pairs
2233    pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
2234        self.ast
2235            .annotations()
2236            .map(|(k, v)| (k.as_ref(), v.as_str()))
2237    }
2238
2239    /// Iterate over the open slots in this `Template`
2240    pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
2241        self.ast.slots().map(SlotId::ref_cast)
2242    }
2243
2244    /// Get the head constraint on this policy's principal
2245    pub fn principal_constraint(&self) -> TemplatePrincipalConstraint {
2246        match self.ast.principal_constraint().as_inner() {
2247            ast::PrincipalOrResourceConstraint::Any => TemplatePrincipalConstraint::Any,
2248            ast::PrincipalOrResourceConstraint::In(eref) => {
2249                TemplatePrincipalConstraint::In(match eref {
2250                    ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
2251                    ast::EntityReference::Slot => None,
2252                })
2253            }
2254            ast::PrincipalOrResourceConstraint::Eq(eref) => {
2255                TemplatePrincipalConstraint::Eq(match eref {
2256                    ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
2257                    ast::EntityReference::Slot => None,
2258                })
2259            }
2260            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
2261                TemplatePrincipalConstraint::Is(EntityTypeName(entity_type.clone()))
2262            }
2263            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
2264                TemplatePrincipalConstraint::IsIn(
2265                    EntityTypeName(entity_type.clone()),
2266                    match eref {
2267                        ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
2268                        ast::EntityReference::Slot => None,
2269                    },
2270                )
2271            }
2272        }
2273    }
2274
2275    /// Get the head constraint on this policy's action
2276    pub fn action_constraint(&self) -> ActionConstraint {
2277        // Clone the data from Core to be consistent with the other constraints
2278        match self.ast.action_constraint() {
2279            ast::ActionConstraint::Any => ActionConstraint::Any,
2280            ast::ActionConstraint::In(ids) => ActionConstraint::In(
2281                ids.iter()
2282                    .map(|id| EntityUid(id.as_ref().clone()))
2283                    .collect(),
2284            ),
2285            ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid(id.as_ref().clone())),
2286        }
2287    }
2288
2289    /// Get the head constraint on this policy's resource
2290    pub fn resource_constraint(&self) -> TemplateResourceConstraint {
2291        match self.ast.resource_constraint().as_inner() {
2292            ast::PrincipalOrResourceConstraint::Any => TemplateResourceConstraint::Any,
2293            ast::PrincipalOrResourceConstraint::In(eref) => {
2294                TemplateResourceConstraint::In(match eref {
2295                    ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
2296                    ast::EntityReference::Slot => None,
2297                })
2298            }
2299            ast::PrincipalOrResourceConstraint::Eq(eref) => {
2300                TemplateResourceConstraint::Eq(match eref {
2301                    ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
2302                    ast::EntityReference::Slot => None,
2303                })
2304            }
2305            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
2306                TemplateResourceConstraint::Is(EntityTypeName(entity_type.clone()))
2307            }
2308            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
2309                TemplateResourceConstraint::IsIn(
2310                    EntityTypeName(entity_type.clone()),
2311                    match eref {
2312                        ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
2313                        ast::EntityReference::Slot => None,
2314                    },
2315                )
2316            }
2317        }
2318    }
2319
2320    /// Create a `Template` from its JSON representation.
2321    /// If `id` is Some, the policy will be given that Policy Id.
2322    /// If `id` is None, then "JSON policy" will be used.
2323    /// The behavior around None may change in the future.
2324    pub fn from_json(
2325        id: Option<PolicyId>,
2326        json: serde_json::Value,
2327    ) -> Result<Self, cedar_policy_core::est::FromJsonError> {
2328        let est: est::Policy =
2329            serde_json::from_value(json).map_err(JsonDeserializationError::Serde)?;
2330        Ok(Self {
2331            ast: est.clone().try_into_ast_template(id.map(|id| id.0))?,
2332            lossless: LosslessPolicy::Est(est),
2333        })
2334    }
2335
2336    /// Get the JSON representation of this `Template`.
2337    pub fn to_json(&self) -> Result<serde_json::Value, impl std::error::Error> {
2338        let est = self.lossless.est()?;
2339        let json = serde_json::to_value(est)?;
2340        Ok::<_, PolicyToJsonError>(json)
2341    }
2342
2343    /// Create a `Template` from its AST representation only. The EST will
2344    /// reflect the AST structure. When possible, don't use this method and
2345    /// create the EST from the policy text or CST instead, as the conversion
2346    /// to AST is lossy. ESTs generated by this method will reflect the AST and
2347    /// not the original policy syntax.
2348    #[cfg_attr(not(feature = "partial-eval"), allow(unused))]
2349    fn from_ast(ast: ast::Template) -> Self {
2350        let text = ast.to_string(); // assume that pretty-printing is faster than `est::Policy::from(ast.clone())`; is that true?
2351        Self {
2352            ast,
2353            lossless: LosslessPolicy::policy_or_template_text(text),
2354        }
2355    }
2356}
2357
2358impl std::fmt::Display for Template {
2359    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2360        // prefer to display the lossless format
2361        self.lossless.fmt(f)
2362    }
2363}
2364
2365impl FromStr for Template {
2366    type Err = ParseErrors;
2367
2368    fn from_str(src: &str) -> Result<Self, Self::Err> {
2369        Self::parse(None, src)
2370    }
2371}
2372
2373/// Head constraint on policy principals.
2374#[derive(Debug, Clone, PartialEq, Eq)]
2375pub enum PrincipalConstraint {
2376    /// Un-constrained
2377    Any,
2378    /// Must be In the given EntityUid
2379    In(EntityUid),
2380    /// Must be equal to the given EntityUid
2381    Eq(EntityUid),
2382    /// Must be the given EntityTypeName
2383    Is(EntityTypeName),
2384    /// Must be the given EntityTypeName, and `in` the EntityUID
2385    IsIn(EntityTypeName, EntityUid),
2386}
2387
2388/// Head constraint on policy principals for templates.
2389#[derive(Debug, Clone, PartialEq, Eq)]
2390pub enum TemplatePrincipalConstraint {
2391    /// Un-constrained
2392    Any,
2393    /// Must be In the given EntityUid.
2394    /// If [`None`], then it is a template slot.
2395    In(Option<EntityUid>),
2396    /// Must be equal to the given EntityUid.
2397    /// If [`None`], then it is a template slot.
2398    Eq(Option<EntityUid>),
2399    /// Must be the given EntityTypeName.
2400    Is(EntityTypeName),
2401    /// Must be the given EntityTypeName, and `in` the EntityUID.
2402    /// If the EntityUID is [`None`], then it is a template slot.
2403    IsIn(EntityTypeName, Option<EntityUid>),
2404}
2405
2406impl TemplatePrincipalConstraint {
2407    /// Does this constraint contain a slot?
2408    pub fn has_slot(&self) -> bool {
2409        match self {
2410            Self::Any | Self::Is(_) => false,
2411            Self::In(o) | Self::Eq(o) | Self::IsIn(_, o) => o.is_none(),
2412        }
2413    }
2414}
2415
2416/// Head constraint on policy actions.
2417#[derive(Debug, Clone, PartialEq, Eq)]
2418pub enum ActionConstraint {
2419    /// Un-constrained
2420    Any,
2421    /// Must be In the given EntityUid
2422    In(Vec<EntityUid>),
2423    /// Must be equal to the given EntityUid
2424    Eq(EntityUid),
2425}
2426
2427/// Head constraint on policy resources.
2428#[derive(Debug, Clone, PartialEq, Eq)]
2429pub enum ResourceConstraint {
2430    /// Un-constrained
2431    Any,
2432    /// Must be In the given EntityUid
2433    In(EntityUid),
2434    /// Must be equal to the given EntityUid
2435    Eq(EntityUid),
2436    /// Must be the given EntityTypeName
2437    Is(EntityTypeName),
2438    /// Must be the given EntityTypeName, and `in` the EntityUID
2439    IsIn(EntityTypeName, EntityUid),
2440}
2441
2442/// Head constraint on policy resources for templates.
2443#[derive(Debug, Clone, PartialEq, Eq)]
2444pub enum TemplateResourceConstraint {
2445    /// Un-constrained
2446    Any,
2447    /// Must be In the given EntityUid.
2448    /// If [`None`], then it is a template slot.
2449    In(Option<EntityUid>),
2450    /// Must be equal to the given EntityUid.
2451    /// If [`None`], then it is a template slot.
2452    Eq(Option<EntityUid>),
2453    /// Must be the given EntityTypeName.
2454    Is(EntityTypeName),
2455    /// Must be the given EntityTypeName, and `in` the EntityUID.
2456    /// If the EntityUID is [`None`], then it is a template slot.
2457    IsIn(EntityTypeName, Option<EntityUid>),
2458}
2459
2460impl TemplateResourceConstraint {
2461    /// Does this constraint contain a slot?
2462    pub fn has_slot(&self) -> bool {
2463        match self {
2464            Self::Any | Self::Is(_) => false,
2465            Self::In(o) | Self::Eq(o) | Self::IsIn(_, o) => o.is_none(),
2466        }
2467    }
2468}
2469
2470/// Unique Ids assigned to policies and templates
2471#[repr(transparent)]
2472#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize, RefCast)]
2473pub struct PolicyId(ast::PolicyID);
2474
2475impl FromStr for PolicyId {
2476    type Err = ParseErrors;
2477
2478    /// Create a `PolicyId` from a string. Currently always returns Ok().
2479    fn from_str(id: &str) -> Result<Self, Self::Err> {
2480        Ok(Self(ast::PolicyID::from_string(id)))
2481    }
2482}
2483
2484impl std::fmt::Display for PolicyId {
2485    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2486        write!(f, "{}", self.0)
2487    }
2488}
2489
2490/// Structure for a `Policy`. Includes both static policies and template-linked policies.
2491#[derive(Debug, Clone)]
2492pub struct Policy {
2493    /// AST representation of the policy, used for most operations.
2494    /// In particular, the `ast` contains the authoritative `PolicyId` for the policy.
2495    ast: ast::Policy,
2496    /// Some "lossless" representation of the policy, whichever is most
2497    /// convenient to provide (and can be provided with the least overhead).
2498    /// This is used just for `to_json()`.
2499    /// We can't just derive this on-demand from `ast`, because the AST is lossy:
2500    /// we can't reconstruct an accurate CST/EST/policy-text from the AST, but
2501    /// we can from the EST (modulo whitespace and a few other things like the
2502    /// order of annotations).
2503    lossless: LosslessPolicy,
2504}
2505
2506impl PartialEq for Policy {
2507    fn eq(&self, other: &Self) -> bool {
2508        // eq is based on just the `ast`
2509        self.ast.eq(&other.ast)
2510    }
2511}
2512impl Eq for Policy {}
2513
2514impl Policy {
2515    /// Get the `PolicyId` of the `Template` this is linked to.
2516    /// If this is a static policy, this will return `None`.
2517    pub fn template_id(&self) -> Option<&PolicyId> {
2518        if self.is_static() {
2519            None
2520        } else {
2521            Some(PolicyId::ref_cast(self.ast.template().id()))
2522        }
2523    }
2524
2525    /// Get the `Effect` (`Permit` or `Forbid`) for this instance
2526    pub fn effect(&self) -> Effect {
2527        self.ast.effect()
2528    }
2529
2530    /// Get an annotation value of this template-linked or static policy
2531    pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
2532        self.ast
2533            .annotation(&key.as_ref().parse().ok()?)
2534            .map(smol_str::SmolStr::as_str)
2535    }
2536
2537    /// Iterate through annotation data of this template-linked or static policy
2538    pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
2539        self.ast
2540            .annotations()
2541            .map(|(k, v)| (k.as_ref(), v.as_str()))
2542    }
2543
2544    /// Get the `PolicyId` for this template-linked or static policy
2545    pub fn id(&self) -> &PolicyId {
2546        PolicyId::ref_cast(self.ast.id())
2547    }
2548
2549    /// Clone this `Policy` with a new `PolicyId`
2550    #[must_use]
2551    pub fn new_id(&self, id: PolicyId) -> Self {
2552        Self {
2553            ast: self.ast.new_id(id.0),
2554            lossless: self.lossless.clone(), // Lossless representation doesn't include the `PolicyId`
2555        }
2556    }
2557
2558    /// Returns `true` if this is a static policy, `false` otherwise.
2559    pub fn is_static(&self) -> bool {
2560        self.ast.is_static()
2561    }
2562
2563    /// Get the head constraint on this policy's principal
2564    pub fn principal_constraint(&self) -> PrincipalConstraint {
2565        let slot_id = ast::SlotId::principal();
2566        match self.ast.template().principal_constraint().as_inner() {
2567            ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::Any,
2568            ast::PrincipalOrResourceConstraint::In(eref) => {
2569                PrincipalConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
2570            }
2571            ast::PrincipalOrResourceConstraint::Eq(eref) => {
2572                PrincipalConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
2573            }
2574            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
2575                PrincipalConstraint::Is(EntityTypeName(entity_type.clone()))
2576            }
2577            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
2578                PrincipalConstraint::IsIn(
2579                    EntityTypeName(entity_type.clone()),
2580                    self.convert_entity_reference(eref, slot_id).clone(),
2581                )
2582            }
2583        }
2584    }
2585
2586    /// Get the head constraint on this policy's action
2587    pub fn action_constraint(&self) -> ActionConstraint {
2588        // Clone the data from Core to be consistant with the other constraints
2589        // INVARIANT: all of the EntityUids come from a policy, which must have Concrete EntityUids
2590        match self.ast.template().action_constraint() {
2591            ast::ActionConstraint::Any => ActionConstraint::Any,
2592            ast::ActionConstraint::In(ids) => ActionConstraint::In(
2593                ids.iter()
2594                    .map(|euid| EntityUid::ref_cast(euid.as_ref()))
2595                    .cloned()
2596                    .collect(),
2597            ),
2598            ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid::ref_cast(id).clone()),
2599        }
2600    }
2601
2602    /// Get the head constraint on this policy's resource
2603    pub fn resource_constraint(&self) -> ResourceConstraint {
2604        let slot_id = ast::SlotId::resource();
2605        match self.ast.template().resource_constraint().as_inner() {
2606            ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::Any,
2607            ast::PrincipalOrResourceConstraint::In(eref) => {
2608                ResourceConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
2609            }
2610            ast::PrincipalOrResourceConstraint::Eq(eref) => {
2611                ResourceConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
2612            }
2613            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
2614                ResourceConstraint::Is(EntityTypeName(entity_type.clone()))
2615            }
2616            ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
2617                ResourceConstraint::IsIn(
2618                    EntityTypeName(entity_type.clone()),
2619                    self.convert_entity_reference(eref, slot_id).clone(),
2620                )
2621            }
2622        }
2623    }
2624
2625    /// To avoid panicking, this function may only be called when `slot` is the
2626    /// `SlotId` corresponding to the scope constraint from which the entity
2627    /// reference `r` was extracted. I.e., If `r` is taken from the principal
2628    /// scope constraint, `slot` must be `?principal`. This ensures that the
2629    /// `SlotId` exists in the policy (and therefore the slot environment map)
2630    /// whenever the `EntityReference` `r` is the Slot variant.
2631    fn convert_entity_reference<'a>(
2632        &'a self,
2633        r: &'a ast::EntityReference,
2634        slot: ast::SlotId,
2635    ) -> &'a EntityUid {
2636        match r {
2637            // INVARIANT: this comes from policy source, so must be concrete
2638            ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid),
2639            // PANIC SAFETY: This `unwrap` here is safe due the invariant (values total map) on policies.
2640            #[allow(clippy::unwrap_used)]
2641            ast::EntityReference::Slot => EntityUid::ref_cast(self.ast.env().get(&slot).unwrap()),
2642        }
2643    }
2644
2645    /// Parse a single policy.
2646    /// If `id` is Some, the policy will be given that Policy Id.
2647    /// If `id` is None, then "policy0" will be used.
2648    /// The behavior around None may change in the future.
2649    ///
2650    /// This can fail if the policy fails to parse.
2651    /// It can also fail if a template was passed in, as this function only accepts static
2652    /// policies
2653    pub fn parse(id: Option<String>, policy_src: impl AsRef<str>) -> Result<Self, ParseErrors> {
2654        let inline_ast = parser::parse_policy(id, policy_src.as_ref())?;
2655        let (_, ast) = ast::Template::link_static_policy(inline_ast);
2656        Ok(Self {
2657            ast,
2658            lossless: LosslessPolicy::policy_or_template_text(policy_src.as_ref()),
2659        })
2660    }
2661
2662    /// Create a `Policy` from its JSON representation.
2663    /// If `id` is Some, the policy will be given that Policy Id.
2664    /// If `id` is None, then "JSON policy" will be used.
2665    /// The behavior around None may change in the future.
2666    ///
2667    /// ```
2668    /// use cedar_policy::{Policy, PolicyId};
2669    /// use std::str::FromStr;
2670    ///
2671    /// let json: serde_json::Value = serde_json::json!(
2672    ///        {
2673    ///            "effect":"permit",
2674    ///            "principal":{
2675    ///            "op":"==",
2676    ///            "entity":{
2677    ///                "type":"User",
2678    ///                "id":"bob"
2679    ///            }
2680    ///            },
2681    ///            "action":{
2682    ///            "op":"==",
2683    ///            "entity":{
2684    ///                "type":"Action",
2685    ///                "id":"view"
2686    ///            }
2687    ///            },
2688    ///            "resource":{
2689    ///            "op":"==",
2690    ///            "entity":{
2691    ///                "type":"Album",
2692    ///                "id":"trip"
2693    ///            }
2694    ///            },
2695    ///            "conditions":[
2696    ///            {
2697    ///                "kind":"when",
2698    ///                "body":{
2699    ///                   ">":{
2700    ///                        "left":{
2701    ///                        ".":{
2702    ///                            "left":{
2703    ///                                "Var":"principal"
2704    ///                            },
2705    ///                            "attr":"age"
2706    ///                        }
2707    ///                        },
2708    ///                        "right":{
2709    ///                        "Value":18
2710    ///                        }
2711    ///                    }
2712    ///                }
2713    ///            }
2714    ///            ]
2715    ///        }
2716    /// );
2717    /// let json_policy = Policy::from_json(None, json).unwrap();
2718    /// let src = r#"
2719    ///   permit(
2720    ///     principal == User::"bob",
2721    ///     action == Action::"view",
2722    ///     resource == Album::"trip"
2723    ///   )
2724    ///   when { principal.age > 18 };"#;
2725    /// let text_policy = Policy::parse(None, src).unwrap();
2726    /// assert_eq!(json_policy.to_json().unwrap(), text_policy.to_json().unwrap());
2727    /// ```
2728    pub fn from_json(
2729        id: Option<PolicyId>,
2730        json: serde_json::Value,
2731    ) -> Result<Self, cedar_policy_core::est::FromJsonError> {
2732        let est: est::Policy =
2733            serde_json::from_value(json).map_err(JsonDeserializationError::Serde)?;
2734        Ok(Self {
2735            ast: est.clone().try_into_ast_policy(id.map(|id| id.0))?,
2736            lossless: LosslessPolicy::Est(est),
2737        })
2738    }
2739
2740    /// Get the JSON representation of this `Policy`.
2741    ///  ```
2742    /// # use cedar_policy::Policy;
2743    /// let src = r#"
2744    ///   permit(
2745    ///     principal == User::"bob",
2746    ///     action == Action::"view",
2747    ///     resource == Album::"trip"
2748    ///   )
2749    ///   when { principal.age > 18 };"#;
2750    ///
2751    /// let policy = Policy::parse(None, src).unwrap();
2752    /// println!("{}", policy);
2753    /// // convert the policy to JSON
2754    /// let json = policy.to_json().unwrap();
2755    /// println!("{}", json);
2756    /// assert_eq!(json, Policy::from_json(None, json.clone()).unwrap().to_json().unwrap());
2757    /// ```
2758    pub fn to_json(&self) -> Result<serde_json::Value, impl std::error::Error> {
2759        let est = self.lossless.est()?;
2760        let json = serde_json::to_value(est)?;
2761        Ok::<_, PolicyToJsonError>(json)
2762    }
2763
2764    /// Create a `Policy` from its AST representation only. The `LosslessPolicy`
2765    /// will reflect the AST structure. When possible, don't use this method and
2766    /// create the `Policy` from the policy text, CST, or EST instead, as the
2767    /// conversion to AST is lossy. ESTs for policies generated by this method
2768    /// will reflect the AST and not the original policy syntax.
2769    #[cfg_attr(not(feature = "partial-eval"), allow(unused))]
2770    pub(crate) fn from_ast(ast: ast::Policy) -> Self {
2771        let text = ast.to_string(); // assume that pretty-printing is faster than `est::Policy::from(ast.clone())`; is that true?
2772        Self {
2773            ast,
2774            lossless: LosslessPolicy::policy_or_template_text(text),
2775        }
2776    }
2777}
2778
2779impl std::fmt::Display for Policy {
2780    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2781        // prefer to display the lossless format
2782        self.lossless.fmt(f)
2783    }
2784}
2785
2786impl FromStr for Policy {
2787    type Err = ParseErrors;
2788    /// Create a policy
2789    ///
2790    /// Important note: Policies have ids, but this interface does not
2791    /// allow them to be set. It will use the default "policy0", which
2792    /// may cause id conflicts if not handled. Use `Policy::parse` to set
2793    /// the id when parsing, or `Policy::new_id` to clone a policy with
2794    /// a new id.
2795    fn from_str(policy: &str) -> Result<Self, Self::Err> {
2796        Self::parse(None, policy)
2797    }
2798}
2799
2800/// See comments on `Policy` and `Template`.
2801///
2802/// This structure can be used for static policies, linked policies, and templates.
2803#[derive(Debug, Clone)]
2804enum LosslessPolicy {
2805    /// EST representation
2806    Est(est::Policy),
2807    /// Text representation
2808    Text {
2809        /// actual policy text, of the policy or template
2810        text: String,
2811        /// For linked policies, map of slot to UID. Only linked policies have
2812        /// this; static policies and (unlinked) templates have an empty map
2813        /// here
2814        slots: HashMap<ast::SlotId, ast::EntityUID>,
2815    },
2816}
2817
2818impl LosslessPolicy {
2819    /// Create a new `LosslessPolicy` from the text of a policy or template.
2820    fn policy_or_template_text(text: impl Into<String>) -> Self {
2821        Self::Text {
2822            text: text.into(),
2823            slots: HashMap::new(),
2824        }
2825    }
2826
2827    /// Get the EST representation of this static policy, linked policy, or template
2828    fn est(&self) -> Result<est::Policy, PolicyToJsonError> {
2829        match self {
2830            Self::Est(est) => Ok(est.clone()),
2831            Self::Text { text, slots } => {
2832                let est = parser::parse_policy_or_template_to_est(text)?;
2833                if slots.is_empty() {
2834                    Ok(est)
2835                } else {
2836                    let unwrapped_vals = slots.iter().map(|(k, v)| (*k, v.into())).collect();
2837                    Ok(est.link(&unwrapped_vals)?)
2838                }
2839            }
2840        }
2841    }
2842
2843    fn link<'a>(
2844        self,
2845        vals: impl IntoIterator<Item = (ast::SlotId, &'a ast::EntityUID)>,
2846    ) -> Result<Self, est::InstantiationError> {
2847        match self {
2848            Self::Est(est) => {
2849                let unwrapped_est_vals: HashMap<ast::SlotId, entities::EntityUidJson> =
2850                    vals.into_iter().map(|(k, v)| (k, v.into())).collect();
2851                Ok(Self::Est(est.link(&unwrapped_est_vals)?))
2852            }
2853            Self::Text { text, slots } => {
2854                debug_assert!(
2855                    slots.is_empty(),
2856                    "shouldn't call link() on an already-linked policy"
2857                );
2858                let slots = vals.into_iter().map(|(k, v)| (k, v.clone())).collect();
2859                Ok(Self::Text { text, slots })
2860            }
2861        }
2862    }
2863}
2864
2865impl std::fmt::Display for LosslessPolicy {
2866    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2867        match self {
2868            Self::Est(est) => write!(f, "{est}"),
2869            Self::Text { text, slots } => {
2870                if slots.is_empty() {
2871                    write!(f, "{text}")
2872                } else {
2873                    // need to replace placeholders according to `slots`.
2874                    // just find-and-replace wouldn't be safe/perfect, we
2875                    // want to use the actual parser; right now we reuse
2876                    // another implementation by just converting to EST and
2877                    // printing that
2878                    match self.est() {
2879                        Ok(est) => write!(f, "{est}"),
2880                        Err(e) => write!(f, "<invalid linked policy: {e}>"),
2881                    }
2882                }
2883            }
2884        }
2885    }
2886}
2887
2888/// Errors that can happen when getting the JSON representation of a policy
2889#[derive(Debug, Error)]
2890pub enum PolicyToJsonError {
2891    /// Parse error in the policy text
2892    #[error(transparent)]
2893    Parse(#[from] ParseErrors),
2894    /// For linked policies, error linking the JSON representation
2895    #[error(transparent)]
2896    Link(#[from] est::InstantiationError),
2897    /// Error in the JSON serialization
2898    #[error(transparent)]
2899    Serde(#[from] serde_json::Error),
2900}
2901
2902/// Expressions to be evaluated
2903#[repr(transparent)]
2904#[derive(Debug, Clone, RefCast)]
2905pub struct Expression(ast::Expr);
2906
2907impl Expression {
2908    /// Create an expression representing a literal string.
2909    pub fn new_string(value: String) -> Self {
2910        Self(ast::Expr::val(value))
2911    }
2912
2913    /// Create an expression representing a literal bool.
2914    pub fn new_bool(value: bool) -> Self {
2915        Self(ast::Expr::val(value))
2916    }
2917
2918    /// Create an expression representing a literal long.
2919    pub fn new_long(value: i64) -> Self {
2920        Self(ast::Expr::val(value))
2921    }
2922
2923    /// Create an expression representing a record.
2924    ///
2925    /// Error if any key appears two or more times in `fields`.
2926    pub fn new_record(
2927        fields: impl IntoIterator<Item = (String, Self)>,
2928    ) -> Result<Self, ExprConstructionError> {
2929        Ok(Self(ast::Expr::record(
2930            fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
2931        )?))
2932    }
2933
2934    /// Create an expression representing a Set.
2935    pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
2936        Self(ast::Expr::set(values.into_iter().map(|v| v.0)))
2937    }
2938}
2939
2940impl FromStr for Expression {
2941    type Err = ParseErrors;
2942
2943    /// create an Expression using Cedar syntax
2944    fn from_str(expression: &str) -> Result<Self, Self::Err> {
2945        ast::Expr::from_str(expression).map(Expression)
2946    }
2947}
2948
2949/// "Restricted" expressions are used for attribute values and `context`.
2950///
2951/// Restricted expressions can contain only the following:
2952///   - bool, int, and string literals
2953///   - literal `EntityUid`s such as `User::"alice"`
2954///   - extension function calls, where the arguments must be other things
2955///       on this list
2956///   - set and record literals, where the values must be other things on
2957///       this list
2958///
2959/// That means the following are not allowed in restricted expressions:
2960///   - `principal`, `action`, `resource`, `context`
2961///   - builtin operators and functions, including `.`, `in`, `has`, `like`,
2962///       `.contains()`
2963///   - if-then-else expressions
2964#[repr(transparent)]
2965#[derive(Debug, Clone, RefCast)]
2966pub struct RestrictedExpression(ast::RestrictedExpr);
2967
2968impl RestrictedExpression {
2969    /// Create an expression representing a literal string.
2970    pub fn new_string(value: String) -> Self {
2971        Self(ast::RestrictedExpr::val(value))
2972    }
2973
2974    /// Create an expression representing a literal bool.
2975    pub fn new_bool(value: bool) -> Self {
2976        Self(ast::RestrictedExpr::val(value))
2977    }
2978
2979    /// Create an expression representing a literal long.
2980    pub fn new_long(value: i64) -> Self {
2981        Self(ast::RestrictedExpr::val(value))
2982    }
2983
2984    /// Create an expression representing a literal `EntityUid`.
2985    pub fn new_entity_uid(value: EntityUid) -> Self {
2986        Self(ast::RestrictedExpr::val(value.0))
2987    }
2988
2989    /// Create an expression representing a record.
2990    ///
2991    /// Error if any key appears two or more times in `fields`.
2992    pub fn new_record(
2993        fields: impl IntoIterator<Item = (String, Self)>,
2994    ) -> Result<Self, ExprConstructionError> {
2995        Ok(Self(ast::RestrictedExpr::record(
2996            fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
2997        )?))
2998    }
2999
3000    /// Create an expression representing a Set.
3001    pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
3002        Self(ast::RestrictedExpr::set(values.into_iter().map(|v| v.0)))
3003    }
3004}
3005
3006impl FromStr for RestrictedExpression {
3007    type Err = RestrictedExprParseError;
3008
3009    /// create a `RestrictedExpression` using Cedar syntax
3010    fn from_str(expression: &str) -> Result<Self, Self::Err> {
3011        ast::RestrictedExpr::from_str(expression).map(RestrictedExpression)
3012    }
3013}
3014
3015/// Builder for a [`Request`]
3016///
3017/// The default for principal, action, resource, and context fields is Unknown
3018/// for partial evaluation.
3019#[cfg(feature = "partial-eval")]
3020#[derive(Debug)]
3021pub struct RequestBuilder<'a> {
3022    principal: ast::EntityUIDEntry,
3023    action: ast::EntityUIDEntry,
3024    resource: ast::EntityUIDEntry,
3025    /// Here, `None` means unknown
3026    context: Option<ast::Context>,
3027    /// Here, `None` means no request validation is performed
3028    schema: Option<&'a Schema>,
3029}
3030
3031#[cfg(feature = "partial-eval")]
3032impl<'a> Default for RequestBuilder<'a> {
3033    fn default() -> Self {
3034        Self {
3035            principal: ast::EntityUIDEntry::Unknown,
3036            action: ast::EntityUIDEntry::Unknown,
3037            resource: ast::EntityUIDEntry::Unknown,
3038            context: None,
3039            schema: None,
3040        }
3041    }
3042}
3043
3044#[cfg(feature = "partial-eval")]
3045impl<'a> RequestBuilder<'a> {
3046    /// Set the principal.
3047    ///
3048    /// Note that you can create the `EntityUid` using `.parse()` on any
3049    /// string (via the `FromStr` implementation for `EntityUid`).
3050    ///
3051    /// Here, passing `None` for `principal` indicates that `principal` does
3052    /// not contribute to authorization decisions (e.g., because it is not
3053    /// used in your policies).
3054    /// This is different than Unknown for partial-evaluation purposes.
3055    pub fn principal(self, principal: Option<EntityUid>) -> Self {
3056        Self {
3057            principal: match principal {
3058                Some(p) => ast::EntityUIDEntry::concrete(p.0),
3059                None => ast::EntityUIDEntry::concrete(ast::EntityUID::unspecified_from_eid(
3060                    ast::Eid::new("principal"),
3061                )),
3062            },
3063            ..self
3064        }
3065    }
3066
3067    /// Set the action.
3068    ///
3069    /// Note that you can create the `EntityUid` using `.parse()` on any
3070    /// string (via the `FromStr` implementation for `EntityUid`).
3071    ///
3072    /// Here, passing `None` for `action` indicates that `action` does
3073    /// not contribute to authorization decisions (e.g., because it is not
3074    /// used in your policies).
3075    /// This is different than Unknown for partial-evaluation purposes.
3076    pub fn action(self, action: Option<EntityUid>) -> Self {
3077        Self {
3078            action: match action {
3079                Some(a) => ast::EntityUIDEntry::concrete(a.0),
3080                None => ast::EntityUIDEntry::concrete(ast::EntityUID::unspecified_from_eid(
3081                    ast::Eid::new("action"),
3082                )),
3083            },
3084            ..self
3085        }
3086    }
3087
3088    /// Set the resource.
3089    ///
3090    /// Note that you can create the `EntityUid` using `.parse()` on any
3091    /// string (via the `FromStr` implementation for `EntityUid`).
3092    ///
3093    /// Here, passing `None` for `resource` indicates that `resource` does
3094    /// not contribute to authorization decisions (e.g., because it is not
3095    /// used in your policies).
3096    /// This is different than Unknown for partial-evaluation purposes.
3097    pub fn resource(self, resource: Option<EntityUid>) -> Self {
3098        Self {
3099            resource: match resource {
3100                Some(r) => ast::EntityUIDEntry::concrete(r.0),
3101                None => ast::EntityUIDEntry::concrete(ast::EntityUID::unspecified_from_eid(
3102                    ast::Eid::new("resource"),
3103                )),
3104            },
3105            ..self
3106        }
3107    }
3108
3109    /// Set the context.
3110    pub fn context(self, context: Context) -> Self {
3111        Self {
3112            context: Some(context.0),
3113            ..self
3114        }
3115    }
3116
3117    /// Set the schema. If present, this will be used for request validation.
3118    pub fn schema(self, schema: &'a Schema) -> Self {
3119        Self {
3120            schema: Some(schema),
3121            ..self
3122        }
3123    }
3124
3125    /// Create the [`Request`]
3126    pub fn build(self) -> Result<Request, RequestValidationError> {
3127        Ok(Request(ast::Request::new_with_unknowns(
3128            self.principal,
3129            self.action,
3130            self.resource,
3131            self.context,
3132            self.schema.map(|schema| &schema.0),
3133            Extensions::all_available(),
3134        )?))
3135    }
3136}
3137
3138/// Represents the request tuple <P, A, R, C> (see the Cedar design doc).
3139#[repr(transparent)]
3140#[derive(Debug, RefCast)]
3141pub struct Request(pub(crate) ast::Request);
3142
3143impl Request {
3144    /// Create a [`RequestBuilder`]
3145    #[cfg(feature = "partial-eval")]
3146    pub fn builder<'a>() -> RequestBuilder<'a> {
3147        RequestBuilder::default()
3148    }
3149
3150    /// Create a Request.
3151    ///
3152    /// Note that you can create the `EntityUid`s using `.parse()` on any
3153    /// string (via the `FromStr` implementation for `EntityUid`).
3154    /// The principal, action, and resource fields are optional to support
3155    /// the case where these fields do not contribute to authorization
3156    /// decisions (e.g., because they are not used in your policies).
3157    /// If any of the fields are `None`, we will automatically generate
3158    /// a unique entity UID that is not equal to any UID in the store.
3159    ///
3160    /// If `schema` is present, this constructor will validate that the
3161    /// `Request` complies with the given `schema`.
3162    pub fn new(
3163        principal: Option<EntityUid>,
3164        action: Option<EntityUid>,
3165        resource: Option<EntityUid>,
3166        context: Context,
3167        schema: Option<&Schema>,
3168    ) -> Result<Self, RequestValidationError> {
3169        let p = match principal {
3170            Some(p) => p.0,
3171            None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("principal")),
3172        };
3173        let a = match action {
3174            Some(a) => a.0,
3175            None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("action")),
3176        };
3177        let r = match resource {
3178            Some(r) => r.0,
3179            None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("resource")),
3180        };
3181        Ok(Self(ast::Request::new(
3182            p,
3183            a,
3184            r,
3185            context.0,
3186            schema.map(|schema| &schema.0),
3187            Extensions::all_available(),
3188        )?))
3189    }
3190
3191    /// Get the principal component of the request. Returns `None` if the principal is
3192    /// "unspecified" (i.e., constructed by passing `None` into the constructor) or
3193    /// "unknown" (i.e., constructed using the partial evaluation APIs).
3194    pub fn principal(&self) -> Option<&EntityUid> {
3195        match self.0.principal() {
3196            ast::EntityUIDEntry::Known(euid) => match euid.entity_type() {
3197                // INVARIANT: we ensure Concrete-ness here
3198                ast::EntityType::Specified(_) => Some(EntityUid::ref_cast(euid.as_ref())),
3199                ast::EntityType::Unspecified => None,
3200            },
3201            ast::EntityUIDEntry::Unknown => None,
3202        }
3203    }
3204
3205    /// Get the action component of the request. Returns `None` if the action is
3206    /// "unspecified" (i.e., constructed by passing `None` into the constructor) or
3207    /// "unknown" (i.e., constructed using the partial evaluation APIs).
3208    pub fn action(&self) -> Option<&EntityUid> {
3209        match self.0.action() {
3210            ast::EntityUIDEntry::Known(euid) => match euid.entity_type() {
3211                // INVARIANT: we ensure Concrete-ness here
3212                ast::EntityType::Specified(_) => Some(EntityUid::ref_cast(euid.as_ref())),
3213                ast::EntityType::Unspecified => None,
3214            },
3215            ast::EntityUIDEntry::Unknown => None,
3216        }
3217    }
3218
3219    /// Get the resource component of the request. Returns `None` if the resource is
3220    /// "unspecified" (i.e., constructed by passing `None` into the constructor) or
3221    /// "unknown" (i.e., constructed using the partial evaluation APIs).
3222    pub fn resource(&self) -> Option<&EntityUid> {
3223        match self.0.resource() {
3224            ast::EntityUIDEntry::Known(euid) => match euid.entity_type() {
3225                // INVARIANT: we ensure Concrete-ness here
3226                ast::EntityType::Specified(_) => Some(EntityUid::ref_cast(euid.as_ref())),
3227                ast::EntityType::Unspecified => None,
3228            },
3229            ast::EntityUIDEntry::Unknown => None,
3230        }
3231    }
3232}
3233
3234/// the Context object for an authorization request
3235#[repr(transparent)]
3236#[derive(Debug, Clone, RefCast)]
3237pub struct Context(ast::Context);
3238
3239impl Context {
3240    /// Create an empty `Context`
3241    /// ```
3242    /// use cedar_policy::Context;
3243    /// let context = Context::empty();
3244    /// ```
3245    pub fn empty() -> Self {
3246        Self(ast::Context::empty())
3247    }
3248
3249    /// Create a `Context` from a map of key to "restricted expression",
3250    /// or a Vec of `(key, restricted expression)` pairs, or any other iterator
3251    /// of `(key, restricted expression)` pairs.
3252    /// ```
3253    /// use cedar_policy::{Context, RestrictedExpression};
3254    /// # use std::collections::HashMap;
3255    /// use std::str::FromStr;
3256    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, Request,PolicySet};
3257    /// let data : serde_json::Value = serde_json::json!({
3258    ///     "sub": "1234",
3259    ///     "groups": {
3260    ///         "1234": {
3261    ///             "group_id": "abcd",
3262    ///             "group_name": "test-group"
3263    ///         }
3264    ///     }
3265    /// });
3266    /// let mut groups: HashMap<String, RestrictedExpression> = HashMap::new();
3267    /// groups.insert("key".to_string(), RestrictedExpression::from_str(&data.to_string()).unwrap());
3268    /// groups.insert("age".to_string(), RestrictedExpression::from_str("18").unwrap());
3269    /// let context = Context::from_pairs(groups).unwrap();
3270    /// # // create a request
3271    /// # let p_eid = EntityId::from_str("alice").unwrap();
3272    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
3273    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
3274    /// #
3275    /// # let a_eid = EntityId::from_str("view").unwrap();
3276    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
3277    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
3278    /// # let r_eid = EntityId::from_str("trip").unwrap();
3279    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
3280    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
3281    /// # let request: Request = Request::new(Some(p), Some(a), Some(r), context, None).unwrap();
3282    /// ```
3283    pub fn from_pairs(
3284        pairs: impl IntoIterator<Item = (String, RestrictedExpression)>,
3285    ) -> Result<Self, ContextCreationError> {
3286        Ok(Self(ast::Context::from_pairs(
3287            pairs.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
3288            Extensions::all_available(),
3289        )?))
3290    }
3291
3292    /// Create a `Context` from a string containing JSON (which must be a JSON
3293    /// object, not any other JSON type, or you will get an error here).
3294    /// JSON here must use the `__entity` and `__extn` escapes for entity
3295    /// references, extension values, etc.
3296    ///
3297    /// If a `schema` is provided, this will inform the parsing: for instance, it
3298    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
3299    /// if attributes have the wrong types (e.g., string instead of integer).
3300    /// Since different Actions have different schemas for `Context`, you also
3301    /// must specify the `Action` for schema-based parsing.
3302    /// ```
3303    /// use cedar_policy::{Context, RestrictedExpression};
3304    /// # use std::collections::HashMap;
3305    /// use std::str::FromStr;
3306    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, Request,PolicySet};
3307    /// let data =r#"{
3308    ///     "sub": "1234",
3309    ///     "groups": {
3310    ///         "1234": {
3311    ///             "group_id": "abcd",
3312    ///             "group_name": "test-group"
3313    ///         }
3314    ///     }
3315    /// }"#;
3316    /// let context = Context::from_json_str(data, None).unwrap();
3317    /// # // create a request
3318    /// # let p_eid = EntityId::from_str("alice").unwrap();
3319    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
3320    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
3321    /// #
3322    /// # let a_eid = EntityId::from_str("view").unwrap();
3323    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
3324    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
3325    /// # let r_eid = EntityId::from_str("trip").unwrap();
3326    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
3327    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
3328    /// # let request: Request = Request::new(Some(p), Some(a), Some(r), context, None).unwrap();
3329    /// ```
3330    pub fn from_json_str(
3331        json: &str,
3332        schema: Option<(&Schema, &EntityUid)>,
3333    ) -> Result<Self, ContextJsonError> {
3334        let schema = schema
3335            .map(|(s, uid)| Self::get_context_schema(s, uid))
3336            .transpose()?;
3337        let context =
3338            entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
3339                .from_json_str(json)?;
3340        Ok(Self(context))
3341    }
3342
3343    /// Create a `Context` from a `serde_json::Value` (which must be a JSON object,
3344    /// not any other JSON type, or you will get an error here).
3345    /// JSON here must use the `__entity` and `__extn` escapes for entity
3346    /// references, extension values, etc.
3347    ///
3348    /// If a `schema` is provided, this will inform the parsing: for instance, it
3349    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
3350    /// if attributes have the wrong types (e.g., string instead of integer).
3351    /// Since different Actions have different schemas for `Context`, you also
3352    /// must specify the `Action` for schema-based parsing.
3353    /// ```
3354    /// use cedar_policy::{Context, RestrictedExpression, Schema};
3355    /// # use std::collections::HashMap;
3356    /// use std::str::FromStr;
3357    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, Request,PolicySet};
3358    /// let data = serde_json::json!(
3359    /// {
3360    ///     "sub": 1234
3361    /// });
3362    /// let schema_json = serde_json::json!(
3363    ///     {
3364    ///       "": {
3365    ///         "entityTypes": {
3366    ///           "User": {},
3367    ///           "Album": {},
3368    ///         },
3369    ///         "actions": {
3370    ///           "view": {
3371    ///              "appliesTo": {
3372    ///                "principalTypes": ["User"],
3373    ///                 "resourceTypes": ["Album"],
3374    ///                 "context": {
3375    ///                   "type": "Record",
3376    ///                   "attributes": {
3377    ///                     "sub": { "type": "Long" }
3378    ///                   }
3379    ///                 }
3380    ///               }
3381    ///             }
3382    ///           }
3383    ///       }
3384    ///     });
3385    /// # // create a request
3386    /// # let p_eid = EntityId::from_str("alice").unwrap();
3387    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
3388    /// # let principal = EntityUid::from_type_name_and_id(p_name, p_eid);
3389    /// let a_eid = EntityId::from_str("view").unwrap();
3390    /// let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
3391    /// let action = EntityUid::from_type_name_and_id(a_name, a_eid);
3392    /// # let r_eid = EntityId::from_str("trip").unwrap();
3393    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
3394    /// # let resource = EntityUid::from_type_name_and_id(r_name, r_eid);
3395    /// let schema = Schema::from_json_value(schema_json).unwrap();
3396    /// let context = Context::from_json_value(data, Some((&schema, &action))).unwrap();
3397    /// # let request: Request = Request::new(Some(principal), Some(action), Some(resource), context, Some(&schema)).unwrap();
3398    /// ```
3399    pub fn from_json_value(
3400        json: serde_json::Value,
3401        schema: Option<(&Schema, &EntityUid)>,
3402    ) -> Result<Self, ContextJsonError> {
3403        let schema = schema
3404            .map(|(s, uid)| Self::get_context_schema(s, uid))
3405            .transpose()?;
3406        let context =
3407            entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
3408                .from_json_value(json)?;
3409        Ok(Self(context))
3410    }
3411
3412    /// Create a `Context` from a JSON file.  The JSON file must contain a JSON
3413    /// object, not any other JSON type, or you will get an error here.
3414    /// JSON here must use the `__entity` and `__extn` escapes for entity
3415    /// references, extension values, etc.
3416    ///
3417    /// If a `schema` is provided, this will inform the parsing: for instance, it
3418    /// will allow `__entity` and `__extn` escapes to be implicit, and it will error
3419    /// if attributes have the wrong types (e.g., string instead of integer).
3420    /// Since different Actions have different schemas for `Context`, you also
3421    /// must specify the `Action` for schema-based parsing.
3422    /// ```no_run
3423    /// # use cedar_policy::{Context, RestrictedExpression};
3424    /// # use cedar_policy::{Entities, EntityId, EntityTypeName, EntityUid, Request,PolicySet};
3425    /// # use std::collections::HashMap;
3426    /// # use std::str::FromStr;
3427    /// # use std::fs::File;
3428    /// let mut json = File::open("json_file.txt").unwrap();
3429    /// let context = Context::from_json_file(&json, None).unwrap();
3430    /// # // create a request
3431    /// # let p_eid = EntityId::from_str("alice").unwrap();
3432    /// # let p_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
3433    /// # let p = EntityUid::from_type_name_and_id(p_name, p_eid);
3434    /// #
3435    /// # let a_eid = EntityId::from_str("view").unwrap();
3436    /// # let a_name: EntityTypeName = EntityTypeName::from_str("Action").unwrap();
3437    /// # let a = EntityUid::from_type_name_and_id(a_name, a_eid);
3438    /// # let r_eid = EntityId::from_str("trip").unwrap();
3439    /// # let r_name: EntityTypeName = EntityTypeName::from_str("Album").unwrap();
3440    /// # let r = EntityUid::from_type_name_and_id(r_name, r_eid);
3441    /// # let request: Request = Request::new(Some(p), Some(a), Some(r), context, None).unwrap();
3442    /// ```
3443    pub fn from_json_file(
3444        json: impl std::io::Read,
3445        schema: Option<(&Schema, &EntityUid)>,
3446    ) -> Result<Self, ContextJsonError> {
3447        let schema = schema
3448            .map(|(s, uid)| Self::get_context_schema(s, uid))
3449            .transpose()?;
3450        let context =
3451            entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
3452                .from_json_file(json)?;
3453        Ok(Self(context))
3454    }
3455
3456    /// Internal helper function to convert `(&Schema, &EntityUid)` to `impl ContextSchema`
3457    fn get_context_schema(
3458        schema: &Schema,
3459        action: &EntityUid,
3460    ) -> Result<impl ContextSchema, ContextJsonError> {
3461        cedar_policy_validator::context_schema_for_action(&schema.0, &action.0).ok_or_else(|| {
3462            ContextJsonError::MissingAction {
3463                action: action.clone(),
3464            }
3465        })
3466    }
3467}
3468
3469/// Error type for parsing `Context` from JSON
3470#[derive(Debug, Error)]
3471pub enum ContextJsonError {
3472    /// Error deserializing the JSON into a Context
3473    #[error(transparent)]
3474    JsonDeserialization(#[from] ContextJsonDeserializationError),
3475    /// The supplied action doesn't exist in the supplied schema
3476    #[error("action `{action}` does not exist in the supplied schema")]
3477    MissingAction {
3478        /// UID of the action which doesn't exist
3479        action: EntityUid,
3480    },
3481}
3482
3483impl std::fmt::Display for Request {
3484    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3485        write!(f, "{}", self.0)
3486    }
3487}
3488
3489/// Result of Evaluation
3490#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
3491pub enum EvalResult {
3492    /// Boolean value
3493    Bool(bool),
3494    /// Signed integer value
3495    Long(i64),
3496    /// String value
3497    String(String),
3498    /// Entity Uid
3499    EntityUid(EntityUid),
3500    /// A first-class set
3501    Set(Set),
3502    /// A first-class anonymous record
3503    Record(Record),
3504    /// An extension value, currently limited to String results
3505    ExtensionValue(String),
3506    // ExtensionValue(std::sync::Arc<dyn InternalExtensionValue>),
3507}
3508
3509/// Sets of Cedar values
3510#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
3511pub struct Set(BTreeSet<EvalResult>);
3512
3513impl Set {
3514    /// Iterate over the members of the set
3515    pub fn iter(&self) -> impl Iterator<Item = &EvalResult> {
3516        self.0.iter()
3517    }
3518
3519    /// Is a given element in the set
3520    pub fn contains(&self, elem: &EvalResult) -> bool {
3521        self.0.contains(elem)
3522    }
3523
3524    /// Get the number of members of the set
3525    pub fn len(&self) -> usize {
3526        self.0.len()
3527    }
3528
3529    /// Test if the set is empty
3530    pub fn is_empty(&self) -> bool {
3531        self.0.is_empty()
3532    }
3533}
3534
3535/// A record of Cedar values
3536#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
3537pub struct Record(BTreeMap<String, EvalResult>);
3538
3539impl Record {
3540    /// Iterate over the attribute/value pairs in the record
3541    pub fn iter(&self) -> impl Iterator<Item = (&String, &EvalResult)> {
3542        self.0.iter()
3543    }
3544
3545    /// Check if a given attribute is in the record
3546    pub fn contains_attribute(&self, key: impl AsRef<str>) -> bool {
3547        self.0.contains_key(key.as_ref())
3548    }
3549
3550    /// Get a given attribute from the record
3551    pub fn get(&self, key: impl AsRef<str>) -> Option<&EvalResult> {
3552        self.0.get(key.as_ref())
3553    }
3554
3555    /// Get the number of attributes in the record
3556    pub fn len(&self) -> usize {
3557        self.0.len()
3558    }
3559
3560    /// Test if the record is empty
3561    pub fn is_empty(&self) -> bool {
3562        self.0.is_empty()
3563    }
3564}
3565
3566#[doc(hidden)]
3567impl From<ast::Value> for EvalResult {
3568    fn from(v: ast::Value) -> Self {
3569        match v {
3570            ast::Value::Lit(ast::Literal::Bool(b)) => Self::Bool(b),
3571            ast::Value::Lit(ast::Literal::Long(i)) => Self::Long(i),
3572            ast::Value::Lit(ast::Literal::String(s)) => Self::String(s.to_string()),
3573            ast::Value::Lit(ast::Literal::EntityUID(e)) => {
3574                Self::EntityUid(EntityUid(ast::EntityUID::clone(&e)))
3575            }
3576            ast::Value::Set(s) => Self::Set(Set(s
3577                .authoritative
3578                .iter()
3579                .map(|v| v.clone().into())
3580                .collect())),
3581            ast::Value::Record(r) => Self::Record(Record(
3582                r.iter()
3583                    .map(|(k, v)| (k.to_string(), v.clone().into()))
3584                    .collect(),
3585            )),
3586            ast::Value::ExtensionValue(v) => Self::ExtensionValue(v.to_string()),
3587        }
3588    }
3589}
3590impl std::fmt::Display for EvalResult {
3591    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3592        match self {
3593            Self::Bool(b) => write!(f, "{b}"),
3594            Self::Long(l) => write!(f, "{l}"),
3595            Self::String(s) => write!(f, "\"{}\"", s.escape_debug()),
3596            Self::EntityUid(uid) => write!(f, "{uid}"),
3597            Self::Set(s) => {
3598                write!(f, "[")?;
3599                for (i, ev) in s.iter().enumerate() {
3600                    write!(f, "{ev}")?;
3601                    if (i + 1) < s.len() {
3602                        write!(f, ", ")?;
3603                    }
3604                }
3605                write!(f, "]")?;
3606                Ok(())
3607            }
3608            Self::Record(r) => {
3609                write!(f, "{{")?;
3610                for (i, (k, v)) in r.iter().enumerate() {
3611                    write!(f, "\"{}\": {v}", k.escape_debug())?;
3612                    if (i + 1) < r.len() {
3613                        write!(f, ", ")?;
3614                    }
3615                }
3616                write!(f, "}}")?;
3617                Ok(())
3618            }
3619            Self::ExtensionValue(s) => write!(f, "{s}"),
3620        }
3621    }
3622}
3623
3624/// Evaluates an expression.
3625/// If evaluation results in an error (e.g., attempting to access a non-existent Entity or Record,
3626/// passing the wrong number of arguments to a function etc.), that error is returned as a String
3627pub fn eval_expression(
3628    request: &Request,
3629    entities: &Entities,
3630    expr: &Expression,
3631) -> Result<EvalResult, EvaluationError> {
3632    let all_ext = Extensions::all_available();
3633    let eval = Evaluator::new(request.0.clone(), &entities.0, &all_ext);
3634    Ok(EvalResult::from(
3635        // Evaluate under the empty slot map, as an expression should not have slots
3636        eval.interpret(&expr.0, &ast::SlotEnv::new())?,
3637    ))
3638}
3639
3640#[cfg(test)]
3641#[cfg(feature = "partial-eval")]
3642mod partial_eval_test {
3643    use std::collections::HashSet;
3644
3645    use crate::{AuthorizationError, PolicyId, PolicySet, ResidualResponse};
3646
3647    #[test]
3648    fn test_pe_response_constructor() {
3649        let p: PolicySet = "permit(principal, action, resource);".parse().unwrap();
3650        let reason: HashSet<PolicyId> = std::iter::once("id1".parse().unwrap()).collect();
3651        let errors: Vec<AuthorizationError> = std::iter::empty().collect();
3652        let a = ResidualResponse::new(p.clone(), reason.clone(), errors.clone());
3653        assert_eq!(a.diagnostics().errors, errors);
3654        assert_eq!(a.diagnostics().reason, reason);
3655        assert_eq!(a.residuals(), &p);
3656    }
3657}