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