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