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