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