1use std::fmt::Display;
18
19use super::SchemaType;
20use crate::ast::{
21 BorrowedRestrictedExpr, EntityAttrEvaluationError, EntityUID, Expr, ExprKind, PolicyID,
22 RestrictedExpr, RestrictedExpressionError, Type,
23};
24use crate::entities::conformance::err::EntitySchemaConformanceError;
25use crate::entities::{Name, ReservedNameError};
26use crate::extensions::ExtensionFunctionLookupError;
27use crate::parser::err::ParseErrors;
28use either::Either;
29use itertools::Itertools;
30use miette::Diagnostic;
31use smol_str::{SmolStr, ToSmolStr};
32use thiserror::Error;
33
34#[derive(Debug)]
36pub enum EscapeKind {
37 Entity,
39 Extension,
41}
42
43impl Display for EscapeKind {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 Self::Entity => write!(f, "__entity"),
47 Self::Extension => write!(f, "__extn"),
48 }
49 }
50}
51
52#[derive(Debug, Diagnostic, Error)]
58#[non_exhaustive]
59pub enum JsonDeserializationError {
60 #[error(transparent)]
62 #[diagnostic(transparent)]
63 Serde(#[from] JsonError),
64 #[error(transparent)]
66 #[diagnostic(transparent)]
67 ParseEscape(ParseEscape),
68 #[error(transparent)]
70 #[diagnostic(transparent)]
71 RestrictedExpressionError(#[from] RestrictedExpressionError),
72 #[error(transparent)]
74 #[diagnostic(transparent)]
75 ExpectedLiteralEntityRef(ExpectedLiteralEntityRef),
76 #[error(transparent)]
78 #[diagnostic(transparent)]
79 ExpectedExtnValue(ExpectedExtnValue),
80 #[error(transparent)]
82 #[diagnostic(transparent)]
83 ActionParentIsNotAction(ActionParentIsNotAction),
84 #[error(transparent)]
87 #[diagnostic(transparent)]
88 MissingImpliedConstructor(MissingImpliedConstructor),
89 #[error(transparent)]
92 #[diagnostic(transparent)]
93 IncorrectNumOfArguments(IncorrectNumOfArguments),
94 #[error(transparent)]
96 #[diagnostic(transparent)]
97 FailedExtensionFunctionLookup(#[from] ExtensionFunctionLookupError),
98 #[error(transparent)]
100 #[diagnostic(transparent)]
101 DuplicateKey(DuplicateKey),
102 #[error(transparent)]
104 #[diagnostic(transparent)]
105 EntityAttributeEvaluation(#[from] EntityAttrEvaluationError),
106 #[error(transparent)]
112 #[diagnostic(transparent)]
113 EntitySchemaConformance(#[from] EntitySchemaConformanceError),
114 #[error(transparent)]
117 #[diagnostic(transparent)]
118 UnexpectedRecordAttr(UnexpectedRecordAttr),
119 #[error(transparent)]
122 #[diagnostic(transparent)]
123 MissingRequiredRecordAttr(MissingRequiredRecordAttr),
124 #[error(transparent)]
131 #[diagnostic(transparent)]
132 TypeMismatch(TypeMismatch),
133 #[cfg(feature = "tolerant-ast")]
135 #[error("Unable to deserialize an AST Error node")]
136 #[diagnostic(help("AST error node indicates that the expression has failed to parse"))]
137 ASTErrorNode,
138 #[error("{0}, the `__expr` escape is no longer supported")]
140 #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
141 ExprTag(Box<JsonDeserializationErrorContext>),
142 #[error("{0}, found a `null`; JSON `null`s are not allowed in Cedar")]
144 Null(Box<JsonDeserializationErrorContext>),
145 #[error(transparent)]
147 #[diagnostic(transparent)]
148 ReservedName(#[from] ReservedNameError),
149 #[deprecated(
153 since = "4.2.0",
154 note = "entity-tags is now stable and fully supported, so this error never occurs"
155 )]
156 #[error("entity tags are not supported in this build; to use entity tags, you must enable the `entity-tags` experimental feature")]
157 UnsupportedEntityTags,
158}
159
160impl JsonDeserializationError {
161 pub(crate) fn parse_escape(
162 kind: EscapeKind,
163 value: impl Into<String>,
164 errs: ParseErrors,
165 ) -> Self {
166 Self::ParseEscape(ParseEscape {
167 kind,
168 value: value.into(),
169 errs,
170 })
171 }
172
173 pub(crate) fn expected_entity_ref(
174 ctx: JsonDeserializationErrorContext,
175 got: Either<serde_json::Value, Expr>,
176 ) -> Self {
177 Self::ExpectedLiteralEntityRef(ExpectedLiteralEntityRef {
178 ctx: Box::new(ctx),
179 got: Box::new(got),
180 })
181 }
182
183 pub(crate) fn action_parent_is_not_action(uid: EntityUID, parent: EntityUID) -> Self {
184 Self::ActionParentIsNotAction(ActionParentIsNotAction { uid, parent })
185 }
186
187 pub(crate) fn missing_implied_constructor(
188 ctx: JsonDeserializationErrorContext,
189 return_type: SchemaType,
190 ) -> Self {
191 Self::MissingImpliedConstructor(MissingImpliedConstructor {
192 ctx: Box::new(ctx),
193 return_type: Box::new(return_type),
194 })
195 }
196
197 pub(crate) fn incorrect_num_of_arguments(
198 expected_arg_num: usize,
199 provided_arg_num: usize,
200 func_name: &str,
201 ) -> Self {
202 Self::IncorrectNumOfArguments(IncorrectNumOfArguments {
203 expected_arg_num,
204 provided_arg_num,
205 func_name: func_name.to_smolstr(),
206 })
207 }
208
209 pub(crate) fn duplicate_key(
210 ctx: JsonDeserializationErrorContext,
211 key: impl Into<SmolStr>,
212 ) -> Self {
213 Self::DuplicateKey(DuplicateKey {
214 ctx: Box::new(ctx),
215 key: key.into(),
216 })
217 }
218
219 pub(crate) fn unexpected_record_attr(
220 ctx: JsonDeserializationErrorContext,
221 record_attr: impl Into<SmolStr>,
222 ) -> Self {
223 Self::UnexpectedRecordAttr(UnexpectedRecordAttr {
224 ctx: Box::new(ctx),
225 record_attr: record_attr.into(),
226 })
227 }
228
229 pub(crate) fn missing_required_record_attr(
230 ctx: JsonDeserializationErrorContext,
231 record_attr: impl Into<SmolStr>,
232 ) -> Self {
233 Self::MissingRequiredRecordAttr(MissingRequiredRecordAttr {
234 ctx: Box::new(ctx),
235 record_attr: record_attr.into(),
236 })
237 }
238
239 pub(crate) fn type_mismatch(
240 ctx: JsonDeserializationErrorContext,
241 err: TypeMismatchError,
242 ) -> Self {
243 Self::TypeMismatch(TypeMismatch {
244 ctx: Box::new(ctx),
245 err,
246 })
247 }
248}
249
250#[derive(Debug, Error, Diagnostic)]
251#[error("{ctx}, {err}")]
252#[diagnostic(forward(err))]
253pub struct TypeMismatch {
255 ctx: Box<JsonDeserializationErrorContext>,
259 err: TypeMismatchError,
261}
262
263#[derive(Debug, Error, Diagnostic)]
264#[error("{}, expected the record to have an attribute `{}`, but it does not", .ctx, .record_attr)]
265pub struct MissingRequiredRecordAttr {
267 ctx: Box<JsonDeserializationErrorContext>,
269 record_attr: SmolStr,
271}
272
273#[derive(Debug, Diagnostic, Error)]
274#[error("{}, record attribute `{}` should not exist according to the schema", .ctx, .record_attr)]
275pub struct UnexpectedRecordAttr {
277 ctx: Box<JsonDeserializationErrorContext>,
279 record_attr: SmolStr,
281}
282
283#[derive(Debug, Error, Diagnostic)]
284#[error("{}, duplicate key `{}` in record", .ctx, .key)]
285pub struct DuplicateKey {
287 ctx: Box<JsonDeserializationErrorContext>,
289 key: SmolStr,
291}
292
293#[derive(Debug, Error, Diagnostic)]
294#[error("{}, missing extension constructor for {}", .ctx, .return_type)]
295#[diagnostic(help("expected a value of type {} because of the schema", .return_type))]
296pub struct MissingImpliedConstructor {
298 ctx: Box<JsonDeserializationErrorContext>,
300 return_type: Box<SchemaType>,
302}
303
304#[derive(Debug, Error, Diagnostic)]
305#[error("expected {expected_arg_num} arguments for function {func_name} but {provided_arg_num} arguments are provided")]
306pub struct IncorrectNumOfArguments {
308 expected_arg_num: usize,
310 provided_arg_num: usize,
312 func_name: SmolStr,
314}
315
316#[derive(Debug, Error, Diagnostic)]
317#[error("action `{}` has a non-action parent `{}`", .uid, .parent)]
318#[diagnostic(help("parents of actions need to have type `Action` themselves, perhaps namespaced"))]
319pub struct ActionParentIsNotAction {
321 uid: EntityUID,
323 parent: EntityUID,
325}
326
327#[derive(Debug, Error, Diagnostic)]
328#[error("failed to parse escape `{kind}`: {value}, errors: {errs}")]
329#[diagnostic(
330 help("{}", match .kind {
331 EscapeKind::Entity => r#"an __entity escape should have a value like `{ "type": "SomeType", "id": "SomeId" }`"#,
332 EscapeKind::Extension => r#"an __extn escape should have a value like `{ "fn": "SomeFn", "arg": "SomeArg" }`"#,
333 }),
334 forward(errs)
335)]
336pub struct ParseEscape {
338 kind: EscapeKind,
340 value: String,
342 errs: ParseErrors,
344}
345
346#[derive(Debug, Error, Diagnostic)]
347#[error("{}, expected a literal entity reference, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
348#[diagnostic(help(
349 r#"literal entity references can be made with `{{ "type": "SomeType", "id": "SomeId" }}`"#
350))]
351pub struct ExpectedLiteralEntityRef {
353 ctx: Box<JsonDeserializationErrorContext>,
355 got: Box<Either<serde_json::Value, Expr>>,
357}
358
359#[derive(Debug, Error, Diagnostic)]
360#[error("{}, expected an extension value, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
361#[diagnostic(help(r#"extension values can be made with `{{ "fn": "SomeFn", "id": "SomeId" }}`"#))]
362pub struct ExpectedExtnValue {
364 ctx: Box<JsonDeserializationErrorContext>,
366 got: Box<Either<serde_json::Value, Expr>>,
368}
369
370#[derive(Debug, Error, Diagnostic)]
376#[error(transparent)]
377pub struct JsonError(#[from] serde_json::Error);
378
379impl From<serde_json::Error> for JsonDeserializationError {
380 fn from(value: serde_json::Error) -> Self {
381 Self::Serde(JsonError(value))
382 }
383}
384
385impl From<serde_json::Error> for JsonSerializationError {
386 fn from(value: serde_json::Error) -> Self {
387 Self::Serde(JsonError(value))
388 }
389}
390
391#[derive(Debug, Diagnostic, Error)]
397#[non_exhaustive]
398pub enum JsonSerializationError {
399 #[error(transparent)]
401 #[diagnostic(transparent)]
402 Serde(#[from] JsonError),
403 #[error(transparent)]
406 #[diagnostic(transparent)]
407 ExtnCall0Arguments(ExtnCall0Arguments),
408 #[error(transparent)]
411 #[diagnostic(transparent)]
412 ReservedKey(ReservedKey),
413 #[error(transparent)]
417 #[diagnostic(transparent)]
418 UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
419 #[error(transparent)]
422 #[diagnostic(transparent)]
423 Residual(Residual),
424 #[error(transparent)]
428 #[diagnostic(transparent)]
429 ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
430}
431
432impl JsonSerializationError {
433 pub(crate) fn call_0_args(func: Name) -> Self {
434 Self::ExtnCall0Arguments(ExtnCall0Arguments { func })
435 }
436
437 pub(crate) fn reserved_key(key: impl Into<SmolStr>) -> Self {
438 Self::ReservedKey(ReservedKey { key: key.into() })
439 }
440
441 pub(crate) fn unexpected_restricted_expr_kind(kind: ExprKind) -> Self {
442 Self::UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind { kind })
443 }
444
445 pub(crate) fn residual(residual: Expr) -> Self {
446 Self::Residual(Residual { residual })
447 }
448}
449
450#[derive(Debug, Error, Diagnostic)]
456#[error("unsupported call to `{}` with 0 arguments", .func)]
457#[diagnostic(help(
458 "extension function calls with 0 arguments are not currently supported in our JSON format"
459))]
460pub struct ExtnCall0Arguments {
461 func: Name,
463}
464
465#[derive(Debug, Error, Diagnostic)]
472#[error("unsupported call to `{}` with 2 or more arguments", .func)]
473#[diagnostic(help("extension function calls with 2 or more arguments are not currently supported in our JSON format"))]
474pub struct ExtnCall2OrMoreArguments {
475 func: Name,
477}
478
479#[derive(Debug, Error, Diagnostic)]
485#[error("record uses reserved key `{}`", .key)]
486pub struct ReservedKey {
487 key: SmolStr,
489}
490
491impl ReservedKey {
492 pub fn key(&self) -> impl AsRef<str> + '_ {
494 &self.key
495 }
496}
497
498#[derive(Debug, Error, Diagnostic)]
504#[error("unexpected restricted expression `{:?}`", .kind)]
505pub struct UnexpectedRestrictedExprKind {
506 kind: ExprKind,
508}
509
510#[derive(Debug, Error, Diagnostic)]
516#[error("cannot encode residual as JSON: {}", .residual)]
517pub struct Residual {
518 residual: Expr,
520}
521
522#[derive(Debug, Clone)]
525pub enum JsonDeserializationErrorContext {
526 EntityAttribute {
528 uid: EntityUID,
530 attr: SmolStr,
532 },
533 EntityTag {
535 uid: EntityUID,
537 tag: SmolStr,
539 },
540 EntityParents {
542 uid: EntityUID,
544 },
545 EntityUid,
547 Context,
549 Policy {
551 id: PolicyID,
553 },
554 TemplateLink,
556 Unknown,
558}
559
560#[derive(Debug, Diagnostic, Error)]
562#[error("type mismatch: value was expected to have type {expected}, but it {mismatch_reason}: `{}`",
563 display_restricted_expr(.actual_val.as_borrowed()),
564)]
565pub struct TypeMismatchError {
566 expected: Box<SchemaType>,
568 mismatch_reason: TypeMismatchReason,
570 actual_val: Box<RestrictedExpr>,
572}
573
574#[derive(Debug, Error)]
575enum TypeMismatchReason {
576 #[error("actually has type {0}")]
579 UnexpectedType(Type),
580 #[error("contains an unexpected attribute `{0}`")]
583 UnexpectedAttr(SmolStr),
584 #[error("is missing the required attribute `{0}`")]
587 MissingRequiredAtr(SmolStr),
588 #[error("does not")]
590 None,
591}
592
593impl TypeMismatchError {
594 pub(crate) fn type_mismatch(
595 expected: SchemaType,
596 actual_ty: Option<Type>,
597 actual_val: RestrictedExpr,
598 ) -> Self {
599 Self {
600 expected: Box::new(expected),
601 mismatch_reason: match actual_ty {
602 Some(ty) => TypeMismatchReason::UnexpectedType(ty),
603 None => TypeMismatchReason::None,
604 },
605 actual_val: Box::new(actual_val),
606 }
607 }
608
609 pub(crate) fn unexpected_attr(
610 expected: SchemaType,
611 unexpected_attr: SmolStr,
612 actual_val: RestrictedExpr,
613 ) -> Self {
614 Self {
615 expected: Box::new(expected),
616 mismatch_reason: TypeMismatchReason::UnexpectedAttr(unexpected_attr),
617 actual_val: Box::new(actual_val),
618 }
619 }
620
621 pub(crate) fn missing_required_attr(
622 expected: SchemaType,
623 missing_attr: SmolStr,
624 actual_val: RestrictedExpr,
625 ) -> Self {
626 Self {
627 expected: Box::new(expected),
628 mismatch_reason: TypeMismatchReason::MissingRequiredAtr(missing_attr),
629 actual_val: Box::new(actual_val),
630 }
631 }
632}
633
634impl std::fmt::Display for JsonDeserializationErrorContext {
635 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
636 match self {
637 Self::EntityAttribute { uid, attr } => write!(f, "in attribute `{attr}` on `{uid}`"),
638 Self::EntityTag { uid, tag } => write!(f, "in tag `{tag}` on `{uid}`"),
639 Self::EntityParents { uid } => write!(f, "in parents field of `{uid}`"),
640 Self::EntityUid => write!(f, "in uid field of <unknown entity>"),
641 Self::Context => write!(f, "while parsing context"),
642 Self::Policy { id } => write!(f, "while parsing JSON policy `{id}`"),
643 Self::TemplateLink => write!(f, "while parsing a template link"),
644 Self::Unknown => write!(f, "parsing context was unknown, please file a bug report at https://github.com/cedar-policy/cedar so we can improve this error message"),
645 }
646 }
647}
648
649fn display_json_value(v: &Either<serde_json::Value, Expr>) -> String {
650 match v {
651 Either::Left(json) => display_value(json),
652 Either::Right(e) => e.to_string(),
653 }
654}
655
656fn display_value(v: &serde_json::Value) -> String {
664 match v {
665 serde_json::Value::Array(contents) => {
666 format!("[{}]", contents.iter().map(display_value).join(", "))
667 }
668 serde_json::Value::Object(map) => {
669 let mut v: Vec<_> = map.iter().collect();
670 v.sort_by_key(|p| p.0);
672 let display_kv = |kv: &(&String, &serde_json::Value)| format!("\"{}\":{}", kv.0, kv.1);
673 format!("{{{}}}", v.iter().map(display_kv).join(","))
674 }
675 other => other.to_string(),
676 }
677}
678
679fn display_restricted_expr(expr: BorrowedRestrictedExpr<'_>) -> String {
683 match expr.expr_kind() {
684 ExprKind::Set(elements) => {
685 let restricted_exprs = elements.iter().map(BorrowedRestrictedExpr::new_unchecked); format!(
687 "[{}]",
688 restricted_exprs
689 .map(display_restricted_expr)
690 .sorted_unstable()
691 .join(", ")
692 )
693 }
694 ExprKind::Record(m) => {
695 format!(
696 "{{{}}}",
697 m.iter()
698 .sorted_unstable_by_key(|(k, _)| SmolStr::clone(k))
699 .map(|(k, v)| format!("\"{}\": {}", k.escape_debug(), v))
700 .join(", ")
701 )
702 }
703 _ => format!("{expr}"), }
705}