use super::{
JsonDeserializationError, JsonDeserializationErrorContext, JsonSerializationError, SchemaType,
};
use crate::ast::{
expression_construction_errors, BorrowedRestrictedExpr, Eid, EntityUID, ExprConstructionError,
ExprKind, Literal, Name, RestrictedExpr, Unknown, Value, ValueKind,
};
use crate::entities::{
schematype_of_restricted_expr, EntitySchemaConformanceError, EscapeKind, GetSchemaTypeError,
TypeMismatchError,
};
use crate::extensions::Extensions;
use crate::FromNormalizedStr;
use either::Either;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use serde_with::{DeserializeAs, SerializeAs};
use smol_str::SmolStr;
use std::collections::{BTreeMap, HashSet};
use std::sync::Arc;
#[cfg(feature = "wasm")]
extern crate tsify;
#[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 CedarValueJson {
ExprEscape {
#[cfg_attr(feature = "wasm", tsify(type = "string"))]
__expr: SmolStr,
},
EntityEscape {
__entity: TypeAndId,
},
ExtnEscape {
__extn: FnAndArg,
},
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,
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct JsonRecord {
#[serde_as(as = "serde_with::MapPreventDuplicates<_, _>")]
#[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)]
#[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_string().into(),
id: AsRef::<str>::as_ref(&eid).into(),
}
}
}
impl From<&EntityUID> for TypeAndId {
fn from(euid: &EntityUID) -> TypeAndId {
TypeAndId {
entity_type: euid.entity_type().to_string().into(),
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, Self::Error> {
Ok(EntityUID::from_components(
Name::from_normalized_str(&e.entity_type)?,
Eid::new(e.id),
None,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub struct FnAndArg {
#[serde(rename = "fn")]
#[cfg_attr(feature = "wasm", tsify(type = "string"))]
pub(crate) ext_fn: SmolStr,
pub(crate) arg: Box<CedarValueJson>,
}
impl CedarValueJson {
pub fn uid(euid: &EntityUID) -> Self {
Self::EntityEscape {
__entity: TypeAndId::from(euid.clone()),
}
}
pub fn into_expr(
self,
ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
) -> 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.clone()))
.collect::<Result<Vec<_>, _>>()?,
)),
Self::Record(map) => Ok(RestrictedExpr::record(
map.into_iter()
.map(|(k, v)| Ok((k, v.into_expr(ctx.clone())?)))
.collect::<Result<Vec<_>, JsonDeserializationError>>()?,
)
.map_err(|e| match e {
ExprConstructionError::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| {
JsonDeserializationError::ParseEscape {
kind: EscapeKind::Entity,
value: serde_json::to_string_pretty(&entity)
.unwrap_or_else(|_| format!("{:?}", &entity)),
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.len() {
0 => Err(JsonSerializationError::ExtnCall0Arguments {
func: fn_name.clone(),
}),
#[allow(clippy::indexing_slicing)]
1 => Ok(Self::ExtnEscape {
__extn: FnAndArg {
ext_fn: fn_name.to_string().into(),
arg: Box::new(CedarValueJson::from_expr(
BorrowedRestrictedExpr::new_unchecked(
&args[0], ),
)?),
},
}),
_ => Err(JsonSerializationError::ExtnCall2OrMoreArguments {
func: fn_name.clone(),
}),
},
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>>()?,
))
}
kind => {
Err(JsonSerializationError::UnexpectedRestrictedExprKind { 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_fn: &Name = &ev.constructor;
Ok(Self::ExtnEscape {
__extn: FnAndArg {
ext_fn: ext_fn.to_string().into(),
arg: match ev.args.as_slice() {
[ref expr] => Box::new(Self::from_expr(expr.as_borrowed())?),
[] => {
return Err(JsonSerializationError::ExtnCall0Arguments {
func: ext_fn.clone(),
})
}
_ => {
return Err(JsonSerializationError::ExtnCall2OrMoreArguments {
func: ext_fn.clone(),
})
}
},
},
})
}
}
}
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(),
},
}
}
}
fn check_for_reserved_keys<'a>(
mut keys: impl Iterator<Item = &'a SmolStr>,
) -> Result<(), JsonSerializationError> {
let reserved_keys: HashSet<&str> = HashSet::from_iter(["__entity", "__extn", "__expr"]);
let collision = keys.find(|k| reserved_keys.contains(k.as_str()));
match collision {
Some(collision) => Err(JsonSerializationError::ReservedKey {
key: collision.clone(),
}),
None => Ok(()),
}
}
impl FnAndArg {
pub fn into_expr(
self,
ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
) -> Result<RestrictedExpr, JsonDeserializationError> {
Ok(RestrictedExpr::call_extension_fn(
Name::from_normalized_str(&self.ext_fn).map_err(|errs| {
JsonDeserializationError::ParseEscape {
kind: EscapeKind::Extension,
value: self.ext_fn.to_string(),
errs,
}
})?,
vec![CedarValueJson::into_expr(*self.arg, ctx)?],
))
}
}
#[derive(Debug, Clone)]
pub struct ValueParser<'e> {
extensions: Extensions<'e>,
}
impl<'e> ValueParser<'e> {
pub fn new(extensions: Extensions<'e>) -> Self {
Self { extensions }
}
pub fn val_into_restricted_expr(
&self,
val: serde_json::Value,
expected_ty: Option<&SchemaType>,
ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
) -> 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: FnAndArg { ext_fn, arg },
} if ext_fn == "unknown" => {
let arg = arg.into_expr(ctx.clone()).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)?;
self.extn_value_json_into_rexpr(extjson, name.clone(), ctx)
}
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.clone())
})
.collect::<Result<Vec<RestrictedExpr>, JsonDeserializationError>>()?,
)),
val => {
let actual_val = {
let jvalue: CedarValueJson = serde_json::from_value(val)?;
jvalue.into_expr(ctx.clone())?
};
let err = TypeMismatchError {
expected: Box::new(expected_ty.clone()),
actual_ty: match schematype_of_restricted_expr(
actual_val.as_borrowed(),
self.extensions,
) {
Ok(actual_ty) => Some(Box::new(actual_ty)),
Err(_) => None, },
actual_val: Either::Right(Box::new(actual_val)),
};
match ctx() {
JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
Err(JsonDeserializationError::EntitySchemaConformance(
EntitySchemaConformanceError::TypeMismatch { uid, attr, err },
))
}
ctx => Err(JsonDeserializationError::TypeMismatch {
ctx: Box::new(ctx),
err,
}),
}
}
},
Some(
expected_ty @ SchemaType::Record {
attrs: expected_attrs,
open_attrs,
},
) => match val {
serde_json::Value::Object(mut actual_attrs) => {
let ctx2 = ctx.clone(); 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.clone()) {
Ok(actual_attr) => Some(Ok((k.clone(), actual_attr))),
Err(e) => Some(Err(e)),
}
}
None if expected_attr_ty.is_required() => Some(Err(JsonDeserializationError::MissingRequiredRecordAttr {
ctx: Box::new(ctx()),
record_attr: 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::UnexpectedRecordAttr {
ctx: Box::new(ctx2()),
record_attr: record_attr.into(),
});
}
}
RestrictedExpr::record(rexpr_pairs).map_err(|e| match e {
ExprConstructionError::DuplicateKey(
expression_construction_errors::DuplicateKeyError { key, .. },
) => JsonDeserializationError::duplicate_key(ctx2(), key),
})
}
val => {
let actual_val = {
let jvalue: CedarValueJson = serde_json::from_value(val)?;
jvalue.into_expr(ctx.clone())?
};
let err = TypeMismatchError {
expected: Box::new(expected_ty.clone()),
actual_ty: match schematype_of_restricted_expr(
actual_val.as_borrowed(),
self.extensions,
) {
Ok(actual_ty) => Some(Box::new(actual_ty)),
Err(_) => None, },
actual_val: Either::Right(Box::new(actual_val)),
};
match ctx() {
JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
Err(JsonDeserializationError::EntitySchemaConformance(
EntitySchemaConformanceError::TypeMismatch { uid, attr, err },
))
}
ctx => Err(JsonDeserializationError::TypeMismatch {
ctx: Box::new(ctx),
err,
}),
}
}
},
Some(_) | None => {
let jvalue: CedarValueJson = serde_json::from_value(val)?;
Ok(jvalue.into_expr(ctx)?)
}
}
}
fn extn_value_json_into_rexpr(
&self,
extnjson: ExtnValueJson,
expected_typename: Name,
ctx: impl Fn() -> JsonDeserializationErrorContext + Clone,
) -> Result<RestrictedExpr, JsonDeserializationError> {
match extnjson {
ExtnValueJson::ExplicitExprEscape { __expr } => {
Err(JsonDeserializationError::ExprTag(Box::new(ctx())))
}
ExtnValueJson::ExplicitExtnEscape { __extn }
| ExtnValueJson::ImplicitExtnEscape(__extn) => {
let jvalue = CedarValueJson::ExtnEscape { __extn };
let expr = jvalue.into_expr(ctx.clone())?;
match expr.expr_kind() {
ExprKind::ExtensionFunctionApp { .. } => Ok(expr),
_ => Err(JsonDeserializationError::ExpectedExtnValue {
ctx: Box::new(ctx()),
got: Box::new(Either::Right(expr.clone().into())),
}),
}
}
ExtnValueJson::ImplicitConstructor(val) => {
let arg = val.into_expr(ctx.clone())?;
let argty = schematype_of_restricted_expr(arg.as_borrowed(), self.extensions)
.map_err(|e| match e {
GetSchemaTypeError::HeterogeneousSet(err) => match ctx() {
JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
JsonDeserializationError::EntitySchemaConformance(
EntitySchemaConformanceError::HeterogeneousSet {
uid,
attr,
err,
},
)
}
ctx => JsonDeserializationError::HeterogeneousSet {
ctx: Box::new(ctx),
err,
},
},
GetSchemaTypeError::ExtensionFunctionLookup(err) => match ctx() {
JsonDeserializationErrorContext::EntityAttribute { uid, attr } => {
JsonDeserializationError::EntitySchemaConformance(
EntitySchemaConformanceError::ExtensionFunctionLookup {
uid,
attr,
err,
},
)
}
ctx => JsonDeserializationError::ExtensionFunctionLookup {
ctx: Box::new(ctx),
err,
},
},
GetSchemaTypeError::UnknownInsufficientTypeInfo { .. }
| GetSchemaTypeError::NontrivialResidual { .. } => {
JsonDeserializationError::UnknownInImplicitConstructorArg {
ctx: Box::new(ctx()),
arg: Box::new(arg.clone()),
}
}
})?;
let func = self
.extensions
.lookup_single_arg_constructor(
&SchemaType::Extension {
name: expected_typename.clone(),
},
&argty,
)
.map_err(|err| JsonDeserializationError::ExtensionFunctionLookup {
ctx: Box::new(ctx()),
err,
})?
.ok_or_else(|| JsonDeserializationError::MissingImpliedConstructor {
ctx: Box::new(ctx()),
return_type: Box::new(SchemaType::Extension {
name: expected_typename,
}),
arg_type: Box::new(argty.clone()),
})?;
Ok(RestrictedExpr::call_extension_fn(
func.name().clone(),
vec![arg],
))
}
}
}
}
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 {
__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: impl Fn() -> JsonDeserializationErrorContext + Clone,
) -> 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::ExpectedLiteralEntityRef {
ctx: Box::new(ctx()),
got: Box::new(Either::Right(expr.clone().into())),
}),
}
}
Self::FoundValue(v) => Err(JsonDeserializationError::ExpectedLiteralEntityRef {
ctx: Box::new(ctx()),
got: Box::new(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: FnAndArg,
},
ImplicitExtnEscape(FnAndArg),
ImplicitConstructor(CedarValueJson),
}