use std::fmt::Display;
use super::SchemaType;
use crate::ast::{
BorrowedRestrictedExpr, EntityAttrEvaluationError, EntityUID, Expr, ExprKind, PolicyID,
RestrictedExpr, RestrictedExpressionError, Type,
};
use crate::entities::conformance::err::EntitySchemaConformanceError;
use crate::entities::{Name, ReservedNameError};
use crate::parser::err::ParseErrors;
use either::Either;
use itertools::Itertools;
use miette::Diagnostic;
use smol_str::SmolStr;
use thiserror::Error;
#[derive(Debug)]
pub enum EscapeKind {
Entity,
Extension,
}
impl Display for EscapeKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Entity => write!(f, "__entity"),
Self::Extension => write!(f, "__extn"),
}
}
}
#[derive(Debug, Diagnostic, Error)]
#[non_exhaustive]
pub enum JsonDeserializationError {
#[error(transparent)]
#[diagnostic(transparent)]
Serde(#[from] JsonError),
#[error(transparent)]
#[diagnostic(transparent)]
ParseEscape(ParseEscape),
#[error(transparent)]
#[diagnostic(transparent)]
RestrictedExpressionError(#[from] RestrictedExpressionError),
#[error(transparent)]
#[diagnostic(transparent)]
ExpectedLiteralEntityRef(ExpectedLiteralEntityRef),
#[error(transparent)]
#[diagnostic(transparent)]
ExpectedExtnValue(ExpectedExtnValue),
#[error(transparent)]
#[diagnostic(transparent)]
ActionParentIsNotAction(ActionParentIsNotAction),
#[error(transparent)]
#[diagnostic(transparent)]
MissingImpliedConstructor(MissingImpliedConstructor),
#[error(transparent)]
#[diagnostic(transparent)]
DuplicateKey(DuplicateKey),
#[error(transparent)]
#[diagnostic(transparent)]
EntityAttributeEvaluation(#[from] EntityAttrEvaluationError),
#[error(transparent)]
#[diagnostic(transparent)]
EntitySchemaConformance(EntitySchemaConformanceError),
#[error(transparent)]
#[diagnostic(transparent)]
UnexpectedRecordAttr(UnexpectedRecordAttr),
#[error(transparent)]
#[diagnostic(transparent)]
MissingRequiredRecordAttr(MissingRequiredRecordAttr),
#[error(transparent)]
#[diagnostic(transparent)]
TypeMismatch(TypeMismatch),
#[error("{0}, the `__expr` escape is no longer supported")]
#[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
ExprTag(Box<JsonDeserializationErrorContext>),
#[error("{0}, found a `null`; JSON `null`s are not allowed in Cedar")]
Null(Box<JsonDeserializationErrorContext>),
#[error(transparent)]
#[diagnostic(transparent)]
ReservedName(#[from] ReservedNameError),
#[deprecated(
since = "4.2.0",
note = "entity-tags is now stable and fully supported, so this error never occurs"
)]
#[error("entity tags are not supported in this build; to use entity tags, you must enable the `entity-tags` experimental feature")]
UnsupportedEntityTags,
}
impl JsonDeserializationError {
pub(crate) fn parse_escape(
kind: EscapeKind,
value: impl Into<String>,
errs: ParseErrors,
) -> Self {
Self::ParseEscape(ParseEscape {
kind,
value: value.into(),
errs,
})
}
pub(crate) fn expected_entity_ref(
ctx: JsonDeserializationErrorContext,
got: Either<serde_json::Value, Expr>,
) -> Self {
Self::ExpectedLiteralEntityRef(ExpectedLiteralEntityRef {
ctx: Box::new(ctx),
got: Box::new(got),
})
}
pub(crate) fn expected_extn_value(
ctx: JsonDeserializationErrorContext,
got: Either<serde_json::Value, Expr>,
) -> Self {
Self::ExpectedExtnValue(ExpectedExtnValue {
ctx: Box::new(ctx),
got: Box::new(got),
})
}
pub(crate) fn action_parent_is_not_action(uid: EntityUID, parent: EntityUID) -> Self {
Self::ActionParentIsNotAction(ActionParentIsNotAction { uid, parent })
}
pub(crate) fn missing_implied_constructor(
ctx: JsonDeserializationErrorContext,
return_type: SchemaType,
) -> Self {
Self::MissingImpliedConstructor(MissingImpliedConstructor {
ctx: Box::new(ctx),
return_type: Box::new(return_type),
})
}
pub(crate) fn duplicate_key(
ctx: JsonDeserializationErrorContext,
key: impl Into<SmolStr>,
) -> Self {
Self::DuplicateKey(DuplicateKey {
ctx: Box::new(ctx),
key: key.into(),
})
}
pub(crate) fn unexpected_record_attr(
ctx: JsonDeserializationErrorContext,
record_attr: impl Into<SmolStr>,
) -> Self {
Self::UnexpectedRecordAttr(UnexpectedRecordAttr {
ctx: Box::new(ctx),
record_attr: record_attr.into(),
})
}
pub(crate) fn missing_required_record_attr(
ctx: JsonDeserializationErrorContext,
record_attr: impl Into<SmolStr>,
) -> Self {
Self::MissingRequiredRecordAttr(MissingRequiredRecordAttr {
ctx: Box::new(ctx),
record_attr: record_attr.into(),
})
}
pub(crate) fn type_mismatch(
ctx: JsonDeserializationErrorContext,
err: TypeMismatchError,
) -> Self {
Self::TypeMismatch(TypeMismatch {
ctx: Box::new(ctx),
err,
})
}
}
#[derive(Debug, Error, Diagnostic)]
#[error("{ctx}, {err}")]
pub struct TypeMismatch {
ctx: Box<JsonDeserializationErrorContext>,
#[diagnostic(transparent)]
err: TypeMismatchError,
}
#[derive(Debug, Error, Diagnostic)]
#[error("{}, expected the record to have an attribute `{}`, but it does not", .ctx, .record_attr)]
pub struct MissingRequiredRecordAttr {
ctx: Box<JsonDeserializationErrorContext>,
record_attr: SmolStr,
}
#[derive(Debug, Diagnostic, Error)]
#[error("{}, record attribute `{}` should not exist according to the schema", .ctx, .record_attr)]
pub struct UnexpectedRecordAttr {
ctx: Box<JsonDeserializationErrorContext>,
record_attr: SmolStr,
}
#[derive(Debug, Error, Diagnostic)]
#[error("{}, duplicate key `{}` in record", .ctx, .key)]
pub struct DuplicateKey {
ctx: Box<JsonDeserializationErrorContext>,
key: SmolStr,
}
#[derive(Debug, Error, Diagnostic)]
#[error("{}, missing extension constructor for {}", .ctx, .return_type)]
#[diagnostic(help("expected a value of type {} because of the schema", .return_type))]
pub struct MissingImpliedConstructor {
ctx: Box<JsonDeserializationErrorContext>,
return_type: Box<SchemaType>,
}
#[derive(Debug, Error, Diagnostic)]
#[error("action `{}` has a non-action parent `{}`", .uid, .parent)]
#[diagnostic(help("parents of actions need to have type `Action` themselves, perhaps namespaced"))]
pub struct ActionParentIsNotAction {
uid: EntityUID,
parent: EntityUID,
}
#[derive(Debug, Error, Diagnostic)]
#[error("failed to parse escape `{kind}`: {value}, errors: {errs}")]
#[diagnostic(help("{}", match .kind {
EscapeKind::Entity => r#"an __entity escape should have a value like `{ "type": "SomeType", "id": "SomeId" }`"#,
EscapeKind::Extension => r#"an __extn escape should have a value like `{ "fn": "SomeFn", "arg": "SomeArg" }`"#,
}))]
pub struct ParseEscape {
kind: EscapeKind,
value: String,
#[diagnostic(transparent)]
errs: ParseErrors,
}
#[derive(Debug, Error, Diagnostic)]
#[error("{}, expected a literal entity reference, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
#[diagnostic(help(
r#"literal entity references can be made with `{{ "type": "SomeType", "id": "SomeId" }}`"#
))]
pub struct ExpectedLiteralEntityRef {
ctx: Box<JsonDeserializationErrorContext>,
got: Box<Either<serde_json::Value, Expr>>,
}
#[derive(Debug, Error, Diagnostic)]
#[error("{}, expected an extension value, but got `{}`", .ctx, display_json_value(.got.as_ref()))]
#[diagnostic(help(r#"extension values can be made with `{{ "fn": "SomeFn", "id": "SomeId" }}`"#))]
pub struct ExpectedExtnValue {
ctx: Box<JsonDeserializationErrorContext>,
got: Box<Either<serde_json::Value, Expr>>,
}
#[derive(Debug, Error, Diagnostic)]
#[error(transparent)]
pub struct JsonError(#[from] serde_json::Error);
impl From<serde_json::Error> for JsonDeserializationError {
fn from(value: serde_json::Error) -> Self {
Self::Serde(JsonError(value))
}
}
impl From<serde_json::Error> for JsonSerializationError {
fn from(value: serde_json::Error) -> Self {
Self::Serde(JsonError(value))
}
}
#[derive(Debug, Diagnostic, Error)]
#[non_exhaustive]
pub enum JsonSerializationError {
#[error(transparent)]
#[diagnostic(transparent)]
Serde(#[from] JsonError),
#[error(transparent)]
#[diagnostic(transparent)]
ExtnCall0Arguments(ExtnCall0Arguments),
#[error(transparent)]
#[diagnostic(transparent)]
ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments),
#[error(transparent)]
#[diagnostic(transparent)]
ReservedKey(ReservedKey),
#[error(transparent)]
#[diagnostic(transparent)]
UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind),
#[error(transparent)]
#[diagnostic(transparent)]
Residual(Residual),
}
impl JsonSerializationError {
pub(crate) fn call_0_args(func: Name) -> Self {
Self::ExtnCall0Arguments(ExtnCall0Arguments { func })
}
pub(crate) fn call_2_or_more_args(func: Name) -> Self {
Self::ExtnCall2OrMoreArguments(ExtnCall2OrMoreArguments { func })
}
pub(crate) fn reserved_key(key: impl Into<SmolStr>) -> Self {
Self::ReservedKey(ReservedKey { key: key.into() })
}
pub(crate) fn unexpected_restricted_expr_kind(kind: ExprKind) -> Self {
Self::UnexpectedRestrictedExprKind(UnexpectedRestrictedExprKind { kind })
}
pub(crate) fn residual(residual: Expr) -> Self {
Self::Residual(Residual { residual })
}
}
#[derive(Debug, Error, Diagnostic)]
#[error("unsupported call to `{}` with 0 arguments", .func)]
#[diagnostic(help(
"extension function calls with 0 arguments are not currently supported in our JSON format"
))]
pub struct ExtnCall0Arguments {
func: Name,
}
#[derive(Debug, Error, Diagnostic)]
#[error("unsupported call to `{}` with 2 or more arguments", .func)]
#[diagnostic(help("extension function calls with 2 or more arguments are not currently supported in our JSON format"))]
pub struct ExtnCall2OrMoreArguments {
func: Name,
}
#[derive(Debug, Error, Diagnostic)]
#[error("record uses reserved key `{}`", .key)]
pub struct ReservedKey {
key: SmolStr,
}
impl ReservedKey {
pub fn key(&self) -> impl AsRef<str> + '_ {
&self.key
}
}
#[derive(Debug, Error, Diagnostic)]
#[error("unexpected restricted expression `{:?}`", .kind)]
pub struct UnexpectedRestrictedExprKind {
kind: ExprKind,
}
#[derive(Debug, Error, Diagnostic)]
#[error("cannot encode residual as JSON: {}", .residual)]
pub struct Residual {
residual: Expr,
}
#[derive(Debug, Clone)]
pub enum JsonDeserializationErrorContext {
EntityAttribute {
uid: EntityUID,
attr: SmolStr,
},
EntityTag {
uid: EntityUID,
tag: SmolStr,
},
EntityParents {
uid: EntityUID,
},
EntityUid,
Context,
Policy {
id: PolicyID,
},
TemplateLink,
Unknown,
}
#[derive(Debug, Diagnostic, Error)]
#[error("type mismatch: value was expected to have type {expected}, but it {mismatch_reason}: `{}`",
display_restricted_expr(.actual_val.as_borrowed()),
)]
pub struct TypeMismatchError {
expected: Box<SchemaType>,
mismatch_reason: TypeMismatchReason,
actual_val: Box<RestrictedExpr>,
}
#[derive(Debug, Error)]
enum TypeMismatchReason {
#[error("actually has type {0}")]
UnexpectedType(Type),
#[error("contains an unexpected attribute `{0}`")]
UnexpectedAttr(SmolStr),
#[error("is missing the required attribute `{0}`")]
MissingRequiredAtr(SmolStr),
#[error("does not")]
None,
}
impl TypeMismatchError {
pub(crate) fn type_mismatch(
expected: SchemaType,
actual_ty: Option<Type>,
actual_val: RestrictedExpr,
) -> Self {
Self {
expected: Box::new(expected),
mismatch_reason: match actual_ty {
Some(ty) => TypeMismatchReason::UnexpectedType(ty),
None => TypeMismatchReason::None,
},
actual_val: Box::new(actual_val),
}
}
pub(crate) fn unexpected_attr(
expected: SchemaType,
unexpected_attr: SmolStr,
actual_val: RestrictedExpr,
) -> Self {
Self {
expected: Box::new(expected),
mismatch_reason: TypeMismatchReason::UnexpectedAttr(unexpected_attr),
actual_val: Box::new(actual_val),
}
}
pub(crate) fn missing_required_attr(
expected: SchemaType,
missing_attr: SmolStr,
actual_val: RestrictedExpr,
) -> Self {
Self {
expected: Box::new(expected),
mismatch_reason: TypeMismatchReason::MissingRequiredAtr(missing_attr),
actual_val: Box::new(actual_val),
}
}
}
impl std::fmt::Display for JsonDeserializationErrorContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::EntityAttribute { uid, attr } => write!(f, "in attribute `{attr}` on `{uid}`"),
Self::EntityTag { uid, tag } => write!(f, "in tag `{tag}` on `{uid}`"),
Self::EntityParents { uid } => write!(f, "in parents field of `{uid}`"),
Self::EntityUid => write!(f, "in uid field of <unknown entity>"),
Self::Context => write!(f, "while parsing context"),
Self::Policy { id } => write!(f, "while parsing JSON policy `{id}`"),
Self::TemplateLink => write!(f, "while parsing a template link"),
Self::Unknown => write!(f, "parsing context was unknown, please file a bug report at https://github.com/cedar-policy/cedar so we can improve this error message"),
}
}
}
fn display_json_value(v: &Either<serde_json::Value, Expr>) -> String {
match v {
Either::Left(json) => display_value(json),
Either::Right(e) => e.to_string(),
}
}
fn display_value(v: &serde_json::Value) -> String {
match v {
serde_json::Value::Array(contents) => {
format!("[{}]", contents.iter().map(display_value).join(", "))
}
serde_json::Value::Object(map) => {
let mut v: Vec<_> = map.iter().collect();
v.sort_by_key(|p| p.0);
let display_kv = |kv: &(&String, &serde_json::Value)| format!("\"{}\":{}", kv.0, kv.1);
format!("{{{}}}", v.iter().map(display_kv).join(","))
}
other => other.to_string(),
}
}
fn display_restricted_expr(expr: BorrowedRestrictedExpr<'_>) -> String {
match expr.expr_kind() {
ExprKind::Set(elements) => {
let restricted_exprs = elements.iter().map(BorrowedRestrictedExpr::new_unchecked); format!(
"[{}]",
restricted_exprs
.map(display_restricted_expr)
.sorted_unstable()
.join(", ")
)
}
ExprKind::Record(m) => {
format!(
"{{{}}}",
m.iter()
.sorted_unstable_by_key(|(k, _)| SmolStr::clone(k))
.map(|(k, v)| format!("\"{}\": {}", k.escape_debug(), v))
.join(", ")
)
}
_ => format!("{expr}"), }
}