Skip to main content

cedar_policy_core/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 crate::{
18    ast::{EntityUID, ReservedNameError},
19    transitive_closure,
20};
21use itertools::{Either, Itertools};
22use miette::Diagnostic;
23use nonempty::NonEmpty;
24use thiserror::Error;
25
26use crate::validator::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    /// This error variant is deprecated and will never be returned.
219    #[error(transparent)]
220    #[diagnostic(transparent)]
221    #[deprecated = "this error is deprecated and should never be returned"]
222    #[expect(deprecated, reason = "inner variant is deprecated too")]
223    ActionAttributesContainEmptySet(#[from] schema_errors::ActionAttributesContainEmptySetError),
224    /// This error variant is deprecated and will never be returned.
225    #[error(transparent)]
226    #[diagnostic(transparent)]
227    #[deprecated = "this error is deprecated and should never be returned"]
228    #[expect(deprecated, reason = "inner variant is deprecated too")]
229    UnsupportedActionAttribute(#[from] schema_errors::UnsupportedActionAttributeError),
230    /// This error variant is deprecated and will never be returned.
231    #[error(transparent)]
232    #[diagnostic(transparent)]
233    #[deprecated = "this error is deprecated and should never be returned"]
234    #[expect(deprecated, reason = "inner variant is deprecated too")]
235    ActionAttrEval(#[from] schema_errors::ActionAttrEvalError),
236    /// This error variant is deprecated and will never be returned.
237    #[error(transparent)]
238    #[diagnostic(transparent)]
239    #[deprecated = "this error is deprecated and should never be returned"]
240    #[expect(deprecated, reason = "inner variant is deprecated too")]
241    ExprEscapeUsed(#[from] schema_errors::ExprEscapeUsedError),
242    /// The schema used an extension type that the validator doesn't know about.
243    #[error(transparent)]
244    #[diagnostic(transparent)]
245    UnknownExtensionType(schema_errors::UnknownExtensionTypeError),
246    /// The schema used a reserved namespace or typename (as of this writing, just `__cedar`).
247    #[error(transparent)]
248    #[diagnostic(transparent)]
249    ReservedName(#[from] ReservedNameError),
250    /// Could not find a definition for a common type, at a point in the code
251    /// where internal invariants should guarantee that we would find one.
252    #[error(transparent)]
253    #[diagnostic(transparent)]
254    CommonTypeInvariantViolation(#[from] schema_errors::CommonTypeInvariantViolationError),
255    /// Could not find a definition for an action, at a point in the code where
256    /// internal invariants should guarantee that we would find one.
257    #[error(transparent)]
258    #[diagnostic(transparent)]
259    ActionInvariantViolation(#[from] schema_errors::ActionInvariantViolationError),
260}
261
262impl From<transitive_closure::TcError<EntityUID>> for SchemaError {
263    fn from(e: transitive_closure::TcError<EntityUID>) -> Self {
264        // we use code in transitive_closure to check for cycles in the action
265        // hierarchy, but in case of an error we want to report the more descriptive
266        // CycleInActionHierarchy instead of ActionTransitiveClosureError
267        match e {
268            transitive_closure::TcError::MissingTcEdge { .. } => {
269                SchemaError::ActionTransitiveClosure(Box::new(e).into())
270            }
271            transitive_closure::TcError::HasCycle(err) => {
272                schema_errors::CycleInActionHierarchyError {
273                    uid: err.vertex_with_loop().clone(),
274                }
275                .into()
276            }
277        }
278    }
279}
280
281impl SchemaError {
282    /// Given one or more `SchemaError`, collect them into a single `SchemaError`.
283    /// Due to current structures, some errors may have to be dropped in some cases.
284    pub fn join_nonempty(errs: NonEmpty<SchemaError>) -> SchemaError {
285        // if we have any `TypeNotDefinedError`s, we can report all of those at once (but have to drop the others).
286        // Same for `ActionNotDefinedError`s.
287        // Any other error, we can just report the first one and have to drop the others.
288        let (type_ndef_errors, non_type_ndef_errors): (Vec<_>, Vec<_>) =
289            errs.into_iter().partition_map(|e| match e {
290                SchemaError::TypeNotDefined(e) => Either::Left(e),
291                _ => Either::Right(e),
292            });
293        if let Some(errs) = NonEmpty::from_vec(type_ndef_errors) {
294            schema_errors::TypeNotDefinedError::join_nonempty(errs).into()
295        } else {
296            let (action_ndef_errors, other_errors): (Vec<_>, Vec<_>) =
297                non_type_ndef_errors.into_iter().partition_map(|e| match e {
298                    SchemaError::ActionNotDefined(e) => Either::Left(e),
299                    _ => Either::Right(e),
300                });
301            if let Some(errs) = NonEmpty::from_vec(action_ndef_errors) {
302                schema_errors::ActionNotDefinedError::join_nonempty(errs).into()
303            } else {
304                // We partitioned a `NonEmpty` (`errs`) into what we now know is an empty vector
305                // (`type_ndef_errors`) and `non_type_ndef_errors`, so `non_type_ndef_errors` cannot
306                // be empty. Then we partitioned `non_type_ndef_errors` into what we now know is an
307                // empty vector (`action_ndef_errors`) and `other_errors`, so `other_errors` cannot
308                // be empty.
309                #[expect(
310                    clippy::expect_used,
311                    reason = "other_errors cannot be empty due to partitioning logic explained in comment above"
312                )]
313                other_errors.into_iter().next().expect("cannot be empty")
314            }
315        }
316    }
317}
318
319impl From<NonEmpty<SchemaError>> for SchemaError {
320    fn from(errs: NonEmpty<SchemaError>) -> Self {
321        Self::join_nonempty(errs)
322    }
323}
324
325impl From<NonEmpty<schema_errors::ActionNotDefinedError>> for SchemaError {
326    fn from(errs: NonEmpty<schema_errors::ActionNotDefinedError>) -> Self {
327        Self::ActionNotDefined(schema_errors::ActionNotDefinedError::join_nonempty(errs))
328    }
329}
330
331impl From<NonEmpty<schema_errors::TypeNotDefinedError>> for SchemaError {
332    fn from(errs: NonEmpty<schema_errors::TypeNotDefinedError>) -> Self {
333        Self::TypeNotDefined(schema_errors::TypeNotDefinedError::join_nonempty(errs))
334    }
335}
336
337/// Convenience alias
338pub type Result<T> = std::result::Result<T, SchemaError>;
339
340/// Error subtypes for [`SchemaError`]
341pub mod schema_errors {
342
343    // If I don't allow this at the module level I get warnings about using a
344    // deprecated type when I try to _define_ the deprecated type, and it still
345    // doesn't work if I try to allow each type individually.
346    #![expect(deprecated, reason = "see comment immediately above")]
347
348    use std::fmt::Display;
349
350    use crate::ast::{EntityType, EntityUID, InternalName, Name};
351    use crate::parser::{join_with_conjunction, Loc};
352    use crate::transitive_closure;
353    use itertools::Itertools;
354    use miette::Diagnostic;
355    use nonempty::NonEmpty;
356    use smol_str::SmolStr;
357    use thiserror::Error;
358
359    /// JSON deserialization error
360    //
361    // CAUTION: this type is publicly exported in `cedar-policy`.
362    // Don't make fields `pub`, don't make breaking changes, and use caution
363    // when adding public methods.
364    #[derive(Debug, Diagnostic, Error)]
365    #[error(transparent)]
366    pub struct JsonSerializationError(#[from] pub(crate) serde_json::Error);
367
368    /// Transitive closure of action hierarchy computation or enforcement error
369    //
370    // CAUTION: this type is publicly exported in `cedar-policy`.
371    // Don't make fields `pub`, don't make breaking changes, and use caution
372    // when adding public methods.
373    #[derive(Debug, Diagnostic, Error)]
374    #[error("transitive closure computation/enforcement error on action hierarchy")]
375    #[diagnostic(transparent)]
376    pub struct ActionTransitiveClosureError(
377        #[from] pub(crate) Box<transitive_closure::TcError<EntityUID>>,
378    );
379
380    /// Transitive closure of entity type hierarchy computation or enforcement error
381    //
382    // CAUTION: this type is publicly exported in `cedar-policy`.
383    // Don't make fields `pub`, don't make breaking changes, and use caution
384    // when adding public methods.
385    #[derive(Debug, Diagnostic, Error)]
386    #[error("transitive closure computation/enforcement error on entity type hierarchy")]
387    #[diagnostic(transparent)]
388    pub struct EntityTypeTransitiveClosureError(
389        #[from] pub(crate) Box<transitive_closure::TcError<EntityType>>,
390    );
391
392    /// Undeclared entity types error
393    //
394    // CAUTION: this type is publicly exported in `cedar-policy`.
395    // Don't make fields `pub`, don't make breaking changes, and use caution
396    // when adding public methods.
397    #[derive(Debug, Error)]
398    pub struct UndeclaredEntityTypesError {
399        /// Entity type(s) which were not declared
400        pub(crate) types: NonEmpty<EntityType>,
401    }
402
403    impl Display for UndeclaredEntityTypesError {
404        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405            if self.types.len() == 1 {
406                write!(f, "undeclared entity type: ")?;
407            } else {
408                write!(f, "undeclared entity types: ")?;
409            }
410            join_with_conjunction(f, "and", self.types.iter().sorted_unstable(), |f, s| {
411                s.fmt(f)
412            })
413        }
414    }
415
416    impl Diagnostic for UndeclaredEntityTypesError {
417        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
418            Some(Box::new("any entity types appearing anywhere in a schema need to be declared in `entityTypes`"))
419        }
420
421        impl_diagnostic_from_method_on_nonempty_field!(types, loc);
422    }
423
424    /// Type resolution error
425    //
426    // CAUTION: this type is publicly exported in `cedar-policy`.
427    // Don't make fields `pub`, don't make breaking changes, and use caution
428    // when adding public methods.
429    #[derive(Debug, Error)]
430    #[error("failed to resolve type{}: {}", if .undefined_types.len() > 1 { "s" } else { "" }, .undefined_types.iter().map(crate::validator::ConditionalName::raw).join(", "))]
431    pub struct TypeNotDefinedError {
432        /// Names of type(s) which were not defined
433        pub(crate) undefined_types: NonEmpty<crate::validator::ConditionalName>,
434    }
435
436    impl Diagnostic for TypeNotDefinedError {
437        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
438            // 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
439            Some(Box::new(
440                self.undefined_types.first().resolution_failure_help(),
441            ))
442        }
443
444        impl_diagnostic_from_method_on_nonempty_field!(undefined_types, loc);
445    }
446
447    impl TypeNotDefinedError {
448        /// Combine all the errors into a single [`TypeNotDefinedError`].
449        ///
450        /// This cannot fail, because `NonEmpty` guarantees there is at least
451        /// one error to join.
452        pub(crate) fn join_nonempty(errs: NonEmpty<TypeNotDefinedError>) -> Self {
453            Self {
454                undefined_types: errs.flat_map(|err| err.undefined_types),
455            }
456        }
457    }
458
459    impl From<NonEmpty<TypeNotDefinedError>> for TypeNotDefinedError {
460        fn from(value: NonEmpty<TypeNotDefinedError>) -> Self {
461            Self::join_nonempty(value)
462        }
463    }
464
465    /// Action resolution error
466    //
467    // CAUTION: this type is publicly exported in `cedar-policy`.
468    // Don't make fields `pub`, don't make breaking changes, and use caution
469    // when adding public methods.
470    #[derive(Debug, Diagnostic, Error)]
471    #[diagnostic(help("any actions appearing as parents need to be declared as actions"))]
472    pub struct ActionNotDefinedError(
473        pub(crate)  NonEmpty<
474            crate::validator::json_schema::ActionEntityUID<crate::validator::ConditionalName>,
475        >,
476    );
477
478    impl ActionNotDefinedError {
479        /// Combine all the errors into a single [`ActionNotDefinedError`].
480        ///
481        /// This cannot fail, because `NonEmpty` guarantees there is at least
482        /// one error to join.
483        pub(crate) fn join_nonempty(errs: NonEmpty<ActionNotDefinedError>) -> Self {
484            Self(errs.flat_map(|err| err.0))
485        }
486    }
487
488    impl From<NonEmpty<ActionNotDefinedError>> for ActionNotDefinedError {
489        fn from(value: NonEmpty<ActionNotDefinedError>) -> Self {
490            Self::join_nonempty(value)
491        }
492    }
493
494    impl Display for ActionNotDefinedError {
495        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496            if self.0.len() == 1 {
497                write!(f, "undeclared action: ")?;
498            } else {
499                write!(f, "undeclared actions: ")?;
500            }
501            join_with_conjunction(
502                f,
503                "and",
504                self.0.iter().map(|aeuid| aeuid.as_raw()),
505                |f, s| s.fmt(f),
506            )
507        }
508    }
509
510    /// Entity/common type shadowing error. Some shadowing relationships are not
511    /// allowed for clarity reasons; see
512    /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
513    //
514    // CAUTION: this type is publicly exported in `cedar-policy`.
515    // Don't make fields `pub`, don't make breaking changes, and use caution
516    // when adding public methods.
517    #[derive(Debug, Error)]
518    #[error(
519        "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
520    )]
521    pub struct TypeShadowingError {
522        /// Definition that is being shadowed illegally
523        pub(crate) shadowed_def: InternalName,
524        /// Definition that is responsible for shadowing it illegally
525        pub(crate) shadowing_def: InternalName,
526    }
527
528    impl Diagnostic for TypeShadowingError {
529        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
530            Some(Box::new(format!(
531                "try renaming one of the definitions, or moving `{}` to a different namespace",
532                self.shadowed_def
533            )))
534        }
535
536        // we use the location of the `shadowing_def` as the location of the error
537        // possible future improvement: provide two underlines
538        impl_diagnostic_from_method_on_field!(shadowing_def, loc);
539    }
540
541    /// Action shadowing error. Some shadowing relationships are not allowed for
542    /// clarity reasons; see
543    /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
544    //
545    // CAUTION: this type is publicly exported in `cedar-policy`.
546    // Don't make fields `pub`, don't make breaking changes, and use caution
547    // when adding public methods.
548    #[derive(Debug, Error)]
549    #[error(
550        "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
551    )]
552    pub struct ActionShadowingError {
553        /// Definition that is being shadowed illegally
554        pub(crate) shadowed_def: EntityUID,
555        /// Definition that is responsible for shadowing it illegally
556        pub(crate) shadowing_def: EntityUID,
557    }
558
559    impl Diagnostic for ActionShadowingError {
560        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
561            Some(Box::new(format!(
562                "try renaming one of the actions, or moving `{}` to a different namespace",
563                self.shadowed_def
564            )))
565        }
566
567        // we use the location of the `shadowing_def` as the location of the error
568        // possible future improvement: provide two underlines
569        impl_diagnostic_from_method_on_field!(shadowing_def, loc);
570    }
571
572    /// Duplicate entity type 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("duplicate entity type `{ty}`")]
579    pub struct DuplicateEntityTypeError {
580        pub(crate) ty: EntityType,
581    }
582
583    impl Diagnostic for DuplicateEntityTypeError {
584        impl_diagnostic_from_method_on_field!(ty, loc);
585    }
586
587    /// Duplicate action 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, Diagnostic, Error)]
593    #[error("duplicate action `{0}`")]
594    pub struct DuplicateActionError(pub(crate) SmolStr);
595
596    /// Duplicate common type error
597    //
598    // CAUTION: this type is publicly exported in `cedar-policy`.
599    // Don't make fields `pub`, don't make breaking changes, and use caution
600    // when adding public methods.
601    #[derive(Debug, Error)]
602    #[error("duplicate common type `{ty}`")]
603    pub struct DuplicateCommonTypeError {
604        pub(crate) ty: InternalName,
605    }
606
607    impl Diagnostic for DuplicateCommonTypeError {
608        impl_diagnostic_from_method_on_field!(ty, loc);
609    }
610
611    /// Cycle in action hierarchy 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("cycle in action hierarchy containing `{uid}`")]
618    pub struct CycleInActionHierarchyError {
619        pub(crate) uid: EntityUID,
620    }
621
622    impl Diagnostic for CycleInActionHierarchyError {
623        impl_diagnostic_from_method_on_field!(uid, loc);
624    }
625
626    /// Cycle in common type hierarchy error
627    //
628    // CAUTION: this type is publicly exported in `cedar-policy`.
629    // Don't make fields `pub`, don't make breaking changes, and use caution
630    // when adding public methods.
631    #[derive(Debug, Error)]
632    #[error("cycle in common type references containing `{ty}`")]
633    pub struct CycleInCommonTypeReferencesError {
634        pub(crate) ty: InternalName,
635    }
636
637    impl Diagnostic for CycleInCommonTypeReferencesError {
638        impl_diagnostic_from_method_on_field!(ty, loc);
639    }
640
641    /// Action declared in `entityType` list 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, Clone, Diagnostic, Error)]
647    #[error("entity type `Action` declared in `entityTypes` list")]
648    pub struct ActionEntityTypeDeclaredError {}
649
650    /// Context or entity type shape not declared as record error
651    //
652    // CAUTION: this type is publicly exported in `cedar-policy`.
653    // Don't make fields `pub`, don't make breaking changes, and use caution
654    // when adding public methods.
655    #[derive(Debug, Error)]
656    #[error("{ctx_or_shape} is declared with a type other than `Record`")]
657    pub struct ContextOrShapeNotRecordError {
658        pub(crate) ctx_or_shape: ContextOrShape,
659    }
660
661    impl Diagnostic for ContextOrShapeNotRecordError {
662        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
663            match &self.ctx_or_shape {
664                ContextOrShape::ActionContext(_) => {
665                    Some(Box::new("action contexts must have type `Record`"))
666                }
667                ContextOrShape::EntityTypeShape(_) => {
668                    Some(Box::new("entity type shapes must have type `Record`"))
669                }
670            }
671        }
672
673        impl_diagnostic_from_method_on_field!(ctx_or_shape, loc);
674    }
675
676    /// This error variant is deprecated and will never be returned.
677    //
678    // CAUTION: this type is publicly exported in `cedar-policy`.
679    // Don't make fields `pub`, don't make breaking changes, and use caution
680    // when adding public methods.
681    #[derive(Diagnostic, Debug, Error)]
682    #[error("internal invariant violated: this error is deprecated and should never be returned")]
683    #[deprecated = "this error is deprecated and should never be returned"]
684    pub struct ActionAttributesContainEmptySetError {}
685
686    /// This error variant is deprecated and will never be returned.
687    //
688    // CAUTION: this type is publicly exported in `cedar-policy`.
689    // Don't make fields `pub`, don't make breaking changes, and use caution
690    // when adding public methods.
691    #[derive(Diagnostic, Debug, Error)]
692    #[error("internal invariant violated: this error is deprecated and should never be returned")]
693    #[deprecated = "this error is deprecated and should never be returned"]
694    pub struct UnsupportedActionAttributeError {}
695
696    /// This error variant is deprecated and will never be returned.
697    //
698    // CAUTION: this type is publicly exported in `cedar-policy`.
699    // Don't make fields `pub`, don't make breaking changes, and use caution
700    // when adding public methods.
701    #[derive(Diagnostic, Debug, Error)]
702    #[error("internal invariant violated: this error is deprecated and should never be returned")]
703    #[deprecated = "this error is deprecated and should never be returned"]
704    pub struct ExprEscapeUsedError {}
705
706    /// This error variant is deprecated and will never be returned.
707    //
708    // CAUTION: this type is publicly exported in `cedar-policy`.
709    // Don't make fields `pub`, don't make breaking changes, and use caution
710    // when adding public methods.
711    #[derive(Diagnostic, Debug, Error)]
712    #[error("internal invariant violated: this error is deprecated and should never be returned")]
713    #[deprecated = "this error is deprecated and should never be returned"]
714    pub struct ActionAttrEvalError();
715
716    /// Unsupported feature error
717    //
718    // CAUTION: this type is publicly exported in `cedar-policy`.
719    // Don't make fields `pub`, don't make breaking changes, and use caution
720    // when adding public methods.
721    #[derive(Debug, Diagnostic, Error)]
722    #[error("unsupported feature used in schema")]
723    #[diagnostic(transparent)]
724    pub struct UnsupportedFeatureError(#[from] pub(crate) UnsupportedFeature);
725
726    #[derive(Debug)]
727    pub(crate) enum ContextOrShape {
728        ActionContext(EntityUID),
729        EntityTypeShape(EntityType),
730    }
731
732    impl ContextOrShape {
733        pub fn loc(&self) -> Option<&Loc> {
734            match self {
735                ContextOrShape::ActionContext(uid) => uid.loc(),
736                ContextOrShape::EntityTypeShape(ty) => ty.loc(),
737            }
738        }
739    }
740
741    impl std::fmt::Display for ContextOrShape {
742        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
743            match self {
744                ContextOrShape::ActionContext(action) => write!(f, "Context for action {action}"),
745                ContextOrShape::EntityTypeShape(entity_type) => {
746                    write!(f, "Shape for entity type {entity_type}")
747                }
748            }
749        }
750    }
751
752    #[derive(Debug, Diagnostic, Error)]
753    pub(crate) enum UnsupportedFeature {
754        #[error("records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled")]
755        OpenRecordsAndEntities,
756        // Action attributes are allowed if `ActionBehavior` is `PermitAttributes`
757        #[error("action declared with attributes: [{}]", .0.iter().join(", "))]
758        ActionAttributes(Vec<String>),
759    }
760
761    /// This error is thrown when `serde_json` fails to deserialize the JSON
762    //
763    // CAUTION: this type is publicly exported in `cedar-policy`.
764    // Don't make fields `pub`, don't make breaking changes, and use caution
765    // when adding public methods.
766    #[derive(Debug, Error)]
767    #[error("{err}")]
768    pub struct JsonDeserializationError {
769        /// Error thrown by the `serde_json` crate
770        err: serde_json::Error,
771        /// Possible fix for the error
772        advice: Option<JsonDeserializationAdvice>,
773    }
774
775    impl Diagnostic for JsonDeserializationError {
776        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
777            self.advice
778                .as_ref()
779                .map(|h| Box::new(h) as Box<dyn Display>)
780        }
781    }
782
783    #[derive(Debug, Error)]
784    enum JsonDeserializationAdvice {
785        #[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?")]
786        CedarFormat,
787        #[error("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{{ \"\": {{..}} }}`")]
788        MissingNamespace,
789    }
790
791    impl JsonDeserializationError {
792        /// `err`: the `serde_json::Error` that was thrown
793        ///
794        /// `src`: the JSON that we were trying to deserialize (if available in string form)
795        pub(crate) fn new(err: serde_json::Error, src: Option<&str>) -> Self {
796            match src {
797                None => Self { err, advice: None },
798                Some(src) => {
799                    // let's see what the first non-whitespace character is
800                    let advice = match src.trim_start().chars().next() {
801                        None => None, // schema is empty or only whitespace; the problem is unlikely to be JSON vs Cedar format
802                        Some('{') => {
803                            // This looks like it was intended to be a JSON schema. Check fields of top level JSON object to see
804                            // if it looks like it's missing a namespace.
805                            if let Ok(serde_json::Value::Object(obj)) =
806                                serde_json::from_str::<serde_json::Value>(src)
807                            {
808                                if obj.contains_key("entityTypes")
809                                    || obj.contains_key("actions")
810                                    || obj.contains_key("commonTypes")
811                                {
812                                    // These keys are expected inside a namespace, so it's likely the user forgot to specify a
813                                    // namespace if they're at the top level of the schema json object.
814                                    Some(JsonDeserializationAdvice::MissingNamespace)
815                                } else {
816                                    // Probably something wrong inside a namespace definition.
817                                    None
818                                }
819                            } else {
820                                // Invalid JSON
821                                None
822                            }
823                        }
824                        Some(_) => Some(JsonDeserializationAdvice::CedarFormat), // any character other than '{', we suspect it might be a Cedar-format schema
825                    };
826                    Self { err, advice }
827                }
828            }
829        }
830    }
831
832    /// Unknown extension type error
833    //
834    // CAUTION: this type is publicly exported in `cedar-policy`.
835    // Don't make fields `pub`, don't make breaking changes, and use caution
836    // when adding public methods.
837    #[derive(Error, Debug)]
838    #[error("unknown extension type `{actual}`")]
839    pub struct UnknownExtensionTypeError {
840        pub(crate) actual: Name,
841        pub(crate) suggested_replacement: Option<String>,
842    }
843
844    impl Diagnostic for UnknownExtensionTypeError {
845        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
846            self.suggested_replacement.as_ref().map(|suggestion| {
847                Box::new(format!("did you mean `{suggestion}`?")) as Box<dyn Display>
848            })
849        }
850
851        impl_diagnostic_from_method_on_field!(actual, loc);
852    }
853
854    /// Could not find a definition for a common type, at a point in the code
855    /// where internal invariants should guarantee that we would find one.
856    //
857    // CAUTION: this type is publicly exported in `cedar-policy`.
858    // Don't make fields `pub`, don't make breaking changes, and use caution
859    // when adding public methods.
860    #[derive(Error, Debug)]
861    #[error("internal invariant violated: failed to find a common-type definition for {name}")]
862    pub struct CommonTypeInvariantViolationError {
863        /// Fully-qualified [`InternalName`] of the common type we failed to find a definition for
864        pub(crate) name: InternalName,
865    }
866
867    impl Diagnostic for CommonTypeInvariantViolationError {
868        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
869            Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
870        }
871
872        impl_diagnostic_from_method_on_field!(name, loc);
873    }
874
875    /// Could not find a definition for an action, at a point in the code where
876    /// internal invariants should guarantee that we would find one.
877    //
878    // CAUTION: this type is publicly exported in `cedar-policy`.
879    // Don't make fields `pub`, don't make breaking changes, and use caution
880    // when adding public methods.
881    #[derive(Error, Debug)]
882    #[error("internal invariant violated: failed to find {} for {}", if .euids.len() > 1 { "action definitions" } else { "an action definition" }, .euids.iter().join(", "))]
883    pub struct ActionInvariantViolationError {
884        /// Fully-qualified [`EntityUID`]s of the action(s) we failed to find a definition for
885        pub(crate) euids: NonEmpty<EntityUID>,
886    }
887
888    impl Diagnostic for ActionInvariantViolationError {
889        fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
890            Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
891        }
892
893        impl_diagnostic_from_method_on_nonempty_field!(euids, loc);
894    }
895}