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
313impl From<NonEmpty<SchemaError>> for SchemaError {
314 fn from(errs: NonEmpty<SchemaError>) -> Self {
315 Self::join_nonempty(errs)
316 }
317}
318
319impl From<NonEmpty<schema_errors::ActionNotDefinedError>> for SchemaError {
320 fn from(errs: NonEmpty<schema_errors::ActionNotDefinedError>) -> Self {
321 Self::ActionNotDefined(schema_errors::ActionNotDefinedError::join_nonempty(errs))
322 }
323}
324
325impl From<NonEmpty<schema_errors::TypeNotDefinedError>> for SchemaError {
326 fn from(errs: NonEmpty<schema_errors::TypeNotDefinedError>) -> Self {
327 Self::TypeNotDefined(schema_errors::TypeNotDefinedError::join_nonempty(errs))
328 }
329}
330
331pub type Result<T> = std::result::Result<T, SchemaError>;
333
334pub mod schema_errors {
336 use std::fmt::Display;
337
338 use cedar_policy_core::ast::{
339 EntityAttrEvaluationError, EntityType, EntityUID, InternalName, Name,
340 };
341 use cedar_policy_core::parser::{join_with_conjunction, Loc};
342 use cedar_policy_core::{
343 impl_diagnostic_from_method_on_field, impl_diagnostic_from_method_on_nonempty_field,
344 transitive_closure,
345 };
346 use itertools::Itertools;
347 use miette::Diagnostic;
348 use nonempty::NonEmpty;
349 use smol_str::SmolStr;
350 use thiserror::Error;
351
352 #[derive(Debug, Diagnostic, Error)]
358 #[error(transparent)]
359 pub struct JsonSerializationError(#[from] pub(crate) serde_json::Error);
360
361 #[derive(Debug, Diagnostic, Error)]
367 #[error("transitive closure computation/enforcement error on action hierarchy")]
368 #[diagnostic(transparent)]
369 pub struct ActionTransitiveClosureError(
370 #[from] pub(crate) Box<transitive_closure::TcError<EntityUID>>,
371 );
372
373 #[derive(Debug, Diagnostic, Error)]
379 #[error("transitive closure computation/enforcement error on entity type hierarchy")]
380 #[diagnostic(transparent)]
381 pub struct EntityTypeTransitiveClosureError(
382 #[from] pub(crate) Box<transitive_closure::TcError<EntityType>>,
383 );
384
385 #[derive(Debug, Error)]
391 pub struct UndeclaredEntityTypesError {
392 pub(crate) types: NonEmpty<EntityType>,
394 }
395
396 impl Display for UndeclaredEntityTypesError {
397 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398 if self.types.len() == 1 {
399 write!(f, "undeclared entity type: ")?;
400 } else {
401 write!(f, "undeclared entity types: ")?;
402 }
403 join_with_conjunction(f, "and", self.types.iter().sorted_unstable(), |f, s| {
404 s.fmt(f)
405 })
406 }
407 }
408
409 impl Diagnostic for UndeclaredEntityTypesError {
410 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
411 Some(Box::new("any entity types appearing anywhere in a schema need to be declared in `entityTypes`"))
412 }
413
414 impl_diagnostic_from_method_on_nonempty_field!(types, loc);
415 }
416
417 #[derive(Debug, Error)]
423 #[error("failed to resolve type{}: {}", if .undefined_types.len() > 1 { "s" } else { "" }, .undefined_types.iter().map(crate::ConditionalName::raw).join(", "))]
424 pub struct TypeNotDefinedError {
425 pub(crate) undefined_types: NonEmpty<crate::ConditionalName>,
427 }
428
429 impl Diagnostic for TypeNotDefinedError {
430 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
431 Some(Box::new(
433 self.undefined_types.first().resolution_failure_help(),
434 ))
435 }
436
437 impl_diagnostic_from_method_on_nonempty_field!(undefined_types, loc);
438 }
439
440 impl TypeNotDefinedError {
441 pub(crate) fn join_nonempty(errs: NonEmpty<TypeNotDefinedError>) -> Self {
446 Self {
447 undefined_types: errs.flat_map(|err| err.undefined_types),
448 }
449 }
450 }
451
452 impl From<NonEmpty<TypeNotDefinedError>> for TypeNotDefinedError {
453 fn from(value: NonEmpty<TypeNotDefinedError>) -> Self {
454 Self::join_nonempty(value)
455 }
456 }
457
458 #[derive(Debug, Diagnostic, Error)]
464 #[diagnostic(help("any actions appearing as parents need to be declared as actions"))]
465 pub struct ActionNotDefinedError(
466 pub(crate) NonEmpty<crate::json_schema::ActionEntityUID<crate::ConditionalName>>,
467 );
468
469 impl ActionNotDefinedError {
470 pub(crate) fn join_nonempty(errs: NonEmpty<ActionNotDefinedError>) -> Self {
475 Self(errs.flat_map(|err| err.0))
476 }
477 }
478
479 impl From<NonEmpty<ActionNotDefinedError>> for ActionNotDefinedError {
480 fn from(value: NonEmpty<ActionNotDefinedError>) -> Self {
481 Self::join_nonempty(value)
482 }
483 }
484
485 impl Display for ActionNotDefinedError {
486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487 if self.0.len() == 1 {
488 write!(f, "undeclared action: ")?;
489 } else {
490 write!(f, "undeclared actions: ")?;
491 }
492 join_with_conjunction(
493 f,
494 "and",
495 self.0.iter().map(|aeuid| aeuid.as_raw()),
496 |f, s| s.fmt(f),
497 )
498 }
499 }
500
501 #[derive(Debug, Error)]
509 #[error(
510 "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
511 )]
512 pub struct TypeShadowingError {
513 pub(crate) shadowed_def: InternalName,
515 pub(crate) shadowing_def: InternalName,
517 }
518
519 impl Diagnostic for TypeShadowingError {
520 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
521 Some(Box::new(format!(
522 "try renaming one of the definitions, or moving `{}` to a different namespace",
523 self.shadowed_def
524 )))
525 }
526
527 impl_diagnostic_from_method_on_field!(shadowing_def, loc);
530 }
531
532 #[derive(Debug, Error)]
540 #[error(
541 "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
542 )]
543 pub struct ActionShadowingError {
544 pub(crate) shadowed_def: EntityUID,
546 pub(crate) shadowing_def: EntityUID,
548 }
549
550 impl Diagnostic for ActionShadowingError {
551 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
552 Some(Box::new(format!(
553 "try renaming one of the actions, or moving `{}` to a different namespace",
554 self.shadowed_def
555 )))
556 }
557
558 impl_diagnostic_from_method_on_field!(shadowing_def, loc);
561 }
562
563 #[derive(Debug, Error)]
569 #[error("duplicate entity type `{ty}`")]
570 pub struct DuplicateEntityTypeError {
571 pub(crate) ty: EntityType,
572 }
573
574 impl Diagnostic for DuplicateEntityTypeError {
575 impl_diagnostic_from_method_on_field!(ty, loc);
576 }
577
578 #[derive(Debug, Diagnostic, Error)]
584 #[error("duplicate action `{0}`")]
585 pub struct DuplicateActionError(pub(crate) SmolStr);
586
587 #[derive(Debug, Error)]
593 #[error("duplicate common type `{ty}`")]
594 pub struct DuplicateCommonTypeError {
595 pub(crate) ty: InternalName,
596 }
597
598 impl Diagnostic for DuplicateCommonTypeError {
599 impl_diagnostic_from_method_on_field!(ty, loc);
600 }
601
602 #[derive(Debug, Error)]
608 #[error("cycle in action hierarchy containing `{uid}`")]
609 pub struct CycleInActionHierarchyError {
610 pub(crate) uid: EntityUID,
611 }
612
613 impl Diagnostic for CycleInActionHierarchyError {
614 impl_diagnostic_from_method_on_field!(uid, loc);
615 }
616
617 #[derive(Debug, Error)]
623 #[error("cycle in common type references containing `{ty}`")]
624 pub struct CycleInCommonTypeReferencesError {
625 pub(crate) ty: InternalName,
626 }
627
628 impl Diagnostic for CycleInCommonTypeReferencesError {
629 impl_diagnostic_from_method_on_field!(ty, loc);
630 }
631
632 #[derive(Debug, Clone, Diagnostic, Error)]
638 #[error("entity type `Action` declared in `entityTypes` list")]
639 pub struct ActionEntityTypeDeclaredError {}
640
641 #[derive(Debug, Error)]
647 #[error("{ctx_or_shape} is declared with a type other than `Record`")]
648 pub struct ContextOrShapeNotRecordError {
649 pub(crate) ctx_or_shape: ContextOrShape,
650 }
651
652 impl Diagnostic for ContextOrShapeNotRecordError {
653 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
654 match &self.ctx_or_shape {
655 ContextOrShape::ActionContext(_) => {
656 Some(Box::new("action contexts must have type `Record`"))
657 }
658 ContextOrShape::EntityTypeShape(_) => {
659 Some(Box::new("entity type shapes must have type `Record`"))
660 }
661 }
662 }
663
664 impl_diagnostic_from_method_on_field!(ctx_or_shape, loc);
665 }
666
667 #[derive(Debug, Error)]
673 #[error("action `{uid}` has an attribute that is an empty set")]
674 pub struct ActionAttributesContainEmptySetError {
675 pub(crate) uid: EntityUID,
676 }
677
678 impl Diagnostic for ActionAttributesContainEmptySetError {
679 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
680 Some(Box::new(
681 "actions are not currently allowed to have attributes whose value is an empty set",
682 ))
683 }
684
685 impl_diagnostic_from_method_on_field!(uid, loc);
686 }
687
688 #[derive(Debug, Error)]
694 #[error("action `{uid}` has an attribute with unsupported JSON representation: {attr}")]
695 pub struct UnsupportedActionAttributeError {
696 pub(crate) uid: EntityUID,
697 pub(crate) attr: SmolStr,
698 }
699
700 impl Diagnostic for UnsupportedActionAttributeError {
701 impl_diagnostic_from_method_on_field!(uid, loc);
702 }
703
704 #[derive(Debug, Clone, Diagnostic, Error)]
710 #[error("the `__expr` escape is no longer supported")]
711 #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
712 pub struct ExprEscapeUsedError {}
713
714 #[derive(Debug, Diagnostic, Error)]
720 #[error(transparent)]
721 #[diagnostic(transparent)]
722 pub struct ActionAttrEvalError(#[from] pub(crate) EntityAttrEvaluationError);
723
724 #[derive(Debug, Diagnostic, Error)]
730 #[error("unsupported feature used in schema")]
731 #[diagnostic(transparent)]
732 pub struct UnsupportedFeatureError(#[from] pub(crate) UnsupportedFeature);
733
734 #[derive(Debug)]
735 pub(crate) enum ContextOrShape {
736 ActionContext(EntityUID),
737 EntityTypeShape(EntityType),
738 }
739
740 impl ContextOrShape {
741 pub fn loc(&self) -> Option<&Loc> {
742 match self {
743 ContextOrShape::ActionContext(uid) => uid.loc(),
744 ContextOrShape::EntityTypeShape(ty) => ty.loc(),
745 }
746 }
747 }
748
749 impl std::fmt::Display for ContextOrShape {
750 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
751 match self {
752 ContextOrShape::ActionContext(action) => write!(f, "Context for action {}", action),
753 ContextOrShape::EntityTypeShape(entity_type) => {
754 write!(f, "Shape for entity type {}", entity_type)
755 }
756 }
757 }
758 }
759
760 #[derive(Debug, Diagnostic, Error)]
761 pub(crate) enum UnsupportedFeature {
762 #[error("records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled")]
763 OpenRecordsAndEntities,
764 #[error("action declared with attributes: [{}]", .0.iter().join(", "))]
766 ActionAttributes(Vec<String>),
767 }
768
769 #[derive(Debug, Error)]
775 #[error("{err}")]
776 pub struct JsonDeserializationError {
777 err: serde_json::Error,
779 advice: Option<JsonDeserializationAdvice>,
781 }
782
783 impl Diagnostic for JsonDeserializationError {
784 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
785 self.advice
786 .as_ref()
787 .map(|h| Box::new(h) as Box<dyn Display>)
788 }
789 }
790
791 #[derive(Debug, Error)]
792 enum JsonDeserializationAdvice {
793 #[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?")]
794 CedarFormat,
795 #[error("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{{ \"\": {{..}} }}`")]
796 MissingNamespace,
797 }
798
799 impl JsonDeserializationError {
800 pub(crate) fn new(err: serde_json::Error, src: Option<&str>) -> Self {
804 match src {
805 None => Self { err, advice: None },
806 Some(src) => {
807 let advice = match src.trim_start().chars().next() {
809 None => None, Some('{') => {
811 if let Ok(serde_json::Value::Object(obj)) =
814 serde_json::from_str::<serde_json::Value>(src)
815 {
816 if obj.contains_key("entityTypes")
817 || obj.contains_key("actions")
818 || obj.contains_key("commonTypes")
819 {
820 Some(JsonDeserializationAdvice::MissingNamespace)
823 } else {
824 None
826 }
827 } else {
828 None
830 }
831 }
832 Some(_) => Some(JsonDeserializationAdvice::CedarFormat), };
834 Self { err, advice }
835 }
836 }
837 }
838 }
839
840 #[derive(Error, Debug)]
846 #[error("unknown extension type `{actual}`")]
847 pub struct UnknownExtensionTypeError {
848 pub(crate) actual: Name,
849 pub(crate) suggested_replacement: Option<String>,
850 }
851
852 impl Diagnostic for UnknownExtensionTypeError {
853 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
854 self.suggested_replacement.as_ref().map(|suggestion| {
855 Box::new(format!("did you mean `{suggestion}`?")) as Box<dyn Display>
856 })
857 }
858
859 impl_diagnostic_from_method_on_field!(actual, loc);
860 }
861
862 #[derive(Error, Debug)]
869 #[error("internal invariant violated: failed to find a common-type definition for {name}")]
870 pub struct CommonTypeInvariantViolationError {
871 pub(crate) name: InternalName,
873 }
874
875 impl Diagnostic for CommonTypeInvariantViolationError {
876 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
877 Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
878 }
879
880 impl_diagnostic_from_method_on_field!(name, loc);
881 }
882
883 #[derive(Error, Debug)]
890 #[error("internal invariant violated: failed to find {} for {}", if .euids.len() > 1 { "action definitions" } else { "an action definition" }, .euids.iter().join(", "))]
891 pub struct ActionInvariantViolationError {
892 pub(crate) euids: NonEmpty<EntityUID>,
894 }
895
896 impl Diagnostic for ActionInvariantViolationError {
897 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
898 Some(Box::new("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error"))
899 }
900
901 impl_diagnostic_from_method_on_nonempty_field!(euids, loc);
902 }
903}