use super::{
PartitionResult,
schema::{EntityRefAttrSrc, EntityRefSetSrc, ExpectedClaimType, TknClaimAttrSrc},
};
use crate::common::cedar_schema::cedar_json::attribute::Attribute;
use cedar_policy::{EntityId, EntityTypeName, EntityUid, RestrictedExpression};
use serde_json::Value;
use smol_str::SmolStr;
use std::{collections::HashMap, fmt::Display, str::FromStr};
use thiserror::Error;
impl Attribute {
pub(crate) fn kind_str(&self) -> &str {
match self {
Attribute::String => "String",
Attribute::Long => "Long",
Attribute::Boolean => "Boolean",
Attribute::Record { .. } => "Record",
Attribute::Set => "Set",
Attribute::Entity { .. } => "Entity",
Attribute::Extension => "Extension",
Attribute::EntityOrCommon { .. } => "EntityOrCommon",
}
}
}
impl TknClaimAttrSrc {
pub(super) fn build_expr(
&self,
src: &Value,
) -> Result<Option<RestrictedExpression>, BuildExprErrorVec> {
build_expr_from_value(&self.expected_type, src)
}
}
impl EntityRefAttrSrc {
pub(super) fn build_expr(&self, id: &str) -> Result<RestrictedExpression, Box<BuildExprError>> {
let entity_type_name = EntityTypeName::from_str(&self.0).map_err(|e| Box::new(e.into()))?;
let entity_id = EntityId::from_str(id).unwrap_or_else(|e| match e {});
let uid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
Ok(RestrictedExpression::new_entity_uid(uid))
}
}
impl EntityRefSetSrc {
pub(super) fn build_expr(
&self,
ids: &[SmolStr],
) -> Result<RestrictedExpression, BuildExprErrorVec> {
let entity_type_name =
EntityTypeName::from_str(&self.0).map_err(|e| BuildExprErrorVec(vec![e.into()]))?;
let uids: Vec<_> = ids
.iter()
.map(|id| {
let entity_id = EntityId::from_str(id).unwrap_or_else(|e| match e {});
RestrictedExpression::new_entity_uid(EntityUid::from_type_name_and_id(
entity_type_name.clone(),
entity_id,
))
})
.collect();
Ok(RestrictedExpression::new_set(uids))
}
}
fn build_expr_from_value(
claim_type: &ExpectedClaimType,
src: &Value,
) -> Result<Option<RestrictedExpression>, BuildExprErrorVec> {
match claim_type {
ExpectedClaimType::Null => Ok(None),
ExpectedClaimType::Bool => {
let val = src.as_bool().ok_or_else(|| TypeMismatchError {
expected: "bool".to_string(),
actual: TypeMismatchError::value_type_name(src).to_string(),
})?;
Ok(Some(RestrictedExpression::new_bool(val)))
},
ExpectedClaimType::Number => {
let val = src.as_i64().ok_or_else(|| TypeMismatchError {
expected: "number".to_string(),
actual: TypeMismatchError::value_type_name(src).to_string(),
})?;
Ok(Some(RestrictedExpression::new_long(val)))
},
ExpectedClaimType::String => {
let val = src.as_str().ok_or_else(|| TypeMismatchError {
expected: "string".to_string(),
actual: TypeMismatchError::value_type_name(src).to_string(),
})?;
Ok(Some(RestrictedExpression::new_string(val.to_string())))
},
ExpectedClaimType::Array(expected_claim_type) => {
let src = src.as_array().ok_or_else(|| TypeMismatchError {
expected: "array".to_string(),
actual: TypeMismatchError::value_type_name(src).to_string(),
})?;
let (vals, errs): (Vec<_>, Vec<_>) = src
.iter()
.map(|src| build_expr_from_value(expected_claim_type, src))
.filter_map(std::result::Result::transpose)
.partition_result();
if !errs.is_empty() {
return Err(BuildExprErrorVec(
errs.into_iter()
.flat_map(BuildExprErrorVec::into_inner)
.collect(),
))?;
}
Ok(Some(RestrictedExpression::new_set(vals)))
},
ExpectedClaimType::Object(expected_obj) => {
let src = src.as_object().ok_or_else(|| TypeMismatchError {
expected: "object".to_string(),
actual: TypeMismatchError::value_type_name(src).to_string(),
})?;
let (fields, errs): (Vec<_>, Vec<_>) = expected_obj
.iter()
.filter_map(|(name, ty)| {
src.get(name.as_str()).and_then(|src| {
build_expr_from_value(ty, src)
.transpose()
.map(|res| res.map(|res| (name.to_string(), res)))
})
})
.partition_result();
let fields: HashMap<String, RestrictedExpression> = if errs.is_empty() {
fields.into_iter().collect()
} else {
return Err(BuildExprErrorVec(
errs.into_iter()
.flat_map(BuildExprErrorVec::into_inner)
.collect(),
))?;
};
Ok(Some(RestrictedExpression::new_record(fields)?))
},
ExpectedClaimType::Extension(name) => {
let val = src.as_str().ok_or_else(|| TypeMismatchError {
expected: "string".to_string(),
actual: TypeMismatchError::value_type_name(src).to_string(),
})?;
let expr = match name.as_str() {
"decimal" => Some(RestrictedExpression::new_decimal(val)),
"ipaddr" => Some(RestrictedExpression::new_ip(val)),
_ => Some(RestrictedExpression::new_unknown(val)),
};
Ok(expr)
},
}
}
#[derive(Debug, Error)]
pub struct BuildExprErrorVec(Vec<BuildExprError>);
impl BuildExprErrorVec {
pub fn into_inner(self) -> Vec<BuildExprError> {
self.0
}
}
impl From<Vec<Box<BuildExprError>>> for BuildExprErrorVec {
fn from(errs: Vec<Box<BuildExprError>>) -> Self {
Self(errs.into_iter().map(|e| *e).collect())
}
}
impl From<Vec<BuildExprError>> for BuildExprErrorVec {
fn from(errs: Vec<BuildExprError>) -> Self {
Self(errs)
}
}
#[derive(Debug, Error)]
pub enum BuildExprError {
#[error(transparent)]
TypeMismatch(#[from] TypeMismatchError),
#[error(transparent)]
ConstructExpr(#[from] cedar_policy::ExpressionConstructionError),
#[error("failed to parse uid: {0}")]
ParseUid(#[from] cedar_policy::ParseErrors),
}
#[derive(Debug, Error)]
#[error("expected a {expected}, but found: '{actual}'")]
pub struct TypeMismatchError {
pub expected: String,
pub actual: String,
}
impl Display for BuildExprErrorVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?}",
self.0.iter().map(ToString::to_string).collect::<Vec<_>>()
)
}
}
impl From<BuildExprError> for BuildExprErrorVec {
fn from(err: BuildExprError) -> Self {
Vec::from([err]).into()
}
}
impl From<TypeMismatchError> for BuildExprErrorVec {
fn from(err: TypeMismatchError) -> Self {
Vec::from([BuildExprError::TypeMismatch(err)]).into()
}
}
impl From<cedar_policy::ExpressionConstructionError> for BuildExprErrorVec {
fn from(err: cedar_policy::ExpressionConstructionError) -> Self {
Vec::from([BuildExprError::ConstructExpr(err)]).into()
}
}
impl TypeMismatchError {
pub fn value_type_name(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
}