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::parser::err::ParseErrors;
27use either::Either;
28use itertools::Itertools;
29use miette::Diagnostic;
30use smol_str::SmolStr;
31use thiserror::Error;
32
33#[derive(Debug)]
35pub enum EscapeKind {
36 Entity,
38 Extension,
40}
41
42impl Display for EscapeKind {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 Self::Entity => write!(f, "__entity"),
46 Self::Extension => write!(f, "__extn"),
47 }
48 }
49}
50
51#[derive(Debug, Diagnostic, Error)]
57#[non_exhaustive]
58pub enum JsonDeserializationError {
59 #[error(transparent)]
61 #[diagnostic(transparent)]
62 Serde(#[from] JsonError),
63 #[error(transparent)]
65 #[diagnostic(transparent)]
66 ParseEscape(ParseEscape),
67 #[error(transparent)]
69 #[diagnostic(transparent)]
70 RestrictedExpressionError(#[from] RestrictedExpressionError),
71 #[error(transparent)]
73 #[diagnostic(transparent)]
74 ExpectedLiteralEntityRef(ExpectedLiteralEntityRef),
75 #[error(transparent)]
77 #[diagnostic(transparent)]
78 ExpectedExtnValue(ExpectedExtnValue),
79 #[error(transparent)]
81 #[diagnostic(transparent)]
82 ActionParentIsNotAction(ActionParentIsNotAction),
83 #[error(transparent)]
86 #[diagnostic(transparent)]
87 MissingImpliedConstructor(MissingImpliedConstructor),
88 #[error(transparent)]
90 #[diagnostic(transparent)]
91 DuplicateKey(DuplicateKey),
92 #[error(transparent)]
94 #[diagnostic(transparent)]
95 EntityAttributeEvaluation(#[from] EntityAttrEvaluationError),
96 #[error(transparent)]
102 #[diagnostic(transparent)]
103 EntitySchemaConformance(EntitySchemaConformanceError),
104 #[error(transparent)]
107 #[diagnostic(transparent)]
108 UnexpectedRecordAttr(UnexpectedRecordAttr),
109 #[error(transparent)]
112 #[diagnostic(transparent)]
113 MissingRequiredRecordAttr(MissingRequiredRecordAttr),
114 #[error(transparent)]
121 #[diagnostic(transparent)]
122 TypeMismatch(TypeMismatch),
123 #[cfg(feature = "tolerant-ast")]
125 #[error("Unable to deserialize an AST Error node")]
126 #[diagnostic(help("AST error node indicates that the expression has failed to parse"))]
127 ASTErrorNode,
128 #[error("{0}, the `__expr` escape is no longer supported")]
130 #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
131 ExprTag(Box<JsonDeserializationErrorContext>),
132 #[error("{0}, found a `null`; JSON `null`s are not allowed in Cedar")]
134 Null(Box<JsonDeserializationErrorContext>),
135 #[error(transparent)]
137 #[diagnostic(transparent)]
138 ReservedName(#[from] ReservedNameError),
139 #[deprecated(
143 since = "4.2.0",
144 note = "entity-tags is now stable and fully supported, so this error never occurs"
145 )]
146 #[error("entity tags are not supported in this build; to use entity tags, you must enable the `entity-tags` experimental feature")]
147 UnsupportedEntityTags,
148}
149
150impl JsonDeserializationError {
151 pub(crate) fn parse_escape(
152 kind: EscapeKind,
153 value: impl Into<String>,
154 errs: ParseErrors,
155 ) -> Self {
156 Self::ParseEscape(ParseEscape {
157 kind,
158 value: value.into(),
159 errs,
160 })
161 }
162
163 pub(crate) fn expected_entity_ref(
164 ctx: JsonDeserializationErrorContext,
165 got: Either<serde_json::Value, Expr>,
166 ) -> Self {
167 Self::ExpectedLiteralEntityRef(ExpectedLiteralEntityRef {
168 ctx: Box::new(ctx),
169 got: Box::new(got),
170 })
171 }
172
173 pub(crate) fn expected_extn_value(
174 ctx: JsonDeserializationErrorContext,
175 got: Either<serde_json::Value, Expr>,
176 ) -> Self {
177 Self::ExpectedExtnValue(ExpectedExtnValue {
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 duplicate_key(
198 ctx: JsonDeserializationErrorContext,
199 key: impl Into<SmolStr>,
200 ) -> Self {
201 Self::DuplicateKey(DuplicateKey {
202 ctx: Box::new(ctx),
203 key: key.into(),
204 })
205 }
206
207 pub(crate) fn unexpected_record_attr(
208 ctx: JsonDeserializationErrorContext,
209 record_attr: impl Into<SmolStr>,
210 ) -> Self {
211 Self::UnexpectedRecordAttr(UnexpectedRecordAttr {
212 ctx: Box::new(ctx),
213 record_attr: record_attr.into(),
214 })
215 }
216
217 pub(crate) fn missing_required_record_attr(
218 ctx: JsonDeserializationErrorContext,
219 record_attr: impl Into<SmolStr>,
220 ) -> Self {
221 Self::MissingRequiredRecordAttr(MissingRequiredRecordAttr {
222 ctx: Box::new(ctx),
223 record_attr: record_attr.into(),
224 })
225 }
226
227 pub(crate) fn type_mismatch(
228 ctx: JsonDeserializationErrorContext,
229 err: TypeMismatchError,
230 ) -> Self {
231 Self::TypeMismatch(TypeMismatch {
232 ctx: Box::new(ctx),
233 err,
234 })
235 }
236}
237
238#[derive(Debug, Error, Diagnostic)]
239#[error("{ctx}, {err}")]
240pub struct TypeMismatch {
242 ctx: Box<JsonDeserializationErrorContext>,
246 #[diagnostic(transparent)]
248 err: TypeMismatchError,
249}
250
251#[derive(Debug, Error, Diagnostic)]
252#[error("{}, expected the record to have an attribute `{}`, but it does not", .ctx, .record_attr)]
253pub struct MissingRequiredRecordAttr {
255 ctx: Box<JsonDeserializationErrorContext>,
257 record_attr: SmolStr,
259}
260
261#[derive(Debug, Diagnostic, Error)]
262#[error("{}, record attribute `{}` should not exist according to the schema", .ctx, .record_attr)]
263pub struct UnexpectedRecordAttr {
265 ctx: Box<JsonDeserializationErrorContext>,
267 record_attr: SmolStr,
269}
270
271#[derive(Debug, Error, Diagnostic)]
272#[error("{}, duplicate key `{}` in record", .ctx, .key)]
273pub struct DuplicateKey {
275 ctx: Box<JsonDeserializationErrorContext>,
277 key: SmolStr,
279}
280
281#[derive(Debug, Error, Diagnostic)]
282#[error("{}, missing extension constructor for {}", .ctx, .return_type)]
283#[diagnostic(help("expected a value of type {} because of the schema", .return_type))]
284pub struct MissingImpliedConstructor {
286 ctx: Box<JsonDeserializationErrorContext>,
288 return_type: Box<SchemaType>,
290}
291
292#[derive(Debug, Error, Diagnostic)]
293#[error("action `{}` has a non-action parent `{}`", .uid, .parent)]
294#[diagnostic(help("parents of actions need to have type `Action` themselves, perhaps namespaced"))]
295pub struct ActionParentIsNotAction {
297 uid: EntityUID,
299 parent: EntityUID,
301}
302
303#[derive(Debug, Error, Diagnostic)]
304#[error("failed to parse escape `{kind}`: {value}, errors: {errs}")]
305#[diagnostic(help("{}", match .kind {
306 EscapeKind::Entity => r#"an __entity escape should have a value like `{ "type": "SomeType", "id": "SomeId" }`"#,
307 EscapeKind::Extension => r#"an __extn escape should have a value like `{ "fn": "SomeFn", "arg": "SomeArg" }`"#,
308 }))]
309pub struct ParseEscape {
311 kind: EscapeKind,
313 value: String,
315 #[diagnostic(transparent)]
317 errs: ParseErrors,
318}
319
320#[derive(Debug, Error, Diagnostic)]
321#[error("{}, expected a literal entity reference, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
322#[diagnostic(help(
323 r#"literal entity references can be made with `{{ "type": "SomeType", "id": "SomeId" }}`"#
324))]
325pub struct ExpectedLiteralEntityRef {
327 ctx: Box<JsonDeserializationErrorContext>,
329 got: Box<Either<serde_json::Value, Expr>>,
331}
332
333#[derive(Debug, Error, Diagnostic)]
334#[error("{}, expected an extension value, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
335#[diagnostic(help(r#"extension values can be made with `{{ "fn": "SomeFn", "id": "SomeId" }}`"#))]
336pub struct ExpectedExtnValue {
338 ctx: Box<JsonDeserializationErrorContext>,
340 got: Box<Either<serde_json::Value, Expr>>,
342}
343
344#[derive(Debug, Error, Diagnostic)]
345#[error(transparent)]
346pub struct JsonError(#[from] serde_json::Error);
348
349impl From<serde_json::Error> for JsonDeserializationError {
350 fn from(value: serde_json::Error) -> Self {
351 Self::Serde(JsonError(value))
352 }
353}
354
355impl From<serde_json::Error> for JsonSerializationError {
356 fn from(value: serde_json::Error) -> Self {
357 Self::Serde(JsonError(value))
358 }
359}
360
361#[derive(Debug, Diagnostic, Error)]
363#[non_exhaustive]
364pub enum JsonSerializationError {
365 #[error(transparent)]
367 #[diagnostic(transparent)]
368 Serde(#[from] JsonError),
369 #[error(transparent)]
372 #[diagnostic(transparent)]
373 ExtnCall0Arguments(ExtnCall0Arguments),
374 #[error(transparent)]
377 #[diagnostic(transparent)]
378 ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
379 #[error(transparent)]
382 #[diagnostic(transparent)]
383 ReservedKey(ReservedKey),
384 #[error(transparent)]
388 #[diagnostic(transparent)]
389 UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
390 #[error(transparent)]
393 #[diagnostic(transparent)]
394 Residual(Residual),
395}
396
397impl JsonSerializationError {
398 pub(crate) fn call_0_args(func: Name) -> Self {
399 Self::ExtnCall0Arguments(ExtnCall0Arguments { func })
400 }
401
402 pub(crate) fn call_2_or_more_args(func: Name) -> Self {
403 Self::ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments { func })
404 }
405
406 pub(crate) fn reserved_key(key: impl Into<SmolStr>) -> Self {
407 Self::ReservedKey(ReservedKey { key: key.into() })
408 }
409
410 pub(crate) fn unexpected_restricted_expr_kind(kind: ExprKind) -> Self {
411 Self::UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind { kind })
412 }
413
414 pub(crate) fn residual(residual: Expr) -> Self {
415 Self::Residual(Residual { residual })
416 }
417}
418
419#[derive(Debug, Error, Diagnostic)]
421#[error("unsupported call to `{}` with 0 arguments", .func)]
422#[diagnostic(help(
423 "extension function calls with 0 arguments are not currently supported in our JSON format"
424))]
425pub struct ExtnCall0Arguments {
426 func: Name,
428}
429
430#[derive(Debug, Error, Diagnostic)]
432#[error("unsupported call to `{}` with 2 or more arguments", .func)]
433#[diagnostic(help("extension function calls with 2 or more arguments are not currently supported in our JSON format"))]
434pub struct ExtnCall2OrMoreArguments {
435 func: Name,
437}
438
439#[derive(Debug, Error, Diagnostic)]
441#[error("record uses reserved key `{}`", .key)]
442pub struct ReservedKey {
443 key: SmolStr,
445}
446
447impl ReservedKey {
448 pub fn key(&self) -> impl AsRef<str> + '_ {
450 &self.key
451 }
452}
453
454#[derive(Debug, Error, Diagnostic)]
456#[error("unexpected restricted expression `{:?}`", .kind)]
457pub struct UnexpectedRestrictedExprKind {
458 kind: ExprKind,
460}
461
462#[derive(Debug, Error, Diagnostic)]
464#[error("cannot encode residual as JSON: {}", .residual)]
465pub struct Residual {
466 residual: Expr,
468}
469
470#[derive(Debug, Clone)]
473pub enum JsonDeserializationErrorContext {
474 EntityAttribute {
476 uid: EntityUID,
478 attr: SmolStr,
480 },
481 EntityTag {
483 uid: EntityUID,
485 tag: SmolStr,
487 },
488 EntityParents {
490 uid: EntityUID,
492 },
493 EntityUid,
495 Context,
497 Policy {
499 id: PolicyID,
501 },
502 TemplateLink,
504 Unknown,
506}
507
508#[derive(Debug, Diagnostic, Error)]
510#[error("type mismatch: value was expected to have type {expected}, but it {mismatch_reason}: `{}`",
511 display_restricted_expr(.actual_val.as_borrowed()),
512)]
513pub struct TypeMismatchError {
514 expected: Box<SchemaType>,
516 mismatch_reason: TypeMismatchReason,
518 actual_val: Box<RestrictedExpr>,
520}
521
522#[derive(Debug, Error)]
523enum TypeMismatchReason {
524 #[error("actually has type {0}")]
527 UnexpectedType(Type),
528 #[error("contains an unexpected attribute `{0}`")]
531 UnexpectedAttr(SmolStr),
532 #[error("is missing the required attribute `{0}`")]
535 MissingRequiredAtr(SmolStr),
536 #[error("does not")]
538 None,
539}
540
541impl TypeMismatchError {
542 pub(crate) fn type_mismatch(
543 expected: SchemaType,
544 actual_ty: Option<Type>,
545 actual_val: RestrictedExpr,
546 ) -> Self {
547 Self {
548 expected: Box::new(expected),
549 mismatch_reason: match actual_ty {
550 Some(ty) => TypeMismatchReason::UnexpectedType(ty),
551 None => TypeMismatchReason::None,
552 },
553 actual_val: Box::new(actual_val),
554 }
555 }
556
557 pub(crate) fn unexpected_attr(
558 expected: SchemaType,
559 unexpected_attr: SmolStr,
560 actual_val: RestrictedExpr,
561 ) -> Self {
562 Self {
563 expected: Box::new(expected),
564 mismatch_reason: TypeMismatchReason::UnexpectedAttr(unexpected_attr),
565 actual_val: Box::new(actual_val),
566 }
567 }
568
569 pub(crate) fn missing_required_attr(
570 expected: SchemaType,
571 missing_attr: SmolStr,
572 actual_val: RestrictedExpr,
573 ) -> Self {
574 Self {
575 expected: Box::new(expected),
576 mismatch_reason: TypeMismatchReason::MissingRequiredAtr(missing_attr),
577 actual_val: Box::new(actual_val),
578 }
579 }
580}
581
582impl std::fmt::Display for JsonDeserializationErrorContext {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 match self {
585 Self::EntityAttribute { uid, attr } => write!(f, "in attribute `{attr}` on `{uid}`"),
586 Self::EntityTag { uid, tag } => write!(f, "in tag `{tag}` on `{uid}`"),
587 Self::EntityParents { uid } => write!(f, "in parents field of `{uid}`"),
588 Self::EntityUid => write!(f, "in uid field of <unknown entity>"),
589 Self::Context => write!(f, "while parsing context"),
590 Self::Policy { id } => write!(f, "while parsing JSON policy `{id}`"),
591 Self::TemplateLink => write!(f, "while parsing a template link"),
592 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"),
593 }
594 }
595}
596
597fn display_json_value(v: &Either<serde_json::Value, Expr>) -> String {
598 match v {
599 Either::Left(json) => display_value(json),
600 Either::Right(e) => e.to_string(),
601 }
602}
603
604fn display_value(v: &serde_json::Value) -> String {
612 match v {
613 serde_json::Value::Array(contents) => {
614 format!("[{}]", contents.iter().map(display_value).join(", "))
615 }
616 serde_json::Value::Object(map) => {
617 let mut v: Vec<_> = map.iter().collect();
618 v.sort_by_key(|p| p.0);
620 let display_kv = |kv: &(&String, &serde_json::Value)| format!("\"{}\":{}", kv.0, kv.1);
621 format!("{{{}}}", v.iter().map(display_kv).join(","))
622 }
623 other => other.to_string(),
624 }
625}
626
627fn display_restricted_expr(expr: BorrowedRestrictedExpr<'_>) -> String {
631 match expr.expr_kind() {
632 ExprKind::Set(elements) => {
633 let restricted_exprs = elements.iter().map(BorrowedRestrictedExpr::new_unchecked); format!(
635 "[{}]",
636 restricted_exprs
637 .map(display_restricted_expr)
638 .sorted_unstable()
639 .join(", ")
640 )
641 }
642 ExprKind::Record(m) => {
643 format!(
644 "{{{}}}",
645 m.iter()
646 .sorted_unstable_by_key(|(k, _)| SmolStr::clone(k))
647 .map(|(k, v)| format!("\"{}\": {}", k.escape_debug(), v))
648 .join(", ")
649 )
650 }
651 _ => format!("{expr}"), }
653}