Skip to main content

rusteal_core/
error.rs

1use std::fmt;
2
3use rusteal_ffi::RustealErrorCode;
4
5#[derive(Debug)]
6pub enum RustealError {
7    ObjectDestroyed,
8    InvalidCast,
9    PropertyNotFound(String),
10    FunctionNotFound(String),
11    TypeMismatch,
12    NullArgument,
13    IndexOutOfRange,
14    InvalidOperation(String),
15    Internal(String),
16    BufferTooSmall,
17}
18
19impl fmt::Display for RustealError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            RustealError::ObjectDestroyed => write!(f, "object has been destroyed"),
23            RustealError::InvalidCast => write!(f, "invalid cast"),
24            RustealError::PropertyNotFound(name) => write!(f, "property not found: {name}"),
25            RustealError::FunctionNotFound(name) => write!(f, "function not found: {name}"),
26            RustealError::TypeMismatch => write!(f, "type mismatch"),
27            RustealError::NullArgument => write!(f, "null argument"),
28            RustealError::IndexOutOfRange => write!(f, "index out of range"),
29            RustealError::InvalidOperation(msg) => write!(f, "invalid operation: {msg}"),
30            RustealError::Internal(msg) => write!(f, "internal error: {msg}"),
31            RustealError::BufferTooSmall => write!(f, "buffer too small"),
32        }
33    }
34}
35
36impl std::error::Error for RustealError {}
37
38pub type RustealResult<T> = Result<T, RustealError>;
39
40pub fn check_ffi(code: RustealErrorCode) -> RustealResult<()> {
41    match code {
42        RustealErrorCode::Ok => Ok(()),
43        other => Err(RustealError::from(other)),
44    }
45}
46
47pub fn check_ffi_ctx(code: RustealErrorCode, context: &str) -> RustealResult<()> {
48    match code {
49        RustealErrorCode::Ok => Ok(()),
50        RustealErrorCode::PropertyNotFound => Err(RustealError::PropertyNotFound(context.into())),
51        RustealErrorCode::FunctionNotFound => Err(RustealError::FunctionNotFound(context.into())),
52        RustealErrorCode::InvalidOperation => Err(RustealError::InvalidOperation(context.into())),
53        other => Err(RustealError::from(other)),
54    }
55}
56
57#[inline(always)]
58pub fn ffi_infallible(code: RustealErrorCode) {
59    debug_assert_eq!(
60        code,
61        RustealErrorCode::Ok,
62        "FFI call returned {code:?} after pre-validation"
63    );
64}
65
66#[inline(always)]
67pub fn ffi_infallible_ctx(code: RustealErrorCode, ctx: &str) {
68    debug_assert_eq!(
69        code,
70        RustealErrorCode::Ok,
71        "FFI '{ctx}' returned {code:?} after pre-validation"
72    );
73}
74
75impl From<RustealErrorCode> for RustealError {
76    #[allow(clippy::match_same_arms)]
77    fn from(code: RustealErrorCode) -> Self {
78        match code {
79            RustealErrorCode::Ok => RustealError::Internal("unexpected Ok error code".into()),
80            RustealErrorCode::ObjectDestroyed => RustealError::ObjectDestroyed,
81            RustealErrorCode::InvalidCast => RustealError::InvalidCast,
82            RustealErrorCode::PropertyNotFound => RustealError::PropertyNotFound(String::new()),
83            RustealErrorCode::FunctionNotFound => RustealError::FunctionNotFound(String::new()),
84            RustealErrorCode::TypeMismatch => RustealError::TypeMismatch,
85            RustealErrorCode::NullArgument => RustealError::NullArgument,
86            RustealErrorCode::IndexOutOfRange => RustealError::IndexOutOfRange,
87            RustealErrorCode::InvalidOperation => RustealError::InvalidOperation(String::new()),
88            RustealErrorCode::InternalError => RustealError::Internal(String::new()),
89            RustealErrorCode::BufferTooSmall => RustealError::BufferTooSmall,
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn check_ffi_ok_returns_ok() {
100        assert!(check_ffi(RustealErrorCode::Ok).is_ok());
101    }
102
103    #[test]
104    fn check_ffi_errors_map_correctly() {
105        let cases = [
106            (RustealErrorCode::ObjectDestroyed, "ObjectDestroyed"),
107            (RustealErrorCode::InvalidCast, "InvalidCast"),
108            (RustealErrorCode::PropertyNotFound, "PropertyNotFound"),
109            (RustealErrorCode::FunctionNotFound, "FunctionNotFound"),
110            (RustealErrorCode::TypeMismatch, "TypeMismatch"),
111            (RustealErrorCode::NullArgument, "NullArgument"),
112            (RustealErrorCode::IndexOutOfRange, "IndexOutOfRange"),
113            (RustealErrorCode::InvalidOperation, "InvalidOperation"),
114            (RustealErrorCode::InternalError, "Internal"),
115        ];
116
117        for (code, expected_variant) in cases {
118            let err = check_ffi(code).unwrap_err();
119            let debug = format!("{err:?}");
120
121            assert!(
122                debug.starts_with(expected_variant),
123                "expected {expected_variant}, got {debug}"
124            );
125        }
126    }
127
128    #[test]
129    fn display_formats_are_human_readable() {
130        let err = RustealError::PropertyNotFound("Health".into());
131        assert_eq!(err.to_string(), "property not found: Health");
132    }
133}