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}