cedar_policy_core/entities/json/value.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 JsonDeserializationError, JsonDeserializationErrorContext, JsonSerializationError, SchemaType,
19};
20use crate::ast::{
21 BorrowedRestrictedExpr, Eid, EntityUID, ExprConstructionError, ExprKind, Literal, Name,
22 RestrictedExpr, Unknown, Value,
23};
24use crate::entities::{
25 schematype_of_restricted_expr, unwrap_or_clone, EntitySchemaConformanceError, EscapeKind,
26 GetSchemaTypeError, TypeMismatchError,
27};
28use crate::extensions::Extensions;
29use crate::FromNormalizedStr;
30use either::Either;
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33use smol_str::SmolStr;
34use std::collections::{BTreeMap, HashSet};
35
36/// The canonical JSON representation of a Cedar value.
37/// Many Cedar values have a natural one-to-one mapping to and from JSON values.
38/// Cedar values of some types, like entity references or extension values,
39/// cannot easily be represented in JSON and thus are represented using the
40/// `__entity`, or `__extn` escapes.
41///
42/// For example, this is the JSON format for attribute values expected by
43/// `EntityJsonParser`, when schema-based parsing is not used.
44#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
45#[serde(untagged)]
46pub enum CedarValueJson {
47 /// The `__expr` escape has been removed, but is still reserved in order to throw meaningful errors.
48 ExprEscape {
49 /// Contents, will be ignored and an error is thrown when attempting to parse this
50 __expr: SmolStr,
51 },
52 /// Special JSON object with single reserved "__entity" key:
53 /// the following item should be a JSON object of the form
54 /// `{ "type": "xxx", "id": "yyy" }`.
55 /// This escape is necessary for entity references.
56 //
57 // listed before `Record` so that it takes priority: otherwise, the escape
58 // would be interpreted as a Record with a key "__entity". see docs on
59 // `serde(untagged)`
60 EntityEscape {
61 /// JSON object containing the entity type and ID
62 __entity: TypeAndId,
63 },
64 /// Special JSON object with single reserved "__extn" key:
65 /// the following item should be a JSON object of the form
66 /// `{ "fn": "xxx", "arg": "yyy" }`.
67 /// This escape is necessary for extension values.
68 //
69 // listed before `Record` so that it takes priority: otherwise, the escape
70 // would be interpreted as a Record with a key "__extn". see docs on
71 // `serde(untagged)`
72 ExtnEscape {
73 /// JSON object containing the extension-constructor call
74 __extn: FnAndArg,
75 },
76 /// JSON bool => Cedar bool
77 Bool(bool),
78 /// JSON int => Cedar long (64-bit signed integer)
79 Long(i64),
80 /// JSON string => Cedar string
81 String(SmolStr),
82 /// JSON list => Cedar set; can contain any `CedarValueJson`s, even
83 /// heterogeneously
84 Set(Vec<CedarValueJson>),
85 /// JSON object => Cedar record; must have string keys, but values
86 /// can be any `CedarValueJson`s, even heterogeneously
87 Record(JsonRecord),
88}
89
90/// Structure representing a Cedar record in JSON
91#[serde_as]
92#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
93pub struct JsonRecord {
94 /// Cedar records must have string keys, but values can be any
95 /// `CedarValueJson`s, even heterogeneously
96 #[serde_as(as = "serde_with::MapPreventDuplicates<_, _>")]
97 #[serde(flatten)]
98 values: BTreeMap<SmolStr, CedarValueJson>,
99}
100
101impl IntoIterator for JsonRecord {
102 type Item = (SmolStr, CedarValueJson);
103 type IntoIter = <BTreeMap<SmolStr, CedarValueJson> as IntoIterator>::IntoIter;
104 fn into_iter(self) -> Self::IntoIter {
105 self.values.into_iter()
106 }
107}
108
109impl<'a> IntoIterator for &'a JsonRecord {
110 type Item = (&'a SmolStr, &'a CedarValueJson);
111 type IntoIter = <&'a BTreeMap<SmolStr, CedarValueJson> as IntoIterator>::IntoIter;
112 fn into_iter(self) -> Self::IntoIter {
113 self.values.iter()
114 }
115}
116
117// At this time, this doesn't check for duplicate keys upon constructing a
118// `JsonRecord` from an iterator.
119// As of this writing, we only construct `JsonRecord` from an iterator during
120// _serialization_, not _deserialization_, and we can assume that values being
121// serialized (i.e., coming from the Cedar engine itself) are already free of
122// duplicate keys.
123impl FromIterator<(SmolStr, CedarValueJson)> for JsonRecord {
124 fn from_iter<T: IntoIterator<Item = (SmolStr, CedarValueJson)>>(iter: T) -> Self {
125 Self {
126 values: BTreeMap::from_iter(iter),
127 }
128 }
129}
130
131impl JsonRecord {
132 /// Iterate over the (k, v) pairs in the record
133 pub fn iter(&self) -> impl Iterator<Item = (&'_ SmolStr, &'_ CedarValueJson)> {
134 self.values.iter()
135 }
136
137 /// Get the number of attributes in the record
138 pub fn len(&self) -> usize {
139 self.values.len()
140 }
141
142 /// Is the record empty (no attributes)
143 pub fn is_empty(&self) -> bool {
144 self.values.is_empty()
145 }
146}
147
148/// Structure expected by the `__entity` escape
149#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
150pub struct TypeAndId {
151 /// Entity typename
152 #[serde(rename = "type")]
153 entity_type: SmolStr,
154 /// Entity id
155 id: SmolStr,
156}
157
158impl From<EntityUID> for TypeAndId {
159 fn from(euid: EntityUID) -> TypeAndId {
160 let (entity_type, eid) = euid.components();
161 TypeAndId {
162 entity_type: entity_type.to_string().into(),
163 id: AsRef::<str>::as_ref(&eid).into(),
164 }
165 }
166}
167
168impl From<&EntityUID> for TypeAndId {
169 fn from(euid: &EntityUID) -> TypeAndId {
170 TypeAndId {
171 entity_type: euid.entity_type().to_string().into(),
172 id: AsRef::<str>::as_ref(&euid.eid()).into(),
173 }
174 }
175}
176
177impl TryFrom<TypeAndId> for EntityUID {
178 type Error = crate::parser::err::ParseErrors;
179
180 fn try_from(e: TypeAndId) -> Result<EntityUID, Self::Error> {
181 Ok(EntityUID::from_components(
182 Name::from_normalized_str(&e.entity_type)?,
183 Eid::new(e.id),
184 ))
185 }
186}
187
188/// Structure expected by the `__extn` escape
189#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
190pub struct FnAndArg {
191 /// Extension constructor function
192 #[serde(rename = "fn")]
193 pub(crate) ext_fn: SmolStr,
194 /// Argument to that constructor
195 pub(crate) arg: Box<CedarValueJson>,
196}
197
198impl CedarValueJson {
199 /// Encode the given `EntityUID` as a `CedarValueJson`
200 pub fn uid(euid: &EntityUID) -> Self {
201 Self::EntityEscape {
202 __entity: TypeAndId::from(euid.clone()),
203 }
204 }
205
206 /// Convert this `CedarValueJson` into a Cedar "restricted expression"
207 pub fn into_expr(
208 self,
209 ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
210 ) -> Result<RestrictedExpr, JsonDeserializationError> {
211 match self {
212 Self::Bool(b) => Ok(RestrictedExpr::val(b)),
213 Self::Long(i) => Ok(RestrictedExpr::val(i)),
214 Self::String(s) => Ok(RestrictedExpr::val(s)),
215 Self::Set(vals) => Ok(RestrictedExpr::set(
216 vals.into_iter()
217 .map(|v| v.into_expr(ctx.clone()))
218 .collect::<Result<Vec<_>, _>>()?,
219 )),
220 Self::Record(map) => Ok(RestrictedExpr::record(
221 map.into_iter()
222 .map(|(k, v)| Ok((k, v.into_expr(ctx.clone())?)))
223 .collect::<Result<Vec<_>, JsonDeserializationError>>()?,
224 )
225 .map_err(|e| match e {
226 ExprConstructionError::DuplicateKeyInRecordLiteral { key } => {
227 JsonDeserializationError::DuplicateKeyInRecordLiteral {
228 ctx: Box::new(ctx()),
229 key,
230 }
231 }
232 })?),
233 Self::EntityEscape { __entity: entity } => Ok(RestrictedExpr::val(
234 EntityUID::try_from(entity.clone()).map_err(|errs| {
235 JsonDeserializationError::ParseEscape {
236 kind: EscapeKind::Entity,
237 value: serde_json::to_string_pretty(&entity)
238 .unwrap_or_else(|_| format!("{:?}", &entity)),
239 errs,
240 }
241 })?,
242 )),
243 Self::ExtnEscape { __extn: extn } => extn.into_expr(ctx),
244 Self::ExprEscape { .. } => Err(JsonDeserializationError::ExprTag(Box::new(
245 JsonDeserializationErrorContext::Context,
246 ))),
247 }
248 }
249
250 /// Convert a Cedar "restricted expression" into a `CedarValueJson`.
251 pub fn from_expr(expr: BorrowedRestrictedExpr<'_>) -> Result<Self, JsonSerializationError> {
252 match expr.as_ref().expr_kind() {
253 ExprKind::Lit(lit) => Ok(Self::from_lit(lit.clone())),
254 ExprKind::ExtensionFunctionApp { fn_name, args } => match args.len() {
255 0 => Err(JsonSerializationError::ExtnCall0Arguments {
256 func: fn_name.clone(),
257 }),
258 // PANIC SAFETY. We've checked that `args` is of length 1, fine to index at 0
259 #[allow(clippy::indexing_slicing)]
260 1 => Ok(Self::ExtnEscape {
261 __extn: FnAndArg {
262 ext_fn: fn_name.to_string().into(),
263 arg: Box::new(CedarValueJson::from_expr(
264 // assuming the invariant holds for `expr`, it must also hold here
265 BorrowedRestrictedExpr::new_unchecked(
266 &args[0], // checked above that |args| == 1
267 ),
268 )?),
269 },
270 }),
271 _ => Err(JsonSerializationError::ExtnCall2OrMoreArguments {
272 func: fn_name.clone(),
273 }),
274 },
275 ExprKind::Set(exprs) => Ok(Self::Set(
276 exprs
277 .iter()
278 .map(BorrowedRestrictedExpr::new_unchecked) // assuming the invariant holds for `expr`, it must also hold here
279 .map(CedarValueJson::from_expr)
280 .collect::<Result<_, JsonSerializationError>>()?,
281 )),
282 ExprKind::Record(map) => {
283 // if `map` contains a key which collides with one of our JSON
284 // escapes, then we have a problem because it would be interpreted
285 // as an escape when being read back in.
286 check_for_reserved_keys(map.keys())?;
287 Ok(Self::Record(
288 map.iter()
289 .map(|(k, v)| {
290 Ok((
291 k.clone(),
292 CedarValueJson::from_expr(
293 // assuming the invariant holds for `expr`, it must also hold here
294 BorrowedRestrictedExpr::new_unchecked(v),
295 )?,
296 ))
297 })
298 .collect::<Result<_, JsonSerializationError>>()?,
299 ))
300 }
301 kind => {
302 Err(JsonSerializationError::UnexpectedRestrictedExprKind { kind: kind.clone() })
303 }
304 }
305 }
306
307 /// Convert a Cedar value into a `CedarValueJson`.
308 ///
309 /// Only throws errors in two cases:
310 /// 1. `value` is (or contains) a record with a reserved key such as
311 /// "__entity"
312 /// 2. `value` is (or contains) an extension value, and the argument to the
313 /// extension constructor that produced that extension value can't
314 /// itself be converted to `CedarJsonValue`. (Either because that
315 /// argument falls into one of these two cases itself, or because the
316 /// argument is a nontrivial residual.)
317 pub fn from_value(value: Value) -> Result<Self, JsonSerializationError> {
318 match value {
319 Value::Lit(lit) => Ok(Self::from_lit(lit)),
320 Value::Set(set) => Ok(Self::Set(
321 set.iter()
322 .cloned()
323 .map(Self::from_value)
324 .collect::<Result<_, _>>()?,
325 )),
326 Value::Record(map) => {
327 // if `map` contains a key which collides with one of our JSON
328 // escapes, then we have a problem because it would be interpreted
329 // as an escape when being read back in.
330 check_for_reserved_keys(map.keys())?;
331 Ok(Self::Record(
332 map.iter()
333 .map(|(k, v)| Ok((k.clone(), Self::from_value(v.clone())?)))
334 .collect::<Result<JsonRecord, JsonSerializationError>>()?,
335 ))
336 }
337 Value::ExtensionValue(ev) => {
338 let ext_fn: &Name = &ev.constructor;
339 Ok(Self::ExtnEscape {
340 __extn: FnAndArg {
341 ext_fn: ext_fn.to_string().into(),
342 arg: match ev.args.as_slice() {
343 [ref expr] => Box::new(Self::from_expr(expr.as_borrowed())?),
344 [] => {
345 return Err(JsonSerializationError::ExtnCall0Arguments {
346 func: ext_fn.clone(),
347 })
348 }
349 _ => {
350 return Err(JsonSerializationError::ExtnCall2OrMoreArguments {
351 func: ext_fn.clone(),
352 })
353 }
354 },
355 },
356 })
357 }
358 }
359 }
360
361 /// Convert a Cedar literal into a `CedarValueJson`.
362 pub fn from_lit(lit: Literal) -> Self {
363 match lit {
364 Literal::Bool(b) => Self::Bool(b),
365 Literal::Long(i) => Self::Long(i),
366 Literal::String(s) => Self::String(s),
367 Literal::EntityUID(euid) => Self::EntityEscape {
368 __entity: unwrap_or_clone(euid).into(),
369 },
370 }
371 }
372}
373
374/// helper function to check if the given keys contain any reserved keys,
375/// throwing an appropriate `JsonSerializationError` if so
376fn check_for_reserved_keys<'a>(
377 mut keys: impl Iterator<Item = &'a SmolStr>,
378) -> Result<(), JsonSerializationError> {
379 // We could be a little more permissive here, but to be
380 // conservative, we throw an error for any record that contains
381 // any key with a reserved name, not just single-key records
382 // with the reserved names.
383 let reserved_keys: HashSet<&str> = HashSet::from_iter(["__entity", "__extn", "__expr"]);
384 let collision = keys.find(|k| reserved_keys.contains(k.as_str()));
385 match collision {
386 Some(collision) => Err(JsonSerializationError::ReservedKey {
387 key: collision.clone(),
388 }),
389 None => Ok(()),
390 }
391}
392
393impl FnAndArg {
394 /// Convert this `FnAndArg` into a Cedar "restricted expression" (which will be a call to an extension constructor)
395 pub fn into_expr(
396 self,
397 ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
398 ) -> Result<RestrictedExpr, JsonDeserializationError> {
399 Ok(RestrictedExpr::call_extension_fn(
400 Name::from_normalized_str(&self.ext_fn).map_err(|errs| {
401 JsonDeserializationError::ParseEscape {
402 kind: EscapeKind::Extension,
403 value: self.ext_fn.to_string(),
404 errs,
405 }
406 })?,
407 vec![CedarValueJson::into_expr(*self.arg, ctx)?],
408 ))
409 }
410}
411
412/// Struct used to parse Cedar values from JSON.
413#[derive(Debug, Clone)]
414pub struct ValueParser<'e> {
415 /// Extensions which are active for the JSON parsing.
416 extensions: Extensions<'e>,
417}
418
419impl<'e> ValueParser<'e> {
420 /// Create a new `ValueParser`.
421 pub fn new(extensions: Extensions<'e>) -> Self {
422 Self { extensions }
423 }
424
425 /// internal function that converts a Cedar value (in JSON) into a
426 /// `RestrictedExpr`. Performs schema-based parsing if `expected_ty` is
427 /// provided. This does not mean that this function fully validates the
428 /// value against `expected_ty` -- it does not.
429 pub fn val_into_restricted_expr(
430 &self,
431 val: serde_json::Value,
432 expected_ty: Option<&SchemaType>,
433 ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
434 ) -> Result<RestrictedExpr, JsonDeserializationError> {
435 // First we have to check if we've been given an Unknown. This is valid
436 // regardless of the expected type (see #418).
437 let parse_as_unknown = |val: serde_json::Value| {
438 let extjson: ExtnValueJson = serde_json::from_value(val).ok()?;
439 match extjson {
440 ExtnValueJson::ExplicitExtnEscape {
441 __extn: FnAndArg { ext_fn, arg },
442 } if ext_fn == "unknown" => {
443 let arg = arg.into_expr(ctx.clone()).ok()?;
444 let name = arg.as_string()?;
445 Some(RestrictedExpr::unknown(Unknown::new_untyped(name.clone())))
446 }
447 _ => None, // only explicit `__extn` escape is valid for this purpose. For instance, if we allowed `ImplicitConstructor` here, then all strings would parse as calls to `unknown()`, which is clearly not what we want.
448 }
449 };
450 if let Some(rexpr) = parse_as_unknown(val.clone()) {
451 return Ok(rexpr);
452 }
453 // otherwise, we do normal schema-based parsing based on the expected type.
454 match expected_ty {
455 // The expected type is an entity reference. Special parsing rules
456 // apply: for instance, the `__entity` escape can optionally be omitted.
457 // What this means is that we parse the contents as `EntityUidJson`, and
458 // then convert that into an entity reference `RestrictedExpr`
459 Some(SchemaType::Entity { .. }) => {
460 let uidjson: EntityUidJson = serde_json::from_value(val)?;
461 Ok(RestrictedExpr::val(uidjson.into_euid(ctx)?))
462 }
463 // The expected type is an extension type. Special parsing rules apply:
464 // for instance, the `__extn` escape can optionally be omitted. What
465 // this means is that we parse the contents as `ExtnValueJson`, and then
466 // convert that into an extension-function-call `RestrictedExpr`
467 Some(SchemaType::Extension { ref name, .. }) => {
468 let extjson: ExtnValueJson = serde_json::from_value(val)?;
469 self.extn_value_json_into_rexpr(extjson, name.clone(), ctx)
470 }
471 // The expected type is a set type. No special parsing rules apply, but
472 // we need to parse the elements according to the expected element type
473 Some(expected_ty @ SchemaType::Set { element_ty }) => match val {
474 serde_json::Value::Array(elements) => Ok(RestrictedExpr::set(
475 elements
476 .into_iter()
477 .map(|element| {
478 self.val_into_restricted_expr(element, Some(element_ty), ctx.clone())
479 })
480 .collect::<Result<Vec<RestrictedExpr>, JsonDeserializationError>>()?,
481 )),
482 val => {
483 let actual_val = {
484 let jvalue: CedarValueJson = serde_json::from_value(val)?;
485 jvalue.into_expr(ctx.clone())?
486 };
487 let err = TypeMismatchError {
488 expected: Box::new(expected_ty.clone()),
489 actual_ty: match schematype_of_restricted_expr(
490 actual_val.as_borrowed(),
491 self.extensions,
492 ) {
493 Ok(actual_ty) => Some(Box::new(actual_ty)),
494 Err(_) => None, // just don't report the type if there was an error computing it
495 },
496 actual_val: Either::Right(Box::new(actual_val)),
497 };
498 match ctx() {
499 JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
500 Err(JsonDeserializationError::EntitySchemaConformance(
501 EntitySchemaConformanceError::TypeMismatch { uid, attr, err },
502 ))
503 }
504 ctx => Err(JsonDeserializationError::TypeMismatch {
505 ctx: Box::new(ctx),
506 err,
507 }),
508 }
509 }
510 },
511 // The expected type is a record type. No special parsing rules
512 // apply, but we need to parse the attribute values according to
513 // their expected element types
514 Some(
515 expected_ty @ SchemaType::Record {
516 attrs: expected_attrs,
517 open_attrs,
518 },
519 ) => match val {
520 serde_json::Value::Object(mut actual_attrs) => {
521 let ctx2 = ctx.clone(); // for borrow-check, so the original `ctx` can be moved into the closure below
522 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`
523 let rexpr_pairs = expected_attrs
524 .iter()
525 .filter_map(move |(k, expected_attr_ty)| {
526 match mut_actual_attrs.remove(k.as_str()) {
527 Some(actual_attr) => {
528 match self.val_into_restricted_expr(actual_attr, Some(expected_attr_ty.schema_type()), ctx.clone()) {
529 Ok(actual_attr) => Some(Ok((k.clone(), actual_attr))),
530 Err(e) => Some(Err(e)),
531 }
532 }
533 None if expected_attr_ty.is_required() => Some(Err(JsonDeserializationError::MissingRequiredRecordAttr {
534 ctx: Box::new(ctx()),
535 record_attr: k.clone(),
536 })),
537 None => None,
538 }
539 })
540 .collect::<Result<Vec<(SmolStr, RestrictedExpr)>, JsonDeserializationError>>()?;
541
542 if !open_attrs {
543 // we've now checked that all expected attrs exist, and removed them from `actual_attrs`.
544 // we still need to verify that we didn't have any unexpected attrs.
545 if let Some((record_attr, _)) = actual_attrs.into_iter().next() {
546 return Err(JsonDeserializationError::UnexpectedRecordAttr {
547 ctx: Box::new(ctx2()),
548 record_attr: record_attr.into(),
549 });
550 }
551 }
552
553 // having duplicate keys should be impossible here (because
554 // neither `actual_attrs` nor `expected_attrs` can have
555 // duplicate keys; they're both maps), but we can still throw
556 // the error properly in the case that it somehow happens
557 RestrictedExpr::record(rexpr_pairs).map_err(|e| match e {
558 ExprConstructionError::DuplicateKeyInRecordLiteral { key } => {
559 JsonDeserializationError::DuplicateKeyInRecordLiteral {
560 ctx: Box::new(ctx2()),
561 key,
562 }
563 }
564 })
565 }
566 val => {
567 let actual_val = {
568 let jvalue: CedarValueJson = serde_json::from_value(val)?;
569 jvalue.into_expr(ctx.clone())?
570 };
571 let err = TypeMismatchError {
572 expected: Box::new(expected_ty.clone()),
573 actual_ty: match schematype_of_restricted_expr(
574 actual_val.as_borrowed(),
575 self.extensions,
576 ) {
577 Ok(actual_ty) => Some(Box::new(actual_ty)),
578 Err(_) => None, // just don't report the type if there was an error computing it
579 },
580 actual_val: Either::Right(Box::new(actual_val)),
581 };
582 match ctx() {
583 JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
584 Err(JsonDeserializationError::EntitySchemaConformance(
585 EntitySchemaConformanceError::TypeMismatch { uid, attr, err },
586 ))
587 }
588 ctx => Err(JsonDeserializationError::TypeMismatch {
589 ctx: Box::new(ctx),
590 err,
591 }),
592 }
593 }
594 },
595 // The expected type is any other type, or we don't have an expected type.
596 // No special parsing rules apply; we do ordinary, non-schema-based parsing.
597 Some(_) | None => {
598 // Everything is parsed as `CedarValueJson`, and converted into
599 // `RestrictedExpr` from that.
600 let jvalue: CedarValueJson = serde_json::from_value(val)?;
601 Ok(jvalue.into_expr(ctx)?)
602 }
603 }
604 }
605
606 /// internal function that converts an `ExtnValueJson` into a
607 /// `RestrictedExpr`, which will be an extension constructor call.
608 ///
609 /// `expected_typename`: Specific extension type that is expected.
610 fn extn_value_json_into_rexpr(
611 &self,
612 extnjson: ExtnValueJson,
613 expected_typename: Name,
614 ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
615 ) -> Result<RestrictedExpr, JsonDeserializationError> {
616 match extnjson {
617 ExtnValueJson::ExplicitExprEscape { __expr } => {
618 Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
619 }
620 ExtnValueJson::ExplicitExtnEscape { __extn }
621 | ExtnValueJson::ImplicitExtnEscape(__extn) => {
622 // reuse the same logic that parses CedarValueJson
623 let jvalue = CedarValueJson::ExtnEscape { __extn };
624 let expr = jvalue.into_expr(ctx.clone())?;
625 match expr.expr_kind() {
626 ExprKind::ExtensionFunctionApp { .. } => Ok(expr),
627 _ => Err(JsonDeserializationError::ExpectedExtnValue {
628 ctx: Box::new(ctx()),
629 got: Box::new(Either::Right(expr.clone().into())),
630 }),
631 }
632 }
633 ExtnValueJson::ImplicitConstructor(val) => {
634 let arg = val.into_expr(ctx.clone())?;
635 let argty = schematype_of_restricted_expr(arg.as_borrowed(), self.extensions)
636 .map_err(|e| match e {
637 GetSchemaTypeError::HeterogeneousSet(err) => match ctx() {
638 JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
639 JsonDeserializationError::EntitySchemaConformance(
640 EntitySchemaConformanceError::HeterogeneousSet {
641 uid,
642 attr,
643 err,
644 },
645 )
646 }
647 ctx => JsonDeserializationError::HeterogeneousSet {
648 ctx: Box::new(ctx),
649 err,
650 },
651 },
652 GetSchemaTypeError::ExtensionFunctionLookup(err) => match ctx() {
653 JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
654 JsonDeserializationError::EntitySchemaConformance(
655 EntitySchemaConformanceError::ExtensionFunctionLookup {
656 uid,
657 attr,
658 err,
659 },
660 )
661 }
662 ctx => JsonDeserializationError::ExtensionFunctionLookup {
663 ctx: Box::new(ctx),
664 err,
665 },
666 },
667 GetSchemaTypeError::UnknownInsufficientTypeInfo { .. }
668 | GetSchemaTypeError::NontrivialResidual { .. } => {
669 JsonDeserializationError::UnknownInImplicitConstructorArg {
670 ctx: Box::new(ctx()),
671 arg: Box::new(arg.clone()),
672 }
673 }
674 })?;
675 let func = self
676 .extensions
677 .lookup_single_arg_constructor(
678 &SchemaType::Extension {
679 name: expected_typename.clone(),
680 },
681 &argty,
682 )
683 .map_err(|err| JsonDeserializationError::ExtensionFunctionLookup {
684 ctx: Box::new(ctx()),
685 err,
686 })?
687 .ok_or_else(|| JsonDeserializationError::MissingImpliedConstructor {
688 ctx: Box::new(ctx()),
689 return_type: Box::new(SchemaType::Extension {
690 name: expected_typename,
691 }),
692 arg_type: Box::new(argty.clone()),
693 })?;
694 Ok(RestrictedExpr::call_extension_fn(
695 func.name().clone(),
696 vec![arg],
697 ))
698 }
699 }
700 }
701}
702
703/// Serde JSON format for Cedar values where we know we're expecting an entity
704/// reference
705#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
706#[serde(untagged)]
707pub enum EntityUidJson {
708 /// This was removed in 3.0 and is only here for generating nice error messages.
709 ExplicitExprEscape {
710 /// Contents are ignored.
711 __expr: String,
712 },
713 /// Explicit `__entity` escape; see notes on `CedarValueJson::EntityEscape`
714 ExplicitEntityEscape {
715 /// JSON object containing the entity type and ID
716 __entity: TypeAndId,
717 },
718 /// Implicit `__entity` escape, in which case we'll see just the TypeAndId
719 /// structure
720 ImplicitEntityEscape(TypeAndId),
721
722 /// Implicit catch-all case for error handling
723 FoundValue(serde_json::Value),
724}
725
726impl EntityUidJson {
727 /// Construct an `EntityUidJson` from entity type name and eid.
728 ///
729 /// This will use the `ImplicitEntityEscape` form, if it matters.
730 pub fn new(entity_type: impl Into<SmolStr>, id: impl Into<SmolStr>) -> Self {
731 Self::ImplicitEntityEscape(TypeAndId {
732 entity_type: entity_type.into(),
733 id: id.into(),
734 })
735 }
736
737 /// Convert this `EntityUidJson` into an `EntityUID`
738 pub fn into_euid(
739 self,
740 ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
741 ) -> Result<EntityUID, JsonDeserializationError> {
742 match self {
743 Self::ExplicitEntityEscape { __entity } | Self::ImplicitEntityEscape(__entity) => {
744 // reuse the same logic that parses CedarValueJson
745 let jvalue = CedarValueJson::EntityEscape { __entity };
746 let expr = jvalue.into_expr(ctx.clone())?;
747 match expr.expr_kind() {
748 ExprKind::Lit(Literal::EntityUID(euid)) => Ok((**euid).clone()),
749 _ => Err(JsonDeserializationError::ExpectedLiteralEntityRef {
750 ctx: Box::new(ctx()),
751 got: Box::new(Either::Right(expr.clone().into())),
752 }),
753 }
754 }
755 Self::FoundValue(v) => Err(JsonDeserializationError::ExpectedLiteralEntityRef {
756 ctx: Box::new(ctx()),
757 got: Box::new(Either::Left(v)),
758 }),
759 Self::ExplicitExprEscape { __expr } => {
760 Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
761 }
762 }
763 }
764}
765
766/// Convert an `EntityUID` to `EntityUidJson`, using the `ExplicitEntityEscape` option
767impl From<EntityUID> for EntityUidJson {
768 fn from(uid: EntityUID) -> EntityUidJson {
769 EntityUidJson::ExplicitEntityEscape {
770 __entity: uid.into(),
771 }
772 }
773}
774
775/// Convert an `EntityUID` to `EntityUidJson`, using the `ExplicitEntityEscape` option
776impl From<&EntityUID> for EntityUidJson {
777 fn from(uid: &EntityUID) -> EntityUidJson {
778 EntityUidJson::ExplicitEntityEscape {
779 __entity: uid.into(),
780 }
781 }
782}
783
784/// Serde JSON format for Cedar values where we know we're expecting an
785/// extension value
786#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
787#[serde(untagged)]
788pub enum ExtnValueJson {
789 /// This was removed in 3.0 and is only here for generating nice error messages.
790 ExplicitExprEscape {
791 /// Contents are ignored.
792 __expr: String,
793 },
794 /// Explicit `__extn` escape; see notes on `CedarValueJson::ExtnEscape`
795 ExplicitExtnEscape {
796 /// JSON object containing the extension-constructor call
797 __extn: FnAndArg,
798 },
799 /// Implicit `__extn` escape, in which case we'll just see the `FnAndArg`
800 /// directly
801 ImplicitExtnEscape(FnAndArg),
802 /// Implicit `__extn` escape and constructor. Constructor is implicitly
803 /// selected based on the argument type and the expected type.
804 //
805 // This is listed last so that it has lowest priority when deserializing.
806 // If one of the above forms fits, we use that.
807 ImplicitConstructor(CedarValueJson),
808}