zlayer_sdk/lib.rs
1//! `ZLayer` SDK for building WASM plugins in Rust.
2//!
3//! This crate provides type-safe bindings to `ZLayer` host functions
4//! and convenient macros for implementing plugins.
5//!
6//! # Example
7//!
8//! ```rust,ignore
9//! #![no_std]
10//! extern crate alloc;
11//!
12//! use alloc::string::String;
13//! use alloc::vec::Vec;
14//! use zlayer_sdk::bindings::{
15//! export_handler,
16//! exports::zlayer::plugin::handler::{
17//! Guest, Capabilities, HandleResult, InitError, PluginInfo, PluginRequest,
18//! },
19//! zlayer::plugin::{
20//! config, logging,
21//! plugin_metadata::Version,
22//! request_types::PluginResponse,
23//! },
24//! };
25//!
26//! struct MyPlugin;
27//!
28//! impl Guest for MyPlugin {
29//! fn init() -> Result<Capabilities, InitError> {
30//! logging::info("Initializing...");
31//! Ok(Capabilities::CONFIG | Capabilities::LOGGING)
32//! }
33//!
34//! fn info() -> PluginInfo {
35//! PluginInfo {
36//! id: String::from("my:plugin"),
37//! name: String::from("My Plugin"),
38//! version: Version { major: 0, minor: 1, patch: 0, pre_release: None },
39//! description: String::from("A custom plugin"),
40//! author: String::from("Author"),
41//! license: None,
42//! homepage: None,
43//! metadata: Vec::new(),
44//! }
45//! }
46//!
47//! fn handle(request: PluginRequest) -> HandleResult {
48//! logging::info("Handling request");
49//! HandleResult::Response(PluginResponse {
50//! status: 200,
51//! headers: Vec::new(),
52//! body: b"Hello from ZLayer!".to_vec(),
53//! })
54//! }
55//!
56//! fn shutdown() {
57//! logging::info("Shutting down...");
58//! }
59//! }
60//!
61//! // Note: `with_types_in` is required when using the SDK from an external crate
62//! export_handler!(MyPlugin with_types_in zlayer_sdk::bindings);
63//! ```
64//!
65//! # Features
66//!
67//! - **Type-safe bindings** - Generated from WIT definitions
68//! - **Host function access** - Config, KV storage, logging, secrets, metrics
69//! - **HTTP capabilities** - Make outbound HTTP requests
70//! - **`no_std` compatible** - Minimal runtime dependencies
71//!
72//! # `no_std` Support
73//!
74//! This crate is `no_std` by default for WASM plugin compilation.
75//! Enable the `std` feature for native testing or non-WASM targets.
76
77// Conditional no_std based on feature flag
78#![cfg_attr(not(feature = "std"), no_std)]
79
80extern crate alloc;
81
82// =============================================================================
83// no_std Runtime Support
84// =============================================================================
85
86// Use dlmalloc as the global allocator for no_std WASM builds
87#[cfg(all(feature = "wasm-alloc", not(feature = "std")))]
88#[global_allocator]
89static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc;
90
91// Panic handler for no_std WASM targets
92// When std is enabled, the standard library provides this
93#[cfg(all(not(feature = "std"), target_arch = "wasm32"))]
94#[panic_handler]
95fn panic(_info: &core::panic::PanicInfo) -> ! {
96 // In WASM, we can use unreachable to trap
97 core::arch::wasm32::unreachable()
98}
99
100// For non-WASM no_std targets (rare, but handle gracefully)
101#[cfg(all(not(feature = "std"), not(target_arch = "wasm32")))]
102#[panic_handler]
103fn panic(_info: &core::panic::PanicInfo) -> ! {
104 loop {}
105}
106
107/// Generated bindings from WIT definitions.
108///
109/// This module contains all the type-safe bindings generated by `wit-bindgen`
110/// from the `ZLayer` WIT interface definitions. It includes:
111///
112/// - **Imports**: Host-provided interfaces (config, keyvalue, logging, secrets, metrics)
113/// - **Exports**: Plugin-exported interfaces (handler, transformer, authenticator, etc.)
114/// - **Types**: Common types used across interfaces
115///
116/// # Usage
117///
118/// Import the bindings you need:
119///
120/// ```rust,ignore
121/// use zlayer_sdk::bindings::{
122/// export_handler,
123/// exports::zlayer::plugin::handler::Guest,
124/// zlayer::plugin::{config, logging},
125/// };
126/// ```
127pub mod bindings {
128 // Allow the generated code to use alloc types
129 extern crate alloc;
130
131 wit_bindgen::generate!({
132 // Use the minimal world that doesn't require WASI dependencies
133 // For full WASI support, use cargo-component which fetches deps automatically
134 world: "zlayer-plugin-minimal",
135 path: "wit",
136 // Export macro for handler registration - made public for SDK users
137 export_macro_name: "export_handler",
138 pub_export_macro: true,
139 });
140}
141
142/// Prelude module for convenient imports.
143///
144/// Import everything you need with a single use statement:
145///
146/// ```rust,ignore
147/// use zlayer_sdk::prelude::*;
148/// ```
149pub mod prelude {
150 // Re-export the main handler trait and types
151 pub use crate::bindings::exports::zlayer::plugin::handler::{
152 Capabilities, Guest as Handler, HandleResult, InitError, PluginInfo, PluginRequest,
153 };
154 // These types come from their source interfaces, not the handler export
155 pub use crate::bindings::zlayer::plugin::plugin_metadata::Version;
156 pub use crate::bindings::zlayer::plugin::request_types::PluginResponse;
157
158 // Re-export common types
159 pub use crate::bindings::zlayer::plugin::common::{Error, KeyValue};
160
161 // Re-export host interfaces
162 pub use crate::bindings::zlayer::plugin::config;
163 pub use crate::bindings::zlayer::plugin::keyvalue;
164 pub use crate::bindings::zlayer::plugin::logging;
165 pub use crate::bindings::zlayer::plugin::metrics;
166 pub use crate::bindings::zlayer::plugin::secrets;
167
168 // Re-export the export macro
169 pub use crate::bindings::export_handler;
170
171 // Re-export alloc types for no_std compatibility
172 pub use alloc::format;
173 pub use alloc::string::String;
174 pub use alloc::vec;
175 pub use alloc::vec::Vec;
176}
177
178/// Testing utilities for `ZLayer` plugins.
179///
180/// This module provides mock implementations of `ZLayer` host functions
181/// for unit testing plugins without running them in the actual runtime.
182///
183/// Enable with the `testing` feature:
184///
185/// ```toml
186/// [dev-dependencies]
187/// zlayer-sdk = { version = "0.1", features = ["testing"] }
188/// ```
189///
190/// # Example
191///
192/// ```rust,ignore
193/// use zlayer_sdk::testing::{MockHost, LogLevel};
194/// use zlayer_sdk::zlayer_test;
195///
196/// zlayer_test!(test_my_plugin, |host| {
197/// host.set_config("api_key", "test-key")
198/// .set_secret("db_password", "secret123");
199/// }, |host| {
200/// // Test your plugin logic here
201/// assert!(host.has_log(LogLevel::Info, "initialized"));
202/// });
203/// ```
204#[cfg(feature = "testing")]
205pub mod testing;
206
207/// Helper module for building plugin responses.
208///
209/// Provides convenience functions for common response patterns.
210pub mod response {
211 use crate::bindings::zlayer::plugin::request_types::PluginResponse;
212 use crate::bindings::zlayer::plugin::common::KeyValue;
213 use alloc::string::String;
214 use alloc::vec::Vec;
215
216 /// Create a successful JSON response.
217 #[must_use]
218 pub fn json(body: &[u8]) -> PluginResponse {
219 PluginResponse {
220 status: 200,
221 headers: alloc::vec![KeyValue {
222 key: String::from("Content-Type"),
223 value: String::from("application/json"),
224 }],
225 body: body.to_vec(),
226 }
227 }
228
229 /// Create a successful plain text response.
230 #[must_use]
231 pub fn text(body: &str) -> PluginResponse {
232 PluginResponse {
233 status: 200,
234 headers: alloc::vec![KeyValue {
235 key: String::from("Content-Type"),
236 value: String::from("text/plain; charset=utf-8"),
237 }],
238 body: body.as_bytes().to_vec(),
239 }
240 }
241
242 /// Create an empty successful response.
243 #[must_use]
244 pub fn ok() -> PluginResponse {
245 PluginResponse {
246 status: 200,
247 headers: Vec::new(),
248 body: Vec::new(),
249 }
250 }
251
252 /// Create a not found response.
253 #[must_use]
254 pub fn not_found() -> PluginResponse {
255 PluginResponse {
256 status: 404,
257 headers: alloc::vec![KeyValue {
258 key: String::from("Content-Type"),
259 value: String::from("text/plain"),
260 }],
261 body: b"Not Found".to_vec(),
262 }
263 }
264
265 /// Create a bad request response with a message.
266 #[must_use]
267 pub fn bad_request(message: &str) -> PluginResponse {
268 PluginResponse {
269 status: 400,
270 headers: alloc::vec![KeyValue {
271 key: String::from("Content-Type"),
272 value: String::from("text/plain"),
273 }],
274 body: message.as_bytes().to_vec(),
275 }
276 }
277
278 /// Create an internal server error response.
279 #[must_use]
280 pub fn internal_error(message: &str) -> PluginResponse {
281 PluginResponse {
282 status: 500,
283 headers: alloc::vec![KeyValue {
284 key: String::from("Content-Type"),
285 value: String::from("text/plain"),
286 }],
287 body: message.as_bytes().to_vec(),
288 }
289 }
290
291 /// Create a response with custom status, headers, and body.
292 #[must_use]
293 pub fn custom(status: u16, headers: Vec<KeyValue>, body: Vec<u8>) -> PluginResponse {
294 PluginResponse {
295 status,
296 headers,
297 body,
298 }
299 }
300}
301
302/// Helper module for working with plugin metadata.
303pub mod metadata {
304 use crate::bindings::exports::zlayer::plugin::handler::PluginInfo;
305 use crate::bindings::zlayer::plugin::common::KeyValue;
306 use crate::bindings::zlayer::plugin::plugin_metadata::Version;
307 use alloc::string::String;
308 use alloc::vec::Vec;
309
310 /// Builder for creating plugin info.
311 pub struct PluginInfoBuilder {
312 id: String,
313 name: String,
314 version: Version,
315 description: String,
316 author: String,
317 license: Option<String>,
318 homepage: Option<String>,
319 metadata: Vec<KeyValue>,
320 }
321
322 impl PluginInfoBuilder {
323 /// Create a new builder with required fields.
324 #[must_use]
325 pub fn new(id: &str, name: &str, author: &str) -> Self {
326 Self {
327 id: String::from(id),
328 name: String::from(name),
329 version: Version {
330 major: 0,
331 minor: 1,
332 patch: 0,
333 pre_release: None,
334 },
335 description: String::new(),
336 author: String::from(author),
337 license: None,
338 homepage: None,
339 metadata: Vec::new(),
340 }
341 }
342
343 /// Set the version.
344 #[must_use]
345 pub fn version(mut self, major: u32, minor: u32, patch: u32) -> Self {
346 self.version = Version {
347 major,
348 minor,
349 patch,
350 pre_release: None,
351 };
352 self
353 }
354
355 /// Set the version with pre-release tag.
356 #[must_use]
357 pub fn version_prerelease(
358 mut self,
359 major: u32,
360 minor: u32,
361 patch: u32,
362 prerelease: &str,
363 ) -> Self {
364 self.version = Version {
365 major,
366 minor,
367 patch,
368 pre_release: Some(String::from(prerelease)),
369 };
370 self
371 }
372
373 /// Set the description.
374 #[must_use]
375 pub fn description(mut self, desc: &str) -> Self {
376 self.description = String::from(desc);
377 self
378 }
379
380 /// Set the license.
381 #[must_use]
382 pub fn license(mut self, license: &str) -> Self {
383 self.license = Some(String::from(license));
384 self
385 }
386
387 /// Set the homepage URL.
388 #[must_use]
389 pub fn homepage(mut self, url: &str) -> Self {
390 self.homepage = Some(String::from(url));
391 self
392 }
393
394 /// Add a metadata key-value pair.
395 #[must_use]
396 pub fn meta(mut self, key: &str, value: &str) -> Self {
397 self.metadata.push(KeyValue {
398 key: String::from(key),
399 value: String::from(value),
400 });
401 self
402 }
403
404 /// Build the plugin info.
405 #[must_use]
406 pub fn build(self) -> PluginInfo {
407 PluginInfo {
408 id: self.id,
409 name: self.name,
410 version: self.version,
411 description: self.description,
412 author: self.author,
413 license: self.license,
414 homepage: self.homepage,
415 metadata: self.metadata,
416 }
417 }
418 }
419}
420
421// =============================================================================
422// Error Types
423// =============================================================================
424
425use alloc::string::String;
426use alloc::vec::Vec;
427
428/// Error returned by config operations
429#[derive(Debug, Clone)]
430pub struct ConfigError {
431 /// Error code
432 pub code: String,
433 /// Error message
434 pub message: String,
435}
436
437impl ConfigError {
438 /// Create a new config error
439 #[must_use]
440 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
441 Self {
442 code: code.into(),
443 message: message.into(),
444 }
445 }
446
447 /// Create a "not found" error
448 #[must_use]
449 pub fn not_found(key: &str) -> Self {
450 Self::new("not_found", alloc::format!("config key '{}' not found", key))
451 }
452}
453
454impl core::fmt::Display for ConfigError {
455 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
456 write!(f, "{}: {}", self.code, self.message)
457 }
458}
459
460/// Error returned by key-value operations
461#[derive(Debug, Clone)]
462pub enum KvError {
463 /// Key not found
464 NotFound,
465 /// Value too large
466 ValueTooLarge,
467 /// Storage quota exceeded
468 QuotaExceeded,
469 /// Key format invalid
470 InvalidKey,
471 /// Generic storage error
472 Storage(String),
473}
474
475impl KvError {
476 fn from_wit(err: bindings::zlayer::plugin::keyvalue::KvError) -> Self {
477 use bindings::zlayer::plugin::keyvalue::KvError as WitKvError;
478 match err {
479 WitKvError::NotFound => KvError::NotFound,
480 WitKvError::ValueTooLarge => KvError::ValueTooLarge,
481 WitKvError::QuotaExceeded => KvError::QuotaExceeded,
482 WitKvError::InvalidKey => KvError::InvalidKey,
483 WitKvError::Storage(msg) => KvError::Storage(msg),
484 }
485 }
486}
487
488impl core::fmt::Display for KvError {
489 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
490 match self {
491 KvError::NotFound => write!(f, "key not found"),
492 KvError::ValueTooLarge => write!(f, "value too large"),
493 KvError::QuotaExceeded => write!(f, "storage quota exceeded"),
494 KvError::InvalidKey => write!(f, "invalid key format"),
495 KvError::Storage(msg) => write!(f, "storage error: {msg}"),
496 }
497 }
498}
499
500/// Error returned by secret operations
501#[derive(Debug, Clone)]
502pub struct SecretError {
503 /// Error code
504 pub code: String,
505 /// Error message
506 pub message: String,
507}
508
509impl SecretError {
510 /// Create a new secret error
511 #[must_use]
512 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
513 Self {
514 code: code.into(),
515 message: message.into(),
516 }
517 }
518
519 /// Create a "not found" error
520 #[must_use]
521 pub fn not_found(name: &str) -> Self {
522 Self::new("not_found", alloc::format!("secret '{name}' not found"))
523 }
524}
525
526impl core::fmt::Display for SecretError {
527 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
528 write!(f, "{}: {}", self.code, self.message)
529 }
530}
531
532// =============================================================================
533// Config Module - Ergonomic Wrappers
534// =============================================================================
535
536/// Configuration access for plugins.
537///
538/// Provides ergonomic wrappers around the host config interface.
539///
540/// # Example
541///
542/// ```rust,ignore
543/// use zlayer_sdk::config;
544///
545/// // Get optional config
546/// if let Some(value) = config::get("my_key") {
547/// log::info(&format!("Got value: {}", value));
548/// }
549///
550/// // Get required config
551/// let required = config::get_required("database.host")?;
552///
553/// // Get typed values
554/// let port = config::get_int("database.port").unwrap_or(5432);
555/// let enabled = config::get_bool("feature.enabled").unwrap_or(false);
556/// ```
557pub mod config {
558 use super::*;
559 use crate::bindings::zlayer::plugin::config as host_config;
560
561 /// Get a configuration value by key.
562 ///
563 /// Returns `None` if the key doesn't exist.
564 #[must_use]
565 pub fn get(key: &str) -> Option<String> {
566 host_config::get(key)
567 }
568
569 /// Get a configuration value, returning an error if not found.
570 ///
571 /// # Errors
572 ///
573 /// Returns `ConfigError` if the key is not found.
574 pub fn get_required(key: &str) -> Result<String, ConfigError> {
575 host_config::get(key).ok_or_else(|| ConfigError::not_found(key))
576 }
577
578 /// Get a configuration value as a boolean.
579 ///
580 /// Recognizes: "true", "false", "1", "0", "yes", "no"
581 #[must_use]
582 pub fn get_bool(key: &str) -> Option<bool> {
583 host_config::get_bool(key)
584 }
585
586 /// Get a configuration value as an integer.
587 #[must_use]
588 pub fn get_int(key: &str) -> Option<i64> {
589 host_config::get_int(key)
590 }
591
592 /// Get a configuration value as a float.
593 #[must_use]
594 pub fn get_float(key: &str) -> Option<f64> {
595 host_config::get_float(key)
596 }
597
598 /// Check if a configuration key exists.
599 #[must_use]
600 pub fn exists(key: &str) -> bool {
601 host_config::exists(key)
602 }
603
604 /// Get multiple configuration values at once.
605 ///
606 /// Returns a list of (key, value) pairs for keys that exist.
607 #[must_use]
608 pub fn get_many(keys: &[&str]) -> Vec<(String, String)> {
609 let keys_vec: Vec<String> = keys.iter().map(|k| String::from(*k)).collect();
610 host_config::get_many(&keys_vec)
611 }
612
613 /// Get all configuration keys with a given prefix.
614 ///
615 /// Example: `get_prefix("database.")` returns all `database.*` keys.
616 #[must_use]
617 pub fn get_prefix(prefix: &str) -> Vec<(String, String)> {
618 host_config::get_prefix(prefix)
619 }
620
621 /// Get all configuration as a JSON string (for debugging).
622 #[must_use]
623 pub fn get_all() -> String {
624 // Build a simple JSON representation
625 let items = host_config::get_prefix("");
626 let mut result = String::from("{");
627 for (i, (key, value)) in items.iter().enumerate() {
628 if i > 0 {
629 result.push_str(", ");
630 }
631 result.push('"');
632 result.push_str(key);
633 result.push_str("\": \"");
634 result.push_str(value);
635 result.push('"');
636 }
637 result.push('}');
638 result
639 }
640}
641
642// =============================================================================
643// Key-Value Storage Module
644// =============================================================================
645
646/// Key-value storage for plugin state.
647///
648/// Provides persistent storage that survives across plugin invocations.
649///
650/// # Example
651///
652/// ```rust,ignore
653/// use zlayer_sdk::kv;
654///
655/// // Store a value
656/// kv::set("state", "counter", b"42")?;
657///
658/// // Retrieve a value
659/// if let Some(data) = kv::get("state", "counter")? {
660/// let count: i32 = core::str::from_utf8(&data)
661/// .ok()
662/// .and_then(|s| s.parse().ok())
663/// .unwrap_or(0);
664/// }
665///
666/// // String convenience methods
667/// kv::set_string("state", "name", "my-value")?;
668/// let name = kv::get_string("state", "name")?;
669/// ```
670pub mod kv {
671 use super::*;
672 use alloc::string::ToString;
673 use crate::bindings::zlayer::plugin::keyvalue as host_kv;
674
675 /// Get a value by key.
676 ///
677 /// The bucket parameter namespaces keys to avoid collisions.
678 ///
679 /// # Errors
680 ///
681 /// Returns `KvError` if the operation fails.
682 pub fn get(bucket: &str, key: &str) -> Result<Option<Vec<u8>>, KvError> {
683 let full_key = make_key(bucket, key);
684 host_kv::get(&full_key).map_err(KvError::from_wit)
685 }
686
687 /// Get a value as a UTF-8 string.
688 ///
689 /// # Errors
690 ///
691 /// Returns `KvError` if the operation fails.
692 pub fn get_string(bucket: &str, key: &str) -> Result<Option<String>, KvError> {
693 let full_key = make_key(bucket, key);
694 host_kv::get_string(&full_key).map_err(KvError::from_wit)
695 }
696
697 /// Set a value.
698 ///
699 /// # Errors
700 ///
701 /// Returns `KvError` if the operation fails.
702 pub fn set(bucket: &str, key: &str, value: &[u8]) -> Result<(), KvError> {
703 let full_key = make_key(bucket, key);
704 host_kv::set(&full_key, value).map_err(KvError::from_wit)
705 }
706
707 /// Set a string value.
708 ///
709 /// # Errors
710 ///
711 /// Returns `KvError` if the operation fails.
712 pub fn set_string(bucket: &str, key: &str, value: &str) -> Result<(), KvError> {
713 let full_key = make_key(bucket, key);
714 host_kv::set_string(&full_key, value).map_err(KvError::from_wit)
715 }
716
717 /// Delete a key.
718 ///
719 /// Returns `Ok(true)` if the key existed, `Ok(false)` otherwise.
720 ///
721 /// # Errors
722 ///
723 /// Returns `KvError` if the operation fails.
724 pub fn delete(bucket: &str, key: &str) -> Result<bool, KvError> {
725 let full_key = make_key(bucket, key);
726 host_kv::delete(&full_key).map_err(KvError::from_wit)
727 }
728
729 /// List all keys with a given prefix within a bucket.
730 ///
731 /// # Errors
732 ///
733 /// Returns `KvError` if the operation fails.
734 pub fn keys(bucket: &str, prefix: &str) -> Result<Vec<String>, KvError> {
735 let full_prefix = make_key(bucket, prefix);
736 let all_keys = host_kv::list_keys(&full_prefix).map_err(KvError::from_wit)?;
737 // Strip the bucket prefix from returned keys
738 let bucket_prefix = alloc::format!("{bucket}/");
739 Ok(all_keys
740 .into_iter()
741 .map(|k| k.strip_prefix(&bucket_prefix).unwrap_or(&k).to_string())
742 .collect())
743 }
744
745 /// Check if a key exists.
746 #[must_use]
747 pub fn exists(bucket: &str, key: &str) -> bool {
748 let full_key = make_key(bucket, key);
749 host_kv::exists(&full_key)
750 }
751
752 /// Set a value with a TTL (time-to-live) in seconds.
753 ///
754 /// # Errors
755 ///
756 /// Returns `KvError` if the operation fails.
757 pub fn set_with_ttl(
758 bucket: &str,
759 key: &str,
760 value: &[u8],
761 ttl_secs: u64,
762 ) -> Result<(), KvError> {
763 let full_key = make_key(bucket, key);
764 let ttl_ns = ttl_secs.saturating_mul(1_000_000_000);
765 host_kv::set_with_ttl(&full_key, value, ttl_ns).map_err(KvError::from_wit)
766 }
767
768 /// Increment a numeric value atomically.
769 ///
770 /// Returns the new value after increment.
771 ///
772 /// # Errors
773 ///
774 /// Returns `KvError` if the operation fails.
775 pub fn increment(bucket: &str, key: &str, delta: i64) -> Result<i64, KvError> {
776 let full_key = make_key(bucket, key);
777 host_kv::increment(&full_key, delta).map_err(KvError::from_wit)
778 }
779
780 /// Compare and swap - set value only if current value matches expected.
781 ///
782 /// Returns `Ok(true)` if swap succeeded, `Ok(false)` if current value didn't match.
783 ///
784 /// # Errors
785 ///
786 /// Returns `KvError` if the operation fails.
787 pub fn compare_and_swap(
788 bucket: &str,
789 key: &str,
790 expected: Option<&[u8]>,
791 new_value: &[u8],
792 ) -> Result<bool, KvError> {
793 let full_key = make_key(bucket, key);
794 let expected_vec = expected.map(|e| e.to_vec());
795 host_kv::compare_and_swap(&full_key, expected_vec.as_deref(), new_value)
796 .map_err(KvError::from_wit)
797 }
798
799 /// Create a namespaced key from bucket and key.
800 fn make_key(bucket: &str, key: &str) -> String {
801 alloc::format!("{bucket}/{key}")
802 }
803}
804
805// =============================================================================
806// Logging Module
807// =============================================================================
808
809/// Structured logging for plugins.
810///
811/// All log output is captured by the `ZLayer` host and can be filtered,
812/// aggregated, and forwarded to observability systems.
813///
814/// # Example
815///
816/// ```rust,ignore
817/// use zlayer_sdk::log;
818///
819/// log::info("Plugin started");
820/// log::debug("Processing request");
821/// log::warn("Rate limit approaching");
822/// log::error("Failed to connect to database");
823/// ```
824pub mod log {
825 use crate::bindings::zlayer::plugin::common::KeyValue;
826 use crate::bindings::zlayer::plugin::logging as host_log;
827 use alloc::string::String;
828 use alloc::vec::Vec;
829
830 /// Log level for filtering
831 pub use crate::bindings::zlayer::plugin::logging::Level as LogLevel;
832
833 /// Emit a trace-level log message.
834 pub fn trace(msg: &str) {
835 host_log::trace(msg);
836 }
837
838 /// Emit a debug-level log message.
839 pub fn debug(msg: &str) {
840 host_log::debug(msg);
841 }
842
843 /// Emit an info-level log message.
844 pub fn info(msg: &str) {
845 host_log::info(msg);
846 }
847
848 /// Emit a warning-level log message.
849 pub fn warn(msg: &str) {
850 host_log::warn(msg);
851 }
852
853 /// Emit an error-level log message.
854 pub fn error(msg: &str) {
855 host_log::error(msg);
856 }
857
858 /// Emit a log message at the specified level.
859 pub fn log(level: LogLevel, msg: &str) {
860 host_log::log(level, msg);
861 }
862
863 /// Emit a structured log with key-value fields.
864 pub fn log_structured(level: LogLevel, msg: &str, fields: &[(String, String)]) {
865 let kv_fields: Vec<KeyValue> = fields
866 .iter()
867 .map(|(k, v)| KeyValue {
868 key: k.clone(),
869 value: v.clone(),
870 })
871 .collect();
872 host_log::log_structured(level, msg, &kv_fields);
873 }
874
875 /// Check if a log level is enabled.
876 ///
877 /// Use this to avoid expensive log message construction when the level
878 /// is filtered out.
879 #[must_use]
880 pub fn is_enabled(level: LogLevel) -> bool {
881 host_log::is_enabled(level)
882 }
883}
884
885// =============================================================================
886// Secrets Module
887// =============================================================================
888
889/// Secure secret access for plugins.
890///
891/// Secrets are configured at deployment time and are read-only to plugins.
892/// They are stored securely and never logged.
893///
894/// # Example
895///
896/// ```rust,ignore
897/// use zlayer_sdk::secrets;
898///
899/// // Get optional secret
900/// if let Some(api_key) = secrets::get("API_KEY")? {
901/// // Use the API key
902/// }
903///
904/// // Get required secret
905/// let db_password = secrets::get_required("DATABASE_PASSWORD")?;
906/// ```
907pub mod secrets {
908 use super::*;
909 use crate::bindings::zlayer::plugin::secrets as host_secrets;
910
911 /// Get a secret by name.
912 ///
913 /// Returns `Ok(None)` if the secret doesn't exist.
914 ///
915 /// # Errors
916 ///
917 /// Returns `SecretError` if the operation fails.
918 pub fn get(name: &str) -> Result<Option<String>, SecretError> {
919 host_secrets::get(name).map_err(|e| SecretError::new(&e.code, &e.message))
920 }
921
922 /// Get a required secret, returning an error if not found.
923 ///
924 /// # Errors
925 ///
926 /// Returns `SecretError` if the secret is not found.
927 pub fn get_required(name: &str) -> Result<String, SecretError> {
928 host_secrets::get_required(name).map_err(|e| SecretError::new(&e.code, &e.message))
929 }
930
931 /// Check if a secret exists.
932 #[must_use]
933 pub fn exists(name: &str) -> bool {
934 host_secrets::exists(name)
935 }
936
937 /// List available secret names (not values).
938 ///
939 /// Useful for diagnostics without exposing sensitive data.
940 #[must_use]
941 pub fn list_names() -> Vec<String> {
942 host_secrets::list_names()
943 }
944}
945
946// =============================================================================
947// Metrics Module
948// =============================================================================
949
950/// Metrics emission for observability.
951///
952/// Emitted metrics are collected by the `ZLayer` host and can be exported
953/// to Prometheus, OpenTelemetry, or other systems.
954///
955/// # Example
956///
957/// ```rust,ignore
958/// use zlayer_sdk::metrics;
959///
960/// // Increment a counter
961/// metrics::counter_inc("requests_total", 1);
962///
963/// // Set a gauge value
964/// metrics::gauge_set("active_connections", 42.0);
965///
966/// // Record a histogram observation
967/// metrics::histogram_observe("request_duration_seconds", 0.125);
968/// ```
969pub mod metrics {
970 use crate::bindings::zlayer::plugin::common::KeyValue;
971 use crate::bindings::zlayer::plugin::metrics as host_metrics;
972 use alloc::string::String;
973 use alloc::vec::Vec;
974
975 /// Increment a counter metric.
976 pub fn counter_inc(name: &str, value: u64) {
977 host_metrics::counter_inc(name, value);
978 }
979
980 /// Increment a counter with labels.
981 pub fn counter_inc_labeled(name: &str, value: u64, labels: &[(String, String)]) {
982 let kv_labels: Vec<KeyValue> = labels
983 .iter()
984 .map(|(k, v)| KeyValue {
985 key: k.clone(),
986 value: v.clone(),
987 })
988 .collect();
989 host_metrics::counter_inc_labeled(name, value, &kv_labels);
990 }
991
992 /// Set a gauge metric to a value.
993 pub fn gauge_set(name: &str, value: f64) {
994 host_metrics::gauge_set(name, value);
995 }
996
997 /// Set a gauge with labels.
998 pub fn gauge_set_labeled(name: &str, value: f64, labels: &[(String, String)]) {
999 let kv_labels: Vec<KeyValue> = labels
1000 .iter()
1001 .map(|(k, v)| KeyValue {
1002 key: k.clone(),
1003 value: v.clone(),
1004 })
1005 .collect();
1006 host_metrics::gauge_set_labeled(name, value, &kv_labels);
1007 }
1008
1009 /// Add to a gauge value (can be negative).
1010 pub fn gauge_add(name: &str, delta: f64) {
1011 host_metrics::gauge_add(name, delta);
1012 }
1013
1014 /// Record a histogram observation.
1015 pub fn histogram_observe(name: &str, value: f64) {
1016 host_metrics::histogram_observe(name, value);
1017 }
1018
1019 /// Record a histogram observation with labels.
1020 pub fn histogram_observe_labeled(name: &str, value: f64, labels: &[(String, String)]) {
1021 let kv_labels: Vec<KeyValue> = labels
1022 .iter()
1023 .map(|(k, v)| KeyValue {
1024 key: k.clone(),
1025 value: v.clone(),
1026 })
1027 .collect();
1028 host_metrics::histogram_observe_labeled(name, value, &kv_labels);
1029 }
1030
1031 /// Record request duration in nanoseconds.
1032 pub fn record_duration(name: &str, duration_ns: u64) {
1033 host_metrics::record_duration(name, duration_ns);
1034 }
1035
1036 /// Record request duration with labels.
1037 pub fn record_duration_labeled(name: &str, duration_ns: u64, labels: &[(String, String)]) {
1038 let kv_labels: Vec<KeyValue> = labels
1039 .iter()
1040 .map(|(k, v)| KeyValue {
1041 key: k.clone(),
1042 value: v.clone(),
1043 })
1044 .collect();
1045 host_metrics::record_duration_labeled(name, duration_ns, &kv_labels);
1046 }
1047}
1048
1049// =============================================================================
1050// Plugin Trait
1051// =============================================================================
1052
1053/// Trait for implementing `ZLayer` plugins.
1054///
1055/// This trait provides the core interface that all plugins must implement.
1056/// The `zlayer_plugin!` macro generates the necessary WIT bindings glue code.
1057///
1058/// # Example
1059///
1060/// ```rust,ignore
1061/// use zlayer_sdk::prelude::*;
1062/// use zlayer_sdk::{ZLayerPlugin, zlayer_plugin};
1063///
1064/// struct MyPlugin;
1065///
1066/// impl Default for MyPlugin {
1067/// fn default() -> Self {
1068/// Self
1069/// }
1070/// }
1071///
1072/// impl ZLayerPlugin for MyPlugin {
1073/// fn info(&self) -> PluginInfo {
1074/// PluginInfo {
1075/// id: "my-org:my-plugin".into(),
1076/// name: "My Plugin".into(),
1077/// version: Version { major: 1, minor: 0, patch: 0, pre_release: None },
1078/// description: "A simple plugin".into(),
1079/// author: "My Organization".into(),
1080/// license: Some("MIT".into()),
1081/// homepage: None,
1082/// metadata: vec![],
1083/// }
1084/// }
1085///
1086/// fn handle(&self, event_type: &str, payload: &[u8]) -> Result<Vec<u8>, String> {
1087/// Ok(payload.to_vec())
1088/// }
1089/// }
1090///
1091/// zlayer_plugin!(MyPlugin);
1092/// ```
1093pub trait ZLayerPlugin: Default {
1094 /// Initialize the plugin.
1095 ///
1096 /// Called once when the plugin is loaded. Override to perform
1097 /// initialization tasks like reading configuration or connecting
1098 /// to services.
1099 ///
1100 /// Return `Ok(())` on success or `Err(message)` to abort loading.
1101 ///
1102 /// # Errors
1103 ///
1104 /// Returns an error message string if initialization fails.
1105 fn init(&self) -> Result<(), String> {
1106 Ok(())
1107 }
1108
1109 /// Return plugin metadata.
1110 ///
1111 /// This information is used for logging, metrics, and management.
1112 fn info(&self) -> bindings::exports::zlayer::plugin::handler::PluginInfo;
1113
1114 /// Handle an incoming event or request.
1115 ///
1116 /// # Arguments
1117 ///
1118 /// * `event_type` - The type of event (e.g., "GET", "POST", "message", "timer")
1119 /// * `payload` - The event payload as bytes
1120 ///
1121 /// # Returns
1122 ///
1123 /// * `Ok(response)` - The response payload as bytes
1124 /// * `Err(message)` - An error message if handling failed
1125 ///
1126 /// # Errors
1127 ///
1128 /// Returns an error message string if handling fails.
1129 fn handle(&self, event_type: &str, payload: &[u8]) -> Result<Vec<u8>, String>;
1130
1131 /// Graceful shutdown hook.
1132 ///
1133 /// Called when the plugin is being unloaded. Override to clean up
1134 /// resources like database connections or file handles.
1135 ///
1136 /// # Errors
1137 ///
1138 /// Returns an error message string if shutdown fails.
1139 fn shutdown(&self) -> Result<(), String> {
1140 Ok(())
1141 }
1142
1143 /// Return the capabilities this plugin requires.
1144 ///
1145 /// Override to request additional capabilities like HTTP client access.
1146 fn capabilities(&self) -> bindings::exports::zlayer::plugin::handler::Capabilities {
1147 use bindings::exports::zlayer::plugin::handler::Capabilities;
1148 Capabilities::CONFIG | Capabilities::LOGGING
1149 }
1150}
1151
1152// =============================================================================
1153// Plugin Registration Macro
1154// =============================================================================
1155
1156/// Register a plugin implementation with the `ZLayer` runtime.
1157///
1158/// This macro generates the necessary WIT binding exports for your plugin type.
1159/// Your type must implement the `ZLayerPlugin` trait and `Default`.
1160///
1161/// # Example
1162///
1163/// ```rust,ignore
1164/// use zlayer_sdk::prelude::*;
1165/// use zlayer_sdk::{ZLayerPlugin, zlayer_plugin};
1166///
1167/// struct MyPlugin;
1168/// impl Default for MyPlugin {
1169/// fn default() -> Self { Self }
1170/// }
1171///
1172/// impl ZLayerPlugin for MyPlugin {
1173/// fn info(&self) -> PluginInfo {
1174/// PluginInfo {
1175/// id: "example:my-plugin".into(),
1176/// name: "My Plugin".into(),
1177/// version: Version { major: 1, minor: 0, patch: 0, pre_release: None },
1178/// description: "Example plugin".into(),
1179/// author: "Example".into(),
1180/// license: None,
1181/// homepage: None,
1182/// metadata: vec![],
1183/// }
1184/// }
1185///
1186/// fn handle(&self, event_type: &str, payload: &[u8]) -> Result<Vec<u8>, String> {
1187/// Ok(payload.to_vec())
1188/// }
1189/// }
1190///
1191/// zlayer_plugin!(MyPlugin);
1192/// ```
1193#[macro_export]
1194macro_rules! zlayer_plugin {
1195 ($plugin_type:ty) => {
1196 struct ZLayerPluginExport;
1197
1198 impl $crate::bindings::exports::zlayer::plugin::handler::Guest for ZLayerPluginExport {
1199 fn init() -> Result<
1200 $crate::bindings::exports::zlayer::plugin::handler::Capabilities,
1201 $crate::bindings::exports::zlayer::plugin::handler::InitError,
1202 > {
1203 let plugin = <$plugin_type as Default>::default();
1204 match <$plugin_type as $crate::ZLayerPlugin>::init(&plugin) {
1205 Ok(()) => Ok(<$plugin_type as $crate::ZLayerPlugin>::capabilities(&plugin)),
1206 Err(msg) => Err(
1207 $crate::bindings::exports::zlayer::plugin::handler::InitError::Failed(msg),
1208 ),
1209 }
1210 }
1211
1212 fn info() -> $crate::bindings::exports::zlayer::plugin::handler::PluginInfo {
1213 let plugin = <$plugin_type as Default>::default();
1214 <$plugin_type as $crate::ZLayerPlugin>::info(&plugin)
1215 }
1216
1217 fn handle(
1218 request: $crate::bindings::exports::zlayer::plugin::handler::PluginRequest,
1219 ) -> $crate::bindings::exports::zlayer::plugin::handler::HandleResult {
1220 use $crate::bindings::exports::zlayer::plugin::handler::{HandleResult, HttpMethod};
1221 use $crate::bindings::zlayer::plugin::request_types::PluginResponse;
1222
1223 let plugin = <$plugin_type as Default>::default();
1224
1225 let event_type = match request.method {
1226 HttpMethod::Get => "GET",
1227 HttpMethod::Post => "POST",
1228 HttpMethod::Put => "PUT",
1229 HttpMethod::Delete => "DELETE",
1230 HttpMethod::Patch => "PATCH",
1231 HttpMethod::Head => "HEAD",
1232 HttpMethod::Options => "OPTIONS",
1233 HttpMethod::Connect => "CONNECT",
1234 HttpMethod::Trace => "TRACE",
1235 };
1236
1237 match <$plugin_type as $crate::ZLayerPlugin>::handle(
1238 &plugin,
1239 event_type,
1240 &request.body,
1241 ) {
1242 Ok(body) => HandleResult::Response(PluginResponse {
1243 status: 200,
1244 headers: ::alloc::vec::Vec::new(),
1245 body,
1246 }),
1247 Err(msg) => HandleResult::Error(msg),
1248 }
1249 }
1250
1251 fn shutdown() {
1252 let plugin = <$plugin_type as Default>::default();
1253 let _ = <$plugin_type as $crate::ZLayerPlugin>::shutdown(&plugin);
1254 }
1255 }
1256
1257 $crate::bindings::export_handler!(ZLayerPluginExport);
1258 };
1259}
1260
1261// =============================================================================
1262// Additional Helper Functions
1263// =============================================================================
1264
1265/// Create a `KeyValue` pair.
1266#[must_use]
1267pub fn kv_pair(key: impl Into<String>, value: impl Into<String>) -> bindings::zlayer::plugin::common::KeyValue {
1268 bindings::zlayer::plugin::common::KeyValue {
1269 key: key.into(),
1270 value: value.into(),
1271 }
1272}
1273
1274/// Create a Version struct.
1275#[must_use]
1276pub fn version(major: u32, minor: u32, patch: u32) -> bindings::zlayer::plugin::plugin_metadata::Version {
1277 bindings::zlayer::plugin::plugin_metadata::Version {
1278 major,
1279 minor,
1280 patch,
1281 pre_release: None,
1282 }
1283}
1284
1285/// Create a Version struct with pre-release tag.
1286#[must_use]
1287pub fn version_pre(
1288 major: u32,
1289 minor: u32,
1290 patch: u32,
1291 pre_release: &str,
1292) -> bindings::zlayer::plugin::plugin_metadata::Version {
1293 bindings::zlayer::plugin::plugin_metadata::Version {
1294 major,
1295 minor,
1296 patch,
1297 pre_release: Some(String::from(pre_release)),
1298 }
1299}