wasm3x 0.1.0

Safe, Wasmi/Wasmtime-shaped Rust bindings for the Wasm3 interpreter.
Documentation
//! Error handling for the safe Wasm3 bindings.

use core::fmt;
use std::ffi::CStr;

use wasm3x_sys as ffi;

/// A specialized [`Result`](core::result::Result) type for this crate.
pub type Result<T, E = Error> = core::result::Result<T, E>;

/// An error that can occur when working with the Wasm3 interpreter.
#[derive(Debug)]
pub struct Error(Box<ErrorRepr>);

#[derive(Debug)]
enum ErrorRepr {
    /// An error reported by the Wasm3 C-API (`M3Result`).
    Wasm3(String),
    /// A trap raised during execution of a Wasm function.
    Trap(String),
    /// A type or arity mismatch detected by the safe wrapper.
    Mismatch(String),
    /// An error raised by a host function.
    Host(Box<dyn std::error::Error + Send + Sync + 'static>),
    /// A generic wrapper error with a custom message.
    Message(String),
}

impl Error {
    /// Creates a new error from a custom message.
    pub fn new(message: impl Into<String>) -> Self {
        Self(Box::new(ErrorRepr::Message(message.into())))
    }

    /// Creates a host error wrapping an arbitrary error value.
    ///
    /// Use this to signal a trap from within a host function.
    pub fn host<E>(error: E) -> Self
    where
        E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
    {
        Self(Box::new(ErrorRepr::Host(error.into())))
    }

    /// Returns `true` if this error originates from a host function.
    pub fn is_host(&self) -> bool {
        matches!(*self.0, ErrorRepr::Host(_))
    }

    /// Returns `true` if this error represents a Wasm execution trap.
    pub fn is_trap(&self) -> bool {
        matches!(*self.0, ErrorRepr::Trap(_))
    }

    pub(crate) fn mismatch(message: impl Into<String>) -> Self {
        Self(Box::new(ErrorRepr::Mismatch(message.into())))
    }

    /// Converts a raw `M3Result` into a [`Result`].
    ///
    /// A null pointer denotes success. A non-null pointer references a static
    /// C string owned by the Wasm3 library.
    pub(crate) fn from_ffi(result: ffi::M3Result) -> Result<()> {
        if result.is_null() {
            return Ok(());
        }
        Err(Self(Box::new(ErrorRepr::Wasm3(ffi_message(result)))))
    }

    /// Converts a raw `M3Result` originating from a `m3_Call` into a trap error,
    /// enriched with `m3_GetErrorInfo` details when available.
    pub(crate) fn from_trap(runtime: ffi::IM3Runtime, result: ffi::M3Result) -> Self {
        debug_assert!(!result.is_null());
        let mut message = ffi_message(result);
        // Try to enrich the message with backtrace-style error info.
        unsafe {
            let mut info: ffi::M3ErrorInfo = core::mem::zeroed();
            ffi::m3_GetErrorInfo(runtime, &mut info);
            if !info.message.is_null() {
                let detail = CStr::from_ptr(info.message).to_string_lossy();
                if !detail.is_empty() && !message.contains(detail.as_ref()) {
                    message = format!("{message}: {detail}");
                }
            }
        }
        Self(Box::new(ErrorRepr::Trap(message)))
    }
}

/// Reads a non-null `M3Result` into an owned `String`.
fn ffi_message(result: ffi::M3Result) -> String {
    // SAFETY: Wasm3 error results are static, nul-terminated C strings.
    unsafe { CStr::from_ptr(result) }
        .to_string_lossy()
        .into_owned()
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &*self.0 {
            ErrorRepr::Wasm3(msg) => write!(f, "wasm3 error: {msg}"),
            ErrorRepr::Trap(msg) => write!(f, "wasm trap: {msg}"),
            ErrorRepr::Mismatch(msg) => write!(f, "type mismatch: {msg}"),
            ErrorRepr::Host(err) => write!(f, "host error: {err}"),
            ErrorRepr::Message(msg) => f.write_str(msg),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &*self.0 {
            ErrorRepr::Host(err) => Some(&**err),
            _ => None,
        }
    }
}