cedar_policy_core/entities/json/
err.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use 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/// Escape kind
34#[derive(Debug)]
35pub enum EscapeKind {
36    /// Escape `__entity`
37    Entity,
38    /// Escape `__extn`
39    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/// Errors thrown during deserialization from JSON
52#[derive(Debug, Diagnostic, Error)]
53#[non_exhaustive]
54pub enum JsonDeserializationError {
55    /// Error thrown by the `serde_json` crate
56    #[error(transparent)]
57    #[diagnostic(transparent)]
58    Serde(#[from] JsonError),
59    /// Contents of an escape failed to parse.
60    #[error(transparent)]
61    #[diagnostic(transparent)]
62    ParseEscape(ParseEscape),
63    /// Restricted expression error
64    #[error(transparent)]
65    #[diagnostic(transparent)]
66    RestrictedExpressionError(#[from] RestrictedExpressionError),
67    /// A field that needs to be a literal entity reference, was some other JSON value
68    #[error(transparent)]
69    #[diagnostic(transparent)]
70    ExpectedLiteralEntityRef(ExpectedLiteralEntityRef),
71    /// A field that needs to be an extension value, was some other JSON value
72    #[error(transparent)]
73    #[diagnostic(transparent)]
74    ExpectedExtnValue(ExpectedExtnValue),
75    /// Parents of actions should be actions, but this action has a non-action parent
76    #[error(transparent)]
77    #[diagnostic(transparent)]
78    ActionParentIsNotAction(ActionParentIsNotAction),
79    /// Schema-based parsing needed an implicit extension constructor, but no suitable
80    /// constructor was found
81    #[error(transparent)]
82    #[diagnostic(transparent)]
83    MissingImpliedConstructor(MissingImpliedConstructor),
84    /// The same key appears two or more times in a single record
85    #[error(transparent)]
86    #[diagnostic(transparent)]
87    DuplicateKey(DuplicateKey),
88    /// Error when evaluating an entity attribute
89    #[error(transparent)]
90    #[diagnostic(transparent)]
91    EntityAttributeEvaluation(#[from] EntityAttrEvaluationError),
92    /// During schema-based parsing, encountered an entity which does not
93    /// conform to the schema.
94    ///
95    /// This error contains the `Entity` analogues some of the other errors
96    /// listed below, among other things.
97    #[error(transparent)]
98    #[diagnostic(transparent)]
99    EntitySchemaConformance(EntitySchemaConformanceError),
100    /// During schema-based parsing, encountered this attribute on a record, but
101    /// that attribute shouldn't exist on that record
102    #[error(transparent)]
103    #[diagnostic(transparent)]
104    UnexpectedRecordAttr(UnexpectedRecordAttr),
105    /// During schema-based parsing, didn't encounter this attribute of a
106    /// record, but that attribute should have existed
107    #[error(transparent)]
108    #[diagnostic(transparent)]
109    MissingRequiredRecordAttr(MissingRequiredRecordAttr),
110    /// During schema-based parsing, found a different type than the schema indicated.
111    ///
112    /// (This is used in all cases except inside entity attributes; type mismatches in
113    /// entity attributes are reported as `Self::EntitySchemaConformance`. As of
114    /// this writing, that means this should only be used for schema-based
115    /// parsing of the `Context`.)
116    #[error(transparent)]
117    #[diagnostic(transparent)]
118    TypeMismatch(TypeMismatch),
119    /// Raised when a JsonValue contains the no longer supported `__expr` escape
120    #[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    /// Raised when the input JSON contains a `null`
124    #[error("{0}, found a `null`; JSON `null`s are not allowed in Cedar")]
125    Null(Box<JsonDeserializationErrorContext>),
126    /// Returned when a name contains `__cedar`
127    #[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}")]
222/// General error for type mismatches
223pub struct TypeMismatch {
224    /// Context of this error, which will be something other than `EntityAttribute`.
225    /// (Type mismatches in entity attributes are reported as
226    /// `Self::EntitySchemaConformance`.)
227    ctx: Box<JsonDeserializationErrorContext>,
228    /// Underlying error
229    #[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)]
235/// Error type for a record missing a required attr
236pub struct MissingRequiredRecordAttr {
237    /// Context of this error
238    ctx: Box<JsonDeserializationErrorContext>,
239    /// Name of the (Record) attribute which was expected
240    record_attr: SmolStr,
241}
242
243#[derive(Debug, Diagnostic, Error)]
244#[error("{}, record attribute `{}` should not exist according to the schema", .ctx, .record_attr)]
245/// Error type for record attributes that should not exist
246pub struct UnexpectedRecordAttr {
247    /// Context of this error
248    ctx: Box<JsonDeserializationErrorContext>,
249    /// Name of the (Record) attribute which was unexpected
250    record_attr: SmolStr,
251}
252
253#[derive(Debug, Error, Diagnostic)]
254#[error("{}, duplicate key `{}` in record", .ctx, .key)]
255/// Error type for records having duplicate keys
256pub struct DuplicateKey {
257    /// Context of this error
258    ctx: Box<JsonDeserializationErrorContext>,
259    /// The key that appeared two or more times
260    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))]
266/// Error type for missing extension constructors
267pub struct MissingImpliedConstructor {
268    /// Context of this error
269    ctx: Box<JsonDeserializationErrorContext>,
270    /// return type of the constructor we were looking for
271    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"))]
277/// Error type for action  parents not having type `Action`
278pub struct ActionParentIsNotAction {
279    /// Action entity that had the invalid parent
280    uid: EntityUID,
281    /// Parent that is invalid
282    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    }))]
291/// Error type for incorrect escaping
292pub struct ParseEscape {
293    /// Escape kind
294    kind: EscapeKind,
295    /// Escape value at fault
296    value: String,
297    /// Parse errors
298    #[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))]
307/// Error type for getting any expression other than an entity reference
308pub struct ExpectedLiteralEntityRef {
309    /// Context of this error
310    ctx: Box<JsonDeserializationErrorContext>,
311    /// the expression we got instead
312    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" }}`"#))]
318/// Error type for getting any expression other than en extesion value
319pub struct ExpectedExtnValue {
320    /// Context of this error
321    ctx: Box<JsonDeserializationErrorContext>,
322    /// the expression we got instead
323    got: Box<Either<serde_json::Value, Expr>>,
324}
325
326#[derive(Debug, Error, Diagnostic)]
327#[error(transparent)]
328/// Wrapper type for errors from `serde_json`
329pub 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/// Errors thrown during serialization to JSON
344#[derive(Debug, Diagnostic, Error)]
345#[non_exhaustive]
346pub enum JsonSerializationError {
347    /// Error thrown by `serde_json`
348    #[error(transparent)]
349    #[diagnostic(transparent)]
350    Serde(#[from] JsonError),
351    /// Extension-function calls with 0 arguments are not currently supported in
352    /// our JSON format.
353    #[error(transparent)]
354    #[diagnostic(transparent)]
355    ExtnCall0Arguments(ExtnCall0Arguments),
356    /// Extension-function calls with 2 or more arguments are not currently
357    /// supported in our JSON format.
358    #[error(transparent)]
359    #[diagnostic(transparent)]
360    ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
361    /// Encountered a `Record` which can't be serialized to JSON because it
362    /// contains a key which is reserved as a JSON escape.
363    #[error(transparent)]
364    #[diagnostic(transparent)]
365    ReservedKey(ReservedKey),
366    /// Encountered an `ExprKind` which we didn't expect. Either a case is
367    /// missing in `CedarValueJson::from_expr()`, or an internal invariant was
368    /// violated and there is a non-restricted expression in `RestrictedExpr`
369    #[error(transparent)]
370    #[diagnostic(transparent)]
371    UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
372    /// Encountered a (partial-evaluation) residual which can't be encoded in
373    /// JSON
374    #[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/// Error type for extension functions called w/ 0 arguments
402#[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    /// Name of the function which was called with 0 arguments
409    func: Name,
410}
411
412/// Error type for extension functions called w/ 2+ arguments
413#[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    /// Name of the function called w/ 2 or more arguments
418    func: Name,
419}
420
421/// Error type for using a reserved key in a record
422#[derive(Debug, Error, Diagnostic)]
423#[error("record uses reserved key `{}`", .key)]
424pub struct ReservedKey {
425    /// The reserved key used
426    key: SmolStr,
427}
428
429impl ReservedKey {
430    /// The reserved keyword used as a key
431    pub fn key(&self) -> impl AsRef<str> + '_ {
432        &self.key
433    }
434}
435
436/// Error type for a restricted expression containing a non-restricted expression
437#[derive(Debug, Error, Diagnostic)]
438#[error("unexpected restricted expression `{:?}`", .kind)]
439pub struct UnexpectedRestrictedExprKind {
440    /// The [`ExprKind`] we didn't expend to find
441    kind: ExprKind,
442}
443
444/// Error type for residuals that can't be serialized
445#[derive(Debug, Error, Diagnostic)]
446#[error("cannot encode residual as JSON: {}", .residual)]
447pub struct Residual {
448    /// The residual that can't be serialized
449    residual: Expr,
450}
451
452/// Gives information about the context of a JSON deserialization error (e.g.,
453/// where we were in the JSON document).
454#[derive(Debug, Clone)]
455pub enum JsonDeserializationErrorContext {
456    /// The error occurred while deserializing the attribute `attr` of an entity.
457    EntityAttribute {
458        /// Entity where the error occurred
459        uid: EntityUID,
460        /// Attribute where the error occurred
461        attr: SmolStr,
462    },
463    /// The error occurred while deserializing the `parents` field of an entity.
464    EntityParents {
465        /// Entity where the error occurred
466        uid: EntityUID,
467    },
468    /// The error occurred while deserializing the `uid` field of an entity.
469    EntityUid,
470    /// The error occurred while deserializing the `Context`.
471    Context,
472    /// The error occurred while deserializing a policy in JSON (EST) form.
473    Policy {
474        /// ID of the policy we were deserializing
475        id: PolicyID,
476    },
477    /// The error occured while deserializing a template link
478    TemplateLink,
479    /// The context was unknown, this shouldn't surface to users
480    Unknown,
481}
482
483/// Type mismatch error (in terms of `SchemaType`)
484#[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    /// Type which was expected
490    expected: Box<SchemaType>,
491    /// Reason for the type mismatch
492    mismatch_reason: TypeMismatchReason,
493    /// Value which doesn't have the expected type
494    actual_val: Box<RestrictedExpr>,
495}
496
497#[derive(Debug, Error)]
498enum TypeMismatchReason {
499    /// We saw this dynamic type which is not compatible with the expected
500    /// schema type.
501    #[error("actually has type {0}")]
502    UnexpectedType(Type),
503    /// We saw a record type expression as expected, but it contains an
504    /// attribute we didn't expect.
505    #[error("contains an unexpected attribute `{0}`")]
506    UnexpectedAttr(SmolStr),
507    /// We saw a record type expression as expected, but it did not contain an
508    /// attribute we expected.
509    #[error("is missing the required attribute `{0}`")]
510    MissingRequiredAtr(SmolStr),
511    /// No further detail available.
512    #[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
578/// Display a `serde_json::Value`, but sorting object attributes, so that the
579/// output is deterministic (important for tests that check equality of error
580/// messages).
581///
582/// Note that this doesn't sort array elements, because JSON arrays are ordered,
583/// so all JSON-handling functions naturally preserve order for arrays and thus
584/// provide a deterministic output.
585fn 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            // We sort the keys here so that our error messages are consistent and defined
593            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
601/// Display a `RestrictedExpr`, but sorting record attributes and set elements,
602/// so that the output is deterministic (important for tests that check equality
603/// of error messages).
604fn 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); // since the RestrictedExpr invariant holds for the input, it holds for all set elements
608            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}"), // all other cases: use the normal Display
626    }
627}