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 #[error("{0}, the `__expr` escape is no longer supported")]
125 #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
126 ExprTag(Box<JsonDeserializationErrorContext>),
127 #[error("{0}, found a `null`; JSON `null`s are not allowed in Cedar")]
129 Null(Box<JsonDeserializationErrorContext>),
130 #[error(transparent)]
132 #[diagnostic(transparent)]
133 ReservedName(#[from] ReservedNameError),
134 #[deprecated(
138 since = "4.2.0",
139 note = "entity-tags is now stable and fully supported, so this error never occurs"
140 )]
141 #[error("entity tags are not supported in this build; to use entity tags, you must enable the `entity-tags` experimental feature")]
142 UnsupportedEntityTags,
143}
144
145impl JsonDeserializationError {
146 pub(crate) fn parse_escape(
147 kind: EscapeKind,
148 value: impl Into<String>,
149 errs: ParseErrors,
150 ) -> Self {
151 Self::ParseEscape(ParseEscape {
152 kind,
153 value: value.into(),
154 errs,
155 })
156 }
157
158 pub(crate) fn expected_entity_ref(
159 ctx: JsonDeserializationErrorContext,
160 got: Either<serde_json::Value, Expr>,
161 ) -> Self {
162 Self::ExpectedLiteralEntityRef(ExpectedLiteralEntityRef {
163 ctx: Box::new(ctx),
164 got: Box::new(got),
165 })
166 }
167
168 pub(crate) fn expected_extn_value(
169 ctx: JsonDeserializationErrorContext,
170 got: Either<serde_json::Value, Expr>,
171 ) -> Self {
172 Self::ExpectedExtnValue(ExpectedExtnValue {
173 ctx: Box::new(ctx),
174 got: Box::new(got),
175 })
176 }
177
178 pub(crate) fn action_parent_is_not_action(uid: EntityUID, parent: EntityUID) -> Self {
179 Self::ActionParentIsNotAction(ActionParentIsNotAction { uid, parent })
180 }
181
182 pub(crate) fn missing_implied_constructor(
183 ctx: JsonDeserializationErrorContext,
184 return_type: SchemaType,
185 ) -> Self {
186 Self::MissingImpliedConstructor(MissingImpliedConstructor {
187 ctx: Box::new(ctx),
188 return_type: Box::new(return_type),
189 })
190 }
191
192 pub(crate) fn duplicate_key(
193 ctx: JsonDeserializationErrorContext,
194 key: impl Into<SmolStr>,
195 ) -> Self {
196 Self::DuplicateKey(DuplicateKey {
197 ctx: Box::new(ctx),
198 key: key.into(),
199 })
200 }
201
202 pub(crate) fn unexpected_record_attr(
203 ctx: JsonDeserializationErrorContext,
204 record_attr: impl Into<SmolStr>,
205 ) -> Self {
206 Self::UnexpectedRecordAttr(UnexpectedRecordAttr {
207 ctx: Box::new(ctx),
208 record_attr: record_attr.into(),
209 })
210 }
211
212 pub(crate) fn missing_required_record_attr(
213 ctx: JsonDeserializationErrorContext,
214 record_attr: impl Into<SmolStr>,
215 ) -> Self {
216 Self::MissingRequiredRecordAttr(MissingRequiredRecordAttr {
217 ctx: Box::new(ctx),
218 record_attr: record_attr.into(),
219 })
220 }
221
222 pub(crate) fn type_mismatch(
223 ctx: JsonDeserializationErrorContext,
224 err: TypeMismatchError,
225 ) -> Self {
226 Self::TypeMismatch(TypeMismatch {
227 ctx: Box::new(ctx),
228 err,
229 })
230 }
231}
232
233#[derive(Debug, Error, Diagnostic)]
234#[error("{ctx}, {err}")]
235pub struct TypeMismatch {
237 ctx: Box<JsonDeserializationErrorContext>,
241 #[diagnostic(transparent)]
243 err: TypeMismatchError,
244}
245
246#[derive(Debug, Error, Diagnostic)]
247#[error("{}, expected the record to have an attribute `{}`, but it does not", .ctx, .record_attr)]
248pub struct MissingRequiredRecordAttr {
250 ctx: Box<JsonDeserializationErrorContext>,
252 record_attr: SmolStr,
254}
255
256#[derive(Debug, Diagnostic, Error)]
257#[error("{}, record attribute `{}` should not exist according to the schema", .ctx, .record_attr)]
258pub struct UnexpectedRecordAttr {
260 ctx: Box<JsonDeserializationErrorContext>,
262 record_attr: SmolStr,
264}
265
266#[derive(Debug, Error, Diagnostic)]
267#[error("{}, duplicate key `{}` in record", .ctx, .key)]
268pub struct DuplicateKey {
270 ctx: Box<JsonDeserializationErrorContext>,
272 key: SmolStr,
274}
275
276#[derive(Debug, Error, Diagnostic)]
277#[error("{}, missing extension constructor for {}", .ctx, .return_type)]
278#[diagnostic(help("expected a value of type {} because of the schema", .return_type))]
279pub struct MissingImpliedConstructor {
281 ctx: Box<JsonDeserializationErrorContext>,
283 return_type: Box<SchemaType>,
285}
286
287#[derive(Debug, Error, Diagnostic)]
288#[error("action `{}` has a non-action parent `{}`", .uid, .parent)]
289#[diagnostic(help("parents of actions need to have type `Action` themselves, perhaps namespaced"))]
290pub struct ActionParentIsNotAction {
292 uid: EntityUID,
294 parent: EntityUID,
296}
297
298#[derive(Debug, Error, Diagnostic)]
299#[error("failed to parse escape `{kind}`: {value}, errors: {errs}")]
300#[diagnostic(help("{}", match .kind {
301 EscapeKind::Entity => r#"an __entity escape should have a value like `{ "type": "SomeType", "id": "SomeId" }`"#,
302 EscapeKind::Extension => r#"an __extn escape should have a value like `{ "fn": "SomeFn", "arg": "SomeArg" }`"#,
303 }))]
304pub struct ParseEscape {
306 kind: EscapeKind,
308 value: String,
310 #[diagnostic(transparent)]
312 errs: ParseErrors,
313}
314
315#[derive(Debug, Error, Diagnostic)]
316#[error("{}, expected a literal entity reference, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
317#[diagnostic(help(
318 r#"literal entity references can be made with `{{ "type": "SomeType", "id": "SomeId" }}`"#
319))]
320pub struct ExpectedLiteralEntityRef {
322 ctx: Box<JsonDeserializationErrorContext>,
324 got: Box<Either<serde_json::Value, Expr>>,
326}
327
328#[derive(Debug, Error, Diagnostic)]
329#[error("{}, expected an extension value, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
330#[diagnostic(help(r#"extension values can be made with `{{ "fn": "SomeFn", "id": "SomeId" }}`"#))]
331pub struct ExpectedExtnValue {
333 ctx: Box<JsonDeserializationErrorContext>,
335 got: Box<Either<serde_json::Value, Expr>>,
337}
338
339#[derive(Debug, Error, Diagnostic)]
340#[error(transparent)]
341pub struct JsonError(#[from] serde_json::Error);
343
344impl From<serde_json::Error> for JsonDeserializationError {
345 fn from(value: serde_json::Error) -> Self {
346 Self::Serde(JsonError(value))
347 }
348}
349
350impl From<serde_json::Error> for JsonSerializationError {
351 fn from(value: serde_json::Error) -> Self {
352 Self::Serde(JsonError(value))
353 }
354}
355
356#[derive(Debug, Diagnostic, Error)]
358#[non_exhaustive]
359pub enum JsonSerializationError {
360 #[error(transparent)]
362 #[diagnostic(transparent)]
363 Serde(#[from] JsonError),
364 #[error(transparent)]
367 #[diagnostic(transparent)]
368 ExtnCall0Arguments(ExtnCall0Arguments),
369 #[error(transparent)]
372 #[diagnostic(transparent)]
373 ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
374 #[error(transparent)]
377 #[diagnostic(transparent)]
378 ReservedKey(ReservedKey),
379 #[error(transparent)]
383 #[diagnostic(transparent)]
384 UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
385 #[error(transparent)]
388 #[diagnostic(transparent)]
389 Residual(Residual),
390}
391
392impl JsonSerializationError {
393 pub(crate) fn call_0_args(func: Name) -> Self {
394 Self::ExtnCall0Arguments(ExtnCall0Arguments { func })
395 }
396
397 pub(crate) fn call_2_or_more_args(func: Name) -> Self {
398 Self::ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments { func })
399 }
400
401 pub(crate) fn reserved_key(key: impl Into<SmolStr>) -> Self {
402 Self::ReservedKey(ReservedKey { key: key.into() })
403 }
404
405 pub(crate) fn unexpected_restricted_expr_kind(kind: ExprKind) -> Self {
406 Self::UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind { kind })
407 }
408
409 pub(crate) fn residual(residual: Expr) -> Self {
410 Self::Residual(Residual { residual })
411 }
412}
413
414#[derive(Debug, Error, Diagnostic)]
416#[error("unsupported call to `{}` with 0 arguments", .func)]
417#[diagnostic(help(
418 "extension function calls with 0 arguments are not currently supported in our JSON format"
419))]
420pub struct ExtnCall0Arguments {
421 func: Name,
423}
424
425#[derive(Debug, Error, Diagnostic)]
427#[error("unsupported call to `{}` with 2 or more arguments", .func)]
428#[diagnostic(help("extension function calls with 2 or more arguments are not currently supported in our JSON format"))]
429pub struct ExtnCall2OrMoreArguments {
430 func: Name,
432}
433
434#[derive(Debug, Error, Diagnostic)]
436#[error("record uses reserved key `{}`", .key)]
437pub struct ReservedKey {
438 key: SmolStr,
440}
441
442impl ReservedKey {
443 pub fn key(&self) -> impl AsRef<str> + '_ {
445 &self.key
446 }
447}
448
449#[derive(Debug, Error, Diagnostic)]
451#[error("unexpected restricted expression `{:?}`", .kind)]
452pub struct UnexpectedRestrictedExprKind {
453 kind: ExprKind,
455}
456
457#[derive(Debug, Error, Diagnostic)]
459#[error("cannot encode residual as JSON: {}", .residual)]
460pub struct Residual {
461 residual: Expr,
463}
464
465#[derive(Debug, Clone)]
468pub enum JsonDeserializationErrorContext {
469 EntityAttribute {
471 uid: EntityUID,
473 attr: SmolStr,
475 },
476 EntityTag {
478 uid: EntityUID,
480 tag: SmolStr,
482 },
483 EntityParents {
485 uid: EntityUID,
487 },
488 EntityUid,
490 Context,
492 Policy {
494 id: PolicyID,
496 },
497 TemplateLink,
499 Unknown,
501}
502
503#[derive(Debug, Diagnostic, Error)]
505#[error("type mismatch: value was expected to have type {expected}, but it {mismatch_reason}: `{}`",
506 display_restricted_expr(.actual_val.as_borrowed()),
507)]
508pub struct TypeMismatchError {
509 expected: Box<SchemaType>,
511 mismatch_reason: TypeMismatchReason,
513 actual_val: Box<RestrictedExpr>,
515}
516
517#[derive(Debug, Error)]
518enum TypeMismatchReason {
519 #[error("actually has type {0}")]
522 UnexpectedType(Type),
523 #[error("contains an unexpected attribute `{0}`")]
526 UnexpectedAttr(SmolStr),
527 #[error("is missing the required attribute `{0}`")]
530 MissingRequiredAtr(SmolStr),
531 #[error("does not")]
533 None,
534}
535
536impl TypeMismatchError {
537 pub(crate) fn type_mismatch(
538 expected: SchemaType,
539 actual_ty: Option<Type>,
540 actual_val: RestrictedExpr,
541 ) -> Self {
542 Self {
543 expected: Box::new(expected),
544 mismatch_reason: match actual_ty {
545 Some(ty) => TypeMismatchReason::UnexpectedType(ty),
546 None => TypeMismatchReason::None,
547 },
548 actual_val: Box::new(actual_val),
549 }
550 }
551
552 pub(crate) fn unexpected_attr(
553 expected: SchemaType,
554 unexpected_attr: SmolStr,
555 actual_val: RestrictedExpr,
556 ) -> Self {
557 Self {
558 expected: Box::new(expected),
559 mismatch_reason: TypeMismatchReason::UnexpectedAttr(unexpected_attr),
560 actual_val: Box::new(actual_val),
561 }
562 }
563
564 pub(crate) fn missing_required_attr(
565 expected: SchemaType,
566 missing_attr: SmolStr,
567 actual_val: RestrictedExpr,
568 ) -> Self {
569 Self {
570 expected: Box::new(expected),
571 mismatch_reason: TypeMismatchReason::MissingRequiredAtr(missing_attr),
572 actual_val: Box::new(actual_val),
573 }
574 }
575}
576
577impl std::fmt::Display for JsonDeserializationErrorContext {
578 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
579 match self {
580 Self::EntityAttribute { uid, attr } => write!(f, "in attribute `{attr}` on `{uid}`"),
581 Self::EntityTag { uid, tag } => write!(f, "in tag `{tag}` on `{uid}`"),
582 Self::EntityParents { uid } => write!(f, "in parents field of `{uid}`"),
583 Self::EntityUid => write!(f, "in uid field of <unknown entity>"),
584 Self::Context => write!(f, "while parsing context"),
585 Self::Policy { id } => write!(f, "while parsing JSON policy `{id}`"),
586 Self::TemplateLink => write!(f, "while parsing a template link"),
587 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"),
588 }
589 }
590}
591
592fn display_json_value(v: &Either<serde_json::Value, Expr>) -> String {
593 match v {
594 Either::Left(json) => display_value(json),
595 Either::Right(e) => e.to_string(),
596 }
597}
598
599fn display_value(v: &serde_json::Value) -> String {
607 match v {
608 serde_json::Value::Array(contents) => {
609 format!("[{}]", contents.iter().map(display_value).join(", "))
610 }
611 serde_json::Value::Object(map) => {
612 let mut v: Vec<_> = map.iter().collect();
613 v.sort_by_key(|p| p.0);
615 let display_kv = |kv: &(&String, &serde_json::Value)| format!("\"{}\":{}", kv.0, kv.1);
616 format!("{{{}}}", v.iter().map(display_kv).join(","))
617 }
618 other => other.to_string(),
619 }
620}
621
622fn display_restricted_expr(expr: BorrowedRestrictedExpr<'_>) -> String {
626 match expr.expr_kind() {
627 ExprKind::Set(elements) => {
628 let restricted_exprs = elements.iter().map(BorrowedRestrictedExpr::new_unchecked); format!(
630 "[{}]",
631 restricted_exprs
632 .map(display_restricted_expr)
633 .sorted_unstable()
634 .join(", ")
635 )
636 }
637 ExprKind::Record(m) => {
638 format!(
639 "{{{}}}",
640 m.iter()
641 .sorted_unstable_by_key(|(k, _)| SmolStr::clone(k))
642 .map(|(k, v)| format!("\"{}\": {}", k.escape_debug(), v))
643 .join(", ")
644 )
645 }
646 _ => format!("{expr}"), }
648}