cedar_policy_core/entities/json/
value.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 super::{
18    JsonDeserializationError, JsonDeserializationErrorContext, JsonSerializationError, SchemaType,
19};
20use crate::ast::{
21    expression_construction_errors, BorrowedRestrictedExpr, Eid, EntityUID, ExprConstructionError,
22    ExprKind, Literal, Name, RestrictedExpr, Unknown, Value, ValueKind,
23};
24use crate::entities::{
25    schematype_of_restricted_expr, EntitySchemaConformanceError, EscapeKind, GetSchemaTypeError,
26    TypeMismatchError,
27};
28use crate::extensions::Extensions;
29use crate::FromNormalizedStr;
30use either::Either;
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33use serde_with::{DeserializeAs, SerializeAs};
34use smol_str::SmolStr;
35use std::collections::{BTreeMap, HashSet};
36use std::sync::Arc;
37
38#[cfg(feature = "wasm")]
39extern crate tsify;
40
41/// The canonical JSON representation of a Cedar value.
42/// Many Cedar values have a natural one-to-one mapping to and from JSON values.
43/// Cedar values of some types, like entity references or extension values,
44/// cannot easily be represented in JSON and thus are represented using the
45/// `__entity`, or `__extn` escapes.
46///
47/// For example, this is the JSON format for attribute values expected by
48/// `EntityJsonParser`, when schema-based parsing is not used.
49#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
50#[serde(untagged)]
51#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
52#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
53pub enum CedarValueJson {
54    /// The `__expr` escape has been removed, but is still reserved in order to throw meaningful errors.
55    ExprEscape {
56        /// Contents, will be ignored and an error is thrown when attempting to parse this
57        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
58        __expr: SmolStr,
59    },
60    /// Special JSON object with single reserved "__entity" key:
61    /// the following item should be a JSON object of the form
62    /// `{ "type": "xxx", "id": "yyy" }`.
63    /// This escape is necessary for entity references.
64    //
65    // listed before `Record` so that it takes priority: otherwise, the escape
66    // would be interpreted as a Record with a key "__entity". see docs on
67    // `serde(untagged)`
68    EntityEscape {
69        /// JSON object containing the entity type and ID
70        __entity: TypeAndId,
71    },
72    /// Special JSON object with single reserved "__extn" key:
73    /// the following item should be a JSON object of the form
74    /// `{ "fn": "xxx", "arg": "yyy" }`.
75    /// This escape is necessary for extension values.
76    //
77    // listed before `Record` so that it takes priority: otherwise, the escape
78    // would be interpreted as a Record with a key "__extn". see docs on
79    // `serde(untagged)`
80    ExtnEscape {
81        /// JSON object containing the extension-constructor call
82        __extn: FnAndArg,
83    },
84    /// JSON bool => Cedar bool
85    Bool(bool),
86    /// JSON int => Cedar long (64-bit signed integer)
87    Long(i64),
88    /// JSON string => Cedar string
89    String(#[cfg_attr(feature = "wasm", tsify(type = "string"))] SmolStr),
90    /// JSON list => Cedar set; can contain any `CedarValueJson`s, even
91    /// heterogeneously
92    Set(Vec<CedarValueJson>),
93    /// JSON object => Cedar record; must have string keys, but values
94    /// can be any `CedarValueJson`s, even heterogeneously
95    Record(
96        #[cfg_attr(feature = "wasm", tsify(type = "{ [key: string]: CedarValueJson }"))] JsonRecord,
97    ),
98    /// JSON null, which is never valid, but we put this here in order to
99    /// provide a better error message.
100    Null,
101}
102
103/// Structure representing a Cedar record in JSON
104#[serde_as]
105#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
106pub struct JsonRecord {
107    /// Cedar records must have string keys, but values can be any
108    /// `CedarValueJson`s, even heterogeneously
109    #[serde_as(as = "serde_with::MapPreventDuplicates<_, _>")]
110    #[serde(flatten)]
111    values: BTreeMap<SmolStr, CedarValueJson>,
112}
113
114impl IntoIterator for JsonRecord {
115    type Item = (SmolStr, CedarValueJson);
116    type IntoIter = <BTreeMap<SmolStr, CedarValueJson> as IntoIterator>::IntoIter;
117    fn into_iter(self) -> Self::IntoIter {
118        self.values.into_iter()
119    }
120}
121
122impl<'a> IntoIterator for &'a JsonRecord {
123    type Item = (&'a SmolStr, &'a CedarValueJson);
124    type IntoIter = <&'a BTreeMap<SmolStr, CedarValueJson> as IntoIterator>::IntoIter;
125    fn into_iter(self) -> Self::IntoIter {
126        self.values.iter()
127    }
128}
129
130// At this time, this doesn't check for duplicate keys upon constructing a
131// `JsonRecord` from an iterator.
132// As of this writing, we only construct `JsonRecord` from an iterator during
133// _serialization_, not _deserialization_, and we can assume that values being
134// serialized (i.e., coming from the Cedar engine itself) are already free of
135// duplicate keys.
136impl FromIterator<(SmolStr, CedarValueJson)> for JsonRecord {
137    fn from_iter<T: IntoIterator<Item = (SmolStr, CedarValueJson)>>(iter: T) -> Self {
138        Self {
139            values: BTreeMap::from_iter(iter),
140        }
141    }
142}
143
144impl JsonRecord {
145    /// Iterate over the (k, v) pairs in the record
146    pub fn iter(&self) -> impl Iterator<Item = (&'_ SmolStr, &'_ CedarValueJson)> {
147        self.values.iter()
148    }
149
150    /// Get the number of attributes in the record
151    pub fn len(&self) -> usize {
152        self.values.len()
153    }
154
155    /// Is the record empty (no attributes)
156    pub fn is_empty(&self) -> bool {
157        self.values.is_empty()
158    }
159}
160
161/// Structure expected by the `__entity` escape
162#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
163#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
164#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
165pub struct TypeAndId {
166    /// Entity typename
167    #[cfg_attr(feature = "wasm", tsify(type = "string"))]
168    #[serde(rename = "type")]
169    entity_type: SmolStr,
170    /// Entity id
171    #[cfg_attr(feature = "wasm", tsify(type = "string"))]
172    id: SmolStr,
173}
174
175impl From<EntityUID> for TypeAndId {
176    fn from(euid: EntityUID) -> TypeAndId {
177        let (entity_type, eid) = euid.components();
178        TypeAndId {
179            entity_type: entity_type.to_string().into(),
180            id: AsRef::<str>::as_ref(&eid).into(),
181        }
182    }
183}
184
185impl From<&EntityUID> for TypeAndId {
186    fn from(euid: &EntityUID) -> TypeAndId {
187        TypeAndId {
188            entity_type: euid.entity_type().to_string().into(),
189            id: AsRef::<str>::as_ref(&euid.eid()).into(),
190        }
191    }
192}
193
194impl TryFrom<TypeAndId> for EntityUID {
195    type Error = crate::parser::err::ParseErrors;
196
197    fn try_from(e: TypeAndId) -> Result<EntityUID, Self::Error> {
198        Ok(EntityUID::from_components(
199            Name::from_normalized_str(&e.entity_type)?,
200            Eid::new(e.id),
201            None,
202        ))
203    }
204}
205
206/// Structure expected by the `__extn` escape
207#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
208#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
209#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
210pub struct FnAndArg {
211    /// Extension constructor function
212    #[serde(rename = "fn")]
213    #[cfg_attr(feature = "wasm", tsify(type = "string"))]
214    pub(crate) ext_fn: SmolStr,
215    /// Argument to that constructor
216    pub(crate) arg: Box<CedarValueJson>,
217}
218
219impl CedarValueJson {
220    /// Encode the given `EntityUID` as a `CedarValueJson`
221    pub fn uid(euid: &EntityUID) -> Self {
222        Self::EntityEscape {
223            __entity: TypeAndId::from(euid.clone()),
224        }
225    }
226
227    /// Convert this `CedarValueJson` into a Cedar "restricted expression"
228    pub fn into_expr(
229        self,
230        ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
231    ) -> Result<RestrictedExpr, JsonDeserializationError> {
232        match self {
233            Self::Bool(b) => Ok(RestrictedExpr::val(b)),
234            Self::Long(i) => Ok(RestrictedExpr::val(i)),
235            Self::String(s) => Ok(RestrictedExpr::val(s)),
236            Self::Set(vals) => Ok(RestrictedExpr::set(
237                vals.into_iter()
238                    .map(|v| v.into_expr(ctx.clone()))
239                    .collect::<Result<Vec<_>, _>>()?,
240            )),
241            Self::Record(map) => Ok(RestrictedExpr::record(
242                map.into_iter()
243                    .map(|(k, v)| Ok((k, v.into_expr(ctx.clone())?)))
244                    .collect::<Result<Vec<_>, JsonDeserializationError>>()?,
245            )
246            .map_err(|e| match e {
247                ExprConstructionError::DuplicateKey(
248                    expression_construction_errors::DuplicateKeyError { key, .. },
249                ) => JsonDeserializationError::duplicate_key(ctx(), key),
250            })?),
251            Self::EntityEscape { __entity: entity } => Ok(RestrictedExpr::val(
252                EntityUID::try_from(entity.clone()).map_err(|errs| {
253                    JsonDeserializationError::ParseEscape {
254                        kind: EscapeKind::Entity,
255                        value: serde_json::to_string_pretty(&entity)
256                            .unwrap_or_else(|_| format!("{:?}", &entity)),
257                        errs,
258                    }
259                })?,
260            )),
261            Self::ExtnEscape { __extn: extn } => extn.into_expr(ctx),
262            Self::ExprEscape { .. } => Err(JsonDeserializationError::ExprTag(Box::new(ctx()))),
263            Self::Null => Err(JsonDeserializationError::Null(Box::new(ctx()))),
264        }
265    }
266
267    /// Convert a Cedar "restricted expression" into a `CedarValueJson`.
268    pub fn from_expr(expr: BorrowedRestrictedExpr<'_>) -> Result<Self, JsonSerializationError> {
269        match expr.as_ref().expr_kind() {
270            ExprKind::Lit(lit) => Ok(Self::from_lit(lit.clone())),
271            ExprKind::ExtensionFunctionApp { fn_name, args } => match args.len() {
272                0 => Err(JsonSerializationError::ExtnCall0Arguments {
273                    func: fn_name.clone(),
274                }),
275                // PANIC SAFETY. We've checked that `args` is of length 1, fine to index at 0
276                #[allow(clippy::indexing_slicing)]
277                1 => Ok(Self::ExtnEscape {
278                    __extn: FnAndArg {
279                        ext_fn: fn_name.to_string().into(),
280                        arg: Box::new(CedarValueJson::from_expr(
281                            // assuming the invariant holds for `expr`, it must also hold here
282                            BorrowedRestrictedExpr::new_unchecked(
283                                &args[0], // checked above that |args| == 1
284                            ),
285                        )?),
286                    },
287                }),
288                _ => Err(JsonSerializationError::ExtnCall2OrMoreArguments {
289                    func: fn_name.clone(),
290                }),
291            },
292            ExprKind::Set(exprs) => Ok(Self::Set(
293                exprs
294                    .iter()
295                    .map(BorrowedRestrictedExpr::new_unchecked) // assuming the invariant holds for `expr`, it must also hold here
296                    .map(CedarValueJson::from_expr)
297                    .collect::<Result<_, JsonSerializationError>>()?,
298            )),
299            ExprKind::Record(map) => {
300                // if `map` contains a key which collides with one of our JSON
301                // escapes, then we have a problem because it would be interpreted
302                // as an escape when being read back in.
303                check_for_reserved_keys(map.keys())?;
304                Ok(Self::Record(
305                    map.iter()
306                        .map(|(k, v)| {
307                            Ok((
308                                k.clone(),
309                                CedarValueJson::from_expr(
310                                    // assuming the invariant holds for `expr`, it must also hold here
311                                    BorrowedRestrictedExpr::new_unchecked(v),
312                                )?,
313                            ))
314                        })
315                        .collect::<Result<_, JsonSerializationError>>()?,
316                ))
317            }
318            kind => {
319                Err(JsonSerializationError::UnexpectedRestrictedExprKind { kind: kind.clone() })
320            }
321        }
322    }
323
324    /// Convert a Cedar value into a `CedarValueJson`.
325    ///
326    /// Only throws errors in two cases:
327    /// 1. `value` is (or contains) a record with a reserved key such as
328    ///     "__entity"
329    /// 2. `value` is (or contains) an extension value, and the argument to the
330    ///     extension constructor that produced that extension value can't
331    ///     itself be converted to `CedarJsonValue`. (Either because that
332    ///     argument falls into one of these two cases itself, or because the
333    ///     argument is a nontrivial residual.)
334    pub fn from_value(value: Value) -> Result<Self, JsonSerializationError> {
335        Self::from_valuekind(value.value)
336    }
337
338    /// Convert a Cedar `ValueKind` into a `CedarValueJson`.
339    ///
340    /// For discussion of when this throws errors, see notes on `from_value`.
341    pub fn from_valuekind(value: ValueKind) -> Result<Self, JsonSerializationError> {
342        match value {
343            ValueKind::Lit(lit) => Ok(Self::from_lit(lit)),
344            ValueKind::Set(set) => Ok(Self::Set(
345                set.iter()
346                    .cloned()
347                    .map(Self::from_value)
348                    .collect::<Result<_, _>>()?,
349            )),
350            ValueKind::Record(record) => {
351                // if `map` contains a key which collides with one of our JSON
352                // escapes, then we have a problem because it would be interpreted
353                // as an escape when being read back in.
354                check_for_reserved_keys(record.keys())?;
355                Ok(Self::Record(
356                    record
357                        .iter()
358                        .map(|(k, v)| Ok((k.clone(), Self::from_value(v.clone())?)))
359                        .collect::<Result<JsonRecord, JsonSerializationError>>()?,
360                ))
361            }
362            ValueKind::ExtensionValue(ev) => {
363                let ext_fn: &Name = &ev.constructor;
364                Ok(Self::ExtnEscape {
365                    __extn: FnAndArg {
366                        ext_fn: ext_fn.to_string().into(),
367                        arg: match ev.args.as_slice() {
368                            [ref expr] => Box::new(Self::from_expr(expr.as_borrowed())?),
369                            [] => {
370                                return Err(JsonSerializationError::ExtnCall0Arguments {
371                                    func: ext_fn.clone(),
372                                })
373                            }
374                            _ => {
375                                return Err(JsonSerializationError::ExtnCall2OrMoreArguments {
376                                    func: ext_fn.clone(),
377                                })
378                            }
379                        },
380                    },
381                })
382            }
383        }
384    }
385
386    /// Convert a Cedar literal into a `CedarValueJson`.
387    pub fn from_lit(lit: Literal) -> Self {
388        match lit {
389            Literal::Bool(b) => Self::Bool(b),
390            Literal::Long(i) => Self::Long(i),
391            Literal::String(s) => Self::String(s),
392            Literal::EntityUID(euid) => Self::EntityEscape {
393                __entity: Arc::unwrap_or_clone(euid).into(),
394            },
395        }
396    }
397}
398
399/// helper function to check if the given keys contain any reserved keys,
400/// throwing an appropriate `JsonSerializationError` if so
401fn check_for_reserved_keys<'a>(
402    mut keys: impl Iterator<Item = &'a SmolStr>,
403) -> Result<(), JsonSerializationError> {
404    // We could be a little more permissive here, but to be
405    // conservative, we throw an error for any record that contains
406    // any key with a reserved name, not just single-key records
407    // with the reserved names.
408    let reserved_keys: HashSet<&str> = HashSet::from_iter(["__entity", "__extn", "__expr"]);
409    let collision = keys.find(|k| reserved_keys.contains(k.as_str()));
410    match collision {
411        Some(collision) => Err(JsonSerializationError::ReservedKey {
412            key: collision.clone(),
413        }),
414        None => Ok(()),
415    }
416}
417
418impl FnAndArg {
419    /// Convert this `FnAndArg` into a Cedar "restricted expression" (which will be a call to an extension constructor)
420    pub fn into_expr(
421        self,
422        ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
423    ) -> Result<RestrictedExpr, JsonDeserializationError> {
424        Ok(RestrictedExpr::call_extension_fn(
425            Name::from_normalized_str(&self.ext_fn).map_err(|errs| {
426                JsonDeserializationError::ParseEscape {
427                    kind: EscapeKind::Extension,
428                    value: self.ext_fn.to_string(),
429                    errs,
430                }
431            })?,
432            vec![CedarValueJson::into_expr(*self.arg, ctx)?],
433        ))
434    }
435}
436
437/// Struct used to parse Cedar values from JSON.
438#[derive(Debug, Clone)]
439pub struct ValueParser<'e> {
440    /// Extensions which are active for the JSON parsing.
441    extensions: Extensions<'e>,
442}
443
444impl<'e> ValueParser<'e> {
445    /// Create a new `ValueParser`.
446    pub fn new(extensions: Extensions<'e>) -> Self {
447        Self { extensions }
448    }
449
450    /// internal function that converts a Cedar value (in JSON) into a
451    /// `RestrictedExpr`. Performs schema-based parsing if `expected_ty` is
452    /// provided. This does not mean that this function fully validates the
453    /// value against `expected_ty` -- it does not.
454    pub fn val_into_restricted_expr(
455        &self,
456        val: serde_json::Value,
457        expected_ty: Option<&SchemaType>,
458        ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
459    ) -> Result<RestrictedExpr, JsonDeserializationError> {
460        // First we have to check if we've been given an Unknown. This is valid
461        // regardless of the expected type (see #418).
462        let parse_as_unknown = |val: serde_json::Value| {
463            let extjson: ExtnValueJson = serde_json::from_value(val).ok()?;
464            match extjson {
465                ExtnValueJson::ExplicitExtnEscape {
466                    __extn: FnAndArg { ext_fn, arg },
467                } if ext_fn == "unknown" => {
468                    let arg = arg.into_expr(ctx.clone()).ok()?;
469                    let name = arg.as_string()?;
470                    Some(RestrictedExpr::unknown(Unknown::new_untyped(name.clone())))
471                }
472                _ => None, // only explicit `__extn` escape is valid for this purpose. For instance, if we allowed `ImplicitConstructor` here, then all strings would parse as calls to `unknown()`, which is clearly not what we want.
473            }
474        };
475        if let Some(rexpr) = parse_as_unknown(val.clone()) {
476            return Ok(rexpr);
477        }
478        // otherwise, we do normal schema-based parsing based on the expected type.
479        match expected_ty {
480            // The expected type is an entity reference. Special parsing rules
481            // apply: for instance, the `__entity` escape can optionally be omitted.
482            // What this means is that we parse the contents as `EntityUidJson`, and
483            // then convert that into an entity reference `RestrictedExpr`
484            Some(SchemaType::Entity { .. }) => {
485                let uidjson: EntityUidJson = serde_json::from_value(val)?;
486                Ok(RestrictedExpr::val(uidjson.into_euid(ctx)?))
487            }
488            // The expected type is an extension type. Special parsing rules apply:
489            // for instance, the `__extn` escape can optionally be omitted. What
490            // this means is that we parse the contents as `ExtnValueJson`, and then
491            // convert that into an extension-function-call `RestrictedExpr`
492            Some(SchemaType::Extension { ref name, .. }) => {
493                let extjson: ExtnValueJson = serde_json::from_value(val)?;
494                self.extn_value_json_into_rexpr(extjson, name.clone(), ctx)
495            }
496            // The expected type is a set type. No special parsing rules apply, but
497            // we need to parse the elements according to the expected element type
498            Some(expected_ty @ SchemaType::Set { element_ty }) => match val {
499                serde_json::Value::Array(elements) => Ok(RestrictedExpr::set(
500                    elements
501                        .into_iter()
502                        .map(|element| {
503                            self.val_into_restricted_expr(element, Some(element_ty), ctx.clone())
504                        })
505                        .collect::<Result<Vec<RestrictedExpr>, JsonDeserializationError>>()?,
506                )),
507                val => {
508                    let actual_val = {
509                        let jvalue: CedarValueJson = serde_json::from_value(val)?;
510                        jvalue.into_expr(ctx.clone())?
511                    };
512                    let err = TypeMismatchError {
513                        expected: Box::new(expected_ty.clone()),
514                        actual_ty: match schematype_of_restricted_expr(
515                            actual_val.as_borrowed(),
516                            self.extensions,
517                        ) {
518                            Ok(actual_ty) => Some(Box::new(actual_ty)),
519                            Err(_) => None, // just don't report the type if there was an error computing it
520                        },
521                        actual_val: Either::Right(Box::new(actual_val)),
522                    };
523                    match ctx() {
524                        JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
525                            Err(JsonDeserializationError::EntitySchemaConformance(
526                                EntitySchemaConformanceError::TypeMismatch { uid, attr, err },
527                            ))
528                        }
529                        ctx => Err(JsonDeserializationError::TypeMismatch {
530                            ctx: Box::new(ctx),
531                            err,
532                        }),
533                    }
534                }
535            },
536            // The expected type is a record type. No special parsing rules
537            // apply, but we need to parse the attribute values according to
538            // their expected element types
539            Some(
540                expected_ty @ SchemaType::Record {
541                    attrs: expected_attrs,
542                    open_attrs,
543                },
544            ) => match val {
545                serde_json::Value::Object(mut actual_attrs) => {
546                    let ctx2 = ctx.clone(); // for borrow-check, so the original `ctx` can be moved into the closure below
547                    let mut_actual_attrs = &mut actual_attrs; // for borrow-check, so only a mut ref gets moved into the closure, and we retain ownership of `actual_attrs`
548                    let rexpr_pairs = expected_attrs
549                        .iter()
550                        .filter_map(move |(k, expected_attr_ty)| {
551                            match mut_actual_attrs.remove(k.as_str()) {
552                                Some(actual_attr) => {
553                                    match self.val_into_restricted_expr(actual_attr, Some(expected_attr_ty.schema_type()), ctx.clone()) {
554                                        Ok(actual_attr) => Some(Ok((k.clone(), actual_attr))),
555                                        Err(e) => Some(Err(e)),
556                                    }
557                                }
558                                None if expected_attr_ty.is_required() => Some(Err(JsonDeserializationError::MissingRequiredRecordAttr {
559                                    ctx: Box::new(ctx()),
560                                    record_attr: k.clone(),
561                                })),
562                                None => None,
563                            }
564                        })
565                        .collect::<Result<Vec<(SmolStr, RestrictedExpr)>, JsonDeserializationError>>()?;
566
567                    if !open_attrs {
568                        // we've now checked that all expected attrs exist, and removed them from `actual_attrs`.
569                        // we still need to verify that we didn't have any unexpected attrs.
570                        if let Some((record_attr, _)) = actual_attrs.into_iter().next() {
571                            return Err(JsonDeserializationError::UnexpectedRecordAttr {
572                                ctx: Box::new(ctx2()),
573                                record_attr: record_attr.into(),
574                            });
575                        }
576                    }
577
578                    // having duplicate keys should be impossible here (because
579                    // neither `actual_attrs` nor `expected_attrs` can have
580                    // duplicate keys; they're both maps), but we can still throw
581                    // the error properly in the case that it somehow happens
582                    RestrictedExpr::record(rexpr_pairs).map_err(|e| match e {
583                        ExprConstructionError::DuplicateKey(
584                            expression_construction_errors::DuplicateKeyError { key, .. },
585                        ) => JsonDeserializationError::duplicate_key(ctx2(), key),
586                    })
587                }
588                val => {
589                    let actual_val = {
590                        let jvalue: CedarValueJson = serde_json::from_value(val)?;
591                        jvalue.into_expr(ctx.clone())?
592                    };
593                    let err = TypeMismatchError {
594                        expected: Box::new(expected_ty.clone()),
595                        actual_ty: match schematype_of_restricted_expr(
596                            actual_val.as_borrowed(),
597                            self.extensions,
598                        ) {
599                            Ok(actual_ty) => Some(Box::new(actual_ty)),
600                            Err(_) => None, // just don't report the type if there was an error computing it
601                        },
602                        actual_val: Either::Right(Box::new(actual_val)),
603                    };
604                    match ctx() {
605                        JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
606                            Err(JsonDeserializationError::EntitySchemaConformance(
607                                EntitySchemaConformanceError::TypeMismatch { uid, attr, err },
608                            ))
609                        }
610                        ctx => Err(JsonDeserializationError::TypeMismatch {
611                            ctx: Box::new(ctx),
612                            err,
613                        }),
614                    }
615                }
616            },
617            // The expected type is any other type, or we don't have an expected type.
618            // No special parsing rules apply; we do ordinary, non-schema-based parsing.
619            Some(_) | None => {
620                // Everything is parsed as `CedarValueJson`, and converted into
621                // `RestrictedExpr` from that.
622                let jvalue: CedarValueJson = serde_json::from_value(val)?;
623                Ok(jvalue.into_expr(ctx)?)
624            }
625        }
626    }
627
628    /// internal function that converts an `ExtnValueJson` into a
629    /// `RestrictedExpr`, which will be an extension constructor call.
630    ///
631    /// `expected_typename`: Specific extension type that is expected.
632    fn extn_value_json_into_rexpr(
633        &self,
634        extnjson: ExtnValueJson,
635        expected_typename: Name,
636        ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
637    ) -> Result<RestrictedExpr, JsonDeserializationError> {
638        match extnjson {
639            ExtnValueJson::ExplicitExprEscape { __expr } => {
640                Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
641            }
642            ExtnValueJson::ExplicitExtnEscape { __extn }
643            | ExtnValueJson::ImplicitExtnEscape(__extn) => {
644                // reuse the same logic that parses CedarValueJson
645                let jvalue = CedarValueJson::ExtnEscape { __extn };
646                let expr = jvalue.into_expr(ctx.clone())?;
647                match expr.expr_kind() {
648                    ExprKind::ExtensionFunctionApp { .. } => Ok(expr),
649                    _ => Err(JsonDeserializationError::ExpectedExtnValue {
650                        ctx: Box::new(ctx()),
651                        got: Box::new(Either::Right(expr.clone().into())),
652                    }),
653                }
654            }
655            ExtnValueJson::ImplicitConstructor(val) => {
656                let arg = val.into_expr(ctx.clone())?;
657                let argty = schematype_of_restricted_expr(arg.as_borrowed(), self.extensions)
658                    .map_err(|e| match e {
659                        GetSchemaTypeError::HeterogeneousSet(err) => match ctx() {
660                            JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
661                                JsonDeserializationError::EntitySchemaConformance(
662                                    EntitySchemaConformanceError::HeterogeneousSet {
663                                        uid,
664                                        attr,
665                                        err,
666                                    },
667                                )
668                            }
669                            ctx => JsonDeserializationError::HeterogeneousSet {
670                                ctx: Box::new(ctx),
671                                err,
672                            },
673                        },
674                        GetSchemaTypeError::ExtensionFunctionLookup(err) => match ctx() {
675                            JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
676                                JsonDeserializationError::EntitySchemaConformance(
677                                    EntitySchemaConformanceError::ExtensionFunctionLookup {
678                                        uid,
679                                        attr,
680                                        err,
681                                    },
682                                )
683                            }
684                            ctx => JsonDeserializationError::ExtensionFunctionLookup {
685                                ctx: Box::new(ctx),
686                                err,
687                            },
688                        },
689                        GetSchemaTypeError::UnknownInsufficientTypeInfo { .. }
690                        | GetSchemaTypeError::NontrivialResidual { .. } => {
691                            JsonDeserializationError::UnknownInImplicitConstructorArg {
692                                ctx: Box::new(ctx()),
693                                arg: Box::new(arg.clone()),
694                            }
695                        }
696                    })?;
697                let func = self
698                    .extensions
699                    .lookup_single_arg_constructor(
700                        &SchemaType::Extension {
701                            name: expected_typename.clone(),
702                        },
703                        &argty,
704                    )
705                    .map_err(|err| JsonDeserializationError::ExtensionFunctionLookup {
706                        ctx: Box::new(ctx()),
707                        err,
708                    })?
709                    .ok_or_else(|| JsonDeserializationError::MissingImpliedConstructor {
710                        ctx: Box::new(ctx()),
711                        return_type: Box::new(SchemaType::Extension {
712                            name: expected_typename,
713                        }),
714                        arg_type: Box::new(argty.clone()),
715                    })?;
716                Ok(RestrictedExpr::call_extension_fn(
717                    func.name().clone(),
718                    vec![arg],
719                ))
720            }
721        }
722    }
723}
724
725/// An (optional) static context for deserialization of entity uids.
726/// This is useful when, for plumbing reasons, we can't get the appropriate values into the dynamic
727/// context. Primary use case is in the [`DeserializeAs`] trait.
728pub trait DeserializationContext {
729    /// Access the (optional) static context.
730    /// If returns [`None`], use the dynamic context.
731    fn static_context() -> Option<JsonDeserializationErrorContext>;
732}
733
734/// A [`DeserializationContext`] that always returns [`None`].
735/// This is the default behaviour.
736#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
737pub struct NoStaticContext;
738
739impl DeserializationContext for NoStaticContext {
740    fn static_context() -> Option<JsonDeserializationErrorContext> {
741        None
742    }
743}
744
745/// Serde JSON format for Cedar values where we know we're expecting an entity
746/// reference
747#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
748#[serde(untagged)]
749#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
750#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
751pub enum EntityUidJson<Context = NoStaticContext> {
752    /// This was removed in 3.0 and is only here for generating nice error messages.
753    ExplicitExprEscape {
754        /// Contents are ignored.
755        __expr: String,
756        /// Phantom value for the `Context` type parameter
757        #[serde(skip)]
758        context: std::marker::PhantomData<Context>,
759    },
760    /// Explicit `__entity` escape; see notes on `CedarValueJson::EntityEscape`
761    ExplicitEntityEscape {
762        /// JSON object containing the entity type and ID
763        __entity: TypeAndId,
764    },
765    /// Implicit `__entity` escape, in which case we'll see just the TypeAndId
766    /// structure
767    ImplicitEntityEscape(TypeAndId),
768
769    /// Implicit catch-all case for error handling
770    FoundValue(#[cfg_attr(feature = "wasm", tsify(type = "__skip"))] serde_json::Value),
771}
772
773impl<'de, C: DeserializationContext> DeserializeAs<'de, EntityUID> for EntityUidJson<C> {
774    fn deserialize_as<D>(deserializer: D) -> Result<EntityUID, D::Error>
775    where
776        D: serde::Deserializer<'de>,
777    {
778        use serde::de::Error;
779        // We don't know the context that called us, so we'll rely on the statically set context
780        let context = || JsonDeserializationErrorContext::Unknown;
781        let s = EntityUidJson::<C>::deserialize(deserializer)?;
782        let euid = s.into_euid(context).map_err(Error::custom)?;
783        Ok(euid)
784    }
785}
786
787impl<C> SerializeAs<EntityUID> for EntityUidJson<C> {
788    fn serialize_as<S>(source: &EntityUID, serializer: S) -> Result<S::Ok, S::Error>
789    where
790        S: serde::Serializer,
791    {
792        let json: EntityUidJson = source.clone().into();
793        json.serialize(serializer)
794    }
795}
796
797impl<C: DeserializationContext> EntityUidJson<C> {
798    /// Construct an `EntityUidJson` from entity type name and eid.
799    ///
800    /// This will use the `ImplicitEntityEscape` form, if it matters.
801    pub fn new(entity_type: impl Into<SmolStr>, id: impl Into<SmolStr>) -> Self {
802        Self::ImplicitEntityEscape(TypeAndId {
803            entity_type: entity_type.into(),
804            id: id.into(),
805        })
806    }
807
808    /// Convert this `EntityUidJson` into an `EntityUID`
809    pub fn into_euid(
810        self,
811        dynamic_ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
812    ) -> Result<EntityUID, JsonDeserializationError> {
813        let ctx = || C::static_context().unwrap_or_else(&dynamic_ctx);
814        match self {
815            Self::ExplicitEntityEscape { __entity } | Self::ImplicitEntityEscape(__entity) => {
816                // reuse the same logic that parses CedarValueJson
817                let jvalue = CedarValueJson::EntityEscape { __entity };
818                let expr = jvalue.into_expr(&ctx)?;
819                match expr.expr_kind() {
820                    ExprKind::Lit(Literal::EntityUID(euid)) => Ok((**euid).clone()),
821                    _ => Err(JsonDeserializationError::ExpectedLiteralEntityRef {
822                        ctx: Box::new(ctx()),
823                        got: Box::new(Either::Right(expr.clone().into())),
824                    }),
825                }
826            }
827            Self::FoundValue(v) => Err(JsonDeserializationError::ExpectedLiteralEntityRef {
828                ctx: Box::new(ctx()),
829                got: Box::new(Either::Left(v)),
830            }),
831            Self::ExplicitExprEscape { __expr, .. } => {
832                Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
833            }
834        }
835    }
836}
837
838/// Convert an `EntityUID` to `EntityUidJson`, using the `ExplicitEntityEscape` option
839impl From<EntityUID> for EntityUidJson {
840    fn from(uid: EntityUID) -> EntityUidJson {
841        EntityUidJson::ExplicitEntityEscape {
842            __entity: uid.into(),
843        }
844    }
845}
846
847/// Convert an `EntityUID` to `EntityUidJson`, using the `ExplicitEntityEscape` option
848impl From<&EntityUID> for EntityUidJson {
849    fn from(uid: &EntityUID) -> EntityUidJson {
850        EntityUidJson::ExplicitEntityEscape {
851            __entity: uid.into(),
852        }
853    }
854}
855
856/// Serde JSON format for Cedar values where we know we're expecting an
857/// extension value
858#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
859#[serde(untagged)]
860pub enum ExtnValueJson {
861    /// This was removed in 3.0 and is only here for generating nice error messages.
862    ExplicitExprEscape {
863        /// Contents are ignored.
864        __expr: String,
865    },
866    /// Explicit `__extn` escape; see notes on `CedarValueJson::ExtnEscape`
867    ExplicitExtnEscape {
868        /// JSON object containing the extension-constructor call
869        __extn: FnAndArg,
870    },
871    /// Implicit `__extn` escape, in which case we'll just see the `FnAndArg`
872    /// directly
873    ImplicitExtnEscape(FnAndArg),
874    /// Implicit `__extn` escape and constructor. Constructor is implicitly
875    /// selected based on the argument type and the expected type.
876    //
877    // This is listed last so that it has lowest priority when deserializing.
878    // If one of the above forms fits, we use that.
879    ImplicitConstructor(CedarValueJson),
880}