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