Skip to main content

samp_sdk/
error.rs

1//! Errors emitted by the AMX VM and the SA-MP `amx_*` functions.
2//!
3//! Codes correspond 1:1 to the `AMX_ERR_*` values from the original C header.
4//! Codes outside the known range (including 0 = success interpreted as
5//! failure in context) become [`AmxError::Unknown`] to avoid panicking.
6
7use std::error::Error;
8use std::fmt::{self, Display};
9
10/// Shortcut for `Result<T, AmxError>`.
11pub type AmxResult<T> = Result<T, AmxError>;
12
13/// Error returned by calls to SA-MP `amx_*` functions.
14///
15/// Built via `AmxError::from(code)` from the `i32` returned by the FFI;
16/// implements [`Display`] with English messages equivalent to the original C.
17#[derive(Debug)]
18pub enum AmxError {
19    Exit = 1,
20    Assert = 2,
21    StackError = 3,
22    Bounds = 4,
23    MemoryAccess = 5,
24    InvalidInstruction = 6,
25    StackLow = 7,
26    HeapLow = 8,
27    Callback = 9,
28    Native = 10,
29    Divide = 11,
30    Sleep = 12,
31    InvalidState = 13,
32    Memory = 16,
33    Format = 17,
34    Version = 18,
35    NotFound = 19,
36    Index = 20,
37    Debug = 21,
38    Init = 22,
39    UserData = 23,
40    InitJit = 24,
41    Params = 25,
42    Domain = 26,
43    General = 27,
44    Overlay = 28,
45    Unknown,
46}
47
48impl Display for AmxError {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        // Intentional wildcard: the match covers every variant; importing one
51        // by one would be noisy and would break silently when a new variant
52        // is added to the enum.
53        #[allow(clippy::enum_glob_use)]
54        use self::AmxError::*;
55
56        match self {
57            Exit => write!(f, "Forced exit"),
58            Assert => write!(f, "Assertion failed"),
59            StackError => write!(f, "Stack / heap collision"),
60            Bounds => write!(f, "Index out of bounds"),
61            MemoryAccess => write!(f, "Invalid memory access"),
62            InvalidInstruction => write!(f, "Invalid instruction"),
63            StackLow => write!(f, "Stack underflow"),
64            HeapLow => write!(f, "Heap underflow"),
65            Callback => write!(f, "No callback or invalid callback"),
66            Native => write!(f, "Native function failed"),
67            Divide => write!(f, "Divide by zero"),
68            Sleep => write!(f, "Go into sleepmode"),
69            InvalidState => write!(f, "No implementation for this state, no fall-back"),
70            Memory => write!(f, "Out of memory"),
71            Format => write!(f, "Invalid file format"),
72            Version => write!(f, "File is for a newer version of AMX"),
73            NotFound => write!(f, "Function not found"),
74            Index => write!(f, "Invalid index parameter (bad entry point)"),
75            Debug => write!(f, "Debugger cannot run"),
76            Init => write!(f, "AMX not initialize"),
77            UserData => write!(f, "Unable to set user data field"),
78            InitJit => write!(f, "Cannot initialize the JIT"),
79            Params => write!(f, "Parameter error"),
80            Domain => write!(f, "Domain error, expression result does not fit in range"),
81            General => write!(f, "General error (unknown or unspecific error)"),
82            Overlay => write!(f, "Overlays are unsupported (JIT) or uninitialized"),
83            Unknown => write!(f, "Unknown error"),
84        }
85    }
86}
87
88impl Error for AmxError {}
89
90impl From<i32> for AmxError {
91    fn from(error_code: i32) -> Self {
92        match error_code {
93            1 => AmxError::Exit,
94            2 => AmxError::Assert,
95            3 => AmxError::StackError,
96            4 => AmxError::Bounds,
97            5 => AmxError::MemoryAccess,
98            6 => AmxError::InvalidInstruction,
99            7 => AmxError::StackLow,
100            8 => AmxError::HeapLow,
101            9 => AmxError::Callback,
102            10 => AmxError::Native,
103            11 => AmxError::Divide,
104            12 => AmxError::Sleep,
105            13 => AmxError::InvalidState,
106            16 => AmxError::Memory,
107            17 => AmxError::Format,
108            18 => AmxError::Version,
109            19 => AmxError::NotFound,
110            20 => AmxError::Index,
111            21 => AmxError::Debug,
112            22 => AmxError::Init,
113            23 => AmxError::UserData,
114            24 => AmxError::InitJit,
115            25 => AmxError::Params,
116            26 => AmxError::Domain,
117            27 => AmxError::General,
118            28 => AmxError::Overlay,
119            _ => AmxError::Unknown,
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn from_code_maps_all_known_errors() {
130        let cases: &[(i32, &str)] = &[
131            (1, "Exit"),
132            (2, "Assert"),
133            (3, "StackError"),
134            (4, "Bounds"),
135            (5, "MemoryAccess"),
136            (6, "InvalidInstruction"),
137            (7, "StackLow"),
138            (8, "HeapLow"),
139            (9, "Callback"),
140            (10, "Native"),
141            (11, "Divide"),
142            (12, "Sleep"),
143            (13, "InvalidState"),
144            (16, "Memory"),
145            (17, "Format"),
146            (18, "Version"),
147            (19, "NotFound"),
148            (20, "Index"),
149            (21, "Debug"),
150            (22, "Init"),
151            (23, "UserData"),
152            (24, "InitJit"),
153            (25, "Params"),
154            (26, "Domain"),
155            (27, "General"),
156            (28, "Overlay"),
157        ];
158
159        for &(code, expected_name) in cases {
160            let err = AmxError::from(code);
161            assert_eq!(
162                format!("{err:?}"),
163                expected_name,
164                "code {code} should map to {expected_name}"
165            );
166        }
167    }
168
169    #[test]
170    fn unknown_codes_map_to_unknown() {
171        for code in [0, 14, 15, 29, 100, -1, i32::MAX] {
172            assert!(
173                matches!(AmxError::from(code), AmxError::Unknown),
174                "code {code} should be Unknown"
175            );
176        }
177    }
178
179    #[test]
180    fn display_messages_are_not_empty() {
181        let errors = [
182            AmxError::Exit,
183            AmxError::Bounds,
184            AmxError::Divide,
185            AmxError::NotFound,
186            AmxError::Unknown,
187        ];
188
189        for err in errors {
190            let msg = format!("{err}");
191            assert!(!msg.is_empty(), "{err:?} has empty message");
192        }
193    }
194
195    #[test]
196    fn implements_std_error() {
197        let err = AmxError::General;
198        let _: &dyn std::error::Error = &err;
199    }
200
201    #[test]
202    fn memory_access_display() {
203        let err = AmxError::MemoryAccess;
204        assert_eq!(format!("{err}"), "Invalid memory access");
205    }
206
207    #[test]
208    fn memory_error_display() {
209        let err = AmxError::Memory;
210        assert_eq!(format!("{err}"), "Out of memory");
211    }
212
213    #[test]
214    fn amx_result_ok() {
215        let result: AmxResult<i32> = Ok(42);
216        assert!(result.is_ok());
217    }
218
219    #[test]
220    fn amx_result_err() {
221        let result: AmxResult<i32> = Err(AmxError::General);
222        assert!(result.is_err());
223        let err = AmxError::General;
224        assert_eq!(
225            format!("{err}"),
226            "General error (unknown or unspecific error)"
227        );
228    }
229}