Lines
90 %
Functions
53.33 %
Branches
100 %
//! Script-raise host native: the boundary bridge for in-guest raises.
//!
//! Since Tier 3.2, `(error 'code "msg")` and engine errors (commodity
//! mismatch) `throw $nomi_error` in-guest; this host fn is no longer the
//! raise primitive but the BOUNDARY BRIDGE (ADR-0026): the compiler wraps each
//! host-invoked body in a `try_table` that catches any uncaught `$nomi_error`,
//! reads its `code`/`message`, and `call $__nomi_raise`s here. The fn never
//! returns normally; it always produces `Err(wasmtime::Error::msg(...))`
//! carrying the `__nomi_raise:CODE:MSG` marker that
//! [`scripting::runtime::classify_runtime_error`] parses into a
//! `ScriptRaised { code, message }` for the wire envelope.
use scripting::runtime::{NOMI_RAISE_MARKER, read_string_arg};
use wasmtime::{ArrayRef, Caller, Linker, Rooted};
use crate::session::SessionData;
pub const REGISTERED_COMMANDS: &[&str] = &["error"];
pub fn register(linker: &mut Linker<SessionData>) -> wasmtime::Result<()> {
linker.func_wrap(
"nomi",
"__nomi_raise",
|mut caller: Caller<'_, SessionData>,
code_arg: Option<Rooted<ArrayRef>>,
msg_arg: Option<Rooted<ArrayRef>>|
-> wasmtime::Result<()> {
let code = read_string_arg(&mut caller, code_arg)?
.ok_or_else(|| wasmtime::Error::msg("error: missing :code arg"))?;
let message = read_string_arg(&mut caller, msg_arg)?.unwrap_or_default();
Err(raise_error(&code, &message))
},
)?;
Ok(())
}
/// Builds the marker-prefixed `wasmtime::Error` the classifier will parse.
/// Extracted so the special form's compile-time tests can assert the exact
/// wire format without spinning up a wasm engine.
pub fn raise_error(code: &str, message: &str) -> wasmtime::Error {
wasmtime::Error::msg(format!("{NOMI_RAISE_MARKER}{code}:{message}"))
#[cfg(test)]
mod tests {
use super::*;
use scripting::runtime::{EngineError, classify_runtime_error};
#[test]
fn raise_error_round_trips_through_classifier() {
let err = raise_error("no-such-account", "id=42");
match classify_runtime_error(&err) {
EngineError::ScriptRaised { code, message } => {
assert_eq!(code, "no-such-account");
assert_eq!(message, "id=42");
other => panic!("expected ScriptRaised, got {other:?}"),
fn raise_error_message_can_contain_colons() {
let err = raise_error("parse", "expected ':' at column 7");
assert_eq!(code, "parse");
assert_eq!(message, "expected ':' at column 7");
fn raise_error_empty_message_keeps_code() {
let err = raise_error("oops", "");
assert_eq!(code, "oops");
assert_eq!(message, "");