Lines
88 %
Functions
61.54 %
Branches
100 %
//! Diagnostic / session-introspection natives.
//!
//! Distinct from the 30 server-command bindings: these expose facts about
//! the rpc channel itself (protocol version, authenticated user) that don't
//! pass through `server::command::*`. Useful as a connection sanity check the
//! emacs client can call on connect to verify it's talking to the right
//! server, and as the smallest possible end-to-end proof that the host-fn
//! dispatch path through `SessionData` works before any DB-touching native
//! lands.
use scripting::runtime::alloc_string_ref;
use wasmtime::{ArrayRef, Caller, Linker, Rooted};
use crate::session::SessionData;
/// Wire-level protocol revision the server speaks. Bumped when an
/// incompatible change to the request/response envelope or capture protocol
/// ships. The emacs client can refuse to talk to a mismatched server.
pub const PROTOCOL_VERSION: i32 = 1;
/// Names exposed via `(<name>)` in nomiscript. Not counted in the plan's
/// 30 server-command surface — this is a separate, smaller diagnostic
/// surface that emacs / cli / tui all share.
pub const REGISTERED_NATIVES: &[&str] = &["rpc-protocol-version", "rpc-session-user-id"];
/// Returns the `HostFnSpec` list the nomiscript compiler needs in order to
/// recognise the meta natives during compilation. Mirrors the linker
/// registrations in [`register`] — both must stay in sync, hence one place.
pub fn register(linker: &mut Linker<SessionData>) -> wasmtime::Result<()> {
linker.func_wrap(
"nomi",
"rpc_protocol_version",
|_caller: Caller<'_, SessionData>| -> i32 { PROTOCOL_VERSION },
)?;
"rpc_session_user_id_capture",
|mut caller: Caller<'_, SessionData>| -> wasmtime::Result<Option<Rooted<ArrayRef>>> {
let uid_string = caller.data().ctx().user_id.to_string();
Ok(Some(alloc_string_ref(&mut caller, uid_string.as_bytes())?))
},
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ctx::ScriptCtx;
use scripting::runtime::{EngineOpts, build_engine, compile_wat};
use uuid::Uuid;
use wasmtime::Store;
fn run_module_with_user(uid: Uuid, wat: &str) -> (SessionData, i32) {
let engine = build_engine(EngineOpts::baseline().with_fuel()).unwrap();
let module = compile_wat(&engine, wat).unwrap();
let mut linker: Linker<SessionData> = Linker::new(&engine);
register(&mut linker).unwrap();
let mut store: Store<SessionData> = Store::new(
&engine,
SessionData::new(
ScriptCtx::new(uid),
std::sync::Arc::new(std::sync::Mutex::new(String::new())),
),
);
store.set_fuel(100_000).unwrap();
store.set_epoch_deadline(1);
let instance = linker.instantiate(&mut store, &module).unwrap();
let func = instance
.get_typed_func::<(), i32>(&mut store, "nomi-eval")
.unwrap();
let result = func.call(&mut store, ()).unwrap();
(store.into_data(), result)
#[test]
fn protocol_version_native_returns_constant() {
// Smoke test for `rpc_protocol_version` — wat returns the i32
// directly from nomi-eval so the test stays decoupled from the
// anyref decoder that the rpc Session uses in production.
let wat = r#"
(module
(import "nomi" "rpc_protocol_version" (func $ver (result i32)))
(func (export "nomi-eval") (result i32) (call $ver)))
"#;
let (_, value) = run_module_with_user(Uuid::nil(), wat);
assert_eq!(value, PROTOCOL_VERSION);
fn registered_natives_are_kebab_case() {
for name in REGISTERED_NATIVES {
assert!(
name.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'),
"non-kebab-case meta native: {name}"