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