diff --git a/Cargo.lock b/Cargo.lock index 8aa31b32..2a6896a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1559,6 +1559,7 @@ version = "0.1.0" dependencies = [ "borsh", "nexum-sdk", + "nexum-venue-test", "serde", "thiserror 2.0.18", "toml 1.1.2+spec-1.1.0", @@ -2164,7 +2165,7 @@ version = "0.1.0" dependencies = [ "nexum-venue-test", "videre-sdk", - "wit-bindgen 0.58.0", + "wit-bindgen 0.59.0", ] [[package]] @@ -2382,7 +2383,7 @@ name = "flaky-venue" version = "0.1.0" dependencies = [ "videre-sdk", - "wit-bindgen 0.58.0", + "wit-bindgen 0.59.0", ] [[package]] diff --git a/crates/cow-venue/Cargo.toml b/crates/cow-venue/Cargo.toml index c7ab7fb4..8365b586 100644 --- a/crates/cow-venue/Cargo.toml +++ b/crates/cow-venue/Cargo.toml @@ -39,6 +39,8 @@ thiserror = { workspace = true } serde = { workspace = true } toml = { workspace = true } thiserror = { workspace = true } +# The conformance kit: holds the body codec to its published vector set. +nexum-venue-test = { path = "../nexum-venue-test" } [features] # The body-type + codec slice ships by default; the `client` slice layers diff --git a/crates/cow-venue/src/body.rs b/crates/cow-venue/src/body.rs index 8caed852..b346fd5e 100644 --- a/crates/cow-venue/src/body.rs +++ b/crates/cow-venue/src/body.rs @@ -36,7 +36,7 @@ pub enum CowIntentBody { #[cfg(test)] mod tests { use super::*; - use videre_sdk::BodyError; + use nexum_venue_test::{CodecVectors, Expectation}; use crate::order::{BuyTokenDestination, OrderKind, SellTokenSource}; @@ -65,16 +65,52 @@ mod tests { } } + /// The codec conformance set: both v1 intents as round-trip vectors + /// plus the typed failure contract, in the kit's published form. + fn vectors() -> CodecVectors { + let mut vectors = CodecVectors::new("cow-venue/cow-intent-body"); + vectors + .push_round_trip( + "v1-order", + &CowIntentBody::V1(CowIntent::Order(order_body())), + ) + .expect("order body encodes"); + vectors + .push_round_trip( + "v1-composable", + &CowIntentBody::V1(CowIntent::Composable(composable_body())), + ) + .expect("composable body encodes"); + + let bytes = |intent: CowIntent| CowIntentBody::V1(intent).to_bytes().expect("body encodes"); + let mut unknown = bytes(CowIntent::Order(order_body())); + unknown[0] = 9; + vectors.push_failure( + "unknown-version", + unknown, + Expectation::UnknownVersion { version: 9 }, + ); + vectors.push_failure("empty", Vec::new(), Expectation::Empty); + let mut truncated = bytes(CowIntent::Order(order_body())); + truncated.truncate(truncated.len() - 1); + vectors.push_failure( + "truncated-payload", + truncated, + Expectation::Malformed { version: 0 }, + ); + let mut trailing = bytes(CowIntent::Composable(composable_body())); + trailing.push(0); + vectors.push_failure( + "trailing-bytes", + trailing, + Expectation::Malformed { version: 0 }, + ); + vectors + } + #[test] - fn version_body_round_trips_through_the_derive() { - for intent in [ - CowIntent::Order(order_body()), - CowIntent::Composable(composable_body()), - ] { - let body = CowIntentBody::V1(intent); - let bytes = body.to_bytes().expect("derived payload encodes"); - assert_eq!(CowIntentBody::from_bytes(&bytes).unwrap(), body); - } + fn codec_conforms_to_its_vectors() { + vectors().assert_conforms::(); } #[test] @@ -86,37 +122,15 @@ mod tests { } #[test] - fn unknown_version_fails_typedly() { - let mut bytes = CowIntentBody::V1(CowIntent::Order(order_body())) - .to_bytes() - .unwrap(); - bytes[0] = 9; - assert_eq!( - CowIntentBody::from_bytes(&bytes), - Err(BodyError::UnknownVersion { version: 9 }) + fn divergent_codec_is_caught_by_the_vectors() { + // A vector claiming a different typed failure must fail the + // check, proving it has teeth on this schema. + let mut vectors = CodecVectors::new("cow-venue/cow-intent-body"); + vectors.push_failure( + "empty", + Vec::new(), + Expectation::UnknownVersion { version: 1 }, ); - } - - #[test] - fn empty_and_malformed_bodies_fail_typedly() { - assert_eq!(CowIntentBody::from_bytes(&[]), Err(BodyError::Empty)); - - let mut bytes = CowIntentBody::V1(CowIntent::Order(order_body())) - .to_bytes() - .unwrap(); - bytes.truncate(bytes.len() - 1); - assert!(matches!( - CowIntentBody::from_bytes(&bytes), - Err(BodyError::Malformed { version: 0, .. }) - )); - - let mut bytes = CowIntentBody::V1(CowIntent::Composable(composable_body())) - .to_bytes() - .unwrap(); - bytes.push(0); - assert!(matches!( - CowIntentBody::from_bytes(&bytes), - Err(BodyError::Malformed { version: 0, .. }) - )); + assert!(vectors.check::().is_err()); } } diff --git a/crates/nexum-venue-test/src/header.rs b/crates/nexum-venue-test/src/header.rs index 5791859e..91063dc4 100644 --- a/crates/nexum-venue-test/src/header.rs +++ b/crates/nexum-venue-test/src/header.rs @@ -7,11 +7,9 @@ //! (JSON, a leading format version that fails closed on an unknown tag, //! kebab-case case names matching the WIT, bytes as lowercase hex, //! never zero goldens). The mirrors exist because wit-bindgen types -//! carry no serde; -//! [`GoldenHeader`] converts from the venue SDK's `IntentHeader`, and a -//! macro-built adapter whose bindgen mints its own header type bridges -//! with a field-for-field `From` impl on its crate boundary, the same -//! pattern `nexum-sdk-test` documents for `Fault`. +//! carry no serde; [`GoldenHeader`] converts from the venue SDK's +//! `IntentHeader`, which macro-built adapters speak too, so an +//! adapter's `derive_header` feeds the check directly. use std::fmt; use std::path::Path; diff --git a/crates/nexum-venue-test/src/lib.rs b/crates/nexum-venue-test/src/lib.rs index a4c4fd83..9eae0b8e 100644 --- a/crates/nexum-venue-test/src/lib.rs +++ b/crates/nexum-venue-test/src/lib.rs @@ -52,11 +52,11 @@ //! //! ## Macro-built adapters //! -//! `#[nexum::venue]` adapters mint their own bindgen header type. The -//! codec check is unaffected (bodies are plain Rust types); for the -//! golden check, bridge with a field-for-field `From for -//! GoldenHeader` impl on the adapter crate's boundary, the same -//! trivial-converter pattern `nexum-sdk-test` documents for `Fault`. +//! `#[videre_sdk::venue]` adapters speak the SDK's own types (the +//! macro remaps the type interfaces onto `videre_sdk::bindings`), so +//! both checks apply directly: pass `MyAdapter::derive_header` to +//! [`HeaderGoldens::assert_conforms`] and the derived enum to +//! [`CodecVectors::assert_conforms`]. No bridge types. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![warn(missing_docs)] diff --git a/crates/nexum-world/src/lib.rs b/crates/nexum-world/src/lib.rs index 01971b2d..aae0b9db 100644 --- a/crates/nexum-world/src/lib.rs +++ b/crates/nexum-world/src/lib.rs @@ -144,6 +144,21 @@ pub fn manifest_capabilities(text: &str) -> Result, String> { Ok(names) } +/// Extract the declared `[module] kind` from the manifest text, `None` +/// when absent (the runtime defaults an absent kind to the worker). +pub fn manifest_kind(text: &str) -> Result, String> { + let value: toml::Table = text + .parse() + .map_err(|e| format!("module.toml is not valid TOML: {e}"))?; + match value.get("module").and_then(|module| module.get("kind")) { + None => Ok(None), + Some(kind) => kind + .as_str() + .map(|kind| Some(kind.to_owned())) + .ok_or_else(|| "[module].kind must be a string".to_string()), + } +} + /// Parse the registered extension rows from an `extensions.toml`. Each /// `[extensions.]` table carries the WIT `import` the declaration /// turns into and the extra `packages` its resolve path needs. A file @@ -513,6 +528,24 @@ allow = [] assert_eq!(caps, vec!["logging", "chain", "remote-store"]); } + #[test] + fn manifest_kind_reads_the_module_kind() { + let kind = manifest_kind("[module]\nname = \"x\"\nkind = \"venue-adapter\"\n").unwrap(); + assert_eq!(kind.as_deref(), Some("venue-adapter")); + } + + #[test] + fn manifest_without_a_kind_is_none() { + assert_eq!(manifest_kind("[module]\nname = \"x\"\n").unwrap(), None); + assert_eq!(manifest_kind("").unwrap(), None); + } + + #[test] + fn manifest_with_a_non_string_kind_is_an_error() { + let err = manifest_kind("[module]\nkind = 3\n").unwrap_err(); + assert!(err.contains("[module].kind must be a string")); + } + #[test] fn manifest_without_capabilities_section_is_an_error() { let err = manifest_capabilities("[module]\nname = \"x\"\n").unwrap_err(); diff --git a/crates/videre-macros/Cargo.toml b/crates/videre-macros/Cargo.toml index 0b65cbde..31f0b9a3 100644 --- a/crates/videre-macros/Cargo.toml +++ b/crates/videre-macros/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition.workspace = true license.workspace = true repository.workspace = true -description = "Proc-macro glue for videre venue adapters: #[venue] emits the per-cdylib wit-bindgen and adapter export; derive(IntentBody) emits the versioned body codec." +description = "Proc-macro glue for videre venue adapters: #[venue] turns an impl VenueAdapter into the per-cdylib wit-bindgen and adapter export; derive(IntentBody) emits the versioned body codec." [lib] proc-macro = true diff --git a/crates/videre-macros/src/lib.rs b/crates/videre-macros/src/lib.rs index feb1b620..77f83e0c 100644 --- a/crates/videre-macros/src/lib.rs +++ b/crates/videre-macros/src/lib.rs @@ -1,9 +1,9 @@ //! Proc-macro glue for videre venue adapters. //! -//! [`venue`] emits the per-cdylib wit-bindgen and `export!` for a -//! per-component venue-adapter world exporting the -//! `videre:venue/adapter` face and importing only the manifest's -//! declared scoped transport. +//! [`venue`] is the single blessed venue authoring path: applied to an +//! `impl VenueAdapter` block it emits the per-cdylib wit-bindgen for a +//! manifest-derived world exporting `videre:venue/adapter`, asserts the +//! manifest kind, and expands to the SDK's internal export codegen. //! //! [`derive@IntentBody`] implements the venue SDK's versioned body codec //! over a per-venue version enum. @@ -19,7 +19,7 @@ mod world; use proc_macro::TokenStream; use quote::quote; -use syn::{DeriveInput, ImplItem, ItemImpl, Type}; +use syn::{DeriveInput, ItemImpl, Type}; /// Derive the venue SDK's `IntentBody` codec on the outer per-venue /// version enum: one newtype variant per published body version, each @@ -41,26 +41,26 @@ pub fn derive_intent_body(input: TokenStream) -> TokenStream { .into() } -/// The associated functions the `videre:venue/adapter` face mandates. A -/// venue adapter must define all five; `init` is separate (a no-op when -/// absent, exactly as in a module). -const VENUE_EXPORTS: [&str; 5] = ["derive_header", "quote", "submit", "status", "cancel"]; +/// The manifest `kind` a venue adapter must declare. Mirrors the +/// venue-adapter provider kind's manifest spelling. +const VENUE_KIND: &str = "venue-adapter"; /// Generate the per-cdylib glue for a venue adapter. /// -/// Apply to an inherent `impl` block whose associated functions are the -/// adapter face: `derive_header`, `quote`, `submit`, `status`, `cancel` -/// (all required, from `videre:venue/adapter`), plus an optional `init` -/// (absent means a no-op) and an optional `body_versions` (absent -/// declares none). Each takes and returns the per-cdylib -/// wit-bindgen payloads for its signature. The macro reads the crate's -/// `module.toml`, synthesizes a per-component world exporting the -/// adapter face and importing exactly the manifest's declared scoped -/// transport, then emits `wit_bindgen::generate!`, the `Guest` impls -/// wiring the world to the adapter's functions, and `export!` around the -/// untouched impl. So the built component imports what the manifest -/// declares and nothing else, retiring the toolchain-elision dependency -/// on the venue side. +/// Apply to the adapter's `impl VenueAdapter for MyVenue` block: the +/// macro reads the crate's `module.toml`, asserts its `[module] kind` +/// is `venue-adapter`, synthesizes a per-component world exporting the +/// `videre:venue/adapter` face and importing exactly the manifest's +/// declared scoped transport, then emits `wit_bindgen::generate!`, the +/// untouched trait impl, and the SDK's internal export codegen wiring +/// the world's `Guest` faces through the trait. So the built component +/// imports what the manifest declares and nothing else, by construction +/// of the emitted world. +/// +/// The generated world remaps `videre:types/types`, +/// `videre:value-flow/types`, and `nexum:host/types` onto the SDK's +/// bindings, so the impl speaks `videre_sdk` types directly and shares +/// type identity with the conformance kit and the client core. /// /// A venue's capabilities are scoped transport only: an undeclared /// capability's bindings do not exist (using one is a compile error), @@ -68,10 +68,10 @@ const VENUE_EXPORTS: [&str; 5] = ["derive_header", "quote", "submit", "status", /// `messaging`, `http`) is rejected at expansion. /// /// The same crate-root resolution invariants as `#[module]` apply: the -/// wit-bindgen output lands at the module crate root (so the emitted -/// glue resolves `Guest`, `Fault`, and the `nexum::*`/`videre::*` type modules -/// there), the consuming crate must declare `wit-bindgen` as a direct -/// dependency, and the crate root must not shadow std prelude names. +/// wit-bindgen output lands at the module crate root (so the export +/// codegen resolves `Guest`, `exports`, and `export!` there), and the +/// consuming crate must declare `wit-bindgen` and `videre-sdk` as +/// direct dependencies. #[proc_macro_attribute] pub fn venue(attr: TokenStream, item: TokenStream) -> TokenStream { if !attr.is_empty() { @@ -85,51 +85,39 @@ pub fn venue(attr: TokenStream, item: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(item as ItemImpl); - let self_ty = &input.self_ty; - if !is_plain_type(self_ty) { + let Some((None, trait_path, _)) = &input.trait_ else { return syn::Error::new_spanned( - self_ty, - "#[videre_sdk::venue] must be applied to an inherent impl of a named type", + &input.self_ty, + "#[videre_sdk::venue] must be applied to an `impl VenueAdapter for ...` block", ) .to_compile_error() .into(); - } - if let Some((_, trait_path, _)) = &input.trait_ { + }; + if trait_path + .segments + .last() + .is_none_or(|segment| segment.ident != "VenueAdapter") + { return syn::Error::new_spanned( trait_path, - "#[videre_sdk::venue] must be applied to an inherent impl, not a trait impl", + "#[videre_sdk::venue] must be applied to an impl of `videre_sdk::VenueAdapter`", ) .to_compile_error() .into(); } - if !input.generics.params.is_empty() { + let self_ty = &input.self_ty; + if !is_plain_type(self_ty) { return syn::Error::new_spanned( - &input.generics, - "#[videre_sdk::venue] must be applied to a non-generic impl", + self_ty, + "#[videre_sdk::venue] must be applied to an impl on a named type", ) .to_compile_error() .into(); } - - let defines = |name: &str| { - input - .items - .iter() - .any(|item| matches!(item, ImplItem::Fn(f) if f.sig.ident == name)) - }; - let missing: Vec<&str> = VENUE_EXPORTS - .into_iter() - .filter(|name| !defines(name)) - .collect(); - if !missing.is_empty() { + if !input.generics.params.is_empty() { return syn::Error::new_spanned( - self_ty, - format!( - "#[videre_sdk::venue] requires the adapter face; this impl is missing {:?}. \ - Define all of `derive_header`, `quote`, `submit`, `status`, `cancel` (plus an \ - optional `init`)", - missing - ), + &input.generics, + "#[videre_sdk::venue] must be applied to a non-generic impl", ) .to_compile_error() .into(); @@ -153,43 +141,6 @@ pub fn venue(attr: TokenStream, item: TokenStream) -> TokenStream { }; let inline_world = &venue_world.wit; - // `body-versions` is a required adapter export; when the adapter - // omits it, declare none. Install asserts the export equals the - // manifest `[venue] body_versions` set. - let body_versions_impl = if defines("body_versions") { - quote! { - fn body_versions() -> ::std::vec::Vec { - <#self_ty>::body_versions() - } - } - } else { - quote! { - fn body_versions() -> ::std::vec::Vec { - ::std::vec::Vec::new() - } - } - }; - - // `init` is a required world export; when the adapter omits it the - // config is bound but unused, so drop it to stay warning-clean. - let init_impl = if defines("init") { - quote! { - fn init( - config: ::std::vec::Vec<(::std::string::String, ::std::string::String)>, - ) -> ::core::result::Result<(), Fault> { - <#self_ty>::init(config) - } - } - } else { - quote! { - fn init( - _config: ::std::vec::Vec<(::std::string::String, ::std::string::String)>, - ) -> ::core::result::Result<(), Fault> { - ::core::result::Result::Ok(()) - } - } - }; - quote! { // Anchor a rebuild on the manifest: the emitted world is derived // from it, so an edited [capabilities] must recompile the adapter. @@ -200,64 +151,17 @@ pub fn venue(attr: TokenStream, item: TokenStream) -> TokenStream { path: [#(#wit_paths),*], world: "nexum:venue-world/venue-adapter", generate_all, + with: { + "nexum:host/types@0.1.0": ::videre_sdk::bindings::nexum::host::types, + "videre:types/types@0.1.0": ::videre_sdk::bindings::videre::types::types, + "videre:value-flow/types@0.1.0": + ::videre_sdk::bindings::videre::value_flow::types, + }, }); #input - #[doc(hidden)] - struct __NexumVenueAdapterExport; - - impl Guest for __NexumVenueAdapterExport { - #init_impl - } - - impl exports::videre::venue::adapter::Guest for __NexumVenueAdapterExport { - #body_versions_impl - - fn derive_header( - body: ::std::vec::Vec, - ) -> ::core::result::Result< - videre::types::types::IntentHeader, - videre::types::types::VenueError, - > { - <#self_ty>::derive_header(body) - } - - fn quote( - body: ::std::vec::Vec, - ) -> ::core::result::Result< - videre::types::types::Quotation, - videre::types::types::VenueError, - > { - <#self_ty>::quote(body) - } - - fn submit( - body: ::std::vec::Vec, - ) -> ::core::result::Result< - videre::types::types::SubmitOutcome, - videre::types::types::VenueError, - > { - <#self_ty>::submit(body) - } - - fn status( - receipt: ::std::vec::Vec, - ) -> ::core::result::Result< - videre::types::types::IntentStatus, - videre::types::types::VenueError, - > { - <#self_ty>::status(receipt) - } - - fn cancel( - receipt: ::std::vec::Vec, - ) -> ::core::result::Result<(), videre::types::types::VenueError> { - <#self_ty>::cancel(receipt) - } - } - - export!(__NexumVenueAdapterExport); + ::videre_sdk::__export_venue_adapter!(#self_ty); } .into() } @@ -276,10 +180,10 @@ fn manifest_dir() -> Result { .map_err(|_| "CARGO_MANIFEST_DIR is not set".to_string()) } -/// Read the consuming crate's `module.toml` and synthesize the -/// per-component venue-adapter world from its `[capabilities]` -/// declarations. Returns the manifest path (for the rebuild anchor) -/// alongside the world. +/// Read the consuming crate's `module.toml`, assert it declares the +/// venue-adapter kind, and synthesize the per-component venue-adapter +/// world from its `[capabilities]` declarations. Returns the manifest +/// path (for the rebuild anchor) alongside the world. fn derive_venue_world() -> Result<(String, nexum_world::ModuleWorld), String> { let manifest_path = manifest_dir()?.join("module.toml"); let text = std::fs::read_to_string(&manifest_path).map_err(|e| { @@ -290,6 +194,16 @@ fn derive_venue_world() -> Result<(String, nexum_world::ModuleWorld), String> { manifest_path.display() ) })?; + let kind = nexum_world::manifest_kind(&text) + .map_err(|e| format!("{}: {e}", manifest_path.display()))?; + if kind.as_deref() != Some(VENUE_KIND) { + return Err(format!( + "{}: [module] kind must be \"{VENUE_KIND}\" for a #[videre_sdk::venue] adapter, \ + found {}", + manifest_path.display(), + kind.map_or_else(|| "none".to_owned(), |kind| format!("\"{kind}\"")), + )); + } let declared = nexum_world::manifest_capabilities(&text) .map_err(|e| format!("{}: {e}", manifest_path.display()))?; let manifest_path = manifest_path.to_string_lossy().into_owned(); diff --git a/crates/videre-sdk/src/adapter.rs b/crates/videre-sdk/src/adapter.rs index a2d58d9a..0e6f3edb 100644 --- a/crates/videre-sdk/src/adapter.rs +++ b/crates/videre-sdk/src/adapter.rs @@ -1,5 +1,5 @@ -//! The [`VenueAdapter`] trait and the export glue that turns an impl of -//! it into the component's `venue-adapter` world surface. +//! The [`VenueAdapter`] trait and the internal export codegen that turns +//! an impl of it into the component's `venue-adapter` world surface. //! //! The trait mirrors the world's export face one to one: `init` from the //! world itself, the intent functions and the body-version declaration @@ -11,8 +11,8 @@ use crate::{Config, Fault, IntentHeader, IntentStatus, Quotation, SubmitOutcome, VenueError}; /// One venue's protocol speaker: the guest-side face of the -/// `venue-adapter` world. Implement it on a unit struct and hand that to -/// [`export_venue_adapter!`](crate::export_venue_adapter); bodies and +/// `venue-adapter` world. Implement it on a unit struct and apply +/// [`#[videre_sdk::venue]`](crate::venue) to the impl; bodies and /// receipts arrive as the opaque bytes the wire carries, and impls /// recover typing through [`IntentBody`](crate::IntentBody) (whose /// [`BodyError`](crate::BodyError) converts into [`VenueError`] via `?`). @@ -54,20 +54,20 @@ pub trait VenueAdapter { fn cancel(receipt: Vec) -> Result<(), VenueError>; } -/// Export a [`VenueAdapter`] impl as the crate's `venue-adapter` world. -/// -/// Invoke once at the top level of the adapter's cdylib crate. Emits a -/// hidden shim type wiring the world's `Guest` traits to the adapter's -/// associated functions, then the wit-bindgen export glue; the linker -/// rejects a second invocation in one component (duplicate export -/// symbols), matching the one-adapter-per-component contract. +/// Internal codegen `#[videre_sdk::venue]` expands to: a hidden shim +/// wiring a [`VenueAdapter`] impl to the macro-synthesized world's +/// `Guest` faces, then that world's `export!`. `Guest`, `exports`, and +/// `export!` resolve at the expansion site (the adapter crate root, +/// where the attribute put the world's bindgen), so the macro is +/// meaningful only inside the attribute's output. Not public API. +#[doc(hidden)] #[macro_export] -macro_rules! export_venue_adapter { +macro_rules! __export_venue_adapter { ($adapter:ty) => { #[doc(hidden)] struct __VidereVenueAdapterExport; - impl $crate::bindings::Guest for __VidereVenueAdapterExport { + impl Guest for __VidereVenueAdapterExport { fn init( config: ::std::vec::Vec<(::std::string::String, ::std::string::String)>, ) -> ::core::result::Result<(), $crate::Fault> { @@ -75,9 +75,7 @@ macro_rules! export_venue_adapter { } } - impl $crate::bindings::exports::videre::venue::adapter::Guest - for __VidereVenueAdapterExport - { + impl exports::videre::venue::adapter::Guest for __VidereVenueAdapterExport { fn body_versions() -> ::std::vec::Vec { <$adapter as $crate::VenueAdapter>::body_versions() } @@ -113,8 +111,6 @@ macro_rules! export_venue_adapter { } } - $crate::bindings::__export_venue_adapter_world!( - __VidereVenueAdapterExport with_types_in $crate::bindings - ); + export!(__VidereVenueAdapterExport); }; } diff --git a/crates/videre-sdk/src/bindings.rs b/crates/videre-sdk/src/bindings.rs index e735f93c..eb426521 100644 --- a/crates/videre-sdk/src/bindings.rs +++ b/crates/videre-sdk/src/bindings.rs @@ -4,11 +4,10 @@ //! the venue SDK generates the adapter world's bindings once, here: the //! [`VenueAdapter`](crate::VenueAdapter) trait, the typed transport //! wrappers, and the intent client core are all expressed over these -//! types, and [`export_venue_adapter!`](crate::export_venue_adapter) -//! emits the component export glue into the adapter's own cdylib via the -//! generated (hidden) export macro. Downstream bindgens wanting type -//! identity with this crate remap `videre:types/types` and -//! `videre:value-flow/types` onto these modules with `with`. +//! types. The `#[videre_sdk::venue]` attribute's per-cdylib bindgen +//! remaps `videre:types/types`, `videre:value-flow/types`, and +//! `nexum:host/types` onto these modules with `with`, so a macro-built +//! adapter shares type identity with the SDK and the conformance kit. wit_bindgen::generate!({ path: [ @@ -19,8 +18,5 @@ wit_bindgen::generate!({ ], world: "videre:venue/venue-adapter", generate_all, - pub_export_macro: true, - export_macro_name: "__export_venue_adapter_world", - default_bindings_module: "videre_sdk::bindings", additional_derives: [PartialEq], }); diff --git a/crates/videre-sdk/src/lib.rs b/crates/videre-sdk/src/lib.rs index e4d1da82..4c34ada2 100644 --- a/crates/videre-sdk/src/lib.rs +++ b/crates/videre-sdk/src/lib.rs @@ -9,9 +9,9 @@ //! ## What lives here //! //! - [`VenueAdapter`] - the trait mirroring the world's export face -//! (`init` plus the five intent functions), and -//! [`export_venue_adapter!`] which turns an impl into the component's -//! export glue. +//! (`init` plus the five intent functions). `#[videre_sdk::venue]` +//! on the impl turns it into the component's export glue: the single +//! blessed authoring path. //! //! - [`IntentBody`] (trait and derive) with [`BodyError`] - the borsh //! codec over the outer per-venue version enum. The wire form is a @@ -42,11 +42,11 @@ //! //! ## Why the bindgen lives in this crate //! -//! Unlike event modules (per-cdylib `wit_bindgen::generate!`), the -//! adapter world's bindings generate once, in [`bindings`]: the trait, -//! wrappers, and client core are all typed over them, and the export -//! macro reaches back in via `with_types_in`. An adapter crate therefore -//! needs no wit-bindgen dependency and no world knowledge of its own. +//! The adapter world's types generate once, in [`bindings`]: the trait, +//! wrappers, and client core are all typed over them. `#[venue]`'s +//! per-cdylib bindgen remaps the type interfaces onto [`bindings`], so +//! an adapter speaks these types while its world imports stay derived +//! from its own manifest. //! //! [`ChainHost`]: nexum_sdk::host::ChainHost //! [`IntentClient`]: client::IntentClient @@ -73,17 +73,12 @@ pub use keeper::{Keeper, Sweep, SweepReport}; /// Derive [`IntentBody`] on the outer per-venue version enum. See /// [`videre_macros::IntentBody`]. pub use videre_macros::IntentBody; -/// Emit the per-cdylib export glue and per-component world for a venue -/// adapter. Apply to an inherent `impl` of the adapter face -/// (`derive_header`, `quote`, `submit`, `status`, `cancel`, plus an -/// optional `init`); the built component imports exactly the manifest's declared -/// scoped transport. See [`videre_macros::venue`]. -/// -/// The self-contained per-cdylib alternative to -/// [`export_venue_adapter!`]: that macro exports through this crate's -/// shared blanket-world bindgen (chain and messaging always imported, -/// relying on toolchain elision), whereas `#[venue]` derives a narrowed -/// world from the manifest and generates its own bindings. +/// The single blessed venue authoring path. Apply to the adapter's +/// `impl VenueAdapter for MyVenue` block: emits the per-cdylib bindgen +/// for a world derived from `module.toml` (asserting its +/// `kind = "venue-adapter"`), the `videre:venue/adapter` export glue, +/// and `export!`. The built component imports exactly the manifest's +/// declared scoped transport. See [`videre_macros::venue`]. pub use videre_macros::venue; /// The intent ontology at its plain spellings: the types the diff --git a/crates/videre-sdk/tests/adapter.rs b/crates/videre-sdk/tests/adapter.rs index 8af3621f..e71fe381 100644 --- a/crates/videre-sdk/tests/adapter.rs +++ b/crates/videre-sdk/tests/adapter.rs @@ -1,9 +1,9 @@ //! Acceptance surface for the venue SDK: a hand-written adapter -//! compiles against [`VenueAdapter`], exports through -//! `export_venue_adapter!`, and round-trips a versioned body through -//! `#[derive(IntentBody)]` - including the typed unknown-version -//! failure and the typed client core driving the adapter through the -//! [`VenueClient`] seam. +//! compiles against [`VenueAdapter`] and round-trips a versioned body +//! through `#[derive(IntentBody)]` - including the typed +//! unknown-version failure and the typed client core driving the +//! adapter through the [`VenueClient`] seam. The world-export glue is +//! `#[videre_sdk::venue]`'s alone; echo-venue is its worked target. use borsh::{BorshDeserialize, BorshSerialize}; use videre_sdk::value_flow::{Asset, AssetAmount}; @@ -115,10 +115,6 @@ impl VenueAdapter for DemoAdapter { } } -// The acceptance gate proper: the hand-written adapter exports as the -// venue-adapter world. -videre_sdk::export_venue_adapter!(DemoAdapter); - /// In-process client: routes the demo venue id straight into the adapter, /// standing in for the host registry the keeper-side seam will bind. struct InProcessClient; diff --git a/modules/examples/echo-venue/Cargo.toml b/modules/examples/echo-venue/Cargo.toml index cd7f6172..8db8e22d 100644 --- a/modules/examples/echo-venue/Cargo.toml +++ b/modules/examples/echo-venue/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["cdylib"] [dependencies] videre-sdk = { path = "../../../crates/videre-sdk" } -wit-bindgen = { version = "0.58", default-features = false, features = ["macros", "realloc"] } +wit-bindgen = { version = "0.59", default-features = false, features = ["macros", "realloc"] } [dev-dependencies] # The conformance kit: holds this adapter's header derivation to the kit's diff --git a/modules/examples/echo-venue/src/lib.rs b/modules/examples/echo-venue/src/lib.rs index 7e62a376..feb69b1a 100644 --- a/modules/examples/echo-venue/src/lib.rs +++ b/modules/examples/echo-venue/src/lib.rs @@ -4,10 +4,10 @@ //! as the receipt, and settles instantly (every receipt it issued reports //! `fulfilled`). It carries no real venue protocol, so it doubles as the //! smallest end-to-end demonstration of `#[videre_sdk::venue]` - the -//! attribute supplies the per-cdylib wit-bindgen call for a world derived -//! from `module.toml`, the `Guest` export glue, and `export!`, leaving only -//! the adapter face - and as the `nexum-venue-test` conformance target (see -//! the tests below). +//! attribute takes the `impl VenueAdapter` block and supplies the +//! per-cdylib wit-bindgen for a world derived from `module.toml` plus the +//! export glue - and as the `nexum-venue-test` conformance target (see the +//! tests below). //! //! It declares one capability (`chain`), so the built component imports //! `nexum:host/chain` and nothing else: the per-component world matches @@ -18,16 +18,19 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![allow(clippy::too_many_arguments)] +// `Config` and `Fault` come from the macro's world bindgen at the crate +// root: aliases of the SDK types, so the trait impl lines up. use nexum::host::chain; -use videre::types::types::{ - AuthScheme, IntentHeader, IntentStatus, Quotation, Settlement, SubmitOutcome, VenueError, +use videre_sdk::value_flow::{Asset, AssetAmount}; +use videre_sdk::{ + AuthScheme, IntentHeader, IntentStatus, Quotation, Settlement, SubmitOutcome, VenueAdapter, + VenueError, }; -use videre::value_flow::types::{Asset, AssetAmount}; struct EchoVenue; #[videre_sdk::venue] -impl EchoVenue { +impl VenueAdapter for EchoVenue { fn init(_config: Config) -> Result<(), Fault> { Ok(()) } @@ -105,11 +108,9 @@ fn minimal_be(value: u64) -> Vec { } /// echo-venue as the `nexum-venue-test` conformance target: the adapter's -/// pure header derivation is held to a hand-written golden through the kit's -/// serde mirror types. The macro mints echo-venue's own bindgen -/// `IntentHeader`, so the check bridges it to [`GoldenHeader`] field for -/// field - the pattern the kit documents for macro-built adapters - rather -/// than reusing the SDK's `From`. +/// pure header derivation is held to a hand-written golden. The macro +/// remaps the type interfaces onto the SDK bindings, so the derivation +/// feeds the kit directly through its `From` mirror. #[cfg(test)] mod conformance { use super::*; @@ -118,43 +119,6 @@ mod conformance { GoldenSettlement, HeaderGolden, HeaderGoldens, }; - fn asset_to_golden(asset: Asset) -> GoldenAsset { - match asset { - Asset::Native => GoldenAsset::Native, - Asset::Erc20(erc20) => GoldenAsset::Erc20 { token: erc20.token }, - } - } - - fn amount_to_golden(amount: AssetAmount) -> GoldenAssetAmount { - GoldenAssetAmount { - asset: asset_to_golden(amount.asset), - amount: amount.amount, - } - } - - fn auth_to_golden(scheme: AuthScheme) -> GoldenAuthScheme { - match scheme { - AuthScheme::Eip1271 => GoldenAuthScheme::Eip1271, - AuthScheme::Eip712 => GoldenAuthScheme::Eip712, - } - } - - fn header_to_golden(header: IntentHeader) -> GoldenHeader { - GoldenHeader { - gives: amount_to_golden(header.gives), - wants: amount_to_golden(header.wants), - settlement: GoldenSettlement { - chain: header.settlement.chain, - }, - authorisation: auth_to_golden(header.authorisation), - } - } - - /// The adapter derivation the kit checks, bridged to the golden mirror. - fn derive_golden(body: Vec) -> Result { - EchoVenue::derive_header(body).map(header_to_golden) - } - fn zero_native() -> GoldenAssetAmount { GoldenAssetAmount { asset: GoldenAsset::Native, @@ -187,7 +151,7 @@ mod conformance { venue: "echo-venue".to_owned(), goldens: vec![golden], }; - goldens.assert_conforms(derive_golden); + goldens.assert_conforms(EchoVenue::derive_header); } #[test] @@ -212,6 +176,6 @@ mod conformance { notes: None, }], }; - assert!(goldens.check(derive_golden).is_err()); + assert!(goldens.check(EchoVenue::derive_header).is_err()); } } diff --git a/modules/fixtures/flaky-venue/Cargo.toml b/modules/fixtures/flaky-venue/Cargo.toml index bcbd4c95..4987897e 100644 --- a/modules/fixtures/flaky-venue/Cargo.toml +++ b/modules/fixtures/flaky-venue/Cargo.toml @@ -14,4 +14,4 @@ crate-type = ["cdylib"] [dependencies] videre-sdk = { path = "../../../crates/videre-sdk" } -wit-bindgen = { version = "0.58", default-features = false, features = ["macros", "realloc"] } +wit-bindgen = { version = "0.59", default-features = false, features = ["macros", "realloc"] } diff --git a/modules/fixtures/flaky-venue/src/lib.rs b/modules/fixtures/flaky-venue/src/lib.rs index 0970a63c..10716274 100644 --- a/modules/fixtures/flaky-venue/src/lib.rs +++ b/modules/fixtures/flaky-venue/src/lib.rs @@ -14,11 +14,14 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![allow(clippy::too_many_arguments)] +// `Config` and `Fault` come from the macro's world bindgen at the crate +// root: aliases of the SDK types, so the trait impl lines up. use nexum::host::chain; -use videre::types::types::{ - AuthScheme, IntentHeader, IntentStatus, Quotation, Settlement, SubmitOutcome, VenueError, +use videre_sdk::value_flow::{Asset, AssetAmount}; +use videre_sdk::{ + AuthScheme, IntentHeader, IntentStatus, Quotation, Settlement, SubmitOutcome, VenueAdapter, + VenueError, }; -use videre::value_flow::types::{Asset, AssetAmount}; /// The chain-head response that detonates `submit`. const POISON_HEAD: &str = "0xdead"; @@ -26,7 +29,7 @@ const POISON_HEAD: &str = "0xdead"; struct FlakyVenue; #[videre_sdk::venue] -impl FlakyVenue { +impl VenueAdapter for FlakyVenue { fn init(_config: Config) -> Result<(), Fault> { Ok(()) }