rust-samp-sdk 3.0.0

Low-level FFI bindings for the SA-MP AMX virtual machine and open.mp native component ABI. Used internally by `rust-samp`; depend on it directly only if you need raw access without the higher-level macros and lifecycle.
Documentation
//! Errors emitted by the AMX VM and the SA-MP `amx_*` functions.
//!
//! Codes correspond 1:1 to the `AMX_ERR_*` values from the original C header.
//! Codes outside the known range (including 0 = success interpreted as
//! failure in context) become [`AmxError::Unknown`] to avoid panicking.

use std::error::Error;
use std::fmt::{self, Display};

/// Shortcut for `Result<T, AmxError>`.
pub type AmxResult<T> = Result<T, AmxError>;

/// Error returned by calls to SA-MP `amx_*` functions.
///
/// Built via `AmxError::from(code)` from the `i32` returned by the FFI;
/// implements [`Display`] with English messages equivalent to the original C.
#[derive(Debug)]
pub enum AmxError {
    Exit = 1,
    Assert = 2,
    StackError = 3,
    Bounds = 4,
    MemoryAccess = 5,
    InvalidInstruction = 6,
    StackLow = 7,
    HeapLow = 8,
    Callback = 9,
    Native = 10,
    Divide = 11,
    Sleep = 12,
    InvalidState = 13,
    Memory = 16,
    Format = 17,
    Version = 18,
    NotFound = 19,
    Index = 20,
    Debug = 21,
    Init = 22,
    UserData = 23,
    InitJit = 24,
    Params = 25,
    Domain = 26,
    General = 27,
    Overlay = 28,
    Unknown,
}

impl Display for AmxError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Intentional wildcard: the match covers every variant; importing one
        // by one would be noisy and would break silently when a new variant
        // is added to the enum.
        #[allow(clippy::enum_glob_use)]
        use self::AmxError::*;

        match self {
            Exit => write!(f, "Forced exit"),
            Assert => write!(f, "Assertion failed"),
            StackError => write!(f, "Stack / heap collision"),
            Bounds => write!(f, "Index out of bounds"),
            MemoryAccess => write!(f, "Invalid memory access"),
            InvalidInstruction => write!(f, "Invalid instruction"),
            StackLow => write!(f, "Stack underflow"),
            HeapLow => write!(f, "Heap underflow"),
            Callback => write!(f, "No callback or invalid callback"),
            Native => write!(f, "Native function failed"),
            Divide => write!(f, "Divide by zero"),
            Sleep => write!(f, "Go into sleepmode"),
            InvalidState => write!(f, "No implementation for this state, no fall-back"),
            Memory => write!(f, "Out of memory"),
            Format => write!(f, "Invalid file format"),
            Version => write!(f, "File is for a newer version of AMX"),
            NotFound => write!(f, "Function not found"),
            Index => write!(f, "Invalid index parameter (bad entry point)"),
            Debug => write!(f, "Debugger cannot run"),
            Init => write!(f, "AMX not initialize"),
            UserData => write!(f, "Unable to set user data field"),
            InitJit => write!(f, "Cannot initialize the JIT"),
            Params => write!(f, "Parameter error"),
            Domain => write!(f, "Domain error, expression result does not fit in range"),
            General => write!(f, "General error (unknown or unspecific error)"),
            Overlay => write!(f, "Overlays are unsupported (JIT) or uninitialized"),
            Unknown => write!(f, "Unknown error"),
        }
    }
}

impl Error for AmxError {}

impl From<i32> for AmxError {
    fn from(error_code: i32) -> Self {
        match error_code {
            1 => AmxError::Exit,
            2 => AmxError::Assert,
            3 => AmxError::StackError,
            4 => AmxError::Bounds,
            5 => AmxError::MemoryAccess,
            6 => AmxError::InvalidInstruction,
            7 => AmxError::StackLow,
            8 => AmxError::HeapLow,
            9 => AmxError::Callback,
            10 => AmxError::Native,
            11 => AmxError::Divide,
            12 => AmxError::Sleep,
            13 => AmxError::InvalidState,
            16 => AmxError::Memory,
            17 => AmxError::Format,
            18 => AmxError::Version,
            19 => AmxError::NotFound,
            20 => AmxError::Index,
            21 => AmxError::Debug,
            22 => AmxError::Init,
            23 => AmxError::UserData,
            24 => AmxError::InitJit,
            25 => AmxError::Params,
            26 => AmxError::Domain,
            27 => AmxError::General,
            28 => AmxError::Overlay,
            _ => AmxError::Unknown,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn from_code_maps_all_known_errors() {
        let cases: &[(i32, &str)] = &[
            (1, "Exit"),
            (2, "Assert"),
            (3, "StackError"),
            (4, "Bounds"),
            (5, "MemoryAccess"),
            (6, "InvalidInstruction"),
            (7, "StackLow"),
            (8, "HeapLow"),
            (9, "Callback"),
            (10, "Native"),
            (11, "Divide"),
            (12, "Sleep"),
            (13, "InvalidState"),
            (16, "Memory"),
            (17, "Format"),
            (18, "Version"),
            (19, "NotFound"),
            (20, "Index"),
            (21, "Debug"),
            (22, "Init"),
            (23, "UserData"),
            (24, "InitJit"),
            (25, "Params"),
            (26, "Domain"),
            (27, "General"),
            (28, "Overlay"),
        ];

        for &(code, expected_name) in cases {
            let err = AmxError::from(code);
            assert_eq!(
                format!("{err:?}"),
                expected_name,
                "code {code} should map to {expected_name}"
            );
        }
    }

    #[test]
    fn unknown_codes_map_to_unknown() {
        for code in [0, 14, 15, 29, 100, -1, i32::MAX] {
            assert!(
                matches!(AmxError::from(code), AmxError::Unknown),
                "code {code} should be Unknown"
            );
        }
    }

    #[test]
    fn display_messages_are_not_empty() {
        let errors = [
            AmxError::Exit,
            AmxError::Bounds,
            AmxError::Divide,
            AmxError::NotFound,
            AmxError::Unknown,
        ];

        for err in errors {
            let msg = format!("{err}");
            assert!(!msg.is_empty(), "{err:?} has empty message");
        }
    }

    #[test]
    fn implements_std_error() {
        let err = AmxError::General;
        let _: &dyn std::error::Error = &err;
    }

    #[test]
    fn memory_access_display() {
        let err = AmxError::MemoryAccess;
        assert_eq!(format!("{err}"), "Invalid memory access");
    }

    #[test]
    fn memory_error_display() {
        let err = AmxError::Memory;
        assert_eq!(format!("{err}"), "Out of memory");
    }

    #[test]
    fn amx_result_ok() {
        let result: AmxResult<i32> = Ok(42);
        assert!(result.is_ok());
    }

    #[test]
    fn amx_result_err() {
        let result: AmxResult<i32> = Err(AmxError::General);
        assert!(result.is_err());
        let err = AmxError::General;
        assert_eq!(
            format!("{err}"),
            "General error (unknown or unspecific error)"
        );
    }
}