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