cedar_policy_core/entities/json/
jsonvalue.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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    AttributeType, JsonDeserializationError, JsonDeserializationErrorContext,
19    JsonSerializationError, SchemaType,
20};
21use crate::ast::{
22    BorrowedRestrictedExpr, Eid, EntityUID, Expr, ExprKind, Literal, Name, RestrictedExpr,
23};
24use crate::extensions::{Extensions, ExtensionsError};
25use crate::FromNormalizedStr;
26use serde::{Deserialize, Serialize};
27use smol_str::SmolStr;
28use std::collections::{HashMap, HashSet};
29
30/// The canonical JSON representation of a Cedar value.
31/// Many Cedar values have a natural one-to-one mapping to and from JSON values.
32/// Cedar values of some types, like entity references or extension values,
33/// cannot easily be represented in JSON and thus are represented using the
34/// `__expr`, `__entity`, or `__extn` escapes.
35///
36/// For example, this is the JSON format for attribute values expected by
37/// `EntityJsonParser`, when schema-based parsing is not used.
38#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
39#[serde(untagged)]
40pub enum JSONValue {
41    /// Special JSON object with single reserved "__expr" key:
42    /// interpret the following string as a (restricted) Cedar expression.
43    /// Some escape (this or the following ones) is necessary for extension
44    /// values and entity references, but this `__expr` escape could also be
45    /// used for any other values.
46    ///
47    /// `__expr` is deprecated (starting with the 1.2 release) and will be
48    /// removed in favor of `__entity` and `__extn`, which together cover all of
49    /// the use-cases where `__expr` would have been necessary.
50    //
51    // listed before `Record` so that it takes priority: otherwise, the escape
52    // would be interpreted as a Record with a key "__expr". see docs on
53    // `serde(untagged)`
54    ExprEscape {
55        /// String to interpret as a (restricted) Cedar expression
56        __expr: SmolStr,
57    },
58    /// Special JSON object with single reserved "__entity" key:
59    /// the following item should be a JSON object of the form
60    /// `{ "type": "xxx", "id": "yyy" }`.
61    /// Some escape (this or `__expr`, which is deprecated) is necessary for
62    /// entity references.
63    //
64    // listed before `Record` so that it takes priority: otherwise, the escape
65    // would be interpreted as a Record with a key "__entity". see docs on
66    // `serde(untagged)`
67    EntityEscape {
68        /// JSON object containing the entity type and ID
69        __entity: TypeAndId,
70    },
71    /// Special JSON object with single reserved "__extn" key:
72    /// the following item should be a JSON object of the form
73    /// `{ "fn": "xxx", "arg": "yyy" }`.
74    /// Some escape (this or `__expr`, which is deprecated) is necessary for
75    /// extension values.
76    //
77    // listed before `Record` so that it takes priority: otherwise, the escape
78    // would be interpreted as a Record with a key "__extn". see docs on
79    // `serde(untagged)`
80    ExtnEscape {
81        /// JSON object containing the extension-constructor call
82        __extn: FnAndArg,
83    },
84    /// JSON bool => Cedar bool
85    Bool(bool),
86    /// JSON int => Cedar long (64-bit signed integer)
87    Long(i64),
88    /// JSON string => Cedar string
89    String(SmolStr),
90    /// JSON list => Cedar set; can contain any JSONValues, even
91    /// heterogeneously
92    Set(Vec<JSONValue>),
93    /// JSON object => Cedar record; must have string keys, but values
94    /// can be any JSONValues, even heterogeneously
95    Record(HashMap<SmolStr, JSONValue>),
96}
97
98/// Structure expected by the `__entity` escape
99#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
100pub struct TypeAndId {
101    /// Entity typename
102    #[serde(rename = "type")]
103    entity_type: SmolStr,
104    /// Entity id
105    id: SmolStr,
106}
107
108impl From<EntityUID> for TypeAndId {
109    fn from(euid: EntityUID) -> TypeAndId {
110        let (entity_type, eid) = euid.components();
111        TypeAndId {
112            entity_type: entity_type.to_string().into(),
113            id: AsRef::<str>::as_ref(&eid).into(),
114        }
115    }
116}
117
118impl From<&EntityUID> for TypeAndId {
119    fn from(euid: &EntityUID) -> TypeAndId {
120        TypeAndId {
121            entity_type: euid.entity_type().to_string().into(),
122            id: AsRef::<str>::as_ref(&euid.eid()).into(),
123        }
124    }
125}
126
127impl TryFrom<TypeAndId> for EntityUID {
128    type Error = Vec<crate::parser::err::ParseError>;
129
130    fn try_from(e: TypeAndId) -> Result<EntityUID, Self::Error> {
131        Ok(EntityUID::from_components(
132            Name::from_normalized_str(&e.entity_type)?,
133            Eid::new(e.id),
134        ))
135    }
136}
137
138/// Structure expected by the `__extn` escape
139#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
140pub struct FnAndArg {
141    /// Extension constructor function
142    #[serde(rename = "fn")]
143    ext_fn: SmolStr,
144    /// Argument to that constructor
145    arg: Box<JSONValue>,
146}
147
148impl JSONValue {
149    /// Encode the given `EntityUID` as a `JSONValue`
150    pub fn uid(euid: &EntityUID) -> Self {
151        Self::EntityEscape {
152            __entity: TypeAndId::from(euid.clone()),
153        }
154    }
155
156    /// Convert this JSONValue into a Cedar "restricted expression"
157    pub fn into_expr(self) -> Result<RestrictedExpr, JsonDeserializationError> {
158        match self {
159            Self::Bool(b) => Ok(RestrictedExpr::val(b)),
160            Self::Long(i) => Ok(RestrictedExpr::val(i)),
161            Self::String(s) => Ok(RestrictedExpr::val(s)),
162            Self::Set(vals) => Ok(RestrictedExpr::set(
163                vals.into_iter()
164                    .map(JSONValue::into_expr)
165                    .collect::<Result<Vec<_>, _>>()?,
166            )),
167            Self::Record(map) => Ok(RestrictedExpr::record(
168                map.into_iter()
169                    .map(|(k, v)| Ok((k, v.into_expr()?)))
170                    .collect::<Result<Vec<_>, JsonDeserializationError>>()?,
171            )),
172            Self::ExprEscape { __expr: expr } => {
173                use crate::parser;
174                let expr: Expr = parser::parse_expr(&expr).map_err(|errs| {
175                    JsonDeserializationError::ExprParseError(parser::err::ParseError::WithContext {
176                        context: format!(
177                            "contents of __expr escape {} are not a valid Cedar expression",
178                            expr
179                        ),
180                        errs,
181                    })
182                })?;
183                Ok(RestrictedExpr::new(expr)?)
184            }
185            Self::EntityEscape { __entity: entity } => {
186                use crate::parser;
187                Ok(RestrictedExpr::val(
188                    EntityUID::try_from(entity.clone()).map_err(|errs| {
189                        JsonDeserializationError::EntityParseError(
190                            parser::err::ParseError::WithContext {
191                                context: format!(
192                                    "contents of __entity escape {} do not make a valid entity reference",
193                                    serde_json::to_string_pretty(&entity).unwrap_or_else(|_| format!("{:?}", &entity))
194                                ),
195                                errs,
196                            },
197                        )
198                    })?,
199                ))
200            }
201            Self::ExtnEscape { __extn: extn } => extn.into_expr(),
202        }
203    }
204
205    /// Convert a Cedar "restricted expression" into a `JSONValue`.
206    pub fn from_expr(expr: BorrowedRestrictedExpr<'_>) -> Result<Self, JsonSerializationError> {
207        match expr.as_ref().expr_kind() {
208            ExprKind::Lit(lit) => Ok(Self::from_lit(lit.clone())),
209            ExprKind::ExtensionFunctionApp { fn_name, args } => match args.len() {
210                0 => Err(JsonSerializationError::ExtnCall0Arguments {
211                    func: fn_name.clone(),
212                }),
213                // PANIC SAFETY. We've checked that `args` is of length 1, fine to index at 0
214                #[allow(clippy::indexing_slicing)]
215                1 => Ok(Self::ExtnEscape {
216                    __extn: FnAndArg {
217                        ext_fn: fn_name.to_string().into(),
218                        arg: Box::new(JSONValue::from_expr(
219                            BorrowedRestrictedExpr::new_unchecked(
220                                // assuming the invariant holds for `expr`, it must also hold here
221                                &args[0], // checked above that |args| == 1
222                            ),
223                        )?),
224                    },
225                }),
226                _ => Err(JsonSerializationError::ExtnCall2OrMoreArguments {
227                    func: fn_name.clone(),
228                }),
229            },
230            ExprKind::Set(exprs) => Ok(Self::Set(
231                exprs
232                    .iter()
233                    .map(BorrowedRestrictedExpr::new_unchecked) // assuming the invariant holds for `expr`, it must also hold here
234                    .map(JSONValue::from_expr)
235                    .collect::<Result<_, JsonSerializationError>>()?,
236            )),
237            ExprKind::Record { pairs } => {
238                // if `pairs` contains a key which collides with one of our JSON
239                // escapes, then we have a problem because it would be interpreted
240                // as an escape when being read back in.
241                // We could be a little more permissive here, but to be
242                // conservative, we throw an error for any record that contains
243                // any key with a reserved name, not just single-key records
244                // with the reserved names.
245                let reserved_keys: HashSet<&str> =
246                    HashSet::from_iter(["__entity", "__extn", "__expr"]);
247                let collision = pairs
248                    .iter()
249                    .find(|(k, _)| reserved_keys.contains(k.as_str()));
250                if let Some(collision) = collision {
251                    Err(JsonSerializationError::ReservedKey {
252                        key: collision.0.clone(),
253                    })
254                } else {
255                    // the common case: the record doesn't use any reserved keys
256                    Ok(Self::Record(
257                        pairs
258                            .iter()
259                            .map(|(k, v)| {
260                                Ok((
261                                    k.clone(),
262                                    JSONValue::from_expr(BorrowedRestrictedExpr::new_unchecked(v))?, // assuming the invariant holds for `expr`, it must also hold here
263                                ))
264                            })
265                            .collect::<Result<_, JsonSerializationError>>()?,
266                    ))
267                }
268            }
269            kind => {
270                Err(JsonSerializationError::UnexpectedRestrictedExprKind { kind: kind.clone() })
271            }
272        }
273    }
274
275    /// Convert a Cedar literal into a `JSONValue`.
276    pub fn from_lit(lit: Literal) -> Self {
277        match lit {
278            Literal::Bool(b) => Self::Bool(b),
279            Literal::Long(i) => Self::Long(i),
280            Literal::String(s) => Self::String(s),
281            Literal::EntityUID(euid) => Self::EntityEscape {
282                __entity: (*euid).clone().into(),
283            },
284        }
285    }
286}
287
288impl FnAndArg {
289    /// Convert this `FnAndArg` into a Cedar "restricted expression" (which will be a call to an extension constructor)
290    pub fn into_expr(self) -> Result<RestrictedExpr, JsonDeserializationError> {
291        use crate::parser;
292        Ok(RestrictedExpr::call_extension_fn(
293            Name::from_normalized_str(&self.ext_fn).map_err(|errs| {
294                JsonDeserializationError::ExtnParseError(parser::err::ParseError::WithContext {
295                    context: format!(
296                        "in __extn escape, {:?} is not a valid function name",
297                        &self.ext_fn,
298                    ),
299                    errs,
300                })
301            })?,
302            vec![JSONValue::into_expr(*self.arg)?],
303        ))
304    }
305}
306
307/// Struct used to parse Cedar values from JSON.
308#[derive(Debug, Clone)]
309pub struct ValueParser<'e> {
310    /// Extensions which are active for the JSON parsing.
311    extensions: Extensions<'e>,
312}
313
314impl<'e> ValueParser<'e> {
315    /// Create a new `ValueParser`.
316    pub fn new(extensions: Extensions<'e>) -> Self {
317        Self { extensions }
318    }
319
320    /// internal function that converts a Cedar value (in JSON) into a
321    /// `RestrictedExpr`. Performs schema-based parsing if `expected_ty` is
322    /// provided.
323    pub fn val_into_rexpr(
324        &self,
325        val: serde_json::Value,
326        expected_ty: Option<&SchemaType>,
327        ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
328    ) -> Result<RestrictedExpr, JsonDeserializationError> {
329        match expected_ty {
330            None => {
331                // ordinary, non-schema-based parsing. Everything is parsed as
332                // `JSONValue`, and converted into `RestrictedExpr` from that.
333                let jvalue: JSONValue = serde_json::from_value(val)?;
334                jvalue.into_expr()
335            }
336            // The expected type is an entity reference. Special parsing rules
337            // apply: for instance, the `__entity` escape can optionally be omitted.
338            // What this means is that we parse the contents as `EntityUidJSON`, and
339            // then convert that into an entity reference `RestrictedExpr`
340            Some(SchemaType::Entity { .. }) => {
341                let uidjson: EntityUidJSON = serde_json::from_value(val)?;
342                Ok(RestrictedExpr::val(uidjson.into_euid(ctx)?))
343            }
344            // The expected type is an extension type. Special parsing rules apply:
345            // for instance, the `__extn` escape can optionally be omitted. What
346            // this means is that we parse the contents as `ExtnValueJSON`, and then
347            // convert that into an extension-function-call `RestrictedExpr`
348            Some(SchemaType::Extension { ref name, .. }) => {
349                let extjson: ExtnValueJSON = serde_json::from_value(val)?;
350                self.extn_value_json_into_rexpr(extjson, name.clone(), ctx)
351            }
352            // The expected type is a set type. No special parsing rules apply, but
353            // we need to parse the elements according to the expected element type
354            Some(expected_ty @ SchemaType::Set { element_ty }) => match val {
355                serde_json::Value::Array(elements) => Ok(RestrictedExpr::set(
356                    elements
357                        .into_iter()
358                        .map(|element| self.val_into_rexpr(element, Some(element_ty), ctx.clone()))
359                        .collect::<Result<Vec<RestrictedExpr>, JsonDeserializationError>>()?,
360                )),
361                _ => Err(JsonDeserializationError::TypeMismatch {
362                    ctx: Box::new(ctx()),
363                    expected: Box::new(expected_ty.clone()),
364                    actual: {
365                        let jvalue: JSONValue = serde_json::from_value(val)?;
366                        Box::new(self.type_of_rexpr(jvalue.into_expr()?.as_borrowed(), ctx)?)
367                    },
368                }),
369            },
370            // The expected type is a record type. No special parsing rules
371            // apply, but we need to parse the attribute values according to
372            // their expected element types
373            Some(
374                expected_ty @ SchemaType::Record {
375                    attrs: expected_attrs,
376                },
377            ) => match val {
378                serde_json::Value::Object(mut actual_attrs) => {
379                    let ctx2 = ctx.clone(); // for borrow-check, so the original `ctx` can be moved into the closure below
380                    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`
381                    let rexpr_pairs = expected_attrs
382                        .iter()
383                        .filter_map(move |(k, expected_attr_ty)| {
384                            match mut_actual_attrs.remove(k.as_str()) {
385                                Some(actual_attr) => {
386                                    match self.val_into_rexpr(actual_attr, Some(expected_attr_ty.schema_type()), ctx.clone()) {
387                                        Ok(actual_attr) => Some(Ok((k.clone(), actual_attr))),
388                                        Err(e) => Some(Err(e)),
389                                    }
390                                }
391                                None if expected_attr_ty.is_required() => Some(Err(JsonDeserializationError::MissingRequiredRecordAttr {
392                                    ctx: Box::new(ctx()),
393                                    record_attr: k.clone(),
394                                })),
395                                None => None,
396                            }
397                        })
398                        .collect::<Result<Vec<(SmolStr, RestrictedExpr)>, JsonDeserializationError>>()?;
399                    // we've now checked that all expected attrs exist, and removed them from `actual_attrs`.
400                    // we still need to verify that we didn't have any unexpected attrs.
401                    if let Some((record_attr, _)) = actual_attrs.into_iter().next() {
402                        return Err(JsonDeserializationError::UnexpectedRecordAttr {
403                            ctx: Box::new(ctx2()),
404                            record_attr: record_attr.into(),
405                        });
406                    }
407                    Ok(RestrictedExpr::record(rexpr_pairs))
408                }
409                _ => Err(JsonDeserializationError::TypeMismatch {
410                    ctx: Box::new(ctx()),
411                    expected: Box::new(expected_ty.clone()),
412                    actual: {
413                        let jvalue: JSONValue = serde_json::from_value(val)?;
414                        Box::new(self.type_of_rexpr(jvalue.into_expr()?.as_borrowed(), ctx)?)
415                    },
416                }),
417            },
418            // The expected type is any other type. No special parsing rules apply,
419            // and we treat this exactly as the non-schema-based-parsing case.
420            Some(_) => {
421                let jvalue: JSONValue = serde_json::from_value(val)?;
422                jvalue.into_expr()
423            }
424        }
425    }
426
427    /// internal function that converts an `ExtnValueJSON` into a
428    /// `RestrictedExpr`, which will be an extension constructor call.
429    ///
430    /// `expected_typename`: Specific extension type that is expected.
431    fn extn_value_json_into_rexpr(
432        &self,
433        extnjson: ExtnValueJSON,
434        expected_typename: Name,
435        ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
436    ) -> Result<RestrictedExpr, JsonDeserializationError> {
437        match extnjson {
438            ExtnValueJSON::ExplicitExprEscape { __expr } => {
439                // reuse the same logic that parses JSONValue
440                let jvalue = JSONValue::ExprEscape { __expr };
441                let expr = jvalue.into_expr()?;
442                match expr.expr_kind() {
443                    ExprKind::ExtensionFunctionApp { .. } => Ok(expr),
444                    _ => Err(JsonDeserializationError::ExpectedExtnValue {
445                        ctx: Box::new(ctx()),
446                        got: Box::new(expr.clone().into()),
447                    }),
448                }
449            }
450            ExtnValueJSON::ExplicitExtnEscape { __extn }
451            | ExtnValueJSON::ImplicitExtnEscape(__extn) => {
452                // reuse the same logic that parses JSONValue
453                let jvalue = JSONValue::ExtnEscape { __extn };
454                let expr = jvalue.into_expr()?;
455                match expr.expr_kind() {
456                    ExprKind::ExtensionFunctionApp { .. } => Ok(expr),
457                    _ => Err(JsonDeserializationError::ExpectedExtnValue {
458                        ctx: Box::new(ctx()),
459                        got: Box::new(expr.clone().into()),
460                    }),
461                }
462            }
463            ExtnValueJSON::ImplicitConstructor(val) => {
464                let arg = val.into_expr()?;
465                let argty = self.type_of_rexpr(arg.as_borrowed(), ctx.clone())?;
466                let func = self
467                    .extensions
468                    .lookup_single_arg_constructor(
469                        &SchemaType::Extension {
470                            name: expected_typename.clone(),
471                        },
472                        &argty,
473                    )?
474                    .ok_or_else(|| JsonDeserializationError::ImpliedConstructorNotFound {
475                        ctx: Box::new(ctx()),
476                        return_type: Box::new(SchemaType::Extension {
477                            name: expected_typename,
478                        }),
479                        arg_type: Box::new(argty.clone()),
480                    })?;
481                Ok(RestrictedExpr::call_extension_fn(
482                    func.name().clone(),
483                    vec![arg],
484                ))
485            }
486        }
487    }
488
489    /// Get the `SchemaType` of a restricted expression.
490    ///
491    /// This isn't possible for general `Expr`s (without a Request, full schema,
492    /// etc), but is possible for restricted expressions, given the information
493    /// in `Extensions`.
494    pub fn type_of_rexpr(
495        &self,
496        rexpr: BorrowedRestrictedExpr<'_>,
497        ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
498    ) -> Result<SchemaType, JsonDeserializationError> {
499        match rexpr.expr_kind() {
500            ExprKind::Lit(Literal::Bool(_)) => Ok(SchemaType::Bool),
501            ExprKind::Lit(Literal::Long(_)) => Ok(SchemaType::Long),
502            ExprKind::Lit(Literal::String(_)) => Ok(SchemaType::String),
503            ExprKind::Lit(Literal::EntityUID(uid)) => Ok(SchemaType::Entity { ty: uid.entity_type().clone() }),
504            ExprKind::Set(elements) => {
505                let mut element_types = elements.iter().map(|el| {
506                    self.type_of_rexpr(BorrowedRestrictedExpr::new_unchecked(el), ctx.clone()) // assuming the invariant holds for the set as a whole, it will also hold for each element
507                });
508                match element_types.next() {
509                    None => Ok(SchemaType::EmptySet),
510                    Some(Err(e)) => Err(e),
511                    Some(Ok(element_ty)) => {
512                        let matches_element_ty = |ty: &Result<SchemaType, JsonDeserializationError>| matches!(ty, Ok(ty) if ty.is_consistent_with(&element_ty));
513                        let conflicting_ty = element_types.find(|ty| !matches_element_ty(ty));
514                        match conflicting_ty {
515                            None => Ok(SchemaType::Set { element_ty: Box::new(element_ty) }),
516                            Some(Ok(conflicting_ty)) =>
517                                Err(JsonDeserializationError::HeterogeneousSet {
518                                    ctx: Box::new(ctx()),
519                                    ty1: Box::new(element_ty),
520                                    ty2: Box::new(conflicting_ty),
521                                }),
522                            Some(Err(e)) => Err(e),
523                        }
524                    }
525                }
526            }
527            ExprKind::Record { pairs } => {
528                Ok(SchemaType::Record { attrs: {
529                    pairs.iter().map(|(k, v)| {
530                        let attr_type = self.type_of_rexpr(
531                            BorrowedRestrictedExpr::new_unchecked(v), // assuming the invariant holds for the record as a whole, it will also hold for each attribute value
532                            ctx.clone(),
533                        )?;
534                        // we can't know if the attribute is required or optional,
535                        // but marking it optional is more flexible -- allows the
536                        // attribute type to `is_consistent_with()` more types
537                        Ok((k.clone(), AttributeType::optional(attr_type)))
538                    }).collect::<Result<HashMap<_,_>, JsonDeserializationError>>()?
539                }})
540            }
541            ExprKind::ExtensionFunctionApp { fn_name, .. } => {
542                let efunc = self.extensions.func(fn_name)?;
543                Ok(efunc.return_type().cloned().ok_or_else(|| ExtensionsError::HasNoType {
544                    name: efunc.name().clone()
545                })?)
546            }
547            // PANIC SAFETY. Unreachable by invariant on restricted expressions
548            #[allow(clippy::unreachable)]
549            expr => unreachable!("internal invariant violation: BorrowedRestrictedExpr somehow contained this expr case: {expr:?}"),
550        }
551    }
552}
553
554/// Serde JSON format for Cedar values where we know we're expecting an entity
555/// reference
556#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
557#[serde(untagged)]
558pub enum EntityUidJSON {
559    /// Explicit `__expr` escape; see notes on JSONValue::ExprEscape.
560    ///
561    /// Deprecated since the 1.2 release; use
562    /// `{ "__entity": { "type": "...", "id": "..." } }` instead.
563    ExplicitExprEscape {
564        /// String to interpret as a (restricted) Cedar expression.
565        /// In this case, it must evaluate to an entity reference.
566        __expr: SmolStr,
567    },
568    /// Explicit `__entity` escape; see notes on JSONValue::EntityEscape
569    ExplicitEntityEscape {
570        /// JSON object containing the entity type and ID
571        __entity: TypeAndId,
572    },
573    /// Implicit `__expr` escape, in which case we'll just see a JSON string.
574    ///
575    /// Deprecated since the 1.2 release; use
576    /// `{ "type": "...", "id": "..." }` instead.
577    ImplicitExprEscape(SmolStr),
578    /// Implicit `__entity` escape, in which case we'll see just the TypeAndId
579    /// structure
580    ImplicitEntityEscape(TypeAndId),
581}
582
583/// Serde JSON format for Cedar values where we know we're expecting an
584/// extension value
585#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
586#[serde(untagged)]
587pub enum ExtnValueJSON {
588    /// Explicit `__expr` escape; see notes on JSONValue::ExprEscape.
589    ///
590    /// Deprecated since the 1.2 release; use
591    /// `{ "__extn": { "fn": "...", "arg": "..." } }` instead.
592    ExplicitExprEscape {
593        /// String to interpret as a (restricted) Cedar expression.
594        /// In this case, it must evaluate to an extension value.
595        __expr: SmolStr,
596    },
597    /// Explicit `__extn` escape; see notes on JSONValue::ExtnEscape
598    ExplicitExtnEscape {
599        /// JSON object containing the extension-constructor call
600        __extn: FnAndArg,
601    },
602    /// Implicit `__extn` escape, in which case we'll just see the `FnAndArg`
603    /// directly
604    ImplicitExtnEscape(FnAndArg),
605    /// Implicit `__extn` escape and constructor. Constructor is implicitly
606    /// selected based on the argument type and the expected type.
607    //
608    // This is listed last so that it has lowest priority when deserializing.
609    // If one of the above forms fits, we use that.
610    ImplicitConstructor(JSONValue),
611}
612
613impl EntityUidJSON {
614    /// Construct an `EntityUidJSON` from entity type name and EID.
615    ///
616    /// This will use the `ImplicitEntityEscape` form, if it matters.
617    pub fn new(entity_type: impl Into<SmolStr>, id: impl Into<SmolStr>) -> Self {
618        Self::ImplicitEntityEscape(TypeAndId {
619            entity_type: entity_type.into(),
620            id: id.into(),
621        })
622    }
623
624    /// Convert this `EntityUidJSON` into an `EntityUID`
625    pub fn into_euid(
626        self,
627        ctx: impl Fn() -> JsonDeserializationErrorContext,
628    ) -> Result<EntityUID, JsonDeserializationError> {
629        let is_implicit_expr = matches!(self, Self::ImplicitExprEscape(_));
630        match self {
631            Self::ExplicitExprEscape { __expr } | Self::ImplicitExprEscape(__expr) => {
632                // reuse the same logic that parses JSONValue
633                let jvalue = JSONValue::ExprEscape {
634                    __expr: __expr.clone(),
635                };
636                let expr = jvalue.into_expr().map_err(|e| {
637                    if is_implicit_expr {
638                        // in this case, the user provided a string that wasn't
639                        // an appropriate entity reference.
640                        // Perhaps they didn't realize they needed to provide an
641                        // entity reference at all, or perhaps they just had an
642                        // entity syntax error.
643                        // We'll give them the `ExpectedLiteralEntityRef` error
644                        // message instead of the `ExprParseError` error message,
645                        // as it's likely to be more helpful in my opinion
646                        // PANIC SAFETY: Every `String` can be turned into a restricted expression
647                        #[allow(clippy::unwrap_used)]
648                        JsonDeserializationError::ExpectedLiteralEntityRef {
649                            ctx: Box::new(ctx()),
650                            got: Box::new(JSONValue::String(__expr).into_expr().unwrap().into()),
651                        }
652                    } else {
653                        e
654                    }
655                })?;
656                match expr.expr_kind() {
657                    ExprKind::Lit(Literal::EntityUID(euid)) => Ok((**euid).clone()),
658                    _ => Err(JsonDeserializationError::ExpectedLiteralEntityRef {
659                        ctx: Box::new(ctx()),
660                        got: Box::new(expr.clone().into()),
661                    }),
662                }
663            }
664            Self::ExplicitEntityEscape { __entity } | Self::ImplicitEntityEscape(__entity) => {
665                // reuse the same logic that parses JSONValue
666                let jvalue = JSONValue::EntityEscape { __entity };
667                let expr = jvalue.into_expr()?;
668                match expr.expr_kind() {
669                    ExprKind::Lit(Literal::EntityUID(euid)) => Ok((**euid).clone()),
670                    _ => Err(JsonDeserializationError::ExpectedLiteralEntityRef {
671                        ctx: Box::new(ctx()),
672                        got: Box::new(expr.clone().into()),
673                    }),
674                }
675            }
676        }
677    }
678}
679
680/// Convert an EntityUID to EntityUidJSON, using the ExplicitEntityEscape option
681impl From<EntityUID> for EntityUidJSON {
682    fn from(uid: EntityUID) -> EntityUidJSON {
683        EntityUidJSON::ExplicitEntityEscape {
684            __entity: uid.into(),
685        }
686    }
687}
688
689/// Convert an EntityUID to EntityUidJSON, using the ExplicitEntityEscape option
690impl From<&EntityUID> for EntityUidJSON {
691    fn from(uid: &EntityUID) -> EntityUidJSON {
692        EntityUidJSON::ExplicitEntityEscape {
693            __entity: uid.into(),
694        }
695    }
696}