Skip to main content

ruststream/
lib.rs

1//! Rust core of the [`RustStream`](https://github.com/powersemmi/ruststream) messaging
2//! framework: broker-agnostic traits, message types, codecs, router runtime, and a
3//! conformance harness for broker authors.
4//!
5//! # Cargo features
6//!
7//! The core traits, the [`runtime::RustStream`] application object, middleware, and dispatch are
8//! always present. The rest is additive and opt-in. Codec features are mutually compatible and
9//! enable only the deserializers you need.
10//!
11//! * `json` (default): [`codec::JsonCodec`].
12//! * `msgpack`: [`codec::MsgpackCodec`].
13//! * `cbor`: [`codec::CborCodec`].
14//! * `memory`: [`memory::MemoryBroker`], an in-process broker usable in applications, prototypes
15//!   and tests.
16//! * `macros`: the `#[subscriber]`, [`#[ruststream::app]`](macro@app), and
17//!   [`#[derive(Message)]`](macro@Message) macros.
18//! * `asyncapi`: `AsyncAPI` document generation and the HTML viewer.
19//! * `metrics`: Prometheus metrics middleware and exporter.
20//! * `logging`: colored, `RUST_LOG`-driven console logging via `tracing-subscriber`
21//!   ([`logging::init`]). The generated `cli` `run` command installs it automatically.
22//! * `conformance`: the [`conformance::harness`] contract suite, per-capability suites in
23//!   [`conformance::capabilities`], and broker-agnostic [`conformance::helpers`] for application
24//!   tests. Generic over any broker's [`testing::TestableBroker`], so it pulls in no concrete broker
25//!   (enable `memory` too to run it against [`memory::MemoryBroker`]).
26//! * `testing`: the [`testing::TestApp`] in-process harness for application unit tests.
27//! * `cli`: the `ruststream` binary (`run`, `asyncapi gen`, `new`).
28//!
29//! Disable defaults (`default-features = false`) to drop the bundled JSON codec; the core traits,
30//! runtime, and dispatch remain. Add back only what you need.
31
32#![forbid(unsafe_code)]
33
34mod broker;
35mod buffered;
36mod capability;
37mod error;
38mod field;
39mod headers;
40mod message;
41mod publisher;
42mod schema;
43mod subscriber;
44mod subscription;
45pub mod testing;
46
47/// Re-exported for the [`register_testable_broker!`] macro's expansion; not a stable API.
48#[cfg(feature = "testing")]
49#[doc(hidden)]
50pub use inventory;
51
52pub use broker::Broker;
53pub use buffered::{Buffered, BufferedSubscriber};
54pub use capability::{
55    BatchSubscriber, DescribeServer, Partitioned, RequestReply, ServerSpec, Subscribe,
56    TransactionalPublisher,
57};
58pub use error::AckError;
59pub use field::{BuildContext, Field, FieldMut};
60pub use headers::Headers;
61pub use message::{IncomingMessage, OutgoingMessage, RawMessage};
62pub use publisher::Publisher;
63pub use schema::Message;
64pub use subscriber::Subscriber;
65pub use subscription::{Name, SubscriptionSource};
66
67pub mod codec;
68
69#[cfg(feature = "memory")]
70pub mod memory;
71
72pub mod runtime;
73
74pub use runtime::RustStream;
75
76/// Attribute macro that turns an `async fn` into a mountable subscriber definition.
77///
78/// Available with the `macros` feature. See [`ruststream_macros::subscriber`].
79#[cfg(feature = "macros")]
80pub use ruststream_macros::subscriber;
81
82/// Attribute macro that generates a `main` entry point from a `RustStream` builder function.
83///
84/// Available with the `macros` feature. See [`ruststream_macros::app`] and
85/// [`runtime::cli`].
86#[cfg(feature = "macros")]
87pub use ruststream_macros::app;
88
89/// Derive macro for [`Message`] metadata (type name + doc description).
90///
91/// Available with the `macros` feature.
92#[cfg(feature = "macros")]
93pub use ruststream_macros::Message;
94
95/// Derive macro that implements [`FromRef`](runtime::FromRef) for each field of an application
96/// state, so handlers can inject any field with [`State<T>`](runtime::State).
97///
98/// Available with the `macros` feature.
99#[cfg(feature = "macros")]
100pub use ruststream_macros::FromRef;
101
102#[cfg(feature = "conformance")]
103pub mod conformance;
104
105#[cfg(feature = "asyncapi")]
106pub mod asyncapi;
107
108/// Re-export of [`schemars`] so message types can derive `JsonSchema` without a direct dependency.
109///
110/// Derive it on a message type (`#[derive(ruststream::schemars::JsonSchema)]`) and its payload
111/// schema is emitted into the generated [`AsyncAPI`](asyncapi) document. Available with the
112/// `asyncapi` feature.
113#[cfg(feature = "asyncapi")]
114pub use schemars;
115
116#[cfg(feature = "metrics")]
117pub mod metrics;
118
119#[cfg(feature = "logging")]
120pub mod logging;
121
122#[cfg(feature = "opentelemetry")]
123pub mod opentelemetry;
124
125/// Implementation detail used by the `#[subscriber]` macro to capture a payload's JSON Schema.
126///
127/// Not part of the public API; no stability guarantees.
128#[doc(hidden)]
129pub mod __private {
130    use core::marker::PhantomData;
131
132    /// A type-carrying probe the macro reads a payload schema off.
133    ///
134    /// Schema selection uses inherent-vs-trait specialization (a stable-Rust trick): the schema
135    /// path is an inherent method on `Probe<T>` bounded by `T: JsonSchema`, and
136    /// [`NoSchemaProbe::schema_json`] is the trait fallback. Inherent methods win when present, so
137    /// `Probe::<T>::new().schema_json()` returns the schema for a concrete `T: JsonSchema` and
138    /// `None` otherwise - without forcing the bound onto every message type. The inherent method
139    /// exists only with the `asyncapi` feature.
140    #[derive(Debug)]
141    pub struct Probe<T>(pub PhantomData<T>);
142
143    impl<T> Probe<T> {
144        /// Constructs a probe for `T`.
145        #[must_use]
146        pub const fn new() -> Self {
147            Self(PhantomData)
148        }
149    }
150
151    impl<T> Default for Probe<T> {
152        fn default() -> Self {
153            Self::new()
154        }
155    }
156
157    /// The trait fallback: chosen for any `T` the inherent schema method does not cover.
158    pub trait NoSchemaProbe {
159        /// Returns `None` (no schema available for the probed type).
160        fn schema_json(&self) -> Option<String>;
161    }
162
163    impl<T> NoSchemaProbe for Probe<T> {
164        fn schema_json(&self) -> Option<String> {
165            None
166        }
167    }
168
169    #[cfg(feature = "asyncapi")]
170    impl<T: schemars::JsonSchema> Probe<T> {
171        /// Returns the serialized JSON Schema for `T` (inherent; preferred over the trait fallback).
172        #[must_use]
173        pub fn schema_json(&self) -> Option<String> {
174            serde_json::to_string(&schemars::schema_for!(T)).ok()
175        }
176    }
177
178    /// The trait fallback for [`Message`](crate::Message) metadata: chosen for any `T` the
179    /// inherent methods below do not cover.
180    pub trait NoMessageProbe {
181        /// Returns `None` (the probed type does not implement `Message`).
182        fn message_name(&self) -> Option<&'static str>;
183        /// Returns `None` (the probed type does not implement `Message`).
184        fn message_description(&self) -> Option<&'static str>;
185    }
186
187    impl<T> NoMessageProbe for Probe<T> {
188        fn message_name(&self) -> Option<&'static str> {
189            None
190        }
191
192        fn message_description(&self) -> Option<&'static str> {
193            None
194        }
195    }
196
197    impl<T: crate::Message> Probe<T> {
198        /// Returns [`Message::NAME`](crate::Message::NAME) for `T` (inherent; preferred over the
199        /// trait fallback).
200        #[must_use]
201        pub fn message_name(&self) -> Option<&'static str> {
202            Some(T::NAME)
203        }
204
205        /// Returns [`Message::DESCRIPTION`](crate::Message::DESCRIPTION) for `T` (inherent;
206        /// preferred over the trait fallback).
207        #[must_use]
208        pub fn message_description(&self) -> Option<&'static str> {
209            T::DESCRIPTION
210        }
211    }
212}