use std::collections::HashSet;
use crate::extensions::ExtensionFunctionLookupError;
use crate::pst;
use miette::Diagnostic;
use smol_str::ToSmolStr;
use thiserror::Error;
use crate::ast;
use crate::est;
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[non_exhaustive]
pub enum PstConstructionError {
#[error(transparent)]
#[diagnostic(transparent)]
PolicyFromEmptyRepresentation(#[from] error_body::PolicyFromEmptyRepresentationError),
#[error(transparent)]
#[diagnostic(transparent)]
PolicyMissingLinkId(#[from] error_body::PolicyMissingLinkIdError),
#[error(transparent)]
#[diagnostic(transparent)]
ActionConstraintCannotHaveSlots(#[from] error_body::ActionConstraintCannotHaveSlotsError),
#[error(transparent)]
#[diagnostic(transparent)]
ExpectedTemplateWithSlots(#[from] error_body::ExpectedTemplateWithSlotsError),
#[error(transparent)]
#[diagnostic(transparent)]
WrongSlotPosition(#[from] error_body::WrongSlotPositionError),
#[error(transparent)]
#[diagnostic(transparent)]
DuplicateRecordKey(#[from] error_body::DuplicateRecordKeyError),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidAnnotation(#[from] error_body::InvalidAnnotationError),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidEntityUid(#[from] error_body::InvalidEntityUidError),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidEntityType(#[from] error_body::InvalidEntityTypeError),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidExpression(#[from] error_body::InvalidExpressionError),
#[error(transparent)]
#[diagnostic(transparent)]
UnknownFunction(#[from] error_body::UnknownFunctionError),
#[error(transparent)]
#[diagnostic(transparent)]
WrongArity(#[from] error_body::WrongArityError),
#[error(transparent)]
#[diagnostic(transparent)]
UnsupportedErrorNode(#[from] error_body::UnsupportedErrorNode),
#[error(transparent)]
#[diagnostic(transparent)]
ParsingFailed(#[from] error_body::ParsingFailedError),
#[error(transparent)]
#[diagnostic(transparent)]
LinkingFailed(#[from] error_body::LinkingError),
#[error(transparent)]
#[diagnostic(transparent)]
ContainsSlots(#[from] error_body::ContainsSlotError),
}
#[doc(hidden)]
impl From<est::FromJsonError> for PstConstructionError {
fn from(err: est::FromJsonError) -> Self {
match err {
est::FromJsonError::UnknownExtensionFunction(e) => {
error_body::UnknownFunctionError::new(e.to_smolstr()).into()
}
est::FromJsonError::InvalidEntityType(e) => error_body::InvalidEntityTypeError {
description: e.to_string(),
}
.into(),
est::FromJsonError::JsonDeserializationError(e) => {
error_body::ParsingFailedError::new(e.to_string()).into()
}
est::FromJsonError::UnescapeError(e) => {
error_body::ParsingFailedError::new(e.head.to_string()).into()
}
est::FromJsonError::ActionSlot => {
error_body::ActionConstraintCannotHaveSlotsError.into()
}
est::FromJsonError::InvalidActionType(e) => error_body::InvalidEntityTypeError {
description: e.to_string(),
}
.into(),
est::FromJsonError::InvalidSlotName => {
error_body::ParsingFailedError::new(err.to_string()).into()
}
est::FromJsonError::TemplateToPolicy(e) => {
let mut slots: HashSet<pst::SlotId, _> = HashSet::new();
slots.insert(e.slot.id.into());
error_body::ContainsSlotError { slots }.into()
}
est::FromJsonError::PolicyToTemplate(_) => {
error_body::ExpectedTemplateWithSlotsError.into()
}
est::FromJsonError::SlotsInConditionClause(e) => error_body::ContainsSlotError {
slots: std::iter::once(e.slot.id.into()).collect(),
}
.into(),
est::FromJsonError::MissingOperator | est::FromJsonError::MultipleOperators { .. } => {
error_body::InvalidExpressionError::new(err.to_string()).into()
}
#[cfg(feature = "tolerant-ast")]
est::FromJsonError::ASTErrorNode => {
error_body::UnsupportedErrorNode::new("AST contains an error node").into()
}
}
}
}
#[doc(hidden)]
impl From<ast::ExpressionConstructionError> for PstConstructionError {
fn from(err: ast::ExpressionConstructionError) -> Self {
let ast::ExpressionConstructionError::DuplicateKey(k) = err;
error_body::DuplicateRecordKeyError { key: k.key }.into()
}
}
#[doc(hidden)]
impl From<crate::parser::err::ParseErrors> for PstConstructionError {
fn from(value: crate::parser::err::ParseErrors) -> Self {
error_body::ParsingFailedError::from(value).into()
}
}
#[doc(hidden)]
impl From<ExtensionFunctionLookupError> for PstConstructionError {
fn from(err: ExtensionFunctionLookupError) -> Self {
let ExtensionFunctionLookupError::FuncDoesNotExist(body) = err;
error_body::UnknownFunctionError::new(body.name.to_smolstr()).into()
}
}
pub mod error_body {
use miette::Diagnostic;
use smol_str::SmolStr;
use std::collections::HashSet;
use thiserror::Error;
use crate::est;
use crate::pst;
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("cannot construct policy from empty representation")]
pub struct PolicyFromEmptyRepresentationError;
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("linked policy is missing an instance id")]
pub struct PolicyMissingLinkIdError;
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("action constraint cannot have slots")]
pub struct ActionConstraintCannotHaveSlotsError;
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("expected a template with slots, but found a static policy")]
pub struct ExpectedTemplateWithSlotsError;
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("slot `{found}` cannot be used in this position (expected slot `{expected}`)")]
pub struct WrongSlotPositionError {
found: pst::SlotId,
expected: pst::SlotId,
}
impl WrongSlotPositionError {
pub(crate) fn new(found: pst::SlotId, expected: pst::SlotId) -> Self {
Self { found, expected }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("duplicate record key: `{key}`")]
pub struct DuplicateRecordKeyError {
pub(crate) key: SmolStr,
}
impl DuplicateRecordKeyError {
pub fn key(&self) -> &str {
&self.key
}
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("invalid annotation: {description}")]
pub struct InvalidAnnotationError {
description: String,
}
impl InvalidAnnotationError {
pub(crate) fn new(description: impl Into<String>) -> Self {
Self {
description: description.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("invalid entity UID: {description}")]
pub struct InvalidEntityUidError {
pub(crate) description: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("invalid entity type: `{description}`")]
pub struct InvalidEntityTypeError {
pub(crate) description: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("invalid expression: {description}")]
pub struct InvalidExpressionError {
pub(crate) description: String,
}
impl InvalidExpressionError {
pub(crate) fn new(description: String) -> Self {
Self { description }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("unknown function: `{name}`")]
pub struct UnknownFunctionError {
pub(crate) name: SmolStr,
}
impl UnknownFunctionError {
pub(crate) fn new(name: SmolStr) -> Self {
Self { name }
}
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("function `{name}` expects {expected} argument(s), got {got}")]
pub struct WrongArityError {
pub(crate) name: String,
pub(crate) expected: usize,
pub(crate) got: usize,
}
impl WrongArityError {
pub(crate) fn new(name: String, expected: usize, got: usize) -> Self {
Self {
name,
expected,
got,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn expected(&self) -> usize {
self.expected
}
pub fn got(&self) -> usize {
self.got
}
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("error nodes not supported in conversion: {description}")]
pub struct UnsupportedErrorNode {
description: &'static str,
}
impl UnsupportedErrorNode {
pub(crate) fn new(description: &'static str) -> Self {
Self { description }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("parse error: {description}")]
pub struct ParsingFailedError {
pub(crate) description: String,
}
impl ParsingFailedError {
pub(crate) fn new(description: String) -> Self {
Self { description }
}
}
impl From<crate::parser::err::ParseErrors> for ParsingFailedError {
fn from(value: crate::parser::err::ParseErrors) -> Self {
Self::new(format!("{value}"))
}
}
#[derive(Debug, PartialEq, Eq, Diagnostic, Error, Clone)]
pub enum LinkingError {
#[error("failed to link template: no value provided for `{slot}`")]
MissedSlot {
slot: pst::SlotId,
},
}
impl From<LinkingError> for est::LinkingError {
fn from(err: LinkingError) -> Self {
match err {
LinkingError::MissedSlot { slot } => Self::MissedSlot { slot: slot.into() },
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[error("policy or expression contains slots: {slots:?}")]
pub struct ContainsSlotError {
pub(crate) slots: HashSet<crate::pst::SlotId>,
}
}
#[cfg(test)]
mod tests {
use super::*;
use cool_asserts::assert_matches;
#[test]
fn from_json_error_conversions() {
use crate::est::FromJsonError;
let serde_err = serde_json::from_str::<String>("bad").unwrap_err();
let json_deser_err: crate::entities::json::err::JsonDeserializationError = serde_err.into();
let err: PstConstructionError =
FromJsonError::JsonDeserializationError(json_deser_err).into();
assert_matches!(err, PstConstructionError::ParsingFailed(..));
let err: PstConstructionError = FromJsonError::ActionSlot.into();
assert!(matches!(
err,
PstConstructionError::ActionConstraintCannotHaveSlots(..)
));
let euid = ast::EntityUID::with_eid_and_type("Bad", "act").unwrap();
let err: PstConstructionError =
FromJsonError::InvalidActionType(crate::parser::err::parse_errors::InvalidActionType {
euids: nonempty::nonempty![std::sync::Arc::new(euid)],
})
.into();
assert_matches!(err, PstConstructionError::InvalidEntityType(..));
let err: PstConstructionError = FromJsonError::InvalidSlotName.into();
assert_matches!(err, PstConstructionError::ParsingFailed(..));
let err: PstConstructionError = FromJsonError::TemplateToPolicy(
crate::parser::err::parse_errors::ExpectedStaticPolicy {
slot: ast::Slot {
id: ast::SlotId::principal(),
loc: None,
},
},
)
.into();
assert_matches!(err, PstConstructionError::ContainsSlots(..));
let err: PstConstructionError = FromJsonError::PolicyToTemplate(
crate::parser::err::parse_errors::ExpectedTemplate::new(),
)
.into();
assert!(matches!(
err,
PstConstructionError::ExpectedTemplateWithSlots(..)
));
let err: PstConstructionError = FromJsonError::SlotsInConditionClause(
crate::parser::err::parse_errors::SlotsInConditionClause {
slot: ast::Slot {
id: ast::SlotId::resource(),
loc: None,
},
clause_type: "when",
},
)
.into();
assert_matches!(err, PstConstructionError::ContainsSlots(..));
let err: PstConstructionError = FromJsonError::MissingOperator.into();
assert_matches!(err, PstConstructionError::InvalidExpression(..));
let err: PstConstructionError = FromJsonError::MultipleOperators {
ops: vec!["a".into(), "b".into()],
}
.into();
assert_matches!(err, PstConstructionError::InvalidExpression(..));
}
#[test]
fn from_expression_construction_error() {
let err: PstConstructionError = ast::ExpressionConstructionError::DuplicateKey(
ast::expression_construction_errors::DuplicateKeyError {
key: "k".into(),
context: "in record literal",
},
)
.into();
assert_matches!(err, PstConstructionError::DuplicateRecordKey(..));
}
#[test]
fn from_extension_function_lookup_error() {
use crate::extensions::ExtensionFunctionLookupError;
let err: PstConstructionError = ExtensionFunctionLookupError::FuncDoesNotExist(
crate::extensions::extension_function_lookup_errors::FuncDoesNotExistError {
name: "bogus".parse().unwrap(),
source_loc: None,
},
)
.into();
assert_matches!(err, PstConstructionError::UnknownFunction(..));
}
}