Skip to main content

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: FnAndArgs,
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))] if r.values.len() >= 2 => {
149                            if let Some(RawCedarValueJson::String(fn_name)) = r.values.get("fn") {
150                                if let Some(arg) = r.values.get("arg") {
151                                    return Self::ExtnEscape {
152                                        __extn: FnAndArgs::Single {
153                                            ext_fn: fn_name.clone(),
154                                            arg: Box::new(arg.clone().into()),
155                                        },
156                                    };
157                                }
158                                if let Some(RawCedarValueJson::Set(args)) = r.values.get("args") {
159                                    return Self::ExtnEscape {
160                                        __extn: FnAndArgs::Multi {
161                                            ext_fn: fn_name.clone(),
162                                            args: args.iter().cloned().map(Into::into).collect(),
163                                        },
164                                    };
165                                }
166                            }
167                        }
168                        [("__expr", RawCedarValueJson::String(s))] => {
169                            return Self::ExprEscape { __expr: s.clone() };
170                        }
171                        [("__entity", RawCedarValueJson::Record(r))] if r.values.len() >= 2 => {
172                            if let Some(RawCedarValueJson::String(ty)) = r.values.get("type") {
173                                if let Some(RawCedarValueJson::String(id)) = r.values.get("id") {
174                                    return Self::EntityEscape {
175                                        __entity: TypeAndId {
176                                            entity_type: ty.clone(),
177                                            id: id.clone(),
178                                        },
179                                    };
180                                }
181                            }
182                        }
183                        _ => {}
184                    }
185                }
186                Self::Record(r.into())
187            }
188            RawCedarValueJson::Set(s) => Self::Set(s.into_iter().map(Into::into).collect()),
189            RawCedarValueJson::String(s) => Self::String(s),
190        }
191    }
192}
193
194#[serde_as]
195#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
196struct RawJsonRecord {
197    /// Cedar records must have string keys, but values can be any
198    /// `CedarValueJson`s, even heterogeneously
199    #[serde_as(as = "serde_with::MapPreventDuplicates<_, _>")]
200    #[serde(flatten)]
201    values: BTreeMap<SmolStr, RawCedarValueJson>,
202}
203
204impl From<RawJsonRecord> for JsonRecord {
205    fn from(value: RawJsonRecord) -> Self {
206        JsonRecord {
207            values: value
208                .values
209                .into_iter()
210                .map(|(k, v)| (k, v.into()))
211                .collect(),
212        }
213    }
214}
215
216/// Structure representing a Cedar record in JSON
217#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
218pub struct JsonRecord {
219    /// Cedar records must have string keys, but values can be any
220    /// `CedarValueJson`s, even heterogeneously
221    #[serde(flatten)]
222    values: BTreeMap<SmolStr, CedarValueJson>,
223}
224
225impl IntoIterator for JsonRecord {
226    type Item = (SmolStr, CedarValueJson);
227    type IntoIter = <BTreeMap<SmolStr, CedarValueJson> as IntoIterator>::IntoIter;
228    fn into_iter(self) -> Self::IntoIter {
229        self.values.into_iter()
230    }
231}
232
233impl<'a> IntoIterator for &'a JsonRecord {
234    type Item = (&'a SmolStr, &'a CedarValueJson);
235    type IntoIter = <&'a BTreeMap<SmolStr, CedarValueJson> as IntoIterator>::IntoIter;
236    fn into_iter(self) -> Self::IntoIter {
237        self.values.iter()
238    }
239}
240
241// At this time, this doesn't check for duplicate keys upon constructing a
242// `JsonRecord` from an iterator.
243// As of this writing, we only construct `JsonRecord` from an iterator during
244// _serialization_, not _deserialization_, and we can assume that values being
245// serialized (i.e., coming from the Cedar engine itself) are already free of
246// duplicate keys.
247impl FromIterator<(SmolStr, CedarValueJson)> for JsonRecord {
248    fn from_iter<T: IntoIterator<Item = (SmolStr, CedarValueJson)>>(iter: T) -> Self {
249        Self {
250            values: BTreeMap::from_iter(iter),
251        }
252    }
253}
254
255impl JsonRecord {
256    /// Iterate over the (k, v) pairs in the record
257    pub fn iter(&self) -> impl Iterator<Item = (&'_ SmolStr, &'_ CedarValueJson)> {
258        self.values.iter()
259    }
260
261    /// Get the number of attributes in the record
262    pub fn len(&self) -> usize {
263        self.values.len()
264    }
265
266    /// Is the record empty (no attributes)
267    pub fn is_empty(&self) -> bool {
268        self.values.is_empty()
269    }
270}
271
272/// Structure expected by the `__entity` escape
273#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
274#[serde(rename_all = "camelCase")]
275#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
276#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
277pub struct TypeAndId {
278    /// Entity typename
279    #[cfg_attr(feature = "wasm", tsify(type = "string"))]
280    #[serde(rename = "type")]
281    entity_type: SmolStr,
282    /// Entity id
283    #[cfg_attr(feature = "wasm", tsify(type = "string"))]
284    id: SmolStr,
285}
286
287impl From<EntityUID> for TypeAndId {
288    fn from(euid: EntityUID) -> TypeAndId {
289        let (entity_type, eid) = euid.components();
290        TypeAndId {
291            entity_type: entity_type.to_smolstr(),
292            id: AsRef::<str>::as_ref(&eid).into(),
293        }
294    }
295}
296
297impl From<&EntityUID> for TypeAndId {
298    fn from(euid: &EntityUID) -> TypeAndId {
299        TypeAndId {
300            entity_type: euid.entity_type().to_smolstr(),
301            id: AsRef::<str>::as_ref(&euid.eid()).into(),
302        }
303    }
304}
305
306impl TryFrom<TypeAndId> for EntityUID {
307    type Error = crate::parser::err::ParseErrors;
308
309    fn try_from(e: TypeAndId) -> Result<EntityUID, crate::parser::err::ParseErrors> {
310        Ok(EntityUID::from_components(
311            Name::from_normalized_str(&e.entity_type)?.into(),
312            Eid::new(e.id),
313            None,
314        ))
315    }
316}
317
318/// Structure expected by the `__extn` escape
319#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
320#[serde(rename_all = "camelCase")]
321#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
322#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
323#[serde(untagged)]
324pub enum FnAndArgs {
325    /// Single-argument function
326    Single {
327        /// Extension constructor function
328        #[serde(rename = "fn")]
329        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
330        ext_fn: SmolStr,
331        /// Argument to that constructor
332        arg: Box<CedarValueJson>,
333    },
334    /// Multi-argument function
335    Multi {
336        /// Extension constructor function
337        #[serde(rename = "fn")]
338        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
339        ext_fn: SmolStr,
340        /// Arguments to that constructor
341        args: Vec<CedarValueJson>,
342    },
343}
344
345impl FnAndArgs {
346    pub(crate) fn fn_str(&self) -> &str {
347        match self {
348            Self::Multi { ext_fn, .. } | Self::Single { ext_fn, .. } => ext_fn,
349        }
350    }
351
352    pub(crate) fn args(&self) -> &[CedarValueJson] {
353        match self {
354            Self::Multi { args, .. } => args,
355            Self::Single { arg, .. } => std::slice::from_ref(arg),
356        }
357    }
358}
359
360impl CedarValueJson {
361    /// Encode the given `EntityUID` as a `CedarValueJson`
362    pub fn uid(euid: &EntityUID) -> Self {
363        Self::EntityEscape {
364            __entity: TypeAndId::from(euid.clone()),
365        }
366    }
367
368    /// Compute the height of this value tree, defined as the number of
369    /// edges on the longest root-to-leaf path through nested
370    /// `CedarValueJson` nodes.
371    ///
372    /// Leaf nodes (`Bool`, `Long`, `String`, `Null`, `ExprEscape`, and `EntityEscape`)
373    /// have height 0.
374    /// An empty `Set` or empty `Record` also has height 0.
375    ///
376    /// For internal nodes (`Set`, `Record`, and `ExtnEscape`) with at least
377    /// one child, the height is 1 + the maximum height among all
378    /// children.
379    ///
380    /// Uses an iterative depth-first traversal rather than recursion.
381    pub fn height(&self) -> usize {
382        // Stack of (node, depth) pairs.
383        // Start with this node at depth 0.
384        let mut stack: Vec<(&CedarValueJson, usize)> = vec![(self, 0)];
385        let mut max_depth: usize = 0;
386        while let Some((val, depth)) = stack.pop() {
387            max_depth = max_depth.max(depth);
388            let child_depth = depth + 1;
389            match val {
390                Self::Bool(_)
391                | Self::Long(_)
392                | Self::String(_)
393                | Self::Null
394                | Self::ExprEscape { .. }
395                | Self::EntityEscape { .. } => {}
396                Self::Set(vals) => {
397                    for v in vals {
398                        stack.push((v, child_depth));
399                    }
400                }
401                Self::Record(rec) => {
402                    for (_, v) in rec {
403                        stack.push((v, child_depth));
404                    }
405                }
406                Self::ExtnEscape { __extn } => {
407                    for arg in __extn.args() {
408                        stack.push((arg, child_depth));
409                    }
410                }
411            }
412        }
413        max_depth
414    }
415
416    /// Convert this `CedarValueJson` into a Cedar "restricted expression"
417    pub fn into_expr(
418        self,
419        ctx: &dyn Fn() -> JsonDeserializationErrorContext,
420    ) -> Result<RestrictedExpr, JsonDeserializationError> {
421        match self {
422            Self::Bool(b) => Ok(RestrictedExpr::val(b)),
423            Self::Long(i) => Ok(RestrictedExpr::val(i)),
424            Self::String(s) => Ok(RestrictedExpr::val(s)),
425            Self::Set(vals) => Ok(RestrictedExpr::set(
426                vals.into_iter()
427                    .map(|v| v.into_expr(ctx))
428                    .collect::<Result<Vec<_>, _>>()?,
429            )),
430            Self::Record(map) => Ok(RestrictedExpr::record(
431                map.into_iter()
432                    .map(|(k, v)| Ok((k, v.into_expr(ctx)?)))
433                    .collect::<Result<Vec<_>, JsonDeserializationError>>()?,
434            )
435            .map_err(|e| match e {
436                ExpressionConstructionError::DuplicateKey(
437                    expression_construction_errors::DuplicateKeyError { key, .. },
438                ) => JsonDeserializationError::duplicate_key(ctx(), key),
439            })?),
440            Self::EntityEscape { __entity: entity } => Ok(RestrictedExpr::val(
441                EntityUID::try_from(entity.clone()).map_err(|errs| {
442                    let err_msg = serde_json::to_string_pretty(&entity)
443                        .unwrap_or_else(|_| format!("{:?}", &entity));
444                    JsonDeserializationError::parse_escape(EscapeKind::Entity, err_msg, errs)
445                })?,
446            )),
447            Self::ExtnEscape { __extn: extn } => extn.into_expr(ctx),
448            Self::ExprEscape { .. } => Err(JsonDeserializationError::ExprTag(Box::new(ctx()))),
449            Self::Null => Err(JsonDeserializationError::Null(Box::new(ctx()))),
450        }
451    }
452
453    /// Convert a Cedar "restricted expression" into a `CedarValueJson`.
454    pub fn from_expr(expr: BorrowedRestrictedExpr<'_>) -> Result<Self, JsonSerializationError> {
455        match expr.as_ref().expr_kind() {
456            ExprKind::Lit(lit) => Ok(Self::from_lit(lit.clone())),
457            ExprKind::ExtensionFunctionApp { fn_name, args } => match args.as_slice() {
458                [] => Err(JsonSerializationError::call_0_args(fn_name.clone())),
459                [arg] => Ok(Self::ExtnEscape {
460                    __extn: FnAndArgs::Single {
461                        ext_fn: fn_name.to_smolstr(),
462                        arg: Box::new(CedarValueJson::from_expr(
463                            // assuming the invariant holds for `expr`, it must also hold here
464                            BorrowedRestrictedExpr::new_unchecked(arg),
465                        )?),
466                    },
467                }),
468                args => Ok(Self::ExtnEscape {
469                    __extn: FnAndArgs::Multi {
470                        ext_fn: fn_name.to_smolstr(),
471                        args: args
472                            .iter()
473                            .map(|arg| {
474                                CedarValueJson::from_expr(BorrowedRestrictedExpr::new_unchecked(
475                                    arg,
476                                ))
477                            })
478                            .collect::<Result<Vec<_>, _>>()?,
479                    },
480                }),
481            },
482            ExprKind::Set(exprs) => Ok(Self::Set(
483                exprs
484                    .iter()
485                    .map(BorrowedRestrictedExpr::new_unchecked) // assuming the invariant holds for `expr`, it must also hold here
486                    .map(CedarValueJson::from_expr)
487                    .collect::<Result<_, JsonSerializationError>>()?,
488            )),
489            ExprKind::Record(map) => {
490                // if `map` contains a key which collides with one of our JSON
491                // escapes, then we have a problem because it would be interpreted
492                // as an escape when being read back in.
493                check_for_reserved_keys(map.keys())?;
494                Ok(Self::Record(
495                    map.iter()
496                        .map(|(k, v)| {
497                            Ok((
498                                k.clone(),
499                                CedarValueJson::from_expr(
500                                    // assuming the invariant holds for `expr`, it must also hold here
501                                    BorrowedRestrictedExpr::new_unchecked(v),
502                                )?,
503                            ))
504                        })
505                        .collect::<Result<_, JsonSerializationError>>()?,
506                ))
507            }
508            // Serialize Unknown as a extension function with a single argument. The name of the unknown.
509            // This matches how Unknown is deserialized from JSON format.
510            ExprKind::Unknown(unknown) => Ok(Self::ExtnEscape {
511                __extn: FnAndArgs::Single {
512                    ext_fn: "unknown".into(),
513                    arg: Box::new(CedarValueJson::String(unknown.name.clone())),
514                },
515            }),
516            kind => Err(JsonSerializationError::unexpected_restricted_expr_kind(
517                kind.clone(),
518            )),
519        }
520    }
521
522    /// Convert a Cedar value into a `CedarValueJson`.
523    ///
524    /// Only throws errors in two cases:
525    /// 1. `value` is (or contains) a record with a reserved key such as
526    ///    "__entity"
527    /// 2. `value` is (or contains) an extension value, and the argument to the
528    ///    extension constructor that produced that extension value can't
529    ///    itself be converted to `CedarJsonValue`. (Either because that
530    ///    argument falls into one of these two cases itself, or because the
531    ///    argument is a nontrivial residual.)
532    pub fn from_value(value: Value) -> Result<Self, JsonSerializationError> {
533        Self::from_valuekind(value.value)
534    }
535
536    /// Convert a Cedar `ValueKind` into a `CedarValueJson`.
537    ///
538    /// For discussion of when this throws errors, see notes on `from_value`.
539    pub fn from_valuekind(value: ValueKind) -> Result<Self, JsonSerializationError> {
540        match value {
541            ValueKind::Lit(lit) => Ok(Self::from_lit(lit)),
542            ValueKind::Set(set) => Ok(Self::Set(
543                set.iter()
544                    .cloned()
545                    .map(Self::from_value)
546                    .collect::<Result<_, _>>()?,
547            )),
548            ValueKind::Record(record) => {
549                // if `map` contains a key which collides with one of our JSON
550                // escapes, then we have a problem because it would be interpreted
551                // as an escape when being read back in.
552                check_for_reserved_keys(record.keys())?;
553                Ok(Self::Record(
554                    record
555                        .iter()
556                        .map(|(k, v)| Ok((k.clone(), Self::from_value(v.clone())?)))
557                        .collect::<Result<JsonRecord, JsonSerializationError>>()?,
558                ))
559            }
560            ValueKind::ExtensionValue(ev) => {
561                let ext_func = &ev.func;
562                match ev.args.as_slice() {
563                    [] => Err(JsonSerializationError::call_0_args(ext_func.clone())),
564                    [ref expr] => Ok(Self::ExtnEscape {
565                        __extn: FnAndArgs::Single {
566                            ext_fn: ext_func.to_smolstr(),
567                            arg: Box::new(Self::from_expr(expr.as_borrowed())?),
568                        },
569                    }),
570                    exprs => Ok(Self::ExtnEscape {
571                        __extn: FnAndArgs::Multi {
572                            ext_fn: ext_func.to_smolstr(),
573                            args: exprs
574                                .iter()
575                                .map(|expr| Self::from_expr(expr.as_borrowed()))
576                                .collect::<Result<Vec<_>, _>>()?,
577                        },
578                    }),
579                }
580            }
581        }
582    }
583
584    /// Convert a Cedar literal into a `CedarValueJson`.
585    pub fn from_lit(lit: Literal) -> Self {
586        match lit {
587            Literal::Bool(b) => Self::Bool(b),
588            Literal::Long(i) => Self::Long(i),
589            Literal::String(s) => Self::String(s),
590            Literal::EntityUID(euid) => Self::EntityEscape {
591                __entity: Arc::unwrap_or_clone(euid).into(),
592            },
593        }
594    }
595
596    /// Substitute entity literals
597    pub fn sub_entity_literals(
598        self,
599        mapping: &BTreeMap<EntityUID, EntityUID>,
600    ) -> Result<Self, JsonDeserializationError> {
601        match self {
602            // Since we are modifying an already legal policy, this should be unreachable.
603            CedarValueJson::ExprEscape { __expr } => Err(JsonDeserializationError::ExprTag(
604                Box::new(JsonDeserializationErrorContext::Unknown),
605            )),
606            CedarValueJson::EntityEscape { __entity } => {
607                let euid = EntityUID::try_from(__entity.clone());
608                match euid {
609                    Ok(euid) => match mapping.get(&euid) {
610                        Some(new_euid) => Ok(CedarValueJson::EntityEscape {
611                            __entity: new_euid.into(),
612                        }),
613                        None => Ok(CedarValueJson::EntityEscape { __entity }),
614                    },
615                    Err(_) => Ok(CedarValueJson::EntityEscape { __entity }),
616                }
617            }
618            CedarValueJson::ExtnEscape {
619                __extn: FnAndArgs::Single { ext_fn, arg },
620            } => Ok(CedarValueJson::ExtnEscape {
621                __extn: FnAndArgs::Single {
622                    ext_fn,
623                    arg: Box::new((*arg).sub_entity_literals(mapping)?),
624                },
625            }),
626            CedarValueJson::ExtnEscape {
627                __extn: FnAndArgs::Multi { ext_fn, args },
628            } => Ok(CedarValueJson::ExtnEscape {
629                __extn: FnAndArgs::Multi {
630                    ext_fn,
631                    args: args
632                        .into_iter()
633                        .map(|arg| arg.sub_entity_literals(mapping))
634                        .collect::<Result<Vec<_>, _>>()?,
635                },
636            }),
637            v @ CedarValueJson::Bool(_) => Ok(v),
638            v @ CedarValueJson::Long(_) => Ok(v),
639            v @ CedarValueJson::String(_) => Ok(v),
640            CedarValueJson::Set(v) => Ok(CedarValueJson::Set(
641                v.into_iter()
642                    .map(|e| e.sub_entity_literals(mapping))
643                    .collect::<Result<Vec<_>, _>>()?,
644            )),
645            CedarValueJson::Record(r) => {
646                let mut new_m = BTreeMap::new();
647                for (k, v) in r.values {
648                    new_m.insert(k, v.sub_entity_literals(mapping)?);
649                }
650                Ok(CedarValueJson::Record(JsonRecord { values: new_m }))
651            }
652            v @ CedarValueJson::Null => Ok(v),
653        }
654    }
655}
656
657/// helper function to check if the given keys contain any reserved keys,
658/// throwing an appropriate `JsonSerializationError` if so
659fn check_for_reserved_keys<'a>(
660    mut keys: impl Iterator<Item = &'a SmolStr>,
661) -> Result<(), JsonSerializationError> {
662    // We could be a little more permissive here, but to be
663    // conservative, we throw an error for any record that contains
664    // any key with a reserved name, not just single-key records
665    // with the reserved names.
666    let reserved_keys: HashSet<&str> = HashSet::from_iter(["__entity", "__extn", "__expr"]);
667    let collision = keys.find(|k| reserved_keys.contains(k.as_str()));
668    match collision {
669        Some(collision) => Err(JsonSerializationError::reserved_key(collision.clone())),
670        None => Ok(()),
671    }
672}
673
674impl FnAndArgs {
675    /// Convert this `FnAndArg` into a Cedar "restricted expression" (which will be a call to an extension constructor)
676    pub fn into_expr(
677        self,
678        ctx: &dyn Fn() -> JsonDeserializationErrorContext,
679    ) -> Result<RestrictedExpr, JsonDeserializationError> {
680        let ext_fn = self.fn_str();
681        let args = self.args();
682        Ok(RestrictedExpr::call_extension_fn(
683            Name::from_normalized_str(ext_fn).map_err(|errs| {
684                JsonDeserializationError::parse_escape(EscapeKind::Extension, ext_fn, errs)
685            })?,
686            args.iter()
687                .map(|arg| CedarValueJson::into_expr(arg.clone(), ctx))
688                .collect::<Result<Vec<_>, _>>()?,
689        ))
690    }
691}
692
693/// Struct used to parse Cedar values from JSON.
694#[derive(Debug, Clone)]
695pub struct ValueParser<'e> {
696    /// Extensions which are active for the JSON parsing.
697    extensions: &'e Extensions<'e>,
698}
699
700impl<'e> ValueParser<'e> {
701    /// Create a new `ValueParser`.
702    pub fn new(extensions: &'e Extensions<'e>) -> Self {
703        Self { extensions }
704    }
705
706    /// internal function that converts a Cedar value (in JSON) into a
707    /// `RestrictedExpr`. Performs schema-based parsing if `expected_ty` is
708    /// provided. This does not mean that this function fully validates the
709    /// value against `expected_ty` -- it does not.
710    pub fn val_into_restricted_expr(
711        &self,
712        val: serde_json::Value,
713        expected_ty: Option<&SchemaType>,
714        ctx: &dyn Fn() -> JsonDeserializationErrorContext,
715    ) -> Result<RestrictedExpr, JsonDeserializationError> {
716        // First we have to check if we've been given an Unknown. This is valid
717        // regardless of the expected type (see #418).
718        let parse_as_unknown = |val: serde_json::Value| {
719            let extjson: ExtnValueJson = serde_json::from_value(val).ok()?;
720            match extjson {
721                ExtnValueJson::ExplicitExtnEscape {
722                    __extn: FnAndArgs::Single { ext_fn, arg },
723                } if ext_fn == "unknown" => {
724                    let arg = arg.into_expr(ctx).ok()?;
725                    let name = arg.as_string()?;
726                    Some(RestrictedExpr::unknown(Unknown::new_untyped(name.clone())))
727                }
728                _ => 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.
729            }
730        };
731        if let Some(rexpr) = parse_as_unknown(val.clone()) {
732            return Ok(rexpr);
733        }
734        // otherwise, we do normal schema-based parsing based on the expected type.
735        match expected_ty {
736            // The expected type is an entity reference. Special parsing rules
737            // apply: for instance, the `__entity` escape can optionally be omitted.
738            // What this means is that we parse the contents as `EntityUidJson`, and
739            // then convert that into an entity reference `RestrictedExpr`
740            Some(SchemaType::Entity { .. }) => {
741                let uidjson: EntityUidJson = serde_json::from_value(val)?;
742                Ok(RestrictedExpr::val(uidjson.into_euid(ctx)?))
743            }
744            // The expected type is an extension type. Special parsing rules apply:
745            // for instance, the `__extn` escape can optionally be omitted. What
746            // this means is that we parse the contents as `ExtnValueJson`, and then
747            // convert that into an extension-function-call `RestrictedExpr`
748            Some(SchemaType::Extension { ref name, .. }) => {
749                let extjson: ExtnValueJson = serde_json::from_value(val)?;
750                match extjson {
751                    ExtnValueJson::ExplicitExprEscape { .. } => {
752                        Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
753                    }
754                    ExtnValueJson::ExplicitExtnEscape { __extn }
755                    | ExtnValueJson::ImplicitExtnEscape(__extn) => {
756                        let func = self.extensions.func(
757                            &Name::from_normalized_str(__extn.fn_str()).map_err(|errs| {
758                                JsonDeserializationError::parse_escape(
759                                    EscapeKind::Extension,
760                                    __extn.fn_str(),
761                                    errs,
762                                )
763                            })?,
764                        )?;
765                        let arg_types = func.arg_types();
766                        let args = __extn.args();
767                        if args.len() != arg_types.len() {
768                            return Err(JsonDeserializationError::incorrect_num_of_arguments(
769                                arg_types.len(),
770                                args.len(),
771                                __extn.fn_str(),
772                            ));
773                        }
774                        Ok(RestrictedExpr::call_extension_fn(
775                            func.name().clone(),
776                            arg_types
777                                .iter()
778                                .zip(args.iter())
779                                .map(|(arg_type, arg)| {
780                                    // We need to recur here because there
781                                    // could be arguments of non-primitive
782                                    // types like `datetime`, which could be
783                                    // expressed using `ImplicitExtnEscape` or
784                                    // `ImplicitConstructor`
785                                    self.val_into_restricted_expr(
786                                        // So, we have to serialize
787                                        // `CedarValueJson` here
788                                        serde_json::to_value(arg)?,
789                                        Some(arg_type),
790                                        ctx,
791                                    )
792                                })
793                                .collect::<Result<Vec<_>, _>>()?,
794                        ))
795                    }
796                    ExtnValueJson::ImplicitConstructor(val) => {
797                        let expected_return_type = SchemaType::Extension { name: name.clone() };
798                        // Unfortunately, we can only allow one argument
799                        // constructor here because it's impossible to
800                        // distinguish two cases where there are
801                        // multiple arguments and where there is one
802                        // argument of a set type
803                        if let Some(constructor) = self
804                            .extensions
805                            .lookup_single_arg_constructor(&expected_return_type)
806                        {
807                            #[expect(
808                                clippy::indexing_slicing,
809                                reason = "we've concluded above that it has one arugment"
810                            )]
811                            Ok(RestrictedExpr::call_extension_fn(
812                                constructor.name().clone(),
813                                std::iter::once(self.val_into_restricted_expr(
814                                    serde_json::to_value(val)?,
815                                    Some(&constructor.arg_types()[0]),
816                                    ctx,
817                                )?),
818                            ))
819                        } else {
820                            Err(JsonDeserializationError::missing_implied_constructor(
821                                ctx(),
822                                expected_return_type,
823                            ))
824                        }
825                    }
826                }
827            }
828            // The expected type is a set type. No special parsing rules apply, but
829            // we need to parse the elements according to the expected element type
830            Some(expected_ty @ SchemaType::Set { element_ty }) => match val {
831                serde_json::Value::Array(elements) => Ok(RestrictedExpr::set(
832                    elements
833                        .into_iter()
834                        .map(|element| {
835                            self.val_into_restricted_expr(element, Some(element_ty), ctx)
836                        })
837                        .collect::<Result<Vec<RestrictedExpr>, JsonDeserializationError>>()?,
838                )),
839                val => {
840                    let actual_val = {
841                        let jvalue: CedarValueJson = serde_json::from_value(val)?;
842                        jvalue.into_expr(ctx)?
843                    };
844                    let err = TypeMismatchError::type_mismatch(
845                        expected_ty.clone(),
846                        actual_val.try_type_of(self.extensions),
847                        actual_val,
848                    );
849                    match ctx() {
850                        JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
851                            Err(JsonDeserializationError::EntitySchemaConformance(
852                                EntitySchemaConformanceError::type_mismatch(
853                                    uid,
854                                    attr,
855                                    crate::entities::conformance::err::AttrOrTag::Attr,
856                                    err,
857                                ),
858                            ))
859                        }
860                        ctx => Err(JsonDeserializationError::type_mismatch(ctx, err)),
861                    }
862                }
863            },
864            // The expected type is a record type. No special parsing rules
865            // apply, but we need to parse the attribute values according to
866            // their expected element types
867            Some(
868                expected_ty @ SchemaType::Record {
869                    attrs: expected_attrs,
870                    open_attrs,
871                },
872            ) => match val {
873                serde_json::Value::Object(mut actual_attrs) => {
874                    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`
875                    let rexpr_pairs = expected_attrs
876                        .iter()
877                        .filter_map(move |(k, expected_attr_ty)| {
878                            match mut_actual_attrs.remove(k.as_str()) {
879                                Some(actual_attr) => {
880                                    match self.val_into_restricted_expr(actual_attr, Some(expected_attr_ty.schema_type()), ctx) {
881                                        Ok(actual_attr) => Some(Ok((k.clone(), actual_attr))),
882                                        Err(e) => Some(Err(e)),
883                                    }
884                                }
885                                None if expected_attr_ty.is_required() => Some(Err(JsonDeserializationError::missing_required_record_attr(ctx(), k.clone()))),
886                                None => None,
887                            }
888                        })
889                        .collect::<Result<Vec<(SmolStr, RestrictedExpr)>, JsonDeserializationError>>()?;
890
891                    if !open_attrs {
892                        // we've now checked that all expected attrs exist, and removed them from `actual_attrs`.
893                        // we still need to verify that we didn't have any unexpected attrs.
894                        if let Some((record_attr, _)) = actual_attrs.into_iter().next() {
895                            return Err(JsonDeserializationError::unexpected_record_attr(
896                                ctx(),
897                                record_attr,
898                            ));
899                        }
900                    }
901
902                    // having duplicate keys should be impossible here (because
903                    // neither `actual_attrs` nor `expected_attrs` can have
904                    // duplicate keys; they're both maps), but we can still throw
905                    // the error properly in the case that it somehow happens
906                    RestrictedExpr::record(rexpr_pairs).map_err(|e| match e {
907                        ExpressionConstructionError::DuplicateKey(
908                            expression_construction_errors::DuplicateKeyError { key, .. },
909                        ) => JsonDeserializationError::duplicate_key(ctx(), key),
910                    })
911                }
912                val => {
913                    let actual_val = {
914                        let jvalue: CedarValueJson = serde_json::from_value(val)?;
915                        jvalue.into_expr(ctx)?
916                    };
917                    let err = TypeMismatchError::type_mismatch(
918                        expected_ty.clone(),
919                        actual_val.try_type_of(self.extensions),
920                        actual_val,
921                    );
922                    match ctx() {
923                        JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
924                            Err(JsonDeserializationError::EntitySchemaConformance(
925                                EntitySchemaConformanceError::type_mismatch(
926                                    uid,
927                                    attr,
928                                    crate::entities::conformance::err::AttrOrTag::Attr,
929                                    err,
930                                ),
931                            ))
932                        }
933                        ctx => Err(JsonDeserializationError::type_mismatch(ctx, err)),
934                    }
935                }
936            },
937            // The expected type is any other type, or we don't have an expected type.
938            // No special parsing rules apply; we do ordinary, non-schema-based parsing.
939            Some(_) | None => {
940                // Everything is parsed as `CedarValueJson`, and converted into
941                // `RestrictedExpr` from that.
942                let jvalue: CedarValueJson = serde_json::from_value(val)?;
943                Ok(jvalue.into_expr(ctx)?)
944            }
945        }
946    }
947}
948
949/// A (optional) static context for deserialization of entity uids
950/// This is useful when, for plumbing reasons, we can't get the appopriate values into the dynamic
951/// context. Primary use case is in the [`DeserializeAs`] trait.
952pub trait DeserializationContext {
953    /// Access the (optional) static context.
954    /// If returns [`None`], use the dynamic context.
955    fn static_context() -> Option<JsonDeserializationErrorContext>;
956}
957
958/// A [`DeserializationContext`] that always returns [`None`].
959/// This is the default behaviour,
960#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
961pub struct NoStaticContext;
962
963impl DeserializationContext for NoStaticContext {
964    fn static_context() -> Option<JsonDeserializationErrorContext> {
965        None
966    }
967}
968
969/// Serde JSON format for Cedar values where we know we're expecting an entity
970/// reference
971#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
972#[serde(untagged)]
973#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
974#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
975pub enum EntityUidJson<Context = NoStaticContext> {
976    /// This was removed in 3.0 and is only here for generating nice error messages.
977    ExplicitExprEscape {
978        /// Contents are ignored.
979        #[cfg_attr(feature = "wasm", tsify(type = "__skip"))]
980        __expr: String,
981        /// Phantom value for the `Context` type parameter
982        #[serde(skip)]
983        context: std::marker::PhantomData<Context>,
984    },
985    /// Explicit `__entity` escape; see notes on `CedarValueJson::EntityEscape`
986    ExplicitEntityEscape {
987        /// JSON object containing the entity type and ID
988        __entity: TypeAndId,
989    },
990    /// Implicit `__entity` escape, in which case we'll see just the TypeAndId
991    /// structure
992    ImplicitEntityEscape(TypeAndId),
993
994    /// Implicit catch-all case for error handling
995    FoundValue(#[cfg_attr(feature = "wasm", tsify(type = "__skip"))] serde_json::Value),
996}
997
998impl<'de, C: DeserializationContext> DeserializeAs<'de, EntityUID> for EntityUidJson<C> {
999    fn deserialize_as<D>(deserializer: D) -> Result<EntityUID, D::Error>
1000    where
1001        D: serde::Deserializer<'de>,
1002    {
1003        use serde::de::Error;
1004        // We don't know the context that called us, so we'll rely on the statically set context
1005        let context = || JsonDeserializationErrorContext::Unknown;
1006        let s = EntityUidJson::<C>::deserialize(deserializer)?;
1007        let euid = s.into_euid(&context).map_err(Error::custom)?;
1008        Ok(euid)
1009    }
1010}
1011
1012impl<C> SerializeAs<EntityUID> for EntityUidJson<C> {
1013    fn serialize_as<S>(source: &EntityUID, serializer: S) -> Result<S::Ok, S::Error>
1014    where
1015        S: serde::Serializer,
1016    {
1017        let json: EntityUidJson = source.clone().into();
1018        json.serialize(serializer)
1019    }
1020}
1021
1022impl<C: DeserializationContext> EntityUidJson<C> {
1023    /// Construct an `EntityUidJson` from entity type name and eid.
1024    ///
1025    /// This will use the `ImplicitEntityEscape` form, if it matters.
1026    pub fn new(entity_type: impl Into<SmolStr>, id: impl Into<SmolStr>) -> Self {
1027        Self::ImplicitEntityEscape(TypeAndId {
1028            entity_type: entity_type.into(),
1029            id: id.into(),
1030        })
1031    }
1032
1033    /// Convert this `EntityUidJson` into an `EntityUID`
1034    pub fn into_euid(
1035        self,
1036        dynamic_ctx: &dyn Fn() -> JsonDeserializationErrorContext,
1037    ) -> Result<EntityUID, JsonDeserializationError> {
1038        let ctx = &|| C::static_context().unwrap_or_else(&dynamic_ctx);
1039        match self {
1040            Self::ExplicitEntityEscape { __entity } | Self::ImplicitEntityEscape(__entity) => {
1041                // reuse the same logic that parses CedarValueJson
1042                let jvalue = CedarValueJson::EntityEscape { __entity };
1043                let expr = jvalue.into_expr(ctx)?;
1044                match expr.expr_kind() {
1045                    ExprKind::Lit(Literal::EntityUID(euid)) => Ok((**euid).clone()),
1046                    _ => Err(JsonDeserializationError::expected_entity_ref(
1047                        ctx(),
1048                        Either::Right(expr.clone().into()),
1049                    )),
1050                }
1051            }
1052            Self::FoundValue(v) => Err(JsonDeserializationError::expected_entity_ref(
1053                ctx(),
1054                Either::Left(v),
1055            )),
1056            Self::ExplicitExprEscape { __expr, .. } => {
1057                Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
1058            }
1059        }
1060    }
1061}
1062
1063/// Convert an `EntityUID` to `EntityUidJson`, using the `ExplicitEntityEscape` option
1064impl From<EntityUID> for EntityUidJson {
1065    fn from(uid: EntityUID) -> EntityUidJson {
1066        EntityUidJson::ExplicitEntityEscape {
1067            __entity: uid.into(),
1068        }
1069    }
1070}
1071
1072/// Convert an `EntityUID` to `EntityUidJson`, using the `ExplicitEntityEscape` option
1073impl From<&EntityUID> for EntityUidJson {
1074    fn from(uid: &EntityUID) -> EntityUidJson {
1075        EntityUidJson::ExplicitEntityEscape {
1076            __entity: uid.into(),
1077        }
1078    }
1079}
1080
1081/// Serde JSON format for Cedar values where we know we're expecting an
1082/// extension value
1083#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1084#[serde(untagged)]
1085pub enum ExtnValueJson {
1086    /// This was removed in 3.0 and is only here for generating nice error messages.
1087    ExplicitExprEscape {
1088        /// Contents are ignored.
1089        __expr: String,
1090    },
1091    /// Explicit `__extn` escape; see notes on `CedarValueJson::ExtnEscape`
1092    ExplicitExtnEscape {
1093        /// JSON object containing the extension-constructor call
1094        __extn: FnAndArgs,
1095    },
1096    /// Implicit `__extn` escape, in which case we'll just see the `FnAndArg`
1097    /// directly
1098    ImplicitExtnEscape(FnAndArgs),
1099    /// Implicit `__extn` escape and constructor. Constructor is implicitly
1100    /// selected based on the argument type and the expected type.
1101    //
1102    // This is listed last so that it has lowest priority when deserializing.
1103    // If one of the above forms fits, we use that.
1104    ImplicitConstructor(CedarValueJson),
1105}
1106
1107#[cfg(test)]
1108mod test {
1109    use super::*;
1110
1111    #[test]
1112    fn height_leaf() {
1113        assert_eq!(CedarValueJson::Bool(true).height(), 0);
1114        assert_eq!(CedarValueJson::Long(42).height(), 0);
1115        assert_eq!(CedarValueJson::String("hi".into()).height(), 0);
1116        assert_eq!(CedarValueJson::Null.height(), 0);
1117        assert_eq!(
1118            CedarValueJson::ExprEscape {
1119                __expr: "1 + 2".into()
1120            }
1121            .height(),
1122            0
1123        );
1124        assert_eq!(
1125            CedarValueJson::EntityEscape {
1126                __entity: TypeAndId {
1127                    entity_type: "User".into(),
1128                    id: "alice".into(),
1129                }
1130            }
1131            .height(),
1132            0
1133        );
1134        assert_eq!(CedarValueJson::Set(vec![]).height(), 0);
1135        assert_eq!(
1136            CedarValueJson::Record(vec![].into_iter().collect()).height(),
1137            0
1138        );
1139    }
1140
1141    #[test]
1142    fn height_set() {
1143        let val = CedarValueJson::Set(vec![CedarValueJson::Long(1), CedarValueJson::Long(2)]);
1144        assert_eq!(val.height(), 1);
1145    }
1146
1147    #[test]
1148    fn height_record() {
1149        let rec: JsonRecord = vec![("a".into(), CedarValueJson::Long(1))]
1150            .into_iter()
1151            .collect();
1152        assert_eq!(CedarValueJson::Record(rec).height(), 1);
1153    }
1154
1155    #[test]
1156    fn height_nested_set() {
1157        let inner = CedarValueJson::Set(vec![CedarValueJson::Long(1)]);
1158        let outer = CedarValueJson::Set(vec![inner]);
1159        assert_eq!(outer.height(), 2);
1160    }
1161
1162    #[test]
1163    fn height_asymmetric_set() {
1164        let val = CedarValueJson::Set(vec![
1165            CedarValueJson::Long(1),
1166            CedarValueJson::Set(vec![CedarValueJson::Long(2)]),
1167        ]);
1168        assert_eq!(val.height(), 2);
1169    }
1170
1171    #[test]
1172    fn height_extn_escape() {
1173        let val = CedarValueJson::ExtnEscape {
1174            __extn: FnAndArgs::Single {
1175                ext_fn: "decimal".into(),
1176                arg: Box::new(CedarValueJson::String("1.0".into())),
1177            },
1178        };
1179        assert_eq!(val.height(), 1);
1180    }
1181
1182    #[test]
1183    fn height_extn_escape_multi() {
1184        let val = CedarValueJson::ExtnEscape {
1185            __extn: FnAndArgs::Multi {
1186                ext_fn: "foo".into(),
1187                args: vec![CedarValueJson::Long(1), CedarValueJson::Long(2)],
1188            },
1189        };
1190        assert_eq!(val.height(), 1);
1191    }
1192
1193    #[test]
1194    fn height_nested_composites() {
1195        // Record containing a Set containing an ExtnEscape
1196        let extn = CedarValueJson::ExtnEscape {
1197            __extn: FnAndArgs::Single {
1198                ext_fn: "decimal".into(),
1199                arg: Box::new(CedarValueJson::String("1.0".into())),
1200            },
1201        };
1202        let set = CedarValueJson::Set(vec![extn]);
1203        let rec: JsonRecord = vec![("a".into(), set)].into_iter().collect();
1204        let val = CedarValueJson::Record(rec);
1205        assert_eq!(val.height(), 3);
1206    }
1207}