rusteal-core 0.6.1

Safe Rust runtime for rusteal (object refs, lifecycle, dynamic calls)
Documentation
use std::fmt;

use rusteal_ffi::RustealErrorCode;

#[derive(Debug)]
pub enum RustealError {
    ObjectDestroyed,
    InvalidCast,
    PropertyNotFound(String),
    FunctionNotFound(String),
    TypeMismatch,
    NullArgument,
    IndexOutOfRange,
    InvalidOperation(String),
    Internal(String),
    BufferTooSmall,
}

impl fmt::Display for RustealError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            RustealError::ObjectDestroyed => write!(f, "object has been destroyed"),
            RustealError::InvalidCast => write!(f, "invalid cast"),
            RustealError::PropertyNotFound(name) => write!(f, "property not found: {name}"),
            RustealError::FunctionNotFound(name) => write!(f, "function not found: {name}"),
            RustealError::TypeMismatch => write!(f, "type mismatch"),
            RustealError::NullArgument => write!(f, "null argument"),
            RustealError::IndexOutOfRange => write!(f, "index out of range"),
            RustealError::InvalidOperation(msg) => write!(f, "invalid operation: {msg}"),
            RustealError::Internal(msg) => write!(f, "internal error: {msg}"),
            RustealError::BufferTooSmall => write!(f, "buffer too small"),
        }
    }
}

impl std::error::Error for RustealError {}

pub type RustealResult<T> = Result<T, RustealError>;

pub fn check_ffi(code: RustealErrorCode) -> RustealResult<()> {
    match code {
        RustealErrorCode::Ok => Ok(()),
        other => Err(RustealError::from(other)),
    }
}

pub fn check_ffi_ctx(code: RustealErrorCode, context: &str) -> RustealResult<()> {
    match code {
        RustealErrorCode::Ok => Ok(()),
        RustealErrorCode::PropertyNotFound => Err(RustealError::PropertyNotFound(context.into())),
        RustealErrorCode::FunctionNotFound => Err(RustealError::FunctionNotFound(context.into())),
        RustealErrorCode::InvalidOperation => Err(RustealError::InvalidOperation(context.into())),
        other => Err(RustealError::from(other)),
    }
}

#[inline(always)]
pub fn ffi_infallible(code: RustealErrorCode) {
    debug_assert_eq!(
        code,
        RustealErrorCode::Ok,
        "FFI call returned {code:?} after pre-validation"
    );
}

#[inline(always)]
pub fn ffi_infallible_ctx(code: RustealErrorCode, ctx: &str) {
    debug_assert_eq!(
        code,
        RustealErrorCode::Ok,
        "FFI '{ctx}' returned {code:?} after pre-validation"
    );
}

impl From<RustealErrorCode> for RustealError {
    #[allow(clippy::match_same_arms)]
    fn from(code: RustealErrorCode) -> Self {
        match code {
            RustealErrorCode::Ok => RustealError::Internal("unexpected Ok error code".into()),
            RustealErrorCode::ObjectDestroyed => RustealError::ObjectDestroyed,
            RustealErrorCode::InvalidCast => RustealError::InvalidCast,
            RustealErrorCode::PropertyNotFound => RustealError::PropertyNotFound(String::new()),
            RustealErrorCode::FunctionNotFound => RustealError::FunctionNotFound(String::new()),
            RustealErrorCode::TypeMismatch => RustealError::TypeMismatch,
            RustealErrorCode::NullArgument => RustealError::NullArgument,
            RustealErrorCode::IndexOutOfRange => RustealError::IndexOutOfRange,
            RustealErrorCode::InvalidOperation => RustealError::InvalidOperation(String::new()),
            RustealErrorCode::InternalError => RustealError::Internal(String::new()),
            RustealErrorCode::BufferTooSmall => RustealError::BufferTooSmall,
        }
    }
}

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

    #[test]
    fn check_ffi_ok_returns_ok() {
        assert!(check_ffi(RustealErrorCode::Ok).is_ok());
    }

    #[test]
    fn check_ffi_errors_map_correctly() {
        let cases = [
            (RustealErrorCode::ObjectDestroyed, "ObjectDestroyed"),
            (RustealErrorCode::InvalidCast, "InvalidCast"),
            (RustealErrorCode::PropertyNotFound, "PropertyNotFound"),
            (RustealErrorCode::FunctionNotFound, "FunctionNotFound"),
            (RustealErrorCode::TypeMismatch, "TypeMismatch"),
            (RustealErrorCode::NullArgument, "NullArgument"),
            (RustealErrorCode::IndexOutOfRange, "IndexOutOfRange"),
            (RustealErrorCode::InvalidOperation, "InvalidOperation"),
            (RustealErrorCode::InternalError, "Internal"),
        ];

        for (code, expected_variant) in cases {
            let err = check_ffi(code).unwrap_err();
            let debug = format!("{err:?}");

            assert!(
                debug.starts_with(expected_variant),
                "expected {expected_variant}, got {debug}"
            );
        }
    }

    #[test]
    fn display_formats_are_human_readable() {
        let err = RustealError::PropertyNotFound("Health".into());
        assert_eq!(err.to_string(), "property not found: Health");
    }
}