use std::error::Error;
use std::fmt;
use godot_ffi::VariantType;
use crate::builtin::Variant;
use crate::meta::inspect::ElementType;
use crate::meta::{ClassId, ToGodot};
type Cause = Box<dyn Error + Send + Sync>;
#[derive(Debug)]
pub struct ConvertError {
kind: ErrorKind,
value: Option<Variant>,
}
impl ConvertError {
pub fn new(user_message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Custom(Some(user_message.into().into())),
..Default::default()
}
}
#[allow(dead_code)] pub(crate) fn with_kind(kind: ErrorKind) -> Self {
Self { kind, value: None }
}
pub(crate) fn with_kind_value<V>(kind: ErrorKind, value: V) -> Self
where
V: ToGodot,
{
Self {
kind,
value: Some(value.to_variant()),
}
}
pub fn with_error<E>(error: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
Self {
kind: ErrorKind::Custom(Some(error.into())),
..Default::default()
}
}
pub fn with_error_value<E, V>(error: E, value: V) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
V: ToGodot,
{
Self {
kind: ErrorKind::Custom(Some(error.into())),
value: Some(value.to_variant()),
}
}
pub fn cause(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
match &self.kind {
ErrorKind::Custom(Some(cause)) => Some(&**cause),
_ => None,
}
}
pub fn value(&self) -> Option<&Variant> {
self.value.as_ref()
}
pub fn into_erased(self) -> impl Error + Send + Sync {
ErasedConvertError::from(self)
}
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
pub(crate) fn kind(&self) -> &ErrorKind {
&self.kind
}
}
impl fmt::Display for ConvertError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(value) = &self.value {
write!(f, ": {value:?}")?;
}
Ok(())
}
}
impl Error for ConvertError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.cause().map(|v| v as &(dyn Error + 'static))
}
}
impl Default for ConvertError {
fn default() -> Self {
Self {
kind: ErrorKind::Custom(None),
value: None,
}
}
}
#[derive(Debug)]
pub(crate) struct ErasedConvertError {
kind: ErrorKind,
}
impl From<ConvertError> for ErasedConvertError {
fn from(v: ConvertError) -> Self {
let ConvertError { kind, .. } = v;
Self { kind }
}
}
impl fmt::Display for ErasedConvertError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)
}
}
impl Error for ErasedConvertError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.kind {
ErrorKind::Custom(Some(cause)) => Some(&**cause),
_ => None,
}
}
}
#[derive(Debug)]
pub(crate) enum ErrorKind {
FromGodot(FromGodotError),
FromFfi(FromFfiError),
FromVariant(FromVariantError),
Custom(Option<Cause>),
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FromGodot(from_godot) => write!(f, "{from_godot}"),
Self::FromVariant(from_variant) => write!(f, "{from_variant}"),
Self::FromFfi(from_ffi) => write!(f, "{from_ffi}"),
Self::Custom(cause) => match cause {
Some(c) => write!(f, "{c}"),
None => write!(f, "custom error"),
},
}
}
}
#[derive(Eq, PartialEq, Debug)]
pub(crate) enum FromGodotError {
BadArrayType(ArrayMismatch),
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
BadDictionaryType(DictionaryMismatch),
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
BadArrayTypeInt {
expected_int_type: &'static str,
value: i64,
},
InvalidEnum,
UnimplementedDynTrait {
trait_name: String,
class_name: String,
},
UnregisteredDynTrait { trait_name: String },
ZeroInstanceId,
}
impl FromGodotError {
pub fn into_error<V>(self, value: V) -> ConvertError
where
V: ToGodot,
{
ConvertError::with_kind_value(ErrorKind::FromGodot(self), value)
}
}
impl fmt::Display for FromGodotError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadArrayType(mismatch) => write!(f, "{mismatch}"),
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
Self::BadDictionaryType(mismatch) => write!(f, "{mismatch}"),
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
Self::BadArrayTypeInt {
expected_int_type,
value,
} => {
write!(
f,
"integer value {value} does not fit into Array<{expected_int_type}>"
)
}
Self::InvalidEnum => write!(f, "invalid engine enum value"),
Self::ZeroInstanceId => write!(f, "`InstanceId` cannot be 0"),
Self::UnimplementedDynTrait {
trait_name,
class_name,
} => {
write!(
f,
"none of the classes derived from `{class_name}` have been linked to trait `{trait_name}` with #[godot_dyn]"
)
}
FromGodotError::UnregisteredDynTrait { trait_name } => {
write!(
f,
"trait `{trait_name}` has not been registered with #[godot_dyn]"
)
}
}
}
}
#[derive(Eq, PartialEq, Debug)]
pub(crate) struct ArrayMismatch {
pub expected: ElementType,
pub actual: ElementType,
}
impl fmt::Display for ArrayMismatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ArrayMismatch { expected, actual } = self;
if expected.variant_type() != actual.variant_type() {
return write!(f, "expected array of type {expected:?}, got {actual:?}");
}
let exp_class = format!("{expected:?}");
let act_class = format!("{actual:?}");
write!(f, "expected array of type {exp_class}, got {act_class}")
}
}
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
#[derive(Eq, PartialEq, Debug)]
pub(crate) struct DictionaryMismatch {
pub expected_key: ElementType,
pub expected_value: ElementType,
pub actual_key: ElementType,
pub actual_value: ElementType,
}
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
impl fmt::Display for DictionaryMismatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let DictionaryMismatch {
expected_key,
expected_value,
actual_key,
actual_value,
} = self;
write!(
f,
"expected dictionary of type Dictionary<{expected_key:?}, {expected_value:?}>, got Dictionary<{actual_key:?}, {actual_value:?}>"
)
}
}
#[derive(Eq, PartialEq, Debug)]
#[non_exhaustive]
pub(crate) enum FromFfiError {
NullRawGd,
WrongObjectType,
I8,
U8,
I16,
U16,
I32,
U32,
}
impl FromFfiError {
pub fn into_error<V>(self, value: V) -> ConvertError
where
V: ToGodot,
{
ConvertError::with_kind_value(ErrorKind::FromFfi(self), value)
}
}
impl fmt::Display for FromFfiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let target = match self {
Self::NullRawGd => return write!(f, "`Gd` cannot be null"),
Self::WrongObjectType => {
return write!(f, "given object cannot be cast to target type");
}
Self::I8 => "i8",
Self::U8 => "u8",
Self::I16 => "i16",
Self::U16 => "u16",
Self::I32 => "i32",
Self::U32 => "u32",
};
write!(f, "`{target}` cannot store the given value")
}
}
#[derive(Eq, PartialEq, Debug)]
pub(crate) enum FromVariantError {
BadType {
expected: VariantType,
actual: VariantType,
},
WrongClass {
expected: ClassId,
},
DeadObject,
}
impl FromVariantError {
pub fn into_error<V>(self, value: V) -> ConvertError
where
V: ToGodot,
{
ConvertError::with_kind_value(ErrorKind::FromVariant(self), value)
}
}
impl fmt::Display for FromVariantError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadType { expected, actual } => {
write!(f, "cannot convert from {actual:?} to {expected:?}")
}
Self::WrongClass { expected } => {
write!(f, "cannot convert to class {expected}")
}
Self::DeadObject => write!(f, "variant holds object which is no longer alive"),
}
}
}
fn __ensure_send_sync() {
fn check<T: Send + Sync>() {}
check::<ErasedConvertError>();
}