cedar_policy/
api.rs

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