Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/cow-venue/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
96 changes: 55 additions & 41 deletions crates/cow-venue/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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::<CowIntentBody>();
}

#[test]
Expand All @@ -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::<CowIntentBody>().is_err());
}
}
8 changes: 3 additions & 5 deletions crates/nexum-venue-test/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions crates/nexum-venue-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TheirHeader> 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)]
Expand Down
33 changes: 33 additions & 0 deletions crates/nexum-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,21 @@ pub fn manifest_capabilities(text: &str) -> Result<Vec<String>, 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<Option<String>, 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.<name>]` table carries the WIT `import` the declaration
/// turns into and the extra `packages` its resolve path needs. A file
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion crates/videre-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading