use super::TypeMismatchError;
use crate::ast::{Eid, EntityType, EntityUID};
use crate::extensions::ExtensionFunctionLookupError;
use crate::impl_diagnostic_from_method_on_field;
use itertools::Itertools;
use miette::Diagnostic;
use smol_str::SmolStr;
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
#[non_exhaustive]
pub enum EntitySchemaConformanceError {
#[error(transparent)]
#[diagnostic(transparent)]
UnexpectedEntityAttr(UnexpectedEntityAttr),
#[error(transparent)]
#[diagnostic(transparent)]
UnexpectedEntityTag(UnexpectedEntityTag),
#[error(transparent)]
#[diagnostic(transparent)]
MissingRequiredEntityAttr(MissingRequiredEntityAttr),
#[error(transparent)]
#[diagnostic(transparent)]
TypeMismatch(TypeMismatch),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidAncestorType(InvalidAncestorType),
#[error(transparent)]
#[diagnostic(transparent)]
UnexpectedEntityType(#[from] UnexpectedEntityTypeError),
#[error(transparent)]
#[diagnostic(transparent)]
UndeclaredAction(#[from] UndeclaredAction),
#[error(transparent)]
#[diagnostic(transparent)]
ActionDeclarationMismatch(ActionDeclarationMismatch),
#[error(transparent)]
#[diagnostic(transparent)]
ExtensionFunctionLookup(ExtensionFunctionLookup),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidEnumEntity(#[from] InvalidEnumEntity),
}
impl EntitySchemaConformanceError {
pub(crate) fn unexpected_entity_attr(uid: EntityUID, attr: impl Into<SmolStr>) -> Self {
Self::UnexpectedEntityAttr(UnexpectedEntityAttr {
uid,
attr: attr.into(),
})
}
pub(crate) fn unexpected_entity_tag(uid: EntityUID, tag: impl Into<SmolStr>) -> Self {
Self::UnexpectedEntityTag(UnexpectedEntityTag {
uid,
tag: tag.into(),
})
}
pub(crate) fn missing_entity_attr(uid: EntityUID, attr: impl Into<SmolStr>) -> Self {
Self::MissingRequiredEntityAttr(MissingRequiredEntityAttr {
uid,
attr: attr.into(),
})
}
pub(crate) fn type_mismatch(
uid: EntityUID,
attr_or_tag: impl Into<SmolStr>,
context: AttrOrTag,
err: TypeMismatchError,
) -> Self {
Self::TypeMismatch(TypeMismatch {
uid,
attr_or_tag: attr_or_tag.into(),
context,
err,
})
}
pub(crate) fn invalid_ancestor_type(uid: EntityUID, ancestor_type: EntityType) -> Self {
Self::InvalidAncestorType(InvalidAncestorType {
uid,
ancestor_ty: Box::new(ancestor_type),
})
}
pub(crate) fn undeclared_action(uid: EntityUID) -> Self {
Self::UndeclaredAction(UndeclaredAction { uid })
}
pub(crate) fn action_declaration_mismatch(uid: EntityUID) -> Self {
Self::ActionDeclarationMismatch(ActionDeclarationMismatch { uid })
}
pub(crate) fn extension_function_lookup(
uid: EntityUID,
attr_or_tag: impl Into<SmolStr>,
context: AttrOrTag,
err: ExtensionFunctionLookupError,
) -> Self {
Self::ExtensionFunctionLookup(ExtensionFunctionLookup {
uid,
attr_or_tag: attr_or_tag.into(),
context,
err,
})
}
}
#[derive(Debug, Error, Diagnostic)]
#[error("in `{context}` `{attr_or_tag}` on `{uid}`, {err}")]
#[diagnostic(forward(err))]
pub struct ExtensionFunctionLookup {
uid: EntityUID,
attr_or_tag: SmolStr,
context: AttrOrTag,
err: ExtensionFunctionLookupError,
}
#[derive(Debug, Error, Diagnostic)]
#[error("definition of action `{uid}` does not match its schema declaration")]
#[diagnostic(help(
"to use the schema's definition of `{uid}`, simply omit it from the entities input data"
))]
pub struct ActionDeclarationMismatch {
uid: EntityUID,
}
#[derive(Debug, Error, Diagnostic)]
#[error("found action entity `{uid}`, but it was not declared as an action in the schema")]
pub struct UndeclaredAction {
pub(crate) uid: EntityUID,
}
#[derive(Debug, Error, Diagnostic)]
#[error(
"`{uid}` is not allowed to have an ancestor of type `{ancestor_ty}` according to the schema"
)]
pub struct InvalidAncestorType {
uid: EntityUID,
ancestor_ty: Box<EntityType>, }
#[derive(Debug, Error, Diagnostic)]
#[error("attribute `{attr}` on `{uid}` should not exist according to the schema")]
pub struct UnexpectedEntityAttr {
uid: EntityUID,
attr: SmolStr,
}
#[derive(Debug, Error, Diagnostic)]
#[error(
"found a tag `{tag}` on `{uid}`, but no tags should exist on `{uid}` according to the schema"
)]
pub struct UnexpectedEntityTag {
uid: EntityUID,
tag: SmolStr,
}
#[derive(Debug, Error, Diagnostic)]
#[error("expected entity `{uid}` to have attribute `{attr}`, but it does not")]
pub struct MissingRequiredEntityAttr {
uid: EntityUID,
attr: SmolStr,
}
#[derive(Debug, Clone, Copy)]
pub enum AttrOrTag {
Attr,
Tag,
}
impl std::fmt::Display for AttrOrTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AttrOrTag::Attr => write!(f, "attribute"),
AttrOrTag::Tag => write!(f, "tag"),
}
}
}
#[derive(Debug, Error, Diagnostic)]
#[error("in {context} `{attr_or_tag}` on `{uid}`, {err}")]
#[diagnostic(forward(err))]
pub struct TypeMismatch {
uid: EntityUID,
attr_or_tag: SmolStr,
context: AttrOrTag,
err: TypeMismatchError,
}
#[derive(Debug, Error)]
#[error("entity `{uid}` has type `{}` which is not declared in the schema", .uid.entity_type())]
pub struct UnexpectedEntityTypeError {
pub uid: EntityUID,
pub suggested_types: Vec<EntityType>,
}
impl Diagnostic for UnexpectedEntityTypeError {
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
match self.suggested_types.as_slice() {
[] => None,
[ty] => Some(Box::new(format!("did you mean `{ty}`?"))),
tys => Some(Box::new(format!(
"did you mean one of {:?}?",
tys.iter().map(ToString::to_string).collect::<Vec<String>>()
))),
}
}
}
#[derive(Debug, Error, Diagnostic)]
#[error(transparent)]
#[diagnostic(transparent)]
pub struct InvalidEnumEntity {
err: InvalidEnumEntityError,
}
impl From<InvalidEnumEntityError> for InvalidEnumEntity {
fn from(value: InvalidEnumEntityError) -> Self {
Self { err: value }
}
}
#[derive(Debug, Error, Clone, PartialEq, Eq, Hash)]
#[error("entity `{uid}` is of an enumerated entity type, but `\"{}\"` is not declared as a valid eid", uid.eid().escaped())]
pub struct InvalidEnumEntityError {
pub uid: EntityUID,
pub choices: Vec<Eid>,
}
impl Diagnostic for InvalidEnumEntityError {
impl_diagnostic_from_method_on_field!(uid, loc);
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
Some(Box::new(format!(
"valid entity eids: {}",
self.choices
.iter()
.map(|e| format!("\"{}\"", e.escaped()))
.join(", ")
)))
}
}