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}