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