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