1use 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#[derive(Debug, Error, Diagnostic)]
30pub enum CedarSchemaError {
31 #[error(transparent)]
33 #[diagnostic(transparent)]
34 Schema(#[from] SchemaError),
35 #[error(transparent)]
37 IO(#[from] std::io::Error),
38 #[error(transparent)]
40 #[diagnostic(transparent)]
41 Parsing(#[from] CedarSchemaParseError),
42}
43
44#[derive(Debug, Error)]
47#[error("error parsing schema: {errs}")]
48pub struct CedarSchemaParseError {
49 errs: cedar_schema::parser::CedarSchemaParseErrors,
51 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 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 pub(crate) fn new(errs: cedar_schema::parser::CedarSchemaParseErrors, src: &str) -> Self {
101 let suspect_json_format = match src.trim_start().chars().next() {
103 None => false, Some('{') => true, Some(_) => false, };
107 Self {
108 errs,
109 suspect_json_format,
110 }
111 }
112
113 pub fn suspect_json_format(&self) -> bool {
118 self.suspect_json_format
119 }
120
121 pub fn errors(&self) -> &cedar_schema::parser::CedarSchemaParseErrors {
123 &self.errs
124 }
125}
126
127#[derive(Debug, Diagnostic, Error)]
133#[non_exhaustive]
134pub enum SchemaError {
135 #[error(transparent)]
137 #[diagnostic(transparent)]
138 JsonSerialization(#[from] schema_errors::JsonSerializationError),
139 #[error(transparent)]
141 #[diagnostic(transparent)]
142 JsonDeserialization(#[from] schema_errors::JsonDeserializationError),
143 #[error(transparent)]
146 #[diagnostic(transparent)]
147 ActionTransitiveClosure(#[from] schema_errors::ActionTransitiveClosureError),
148 #[error(transparent)]
151 #[diagnostic(transparent)]
152 EntityTypeTransitiveClosure(#[from] schema_errors::EntityTypeTransitiveClosureError),
153 #[error(transparent)]
155 #[diagnostic(transparent)]
156 UnsupportedFeature(#[from] schema_errors::UnsupportedFeatureError),
157 #[error(transparent)]
162 #[diagnostic(transparent)]
163 UndeclaredEntityTypes(#[from] schema_errors::UndeclaredEntityTypesError),
164 #[error(transparent)]
167 #[diagnostic(transparent)]
168 TypeNotDefined(#[from] schema_errors::TypeNotDefinedError),
169 #[error(transparent)]
173 #[diagnostic(transparent)]
174 ActionNotDefined(#[from] schema_errors::ActionNotDefinedError),
175 #[error(transparent)]
179 #[diagnostic(transparent)]
180 TypeShadowing(#[from] schema_errors::TypeShadowingError),
181 #[error(transparent)]
185 #[diagnostic(transparent)]
186 ActionShadowing(#[from] schema_errors::ActionShadowingError),
187 #[error(transparent)]
189 #[diagnostic(transparent)]
190 DuplicateEntityType(#[from] schema_errors::DuplicateEntityTypeError),
191 #[error(transparent)]
193 #[diagnostic(transparent)]
194 DuplicateAction(#[from] schema_errors::DuplicateActionError),
195 #[error(transparent)]
197 #[diagnostic(transparent)]
198 DuplicateCommonType(#[from] schema_errors::DuplicateCommonTypeError),
199 #[error(transparent)]
201 #[diagnostic(transparent)]
202 CycleInActionHierarchy(#[from] schema_errors::CycleInActionHierarchyError),
203 #[error(transparent)]
205 #[diagnostic(transparent)]
206 CycleInCommonTypeReferences(#[from] schema_errors::CycleInCommonTypeReferencesError),
207 #[error(transparent)]
212 #[diagnostic(transparent)]
213 ActionEntityTypeDeclared(#[from] schema_errors::ActionEntityTypeDeclaredError),
214 #[error(transparent)]
216 #[diagnostic(transparent)]
217 ContextOrShapeNotRecord(#[from] schema_errors::ContextOrShapeNotRecordError),
218 #[error(transparent)]
222 #[diagnostic(transparent)]
223 ActionAttributesContainEmptySet(#[from] schema_errors::ActionAttributesContainEmptySetError),
224 #[error(transparent)]
227 #[diagnostic(transparent)]
228 UnsupportedActionAttribute(#[from] schema_errors::UnsupportedActionAttributeError),
229 #[error(transparent)]
231 #[diagnostic(transparent)]
232 ActionAttrEval(#[from] schema_errors::ActionAttrEvalError),
233 #[error(transparent)]
236 #[diagnostic(transparent)]
237 ExprEscapeUsed(#[from] schema_errors::ExprEscapeUsedError),
238 #[error(transparent)]
240 #[diagnostic(transparent)]
241 UnknownExtensionType(schema_errors::UnknownExtensionTypeError),
242 #[error(transparent)]
244 #[diagnostic(transparent)]
245 ReservedName(#[from] ReservedNameError),
246 #[error(transparent)]
249 #[diagnostic(transparent)]
250 CommonTypeInvariantViolation(#[from] schema_errors::CommonTypeInvariantViolationError),
251 #[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 match e {
264 transitive_closure::TcError::MissingTcEdge { .. } => {
265 SchemaError::ActionTransitiveClosure(Box::new(e).into())
266 }
267 transitive_closure::TcError::HasCycle(err) => {
268 schema_errors::CycleInActionHierarchyError {
269 uid: err.vertex_with_loop().clone(),
270 }
271 .into()
272 }
273 }
274 }
275}
276
277impl SchemaError {
278 pub fn join_nonempty(errs: NonEmpty<SchemaError>) -> SchemaError {
281 let (type_ndef_errors, non_type_ndef_errors): (Vec<_>, Vec<_>) =
285 errs.into_iter().partition_map(|e| match e {
286 SchemaError::TypeNotDefined(e) => Either::Left(e),
287 _ => Either::Right(e),
288 });
289 if let Some(errs) = NonEmpty::from_vec(type_ndef_errors) {
290 schema_errors::TypeNotDefinedError::join_nonempty(errs).into()
291 } else {
292 let (action_ndef_errors, other_errors): (Vec<_>, Vec<_>) =
293 non_type_ndef_errors.into_iter().partition_map(|e| match e {
294 SchemaError::ActionNotDefined(e) => Either::Left(e),
295 _ => Either::Right(e),
296 });
297 if let Some(errs) = NonEmpty::from_vec(action_ndef_errors) {
298 schema_errors::ActionNotDefinedError::join_nonempty(errs).into()
299 } else {
300 #[allow(clippy::expect_used)]
307 other_errors.into_iter().next().expect("cannot be empty")
308 }
309 }
310 }
311}
312
313pub type Result<T> = std::result::Result<T, SchemaError>;
315
316pub mod schema_errors {
318 use std::fmt::Display;
319
320 use cedar_policy_core::ast::{
321 EntityAttrEvaluationError, EntityType, EntityUID, InternalName, Name,
322 };
323 use cedar_policy_core::parser::{join_with_conjunction, Loc};
324 use cedar_policy_core::{
325 impl_diagnostic_from_method_on_field, impl_diagnostic_from_method_on_nonempty_field,
326 transitive_closure,
327 };
328 use itertools::Itertools;
329 use miette::Diagnostic;
330 use nonempty::NonEmpty;
331 use smol_str::SmolStr;
332 use thiserror::Error;
333
334 #[derive(Debug, Diagnostic, Error)]
340 #[error(transparent)]
341 pub struct JsonSerializationError(#[from] pub(crate) serde_json::Error);
342
343 #[derive(Debug, Diagnostic, Error)]
349 #[error("transitive closure computation/enforcement error on action hierarchy")]
350 #[diagnostic(transparent)]
351 pub struct ActionTransitiveClosureError(
352 #[from] pub(crate) Box<transitive_closure::TcError<EntityUID>>,
353 );
354
355 #[derive(Debug, Diagnostic, Error)]
361 #[error("transitive closure computation/enforcement error on entity type hierarchy")]
362 #[diagnostic(transparent)]
363 pub struct EntityTypeTransitiveClosureError(
364 #[from] pub(crate) Box<transitive_closure::TcError<EntityType>>,
365 );
366
367 #[derive(Debug, Error)]
373 pub struct UndeclaredEntityTypesError {
374 pub(crate) types: NonEmpty<EntityType>,
376 }
377
378 impl Display for UndeclaredEntityTypesError {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 if self.types.len() == 1 {
381 write!(f, "undeclared entity type: ")?;
382 } else {
383 write!(f, "undeclared entity types: ")?;
384 }
385 join_with_conjunction(f, "and", self.types.iter().sorted_unstable(), |f, s| {
386 s.fmt(f)
387 })
388 }
389 }
390
391 impl Diagnostic for UndeclaredEntityTypesError {
392 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
393 Some(Box::new("any entity types appearing anywhere in a schema need to be declared in `entityTypes`"))
394 }
395
396 impl_diagnostic_from_method_on_nonempty_field!(types, loc);
397 }
398
399 #[derive(Debug, Error)]
405 #[error("failed to resolve type{}: {}", if .undefined_types.len() > 1 { "s" } else { "" }, .undefined_types.iter().map(crate::ConditionalName::raw).join(", "))]
406 pub struct TypeNotDefinedError {
407 pub(crate) undefined_types: NonEmpty<crate::ConditionalName>,
409 }
410
411 impl Diagnostic for TypeNotDefinedError {
412 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
413 Some(Box::new(
415 self.undefined_types.first().resolution_failure_help(),
416 ))
417 }
418
419 impl_diagnostic_from_method_on_nonempty_field!(undefined_types, loc);
420 }
421
422 impl TypeNotDefinedError {
423 pub(crate) fn join_nonempty(errs: NonEmpty<TypeNotDefinedError>) -> Self {
428 Self {
429 undefined_types: errs.flat_map(|err| err.undefined_types),
430 }
431 }
432 }
433
434 #[derive(Debug, Diagnostic, Error)]
440 #[diagnostic(help("any actions appearing as parents need to be declared as actions"))]
441 pub struct ActionNotDefinedError(
442 pub(crate) NonEmpty<crate::json_schema::ActionEntityUID<crate::ConditionalName>>,
443 );
444
445 impl ActionNotDefinedError {
446 pub(crate) fn join_nonempty(errs: NonEmpty<ActionNotDefinedError>) -> Self {
451 Self(errs.flat_map(|err| err.0))
452 }
453 }
454
455 impl Display for ActionNotDefinedError {
456 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
457 if self.0.len() == 1 {
458 write!(f, "undeclared action: ")?;
459 } else {
460 write!(f, "undeclared actions: ")?;
461 }
462 join_with_conjunction(
463 f,
464 "and",
465 self.0.iter().map(|aeuid| aeuid.as_raw()),
466 |f, s| s.fmt(f),
467 )
468 }
469 }
470
471 #[derive(Debug, Error)]
479 #[error(
480 "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
481 )]
482 pub struct TypeShadowingError {
483 pub(crate) shadowed_def: InternalName,
485 pub(crate) shadowing_def: InternalName,
487 }
488
489 impl Diagnostic for TypeShadowingError {
490 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
491 Some(Box::new(format!(
492 "try renaming one of the definitions, or moving `{}` to a different namespace",
493 self.shadowed_def
494 )))
495 }
496
497 impl_diagnostic_from_method_on_field!(shadowing_def, loc);
500 }
501
502 #[derive(Debug, Error)]
510 #[error(
511 "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
512 )]
513 pub struct ActionShadowingError {
514 pub(crate) shadowed_def: EntityUID,
516 pub(crate) shadowing_def: EntityUID,
518 }
519
520 impl Diagnostic for ActionShadowingError {
521 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
522 Some(Box::new(format!(
523 "try renaming one of the actions, or moving `{}` to a different namespace",
524 self.shadowed_def
525 )))
526 }
527
528 impl_diagnostic_from_method_on_field!(shadowing_def, loc);
531 }
532
533 #[derive(Debug, Error)]
539 #[error("duplicate entity type `{ty}`")]
540 pub struct DuplicateEntityTypeError {
541 pub(crate) ty: EntityType,
542 }
543
544 impl Diagnostic for DuplicateEntityTypeError {
545 impl_diagnostic_from_method_on_field!(ty, loc);
546 }
547
548 #[derive(Debug, Diagnostic, Error)]
554 #[error("duplicate action `{0}`")]
555 pub struct DuplicateActionError(pub(crate) SmolStr);
556
557 #[derive(Debug, Error)]
563 #[error("duplicate common type `{ty}`")]
564 pub struct DuplicateCommonTypeError {
565 pub(crate) ty: InternalName,
566 }
567
568 impl Diagnostic for DuplicateCommonTypeError {
569 impl_diagnostic_from_method_on_field!(ty, loc);
570 }
571
572 #[derive(Debug, Error)]
578 #[error("cycle in action hierarchy containing `{uid}`")]
579 pub struct CycleInActionHierarchyError {
580 pub(crate) uid: EntityUID,
581 }
582
583 impl Diagnostic for CycleInActionHierarchyError {
584 impl_diagnostic_from_method_on_field!(uid, loc);
585 }
586
587 #[derive(Debug, Error)]
593 #[error("cycle in common type references containing `{ty}`")]
594 pub struct CycleInCommonTypeReferencesError {
595 pub(crate) ty: InternalName,
596 }
597
598 impl Diagnostic for CycleInCommonTypeReferencesError {
599 impl_diagnostic_from_method_on_field!(ty, loc);
600 }
601
602 #[derive(Debug, Clone, Diagnostic, Error)]
608 #[error("entity type `Action` declared in `entityTypes` list")]
609 pub struct ActionEntityTypeDeclaredError {}
610
611 #[derive(Debug, Error)]
617 #[error("{ctx_or_shape} is declared with a type other than `Record`")]
618 pub struct ContextOrShapeNotRecordError {
619 pub(crate) ctx_or_shape: ContextOrShape,
620 }
621
622 impl Diagnostic for ContextOrShapeNotRecordError {
623 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
624 match &self.ctx_or_shape {
625 ContextOrShape::ActionContext(_) => {
626 Some(Box::new("action contexts must have type `Record`"))
627 }
628 ContextOrShape::EntityTypeShape(_) => {
629 Some(Box::new("entity type shapes must have type `Record`"))
630 }
631 }
632 }
633
634 impl_diagnostic_from_method_on_field!(ctx_or_shape, loc);
635 }
636
637 #[derive(Debug, Error)]
643 #[error("action `{uid}` has an attribute that is an empty set")]
644 pub struct ActionAttributesContainEmptySetError {
645 pub(crate) uid: EntityUID,
646 }
647
648 impl Diagnostic for ActionAttributesContainEmptySetError {
649 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
650 Some(Box::new(
651 "actions are not currently allowed to have attributes whose value is an empty set",
652 ))
653 }
654
655 impl_diagnostic_from_method_on_field!(uid, loc);
656 }
657
658 #[derive(Debug, Error)]
664 #[error("action `{uid}` has an attribute with unsupported JSON representation: {attr}")]
665 pub struct UnsupportedActionAttributeError {
666 pub(crate) uid: EntityUID,
667 pub(crate) attr: SmolStr,
668 }
669
670 impl Diagnostic for UnsupportedActionAttributeError {
671 impl_diagnostic_from_method_on_field!(uid, loc);
672 }
673
674 #[derive(Debug, Clone, Diagnostic, Error)]
680 #[error("the `__expr` escape is no longer supported")]
681 #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
682 pub struct ExprEscapeUsedError {}
683
684 #[derive(Debug, Diagnostic, Error)]
690 #[error(transparent)]
691 #[diagnostic(transparent)]
692 pub struct ActionAttrEvalError(#[from] pub(crate) EntityAttrEvaluationError);
693
694 #[derive(Debug, Diagnostic, Error)]
700 #[error("unsupported feature used in schema")]
701 #[diagnostic(transparent)]
702 pub struct UnsupportedFeatureError(#[from] pub(crate) UnsupportedFeature);
703
704 #[derive(Debug)]
705 pub(crate) enum ContextOrShape {
706 ActionContext(EntityUID),
707 EntityTypeShape(EntityType),
708 }
709
710 impl ContextOrShape {
711 pub fn loc(&self) -> Option<&Loc> {
712 match self {
713 ContextOrShape::ActionContext(uid) => uid.loc(),
714 ContextOrShape::EntityTypeShape(ty) => ty.loc(),
715 }
716 }
717 }
718
719 impl std::fmt::Display for ContextOrShape {
720 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721 match self {
722 ContextOrShape::ActionContext(action) => write!(f, "Context for action {}", action),
723 ContextOrShape::EntityTypeShape(entity_type) => {
724 write!(f, "Shape for entity type {}", entity_type)
725 }
726 }
727 }
728 }
729
730 #[derive(Debug, Diagnostic, Error)]
731 pub(crate) enum UnsupportedFeature {
732 #[error("records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled")]
733 OpenRecordsAndEntities,
734 #[error("action declared with attributes: [{}]", .0.iter().join(", "))]
736 ActionAttributes(Vec<String>),
737 }
738
739 #[derive(Debug, Error)]
745 #[error("{err}")]
746 pub struct JsonDeserializationError {
747 err: serde_json::Error,
749 advice: Option<JsonDeserializationAdvice>,
751 }
752
753 impl Diagnostic for JsonDeserializationError {
754 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
755 self.advice
756 .as_ref()
757 .map(|h| Box::new(h) as Box<dyn Display>)
758 }
759 }
760
761 #[derive(Debug, Error)]
762 enum JsonDeserializationAdvice {
763 #[error("this API was expecting a schema in the JSON format; did you mean to use a different function, which expects the Cedar schema format?")]
764 CedarFormat,
765 #[error("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{{ \"\": {{..}} }}`")]
766 MissingNamespace,
767 }
768
769 impl JsonDeserializationError {
770 pub(crate) fn new(err: serde_json::Error, src: Option<&str>) -> Self {
774 match src {
775 None => Self { err, advice: None },
776 Some(src) => {
777 let advice = match src.trim_start().chars().next() {
779 None => None, Some('{') => {
781 if let Ok(serde_json::Value::Object(obj)) =
784 serde_json::from_str::<serde_json::Value>(src)
785 {
786 if obj.contains_key("entityTypes")
787 || obj.contains_key("actions")
788 || obj.contains_key("commonTypes")
789 {
790 Some(JsonDeserializationAdvice::MissingNamespace)
793 } else {
794 None
796 }
797 } else {
798 None
800 }
801 }
802 Some(_) => Some(JsonDeserializationAdvice::CedarFormat), };
804 Self { err, advice }
805 }
806 }
807 }
808 }
809
810 #[derive(Error, Debug)]
816 #[error("unknown extension type `{actual}`")]
817 pub struct UnknownExtensionTypeError {
818 pub(crate) actual: Name,
819 pub(crate) suggested_replacement: Option<String>,
820 }
821
822 impl Diagnostic for UnknownExtensionTypeError {
823 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
824 self.suggested_replacement.as_ref().map(|suggestion| {
825 Box::new(format!("did you mean `{suggestion}`?")) as Box<dyn Display>
826 })
827 }
828
829 impl_diagnostic_from_method_on_field!(actual, loc);
830 }
831
832 #[derive(Error, Debug)]
839 #[error("internal invariant violated: failed to find a common-type definition for {name}")]
840 pub struct CommonTypeInvariantViolationError {
841 pub(crate) name: InternalName,
843 }
844
845 impl Diagnostic for CommonTypeInvariantViolationError {
846 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
847 Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
848 }
849
850 impl_diagnostic_from_method_on_field!(name, loc);
851 }
852
853 #[derive(Error, Debug)]
860 #[error("internal invariant violated: failed to find {} for {}", if .euids.len() > 1 { "action definitions" } else { "an action definition" }, .euids.iter().join(", "))]
861 pub struct ActionInvariantViolationError {
862 pub(crate) euids: NonEmpty<EntityUID>,
864 }
865
866 impl Diagnostic for ActionInvariantViolationError {
867 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
868 Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
869 }
870
871 impl_diagnostic_from_method_on_nonempty_field!(euids, loc);
872 }
873}