diff --git a/crates/nexum-sdk/src/wit_bindgen_macro.rs b/crates/nexum-sdk/src/wit_bindgen_macro.rs index 25a91376..6ac615e4 100644 --- a/crates/nexum-sdk/src/wit_bindgen_macro.rs +++ b/crates/nexum-sdk/src/wit_bindgen_macro.rs @@ -3,10 +3,9 @@ //! //! Before this macro existed, each module hand-rolled ~80 lines of //! mechanical glue: the `struct WitBindgenHost;` plus the core trait -//! impls (`ChainHost`, `LocalStoreHost`, `LoggingHost`) plus -//! `convert_chain_err` / `convert_fault` / `sdk_fault_into_wit` / -//! `convert_level`. The code differed across modules in zero places -//! that were not bugs. +//! impls (`ChainHost`, `LocalStoreHost`, `LoggingHost`) plus the fault, +//! chain-error, and level conversions. The code differed across modules +//! in zero places that were not bugs. //! //! The adapter is capability-selected: the `caps: [...]` form emits //! only the pieces backed by the module's declared capabilities @@ -32,10 +31,10 @@ //! // or, capability-selected: //! // nexum_sdk::bind_host_via_wit_bindgen!(caps: [chain, logging]); //! -//! // `WitBindgenHost`, `convert_fault`, and `sdk_fault_into_wit` are -//! // now in scope, plus per selected capability: `convert_chain_err` -//! // (chain), the `LocalStoreHost` impl (local_store), and -//! // `convert_level`, `HostLogSink`, and `install_tracing` +//! // `WitBindgenHost` and the `Fault` `From` impls (both directions) +//! // are now in scope, plus per selected capability: `convert_chain_err` +//! // (chain), the `LocalStoreHost` impl (local_store), and the +//! // `Level` `From` impl, `HostLogSink`, and `install_tracing` //! // (logging), with the wit-bindgen and SDK types tied together //! // through identifier resolution. Call `install_tracing()` once at //! // the top of `Guest::init` to route `tracing::info!(...)` to the @@ -45,12 +44,15 @@ //! ``` /// Generate `WitBindgenHost` + the `*Host` trait impls + the error / -/// level converters for the selected capabilities. See module docs. +/// level `From` impls for the selected capabilities. See module docs. +/// +/// The fault and level conversions are `From` impls: orphan-legal here +/// because the wit-bindgen types are local to the expanding cdylib. /// /// Macro hygiene note: `macro_rules!` is not hygienic for type names /// or function items, so the names `WitBindgenHost`, `convert_chain_err`, -/// `convert_fault`, `sdk_fault_into_wit`, `convert_level`, `HostLogSink`, -/// and `install_tracing` are intentionally visible in the caller's scope. +/// `HostLogSink`, and `install_tracing` are intentionally visible in the +/// caller's scope. #[macro_export] macro_rules! bind_host_via_wit_bindgen { // Blanket-world form: every core interface is in scope, emit the @@ -71,46 +73,51 @@ macro_rules! bind_host_via_wit_bindgen { /// Lift the wit-bindgen `types.fault` (per-cdylib) into the /// SDK's `Fault`. Exhaustive on the seven-case vocabulary; the /// `rate-limited` backoff record maps field for field. - fn convert_fault(f: nexum::host::types::Fault) -> $crate::host::Fault { - match f { - nexum::host::types::Fault::Unsupported(s) => $crate::host::Fault::Unsupported(s), - nexum::host::types::Fault::Unavailable(s) => $crate::host::Fault::Unavailable(s), - nexum::host::types::Fault::Denied(s) => $crate::host::Fault::Denied(s), - nexum::host::types::Fault::RateLimited(rl) => { - $crate::host::Fault::RateLimited($crate::host::RateLimit { - retry_after_ms: rl.retry_after_ms, - }) + impl ::core::convert::From for $crate::host::Fault { + fn from(f: nexum::host::types::Fault) -> Self { + match f { + nexum::host::types::Fault::Unsupported(s) => Self::Unsupported(s), + nexum::host::types::Fault::Unavailable(s) => Self::Unavailable(s), + nexum::host::types::Fault::Denied(s) => Self::Denied(s), + nexum::host::types::Fault::RateLimited(rl) => { + Self::RateLimited($crate::host::RateLimit { + retry_after_ms: rl.retry_after_ms, + }) + } + nexum::host::types::Fault::Timeout => Self::Timeout, + nexum::host::types::Fault::InvalidInput(s) => Self::InvalidInput(s), + nexum::host::types::Fault::Internal(s) => Self::Internal(s), } - nexum::host::types::Fault::Timeout => $crate::host::Fault::Timeout, - nexum::host::types::Fault::InvalidInput(s) => $crate::host::Fault::InvalidInput(s), - nexum::host::types::Fault::Internal(s) => $crate::host::Fault::Internal(s), } } - /// Reverse direction: lower the SDK [`Fault`]( - /// $crate::host::Fault) back into the per-cdylib wit-bindgen - /// `Fault` so `Guest::init` / `Guest::on_event` can return what - /// the export signature expects. + /// Reverse direction: lower the SDK `Fault` back into the + /// per-cdylib wit-bindgen `Fault` so `Guest::init` / + /// `Guest::on_event` can return what the export signature + /// expects (`?` applies it). /// /// Carries a wildcard arm because `$crate::host::Fault` is /// `#[non_exhaustive]`: a future SDK-side case must compile in /// module crates without source changes. Falls back to /// `internal` carrying the `Display` detail. - fn sdk_fault_into_wit(f: $crate::host::Fault) -> nexum::host::types::Fault { - use nexum::host::types::{Fault as WitFault, RateLimit as WitRateLimit}; - match f { - $crate::host::Fault::Unsupported(s) => WitFault::Unsupported(s), - $crate::host::Fault::Unavailable(s) => WitFault::Unavailable(s), - $crate::host::Fault::Denied(s) => WitFault::Denied(s), - $crate::host::Fault::RateLimited(rl) => WitFault::RateLimited(WitRateLimit { - retry_after_ms: rl.retry_after_ms, - }), - $crate::host::Fault::Timeout => WitFault::Timeout, - $crate::host::Fault::InvalidInput(s) => WitFault::InvalidInput(s), - $crate::host::Fault::Internal(s) => WitFault::Internal(s), - // `$crate::host::Fault` is `#[non_exhaustive]`; a future - // SDK case lands here as `internal`. - other => WitFault::Internal(::std::string::ToString::to_string(&other)), + impl ::core::convert::From<$crate::host::Fault> for nexum::host::types::Fault { + fn from(f: $crate::host::Fault) -> Self { + match f { + $crate::host::Fault::Unsupported(s) => Self::Unsupported(s), + $crate::host::Fault::Unavailable(s) => Self::Unavailable(s), + $crate::host::Fault::Denied(s) => Self::Denied(s), + $crate::host::Fault::RateLimited(rl) => { + Self::RateLimited(nexum::host::types::RateLimit { + retry_after_ms: rl.retry_after_ms, + }) + } + $crate::host::Fault::Timeout => Self::Timeout, + $crate::host::Fault::InvalidInput(s) => Self::InvalidInput(s), + $crate::host::Fault::Internal(s) => Self::Internal(s), + // `$crate::host::Fault` is `#[non_exhaustive]`; a + // future SDK case lands here as `internal`. + other => Self::Internal(::std::string::ToString::to_string(&other)), + } } } @@ -163,7 +170,7 @@ macro_rules! __bind_host_cap_via_wit_bindgen { fn convert_chain_err(e: nexum::host::chain::ChainError) -> $crate::host::ChainError { match e { nexum::host::chain::ChainError::Fault(f) => { - $crate::host::ChainError::Fault(convert_fault(f)) + $crate::host::ChainError::Fault(::core::convert::Into::into(f)) } nexum::host::chain::ChainError::Rpc(r) => { $crate::host::ChainError::Rpc($crate::host::RpcError { @@ -184,31 +191,31 @@ macro_rules! __bind_host_cap_via_wit_bindgen { ::core::option::Option<::std::vec::Vec>, $crate::host::Fault, > { - nexum::host::local_store::get(key).map_err(convert_fault) + nexum::host::local_store::get(key).map_err($crate::host::Fault::from) } fn set( &self, key: &str, value: &[u8], ) -> ::core::result::Result<(), $crate::host::Fault> { - nexum::host::local_store::set(key, value).map_err(convert_fault) + nexum::host::local_store::set(key, value).map_err($crate::host::Fault::from) } fn delete(&self, key: &str) -> ::core::result::Result<(), $crate::host::Fault> { - nexum::host::local_store::delete(key).map_err(convert_fault) + nexum::host::local_store::delete(key).map_err($crate::host::Fault::from) } fn list_keys( &self, prefix: &str, ) -> ::core::result::Result<::std::vec::Vec<::std::string::String>, $crate::host::Fault> { - nexum::host::local_store::list_keys(prefix).map_err(convert_fault) + nexum::host::local_store::list_keys(prefix).map_err($crate::host::Fault::from) } } }; (logging) => { impl $crate::host::LoggingHost for WitBindgenHost { fn log(&self, level: $crate::Level, message: &str) { - nexum::host::logging::log(convert_level(level), message); + nexum::host::logging::log(nexum::host::logging::Level::from(level), message); } } @@ -216,18 +223,19 @@ macro_rules! __bind_host_cap_via_wit_bindgen { /// `logging::Level` wire enum. `Level` is a set of associated /// consts, not a matchable enum, so compare rather than match; /// the five tiers are total, so the final arm is `Trace`. - fn convert_level(level: $crate::Level) -> nexum::host::logging::Level { - use $crate::Level; - if level == Level::ERROR { - nexum::host::logging::Level::Error - } else if level == Level::WARN { - nexum::host::logging::Level::Warn - } else if level == Level::INFO { - nexum::host::logging::Level::Info - } else if level == Level::DEBUG { - nexum::host::logging::Level::Debug - } else { - nexum::host::logging::Level::Trace + impl ::core::convert::From<$crate::Level> for nexum::host::logging::Level { + fn from(level: $crate::Level) -> Self { + if level == $crate::Level::ERROR { + Self::Error + } else if level == $crate::Level::WARN { + Self::Warn + } else if level == $crate::Level::INFO { + Self::Info + } else if level == $crate::Level::DEBUG { + Self::Debug + } else { + Self::Trace + } } } diff --git a/crates/shepherd-sdk/src/wit_bindgen_macro.rs b/crates/shepherd-sdk/src/wit_bindgen_macro.rs index 0a4c9b14..fa7060d2 100644 --- a/crates/shepherd-sdk/src/wit_bindgen_macro.rs +++ b/crates/shepherd-sdk/src/wit_bindgen_macro.rs @@ -3,7 +3,7 @@ //! //! Layers on `nexum_sdk::bind_host_via_wit_bindgen!`, which emits the //! core adapter (`WitBindgenHost`, the `ChainHost` / `LocalStoreHost` -//! / `LoggingHost` impls, the error and level converters, and the +//! / `LoggingHost` impls, the fault and level `From` impls, and the //! tracing wiring). This macro invokes it and adds the //! [`CowApiHost`](crate::cow::CowApiHost) impl over the //! `shepherd:cow/cow-api` import shims. @@ -37,7 +37,7 @@ macro_rules! bind_cow_host_via_wit_bindgen { fn convert_cow_err(e: shepherd::cow::cow_api::CowApiError) -> $crate::cow::CowApiError { match e { shepherd::cow::cow_api::CowApiError::Fault(f) => { - $crate::cow::CowApiError::Fault(convert_fault(f)) + $crate::cow::CowApiError::Fault(::core::convert::Into::into(f)) } shepherd::cow::cow_api::CowApiError::Http(h) => { $crate::cow::CowApiError::Http($crate::cow::HttpFailure { diff --git a/docs/05-sdk-design.md b/docs/05-sdk-design.md index 60594ad3..c5e4d812 100755 --- a/docs/05-sdk-design.md +++ b/docs/05-sdk-design.md @@ -176,14 +176,14 @@ struct HttpProbe; impl HttpProbe { fn init(config: Vec<(String, String)>) -> Result<(), Fault> { install_tracing(); - let cfg = strategy::parse_config(&config).map_err(sdk_fault_into_wit)?; + let cfg = strategy::parse_config(&config)?; // ... Ok(()) } fn on_block(block: types::Block) -> Result<(), Fault> { strategy::on_block(&nexum_sdk::http::WasiFetch, /* ... */ block.number) - .map_err(sdk_fault_into_wit) + .map_err(Into::into) } } ``` diff --git a/docs/tutorial-first-module.md b/docs/tutorial-first-module.md index 9c4c5604..6dc7d2ae 100644 --- a/docs/tutorial-first-module.md +++ b/docs/tutorial-first-module.md @@ -304,8 +304,8 @@ use std::sync::OnceLock; use nexum::host::types; -// `WitBindgenHost`, `convert_fault`, `sdk_fault_into_wit`, `convert_level`, -// `HostLogSink`, `install_tracing` are generated below. Single source +// `WitBindgenHost`, the fault and level `From` impls, `HostLogSink`, +// and `install_tracing` are generated below. Single source // of truth in `nexum-sdk` + `shepherd-sdk`. shepherd_sdk::bind_cow_host_via_wit_bindgen!(); @@ -316,7 +316,7 @@ struct StopLoss; impl Guest for StopLoss { fn init(config: Vec<(String, String)>) -> Result<(), Fault> { install_tracing(); - let cfg = strategy::parse_config(&config).map_err(sdk_fault_into_wit)?; + let cfg = strategy::parse_config(&config)?; tracing::info!( "stop-loss init: owner={:#x} trigger={} sell={:#x} buy={:#x}", cfg.owner, @@ -333,7 +333,7 @@ impl Guest for StopLoss { return Ok(()); }; if let types::Event::Block(block) = event { - strategy::on_block(&WitBindgenHost, block.chain_id, cfg).map_err(sdk_fault_into_wit)?; + strategy::on_block(&WitBindgenHost, block.chain_id, cfg)?; } Ok(()) } @@ -344,7 +344,7 @@ export!(StopLoss); The macro generates `WitBindgenHost`, the `ChainHost` / `LocalStoreHost` / `LoggingHost` / `CowApiHost` impls, the -`Fault` conversions (`convert_fault`, `sdk_fault_into_wit`), and +`Fault` `From` impls (both directions), and `install_tracing`, which installs the guest `tracing` facade so the `tracing::info!`, `warn!`, and `error!` macros reach the host log call with no `Host` value to thread through. Call it once at diff --git a/modules/ethflow-watcher/src/lib.rs b/modules/ethflow-watcher/src/lib.rs index 21fd3d1c..8cca9f36 100644 --- a/modules/ethflow-watcher/src/lib.rs +++ b/modules/ethflow-watcher/src/lib.rs @@ -47,7 +47,7 @@ wit_bindgen::generate!({ pub mod strategy; -// `WitBindgenHost`, `sdk_fault_into_wit`, `convert_level` +// `WitBindgenHost` and the fault and level `From` impls // are generated below. Single source of truth in `nexum-sdk` + `shepherd-sdk`. // Gated on `wasm32` so the strategy can be reused in native targets // (e.g. the backtest replay harness in `crates/shepherd-backtest`). @@ -72,8 +72,7 @@ impl Guest for EthFlowWatcher { if let types::Event::ChainLogs(batch) = event { let logs: Vec = batch.logs.into_iter().map(Into::into).collect(); - strategy::on_chain_logs(&WitBindgenHost, batch.chain_id, &logs) - .map_err(sdk_fault_into_wit)?; + strategy::on_chain_logs(&WitBindgenHost, batch.chain_id, &logs)?; } // Block / Tick / Message are not used by this module. Ok(()) diff --git a/modules/examples/balance-tracker/src/lib.rs b/modules/examples/balance-tracker/src/lib.rs index 263a7bcd..7b89ece6 100644 --- a/modules/examples/balance-tracker/src/lib.rs +++ b/modules/examples/balance-tracker/src/lib.rs @@ -35,7 +35,7 @@ use nexum::host::types; static SETTINGS: OnceLock = OnceLock::new(); -// `WitBindgenHost`, `sdk_fault_into_wit`, and `install_tracing` (used +// `WitBindgenHost`, the fault `From` impls, and `install_tracing` (used // by the handlers) are generated by the attribute alongside the // wit-bindgen call and the `Guest`/`export!` glue. struct BalanceTracker; @@ -44,7 +44,7 @@ struct BalanceTracker; impl BalanceTracker { fn init(config: Vec<(String, String)>) -> Result<(), Fault> { install_tracing(); - let cfg = strategy::parse_config(&config).map_err(sdk_fault_into_wit)?; + let cfg = strategy::parse_config(&config)?; tracing::info!( "balance-tracker init: {} addresses, threshold={} wei", cfg.addresses.len(), @@ -58,6 +58,6 @@ impl BalanceTracker { let Some(cfg) = SETTINGS.get() else { return Ok(()); }; - strategy::on_block(&WitBindgenHost, block.chain_id, cfg).map_err(sdk_fault_into_wit) + strategy::on_block(&WitBindgenHost, block.chain_id, cfg).map_err(Into::into) } } diff --git a/modules/examples/http-probe/src/lib.rs b/modules/examples/http-probe/src/lib.rs index c48377b1..672f0001 100644 --- a/modules/examples/http-probe/src/lib.rs +++ b/modules/examples/http-probe/src/lib.rs @@ -43,7 +43,7 @@ use nexum::host::types; static SETTINGS: OnceLock = OnceLock::new(); -// `sdk_fault_into_wit` and `install_tracing` (used by the handlers) are +// The fault `From` impls and `install_tracing` (used by the handlers) are // generated by the attribute alongside the wit-bindgen call and the // `Guest`/`export!` glue. struct HttpProbe; @@ -52,7 +52,7 @@ struct HttpProbe; impl HttpProbe { fn init(config: Vec<(String, String)>) -> Result<(), Fault> { install_tracing(); - let cfg = strategy::parse_config(&config).map_err(sdk_fault_into_wit)?; + let cfg = strategy::parse_config(&config)?; tracing::info!( "http-probe init: probe_url={} denied_url={} every_n_blocks={}", cfg.probe_url, @@ -67,7 +67,6 @@ impl HttpProbe { let Some(cfg) = SETTINGS.get() else { return Ok(()); }; - strategy::on_block(&nexum_sdk::http::WasiFetch, cfg, block.number) - .map_err(sdk_fault_into_wit) + strategy::on_block(&nexum_sdk::http::WasiFetch, cfg, block.number).map_err(Into::into) } } diff --git a/modules/examples/price-alert/src/lib.rs b/modules/examples/price-alert/src/lib.rs index 94285268..f8e3a825 100644 --- a/modules/examples/price-alert/src/lib.rs +++ b/modules/examples/price-alert/src/lib.rs @@ -50,7 +50,7 @@ use nexum::host::types; static SETTINGS: OnceLock = OnceLock::new(); -// `WitBindgenHost`, `sdk_fault_into_wit`, and `install_tracing` (used +// `WitBindgenHost`, the fault `From` impls, and `install_tracing` (used // by the handlers) are generated by the attribute alongside the // wit-bindgen call and the `Guest`/`export!` glue. struct PriceAlert; @@ -59,7 +59,7 @@ struct PriceAlert; impl PriceAlert { fn init(config: Vec<(String, String)>) -> Result<(), Fault> { install_tracing(); - let cfg = strategy::parse_config(&config).map_err(sdk_fault_into_wit)?; + let cfg = strategy::parse_config(&config)?; tracing::info!( "price-alert init: oracle={:#x} threshold={} direction={:?} every_n_blocks={}", cfg.oracle_address, @@ -75,7 +75,6 @@ impl PriceAlert { let Some(cfg) = SETTINGS.get() else { return Ok(()); }; - strategy::on_block(&WitBindgenHost, block.chain_id, cfg, block.number) - .map_err(sdk_fault_into_wit) + strategy::on_block(&WitBindgenHost, block.chain_id, cfg, block.number).map_err(Into::into) } } diff --git a/modules/examples/stop-loss/src/lib.rs b/modules/examples/stop-loss/src/lib.rs index ff846bdf..5e203753 100644 --- a/modules/examples/stop-loss/src/lib.rs +++ b/modules/examples/stop-loss/src/lib.rs @@ -37,7 +37,7 @@ use std::sync::OnceLock; use nexum::host::types; -// `WitBindgenHost`, `sdk_fault_into_wit`, `convert_level` are generated +// `WitBindgenHost` and the fault and level `From` impls are generated // below. Single source of truth in `nexum-sdk` + `shepherd-sdk`. shepherd_sdk::bind_cow_host_via_wit_bindgen!(); @@ -48,7 +48,7 @@ struct StopLoss; impl Guest for StopLoss { fn init(config: Vec<(String, String)>) -> Result<(), Fault> { install_tracing(); - let cfg = strategy::parse_config(&config).map_err(sdk_fault_into_wit)?; + let cfg = strategy::parse_config(&config)?; tracing::info!( "stop-loss init: owner={:#x} trigger={} sell={:#x} buy={:#x}", cfg.owner, @@ -65,7 +65,7 @@ impl Guest for StopLoss { return Ok(()); }; if let types::Event::Block(block) = event { - strategy::on_block(&WitBindgenHost, block.chain_id, cfg).map_err(sdk_fault_into_wit)?; + strategy::on_block(&WitBindgenHost, block.chain_id, cfg)?; } Ok(()) } diff --git a/modules/twap-monitor/src/lib.rs b/modules/twap-monitor/src/lib.rs index 0483cb52..fda3ac82 100644 --- a/modules/twap-monitor/src/lib.rs +++ b/modules/twap-monitor/src/lib.rs @@ -36,7 +36,7 @@ mod strategy; use nexum::host::types; -// `WitBindgenHost`, `sdk_fault_into_wit`, `convert_level` +// `WitBindgenHost` and the fault and level `From` impls // are generated below. Single source of truth in `nexum-sdk` + `shepherd-sdk`. shepherd_sdk::bind_cow_host_via_wit_bindgen!(); @@ -54,7 +54,7 @@ impl Guest for TwapMonitor { types::Event::ChainLogs(batch) => { let logs: Vec = batch.logs.into_iter().map(Into::into).collect(); - strategy::on_chain_logs(&WitBindgenHost, &logs).map_err(sdk_fault_into_wit)?; + strategy::on_chain_logs(&WitBindgenHost, &logs)?; } types::Event::Block(block) => { let info = strategy::BlockInfo { @@ -62,7 +62,7 @@ impl Guest for TwapMonitor { number: block.number, timestamp: block.timestamp, }; - strategy::on_block(&WitBindgenHost, info).map_err(sdk_fault_into_wit)?; + strategy::on_block(&WitBindgenHost, info)?; } // Tick / Message are not used by this module. _ => {}