use super::{
err::{JsonDeserializationError, JsonDeserializationErrorContext, JsonSerializationError},
SchemaType,
};
use crate::entities::{
conformance::err::EntitySchemaConformanceError,
json::err::{EscapeKind, TypeMismatchError},
};
use crate::extensions::Extensions;
use crate::FromNormalizedStr;
use crate::{
ast::{
expression_construction_errors, BorrowedRestrictedExpr, Eid, EntityUID, ExprKind,
ExpressionConstructionError, Literal, RestrictedExpr, Unknown, Value, ValueKind,
},
entities::Name,
};
use either::Either;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use serde_with::{DeserializeAs, SerializeAs};
use smol_str::{SmolStr, ToSmolStr};
use std::collections::BTreeMap;
use std::sync::Arc;
#[cfg(feature = "wasm")]
extern crate tsify;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
enum RawCedarValueJson {
Bool(bool),
Long(i64),
String(SmolStr),
Set(Vec<RawCedarValueJson>),
Record(RawJsonRecord),
Null,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(untagged)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub enum CedarValueJson {
ExprEscape {
#[cfg_attr(feature = "wasm", tsify(type = "__skip"))]
__expr: SmolStr,
},
EntityEscape {
__entity: TypeAndId,
},
ExtnEscape {
__extn: FnAndArgs,
},
Bool(bool),
Long(i64),
String(#[cfg_attr(feature = "wasm", tsify(type = "string"))] SmolStr),
Set(Vec<CedarValueJson>),
Record(
#[cfg_attr(feature = "wasm", tsify(type = "{ [key: string]: CedarValueJson }"))] JsonRecord,
),
Null,
}
impl<'de> Deserialize<'de> for CedarValueJson {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let v: RawCedarValueJson = RawCedarValueJson::deserialize(deserializer)?;
Ok(v.into())
}
}
impl From<RawCedarValueJson> for CedarValueJson {
fn from(value: RawCedarValueJson) -> Self {
match value {
RawCedarValueJson::Bool(b) => Self::Bool(b),
RawCedarValueJson::Long(l) => Self::Long(l),
RawCedarValueJson::Null => Self::Null,
RawCedarValueJson::Record(r) => {
let values = &r.values;
if values.len() == 1 {
match values.iter().map(|(k, v)| (k.as_str(), v)).collect_vec()[..] {
[("__extn", RawCedarValueJson::Record(r))] if r.values.len() >= 2 => {
if let Some(RawCedarValueJson::String(fn_name)) = r.values.get("fn") {
if let Some(arg) = r.values.get("arg") {
return Self::ExtnEscape {
__extn: FnAndArgs::Single {
ext_fn: fn_name.clone(),
arg: Box::new(arg.clone().into()),
},
};
}
if let Some(RawCedarValueJson::Set(args)) = r.values.get("args") {
return Self::ExtnEscape {
__extn: FnAndArgs::Multi {
ext_fn: fn_name.clone(),
args: args.iter().cloned().map(Into::into).collect(),
},
};
}
}
}
[("__expr", RawCedarValueJson::String(s))] => {
return Self::ExprEscape { __expr: s.clone() };
}
[("__entity", RawCedarValueJson::Record(r))] if r.values.len() >= 2 => {
if let Some(RawCedarValueJson::String(ty)) = r.values.get("type") {
if let Some(RawCedarValueJson::String(id)) = r.values.get("id") {
return Self::EntityEscape {
__entity: TypeAndId {
entity_type: ty.clone(),
id: id.clone(),
},
};
}
}
}
_ => {}
}
}
Self::Record(r.into())
}
RawCedarValueJson::Set(s) => Self::Set(s.into_iter().map(Into::into).collect()),
RawCedarValueJson::String(s) => Self::String(s),
}
}
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
struct RawJsonRecord {
#[serde_as(as = "serde_with::MapPreventDuplicates<_, _>")]
#[serde(flatten)]
values: BTreeMap<SmolStr, RawCedarValueJson>,
}
impl From<RawJsonRecord> for JsonRecord {
fn from(value: RawJsonRecord) -> Self {
JsonRecord {
values: value
.values
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct JsonRecord {
#[serde(flatten)]
values: BTreeMap<SmolStr, CedarValueJson>,
}
impl IntoIterator for JsonRecord {
type Item = (SmolStr, CedarValueJson);
type IntoIter = <BTreeMap<SmolStr, CedarValueJson> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.values.into_iter()
}
}
impl<'a> IntoIterator for &'a JsonRecord {
type Item = (&'a SmolStr, &'a CedarValueJson);
type IntoIter = <&'a BTreeMap<SmolStr, CedarValueJson> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.values.iter()
}
}
impl FromIterator<(SmolStr, CedarValueJson)> for JsonRecord {
fn from_iter<T: IntoIterator<Item = (SmolStr, CedarValueJson)>>(iter: T) -> Self {
Self {
values: BTreeMap::from_iter(iter),
}
}
}
impl JsonRecord {
pub fn iter(&self) -> impl Iterator<Item = (&'_ SmolStr, &'_ CedarValueJson)> {
self.values.iter()
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub struct TypeAndId {
#[cfg_attr(feature = "wasm", tsify(type = "string"))]
#[serde(rename = "type")]
entity_type: SmolStr,
#[cfg_attr(feature = "wasm", tsify(type = "string"))]
id: SmolStr,
}
impl From<EntityUID> for TypeAndId {
fn from(euid: EntityUID) -> TypeAndId {
let (entity_type, eid) = euid.components();
TypeAndId {
entity_type: entity_type.to_smolstr(),
id: AsRef::<str>::as_ref(&eid).into(),
}
}
}
impl From<&EntityUID> for TypeAndId {
fn from(euid: &EntityUID) -> TypeAndId {
TypeAndId {
entity_type: euid.entity_type().to_smolstr(),
id: AsRef::<str>::as_ref(&euid.eid()).into(),
}
}
}
impl TryFrom<TypeAndId> for EntityUID {
type Error = crate::parser::err::ParseErrors;
fn try_from(e: TypeAndId) -> Result<EntityUID, crate::parser::err::ParseErrors> {
Ok(EntityUID::from_components(
Name::from_normalized_str(&e.entity_type)?.into(),
Eid::new(e.id),
None,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(untagged)]
pub enum FnAndArgs {
Single {
#[serde(rename = "fn")]
#[cfg_attr(feature = "wasm", tsify(type = "string"))]
ext_fn: SmolStr,
arg: Box<CedarValueJson>,
},
Multi {
#[serde(rename = "fn")]
#[cfg_attr(feature = "wasm", tsify(type = "string"))]
ext_fn: SmolStr,
args: Vec<CedarValueJson>,
},
}
impl FnAndArgs {
pub(crate) fn fn_str(&self) -> &str {
match self {
Self::Multi { ext_fn, .. } | Self::Single { ext_fn, .. } => ext_fn,
}
}
pub(crate) fn args(&self) -> &[CedarValueJson] {
match self {
Self::Multi { args, .. } => args,
Self::Single { arg, .. } => std::slice::from_ref(arg),
}
}
}
impl CedarValueJson {
pub fn uid(euid: &EntityUID) -> Self {
Self::EntityEscape {
__entity: TypeAndId::from(euid.clone()),
}
}
pub fn height(&self) -> usize {
let mut stack: Vec<(&CedarValueJson, usize)> = vec![(self, 0)];
let mut max_depth: usize = 0;
while let Some((val, depth)) = stack.pop() {
max_depth = max_depth.max(depth);
let child_depth = depth + 1;
match val {
Self::Bool(_)
| Self::Long(_)
| Self::String(_)
| Self::Null
| Self::ExprEscape { .. }
| Self::EntityEscape { .. } => {}
Self::Set(vals) => {
for v in vals {
stack.push((v, child_depth));
}
}
Self::Record(rec) => {
for (_, v) in rec {
stack.push((v, child_depth));
}
}
Self::ExtnEscape { __extn } => {
for arg in __extn.args() {
stack.push((arg, child_depth));
}
}
}
}
max_depth
}
pub fn into_expr(
self,
ctx: &dyn Fn() -> JsonDeserializationErrorContext,
) -> Result<RestrictedExpr, JsonDeserializationError> {
match self {
Self::Bool(b) => Ok(RestrictedExpr::val(b)),
Self::Long(i) => Ok(RestrictedExpr::val(i)),
Self::String(s) => Ok(RestrictedExpr::val(s)),
Self::Set(vals) => Ok(RestrictedExpr::set(
vals.into_iter()
.map(|v| v.into_expr(ctx))
.collect::<Result<Vec<_>, _>>()?,
)),
Self::Record(map) => Ok(RestrictedExpr::record(
map.into_iter()
.map(|(k, v)| Ok((k, v.into_expr(ctx)?)))
.collect::<Result<Vec<_>, JsonDeserializationError>>()?,
)
.map_err(|e| match e {
ExpressionConstructionError::DuplicateKey(
expression_construction_errors::DuplicateKeyError { key, .. },
) => JsonDeserializationError::duplicate_key(ctx(), key),
})?),
Self::EntityEscape { __entity: entity } => Ok(RestrictedExpr::val(
EntityUID::try_from(entity.clone()).map_err(|errs| {
let err_msg = serde_json::to_string_pretty(&entity)
.unwrap_or_else(|_| format!("{:?}", entity));
JsonDeserializationError::parse_escape(EscapeKind::Entity, err_msg, errs)
})?,
)),
Self::ExtnEscape { __extn: extn } => extn.into_expr(ctx),
Self::ExprEscape { .. } => Err(JsonDeserializationError::ExprTag(Box::new(ctx()))),
Self::Null => Err(JsonDeserializationError::Null(Box::new(ctx()))),
}
}
pub fn from_expr(expr: BorrowedRestrictedExpr<'_>) -> Result<Self, JsonSerializationError> {
match expr.as_ref().expr_kind() {
ExprKind::Lit(lit) => Ok(Self::from_lit(lit.clone())),
ExprKind::ExtensionFunctionApp { fn_name, args } => match args.as_slice() {
[] => Err(JsonSerializationError::call_0_args(fn_name.clone())),
[arg] => Ok(Self::ExtnEscape {
__extn: FnAndArgs::Single {
ext_fn: fn_name.to_smolstr(),
arg: Box::new(CedarValueJson::from_expr(
BorrowedRestrictedExpr::new_unchecked(arg),
)?),
},
}),
args => Ok(Self::ExtnEscape {
__extn: FnAndArgs::Multi {
ext_fn: fn_name.to_smolstr(),
args: args
.iter()
.map(|arg| {
CedarValueJson::from_expr(BorrowedRestrictedExpr::new_unchecked(
arg,
))
})
.collect::<Result<Vec<_>, _>>()?,
},
}),
},
ExprKind::Set(exprs) => Ok(Self::Set(
exprs
.iter()
.map(BorrowedRestrictedExpr::new_unchecked) .map(CedarValueJson::from_expr)
.collect::<Result<_, JsonSerializationError>>()?,
)),
ExprKind::Record(map) => {
check_for_reserved_keys(map.keys())?;
Ok(Self::Record(
map.iter()
.map(|(k, v)| {
Ok((
k.clone(),
CedarValueJson::from_expr(
BorrowedRestrictedExpr::new_unchecked(v),
)?,
))
})
.collect::<Result<_, JsonSerializationError>>()?,
))
}
ExprKind::Unknown(unknown) => Ok(Self::ExtnEscape {
__extn: FnAndArgs::Single {
ext_fn: "unknown".into(),
arg: Box::new(CedarValueJson::String(unknown.name.clone())),
},
}),
kind => Err(JsonSerializationError::unexpected_restricted_expr_kind(
kind.clone(),
)),
}
}
pub fn from_value(value: Value) -> Result<Self, JsonSerializationError> {
Self::from_valuekind(value.value)
}
pub fn from_valuekind(value: ValueKind) -> Result<Self, JsonSerializationError> {
match value {
ValueKind::Lit(lit) => Ok(Self::from_lit(lit)),
ValueKind::Set(set) => Ok(Self::Set(
set.iter()
.cloned()
.map(Self::from_value)
.collect::<Result<_, _>>()?,
)),
ValueKind::Record(record) => {
check_for_reserved_keys(record.keys())?;
Ok(Self::Record(
record
.iter()
.map(|(k, v)| Ok((k.clone(), Self::from_value(v.clone())?)))
.collect::<Result<JsonRecord, JsonSerializationError>>()?,
))
}
ValueKind::ExtensionValue(ev) => {
let ext_func = ev.func();
match ev.args() {
[] => Err(JsonSerializationError::call_0_args(ext_func.clone())),
[ref expr] => Ok(Self::ExtnEscape {
__extn: FnAndArgs::Single {
ext_fn: ext_func.to_smolstr(),
arg: Box::new(Self::from_expr(expr.as_borrowed())?),
},
}),
exprs => Ok(Self::ExtnEscape {
__extn: FnAndArgs::Multi {
ext_fn: ext_func.to_smolstr(),
args: exprs
.iter()
.map(|expr| Self::from_expr(expr.as_borrowed()))
.collect::<Result<Vec<_>, _>>()?,
},
}),
}
}
}
}
pub fn from_lit(lit: Literal) -> Self {
match lit {
Literal::Bool(b) => Self::Bool(b),
Literal::Long(i) => Self::Long(i),
Literal::String(s) => Self::String(s),
Literal::EntityUID(euid) => Self::EntityEscape {
__entity: Arc::unwrap_or_clone(euid).into(),
},
}
}
pub fn sub_entity_literals(
self,
mapping: &BTreeMap<EntityUID, EntityUID>,
) -> Result<Self, JsonDeserializationError> {
match self {
CedarValueJson::ExprEscape { __expr } => Err(JsonDeserializationError::ExprTag(
Box::new(JsonDeserializationErrorContext::Unknown),
)),
CedarValueJson::EntityEscape { __entity } => {
let euid = EntityUID::try_from(__entity.clone());
match euid {
Ok(euid) => match mapping.get(&euid) {
Some(new_euid) => Ok(CedarValueJson::EntityEscape {
__entity: new_euid.into(),
}),
None => Ok(CedarValueJson::EntityEscape { __entity }),
},
Err(_) => Ok(CedarValueJson::EntityEscape { __entity }),
}
}
CedarValueJson::ExtnEscape {
__extn: FnAndArgs::Single { ext_fn, arg },
} => Ok(CedarValueJson::ExtnEscape {
__extn: FnAndArgs::Single {
ext_fn,
arg: Box::new((*arg).sub_entity_literals(mapping)?),
},
}),
CedarValueJson::ExtnEscape {
__extn: FnAndArgs::Multi { ext_fn, args },
} => Ok(CedarValueJson::ExtnEscape {
__extn: FnAndArgs::Multi {
ext_fn,
args: args
.into_iter()
.map(|arg| arg.sub_entity_literals(mapping))
.collect::<Result<Vec<_>, _>>()?,
},
}),
v @ CedarValueJson::Bool(_) => Ok(v),
v @ CedarValueJson::Long(_) => Ok(v),
v @ CedarValueJson::String(_) => Ok(v),
CedarValueJson::Set(v) => Ok(CedarValueJson::Set(
v.into_iter()
.map(|e| e.sub_entity_literals(mapping))
.collect::<Result<Vec<_>, _>>()?,
)),
CedarValueJson::Record(r) => {
let mut new_m = BTreeMap::new();
for (k, v) in r.values {
new_m.insert(k, v.sub_entity_literals(mapping)?);
}
Ok(CedarValueJson::Record(JsonRecord { values: new_m }))
}
v @ CedarValueJson::Null => Ok(v),
}
}
}
pub(crate) const RESERVED_KEYS: [&str; 3] = ["__entity", "__extn", "__expr"];
pub(crate) fn is_reserved_key(key: &str) -> bool {
RESERVED_KEYS.contains(&key)
}
fn check_for_reserved_keys<'a>(
mut keys: impl Iterator<Item = &'a SmolStr>,
) -> Result<(), JsonSerializationError> {
let collision = keys.find(|k| is_reserved_key(k.as_str()));
match collision {
Some(collision) => Err(JsonSerializationError::reserved_key(collision.clone())),
None => Ok(()),
}
}
impl FnAndArgs {
pub fn into_expr(
self,
ctx: &dyn Fn() -> JsonDeserializationErrorContext,
) -> Result<RestrictedExpr, JsonDeserializationError> {
let ext_fn = self.fn_str();
let args = self.args();
Ok(RestrictedExpr::call_extension_fn(
Name::from_normalized_str(ext_fn).map_err(|errs| {
JsonDeserializationError::parse_escape(EscapeKind::Extension, ext_fn, errs)
})?,
args.iter()
.map(|arg| CedarValueJson::into_expr(arg.clone(), ctx))
.collect::<Result<Vec<_>, _>>()?,
))
}
}
#[derive(Debug, Clone)]
pub struct ValueParser<'e> {
extensions: &'e Extensions<'e>,
}
impl<'e> ValueParser<'e> {
pub fn new(extensions: &'e Extensions<'e>) -> Self {
Self { extensions }
}
pub fn val_into_restricted_expr(
&self,
val: serde_json::Value,
expected_ty: Option<&SchemaType>,
ctx: &dyn Fn() -> JsonDeserializationErrorContext,
) -> Result<RestrictedExpr, JsonDeserializationError> {
let parse_as_unknown = |val: serde_json::Value| {
let extjson: ExtnValueJson = serde_json::from_value(val).ok()?;
match extjson {
ExtnValueJson::ExplicitExtnEscape {
__extn: FnAndArgs::Single { ext_fn, arg },
} if ext_fn == "unknown" => {
let arg = arg.into_expr(ctx).ok()?;
let name = arg.as_string()?;
Some(RestrictedExpr::unknown(Unknown::new_untyped(name.clone())))
}
_ => None, }
};
if let Some(rexpr) = parse_as_unknown(val.clone()) {
return Ok(rexpr);
}
match expected_ty {
Some(SchemaType::Entity { .. }) => {
let uidjson: EntityUidJson = serde_json::from_value(val)?;
Ok(RestrictedExpr::val(uidjson.into_euid(ctx)?))
}
Some(SchemaType::Extension { ref name, .. }) => {
let extjson: ExtnValueJson = serde_json::from_value(val)?;
match extjson {
ExtnValueJson::ExplicitExprEscape { .. } => {
Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
}
ExtnValueJson::ExplicitExtnEscape { __extn }
| ExtnValueJson::ImplicitExtnEscape(__extn) => {
let func = self.extensions.func(
&Name::from_normalized_str(__extn.fn_str()).map_err(|errs| {
JsonDeserializationError::parse_escape(
EscapeKind::Extension,
__extn.fn_str(),
errs,
)
})?,
)?;
let arg_types = func.arg_types();
let args = __extn.args();
if args.len() != arg_types.len() {
return Err(JsonDeserializationError::incorrect_num_of_arguments(
arg_types.len(),
args.len(),
__extn.fn_str(),
));
}
Ok(RestrictedExpr::call_extension_fn(
func.name().clone(),
arg_types
.iter()
.zip(args.iter())
.map(|(arg_type, arg)| {
self.val_into_restricted_expr(
serde_json::to_value(arg)?,
Some(arg_type),
ctx,
)
})
.collect::<Result<Vec<_>, _>>()?,
))
}
ExtnValueJson::ImplicitConstructor(val) => {
let expected_return_type = SchemaType::Extension { name: name.clone() };
if let Some(constructor) = self
.extensions
.lookup_single_arg_constructor(&expected_return_type)
{
#[expect(
clippy::indexing_slicing,
reason = "we've concluded above that it has one arugment"
)]
Ok(RestrictedExpr::call_extension_fn(
constructor.name().clone(),
std::iter::once(self.val_into_restricted_expr(
serde_json::to_value(val)?,
Some(&constructor.arg_types()[0]),
ctx,
)?),
))
} else {
Err(JsonDeserializationError::missing_implied_constructor(
ctx(),
expected_return_type,
))
}
}
}
}
Some(expected_ty @ SchemaType::Set { element_ty }) => match val {
serde_json::Value::Array(elements) => Ok(RestrictedExpr::set(
elements
.into_iter()
.map(|element| {
self.val_into_restricted_expr(element, Some(element_ty), ctx)
})
.collect::<Result<Vec<RestrictedExpr>, JsonDeserializationError>>()?,
)),
val => {
let actual_val = {
let jvalue: CedarValueJson = serde_json::from_value(val)?;
jvalue.into_expr(ctx)?
};
let err = TypeMismatchError::type_mismatch(
expected_ty.clone(),
actual_val.try_type_of(self.extensions),
actual_val,
);
match ctx() {
JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
Err(JsonDeserializationError::EntitySchemaConformance(
EntitySchemaConformanceError::type_mismatch(
uid,
attr,
crate::entities::conformance::err::AttrOrTag::Attr,
err,
),
))
}
ctx => Err(JsonDeserializationError::type_mismatch(ctx, err)),
}
}
},
Some(
expected_ty @ SchemaType::Record {
attrs: expected_attrs,
open_attrs,
},
) => match val {
serde_json::Value::Object(mut actual_attrs) => {
let mut_actual_attrs = &mut actual_attrs; let rexpr_pairs = expected_attrs
.iter()
.filter_map(move |(k, expected_attr_ty)| {
match mut_actual_attrs.remove(k.as_str()) {
Some(actual_attr) => {
match self.val_into_restricted_expr(actual_attr, Some(expected_attr_ty.schema_type()), ctx) {
Ok(actual_attr) => Some(Ok((k.clone(), actual_attr))),
Err(e) => Some(Err(e)),
}
}
None if expected_attr_ty.is_required() => Some(Err(JsonDeserializationError::missing_required_record_attr(ctx(), k.clone()))),
None => None,
}
})
.collect::<Result<Vec<(SmolStr, RestrictedExpr)>, JsonDeserializationError>>()?;
if !open_attrs {
if let Some((record_attr, _)) = actual_attrs.into_iter().next() {
return Err(JsonDeserializationError::unexpected_record_attr(
ctx(),
record_attr,
));
}
}
RestrictedExpr::record(rexpr_pairs).map_err(|e| match e {
ExpressionConstructionError::DuplicateKey(
expression_construction_errors::DuplicateKeyError { key, .. },
) => JsonDeserializationError::duplicate_key(ctx(), key),
})
}
val => {
let actual_val = {
let jvalue: CedarValueJson = serde_json::from_value(val)?;
jvalue.into_expr(ctx)?
};
let err = TypeMismatchError::type_mismatch(
expected_ty.clone(),
actual_val.try_type_of(self.extensions),
actual_val,
);
match ctx() {
JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
Err(JsonDeserializationError::EntitySchemaConformance(
EntitySchemaConformanceError::type_mismatch(
uid,
attr,
crate::entities::conformance::err::AttrOrTag::Attr,
err,
),
))
}
ctx => Err(JsonDeserializationError::type_mismatch(ctx, err)),
}
}
},
Some(_) | None => {
let jvalue: CedarValueJson = serde_json::from_value(val)?;
Ok(jvalue.into_expr(ctx)?)
}
}
}
}
pub trait DeserializationContext {
fn static_context() -> Option<JsonDeserializationErrorContext>;
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct NoStaticContext;
impl DeserializationContext for NoStaticContext {
fn static_context() -> Option<JsonDeserializationErrorContext> {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub enum EntityUidJson<Context = NoStaticContext> {
ExplicitExprEscape {
#[cfg_attr(feature = "wasm", tsify(type = "__skip"))]
__expr: String,
#[serde(skip)]
context: std::marker::PhantomData<Context>,
},
ExplicitEntityEscape {
__entity: TypeAndId,
},
ImplicitEntityEscape(TypeAndId),
FoundValue(#[cfg_attr(feature = "wasm", tsify(type = "__skip"))] serde_json::Value),
}
impl<'de, C: DeserializationContext> DeserializeAs<'de, EntityUID> for EntityUidJson<C> {
fn deserialize_as<D>(deserializer: D) -> Result<EntityUID, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let context = || JsonDeserializationErrorContext::Unknown;
let s = EntityUidJson::<C>::deserialize(deserializer)?;
let euid = s.into_euid(&context).map_err(Error::custom)?;
Ok(euid)
}
}
impl<C> SerializeAs<EntityUID> for EntityUidJson<C> {
fn serialize_as<S>(source: &EntityUID, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let json: EntityUidJson = source.clone().into();
json.serialize(serializer)
}
}
impl<C: DeserializationContext> EntityUidJson<C> {
pub fn new(entity_type: impl Into<SmolStr>, id: impl Into<SmolStr>) -> Self {
Self::ImplicitEntityEscape(TypeAndId {
entity_type: entity_type.into(),
id: id.into(),
})
}
pub fn into_euid(
self,
dynamic_ctx: &dyn Fn() -> JsonDeserializationErrorContext,
) -> Result<EntityUID, JsonDeserializationError> {
let ctx = &|| C::static_context().unwrap_or_else(&dynamic_ctx);
match self {
Self::ExplicitEntityEscape { __entity } | Self::ImplicitEntityEscape(__entity) => {
let jvalue = CedarValueJson::EntityEscape { __entity };
let expr = jvalue.into_expr(ctx)?;
match expr.expr_kind() {
ExprKind::Lit(Literal::EntityUID(euid)) => Ok((**euid).clone()),
_ => Err(JsonDeserializationError::expected_entity_ref(
ctx(),
Either::Right(expr.clone().into()),
)),
}
}
Self::FoundValue(v) => Err(JsonDeserializationError::expected_entity_ref(
ctx(),
Either::Left(v),
)),
Self::ExplicitExprEscape { __expr, .. } => {
Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
}
}
}
}
impl From<EntityUID> for EntityUidJson {
fn from(uid: EntityUID) -> EntityUidJson {
EntityUidJson::ExplicitEntityEscape {
__entity: uid.into(),
}
}
}
impl From<&EntityUID> for EntityUidJson {
fn from(uid: &EntityUID) -> EntityUidJson {
EntityUidJson::ExplicitEntityEscape {
__entity: uid.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ExtnValueJson {
ExplicitExprEscape {
__expr: String,
},
ExplicitExtnEscape {
__extn: FnAndArgs,
},
ImplicitExtnEscape(FnAndArgs),
ImplicitConstructor(CedarValueJson),
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn height_leaf() {
assert_eq!(CedarValueJson::Bool(true).height(), 0);
assert_eq!(CedarValueJson::Long(42).height(), 0);
assert_eq!(CedarValueJson::String("hi".into()).height(), 0);
assert_eq!(CedarValueJson::Null.height(), 0);
assert_eq!(
CedarValueJson::ExprEscape {
__expr: "1 + 2".into()
}
.height(),
0
);
assert_eq!(
CedarValueJson::EntityEscape {
__entity: TypeAndId {
entity_type: "User".into(),
id: "alice".into(),
}
}
.height(),
0
);
assert_eq!(CedarValueJson::Set(vec![]).height(), 0);
assert_eq!(
CedarValueJson::Record(vec![].into_iter().collect()).height(),
0
);
}
#[test]
fn height_set() {
let val = CedarValueJson::Set(vec![CedarValueJson::Long(1), CedarValueJson::Long(2)]);
assert_eq!(val.height(), 1);
}
#[test]
fn height_record() {
let rec: JsonRecord = vec![("a".into(), CedarValueJson::Long(1))]
.into_iter()
.collect();
assert_eq!(CedarValueJson::Record(rec).height(), 1);
}
#[test]
fn height_nested_set() {
let inner = CedarValueJson::Set(vec![CedarValueJson::Long(1)]);
let outer = CedarValueJson::Set(vec![inner]);
assert_eq!(outer.height(), 2);
}
#[test]
fn height_asymmetric_set() {
let val = CedarValueJson::Set(vec![
CedarValueJson::Long(1),
CedarValueJson::Set(vec![CedarValueJson::Long(2)]),
]);
assert_eq!(val.height(), 2);
}
#[test]
fn height_extn_escape() {
let val = CedarValueJson::ExtnEscape {
__extn: FnAndArgs::Single {
ext_fn: "decimal".into(),
arg: Box::new(CedarValueJson::String("1.0".into())),
},
};
assert_eq!(val.height(), 1);
}
#[test]
fn height_extn_escape_multi() {
let val = CedarValueJson::ExtnEscape {
__extn: FnAndArgs::Multi {
ext_fn: "foo".into(),
args: vec![CedarValueJson::Long(1), CedarValueJson::Long(2)],
},
};
assert_eq!(val.height(), 1);
}
#[test]
fn height_nested_composites() {
let extn = CedarValueJson::ExtnEscape {
__extn: FnAndArgs::Single {
ext_fn: "decimal".into(),
arg: Box::new(CedarValueJson::String("1.0".into())),
},
};
let set = CedarValueJson::Set(vec![extn]);
let rec: JsonRecord = vec![("a".into(), set)].into_iter().collect();
let val = CedarValueJson::Record(rec);
assert_eq!(val.height(), 3);
}
}