#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(all(feature = "wasm-alloc", not(feature = "std")))]
#[global_allocator]
static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc;
#[cfg(all(not(feature = "std"), target_arch = "wasm32"))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
core::arch::wasm32::unreachable()
}
#[cfg(all(not(feature = "std"), not(target_arch = "wasm32")))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
pub mod bindings {
extern crate alloc;
wit_bindgen::generate!({
world: "zlayer-plugin-minimal",
path: "wit",
export_macro_name: "export_handler",
pub_export_macro: true,
});
}
pub mod prelude {
pub use crate::bindings::exports::zlayer::plugin::handler::{
Capabilities, Guest as Handler, HandleResult, InitError, PluginInfo, PluginRequest,
};
pub use crate::bindings::zlayer::plugin::plugin_metadata::Version;
pub use crate::bindings::zlayer::plugin::request_types::PluginResponse;
pub use crate::bindings::zlayer::plugin::common::{Error, KeyValue};
pub use crate::bindings::zlayer::plugin::config;
pub use crate::bindings::zlayer::plugin::keyvalue;
pub use crate::bindings::zlayer::plugin::logging;
pub use crate::bindings::zlayer::plugin::metrics;
pub use crate::bindings::zlayer::plugin::secrets;
pub use crate::bindings::export_handler;
pub use alloc::format;
pub use alloc::string::String;
pub use alloc::vec;
pub use alloc::vec::Vec;
}
#[cfg(feature = "testing")]
pub mod testing;
pub mod response {
use crate::bindings::zlayer::plugin::request_types::PluginResponse;
use crate::bindings::zlayer::plugin::common::KeyValue;
use alloc::string::String;
use alloc::vec::Vec;
#[must_use]
pub fn json(body: &[u8]) -> PluginResponse {
PluginResponse {
status: 200,
headers: alloc::vec![KeyValue {
key: String::from("Content-Type"),
value: String::from("application/json"),
}],
body: body.to_vec(),
}
}
#[must_use]
pub fn text(body: &str) -> PluginResponse {
PluginResponse {
status: 200,
headers: alloc::vec![KeyValue {
key: String::from("Content-Type"),
value: String::from("text/plain; charset=utf-8"),
}],
body: body.as_bytes().to_vec(),
}
}
#[must_use]
pub fn ok() -> PluginResponse {
PluginResponse {
status: 200,
headers: Vec::new(),
body: Vec::new(),
}
}
#[must_use]
pub fn not_found() -> PluginResponse {
PluginResponse {
status: 404,
headers: alloc::vec![KeyValue {
key: String::from("Content-Type"),
value: String::from("text/plain"),
}],
body: b"Not Found".to_vec(),
}
}
#[must_use]
pub fn bad_request(message: &str) -> PluginResponse {
PluginResponse {
status: 400,
headers: alloc::vec![KeyValue {
key: String::from("Content-Type"),
value: String::from("text/plain"),
}],
body: message.as_bytes().to_vec(),
}
}
#[must_use]
pub fn internal_error(message: &str) -> PluginResponse {
PluginResponse {
status: 500,
headers: alloc::vec![KeyValue {
key: String::from("Content-Type"),
value: String::from("text/plain"),
}],
body: message.as_bytes().to_vec(),
}
}
#[must_use]
pub fn custom(status: u16, headers: Vec<KeyValue>, body: Vec<u8>) -> PluginResponse {
PluginResponse {
status,
headers,
body,
}
}
}
pub mod metadata {
use crate::bindings::exports::zlayer::plugin::handler::PluginInfo;
use crate::bindings::zlayer::plugin::common::KeyValue;
use crate::bindings::zlayer::plugin::plugin_metadata::Version;
use alloc::string::String;
use alloc::vec::Vec;
pub struct PluginInfoBuilder {
id: String,
name: String,
version: Version,
description: String,
author: String,
license: Option<String>,
homepage: Option<String>,
metadata: Vec<KeyValue>,
}
impl PluginInfoBuilder {
#[must_use]
pub fn new(id: &str, name: &str, author: &str) -> Self {
Self {
id: String::from(id),
name: String::from(name),
version: Version {
major: 0,
minor: 1,
patch: 0,
pre_release: None,
},
description: String::new(),
author: String::from(author),
license: None,
homepage: None,
metadata: Vec::new(),
}
}
#[must_use]
pub fn version(mut self, major: u32, minor: u32, patch: u32) -> Self {
self.version = Version {
major,
minor,
patch,
pre_release: None,
};
self
}
#[must_use]
pub fn version_prerelease(
mut self,
major: u32,
minor: u32,
patch: u32,
prerelease: &str,
) -> Self {
self.version = Version {
major,
minor,
patch,
pre_release: Some(String::from(prerelease)),
};
self
}
#[must_use]
pub fn description(mut self, desc: &str) -> Self {
self.description = String::from(desc);
self
}
#[must_use]
pub fn license(mut self, license: &str) -> Self {
self.license = Some(String::from(license));
self
}
#[must_use]
pub fn homepage(mut self, url: &str) -> Self {
self.homepage = Some(String::from(url));
self
}
#[must_use]
pub fn meta(mut self, key: &str, value: &str) -> Self {
self.metadata.push(KeyValue {
key: String::from(key),
value: String::from(value),
});
self
}
#[must_use]
pub fn build(self) -> PluginInfo {
PluginInfo {
id: self.id,
name: self.name,
version: self.version,
description: self.description,
author: self.author,
license: self.license,
homepage: self.homepage,
metadata: self.metadata,
}
}
}
}
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone)]
pub struct ConfigError {
pub code: String,
pub message: String,
}
impl ConfigError {
#[must_use]
pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
code: code.into(),
message: message.into(),
}
}
#[must_use]
pub fn not_found(key: &str) -> Self {
Self::new("not_found", alloc::format!("config key '{}' not found", key))
}
}
impl core::fmt::Display for ConfigError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}: {}", self.code, self.message)
}
}
#[derive(Debug, Clone)]
pub enum KvError {
NotFound,
ValueTooLarge,
QuotaExceeded,
InvalidKey,
Storage(String),
}
impl KvError {
fn from_wit(err: bindings::zlayer::plugin::keyvalue::KvError) -> Self {
use bindings::zlayer::plugin::keyvalue::KvError as WitKvError;
match err {
WitKvError::NotFound => KvError::NotFound,
WitKvError::ValueTooLarge => KvError::ValueTooLarge,
WitKvError::QuotaExceeded => KvError::QuotaExceeded,
WitKvError::InvalidKey => KvError::InvalidKey,
WitKvError::Storage(msg) => KvError::Storage(msg),
}
}
}
impl core::fmt::Display for KvError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
KvError::NotFound => write!(f, "key not found"),
KvError::ValueTooLarge => write!(f, "value too large"),
KvError::QuotaExceeded => write!(f, "storage quota exceeded"),
KvError::InvalidKey => write!(f, "invalid key format"),
KvError::Storage(msg) => write!(f, "storage error: {msg}"),
}
}
}
#[derive(Debug, Clone)]
pub struct SecretError {
pub code: String,
pub message: String,
}
impl SecretError {
#[must_use]
pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
code: code.into(),
message: message.into(),
}
}
#[must_use]
pub fn not_found(name: &str) -> Self {
Self::new("not_found", alloc::format!("secret '{name}' not found"))
}
}
impl core::fmt::Display for SecretError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}: {}", self.code, self.message)
}
}
pub mod config {
use super::*;
use crate::bindings::zlayer::plugin::config as host_config;
#[must_use]
pub fn get(key: &str) -> Option<String> {
host_config::get(key)
}
pub fn get_required(key: &str) -> Result<String, ConfigError> {
host_config::get(key).ok_or_else(|| ConfigError::not_found(key))
}
#[must_use]
pub fn get_bool(key: &str) -> Option<bool> {
host_config::get_bool(key)
}
#[must_use]
pub fn get_int(key: &str) -> Option<i64> {
host_config::get_int(key)
}
#[must_use]
pub fn get_float(key: &str) -> Option<f64> {
host_config::get_float(key)
}
#[must_use]
pub fn exists(key: &str) -> bool {
host_config::exists(key)
}
#[must_use]
pub fn get_many(keys: &[&str]) -> Vec<(String, String)> {
let keys_vec: Vec<String> = keys.iter().map(|k| String::from(*k)).collect();
host_config::get_many(&keys_vec)
}
#[must_use]
pub fn get_prefix(prefix: &str) -> Vec<(String, String)> {
host_config::get_prefix(prefix)
}
#[must_use]
pub fn get_all() -> String {
let items = host_config::get_prefix("");
let mut result = String::from("{");
for (i, (key, value)) in items.iter().enumerate() {
if i > 0 {
result.push_str(", ");
}
result.push('"');
result.push_str(key);
result.push_str("\": \"");
result.push_str(value);
result.push('"');
}
result.push('}');
result
}
}
pub mod kv {
use super::*;
use alloc::string::ToString;
use crate::bindings::zlayer::plugin::keyvalue as host_kv;
pub fn get(bucket: &str, key: &str) -> Result<Option<Vec<u8>>, KvError> {
let full_key = make_key(bucket, key);
host_kv::get(&full_key).map_err(KvError::from_wit)
}
pub fn get_string(bucket: &str, key: &str) -> Result<Option<String>, KvError> {
let full_key = make_key(bucket, key);
host_kv::get_string(&full_key).map_err(KvError::from_wit)
}
pub fn set(bucket: &str, key: &str, value: &[u8]) -> Result<(), KvError> {
let full_key = make_key(bucket, key);
host_kv::set(&full_key, value).map_err(KvError::from_wit)
}
pub fn set_string(bucket: &str, key: &str, value: &str) -> Result<(), KvError> {
let full_key = make_key(bucket, key);
host_kv::set_string(&full_key, value).map_err(KvError::from_wit)
}
pub fn delete(bucket: &str, key: &str) -> Result<bool, KvError> {
let full_key = make_key(bucket, key);
host_kv::delete(&full_key).map_err(KvError::from_wit)
}
pub fn keys(bucket: &str, prefix: &str) -> Result<Vec<String>, KvError> {
let full_prefix = make_key(bucket, prefix);
let all_keys = host_kv::list_keys(&full_prefix).map_err(KvError::from_wit)?;
let bucket_prefix = alloc::format!("{bucket}/");
Ok(all_keys
.into_iter()
.map(|k| k.strip_prefix(&bucket_prefix).unwrap_or(&k).to_string())
.collect())
}
#[must_use]
pub fn exists(bucket: &str, key: &str) -> bool {
let full_key = make_key(bucket, key);
host_kv::exists(&full_key)
}
pub fn set_with_ttl(
bucket: &str,
key: &str,
value: &[u8],
ttl_secs: u64,
) -> Result<(), KvError> {
let full_key = make_key(bucket, key);
let ttl_ns = ttl_secs.saturating_mul(1_000_000_000);
host_kv::set_with_ttl(&full_key, value, ttl_ns).map_err(KvError::from_wit)
}
pub fn increment(bucket: &str, key: &str, delta: i64) -> Result<i64, KvError> {
let full_key = make_key(bucket, key);
host_kv::increment(&full_key, delta).map_err(KvError::from_wit)
}
pub fn compare_and_swap(
bucket: &str,
key: &str,
expected: Option<&[u8]>,
new_value: &[u8],
) -> Result<bool, KvError> {
let full_key = make_key(bucket, key);
let expected_vec = expected.map(|e| e.to_vec());
host_kv::compare_and_swap(&full_key, expected_vec.as_deref(), new_value)
.map_err(KvError::from_wit)
}
fn make_key(bucket: &str, key: &str) -> String {
alloc::format!("{bucket}/{key}")
}
}
pub mod log {
use crate::bindings::zlayer::plugin::common::KeyValue;
use crate::bindings::zlayer::plugin::logging as host_log;
use alloc::string::String;
use alloc::vec::Vec;
pub use crate::bindings::zlayer::plugin::logging::Level as LogLevel;
pub fn trace(msg: &str) {
host_log::trace(msg);
}
pub fn debug(msg: &str) {
host_log::debug(msg);
}
pub fn info(msg: &str) {
host_log::info(msg);
}
pub fn warn(msg: &str) {
host_log::warn(msg);
}
pub fn error(msg: &str) {
host_log::error(msg);
}
pub fn log(level: LogLevel, msg: &str) {
host_log::log(level, msg);
}
pub fn log_structured(level: LogLevel, msg: &str, fields: &[(String, String)]) {
let kv_fields: Vec<KeyValue> = fields
.iter()
.map(|(k, v)| KeyValue {
key: k.clone(),
value: v.clone(),
})
.collect();
host_log::log_structured(level, msg, &kv_fields);
}
#[must_use]
pub fn is_enabled(level: LogLevel) -> bool {
host_log::is_enabled(level)
}
}
pub mod secrets {
use super::*;
use crate::bindings::zlayer::plugin::secrets as host_secrets;
pub fn get(name: &str) -> Result<Option<String>, SecretError> {
host_secrets::get(name).map_err(|e| SecretError::new(&e.code, &e.message))
}
pub fn get_required(name: &str) -> Result<String, SecretError> {
host_secrets::get_required(name).map_err(|e| SecretError::new(&e.code, &e.message))
}
#[must_use]
pub fn exists(name: &str) -> bool {
host_secrets::exists(name)
}
#[must_use]
pub fn list_names() -> Vec<String> {
host_secrets::list_names()
}
}
pub mod metrics {
use crate::bindings::zlayer::plugin::common::KeyValue;
use crate::bindings::zlayer::plugin::metrics as host_metrics;
use alloc::string::String;
use alloc::vec::Vec;
pub fn counter_inc(name: &str, value: u64) {
host_metrics::counter_inc(name, value);
}
pub fn counter_inc_labeled(name: &str, value: u64, labels: &[(String, String)]) {
let kv_labels: Vec<KeyValue> = labels
.iter()
.map(|(k, v)| KeyValue {
key: k.clone(),
value: v.clone(),
})
.collect();
host_metrics::counter_inc_labeled(name, value, &kv_labels);
}
pub fn gauge_set(name: &str, value: f64) {
host_metrics::gauge_set(name, value);
}
pub fn gauge_set_labeled(name: &str, value: f64, labels: &[(String, String)]) {
let kv_labels: Vec<KeyValue> = labels
.iter()
.map(|(k, v)| KeyValue {
key: k.clone(),
value: v.clone(),
})
.collect();
host_metrics::gauge_set_labeled(name, value, &kv_labels);
}
pub fn gauge_add(name: &str, delta: f64) {
host_metrics::gauge_add(name, delta);
}
pub fn histogram_observe(name: &str, value: f64) {
host_metrics::histogram_observe(name, value);
}
pub fn histogram_observe_labeled(name: &str, value: f64, labels: &[(String, String)]) {
let kv_labels: Vec<KeyValue> = labels
.iter()
.map(|(k, v)| KeyValue {
key: k.clone(),
value: v.clone(),
})
.collect();
host_metrics::histogram_observe_labeled(name, value, &kv_labels);
}
pub fn record_duration(name: &str, duration_ns: u64) {
host_metrics::record_duration(name, duration_ns);
}
pub fn record_duration_labeled(name: &str, duration_ns: u64, labels: &[(String, String)]) {
let kv_labels: Vec<KeyValue> = labels
.iter()
.map(|(k, v)| KeyValue {
key: k.clone(),
value: v.clone(),
})
.collect();
host_metrics::record_duration_labeled(name, duration_ns, &kv_labels);
}
}
pub trait ZLayerPlugin: Default {
fn init(&self) -> Result<(), String> {
Ok(())
}
fn info(&self) -> bindings::exports::zlayer::plugin::handler::PluginInfo;
fn handle(&self, event_type: &str, payload: &[u8]) -> Result<Vec<u8>, String>;
fn shutdown(&self) -> Result<(), String> {
Ok(())
}
fn capabilities(&self) -> bindings::exports::zlayer::plugin::handler::Capabilities {
use bindings::exports::zlayer::plugin::handler::Capabilities;
Capabilities::CONFIG | Capabilities::LOGGING
}
}
#[macro_export]
macro_rules! zlayer_plugin {
($plugin_type:ty) => {
struct ZLayerPluginExport;
impl $crate::bindings::exports::zlayer::plugin::handler::Guest for ZLayerPluginExport {
fn init() -> Result<
$crate::bindings::exports::zlayer::plugin::handler::Capabilities,
$crate::bindings::exports::zlayer::plugin::handler::InitError,
> {
let plugin = <$plugin_type as Default>::default();
match <$plugin_type as $crate::ZLayerPlugin>::init(&plugin) {
Ok(()) => Ok(<$plugin_type as $crate::ZLayerPlugin>::capabilities(&plugin)),
Err(msg) => Err(
$crate::bindings::exports::zlayer::plugin::handler::InitError::Failed(msg),
),
}
}
fn info() -> $crate::bindings::exports::zlayer::plugin::handler::PluginInfo {
let plugin = <$plugin_type as Default>::default();
<$plugin_type as $crate::ZLayerPlugin>::info(&plugin)
}
fn handle(
request: $crate::bindings::exports::zlayer::plugin::handler::PluginRequest,
) -> $crate::bindings::exports::zlayer::plugin::handler::HandleResult {
use $crate::bindings::exports::zlayer::plugin::handler::{HandleResult, HttpMethod};
use $crate::bindings::zlayer::plugin::request_types::PluginResponse;
let plugin = <$plugin_type as Default>::default();
let event_type = match request.method {
HttpMethod::Get => "GET",
HttpMethod::Post => "POST",
HttpMethod::Put => "PUT",
HttpMethod::Delete => "DELETE",
HttpMethod::Patch => "PATCH",
HttpMethod::Head => "HEAD",
HttpMethod::Options => "OPTIONS",
HttpMethod::Connect => "CONNECT",
HttpMethod::Trace => "TRACE",
};
match <$plugin_type as $crate::ZLayerPlugin>::handle(
&plugin,
event_type,
&request.body,
) {
Ok(body) => HandleResult::Response(PluginResponse {
status: 200,
headers: ::alloc::vec::Vec::new(),
body,
}),
Err(msg) => HandleResult::Error(msg),
}
}
fn shutdown() {
let plugin = <$plugin_type as Default>::default();
let _ = <$plugin_type as $crate::ZLayerPlugin>::shutdown(&plugin);
}
}
$crate::bindings::export_handler!(ZLayerPluginExport);
};
}
#[must_use]
pub fn kv_pair(key: impl Into<String>, value: impl Into<String>) -> bindings::zlayer::plugin::common::KeyValue {
bindings::zlayer::plugin::common::KeyValue {
key: key.into(),
value: value.into(),
}
}
#[must_use]
pub fn version(major: u32, minor: u32, patch: u32) -> bindings::zlayer::plugin::plugin_metadata::Version {
bindings::zlayer::plugin::plugin_metadata::Version {
major,
minor,
patch,
pre_release: None,
}
}
#[must_use]
pub fn version_pre(
major: u32,
minor: u32,
patch: u32,
pre_release: &str,
) -> bindings::zlayer::plugin::plugin_metadata::Version {
bindings::zlayer::plugin::plugin_metadata::Version {
major,
minor,
patch,
pre_release: Some(String::from(pre_release)),
}
}