cedar_policy_validator/schema/
err.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use cedar_policy_core::{
18    ast::{EntityUID, ReservedNameError},
19    transitive_closure,
20};
21use itertools::{Either, Itertools};
22use miette::Diagnostic;
23use nonempty::NonEmpty;
24use thiserror::Error;
25
26use crate::cedar_schema;
27
28/// Error creating a schema from the Cedar syntax
29#[derive(Debug, Error, Diagnostic)]
30pub enum CedarSchemaError {
31    /// Errors with the schema content
32    #[error(transparent)]
33    #[diagnostic(transparent)]
34    Schema(#[from] SchemaError),
35    /// IO error
36    #[error(transparent)]
37    IO(#[from] std::io::Error),
38    /// Parse error
39    #[error(transparent)]
40    #[diagnostic(transparent)]
41    Parsing(#[from] CedarSchemaParseError),
42}
43
44/// Error parsing a Cedar-syntax schema
45// WARNING: this type is publicly exported from `cedar-policy`
46#[derive(Debug, Error)]
47#[error("error parsing schema: {errs}")]
48pub struct CedarSchemaParseError {
49    /// Underlying parse error(s)
50    errs: cedar_schema::parser::CedarSchemaParseErrors,
51    /// Did the schema look like it was intended to be JSON format instead of
52    /// Cedar?
53    suspect_json_format: bool,
54}
55
56impl Diagnostic for CedarSchemaParseError {
57    fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
58        let suspect_json_help = if self.suspect_json_format {
59            Some(Box::new("this API was expecting a schema in the Cedar schema format; did you mean to use a different function, which expects a JSON-format Cedar schema"))
60        } else {
61            None
62        };
63        match (suspect_json_help, self.errs.help()) {
64            (Some(json), Some(inner)) => Some(Box::new(format!("{inner}\n{json}"))),
65            (Some(h), None) => Some(h),
66            (None, Some(h)) => Some(h),
67            (None, None) => None,
68        }
69    }
70
71    // Everything else is forwarded to `errs`
72
73    fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
74        self.errs.code()
75    }
76    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
77        self.errs.labels()
78    }
79    fn severity(&self) -> Option<miette::Severity> {
80        self.errs.severity()
81    }
82    fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
83        self.errs.url()
84    }
85    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
86        self.errs.source_code()
87    }
88    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
89        self.errs.diagnostic_source()
90    }
91    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
92        self.errs.related()
93    }
94}
95
96impl CedarSchemaParseError {
97    /// `errs`: the `cedar_schema::parser::CedarSyntaxParseErrors` that were thrown
98    ///
99    /// `src`: the Cedar-syntax text that we were trying to parse
100    pub(crate) fn new(errs: cedar_schema::parser::CedarSchemaParseErrors, src: &str) -> Self {
101        // let's see what the first non-whitespace character is
102        let suspect_json_format = match src.trim_start().chars().next() {
103            None => false, // schema is empty or only whitespace; the problem is unlikely to be JSON vs Cedar format
104            Some('{') => true, // yes, this looks like it was intended to be a JSON schema
105            Some(_) => false, // any character other than '{', not likely it was intended to be a JSON schema
106        };
107        Self {
108            errs,
109            suspect_json_format,
110        }
111    }
112
113    /// Did the schema look like it was JSON data?
114    /// If so, it was probably intended to be parsed as the JSON schema format.
115    /// In that case, the reported errors are probably not super helpful.
116    /// (This check is provided on a best-effort basis)
117    pub fn suspect_json_format(&self) -> bool {
118        self.suspect_json_format
119    }
120
121    /// Get the errors that were encountered while parsing
122    pub fn errors(&self) -> &cedar_schema::parser::CedarSchemaParseErrors {
123        &self.errs
124    }
125}
126
127/// Error when constructing a schema
128//
129// CAUTION: this type is publicly exported in `cedar-policy`.
130// Don't make fields `pub`, don't make breaking changes, and use caution
131// when adding public methods.
132#[derive(Debug, Diagnostic, Error)]
133#[non_exhaustive]
134pub enum SchemaError {
135    /// Error thrown by the `serde_json` crate during serialization
136    #[error(transparent)]
137    #[diagnostic(transparent)]
138    JsonSerialization(#[from] schema_errors::JsonSerializationError),
139    /// This error is thrown when `serde_json` fails to deserialize the JSON
140    #[error(transparent)]
141    #[diagnostic(transparent)]
142    JsonDeserialization(#[from] schema_errors::JsonDeserializationError),
143    /// Errors occurring while computing or enforcing transitive closure on
144    /// action hierarchy.
145    #[error(transparent)]
146    #[diagnostic(transparent)]
147    ActionTransitiveClosure(#[from] schema_errors::ActionTransitiveClosureError),
148    /// Errors occurring while computing or enforcing transitive closure on
149    /// entity type hierarchy.
150    #[error(transparent)]
151    #[diagnostic(transparent)]
152    EntityTypeTransitiveClosure(#[from] schema_errors::EntityTypeTransitiveClosureError),
153    /// Error generated when processing a schema file that uses unsupported features
154    #[error(transparent)]
155    #[diagnostic(transparent)]
156    UnsupportedFeature(#[from] schema_errors::UnsupportedFeatureError),
157    /// Undeclared entity type(s) used in the `memberOf` field of an entity
158    /// type, the `appliesTo` fields of an action, or an attribute type in a
159    /// context or entity attribute record. Entity types in the error message
160    /// are fully qualified, including any implicit or explicit namespaces.
161    #[error(transparent)]
162    #[diagnostic(transparent)]
163    UndeclaredEntityTypes(#[from] schema_errors::UndeclaredEntityTypesError),
164    /// This error occurs when we cannot resolve a typename (because it refers
165    /// to an entity type or common type that was not defined).
166    #[error(transparent)]
167    #[diagnostic(transparent)]
168    TypeNotDefined(#[from] schema_errors::TypeNotDefinedError),
169    /// This error occurs when we cannot resolve an action name used in the
170    /// `memberOf` field of an action (because it refers to an action that was
171    /// not defined).
172    #[error(transparent)]
173    #[diagnostic(transparent)]
174    ActionNotDefined(#[from] schema_errors::ActionNotDefinedError),
175    /// Entity/common type shadowing error. Some shadowing relationships are not
176    /// allowed for clarity reasons; see
177    /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
178    #[error(transparent)]
179    #[diagnostic(transparent)]
180    TypeShadowing(#[from] schema_errors::TypeShadowingError),
181    /// Action shadowing error. Some shadowing relationships are not
182    /// allowed for clarity reasons; see
183    /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
184    #[error(transparent)]
185    #[diagnostic(transparent)]
186    ActionShadowing(#[from] schema_errors::ActionShadowingError),
187    /// Duplicate specifications for an entity type
188    #[error(transparent)]
189    #[diagnostic(transparent)]
190    DuplicateEntityType(#[from] schema_errors::DuplicateEntityTypeError),
191    /// Duplicate specifications for an action
192    #[error(transparent)]
193    #[diagnostic(transparent)]
194    DuplicateAction(#[from] schema_errors::DuplicateActionError),
195    /// Duplicate specification for a common type declaration
196    #[error(transparent)]
197    #[diagnostic(transparent)]
198    DuplicateCommonType(#[from] schema_errors::DuplicateCommonTypeError),
199    /// Cycle in the schema's action hierarchy.
200    #[error(transparent)]
201    #[diagnostic(transparent)]
202    CycleInActionHierarchy(#[from] schema_errors::CycleInActionHierarchyError),
203    /// Cycle in the schema's common type declarations.
204    #[error(transparent)]
205    #[diagnostic(transparent)]
206    CycleInCommonTypeReferences(#[from] schema_errors::CycleInCommonTypeReferencesError),
207    /// The schema file included an entity type `Action` in the entity type
208    /// list. The `Action` entity type is always implicitly declared, and it
209    /// cannot currently have attributes or be in any groups, so there is no
210    /// purposes in adding an explicit entry.
211    #[error(transparent)]
212    #[diagnostic(transparent)]
213    ActionEntityTypeDeclared(#[from] schema_errors::ActionEntityTypeDeclaredError),
214    /// `context` or `shape` fields are not records
215    #[error(transparent)]
216    #[diagnostic(transparent)]
217    ContextOrShapeNotRecord(#[from] schema_errors::ContextOrShapeNotRecordError),
218    /// An action entity (transitively) has an attribute that is an empty set.
219    /// The validator cannot assign a type to an empty set.
220    /// This error variant should only be used when `PermitAttributes` is enabled.
221    #[error(transparent)]
222    #[diagnostic(transparent)]
223    ActionAttributesContainEmptySet(#[from] schema_errors::ActionAttributesContainEmptySetError),
224    /// An action entity (transitively) has an attribute of unsupported type (`ExprEscape`, `EntityEscape` or `ExtnEscape`).
225    /// This error variant should only be used when `PermitAttributes` is enabled.
226    #[error(transparent)]
227    #[diagnostic(transparent)]
228    UnsupportedActionAttribute(#[from] schema_errors::UnsupportedActionAttributeError),
229    /// Error when evaluating an action attribute
230    #[error(transparent)]
231    #[diagnostic(transparent)]
232    ActionAttrEval(#[from] schema_errors::ActionAttrEvalError),
233    /// Error thrown when the schema contains the `__expr` escape.
234    /// Support for this escape form has been dropped.
235    #[error(transparent)]
236    #[diagnostic(transparent)]
237    ExprEscapeUsed(#[from] schema_errors::ExprEscapeUsedError),
238    /// The schema used an extension type that the validator doesn't know about.
239    #[error(transparent)]
240    #[diagnostic(transparent)]
241    UnknownExtensionType(schema_errors::UnknownExtensionTypeError),
242    /// The schema used a reserved namespace or typename (as of this writing, just `__cedar`).
243    #[error(transparent)]
244    #[diagnostic(transparent)]
245    ReservedName(#[from] ReservedNameError),
246    /// Could not find a definition for a common type, at a point in the code
247    /// where internal invariants should guarantee that we would find one.
248    #[error(transparent)]
249    #[diagnostic(transparent)]
250    CommonTypeInvariantViolation(#[from] schema_errors::CommonTypeInvariantViolationError),
251    /// Could not find a definition for an action, at a point in the code where
252    /// internal invariants should guarantee that we would find one.
253    #[error(transparent)]
254    #[diagnostic(transparent)]
255    ActionInvariantViolation(#[from] schema_errors::ActionInvariantViolationError),
256}
257
258impl From<transitive_closure::TcError<EntityUID>> for SchemaError {
259    fn from(e: transitive_closure::TcError<EntityUID>) -> Self {
260        // we use code in transitive_closure to check for cycles in the action
261        // hierarchy, but in case of an error we want to report the more descriptive
262        // CycleInActionHierarchy instead of ActionTransitiveClosureError
263        match e {
264            transitive_closure::TcError::MissingTcEdge { .. } => {
265                SchemaError::ActionTransitiveClosure(Box::new(e).into())
266            }
267            transitive_closure::TcError::HasCycle(err) => {
268                schema_errors::CycleInActionHierarchyError {
269                    uid: err.vertex_with_loop().clone(),
270                }
271                .into()
272            }
273        }
274    }
275}
276
277impl SchemaError {
278    /// Given one or more `SchemaError`, collect them into a single `SchemaError`.
279    /// Due to current structures, some errors may have to be dropped in some cases.
280    pub fn join_nonempty(errs: NonEmpty<SchemaError>) -> SchemaError {
281        // if we have any `TypeNotDefinedError`s, we can report all of those at once (but have to drop the others).
282        // Same for `ActionNotDefinedError`s.
283        // Any other error, we can just report the first one and have to drop the others.
284        let (type_ndef_errors, non_type_ndef_errors): (Vec<_>, Vec<_>) =
285            errs.into_iter().partition_map(|e| match e {
286                SchemaError::TypeNotDefined(e) => Either::Left(e),
287                _ => Either::Right(e),
288            });
289        if let Some(errs) = NonEmpty::from_vec(type_ndef_errors) {
290            schema_errors::TypeNotDefinedError::join_nonempty(errs).into()
291        } else {
292            let (action_ndef_errors, other_errors): (Vec<_>, Vec<_>) =
293                non_type_ndef_errors.into_iter().partition_map(|e| match e {
294                    SchemaError::ActionNotDefined(e) => Either::Left(e),
295                    _ => Either::Right(e),
296                });
297            if let Some(errs) = NonEmpty::from_vec(action_ndef_errors) {
298                schema_errors::ActionNotDefinedError::join_nonempty(errs).into()
299            } else {
300                // We partitioned a `NonEmpty` (`errs`) into what we now know is an empty vector
301                // (`type_ndef_errors`) and `non_type_ndef_errors`, so `non_type_ndef_errors` cannot
302                // be empty. Then we partitioned `non_type_ndef_errors` into what we now know is an
303                // empty vector (`action_ndef_errors`) and `other_errors`, so `other_errors` cannot
304                // be empty.
305                // PANIC SAFETY: see comments immediately above
306                #[allow(clippy::expect_used)]
307                other_errors.into_iter().next().expect("cannot be empty")
308            }
309        }
310    }
311}
312
313impl From<NonEmpty<SchemaError>> for SchemaError {
314    fn from(errs: NonEmpty<SchemaError>) -> Self {
315        Self::join_nonempty(errs)
316    }
317}
318
319impl From<NonEmpty<schema_errors::ActionNotDefinedError>> for SchemaError {
320    fn from(errs: NonEmpty<schema_errors::ActionNotDefinedError>) -> Self {
321        Self::ActionNotDefined(schema_errors::ActionNotDefinedError::join_nonempty(errs))
322    }
323}
324
325impl From<NonEmpty<schema_errors::TypeNotDefinedError>> for SchemaError {
326    fn from(errs: NonEmpty<schema_errors::TypeNotDefinedError>) -> Self {
327        Self::TypeNotDefined(schema_errors::TypeNotDefinedError::join_nonempty(errs))
328    }
329}
330
331/// Convenience alias
332pub type Result<T> = std::result::Result<T, SchemaError>;
333
334/// Error subtypes for [`SchemaError`]
335pub mod schema_errors {
336    use std::fmt::Display;
337
338    use cedar_policy_core::ast::{
339        EntityAttrEvaluationError, EntityType, EntityUID, InternalName, Name,
340    };
341    use cedar_policy_core::parser::{join_with_conjunction, Loc};
342    use cedar_policy_core::{
343        impl_diagnostic_from_method_on_field, impl_diagnostic_from_method_on_nonempty_field,
344        transitive_closure,
345    };
346    use itertools::Itertools;
347    use miette::Diagnostic;
348    use nonempty::NonEmpty;
349    use smol_str::SmolStr;
350    use thiserror::Error;
351
352    /// JSON deserialization error
353    //
354    // CAUTION: this type is publicly exported in `cedar-policy`.
355    // Don't make fields `pub`, don't make breaking changes, and use caution
356    // when adding public methods.
357    #[derive(Debug, Diagnostic, Error)]
358    #[error(transparent)]
359    pub struct JsonSerializationError(#[from] pub(crate) serde_json::Error);
360
361    /// Transitive closure of action hierarchy computation or enforcement error
362    //
363    // CAUTION: this type is publicly exported in `cedar-policy`.
364    // Don't make fields `pub`, don't make breaking changes, and use caution
365    // when adding public methods.
366    #[derive(Debug, Diagnostic, Error)]
367    #[error("transitive closure computation/enforcement error on action hierarchy")]
368    #[diagnostic(transparent)]
369    pub struct ActionTransitiveClosureError(
370        #[from] pub(crate) Box<transitive_closure::TcError<EntityUID>>,
371    );
372
373    /// Transitive closure of entity type hierarchy computation or enforcement error
374    //
375    // CAUTION: this type is publicly exported in `cedar-policy`.
376    // Don't make fields `pub`, don't make breaking changes, and use caution
377    // when adding public methods.
378    #[derive(Debug, Diagnostic, Error)]
379    #[error("transitive closure computation/enforcement error on entity type hierarchy")]
380    #[diagnostic(transparent)]
381    pub struct EntityTypeTransitiveClosureError(
382        #[from] pub(crate) Box<transitive_closure::TcError<EntityType>>,
383    );
384
385    /// Undeclared entity types error
386    //
387    // CAUTION: this type is publicly exported in `cedar-policy`.
388    // Don't make fields `pub`, don't make breaking changes, and use caution
389    // when adding public methods.
390    #[derive(Debug, Error)]
391    pub struct UndeclaredEntityTypesError {
392        /// Entity type(s) which were not declared
393        pub(crate) types: NonEmpty<EntityType>,
394    }
395
396    impl Display for UndeclaredEntityTypesError {
397        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398            if self.types.len() == 1 {
399                write!(f, "undeclared entity type: ")?;
400            } else {
401                write!(f, "undeclared entity types: ")?;
402            }
403            join_with_conjunction(f, "and", self.types.iter().sorted_unstable(), |f, s| {
404                s.fmt(f)
405            })
406        }
407    }
408
409    impl Diagnostic for UndeclaredEntityTypesError {
410        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
411            Some(Box::new("any entity types appearing anywhere in a schema need to be declared in `entityTypes`"))
412        }
413
414        impl_diagnostic_from_method_on_nonempty_field!(types, loc);
415    }
416
417    /// Type resolution error
418    //
419    // CAUTION: this type is publicly exported in `cedar-policy`.
420    // Don't make fields `pub`, don't make breaking changes, and use caution
421    // when adding public methods.
422    #[derive(Debug, Error)]
423    #[error("failed to resolve type{}: {}", if .undefined_types.len() > 1 { "s" } else { "" }, .undefined_types.iter().map(crate::ConditionalName::raw).join(", "))]
424    pub struct TypeNotDefinedError {
425        /// Names of type(s) which were not defined
426        pub(crate) undefined_types: NonEmpty<crate::ConditionalName>,
427    }
428
429    impl Diagnostic for TypeNotDefinedError {
430        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
431            // we choose to give only the help for the first failed-to-resolve name, because otherwise the help message would be too cluttered and complicated
432            Some(Box::new(
433                self.undefined_types.first().resolution_failure_help(),
434            ))
435        }
436
437        impl_diagnostic_from_method_on_nonempty_field!(undefined_types, loc);
438    }
439
440    impl TypeNotDefinedError {
441        /// Combine all the errors into a single [`TypeNotDefinedError`].
442        ///
443        /// This cannot fail, because `NonEmpty` guarantees there is at least
444        /// one error to join.
445        pub(crate) fn join_nonempty(errs: NonEmpty<TypeNotDefinedError>) -> Self {
446            Self {
447                undefined_types: errs.flat_map(|err| err.undefined_types),
448            }
449        }
450    }
451
452    impl From<NonEmpty<TypeNotDefinedError>> for TypeNotDefinedError {
453        fn from(value: NonEmpty<TypeNotDefinedError>) -> Self {
454            Self::join_nonempty(value)
455        }
456    }
457
458    /// Action resolution error
459    //
460    // CAUTION: this type is publicly exported in `cedar-policy`.
461    // Don't make fields `pub`, don't make breaking changes, and use caution
462    // when adding public methods.
463    #[derive(Debug, Diagnostic, Error)]
464    #[diagnostic(help("any actions appearing as parents need to be declared as actions"))]
465    pub struct ActionNotDefinedError(
466        pub(crate) NonEmpty<crate::json_schema::ActionEntityUID<crate::ConditionalName>>,
467    );
468
469    impl ActionNotDefinedError {
470        /// Combine all the errors into a single [`ActionNotDefinedError`].
471        ///
472        /// This cannot fail, because `NonEmpty` guarantees there is at least
473        /// one error to join.
474        pub(crate) fn join_nonempty(errs: NonEmpty<ActionNotDefinedError>) -> Self {
475            Self(errs.flat_map(|err| err.0))
476        }
477    }
478
479    impl From<NonEmpty<ActionNotDefinedError>> for ActionNotDefinedError {
480        fn from(value: NonEmpty<ActionNotDefinedError>) -> Self {
481            Self::join_nonempty(value)
482        }
483    }
484
485    impl Display for ActionNotDefinedError {
486        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487            if self.0.len() == 1 {
488                write!(f, "undeclared action: ")?;
489            } else {
490                write!(f, "undeclared actions: ")?;
491            }
492            join_with_conjunction(
493                f,
494                "and",
495                self.0.iter().map(|aeuid| aeuid.as_raw()),
496                |f, s| s.fmt(f),
497            )
498        }
499    }
500
501    /// Entity/common type shadowing error. Some shadowing relationships are not
502    /// allowed for clarity reasons; see
503    /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
504    //
505    // CAUTION: this type is publicly exported in `cedar-policy`.
506    // Don't make fields `pub`, don't make breaking changes, and use caution
507    // when adding public methods.
508    #[derive(Debug, Error)]
509    #[error(
510        "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
511    )]
512    pub struct TypeShadowingError {
513        /// Definition that is being shadowed illegally
514        pub(crate) shadowed_def: InternalName,
515        /// Definition that is responsible for shadowing it illegally
516        pub(crate) shadowing_def: InternalName,
517    }
518
519    impl Diagnostic for TypeShadowingError {
520        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
521            Some(Box::new(format!(
522                "try renaming one of the definitions, or moving `{}` to a different namespace",
523                self.shadowed_def
524            )))
525        }
526
527        // we use the location of the `shadowing_def` as the location of the error
528        // possible future improvement: provide two underlines
529        impl_diagnostic_from_method_on_field!(shadowing_def, loc);
530    }
531
532    /// Action shadowing error. Some shadowing relationships are not allowed for
533    /// clarity reasons; see
534    /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
535    //
536    // CAUTION: this type is publicly exported in `cedar-policy`.
537    // Don't make fields `pub`, don't make breaking changes, and use caution
538    // when adding public methods.
539    #[derive(Debug, Error)]
540    #[error(
541        "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
542    )]
543    pub struct ActionShadowingError {
544        /// Definition that is being shadowed illegally
545        pub(crate) shadowed_def: EntityUID,
546        /// Definition that is responsible for shadowing it illegally
547        pub(crate) shadowing_def: EntityUID,
548    }
549
550    impl Diagnostic for ActionShadowingError {
551        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
552            Some(Box::new(format!(
553                "try renaming one of the actions, or moving `{}` to a different namespace",
554                self.shadowed_def
555            )))
556        }
557
558        // we use the location of the `shadowing_def` as the location of the error
559        // possible future improvement: provide two underlines
560        impl_diagnostic_from_method_on_field!(shadowing_def, loc);
561    }
562
563    /// Duplicate entity type error
564    //
565    // CAUTION: this type is publicly exported in `cedar-policy`.
566    // Don't make fields `pub`, don't make breaking changes, and use caution
567    // when adding public methods.
568    #[derive(Debug, Error)]
569    #[error("duplicate entity type `{ty}`")]
570    pub struct DuplicateEntityTypeError {
571        pub(crate) ty: EntityType,
572    }
573
574    impl Diagnostic for DuplicateEntityTypeError {
575        impl_diagnostic_from_method_on_field!(ty, loc);
576    }
577
578    /// Duplicate action error
579    //
580    // CAUTION: this type is publicly exported in `cedar-policy`.
581    // Don't make fields `pub`, don't make breaking changes, and use caution
582    // when adding public methods.
583    #[derive(Debug, Diagnostic, Error)]
584    #[error("duplicate action `{0}`")]
585    pub struct DuplicateActionError(pub(crate) SmolStr);
586
587    /// Duplicate common type error
588    //
589    // CAUTION: this type is publicly exported in `cedar-policy`.
590    // Don't make fields `pub`, don't make breaking changes, and use caution
591    // when adding public methods.
592    #[derive(Debug, Error)]
593    #[error("duplicate common type `{ty}`")]
594    pub struct DuplicateCommonTypeError {
595        pub(crate) ty: InternalName,
596    }
597
598    impl Diagnostic for DuplicateCommonTypeError {
599        impl_diagnostic_from_method_on_field!(ty, loc);
600    }
601
602    /// Cycle in action hierarchy error
603    //
604    // CAUTION: this type is publicly exported in `cedar-policy`.
605    // Don't make fields `pub`, don't make breaking changes, and use caution
606    // when adding public methods.
607    #[derive(Debug, Error)]
608    #[error("cycle in action hierarchy containing `{uid}`")]
609    pub struct CycleInActionHierarchyError {
610        pub(crate) uid: EntityUID,
611    }
612
613    impl Diagnostic for CycleInActionHierarchyError {
614        impl_diagnostic_from_method_on_field!(uid, loc);
615    }
616
617    /// Cycle in common type hierarchy error
618    //
619    // CAUTION: this type is publicly exported in `cedar-policy`.
620    // Don't make fields `pub`, don't make breaking changes, and use caution
621    // when adding public methods.
622    #[derive(Debug, Error)]
623    #[error("cycle in common type references containing `{ty}`")]
624    pub struct CycleInCommonTypeReferencesError {
625        pub(crate) ty: InternalName,
626    }
627
628    impl Diagnostic for CycleInCommonTypeReferencesError {
629        impl_diagnostic_from_method_on_field!(ty, loc);
630    }
631
632    /// Action declared in `entityType` list error
633    //
634    // CAUTION: this type is publicly exported in `cedar-policy`.
635    // Don't make fields `pub`, don't make breaking changes, and use caution
636    // when adding public methods.
637    #[derive(Debug, Clone, Diagnostic, Error)]
638    #[error("entity type `Action` declared in `entityTypes` list")]
639    pub struct ActionEntityTypeDeclaredError {}
640
641    /// Context or entity type shape not declared as record error
642    //
643    // CAUTION: this type is publicly exported in `cedar-policy`.
644    // Don't make fields `pub`, don't make breaking changes, and use caution
645    // when adding public methods.
646    #[derive(Debug, Error)]
647    #[error("{ctx_or_shape} is declared with a type other than `Record`")]
648    pub struct ContextOrShapeNotRecordError {
649        pub(crate) ctx_or_shape: ContextOrShape,
650    }
651
652    impl Diagnostic for ContextOrShapeNotRecordError {
653        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
654            match &self.ctx_or_shape {
655                ContextOrShape::ActionContext(_) => {
656                    Some(Box::new("action contexts must have type `Record`"))
657                }
658                ContextOrShape::EntityTypeShape(_) => {
659                    Some(Box::new("entity type shapes must have type `Record`"))
660                }
661            }
662        }
663
664        impl_diagnostic_from_method_on_field!(ctx_or_shape, loc);
665    }
666
667    /// Action attributes contain empty set error
668    //
669    // CAUTION: this type is publicly exported in `cedar-policy`.
670    // Don't make fields `pub`, don't make breaking changes, and use caution
671    // when adding public methods.
672    #[derive(Debug, Error)]
673    #[error("action `{uid}` has an attribute that is an empty set")]
674    pub struct ActionAttributesContainEmptySetError {
675        pub(crate) uid: EntityUID,
676    }
677
678    impl Diagnostic for ActionAttributesContainEmptySetError {
679        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
680            Some(Box::new(
681                "actions are not currently allowed to have attributes whose value is an empty set",
682            ))
683        }
684
685        impl_diagnostic_from_method_on_field!(uid, loc);
686    }
687
688    /// Unsupported action attribute error
689    //
690    // CAUTION: this type is publicly exported in `cedar-policy`.
691    // Don't make fields `pub`, don't make breaking changes, and use caution
692    // when adding public methods.
693    #[derive(Debug, Error)]
694    #[error("action `{uid}` has an attribute with unsupported JSON representation: {attr}")]
695    pub struct UnsupportedActionAttributeError {
696        pub(crate) uid: EntityUID,
697        pub(crate) attr: SmolStr,
698    }
699
700    impl Diagnostic for UnsupportedActionAttributeError {
701        impl_diagnostic_from_method_on_field!(uid, loc);
702    }
703
704    /// Unsupported `__expr` escape error
705    //
706    // CAUTION: this type is publicly exported in `cedar-policy`.
707    // Don't make fields `pub`, don't make breaking changes, and use caution
708    // when adding public methods.
709    #[derive(Debug, Clone, Diagnostic, Error)]
710    #[error("the `__expr` escape is no longer supported")]
711    #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
712    pub struct ExprEscapeUsedError {}
713
714    /// Action attribute evaluation error
715    //
716    // CAUTION: this type is publicly exported in `cedar-policy`.
717    // Don't make fields `pub`, don't make breaking changes, and use caution
718    // when adding public methods.
719    #[derive(Debug, Diagnostic, Error)]
720    #[error(transparent)]
721    #[diagnostic(transparent)]
722    pub struct ActionAttrEvalError(#[from] pub(crate) EntityAttrEvaluationError);
723
724    /// Unsupported feature error
725    //
726    // CAUTION: this type is publicly exported in `cedar-policy`.
727    // Don't make fields `pub`, don't make breaking changes, and use caution
728    // when adding public methods.
729    #[derive(Debug, Diagnostic, Error)]
730    #[error("unsupported feature used in schema")]
731    #[diagnostic(transparent)]
732    pub struct UnsupportedFeatureError(#[from] pub(crate) UnsupportedFeature);
733
734    #[derive(Debug)]
735    pub(crate) enum ContextOrShape {
736        ActionContext(EntityUID),
737        EntityTypeShape(EntityType),
738    }
739
740    impl ContextOrShape {
741        pub fn loc(&self) -> Option<&Loc> {
742            match self {
743                ContextOrShape::ActionContext(uid) => uid.loc(),
744                ContextOrShape::EntityTypeShape(ty) => ty.loc(),
745            }
746        }
747    }
748
749    impl std::fmt::Display for ContextOrShape {
750        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
751            match self {
752                ContextOrShape::ActionContext(action) => write!(f, "Context for action {}", action),
753                ContextOrShape::EntityTypeShape(entity_type) => {
754                    write!(f, "Shape for entity type {}", entity_type)
755                }
756            }
757        }
758    }
759
760    #[derive(Debug, Diagnostic, Error)]
761    pub(crate) enum UnsupportedFeature {
762        #[error("records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled")]
763        OpenRecordsAndEntities,
764        // Action attributes are allowed if `ActionBehavior` is `PermitAttributes`
765        #[error("action declared with attributes: [{}]", .0.iter().join(", "))]
766        ActionAttributes(Vec<String>),
767    }
768
769    /// This error is thrown when `serde_json` fails to deserialize the JSON
770    //
771    // CAUTION: this type is publicly exported in `cedar-policy`.
772    // Don't make fields `pub`, don't make breaking changes, and use caution
773    // when adding public methods.
774    #[derive(Debug, Error)]
775    #[error("{err}")]
776    pub struct JsonDeserializationError {
777        /// Error thrown by the `serde_json` crate
778        err: serde_json::Error,
779        /// Possible fix for the error
780        advice: Option<JsonDeserializationAdvice>,
781    }
782
783    impl Diagnostic for JsonDeserializationError {
784        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
785            self.advice
786                .as_ref()
787                .map(|h| Box::new(h) as Box<dyn Display>)
788        }
789    }
790
791    #[derive(Debug, Error)]
792    enum JsonDeserializationAdvice {
793        #[error("this API was expecting a schema in the JSON format; did you mean to use a different function, which expects the Cedar schema format?")]
794        CedarFormat,
795        #[error("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{{ \"\": {{..}} }}`")]
796        MissingNamespace,
797    }
798
799    impl JsonDeserializationError {
800        /// `err`: the `serde_json::Error` that was thrown
801        ///
802        /// `src`: the JSON that we were trying to deserialize (if available in string form)
803        pub(crate) fn new(err: serde_json::Error, src: Option<&str>) -> Self {
804            match src {
805                None => Self { err, advice: None },
806                Some(src) => {
807                    // let's see what the first non-whitespace character is
808                    let advice = match src.trim_start().chars().next() {
809                        None => None, // schema is empty or only whitespace; the problem is unlikely to be JSON vs Cedar format
810                        Some('{') => {
811                            // This looks like it was intended to be a JSON schema. Check fields of top level JSON object to see
812                            // if it looks like it's missing a namespace.
813                            if let Ok(serde_json::Value::Object(obj)) =
814                                serde_json::from_str::<serde_json::Value>(src)
815                            {
816                                if obj.contains_key("entityTypes")
817                                    || obj.contains_key("actions")
818                                    || obj.contains_key("commonTypes")
819                                {
820                                    // These keys are expected inside a namespace, so it's likely the user forgot to specify a
821                                    // namespace if they're at the top level of the schema json object.
822                                    Some(JsonDeserializationAdvice::MissingNamespace)
823                                } else {
824                                    // Probably something wrong inside a namespace definition.
825                                    None
826                                }
827                            } else {
828                                // Invalid JSON
829                                None
830                            }
831                        }
832                        Some(_) => Some(JsonDeserializationAdvice::CedarFormat), // any character other than '{', we suspect it might be a Cedar-format schema
833                    };
834                    Self { err, advice }
835                }
836            }
837        }
838    }
839
840    /// Unknown extension type error
841    //
842    // CAUTION: this type is publicly exported in `cedar-policy`.
843    // Don't make fields `pub`, don't make breaking changes, and use caution
844    // when adding public methods.
845    #[derive(Error, Debug)]
846    #[error("unknown extension type `{actual}`")]
847    pub struct UnknownExtensionTypeError {
848        pub(crate) actual: Name,
849        pub(crate) suggested_replacement: Option<String>,
850    }
851
852    impl Diagnostic for UnknownExtensionTypeError {
853        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
854            self.suggested_replacement.as_ref().map(|suggestion| {
855                Box::new(format!("did you mean `{suggestion}`?")) as Box<dyn Display>
856            })
857        }
858
859        impl_diagnostic_from_method_on_field!(actual, loc);
860    }
861
862    /// Could not find a definition for a common type, at a point in the code
863    /// where internal invariants should guarantee that we would find one.
864    //
865    // CAUTION: this type is publicly exported in `cedar-policy`.
866    // Don't make fields `pub`, don't make breaking changes, and use caution
867    // when adding public methods.
868    #[derive(Error, Debug)]
869    #[error("internal invariant violated: failed to find a common-type definition for {name}")]
870    pub struct CommonTypeInvariantViolationError {
871        /// Fully-qualified [`InternalName`] of the common type we failed to find a definition for
872        pub(crate) name: InternalName,
873    }
874
875    impl Diagnostic for CommonTypeInvariantViolationError {
876        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
877            Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
878        }
879
880        impl_diagnostic_from_method_on_field!(name, loc);
881    }
882
883    /// Could not find a definition for an action, at a point in the code where
884    /// internal invariants should guarantee that we would find one.
885    //
886    // CAUTION: this type is publicly exported in `cedar-policy`.
887    // Don't make fields `pub`, don't make breaking changes, and use caution
888    // when adding public methods.
889    #[derive(Error, Debug)]
890    #[error("internal invariant violated: failed to find {} for {}", if .euids.len() > 1 { "action definitions" } else { "an action definition" }, .euids.iter().join(", "))]
891    pub struct ActionInvariantViolationError {
892        /// Fully-qualified [`EntityUID`]s of the action(s) we failed to find a definition for
893        pub(crate) euids: NonEmpty<EntityUID>,
894    }
895
896    impl Diagnostic for ActionInvariantViolationError {
897        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
898            Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
899        }
900
901        impl_diagnostic_from_method_on_nonempty_field!(euids, loc);
902    }
903}