cedar_policy_validator/schema/
err.rs

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