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
313/// Convenience alias
314pub type Result<T> = std::result::Result<T, SchemaError>;
315
316/// Error subtypes for [`SchemaError`]
317pub mod schema_errors {
318    use std::fmt::Display;
319
320    use cedar_policy_core::ast::{
321        EntityAttrEvaluationError, EntityType, EntityUID, InternalName, Name,
322    };
323    use cedar_policy_core::parser::{join_with_conjunction, Loc};
324    use cedar_policy_core::{
325        impl_diagnostic_from_method_on_field, impl_diagnostic_from_method_on_nonempty_field,
326        transitive_closure,
327    };
328    use itertools::Itertools;
329    use miette::Diagnostic;
330    use nonempty::NonEmpty;
331    use smol_str::SmolStr;
332    use thiserror::Error;
333
334    /// JSON deserialization error
335    //
336    // CAUTION: this type is publicly exported in `cedar-policy`.
337    // Don't make fields `pub`, don't make breaking changes, and use caution
338    // when adding public methods.
339    #[derive(Debug, Diagnostic, Error)]
340    #[error(transparent)]
341    pub struct JsonSerializationError(#[from] pub(crate) serde_json::Error);
342
343    /// Transitive closure of action hierarchy computation or enforcement error
344    //
345    // CAUTION: this type is publicly exported in `cedar-policy`.
346    // Don't make fields `pub`, don't make breaking changes, and use caution
347    // when adding public methods.
348    #[derive(Debug, Diagnostic, Error)]
349    #[error("transitive closure computation/enforcement error on action hierarchy")]
350    #[diagnostic(transparent)]
351    pub struct ActionTransitiveClosureError(
352        #[from] pub(crate) Box<transitive_closure::TcError<EntityUID>>,
353    );
354
355    /// Transitive closure of entity type hierarchy computation or enforcement error
356    //
357    // CAUTION: this type is publicly exported in `cedar-policy`.
358    // Don't make fields `pub`, don't make breaking changes, and use caution
359    // when adding public methods.
360    #[derive(Debug, Diagnostic, Error)]
361    #[error("transitive closure computation/enforcement error on entity type hierarchy")]
362    #[diagnostic(transparent)]
363    pub struct EntityTypeTransitiveClosureError(
364        #[from] pub(crate) Box<transitive_closure::TcError<EntityType>>,
365    );
366
367    /// Undeclared entity types error
368    //
369    // CAUTION: this type is publicly exported in `cedar-policy`.
370    // Don't make fields `pub`, don't make breaking changes, and use caution
371    // when adding public methods.
372    #[derive(Debug, Error)]
373    pub struct UndeclaredEntityTypesError {
374        /// Entity type(s) which were not declared
375        pub(crate) types: NonEmpty<EntityType>,
376    }
377
378    impl Display for UndeclaredEntityTypesError {
379        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380            if self.types.len() == 1 {
381                write!(f, "undeclared entity type: ")?;
382            } else {
383                write!(f, "undeclared entity types: ")?;
384            }
385            join_with_conjunction(f, "and", self.types.iter().sorted_unstable(), |f, s| {
386                s.fmt(f)
387            })
388        }
389    }
390
391    impl Diagnostic for UndeclaredEntityTypesError {
392        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
393            Some(Box::new("any entity types appearing anywhere in a schema need to be declared in `entityTypes`"))
394        }
395
396        impl_diagnostic_from_method_on_nonempty_field!(types, loc);
397    }
398
399    /// Type resolution error
400    //
401    // CAUTION: this type is publicly exported in `cedar-policy`.
402    // Don't make fields `pub`, don't make breaking changes, and use caution
403    // when adding public methods.
404    #[derive(Debug, Error)]
405    #[error("failed to resolve type{}: {}", if .undefined_types.len() > 1 { "s" } else { "" }, .undefined_types.iter().map(crate::ConditionalName::raw).join(", "))]
406    pub struct TypeNotDefinedError {
407        /// Names of type(s) which were not defined
408        pub(crate) undefined_types: NonEmpty<crate::ConditionalName>,
409    }
410
411    impl Diagnostic for TypeNotDefinedError {
412        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
413            // 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
414            Some(Box::new(
415                self.undefined_types.first().resolution_failure_help(),
416            ))
417        }
418
419        impl_diagnostic_from_method_on_nonempty_field!(undefined_types, loc);
420    }
421
422    impl TypeNotDefinedError {
423        /// Combine all the errors into a single [`TypeNotDefinedError`].
424        ///
425        /// This cannot fail, because `NonEmpty` guarantees there is at least
426        /// one error to join.
427        pub(crate) fn join_nonempty(errs: NonEmpty<TypeNotDefinedError>) -> Self {
428            Self {
429                undefined_types: errs.flat_map(|err| err.undefined_types),
430            }
431        }
432    }
433
434    /// Action resolution error
435    //
436    // CAUTION: this type is publicly exported in `cedar-policy`.
437    // Don't make fields `pub`, don't make breaking changes, and use caution
438    // when adding public methods.
439    #[derive(Debug, Diagnostic, Error)]
440    #[diagnostic(help("any actions appearing as parents need to be declared as actions"))]
441    pub struct ActionNotDefinedError(
442        pub(crate) NonEmpty<crate::json_schema::ActionEntityUID<crate::ConditionalName>>,
443    );
444
445    impl ActionNotDefinedError {
446        /// Combine all the errors into a single [`ActionNotDefinedError`].
447        ///
448        /// This cannot fail, because `NonEmpty` guarantees there is at least
449        /// one error to join.
450        pub(crate) fn join_nonempty(errs: NonEmpty<ActionNotDefinedError>) -> Self {
451            Self(errs.flat_map(|err| err.0))
452        }
453    }
454
455    impl Display for ActionNotDefinedError {
456        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
457            if self.0.len() == 1 {
458                write!(f, "undeclared action: ")?;
459            } else {
460                write!(f, "undeclared actions: ")?;
461            }
462            join_with_conjunction(
463                f,
464                "and",
465                self.0.iter().map(|aeuid| aeuid.as_raw()),
466                |f, s| s.fmt(f),
467            )
468        }
469    }
470
471    /// Entity/common type shadowing error. Some shadowing relationships are not
472    /// allowed for clarity reasons; see
473    /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
474    //
475    // CAUTION: this type is publicly exported in `cedar-policy`.
476    // Don't make fields `pub`, don't make breaking changes, and use caution
477    // when adding public methods.
478    #[derive(Debug, Error)]
479    #[error(
480        "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
481    )]
482    pub struct TypeShadowingError {
483        /// Definition that is being shadowed illegally
484        pub(crate) shadowed_def: InternalName,
485        /// Definition that is responsible for shadowing it illegally
486        pub(crate) shadowing_def: InternalName,
487    }
488
489    impl Diagnostic for TypeShadowingError {
490        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
491            Some(Box::new(format!(
492                "try renaming one of the definitions, or moving `{}` to a different namespace",
493                self.shadowed_def
494            )))
495        }
496
497        // we use the location of the `shadowing_def` as the location of the error
498        // possible future improvement: provide two underlines
499        impl_diagnostic_from_method_on_field!(shadowing_def, loc);
500    }
501
502    /// Action shadowing error. Some shadowing relationships are not allowed for
503    /// clarity reasons; see
504    /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
505    //
506    // CAUTION: this type is publicly exported in `cedar-policy`.
507    // Don't make fields `pub`, don't make breaking changes, and use caution
508    // when adding public methods.
509    #[derive(Debug, Error)]
510    #[error(
511        "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
512    )]
513    pub struct ActionShadowingError {
514        /// Definition that is being shadowed illegally
515        pub(crate) shadowed_def: EntityUID,
516        /// Definition that is responsible for shadowing it illegally
517        pub(crate) shadowing_def: EntityUID,
518    }
519
520    impl Diagnostic for ActionShadowingError {
521        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
522            Some(Box::new(format!(
523                "try renaming one of the actions, or moving `{}` to a different namespace",
524                self.shadowed_def
525            )))
526        }
527
528        // we use the location of the `shadowing_def` as the location of the error
529        // possible future improvement: provide two underlines
530        impl_diagnostic_from_method_on_field!(shadowing_def, loc);
531    }
532
533    /// Duplicate entity type error
534    //
535    // CAUTION: this type is publicly exported in `cedar-policy`.
536    // Don't make fields `pub`, don't make breaking changes, and use caution
537    // when adding public methods.
538    #[derive(Debug, Error)]
539    #[error("duplicate entity type `{ty}`")]
540    pub struct DuplicateEntityTypeError {
541        pub(crate) ty: EntityType,
542    }
543
544    impl Diagnostic for DuplicateEntityTypeError {
545        impl_diagnostic_from_method_on_field!(ty, loc);
546    }
547
548    /// Duplicate action error
549    //
550    // CAUTION: this type is publicly exported in `cedar-policy`.
551    // Don't make fields `pub`, don't make breaking changes, and use caution
552    // when adding public methods.
553    #[derive(Debug, Diagnostic, Error)]
554    #[error("duplicate action `{0}`")]
555    pub struct DuplicateActionError(pub(crate) SmolStr);
556
557    /// Duplicate common type error
558    //
559    // CAUTION: this type is publicly exported in `cedar-policy`.
560    // Don't make fields `pub`, don't make breaking changes, and use caution
561    // when adding public methods.
562    #[derive(Debug, Error)]
563    #[error("duplicate common type `{ty}`")]
564    pub struct DuplicateCommonTypeError {
565        pub(crate) ty: InternalName,
566    }
567
568    impl Diagnostic for DuplicateCommonTypeError {
569        impl_diagnostic_from_method_on_field!(ty, loc);
570    }
571
572    /// Cycle in action hierarchy error
573    //
574    // CAUTION: this type is publicly exported in `cedar-policy`.
575    // Don't make fields `pub`, don't make breaking changes, and use caution
576    // when adding public methods.
577    #[derive(Debug, Error)]
578    #[error("cycle in action hierarchy containing `{uid}`")]
579    pub struct CycleInActionHierarchyError {
580        pub(crate) uid: EntityUID,
581    }
582
583    impl Diagnostic for CycleInActionHierarchyError {
584        impl_diagnostic_from_method_on_field!(uid, loc);
585    }
586
587    /// Cycle in common type hierarchy 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("cycle in common type references containing `{ty}`")]
594    pub struct CycleInCommonTypeReferencesError {
595        pub(crate) ty: InternalName,
596    }
597
598    impl Diagnostic for CycleInCommonTypeReferencesError {
599        impl_diagnostic_from_method_on_field!(ty, loc);
600    }
601
602    /// Action declared in `entityType` list 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, Clone, Diagnostic, Error)]
608    #[error("entity type `Action` declared in `entityTypes` list")]
609    pub struct ActionEntityTypeDeclaredError {}
610
611    /// Context or entity type shape not declared as record error
612    //
613    // CAUTION: this type is publicly exported in `cedar-policy`.
614    // Don't make fields `pub`, don't make breaking changes, and use caution
615    // when adding public methods.
616    #[derive(Debug, Error)]
617    #[error("{ctx_or_shape} is declared with a type other than `Record`")]
618    pub struct ContextOrShapeNotRecordError {
619        pub(crate) ctx_or_shape: ContextOrShape,
620    }
621
622    impl Diagnostic for ContextOrShapeNotRecordError {
623        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
624            match &self.ctx_or_shape {
625                ContextOrShape::ActionContext(_) => {
626                    Some(Box::new("action contexts must have type `Record`"))
627                }
628                ContextOrShape::EntityTypeShape(_) => {
629                    Some(Box::new("entity type shapes must have type `Record`"))
630                }
631            }
632        }
633
634        impl_diagnostic_from_method_on_field!(ctx_or_shape, loc);
635    }
636
637    /// Action attributes contain empty set error
638    //
639    // CAUTION: this type is publicly exported in `cedar-policy`.
640    // Don't make fields `pub`, don't make breaking changes, and use caution
641    // when adding public methods.
642    #[derive(Debug, Error)]
643    #[error("action `{uid}` has an attribute that is an empty set")]
644    pub struct ActionAttributesContainEmptySetError {
645        pub(crate) uid: EntityUID,
646    }
647
648    impl Diagnostic for ActionAttributesContainEmptySetError {
649        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
650            Some(Box::new(
651                "actions are not currently allowed to have attributes whose value is an empty set",
652            ))
653        }
654
655        impl_diagnostic_from_method_on_field!(uid, loc);
656    }
657
658    /// Unsupported action attribute error
659    //
660    // CAUTION: this type is publicly exported in `cedar-policy`.
661    // Don't make fields `pub`, don't make breaking changes, and use caution
662    // when adding public methods.
663    #[derive(Debug, Error)]
664    #[error("action `{uid}` has an attribute with unsupported JSON representation: {attr}")]
665    pub struct UnsupportedActionAttributeError {
666        pub(crate) uid: EntityUID,
667        pub(crate) attr: SmolStr,
668    }
669
670    impl Diagnostic for UnsupportedActionAttributeError {
671        impl_diagnostic_from_method_on_field!(uid, loc);
672    }
673
674    /// Unsupported `__expr` escape error
675    //
676    // CAUTION: this type is publicly exported in `cedar-policy`.
677    // Don't make fields `pub`, don't make breaking changes, and use caution
678    // when adding public methods.
679    #[derive(Debug, Clone, Diagnostic, Error)]
680    #[error("the `__expr` escape is no longer supported")]
681    #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
682    pub struct ExprEscapeUsedError {}
683
684    /// Action attribute evaluation error
685    //
686    // CAUTION: this type is publicly exported in `cedar-policy`.
687    // Don't make fields `pub`, don't make breaking changes, and use caution
688    // when adding public methods.
689    #[derive(Debug, Diagnostic, Error)]
690    #[error(transparent)]
691    #[diagnostic(transparent)]
692    pub struct ActionAttrEvalError(#[from] pub(crate) EntityAttrEvaluationError);
693
694    /// Unsupported feature error
695    //
696    // CAUTION: this type is publicly exported in `cedar-policy`.
697    // Don't make fields `pub`, don't make breaking changes, and use caution
698    // when adding public methods.
699    #[derive(Debug, Diagnostic, Error)]
700    #[error("unsupported feature used in schema")]
701    #[diagnostic(transparent)]
702    pub struct UnsupportedFeatureError(#[from] pub(crate) UnsupportedFeature);
703
704    #[derive(Debug)]
705    pub(crate) enum ContextOrShape {
706        ActionContext(EntityUID),
707        EntityTypeShape(EntityType),
708    }
709
710    impl ContextOrShape {
711        pub fn loc(&self) -> Option<&Loc> {
712            match self {
713                ContextOrShape::ActionContext(uid) => uid.loc(),
714                ContextOrShape::EntityTypeShape(ty) => ty.loc(),
715            }
716        }
717    }
718
719    impl std::fmt::Display for ContextOrShape {
720        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721            match self {
722                ContextOrShape::ActionContext(action) => write!(f, "Context for action {}", action),
723                ContextOrShape::EntityTypeShape(entity_type) => {
724                    write!(f, "Shape for entity type {}", entity_type)
725                }
726            }
727        }
728    }
729
730    #[derive(Debug, Diagnostic, Error)]
731    pub(crate) enum UnsupportedFeature {
732        #[error("records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled")]
733        OpenRecordsAndEntities,
734        // Action attributes are allowed if `ActionBehavior` is `PermitAttributes`
735        #[error("action declared with attributes: [{}]", .0.iter().join(", "))]
736        ActionAttributes(Vec<String>),
737    }
738
739    /// This error is thrown when `serde_json` fails to deserialize the JSON
740    //
741    // CAUTION: this type is publicly exported in `cedar-policy`.
742    // Don't make fields `pub`, don't make breaking changes, and use caution
743    // when adding public methods.
744    #[derive(Debug, Error)]
745    #[error("{err}")]
746    pub struct JsonDeserializationError {
747        /// Error thrown by the `serde_json` crate
748        err: serde_json::Error,
749        /// Possible fix for the error
750        advice: Option<JsonDeserializationAdvice>,
751    }
752
753    impl Diagnostic for JsonDeserializationError {
754        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
755            self.advice
756                .as_ref()
757                .map(|h| Box::new(h) as Box<dyn Display>)
758        }
759    }
760
761    #[derive(Debug, Error)]
762    enum JsonDeserializationAdvice {
763        #[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?")]
764        CedarFormat,
765        #[error("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{{ \"\": {{..}} }}`")]
766        MissingNamespace,
767    }
768
769    impl JsonDeserializationError {
770        /// `err`: the `serde_json::Error` that was thrown
771        ///
772        /// `src`: the JSON that we were trying to deserialize (if available in string form)
773        pub(crate) fn new(err: serde_json::Error, src: Option<&str>) -> Self {
774            match src {
775                None => Self { err, advice: None },
776                Some(src) => {
777                    // let's see what the first non-whitespace character is
778                    let advice = match src.trim_start().chars().next() {
779                        None => None, // schema is empty or only whitespace; the problem is unlikely to be JSON vs Cedar format
780                        Some('{') => {
781                            // This looks like it was intended to be a JSON schema. Check fields of top level JSON object to see
782                            // if it looks like it's missing a namespace.
783                            if let Ok(serde_json::Value::Object(obj)) =
784                                serde_json::from_str::<serde_json::Value>(src)
785                            {
786                                if obj.contains_key("entityTypes")
787                                    || obj.contains_key("actions")
788                                    || obj.contains_key("commonTypes")
789                                {
790                                    // These keys are expected inside a namespace, so it's likely the user forgot to specify a
791                                    // namespace if they're at the top level of the schema json object.
792                                    Some(JsonDeserializationAdvice::MissingNamespace)
793                                } else {
794                                    // Probably something wrong inside a namespace definition.
795                                    None
796                                }
797                            } else {
798                                // Invalid JSON
799                                None
800                            }
801                        }
802                        Some(_) => Some(JsonDeserializationAdvice::CedarFormat), // any character other than '{', we suspect it might be a Cedar-format schema
803                    };
804                    Self { err, advice }
805                }
806            }
807        }
808    }
809
810    /// Unknown extension type error
811    //
812    // CAUTION: this type is publicly exported in `cedar-policy`.
813    // Don't make fields `pub`, don't make breaking changes, and use caution
814    // when adding public methods.
815    #[derive(Error, Debug)]
816    #[error("unknown extension type `{actual}`")]
817    pub struct UnknownExtensionTypeError {
818        pub(crate) actual: Name,
819        pub(crate) suggested_replacement: Option<String>,
820    }
821
822    impl Diagnostic for UnknownExtensionTypeError {
823        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
824            self.suggested_replacement.as_ref().map(|suggestion| {
825                Box::new(format!("did you mean `{suggestion}`?")) as Box<dyn Display>
826            })
827        }
828
829        impl_diagnostic_from_method_on_field!(actual, loc);
830    }
831
832    /// Could not find a definition for a common type, at a point in the code
833    /// where internal invariants should guarantee that we would find one.
834    //
835    // CAUTION: this type is publicly exported in `cedar-policy`.
836    // Don't make fields `pub`, don't make breaking changes, and use caution
837    // when adding public methods.
838    #[derive(Error, Debug)]
839    #[error("internal invariant violated: failed to find a common-type definition for {name}")]
840    pub struct CommonTypeInvariantViolationError {
841        /// Fully-qualified [`InternalName`] of the common type we failed to find a definition for
842        pub(crate) name: InternalName,
843    }
844
845    impl Diagnostic for CommonTypeInvariantViolationError {
846        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
847            Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
848        }
849
850        impl_diagnostic_from_method_on_field!(name, loc);
851    }
852
853    /// Could not find a definition for an action, at a point in the code where
854    /// internal invariants should guarantee that we would find one.
855    //
856    // CAUTION: this type is publicly exported in `cedar-policy`.
857    // Don't make fields `pub`, don't make breaking changes, and use caution
858    // when adding public methods.
859    #[derive(Error, Debug)]
860    #[error("internal invariant violated: failed to find {} for {}", if .euids.len() > 1 { "action definitions" } else { "an action definition" }, .euids.iter().join(", "))]
861    pub struct ActionInvariantViolationError {
862        /// Fully-qualified [`EntityUID`]s of the action(s) we failed to find a definition for
863        pub(crate) euids: NonEmpty<EntityUID>,
864    }
865
866    impl Diagnostic for ActionInvariantViolationError {
867        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
868            Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
869        }
870
871        impl_diagnostic_from_method_on_nonempty_field!(euids, loc);
872    }
873}