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