Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

14 changes: 4 additions & 10 deletions crates/nexum-runtime/src/digest.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Content digests for loaded component artifacts.

use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;

Expand All @@ -9,8 +8,10 @@ use thiserror::Error;

const SCHEME: &str = "sha256";

/// sha256 digest of an artifact's bytes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
/// sha256 digest of an artifact's bytes; `Display` is the canonical
/// lowercase `sha256:<hex>` the manifest grammar parses back.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{SCHEME}:{}", alloy_primitives::hex::encode(_0))]
pub struct ContentDigest([u8; 32]);

impl ContentDigest {
Expand Down Expand Up @@ -56,13 +57,6 @@ impl FromStr for ContentDigest {
}
}

impl fmt::Display for ContentDigest {
/// Canonical lowercase `sha256:<hex>`.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{SCHEME}:{}", alloy_primitives::hex::encode(self.0))
}
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DigestParseError {
Expand Down
16 changes: 3 additions & 13 deletions crates/nexum-runtime/src/test_utils/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
#[derive(Debug, Clone, derive_more::From)]
pub enum ManifestSource {
/// No explicit path; the loader falls back to discovery beside the component.
Beside,
/// A path handed to the loader as-is, existing or not.
#[from]
Path(PathBuf),
/// Manifest text written out at boot.
#[from]
Toml(String),
}

Expand All @@ -32,18 +34,6 @@ impl From<TestManifest> for ManifestSource {
}
}

impl From<String> for ManifestSource {
fn from(toml: String) -> Self {
Self::Toml(toml)
}
}

impl From<PathBuf> for ManifestSource {
fn from(path: PathBuf) -> Self {
Self::Path(path)
}
}

/// Builder for positive-path manifest TOML.
#[derive(Debug, Clone)]
pub struct TestManifest {
Expand Down
1 change: 1 addition & 0 deletions crates/nexum-sdk-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ description = "In-memory host mocks for nexum module unit tests. Implements nexu

[dependencies]
nexum-sdk = { path = "../nexum-sdk" }
derive_more.workspace = true
tracing.workspace = true
13 changes: 1 addition & 12 deletions crates/nexum-sdk-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ pub struct CapturedEvent {
}

/// A field value as tracing's `Visit` delivered it.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, derive_more::Display)]
pub enum FieldValue {
/// A `record_str` value.
Str(String),
Expand All @@ -898,17 +898,6 @@ pub enum FieldValue {
Debug(String),
}

impl fmt::Display for FieldValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FieldValue::Str(v) | FieldValue::Debug(v) => f.write_str(v),
FieldValue::U64(v) => write!(f, "{v}"),
FieldValue::I64(v) => write!(f, "{v}"),
FieldValue::Bool(v) => write!(f, "{v}"),
}
}
}

impl CapturedEvent {
/// The value recorded for `name`, if the event carried it.
pub fn field(&self, name: &str) -> Option<&FieldValue> {
Expand Down
18 changes: 16 additions & 2 deletions crates/nexum-sdk/src/wit_bindgen_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
//! zero-argument form emits the full six-interface set. Either way the
//! wit-bindgen output for the world must already be in scope, so
//! selecting a capability the world does not import is a compile error.
//! A domain SDK layers its own interfaces on the same `WitBindgenHost`.
//! A domain SDK layers its own interfaces on the same `WitBindgenHost`,
//! or binds logging alone via [`crate::bind_host_logging_via_wit_bindgen!`].
//!
//! ```ignore
//! wit_bindgen::generate!({ /* ... */ });
Expand Down Expand Up @@ -326,12 +327,25 @@ macro_rules! __bind_host_cap_via_wit_bindgen {
}
};
(logging) => {
$crate::bind_host_logging_via_wit_bindgen!();

impl $crate::host::LoggingHost for WitBindgenHost {
fn log(&self, level: $crate::Level, message: &str) {
nexum::host::logging::log(nexum::host::logging::Level::from(level), message);
}
}
};
}

/// Logging-only slice of [`bind_host_via_wit_bindgen!`]: needs only the
/// generated `nexum::host::logging` in scope, never `nexum::host::types`
/// or `WitBindgenHost`.
///
/// The generated names `HostLogSink` and `install_tracing` are visible
/// in the caller's scope (`macro_rules!` is not hygienic for items).
#[macro_export]
macro_rules! bind_host_logging_via_wit_bindgen {
() => {
/// Translate a `tracing_core::Level` into the wit-bindgen
/// `logging::Level` wire enum.
impl ::core::convert::From<$crate::Level> for nexum::host::logging::Level {
Expand All @@ -355,7 +369,7 @@ macro_rules! __bind_host_cap_via_wit_bindgen {

impl $crate::tracing::LogSink for HostLogSink {
fn log(&self, level: $crate::Level, message: &str) {
<WitBindgenHost as $crate::host::LoggingHost>::log(&WitBindgenHost, level, message);
nexum::host::logging::log(::core::convert::From::from(level), message);
}
}

Expand Down
69 changes: 69 additions & 0 deletions crates/nexum-sdk/tests/wit_bindgen_logging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! A domain-SDK-shaped consumer binds `bind_host_logging_via_wit_bindgen!`
//! against its own generated `nexum::host::logging`, with no base block
//! (no `WitBindgenHost`, no `nexum:host/types`) in scope.

mod nexum {
pub mod host {
/// Stands in for the per-cdylib wit-bindgen `logging` output.
pub mod logging {
use std::sync::Mutex;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Level {
Trace,
Debug,
Info,
Warn,
Error,
}

pub static RECORDED: Mutex<Vec<(Level, String)>> = Mutex::new(Vec::new());

pub fn log(level: Level, message: &str) {
RECORDED.lock().unwrap().push((level, message.to_owned()));
}
}
}
}

nexum_sdk::bind_host_logging_via_wit_bindgen!();

use nexum::host::logging::Level as Wire;

/// The recorder is process-wide, so every assertion is a containment
/// check rather than an equality on the whole log.
fn recorded(line: &str) -> Option<Wire> {
let recorded = nexum::host::logging::RECORDED.lock().unwrap();
recorded
.iter()
.find(|(_, message)| message == line)
.map(|(level, _)| *level)
}

#[test]
fn sink_forwards_to_the_bound_logging_call() {
use nexum_sdk::tracing::LogSink as _;

HostLogSink.log(nexum_sdk::Level::INFO, "ready");
assert_eq!(recorded("ready"), Some(Wire::Info));
}

#[test]
fn level_mapping_covers_the_wire_enum() {
for (level, wire) in [
(nexum_sdk::Level::ERROR, Wire::Error),
(nexum_sdk::Level::WARN, Wire::Warn),
(nexum_sdk::Level::INFO, Wire::Info),
(nexum_sdk::Level::DEBUG, Wire::Debug),
(nexum_sdk::Level::TRACE, Wire::Trace),
] {
assert_eq!(Wire::from(level), wire);
}
}

#[test]
fn facade_install_routes_events_to_the_bound_logging_call() {
install_tracing();
tracing::warn!("through the facade");
assert_eq!(recorded("through the facade"), Some(Wire::Warn));
}
Loading