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