Skip to main content

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}