Skip to main content

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::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/// Escape kind
35#[derive(Debug)]
36pub enum EscapeKind {
37    /// Escape `__entity`
38    Entity,
39    /// Escape `__extn`
40    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/// Errors thrown during deserialization from JSON
53//
54// CAUTION: this type is publicly exported in `cedar-policy`.
55// Don't make fields `pub`, don't make breaking changes, and use caution
56// when adding public methods.
57#[derive(Debug, Diagnostic, Error)]
58#[non_exhaustive]
59pub enum JsonDeserializationError {
60    /// Error thrown by the `serde_json` crate
61    #[error(transparent)]
62    #[diagnostic(transparent)]
63    Serde(#[from] JsonError),
64    /// Contents of an escape failed to parse.
65    #[error(transparent)]
66    #[diagnostic(transparent)]
67    ParseEscape(ParseEscape),
68    /// Restricted expression error
69    #[error(transparent)]
70    #[diagnostic(transparent)]
71    RestrictedExpressionError(#[from] RestrictedExpressionError),
72    /// A field that needs to be a literal entity reference, was some other JSON value
73    #[error(transparent)]
74    #[diagnostic(transparent)]
75    ExpectedLiteralEntityRef(ExpectedLiteralEntityRef),
76    /// A field that needs to be an extension value, was some other JSON value
77    #[error(transparent)]
78    #[diagnostic(transparent)]
79    ExpectedExtnValue(ExpectedExtnValue),
80    /// Parents of actions should be actions, but this action has a non-action parent
81    #[error(transparent)]
82    #[diagnostic(transparent)]
83    ActionParentIsNotAction(ActionParentIsNotAction),
84    /// Schema-based parsing needed an implicit extension constructor, but no suitable
85    /// constructor was found
86    #[error(transparent)]
87    #[diagnostic(transparent)]
88    MissingImpliedConstructor(MissingImpliedConstructor),
89    /// Incorrect number of arguments of an extension function are provided
90    /// during schema-based parsing
91    #[error(transparent)]
92    #[diagnostic(transparent)]
93    IncorrectNumOfArguments(IncorrectNumOfArguments),
94    /// Failed to look up an extension function
95    #[error(transparent)]
96    #[diagnostic(transparent)]
97    FailedExtensionFunctionLookup(#[from] ExtensionFunctionLookupError),
98    /// The same key appears two or more times in a single record
99    #[error(transparent)]
100    #[diagnostic(transparent)]
101    DuplicateKey(DuplicateKey),
102    /// Error when evaluating an entity attribute
103    #[error(transparent)]
104    #[diagnostic(transparent)]
105    EntityAttributeEvaluation(#[from] EntityAttrEvaluationError),
106    /// During schema-based parsing, encountered an entity which does not
107    /// conform to the schema.
108    ///
109    /// This error contains the `Entity` analogues some of the other errors
110    /// listed below, among other things.
111    #[error(transparent)]
112    #[diagnostic(transparent)]
113    EntitySchemaConformance(#[from] EntitySchemaConformanceError),
114    /// During schema-based parsing, encountered this attribute on a record, but
115    /// that attribute shouldn't exist on that record
116    #[error(transparent)]
117    #[diagnostic(transparent)]
118    UnexpectedRecordAttr(UnexpectedRecordAttr),
119    /// During schema-based parsing, didn't encounter this attribute of a
120    /// record, but that attribute should have existed
121    #[error(transparent)]
122    #[diagnostic(transparent)]
123    MissingRequiredRecordAttr(MissingRequiredRecordAttr),
124    /// During schema-based parsing, found a different type than the schema indicated.
125    ///
126    /// (This is used in all cases except inside entity attributes; type mismatches in
127    /// entity attributes are reported as `Self::EntitySchemaConformance`. As of
128    /// this writing, that means this should only be used for schema-based
129    /// parsing of the `Context`.)
130    #[error(transparent)]
131    #[diagnostic(transparent)]
132    TypeMismatch(TypeMismatch),
133    /// When trying to deserialize an AST with an error in it - this should fail
134    #[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    /// Raised when a JsonValue contains the no longer supported `__expr` escape
139    #[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    /// Raised when the input JSON contains a `null`
143    #[error("{0}, found a `null`; JSON `null`s are not allowed in Cedar")]
144    Null(Box<JsonDeserializationErrorContext>),
145    /// Returned when a name contains `__cedar`
146    #[error(transparent)]
147    #[diagnostic(transparent)]
148    ReservedName(#[from] ReservedNameError),
149    /// Never returned as of 4.2.0 (entity tags are now stable), but this error
150    /// variant not removed because that would be a breaking change on this
151    /// publicly-exported type.
152    #[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))]
253/// General error for type mismatches
254pub struct TypeMismatch {
255    /// Context of this error, which will be something other than `EntityAttribute`.
256    /// (Type mismatches in entity attributes are reported as
257    /// `Self::EntitySchemaConformance`.)
258    ctx: Box<JsonDeserializationErrorContext>,
259    /// Underlying error
260    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)]
265/// Error type for a record missing a required attr
266pub struct MissingRequiredRecordAttr {
267    /// Context of this error
268    ctx: Box<JsonDeserializationErrorContext>,
269    /// Name of the (Record) attribute which was expected
270    record_attr: SmolStr,
271}
272
273#[derive(Debug, Diagnostic, Error)]
274#[error("{}, record attribute `{}` should not exist according to the schema", .ctx, .record_attr)]
275/// Error type for record attributes that should not exist
276pub struct UnexpectedRecordAttr {
277    /// Context of this error
278    ctx: Box<JsonDeserializationErrorContext>,
279    /// Name of the (Record) attribute which was unexpected
280    record_attr: SmolStr,
281}
282
283#[derive(Debug, Error, Diagnostic)]
284#[error("{}, duplicate key `{}` in record", .ctx, .key)]
285/// Error type for records having duplicate keys
286pub struct DuplicateKey {
287    /// Context of this error
288    ctx: Box<JsonDeserializationErrorContext>,
289    /// The key that appeared two or more times
290    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))]
296/// Error type for missing extension constructors
297pub struct MissingImpliedConstructor {
298    /// Context of this error
299    ctx: Box<JsonDeserializationErrorContext>,
300    /// return type of the constructor we were looking for
301    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")]
306/// Error type for incorrect extension function argument number
307pub struct IncorrectNumOfArguments {
308    /// Expected number of arguments
309    expected_arg_num: usize,
310    /// Provided number of argument
311    provided_arg_num: usize,
312    /// Function name
313    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"))]
319/// Error type for action  parents not having type `Action`
320pub struct ActionParentIsNotAction {
321    /// Action entity that had the invalid parent
322    uid: EntityUID,
323    /// Parent that is invalid
324    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)]
336/// Error type for incorrect escaping
337pub struct ParseEscape {
338    /// Escape kind
339    kind: EscapeKind,
340    /// Escape value at fault
341    value: String,
342    /// Parse errors
343    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))]
351/// Error type for getting any expression other than an entity reference
352pub struct ExpectedLiteralEntityRef {
353    /// Context of this error
354    ctx: Box<JsonDeserializationErrorContext>,
355    /// the expression we got instead
356    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" }}`"#))]
362/// Error type for getting any expression other than en extesion value
363pub struct ExpectedExtnValue {
364    /// Context of this error
365    ctx: Box<JsonDeserializationErrorContext>,
366    /// the expression we got instead
367    got: Box<Either<serde_json::Value, Expr>>,
368}
369
370/// Wrapper type for errors from `serde_json`
371//
372// CAUTION: this type is publicly exported in `cedar-policy`.
373// Don't make fields `pub`, don't make breaking changes, and use caution
374// when adding public methods.
375#[derive(Debug, Error, Diagnostic)]
376#[error(transparent)]
377pub struct JsonError(#[from] serde_json::Error);
378
379impl From<serde_json::Error> for JsonDeserializationError {
380    fn from(value: serde_json::Error) -> Self {
381        Self::Serde(JsonError(value))
382    }
383}
384
385impl From<serde_json::Error> for JsonSerializationError {
386    fn from(value: serde_json::Error) -> Self {
387        Self::Serde(JsonError(value))
388    }
389}
390
391/// Errors thrown during serialization to JSON
392//
393// CAUTION: this type is publicly exported in `cedar-policy`.
394// Don't make fields `pub`, don't make breaking changes, and use caution
395// when adding public methods.
396#[derive(Debug, Diagnostic, Error)]
397#[non_exhaustive]
398pub enum JsonSerializationError {
399    /// Error thrown by `serde_json`
400    #[error(transparent)]
401    #[diagnostic(transparent)]
402    Serde(#[from] JsonError),
403    /// Extension-function calls with 0 arguments are not currently supported in
404    /// our JSON format.
405    #[error(transparent)]
406    #[diagnostic(transparent)]
407    ExtnCall0Arguments(ExtnCall0Arguments),
408    /// Encountered a `Record` which can't be serialized to JSON because it
409    /// contains a key which is reserved as a JSON escape.
410    #[error(transparent)]
411    #[diagnostic(transparent)]
412    ReservedKey(ReservedKey),
413    /// Encountered an `ExprKind` which we didn't expect. Either a case is
414    /// missing in `CedarValueJson::from_expr()`, or an internal invariant was
415    /// violated and there is a non-restricted expression in `RestrictedExpr`
416    #[error(transparent)]
417    #[diagnostic(transparent)]
418    UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
419    /// Encountered a (partial-evaluation) residual which can't be encoded in
420    /// JSON
421    #[error(transparent)]
422    #[diagnostic(transparent)]
423    Residual(Residual),
424    /// Extension-function calls with 2 or more arguments are not currently
425    /// supported in our JSON format.
426    /// Cedar should not throw any error of this variant after #1697
427    #[error(transparent)]
428    #[diagnostic(transparent)]
429    ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
430}
431
432impl JsonSerializationError {
433    pub(crate) fn call_0_args(func: Name) -> Self {
434        Self::ExtnCall0Arguments(ExtnCall0Arguments { func })
435    }
436
437    pub(crate) fn reserved_key(key: impl Into<SmolStr>) -> Self {
438        Self::ReservedKey(ReservedKey { key: key.into() })
439    }
440
441    pub(crate) fn unexpected_restricted_expr_kind(kind: ExprKind) -> Self {
442        Self::UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind { kind })
443    }
444
445    pub(crate) fn residual(residual: Expr) -> Self {
446        Self::Residual(Residual { residual })
447    }
448}
449
450/// Error type for extension functions called w/ 0 arguments
451//
452// CAUTION: this type is publicly exported in `cedar-policy`.
453// Don't make fields `pub`, don't make breaking changes, and use caution
454// when adding public methods.
455#[derive(Debug, Error, Diagnostic)]
456#[error("unsupported call to `{}` with 0 arguments", .func)]
457#[diagnostic(help(
458    "extension function calls with 0 arguments are not currently supported in our JSON format"
459))]
460pub struct ExtnCall0Arguments {
461    /// Name of the function which was called with 0 arguments
462    func: Name,
463}
464
465/// Error type for extension functions called w/ 2+ arguments
466/// Cedar should not throw this error after #1697
467//
468// CAUTION: this type is publicly exported in `cedar-policy`.
469// Don't make fields `pub`, don't make breaking changes, and use caution
470// when adding public methods.
471#[derive(Debug, Error, Diagnostic)]
472#[error("unsupported call to `{}` with 2 or more arguments", .func)]
473#[diagnostic(help("extension function calls with 2 or more arguments are not currently supported in our JSON format"))]
474pub struct ExtnCall2OrMoreArguments {
475    /// Name of the function called w/ 2 or more arguments
476    func: Name,
477}
478
479/// Error type for using a reserved key in a record
480//
481// CAUTION: this type is publicly exported in `cedar-policy`.
482// Don't make fields `pub`, don't make breaking changes, and use caution
483// when adding public methods.
484#[derive(Debug, Error, Diagnostic)]
485#[error("record uses reserved key `{}`", .key)]
486pub struct ReservedKey {
487    /// The reserved key used
488    key: SmolStr,
489}
490
491impl ReservedKey {
492    /// The reserved keyword used as a key
493    pub fn key(&self) -> impl AsRef<str> + '_ {
494        &self.key
495    }
496}
497
498/// Error type for a restricted expression containing a non-restricted expression
499//
500// CAUTION: this type is publicly exported in `cedar-policy`.
501// Don't make fields `pub`, don't make breaking changes, and use caution
502// when adding public methods.
503#[derive(Debug, Error, Diagnostic)]
504#[error("unexpected restricted expression `{:?}`", .kind)]
505pub struct UnexpectedRestrictedExprKind {
506    /// The [`ExprKind`] we didn't expend to find
507    kind: ExprKind,
508}
509
510/// Error type for residuals that can't be serialized
511//
512// CAUTION: this type is publicly exported in `cedar-policy`.
513// Don't make fields `pub`, don't make breaking changes, and use caution
514// when adding public methods.
515#[derive(Debug, Error, Diagnostic)]
516#[error("cannot encode residual as JSON: {}", .residual)]
517pub struct Residual {
518    /// The residual that can't be serialized
519    residual: Expr,
520}
521
522/// Gives information about the context of a JSON deserialization error (e.g.,
523/// where we were in the JSON document).
524#[derive(Debug, Clone)]
525pub enum JsonDeserializationErrorContext {
526    /// The error occurred while deserializing the attribute `attr` of an entity.
527    EntityAttribute {
528        /// Entity where the error occurred
529        uid: EntityUID,
530        /// Attribute where the error occurred
531        attr: SmolStr,
532    },
533    /// The error occurred while deserializing the tag `tag` of an entity.
534    EntityTag {
535        /// Entity where the error occurred
536        uid: EntityUID,
537        /// Tag where the error occurred
538        tag: SmolStr,
539    },
540    /// The error occurred while deserializing the `parents` field of an entity.
541    EntityParents {
542        /// Entity where the error occurred
543        uid: EntityUID,
544    },
545    /// The error occurred while deserializing the `uid` field of an entity.
546    EntityUid,
547    /// The error occurred while deserializing the `Context`.
548    Context,
549    /// The error occurred while deserializing a policy in JSON (EST) form.
550    Policy {
551        /// ID of the policy we were deserializing
552        id: PolicyID,
553    },
554    /// The error occured while deserializing a template link
555    TemplateLink,
556    /// The context was unknown, this shouldn't surface to users
557    Unknown,
558}
559
560/// Type mismatch error (in terms of `SchemaType`)
561#[derive(Debug, Diagnostic, Error)]
562#[error("type mismatch: value was expected to have type {expected}, but it {mismatch_reason}: `{}`",
563    display_restricted_expr(.actual_val.as_borrowed()),
564)]
565pub struct TypeMismatchError {
566    /// Type which was expected
567    expected: Box<SchemaType>,
568    /// Reason for the type mismatch
569    mismatch_reason: TypeMismatchReason,
570    /// Value which doesn't have the expected type
571    actual_val: Box<RestrictedExpr>,
572}
573
574#[derive(Debug, Error)]
575enum TypeMismatchReason {
576    /// We saw this dynamic type which is not compatible with the expected
577    /// schema type.
578    #[error("actually has type {0}")]
579    UnexpectedType(Type),
580    /// We saw a record type expression as expected, but it contains an
581    /// attribute we didn't expect.
582    #[error("contains an unexpected attribute `{0}`")]
583    UnexpectedAttr(SmolStr),
584    /// We saw a record type expression as expected, but it did not contain an
585    /// attribute we expected.
586    #[error("is missing the required attribute `{0}`")]
587    MissingRequiredAtr(SmolStr),
588    /// No further detail available.
589    #[error("does not")]
590    None,
591}
592
593impl TypeMismatchError {
594    pub(crate) fn type_mismatch(
595        expected: SchemaType,
596        actual_ty: Option<Type>,
597        actual_val: RestrictedExpr,
598    ) -> Self {
599        Self {
600            expected: Box::new(expected),
601            mismatch_reason: match actual_ty {
602                Some(ty) => TypeMismatchReason::UnexpectedType(ty),
603                None => TypeMismatchReason::None,
604            },
605            actual_val: Box::new(actual_val),
606        }
607    }
608
609    pub(crate) fn unexpected_attr(
610        expected: SchemaType,
611        unexpected_attr: SmolStr,
612        actual_val: RestrictedExpr,
613    ) -> Self {
614        Self {
615            expected: Box::new(expected),
616            mismatch_reason: TypeMismatchReason::UnexpectedAttr(unexpected_attr),
617            actual_val: Box::new(actual_val),
618        }
619    }
620
621    pub(crate) fn missing_required_attr(
622        expected: SchemaType,
623        missing_attr: SmolStr,
624        actual_val: RestrictedExpr,
625    ) -> Self {
626        Self {
627            expected: Box::new(expected),
628            mismatch_reason: TypeMismatchReason::MissingRequiredAtr(missing_attr),
629            actual_val: Box::new(actual_val),
630        }
631    }
632}
633
634impl std::fmt::Display for JsonDeserializationErrorContext {
635    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
636        match self {
637            Self::EntityAttribute { uid, attr } => write!(f, "in attribute `{attr}` on `{uid}`"),
638            Self::EntityTag { uid, tag } => write!(f, "in tag `{tag}` on `{uid}`"),
639            Self::EntityParents { uid } => write!(f, "in parents field of `{uid}`"),
640            Self::EntityUid => write!(f, "in uid field of <unknown entity>"),
641            Self::Context => write!(f, "while parsing context"),
642            Self::Policy { id } => write!(f, "while parsing JSON policy `{id}`"),
643            Self::TemplateLink => write!(f, "while parsing a template link"),
644            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"),
645        }
646    }
647}
648
649fn display_json_value(v: &Either<serde_json::Value, Expr>) -> String {
650    match v {
651        Either::Left(json) => display_value(json),
652        Either::Right(e) => e.to_string(),
653    }
654}
655
656/// Display a `serde_json::Value`, but sorting object attributes, so that the
657/// output is deterministic (important for tests that check equality of error
658/// messages).
659///
660/// Note that this doesn't sort array elements, because JSON arrays are ordered,
661/// so all JSON-handling functions naturally preserve order for arrays and thus
662/// provide a deterministic output.
663fn display_value(v: &serde_json::Value) -> String {
664    match v {
665        serde_json::Value::Array(contents) => {
666            format!("[{}]", contents.iter().map(display_value).join(", "))
667        }
668        serde_json::Value::Object(map) => {
669            let mut v: Vec<_> = map.iter().collect();
670            // We sort the keys here so that our error messages are consistent and defined
671            v.sort_by_key(|p| p.0);
672            let display_kv = |kv: &(&String, &serde_json::Value)| format!("\"{}\":{}", kv.0, kv.1);
673            format!("{{{}}}", v.iter().map(display_kv).join(","))
674        }
675        other => other.to_string(),
676    }
677}
678
679/// Display a `RestrictedExpr`, but sorting record attributes and set elements,
680/// so that the output is deterministic (important for tests that check equality
681/// of error messages).
682fn display_restricted_expr(expr: BorrowedRestrictedExpr<'_>) -> String {
683    match expr.expr_kind() {
684        ExprKind::Set(elements) => {
685            let restricted_exprs = elements.iter().map(BorrowedRestrictedExpr::new_unchecked); // since the RestrictedExpr invariant holds for the input, it holds for all set elements
686            format!(
687                "[{}]",
688                restricted_exprs
689                    .map(display_restricted_expr)
690                    .sorted_unstable()
691                    .join(", ")
692            )
693        }
694        ExprKind::Record(m) => {
695            format!(
696                "{{{}}}",
697                m.iter()
698                    .sorted_unstable_by_key(|(k, _)| SmolStr::clone(k))
699                    .map(|(k, v)| format!("\"{}\": {}", k.escape_debug(), v))
700                    .join(", ")
701            )
702        }
703        _ => format!("{expr}"), // all other cases: use the normal Display
704    }
705}