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}