Skip to main content

reliar_core/
lib.rs

1//! `reliar-core` is the pure envelope/message model every Reliar crate builds on: identity
2//! newtypes, the [`Message`] contract, a validated [`Headers`] map, typed [`Metadata`], and the
3//! [`Envelope`]/[`SerializedEnvelope`] pair that carries them. It has no storage or transport
4//! dependency of its own — no sqlx, no broker client, no routing concept (a Kafka partition key,
5//! a `RabbitMQ` exchange, a NATS subject) — so it is useful standalone, as the shared vocabulary
6//! two independent Reliar crates (say an outbox and a transport) use to talk to each other, or as
7//! a typed envelope model for an application that does not use the rest of Reliar at all.
8//!
9//! # The types you touch
10//!
11//! - [`Message`] — implement this on your own type to give it a stable, renaming-proof identity
12//!   (`TYPE` + `VERSION`), never derived from `std::any::type_name::<T>()` or a module path.
13//! - [`Envelope<T>`] / [`SerializedEnvelope`] — the same generic type on both sides of
14//!   serialization: an `Envelope<YourType>` on the way in, `Envelope<bytes::Bytes>` once a
15//!   [`Serializer`] has turned the body to bytes. Build one with [`Envelope::builder`].
16//! - [`Metadata`] — canonical, typed framework metadata (correlation, trace, routing, delivery,
17//!   tenant). One source of truth: a value here is never duplicated into [`Headers`], and Reliar
18//!   never reads a framework value back out of headers.
19//! - [`Headers`] — your own custom, application-defined metadata. A validating newtype, not a
20//!   bare map: it rejects the entire `reliar-` prefix case-insensitively, so a custom header can
21//!   never collide with — or be mistaken for — a framework one.
22//! - [`Serializer`] (default impl: [`JsonSerializer`], behind the default `json` feature) —
23//!   converts a typed body to and from bytes.
24//! - [`Publisher`] — the trait a transport implements to send an envelope; paired with
25//!   [`Classify`]/[`FailureKind`] so a caller can tell a retryable failure from a permanent one
26//!   without inspecting transport internals.
27//! - [`uuid_id!`]/[`uuid_id_serde!`] — declare a UUID-backed identity newtype (`from_uuid`/
28//!   `as_uuid`, optional minting/`Default`/`serde`) in any crate; paired with the re-exported
29//!   [`mod@uuid`] so the generated signatures name one `Uuid` type everywhere.
30//!
31//! # End to end
32//!
33//! `JsonSerializer` ships behind the default `json` feature; without it this block still shows
34//! the shape but is not compiled (`cargo test --doc --no-default-features` would not see
35//! `JsonSerializer`/`JsonError`).
36#![cfg_attr(not(feature = "json"), doc = "```ignore")]
37#![cfg_attr(feature = "json", doc = "```")]
38//! use reliar_core::{Envelope, JsonSerializer, Message, Serializer};
39//!
40//! #[derive(serde::Serialize, serde::Deserialize)]
41//! struct OrderCreated {
42//!     order_id: u64,
43//! }
44//!
45//! impl Message for OrderCreated {
46//!     const TYPE: &'static str = "orders.created";
47//!     const VERSION: u16 = 1;
48//! }
49//!
50//! // Build a typed envelope; `message_type` and the conversation root are derived, not chosen.
51//! let envelope = Envelope::builder(OrderCreated { order_id: 42 })
52//!     .tenant("acme")
53//!     .build();
54//! assert_eq!(envelope.message_type.to_string(), "orders.created.v1");
55//!
56//! // Serialize the body; the serialized envelope is what storage/transport crates see.
57//! let serializer = JsonSerializer;
58//! let bytes = serializer.serialize(&envelope.body)?;
59//! let serialized = envelope.map_body(|_| bytes);
60//! assert_eq!(serialized.metadata.tenant_id.as_deref(), Some("acme"));
61//! # Ok::<(), reliar_core::JsonError>(())
62//! ```
63//!
64//! Every public error is a hand-rolled, `#[non_exhaustive]` enum with a wired
65//! [`std::error::Error::source`] — no `thiserror`, no `anyhow`. `Debug` on payload-bearing types
66//! elides the bytes; no `Display` here ever prints a payload, a header value, or a credential.
67
68#![cfg_attr(docsrs, feature(doc_cfg))]
69#![forbid(unsafe_code)]
70#![warn(missing_docs)]
71
72mod content_type;
73mod envelope;
74mod failure;
75mod headers;
76mod ids;
77mod mapper;
78mod message;
79mod metadata;
80mod publisher;
81mod serializer;
82mod settings;
83
84pub use content_type::{ContentType, ContentTypeError};
85pub use envelope::{Envelope, EnvelopeBuilder, SerializedEnvelope};
86pub use failure::{Classify, FailureKind};
87pub use headers::{HeaderError, Headers};
88pub use ids::{ConversationId, CorrelationId, IdError, MessageId, RequestId};
89pub use mapper::EnvelopeMapper;
90pub use message::{Message, MessageType};
91pub use metadata::{
92    CorrelationMetadata, DeliveryMetadata, EndpointAddress, Metadata, RoutingMetadata, TraceContext,
93};
94pub use publisher::Publisher;
95pub use serializer::Serializer;
96pub use settings::SettingsError;
97/// Re-exported so a [`uuid_id!`] invocation and its generated `from_uuid`/`as_uuid` signatures
98/// name **this** `Uuid`, whichever crate declares the id (ADR 0045) — a caller needs no `uuid`
99/// dependency of its own to interoperate with an id `reliar-core` declares.
100pub use uuid;
101
102#[cfg(feature = "json")]
103#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
104pub use serializer::{JsonError, JsonSerializer};
105
106// The crate README's only fenced Rust block is the `JsonSerializer` quickstart; gate the whole
107// module on `json` rather than editing static markdown to carry a per-block cfg_attr.
108#[cfg(all(doctest, feature = "json"))]
109mod readme_doctests;