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}")]
252pub struct TypeMismatch {
254 ctx: Box<JsonDeserializationErrorContext>,
258 #[diagnostic(transparent)]
260 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(help("{}", match .kind {
330 EscapeKind::Entity => r#"an __entity escape should have a value like `{ "type": "SomeType", "id": "SomeId" }`"#,
331 EscapeKind::Extension => r#"an __extn escape should have a value like `{ "fn": "SomeFn", "arg": "SomeArg" }`"#,
332 }))]
333pub struct ParseEscape {
335 kind: EscapeKind,
337 value: String,
339 #[diagnostic(transparent)]
341 errs: ParseErrors,
342}
343
344#[derive(Debug, Error, Diagnostic)]
345#[error("{}, expected a literal entity reference, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
346#[diagnostic(help(
347 r#"literal entity references can be made with `{{ "type": "SomeType", "id": "SomeId" }}`"#
348))]
349pub struct ExpectedLiteralEntityRef {
351 ctx: Box<JsonDeserializationErrorContext>,
353 got: Box<Either<serde_json::Value, Expr>>,
355}
356
357#[derive(Debug, Error, Diagnostic)]
358#[error("{}, expected an extension value, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
359#[diagnostic(help(r#"extension values can be made with `{{ "fn": "SomeFn", "id": "SomeId" }}`"#))]
360pub struct ExpectedExtnValue {
362 ctx: Box<JsonDeserializationErrorContext>,
364 got: Box<Either<serde_json::Value, Expr>>,
366}
367
368#[derive(Debug, Error, Diagnostic)]
369#[error(transparent)]
370pub struct JsonError(#[from] serde_json::Error);
372
373impl From<serde_json::Error> for JsonDeserializationError {
374 fn from(value: serde_json::Error) -> Self {
375 Self::Serde(JsonError(value))
376 }
377}
378
379impl From<serde_json::Error> for JsonSerializationError {
380 fn from(value: serde_json::Error) -> Self {
381 Self::Serde(JsonError(value))
382 }
383}
384
385#[derive(Debug, Diagnostic, Error)]
387#[non_exhaustive]
388pub enum JsonSerializationError {
389 #[error(transparent)]
391 #[diagnostic(transparent)]
392 Serde(#[from] JsonError),
393 #[error(transparent)]
396 #[diagnostic(transparent)]
397 ExtnCall0Arguments(ExtnCall0Arguments),
398 #[error(transparent)]
401 #[diagnostic(transparent)]
402 ReservedKey(ReservedKey),
403 #[error(transparent)]
407 #[diagnostic(transparent)]
408 UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
409 #[error(transparent)]
412 #[diagnostic(transparent)]
413 Residual(Residual),
414 #[error(transparent)]
418 #[diagnostic(transparent)]
419 ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
420}
421
422impl JsonSerializationError {
423 pub(crate) fn call_0_args(func: Name) -> Self {
424 Self::ExtnCall0Arguments(ExtnCall0Arguments { func })
425 }
426
427 pub(crate) fn reserved_key(key: impl Into<SmolStr>) -> Self {
428 Self::ReservedKey(ReservedKey { key: key.into() })
429 }
430
431 pub(crate) fn unexpected_restricted_expr_kind(kind: ExprKind) -> Self {
432 Self::UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind { kind })
433 }
434
435 pub(crate) fn residual(residual: Expr) -> Self {
436 Self::Residual(Residual { residual })
437 }
438}
439
440#[derive(Debug, Error, Diagnostic)]
442#[error("unsupported call to `{}` with 0 arguments", .func)]
443#[diagnostic(help(
444 "extension function calls with 0 arguments are not currently supported in our JSON format"
445))]
446pub struct ExtnCall0Arguments {
447 func: Name,
449}
450
451#[derive(Debug, Error, Diagnostic)]
454#[error("unsupported call to `{}` with 2 or more arguments", .func)]
455#[diagnostic(help("extension function calls with 2 or more arguments are not currently supported in our JSON format"))]
456pub struct ExtnCall2OrMoreArguments {
457 func: Name,
459}
460
461#[derive(Debug, Error, Diagnostic)]
463#[error("record uses reserved key `{}`", .key)]
464pub struct ReservedKey {
465 key: SmolStr,
467}
468
469impl ReservedKey {
470 pub fn key(&self) -> impl AsRef<str> + '_ {
472 &self.key
473 }
474}
475
476#[derive(Debug, Error, Diagnostic)]
478#[error("unexpected restricted expression `{:?}`", .kind)]
479pub struct UnexpectedRestrictedExprKind {
480 kind: ExprKind,
482}
483
484#[derive(Debug, Error, Diagnostic)]
486#[error("cannot encode residual as JSON: {}", .residual)]
487pub struct Residual {
488 residual: Expr,
490}
491
492#[derive(Debug, Clone)]
495pub enum JsonDeserializationErrorContext {
496 EntityAttribute {
498 uid: EntityUID,
500 attr: SmolStr,
502 },
503 EntityTag {
505 uid: EntityUID,
507 tag: SmolStr,
509 },
510 EntityParents {
512 uid: EntityUID,
514 },
515 EntityUid,
517 Context,
519 Policy {
521 id: PolicyID,
523 },
524 TemplateLink,
526 Unknown,
528}
529
530#[derive(Debug, Diagnostic, Error)]
532#[error("type mismatch: value was expected to have type {expected}, but it {mismatch_reason}: `{}`",
533 display_restricted_expr(.actual_val.as_borrowed()),
534)]
535pub struct TypeMismatchError {
536 expected: Box<SchemaType>,
538 mismatch_reason: TypeMismatchReason,
540 actual_val: Box<RestrictedExpr>,
542}
543
544#[derive(Debug, Error)]
545enum TypeMismatchReason {
546 #[error("actually has type {0}")]
549 UnexpectedType(Type),
550 #[error("contains an unexpected attribute `{0}`")]
553 UnexpectedAttr(SmolStr),
554 #[error("is missing the required attribute `{0}`")]
557 MissingRequiredAtr(SmolStr),
558 #[error("does not")]
560 None,
561}
562
563impl TypeMismatchError {
564 pub(crate) fn type_mismatch(
565 expected: SchemaType,
566 actual_ty: Option<Type>,
567 actual_val: RestrictedExpr,
568 ) -> Self {
569 Self {
570 expected: Box::new(expected),
571 mismatch_reason: match actual_ty {
572 Some(ty) => TypeMismatchReason::UnexpectedType(ty),
573 None => TypeMismatchReason::None,
574 },
575 actual_val: Box::new(actual_val),
576 }
577 }
578
579 pub(crate) fn unexpected_attr(
580 expected: SchemaType,
581 unexpected_attr: SmolStr,
582 actual_val: RestrictedExpr,
583 ) -> Self {
584 Self {
585 expected: Box::new(expected),
586 mismatch_reason: TypeMismatchReason::UnexpectedAttr(unexpected_attr),
587 actual_val: Box::new(actual_val),
588 }
589 }
590
591 pub(crate) fn missing_required_attr(
592 expected: SchemaType,
593 missing_attr: SmolStr,
594 actual_val: RestrictedExpr,
595 ) -> Self {
596 Self {
597 expected: Box::new(expected),
598 mismatch_reason: TypeMismatchReason::MissingRequiredAtr(missing_attr),
599 actual_val: Box::new(actual_val),
600 }
601 }
602}
603
604impl std::fmt::Display for JsonDeserializationErrorContext {
605 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
606 match self {
607 Self::EntityAttribute { uid, attr } => write!(f, "in attribute `{attr}` on `{uid}`"),
608 Self::EntityTag { uid, tag } => write!(f, "in tag `{tag}` on `{uid}`"),
609 Self::EntityParents { uid } => write!(f, "in parents field of `{uid}`"),
610 Self::EntityUid => write!(f, "in uid field of <unknown entity>"),
611 Self::Context => write!(f, "while parsing context"),
612 Self::Policy { id } => write!(f, "while parsing JSON policy `{id}`"),
613 Self::TemplateLink => write!(f, "while parsing a template link"),
614 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"),
615 }
616 }
617}
618
619fn display_json_value(v: &Either<serde_json::Value, Expr>) -> String {
620 match v {
621 Either::Left(json) => display_value(json),
622 Either::Right(e) => e.to_string(),
623 }
624}
625
626fn display_value(v: &serde_json::Value) -> String {
634 match v {
635 serde_json::Value::Array(contents) => {
636 format!("[{}]", contents.iter().map(display_value).join(", "))
637 }
638 serde_json::Value::Object(map) => {
639 let mut v: Vec<_> = map.iter().collect();
640 v.sort_by_key(|p| p.0);
642 let display_kv = |kv: &(&String, &serde_json::Value)| format!("\"{}\":{}", kv.0, kv.1);
643 format!("{{{}}}", v.iter().map(display_kv).join(","))
644 }
645 other => other.to_string(),
646 }
647}
648
649fn display_restricted_expr(expr: BorrowedRestrictedExpr<'_>) -> String {
653 match expr.expr_kind() {
654 ExprKind::Set(elements) => {
655 let restricted_exprs = elements.iter().map(BorrowedRestrictedExpr::new_unchecked); format!(
657 "[{}]",
658 restricted_exprs
659 .map(display_restricted_expr)
660 .sorted_unstable()
661 .join(", ")
662 )
663 }
664 ExprKind::Record(m) => {
665 format!(
666 "{{{}}}",
667 m.iter()
668 .sorted_unstable_by_key(|(k, _)| SmolStr::clone(k))
669 .map(|(k, v)| format!("\"{}\": {}", k.escape_debug(), v))
670 .join(", ")
671 )
672 }
673 _ => format!("{expr}"), }
675}