1use 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#[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)]
220 #[diagnostic(transparent)]
221 #[deprecated = "this error is deprecated and should never be returned"]
222 #[expect(deprecated, reason = "inner variant is deprecated too")]
223 ActionAttributesContainEmptySet(#[from] schema_errors::ActionAttributesContainEmptySetError),
224 #[error(transparent)]
226 #[diagnostic(transparent)]
227 #[deprecated = "this error is deprecated and should never be returned"]
228 #[expect(deprecated, reason = "inner variant is deprecated too")]
229 UnsupportedActionAttribute(#[from] schema_errors::UnsupportedActionAttributeError),
230 #[error(transparent)]
232 #[diagnostic(transparent)]
233 #[deprecated = "this error is deprecated and should never be returned"]
234 #[expect(deprecated, reason = "inner variant is deprecated too")]
235 ActionAttrEval(#[from] schema_errors::ActionAttrEvalError),
236 #[error(transparent)]
238 #[diagnostic(transparent)]
239 #[deprecated = "this error is deprecated and should never be returned"]
240 #[expect(deprecated, reason = "inner variant is deprecated too")]
241 ExprEscapeUsed(#[from] schema_errors::ExprEscapeUsedError),
242 #[error(transparent)]
244 #[diagnostic(transparent)]
245 UnknownExtensionType(schema_errors::UnknownExtensionTypeError),
246 #[error(transparent)]
248 #[diagnostic(transparent)]
249 ReservedName(#[from] ReservedNameError),
250 #[error(transparent)]
253 #[diagnostic(transparent)]
254 CommonTypeInvariantViolation(#[from] schema_errors::CommonTypeInvariantViolationError),
255 #[error(transparent)]
258 #[diagnostic(transparent)]
259 ActionInvariantViolation(#[from] schema_errors::ActionInvariantViolationError),
260}
261
262impl From<transitive_closure::TcError<EntityUID>> for SchemaError {
263 fn from(e: transitive_closure::TcError<EntityUID>) -> Self {
264 match e {
268 transitive_closure::TcError::MissingTcEdge { .. } => {
269 SchemaError::ActionTransitiveClosure(Box::new(e).into())
270 }
271 transitive_closure::TcError::HasCycle(err) => {
272 schema_errors::CycleInActionHierarchyError {
273 uid: err.vertex_with_loop().clone(),
274 }
275 .into()
276 }
277 }
278 }
279}
280
281impl SchemaError {
282 pub fn join_nonempty(errs: NonEmpty<SchemaError>) -> SchemaError {
285 let (type_ndef_errors, non_type_ndef_errors): (Vec<_>, Vec<_>) =
289 errs.into_iter().partition_map(|e| match e {
290 SchemaError::TypeNotDefined(e) => Either::Left(e),
291 _ => Either::Right(e),
292 });
293 if let Some(errs) = NonEmpty::from_vec(type_ndef_errors) {
294 schema_errors::TypeNotDefinedError::join_nonempty(errs).into()
295 } else {
296 let (action_ndef_errors, other_errors): (Vec<_>, Vec<_>) =
297 non_type_ndef_errors.into_iter().partition_map(|e| match e {
298 SchemaError::ActionNotDefined(e) => Either::Left(e),
299 _ => Either::Right(e),
300 });
301 if let Some(errs) = NonEmpty::from_vec(action_ndef_errors) {
302 schema_errors::ActionNotDefinedError::join_nonempty(errs).into()
303 } else {
304 #[expect(
310 clippy::expect_used,
311 reason = "other_errors cannot be empty due to partitioning logic explained in comment above"
312 )]
313 other_errors.into_iter().next().expect("cannot be empty")
314 }
315 }
316 }
317}
318
319impl From<NonEmpty<SchemaError>> for SchemaError {
320 fn from(errs: NonEmpty<SchemaError>) -> Self {
321 Self::join_nonempty(errs)
322 }
323}
324
325impl From<NonEmpty<schema_errors::ActionNotDefinedError>> for SchemaError {
326 fn from(errs: NonEmpty<schema_errors::ActionNotDefinedError>) -> Self {
327 Self::ActionNotDefined(schema_errors::ActionNotDefinedError::join_nonempty(errs))
328 }
329}
330
331impl From<NonEmpty<schema_errors::TypeNotDefinedError>> for SchemaError {
332 fn from(errs: NonEmpty<schema_errors::TypeNotDefinedError>) -> Self {
333 Self::TypeNotDefined(schema_errors::TypeNotDefinedError::join_nonempty(errs))
334 }
335}
336
337pub type Result<T> = std::result::Result<T, SchemaError>;
339
340pub mod schema_errors {
342
343 #![expect(deprecated, reason = "see comment immediately above")]
347
348 use std::fmt::Display;
349
350 use crate::ast::{EntityType, EntityUID, InternalName, Name};
351 use crate::parser::{join_with_conjunction, Loc};
352 use crate::transitive_closure;
353 use itertools::Itertools;
354 use miette::Diagnostic;
355 use nonempty::NonEmpty;
356 use smol_str::SmolStr;
357 use thiserror::Error;
358
359 #[derive(Debug, Diagnostic, Error)]
365 #[error(transparent)]
366 pub struct JsonSerializationError(#[from] pub(crate) serde_json::Error);
367
368 #[derive(Debug, Diagnostic, Error)]
374 #[error("transitive closure computation/enforcement error on action hierarchy")]
375 #[diagnostic(transparent)]
376 pub struct ActionTransitiveClosureError(
377 #[from] pub(crate) Box<transitive_closure::TcError<EntityUID>>,
378 );
379
380 #[derive(Debug, Diagnostic, Error)]
386 #[error("transitive closure computation/enforcement error on entity type hierarchy")]
387 #[diagnostic(transparent)]
388 pub struct EntityTypeTransitiveClosureError(
389 #[from] pub(crate) Box<transitive_closure::TcError<EntityType>>,
390 );
391
392 #[derive(Debug, Error)]
398 pub struct UndeclaredEntityTypesError {
399 pub(crate) types: NonEmpty<EntityType>,
401 }
402
403 impl Display for UndeclaredEntityTypesError {
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405 if self.types.len() == 1 {
406 write!(f, "undeclared entity type: ")?;
407 } else {
408 write!(f, "undeclared entity types: ")?;
409 }
410 join_with_conjunction(f, "and", self.types.iter().sorted_unstable(), |f, s| {
411 s.fmt(f)
412 })
413 }
414 }
415
416 impl Diagnostic for UndeclaredEntityTypesError {
417 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
418 Some(Box::new("any entity types appearing anywhere in a schema need to be declared in `entityTypes`"))
419 }
420
421 impl_diagnostic_from_method_on_nonempty_field!(types, loc);
422 }
423
424 #[derive(Debug, Error)]
430 #[error("failed to resolve type{}: {}", if .undefined_types.len() > 1 { "s" } else { "" }, .undefined_types.iter().map(crate::validator::ConditionalName::raw).join(", "))]
431 pub struct TypeNotDefinedError {
432 pub(crate) undefined_types: NonEmpty<crate::validator::ConditionalName>,
434 }
435
436 impl Diagnostic for TypeNotDefinedError {
437 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
438 Some(Box::new(
440 self.undefined_types.first().resolution_failure_help(),
441 ))
442 }
443
444 impl_diagnostic_from_method_on_nonempty_field!(undefined_types, loc);
445 }
446
447 impl TypeNotDefinedError {
448 pub(crate) fn join_nonempty(errs: NonEmpty<TypeNotDefinedError>) -> Self {
453 Self {
454 undefined_types: errs.flat_map(|err| err.undefined_types),
455 }
456 }
457 }
458
459 impl From<NonEmpty<TypeNotDefinedError>> for TypeNotDefinedError {
460 fn from(value: NonEmpty<TypeNotDefinedError>) -> Self {
461 Self::join_nonempty(value)
462 }
463 }
464
465 #[derive(Debug, Diagnostic, Error)]
471 #[diagnostic(help("any actions appearing as parents need to be declared as actions"))]
472 pub struct ActionNotDefinedError(
473 pub(crate) NonEmpty<
474 crate::validator::json_schema::ActionEntityUID<crate::validator::ConditionalName>,
475 >,
476 );
477
478 impl ActionNotDefinedError {
479 pub(crate) fn join_nonempty(errs: NonEmpty<ActionNotDefinedError>) -> Self {
484 Self(errs.flat_map(|err| err.0))
485 }
486 }
487
488 impl From<NonEmpty<ActionNotDefinedError>> for ActionNotDefinedError {
489 fn from(value: NonEmpty<ActionNotDefinedError>) -> Self {
490 Self::join_nonempty(value)
491 }
492 }
493
494 impl Display for ActionNotDefinedError {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 if self.0.len() == 1 {
497 write!(f, "undeclared action: ")?;
498 } else {
499 write!(f, "undeclared actions: ")?;
500 }
501 join_with_conjunction(
502 f,
503 "and",
504 self.0.iter().map(|aeuid| aeuid.as_raw()),
505 |f, s| s.fmt(f),
506 )
507 }
508 }
509
510 #[derive(Debug, Error)]
518 #[error(
519 "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
520 )]
521 pub struct TypeShadowingError {
522 pub(crate) shadowed_def: InternalName,
524 pub(crate) shadowing_def: InternalName,
526 }
527
528 impl Diagnostic for TypeShadowingError {
529 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
530 Some(Box::new(format!(
531 "try renaming one of the definitions, or moving `{}` to a different namespace",
532 self.shadowed_def
533 )))
534 }
535
536 impl_diagnostic_from_method_on_field!(shadowing_def, loc);
539 }
540
541 #[derive(Debug, Error)]
549 #[error(
550 "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
551 )]
552 pub struct ActionShadowingError {
553 pub(crate) shadowed_def: EntityUID,
555 pub(crate) shadowing_def: EntityUID,
557 }
558
559 impl Diagnostic for ActionShadowingError {
560 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
561 Some(Box::new(format!(
562 "try renaming one of the actions, or moving `{}` to a different namespace",
563 self.shadowed_def
564 )))
565 }
566
567 impl_diagnostic_from_method_on_field!(shadowing_def, loc);
570 }
571
572 #[derive(Debug, Error)]
578 #[error("duplicate entity type `{ty}`")]
579 pub struct DuplicateEntityTypeError {
580 pub(crate) ty: EntityType,
581 }
582
583 impl Diagnostic for DuplicateEntityTypeError {
584 impl_diagnostic_from_method_on_field!(ty, loc);
585 }
586
587 #[derive(Debug, Diagnostic, Error)]
593 #[error("duplicate action `{0}`")]
594 pub struct DuplicateActionError(pub(crate) SmolStr);
595
596 #[derive(Debug, Error)]
602 #[error("duplicate common type `{ty}`")]
603 pub struct DuplicateCommonTypeError {
604 pub(crate) ty: InternalName,
605 }
606
607 impl Diagnostic for DuplicateCommonTypeError {
608 impl_diagnostic_from_method_on_field!(ty, loc);
609 }
610
611 #[derive(Debug, Error)]
617 #[error("cycle in action hierarchy containing `{uid}`")]
618 pub struct CycleInActionHierarchyError {
619 pub(crate) uid: EntityUID,
620 }
621
622 impl Diagnostic for CycleInActionHierarchyError {
623 impl_diagnostic_from_method_on_field!(uid, loc);
624 }
625
626 #[derive(Debug, Error)]
632 #[error("cycle in common type references containing `{ty}`")]
633 pub struct CycleInCommonTypeReferencesError {
634 pub(crate) ty: InternalName,
635 }
636
637 impl Diagnostic for CycleInCommonTypeReferencesError {
638 impl_diagnostic_from_method_on_field!(ty, loc);
639 }
640
641 #[derive(Debug, Clone, Diagnostic, Error)]
647 #[error("entity type `Action` declared in `entityTypes` list")]
648 pub struct ActionEntityTypeDeclaredError {}
649
650 #[derive(Debug, Error)]
656 #[error("{ctx_or_shape} is declared with a type other than `Record`")]
657 pub struct ContextOrShapeNotRecordError {
658 pub(crate) ctx_or_shape: ContextOrShape,
659 }
660
661 impl Diagnostic for ContextOrShapeNotRecordError {
662 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
663 match &self.ctx_or_shape {
664 ContextOrShape::ActionContext(_) => {
665 Some(Box::new("action contexts must have type `Record`"))
666 }
667 ContextOrShape::EntityTypeShape(_) => {
668 Some(Box::new("entity type shapes must have type `Record`"))
669 }
670 }
671 }
672
673 impl_diagnostic_from_method_on_field!(ctx_or_shape, loc);
674 }
675
676 #[derive(Diagnostic, Debug, Error)]
682 #[error("internal invariant violated: this error is deprecated and should never be returned")]
683 #[deprecated = "this error is deprecated and should never be returned"]
684 pub struct ActionAttributesContainEmptySetError {}
685
686 #[derive(Diagnostic, Debug, Error)]
692 #[error("internal invariant violated: this error is deprecated and should never be returned")]
693 #[deprecated = "this error is deprecated and should never be returned"]
694 pub struct UnsupportedActionAttributeError {}
695
696 #[derive(Diagnostic, Debug, Error)]
702 #[error("internal invariant violated: this error is deprecated and should never be returned")]
703 #[deprecated = "this error is deprecated and should never be returned"]
704 pub struct ExprEscapeUsedError {}
705
706 #[derive(Diagnostic, Debug, Error)]
712 #[error("internal invariant violated: this error is deprecated and should never be returned")]
713 #[deprecated = "this error is deprecated and should never be returned"]
714 pub struct ActionAttrEvalError();
715
716 #[derive(Debug, Diagnostic, Error)]
722 #[error("unsupported feature used in schema")]
723 #[diagnostic(transparent)]
724 pub struct UnsupportedFeatureError(#[from] pub(crate) UnsupportedFeature);
725
726 #[derive(Debug)]
727 pub(crate) enum ContextOrShape {
728 ActionContext(EntityUID),
729 EntityTypeShape(EntityType),
730 }
731
732 impl ContextOrShape {
733 pub fn loc(&self) -> Option<&Loc> {
734 match self {
735 ContextOrShape::ActionContext(uid) => uid.loc(),
736 ContextOrShape::EntityTypeShape(ty) => ty.loc(),
737 }
738 }
739 }
740
741 impl std::fmt::Display for ContextOrShape {
742 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
743 match self {
744 ContextOrShape::ActionContext(action) => write!(f, "Context for action {action}"),
745 ContextOrShape::EntityTypeShape(entity_type) => {
746 write!(f, "Shape for entity type {entity_type}")
747 }
748 }
749 }
750 }
751
752 #[derive(Debug, Diagnostic, Error)]
753 pub(crate) enum UnsupportedFeature {
754 #[error("records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled")]
755 OpenRecordsAndEntities,
756 #[error("action declared with attributes: [{}]", .0.iter().join(", "))]
758 ActionAttributes(Vec<String>),
759 }
760
761 #[derive(Debug, Error)]
767 #[error("{err}")]
768 pub struct JsonDeserializationError {
769 err: serde_json::Error,
771 advice: Option<JsonDeserializationAdvice>,
773 }
774
775 impl Diagnostic for JsonDeserializationError {
776 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
777 self.advice
778 .as_ref()
779 .map(|h| Box::new(h) as Box<dyn Display>)
780 }
781 }
782
783 #[derive(Debug, Error)]
784 enum JsonDeserializationAdvice {
785 #[error("this API was expecting a schema in the JSON format; did you mean to use a different function, which expects the Cedar schema format?")]
786 CedarFormat,
787 #[error("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{{ \"\": {{..}} }}`")]
788 MissingNamespace,
789 }
790
791 impl JsonDeserializationError {
792 pub(crate) fn new(err: serde_json::Error, src: Option<&str>) -> Self {
796 match src {
797 None => Self { err, advice: None },
798 Some(src) => {
799 let advice = match src.trim_start().chars().next() {
801 None => None, Some('{') => {
803 if let Ok(serde_json::Value::Object(obj)) =
806 serde_json::from_str::<serde_json::Value>(src)
807 {
808 if obj.contains_key("entityTypes")
809 || obj.contains_key("actions")
810 || obj.contains_key("commonTypes")
811 {
812 Some(JsonDeserializationAdvice::MissingNamespace)
815 } else {
816 None
818 }
819 } else {
820 None
822 }
823 }
824 Some(_) => Some(JsonDeserializationAdvice::CedarFormat), };
826 Self { err, advice }
827 }
828 }
829 }
830 }
831
832 #[derive(Error, Debug)]
838 #[error("unknown extension type `{actual}`")]
839 pub struct UnknownExtensionTypeError {
840 pub(crate) actual: Name,
841 pub(crate) suggested_replacement: Option<String>,
842 }
843
844 impl Diagnostic for UnknownExtensionTypeError {
845 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
846 self.suggested_replacement.as_ref().map(|suggestion| {
847 Box::new(format!("did you mean `{suggestion}`?")) as Box<dyn Display>
848 })
849 }
850
851 impl_diagnostic_from_method_on_field!(actual, loc);
852 }
853
854 #[derive(Error, Debug)]
861 #[error("internal invariant violated: failed to find a common-type definition for {name}")]
862 pub struct CommonTypeInvariantViolationError {
863 pub(crate) name: InternalName,
865 }
866
867 impl Diagnostic for CommonTypeInvariantViolationError {
868 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
869 Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
870 }
871
872 impl_diagnostic_from_method_on_field!(name, loc);
873 }
874
875 #[derive(Error, Debug)]
882 #[error("internal invariant violated: failed to find {} for {}", if .euids.len() > 1 { "action definitions" } else { "an action definition" }, .euids.iter().join(", "))]
883 pub struct ActionInvariantViolationError {
884 pub(crate) euids: NonEmpty<EntityUID>,
886 }
887
888 impl Diagnostic for ActionInvariantViolationError {
889 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
890 Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
891 }
892
893 impl_diagnostic_from_method_on_nonempty_field!(euids, loc);
894 }
895}