From 285528d2e56f88eec49dc0034baf140d5f19abb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schn=C3=B6rch?= Date: Sat, 24 Oct 2026 21:06:00 +0200 Subject: [PATCH] feat(core): defmt::Format for every type the log macros format Groundwork for forwarding `log_*!` to defmt (#352): under core's `defmt` feature, every argument type core's log lines format now implements `defmt::Format`. - `TransportError`, `CodecError`, `AuthError`, `FrameFault`: derived. - `DbError`, `SerializeFailure`: through their `Display` impl (`defmt::Display2Format`). `DbError`'s `std`-only variants carry `io::Error` / `serde_json::Error`, which do not implement `Format`, and docs.rs builds with every feature on, so a derive would break that build. - core's `defmt` feature enables `defmt/alloc`, for `String`, `Vec` and `Arc`. - A compile-time assertion keeps all of them `Format` under `defmt`. Found by building every defmt-capable crate for thumbv7em with the macro branch in place. No behaviour change on its own. Part of #352. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YPDjQ947yKjLLAq3Xzgu3e --- aimdb-core/Cargo.toml | 2 +- aimdb-core/src/error.rs | 10 ++++++++++ aimdb-core/src/lib.rs | 16 ++++++++++++++++ aimdb-core/src/outbound/routes.rs | 9 +++++++++ aimdb-core/src/session/io.rs | 1 + aimdb-core/src/session/mod.rs | 3 +++ 6 files changed, 40 insertions(+), 1 deletion(-) diff --git a/aimdb-core/Cargo.toml b/aimdb-core/Cargo.toml index 6b2da2c6..c664f375 100644 --- a/aimdb-core/Cargo.toml +++ b/aimdb-core/Cargo.toml @@ -52,7 +52,7 @@ connector-session = ["alloc"] # Observability features (available on both std/no_std) tracing = ["dep:tracing"] # Works in both std and no_std environments -defmt = ["dep:defmt"] # Embedded logging via probe (no_std) +defmt = ["dep:defmt", "defmt/alloc"] # Embedded logging via probe (no_std) # A second facade destination, for hosts that cannot be handed a global # `tracing` subscriber (design 050). Not in `default`: 66 call sites on an MCU # should not each grow a level check because an FFI layer wanted one. diff --git a/aimdb-core/src/error.rs b/aimdb-core/src/error.rs index c6d23747..6d2bdf9b 100644 --- a/aimdb-core/src/error.rs +++ b/aimdb-core/src/error.rs @@ -223,6 +223,16 @@ pub enum DbError { }, } +// Through `Display` (`thiserror`), not a derive: the `std`-only variants carry +// `io::Error` / `serde_json::Error`, which do not implement `defmt::Format`, +// and docs.rs builds with every feature on, `std` and `defmt` together. +#[cfg(feature = "defmt")] +impl defmt::Format for DbError { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{}", defmt::Display2Format(self)) + } +} + // ===== DbError Implementation ===== impl DbError { /// Builds a [`RuntimeError`](DbError::RuntimeError). diff --git a/aimdb-core/src/lib.rs b/aimdb-core/src/lib.rs index acab7040..b770c5fe 100644 --- a/aimdb-core/src/lib.rs +++ b/aimdb-core/src/lib.rs @@ -75,6 +75,22 @@ pub mod __private { pub use ::tracing; } +// Every type the `log_*!` macros format must implement `defmt::Format` when +// `defmt` is on (#352); this fails the build if one stops doing so. +#[cfg(all(feature = "defmt", feature = "connector-session"))] +const _: () = { + fn format() {} + let _ = format::; + let _ = format::; + let _ = format::; + let _ = format::; + let _ = format::; + let _ = format::; + let _ = format::; + let _ = format::>; + let _ = format::>; +}; + pub mod buffer; pub mod builder; #[cfg(feature = "remote")] diff --git a/aimdb-core/src/outbound/routes.rs b/aimdb-core/src/outbound/routes.rs index 2d2644e3..d598cd35 100644 --- a/aimdb-core/src/outbound/routes.rs +++ b/aimdb-core/src/outbound/routes.rs @@ -126,6 +126,15 @@ pub(crate) enum SerializeFailure { InvalidLength { len: usize, capacity: usize }, } +// Through `Display`: the variants carry a `SerializeError` and lengths, and +// one rendering keeps the defmt line identical to the tracing/log one. +#[cfg(feature = "defmt")] +impl defmt::Format for SerializeFailure { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{}", defmt::Display2Format(self)) + } +} + impl core::fmt::Display for SerializeFailure { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { diff --git a/aimdb-core/src/session/io.rs b/aimdb-core/src/session/io.rs index c7344c80..0fd5b4f1 100644 --- a/aimdb-core/src/session/io.rs +++ b/aimdb-core/src/session/io.rs @@ -196,6 +196,7 @@ pub trait Delay { /// none, so nothing tells payload bytes from the next header. Only the framer /// knows which case it is in, so it says, rather than the connection guessing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum FrameFault { /// Bad frame, good link: skip it and keep reading. Recoverable, diff --git a/aimdb-core/src/session/mod.rs b/aimdb-core/src/session/mod.rs index 79a8caac..eaa25c0a 100644 --- a/aimdb-core/src/session/mod.rs +++ b/aimdb-core/src/session/mod.rs @@ -224,6 +224,7 @@ impl Default for SessionLimits { /// Transport-layer failure (Layer 1). #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum TransportError { /// The connection was closed or reset by the peer. @@ -239,6 +240,7 @@ pub enum TransportError { /// Envelope-codec failure — a frame could not be decoded/encoded. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum CodecError { /// The frame was not valid for this envelope format. @@ -262,6 +264,7 @@ pub enum RpcError { /// Authentication failure raised by [`Dispatch::authenticate`]. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum AuthError { /// Credentials were missing or rejected.