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