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)]
53#[non_exhaustive]
54pub enum JsonDeserializationError {
55 #[error(transparent)]
57 #[diagnostic(transparent)]
58 Serde(#[from] JsonError),
59 #[error(transparent)]
61 #[diagnostic(transparent)]
62 ParseEscape(ParseEscape),
63 #[error(transparent)]
65 #[diagnostic(transparent)]
66 RestrictedExpressionError(#[from] RestrictedExpressionError),
67 #[error(transparent)]
69 #[diagnostic(transparent)]
70 ExpectedLiteralEntityRef(ExpectedLiteralEntityRef),
71 #[error(transparent)]
73 #[diagnostic(transparent)]
74 ExpectedExtnValue(ExpectedExtnValue),
75 #[error(transparent)]
77 #[diagnostic(transparent)]
78 ActionParentIsNotAction(ActionParentIsNotAction),
79 #[error(transparent)]
82 #[diagnostic(transparent)]
83 MissingImpliedConstructor(MissingImpliedConstructor),
84 #[error(transparent)]
86 #[diagnostic(transparent)]
87 DuplicateKey(DuplicateKey),
88 #[error(transparent)]
90 #[diagnostic(transparent)]
91 EntityAttributeEvaluation(#[from] EntityAttrEvaluationError),
92 #[error(transparent)]
98 #[diagnostic(transparent)]
99 EntitySchemaConformance(EntitySchemaConformanceError),
100 #[error(transparent)]
103 #[diagnostic(transparent)]
104 UnexpectedRecordAttr(UnexpectedRecordAttr),
105 #[error(transparent)]
108 #[diagnostic(transparent)]
109 MissingRequiredRecordAttr(MissingRequiredRecordAttr),
110 #[error(transparent)]
117 #[diagnostic(transparent)]
118 TypeMismatch(TypeMismatch),
119 #[error("{0}, the `__expr` escape is no longer supported")]
121 #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
122 ExprTag(Box<JsonDeserializationErrorContext>),
123 #[error("{0}, found a `null`; JSON `null`s are not allowed in Cedar")]
125 Null(Box<JsonDeserializationErrorContext>),
126 #[error(transparent)]
128 #[diagnostic(transparent)]
129 ReservedName(#[from] ReservedNameError),
130}
131
132impl JsonDeserializationError {
133 pub(crate) fn parse_escape(
134 kind: EscapeKind,
135 value: impl Into<String>,
136 errs: ParseErrors,
137 ) -> Self {
138 Self::ParseEscape(ParseEscape {
139 kind,
140 value: value.into(),
141 errs,
142 })
143 }
144
145 pub(crate) fn expected_entity_ref(
146 ctx: JsonDeserializationErrorContext,
147 got: Either<serde_json::Value, Expr>,
148 ) -> Self {
149 Self::ExpectedLiteralEntityRef(ExpectedLiteralEntityRef {
150 ctx: Box::new(ctx),
151 got: Box::new(got),
152 })
153 }
154
155 pub(crate) fn expected_extn_value(
156 ctx: JsonDeserializationErrorContext,
157 got: Either<serde_json::Value, Expr>,
158 ) -> Self {
159 Self::ExpectedExtnValue(ExpectedExtnValue {
160 ctx: Box::new(ctx),
161 got: Box::new(got),
162 })
163 }
164
165 pub(crate) fn action_parent_is_not_action(uid: EntityUID, parent: EntityUID) -> Self {
166 Self::ActionParentIsNotAction(ActionParentIsNotAction { uid, parent })
167 }
168
169 pub(crate) fn missing_implied_constructor(
170 ctx: JsonDeserializationErrorContext,
171 return_type: SchemaType,
172 ) -> Self {
173 Self::MissingImpliedConstructor(MissingImpliedConstructor {
174 ctx: Box::new(ctx),
175 return_type: Box::new(return_type),
176 })
177 }
178
179 pub(crate) fn duplicate_key(
180 ctx: JsonDeserializationErrorContext,
181 key: impl Into<SmolStr>,
182 ) -> Self {
183 Self::DuplicateKey(DuplicateKey {
184 ctx: Box::new(ctx),
185 key: key.into(),
186 })
187 }
188
189 pub(crate) fn unexpected_record_attr(
190 ctx: JsonDeserializationErrorContext,
191 record_attr: impl Into<SmolStr>,
192 ) -> Self {
193 Self::UnexpectedRecordAttr(UnexpectedRecordAttr {
194 ctx: Box::new(ctx),
195 record_attr: record_attr.into(),
196 })
197 }
198
199 pub(crate) fn missing_required_record_attr(
200 ctx: JsonDeserializationErrorContext,
201 record_attr: impl Into<SmolStr>,
202 ) -> Self {
203 Self::MissingRequiredRecordAttr(MissingRequiredRecordAttr {
204 ctx: Box::new(ctx),
205 record_attr: record_attr.into(),
206 })
207 }
208
209 pub(crate) fn type_mismatch(
210 ctx: JsonDeserializationErrorContext,
211 err: TypeMismatchError,
212 ) -> Self {
213 Self::TypeMismatch(TypeMismatch {
214 ctx: Box::new(ctx),
215 err,
216 })
217 }
218}
219
220#[derive(Debug, Error, Diagnostic)]
221#[error("{ctx}, {err}")]
222pub struct TypeMismatch {
224 ctx: Box<JsonDeserializationErrorContext>,
228 #[diagnostic(transparent)]
230 err: TypeMismatchError,
231}
232
233#[derive(Debug, Error, Diagnostic)]
234#[error("{}, expected the record to have an attribute `{}`, but it does not", .ctx, .record_attr)]
235pub struct MissingRequiredRecordAttr {
237 ctx: Box<JsonDeserializationErrorContext>,
239 record_attr: SmolStr,
241}
242
243#[derive(Debug, Diagnostic, Error)]
244#[error("{}, record attribute `{}` should not exist according to the schema", .ctx, .record_attr)]
245pub struct UnexpectedRecordAttr {
247 ctx: Box<JsonDeserializationErrorContext>,
249 record_attr: SmolStr,
251}
252
253#[derive(Debug, Error, Diagnostic)]
254#[error("{}, duplicate key `{}` in record", .ctx, .key)]
255pub struct DuplicateKey {
257 ctx: Box<JsonDeserializationErrorContext>,
259 key: SmolStr,
261}
262
263#[derive(Debug, Error, Diagnostic)]
264#[error("{}, missing extension constructor for {}", .ctx, .return_type)]
265#[diagnostic(help("expected a value of type {} because of the schema", .return_type))]
266pub struct MissingImpliedConstructor {
268 ctx: Box<JsonDeserializationErrorContext>,
270 return_type: Box<SchemaType>,
272}
273
274#[derive(Debug, Error, Diagnostic)]
275#[error("action `{}` has a non-action parent `{}`", .uid, .parent)]
276#[diagnostic(help("parents of actions need to have type `Action` themselves, perhaps namespaced"))]
277pub struct ActionParentIsNotAction {
279 uid: EntityUID,
281 parent: EntityUID,
283}
284
285#[derive(Debug, Error, Diagnostic)]
286#[error("failed to parse escape `{kind}`: {value}, errors: {errs}")]
287#[diagnostic(help("{}", match .kind {
288 EscapeKind::Entity => r#"an __entity escape should have a value like `{ "type": "SomeType", "id": "SomeId" }`"#,
289 EscapeKind::Extension => r#"an __extn escape should have a value like `{ "fn": "SomeFn", "arg": "SomeArg" }`"#,
290 }))]
291pub struct ParseEscape {
293 kind: EscapeKind,
295 value: String,
297 #[diagnostic(transparent)]
299 errs: ParseErrors,
300}
301
302#[derive(Debug, Error, Diagnostic)]
303#[error("{}, expected a literal entity reference, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
304#[diagnostic(help(
305 r#"literal entity references can be made with `{{ "type": "SomeType", "id": "SomeId" }}`"#
306))]
307pub struct ExpectedLiteralEntityRef {
309 ctx: Box<JsonDeserializationErrorContext>,
311 got: Box<Either<serde_json::Value, Expr>>,
313}
314
315#[derive(Debug, Error, Diagnostic)]
316#[error("{}, expected an extension value, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
317#[diagnostic(help(r#"extension values can be made with `{{ "fn": "SomeFn", "id": "SomeId" }}`"#))]
318pub struct ExpectedExtnValue {
320 ctx: Box<JsonDeserializationErrorContext>,
322 got: Box<Either<serde_json::Value, Expr>>,
324}
325
326#[derive(Debug, Error, Diagnostic)]
327#[error(transparent)]
328pub struct JsonError(#[from] serde_json::Error);
330
331impl From<serde_json::Error> for JsonDeserializationError {
332 fn from(value: serde_json::Error) -> Self {
333 Self::Serde(JsonError(value))
334 }
335}
336
337impl From<serde_json::Error> for JsonSerializationError {
338 fn from(value: serde_json::Error) -> Self {
339 Self::Serde(JsonError(value))
340 }
341}
342
343#[derive(Debug, Diagnostic, Error)]
345#[non_exhaustive]
346pub enum JsonSerializationError {
347 #[error(transparent)]
349 #[diagnostic(transparent)]
350 Serde(#[from] JsonError),
351 #[error(transparent)]
354 #[diagnostic(transparent)]
355 ExtnCall0Arguments(ExtnCall0Arguments),
356 #[error(transparent)]
359 #[diagnostic(transparent)]
360 ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
361 #[error(transparent)]
364 #[diagnostic(transparent)]
365 ReservedKey(ReservedKey),
366 #[error(transparent)]
370 #[diagnostic(transparent)]
371 UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
372 #[error(transparent)]
375 #[diagnostic(transparent)]
376 Residual(Residual),
377}
378
379impl JsonSerializationError {
380 pub(crate) fn call_0_args(func: Name) -> Self {
381 Self::ExtnCall0Arguments(ExtnCall0Arguments { func })
382 }
383
384 pub(crate) fn call_2_or_more_args(func: Name) -> Self {
385 Self::ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments { func })
386 }
387
388 pub(crate) fn reserved_key(key: impl Into<SmolStr>) -> Self {
389 Self::ReservedKey(ReservedKey { key: key.into() })
390 }
391
392 pub(crate) fn unexpected_restricted_expr_kind(kind: ExprKind) -> Self {
393 Self::UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind { kind })
394 }
395
396 pub(crate) fn residual(residual: Expr) -> Self {
397 Self::Residual(Residual { residual })
398 }
399}
400
401#[derive(Debug, Error, Diagnostic)]
403#[error("unsupported call to `{}` with 0 arguments", .func)]
404#[diagnostic(help(
405 "extension function calls with 0 arguments are not currently supported in our JSON format"
406))]
407pub struct ExtnCall0Arguments {
408 func: Name,
410}
411
412#[derive(Debug, Error, Diagnostic)]
414#[error("unsupported call to `{}` with 2 or more arguments", .func)]
415#[diagnostic(help("extension function calls with 2 or more arguments are not currently supported in our JSON format"))]
416pub struct ExtnCall2OrMoreArguments {
417 func: Name,
419}
420
421#[derive(Debug, Error, Diagnostic)]
423#[error("record uses reserved key `{}`", .key)]
424pub struct ReservedKey {
425 key: SmolStr,
427}
428
429impl ReservedKey {
430 pub fn key(&self) -> impl AsRef<str> + '_ {
432 &self.key
433 }
434}
435
436#[derive(Debug, Error, Diagnostic)]
438#[error("unexpected restricted expression `{:?}`", .kind)]
439pub struct UnexpectedRestrictedExprKind {
440 kind: ExprKind,
442}
443
444#[derive(Debug, Error, Diagnostic)]
446#[error("cannot encode residual as JSON: {}", .residual)]
447pub struct Residual {
448 residual: Expr,
450}
451
452#[derive(Debug, Clone)]
455pub enum JsonDeserializationErrorContext {
456 EntityAttribute {
458 uid: EntityUID,
460 attr: SmolStr,
462 },
463 EntityParents {
465 uid: EntityUID,
467 },
468 EntityUid,
470 Context,
472 Policy {
474 id: PolicyID,
476 },
477 TemplateLink,
479 Unknown,
481}
482
483#[derive(Debug, Diagnostic, Error)]
485#[error("type mismatch: value was expected to have type {expected}, but it {mismatch_reason}: `{}`",
486 display_restricted_expr(.actual_val.as_borrowed()),
487)]
488pub struct TypeMismatchError {
489 expected: Box<SchemaType>,
491 mismatch_reason: TypeMismatchReason,
493 actual_val: Box<RestrictedExpr>,
495}
496
497#[derive(Debug, Error)]
498enum TypeMismatchReason {
499 #[error("actually has type {0}")]
502 UnexpectedType(Type),
503 #[error("contains an unexpected attribute `{0}`")]
506 UnexpectedAttr(SmolStr),
507 #[error("is missing the required attribute `{0}`")]
510 MissingRequiredAtr(SmolStr),
511 #[error("does not")]
513 None,
514}
515
516impl TypeMismatchError {
517 pub(crate) fn type_mismatch(
518 expected: SchemaType,
519 actual_ty: Option<Type>,
520 actual_val: RestrictedExpr,
521 ) -> Self {
522 Self {
523 expected: Box::new(expected),
524 mismatch_reason: match actual_ty {
525 Some(ty) => TypeMismatchReason::UnexpectedType(ty),
526 None => TypeMismatchReason::None,
527 },
528 actual_val: Box::new(actual_val),
529 }
530 }
531
532 pub(crate) fn unexpected_attr(
533 expected: SchemaType,
534 unexpected_attr: SmolStr,
535 actual_val: RestrictedExpr,
536 ) -> Self {
537 Self {
538 expected: Box::new(expected),
539 mismatch_reason: TypeMismatchReason::UnexpectedAttr(unexpected_attr),
540 actual_val: Box::new(actual_val),
541 }
542 }
543
544 pub(crate) fn missing_required_attr(
545 expected: SchemaType,
546 missing_attr: SmolStr,
547 actual_val: RestrictedExpr,
548 ) -> Self {
549 Self {
550 expected: Box::new(expected),
551 mismatch_reason: TypeMismatchReason::MissingRequiredAtr(missing_attr),
552 actual_val: Box::new(actual_val),
553 }
554 }
555}
556
557impl std::fmt::Display for JsonDeserializationErrorContext {
558 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
559 match self {
560 Self::EntityAttribute { uid, attr } => write!(f, "in attribute `{attr}` on `{uid}`"),
561 Self::EntityParents { uid } => write!(f, "in parents field of `{uid}`"),
562 Self::EntityUid => write!(f, "in uid field of <unknown entity>"),
563 Self::Context => write!(f, "while parsing context"),
564 Self::Policy { id } => write!(f, "while parsing JSON policy `{id}`"),
565 Self::TemplateLink => write!(f, "while parsing a template link"),
566 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"),
567 }
568 }
569}
570
571fn display_json_value(v: &Either<serde_json::Value, Expr>) -> String {
572 match v {
573 Either::Left(json) => display_value(json),
574 Either::Right(e) => e.to_string(),
575 }
576}
577
578fn display_value(v: &serde_json::Value) -> String {
586 match v {
587 serde_json::Value::Array(contents) => {
588 format!("[{}]", contents.iter().map(display_value).join(", "))
589 }
590 serde_json::Value::Object(map) => {
591 let mut v: Vec<_> = map.iter().collect();
592 v.sort_by_key(|p| p.0);
594 let display_kv = |kv: &(&String, &serde_json::Value)| format!("\"{}\":{}", kv.0, kv.1);
595 format!("{{{}}}", v.iter().map(display_kv).join(","))
596 }
597 other => other.to_string(),
598 }
599}
600
601fn display_restricted_expr(expr: BorrowedRestrictedExpr<'_>) -> String {
605 match expr.expr_kind() {
606 ExprKind::Set(elements) => {
607 let restricted_exprs = elements.iter().map(BorrowedRestrictedExpr::new_unchecked); format!(
609 "[{}]",
610 restricted_exprs
611 .map(display_restricted_expr)
612 .sorted_unstable()
613 .join(", ")
614 )
615 }
616 ExprKind::Record(m) => {
617 format!(
618 "{{{}}}",
619 m.iter()
620 .sorted_unstable_by_key(|(k, _)| SmolStr::clone(k))
621 .map(|(k, v)| format!("\"{}\": {}", k.escape_debug(), v))
622 .join(", ")
623 )
624 }
625 _ => format!("{expr}"), }
627}