Files
Oxicloud/wasm/oxicloud-plugin-hello/src/lib.rs
T

96 lines
3.4 KiB
Rust
Raw Normal View History

2026-06-16 17:57:57 -06:00
//! Example OxiCloud plugin — ABI v0 (M0 walking skeleton).
//!
2026-06-16 21:26:36 -06:00
//! The default build is the well-behaved "hello" plugin. It exports one handler
//! per event it subscribes to — `on_file_uploaded` and `on_user_login` — each of
//! which reads the event metadata, calls the host `log` function (the only
2026-06-16 17:57:57 -06:00
//! authority a plugin has), and returns `{"ok": true}`.
//!
2026-06-16 21:26:36 -06:00
//! Cargo features select the variants the host's tests load:
//! - `panic` / `sleep` / `net` — make `on_file_uploaded` misbehave (failure
//! isolation, timeout, network-denial tests);
//! - `wrong_abi` — `abi_version` returns 1 (load-rejection test);
//! - `omit_login` — drops the `on_user_login` export (missing-export test).
//!
//! See `scripts/build-plugin-hello.sh`.
2026-06-16 17:57:57 -06:00
use extism_pdk::*;
/// The one host function OxiCloud exposes, imported from its namespaced module.
#[host_fn("oxicloud:host:v0")]
extern "ExtismHost" {
fn log(level: String, message: String);
}
/// Required export: which ABI this plugin was built against. The host rejects
/// the plugin at load if this does not equal its own `OXICLOUD_PLUGIN_ABI`.
#[cfg(not(feature = "wrong_abi"))]
#[plugin_fn]
pub fn abi_version() -> FnResult<u32> {
Ok(0)
}
/// `wrong_abi` variant: claim an ABI the host does not speak.
#[cfg(feature = "wrong_abi")]
#[plugin_fn]
pub fn abi_version() -> FnResult<u32> {
Ok(1)
}
2026-06-16 21:26:36 -06:00
/// Handler for the `file.uploaded` event.
2026-06-16 17:57:57 -06:00
#[plugin_fn]
2026-06-16 21:26:36 -06:00
pub fn on_file_uploaded(input: String) -> FnResult<String> {
2026-06-16 17:57:57 -06:00
// --- misbehaving variants (compiled in only under their feature) ---------
#[cfg(feature = "panic")]
panic!("intentional panic: exercises host failure isolation");
#[cfg(feature = "sleep")]
{
// Busy-loop forever; the host's wall-clock timeout must cancel us.
let mut spin: u64 = 0;
loop {
spin = spin.wrapping_add(1);
std::hint::black_box(spin);
}
}
#[cfg(feature = "net")]
{
// Attempt an outbound HTTP call. The host grants no `allowed_hosts`, so
// Extism denies this before any socket is opened (offline-deterministic)
2026-06-16 21:26:36 -06:00
// and the error propagates out of the handler.
2026-06-16 17:57:57 -06:00
let req = HttpRequest::new("https://example.com/");
let _ = http::request::<()>(&req, None)?;
}
2026-06-16 21:26:36 -06:00
// --- well-behaved path ---------------------------------------------------
2026-06-16 17:57:57 -06:00
let ev: serde_json::Value = serde_json::from_str(&input)?;
let path = ev["payload"]["path"].as_str().unwrap_or("<unknown>");
let size = ev["payload"]["size"].as_u64().unwrap_or(0);
unsafe {
log(
"info".to_string(),
format!("hello plugin saw upload: {path} ({size} bytes)"),
)?;
}
2026-06-16 21:26:36 -06:00
Ok(serde_json::json!({ "ok": true }).to_string())
}
/// Handler for the `user.login` event. Dropped by the `omit_login` variant so
/// the host's missing-export validation has something to reject.
#[cfg(not(feature = "omit_login"))]
#[plugin_fn]
pub fn on_user_login(input: String) -> FnResult<String> {
let ev: serde_json::Value = serde_json::from_str(&input)?;
let user_id = ev["payload"]["user_id"].as_str().unwrap_or("<unknown>");
let first_login = ev["payload"]["first_login"].as_bool().unwrap_or(false);
2026-06-16 17:57:57 -06:00
2026-06-16 21:26:36 -06:00
unsafe {
log(
"info".to_string(),
format!("hello plugin saw login: user {user_id} (first_login={first_login})"),
)?;
}
2026-06-16 17:57:57 -06:00
Ok(serde_json::json!({ "ok": true }).to_string())
}