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)]
371#[error(transparent)]
372pub struct JsonError(#[from] serde_json::Error);
374
375impl From<serde_json::Error> for JsonDeserializationError {
376 fn from(value: serde_json::Error) -> Self {
377 Self::Serde(JsonError(value))
378 }
379}
380
381impl From<serde_json::Error> for JsonSerializationError {
382 fn from(value: serde_json::Error) -> Self {
383 Self::Serde(JsonError(value))
384 }
385}
386
387#[derive(Debug, Diagnostic, Error)]
389#[non_exhaustive]
390pub enum JsonSerializationError {
391 #[error(transparent)]
393 #[diagnostic(transparent)]
394 Serde(#[from] JsonError),
395 #[error(transparent)]
398 #[diagnostic(transparent)]
399 ExtnCall0Arguments(ExtnCall0Arguments),
400 #[error(transparent)]
403 #[diagnostic(transparent)]
404 ReservedKey(ReservedKey),
405 #[error(transparent)]
409 #[diagnostic(transparent)]
410 UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
411 #[error(transparent)]
414 #[diagnostic(transparent)]
415 Residual(Residual),
416 #[error(transparent)]
420 #[diagnostic(transparent)]
421 ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
422}
423
424impl JsonSerializationError {
425 pub(crate) fn call_0_args(func: Name) -> Self {
426 Self::ExtnCall0Arguments(ExtnCall0Arguments { func })
427 }
428
429 pub(crate) fn reserved_key(key: impl Into<SmolStr>) -> Self {
430 Self::ReservedKey(ReservedKey { key: key.into() })
431 }
432
433 pub(crate) fn unexpected_restricted_expr_kind(kind: ExprKind) -> Self {
434 Self::UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind { kind })
435 }
436
437 pub(crate) fn residual(residual: Expr) -> Self {
438 Self::Residual(Residual { residual })
439 }
440}
441
442#[derive(Debug, Error, Diagnostic)]
444#[error("unsupported call to `{}` with 0 arguments", .func)]
445#[diagnostic(help(
446 "extension function calls with 0 arguments are not currently supported in our JSON format"
447))]
448pub struct ExtnCall0Arguments {
449 func: Name,
451}
452
453#[derive(Debug, Error, Diagnostic)]
456#[error("unsupported call to `{}` with 2 or more arguments", .func)]
457#[diagnostic(help("extension function calls with 2 or more arguments are not currently supported in our JSON format"))]
458pub struct ExtnCall2OrMoreArguments {
459 func: Name,
461}
462
463#[derive(Debug, Error, Diagnostic)]
465#[error("record uses reserved key `{}`", .key)]
466pub struct ReservedKey {
467 key: SmolStr,
469}
470
471impl ReservedKey {
472 pub fn key(&self) -> impl AsRef<str> + '_ {
474 &self.key
475 }
476}
477
478#[derive(Debug, Error, Diagnostic)]
480#[error("unexpected restricted expression `{:?}`", .kind)]
481pub struct UnexpectedRestrictedExprKind {
482 kind: ExprKind,
484}
485
486#[derive(Debug, Error, Diagnostic)]
488#[error("cannot encode residual as JSON: {}", .residual)]
489pub struct Residual {
490 residual: Expr,
492}
493
494#[derive(Debug, Clone)]
497pub enum JsonDeserializationErrorContext {
498 EntityAttribute {
500 uid: EntityUID,
502 attr: SmolStr,
504 },
505 EntityTag {
507 uid: EntityUID,
509 tag: SmolStr,
511 },
512 EntityParents {
514 uid: EntityUID,
516 },
517 EntityUid,
519 Context,
521 Policy {
523 id: PolicyID,
525 },
526 TemplateLink,
528 Unknown,
530}
531
532#[derive(Debug, Diagnostic, Error)]
534#[error("type mismatch: value was expected to have type {expected}, but it {mismatch_reason}: `{}`",
535 display_restricted_expr(.actual_val.as_borrowed()),
536)]
537pub struct TypeMismatchError {
538 expected: Box<SchemaType>,
540 mismatch_reason: TypeMismatchReason,
542 actual_val: Box<RestrictedExpr>,
544}
545
546#[derive(Debug, Error)]
547enum TypeMismatchReason {
548 #[error("actually has type {0}")]
551 UnexpectedType(Type),
552 #[error("contains an unexpected attribute `{0}`")]
555 UnexpectedAttr(SmolStr),
556 #[error("is missing the required attribute `{0}`")]
559 MissingRequiredAtr(SmolStr),
560 #[error("does not")]
562 None,
563}
564
565impl TypeMismatchError {
566 pub(crate) fn type_mismatch(
567 expected: SchemaType,
568 actual_ty: Option<Type>,
569 actual_val: RestrictedExpr,
570 ) -> Self {
571 Self {
572 expected: Box::new(expected),
573 mismatch_reason: match actual_ty {
574 Some(ty) => TypeMismatchReason::UnexpectedType(ty),
575 None => TypeMismatchReason::None,
576 },
577 actual_val: Box::new(actual_val),
578 }
579 }
580
581 pub(crate) fn unexpected_attr(
582 expected: SchemaType,
583 unexpected_attr: SmolStr,
584 actual_val: RestrictedExpr,
585 ) -> Self {
586 Self {
587 expected: Box::new(expected),
588 mismatch_reason: TypeMismatchReason::UnexpectedAttr(unexpected_attr),
589 actual_val: Box::new(actual_val),
590 }
591 }
592
593 pub(crate) fn missing_required_attr(
594 expected: SchemaType,
595 missing_attr: SmolStr,
596 actual_val: RestrictedExpr,
597 ) -> Self {
598 Self {
599 expected: Box::new(expected),
600 mismatch_reason: TypeMismatchReason::MissingRequiredAtr(missing_attr),
601 actual_val: Box::new(actual_val),
602 }
603 }
604}
605
606impl std::fmt::Display for JsonDeserializationErrorContext {
607 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608 match self {
609 Self::EntityAttribute { uid, attr } => write!(f, "in attribute `{attr}` on `{uid}`"),
610 Self::EntityTag { uid, tag } => write!(f, "in tag `{tag}` on `{uid}`"),
611 Self::EntityParents { uid } => write!(f, "in parents field of `{uid}`"),
612 Self::EntityUid => write!(f, "in uid field of <unknown entity>"),
613 Self::Context => write!(f, "while parsing context"),
614 Self::Policy { id } => write!(f, "while parsing JSON policy `{id}`"),
615 Self::TemplateLink => write!(f, "while parsing a template link"),
616 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"),
617 }
618 }
619}
620
621fn display_json_value(v: &Either<serde_json::Value, Expr>) -> String {
622 match v {
623 Either::Left(json) => display_value(json),
624 Either::Right(e) => e.to_string(),
625 }
626}
627
628fn display_value(v: &serde_json::Value) -> String {
636 match v {
637 serde_json::Value::Array(contents) => {
638 format!("[{}]", contents.iter().map(display_value).join(", "))
639 }
640 serde_json::Value::Object(map) => {
641 let mut v: Vec<_> = map.iter().collect();
642 v.sort_by_key(|p| p.0);
644 let display_kv = |kv: &(&String, &serde_json::Value)| format!("\"{}\":{}", kv.0, kv.1);
645 format!("{{{}}}", v.iter().map(display_kv).join(","))
646 }
647 other => other.to_string(),
648 }
649}
650
651fn display_restricted_expr(expr: BorrowedRestrictedExpr<'_>) -> String {
655 match expr.expr_kind() {
656 ExprKind::Set(elements) => {
657 let restricted_exprs = elements.iter().map(BorrowedRestrictedExpr::new_unchecked); format!(
659 "[{}]",
660 restricted_exprs
661 .map(display_restricted_expr)
662 .sorted_unstable()
663 .join(", ")
664 )
665 }
666 ExprKind::Record(m) => {
667 format!(
668 "{{{}}}",
669 m.iter()
670 .sorted_unstable_by_key(|(k, _)| SmolStr::clone(k))
671 .map(|(k, v)| format!("\"{}\": {}", k.escape_debug(), v))
672 .join(", ")
673 )
674 }
675 _ => format!("{expr}"), }
677}