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//!
28//! # End to end
29//!
30//! `JsonSerializer` ships behind the default `json` feature; without it this block still shows
31//! the shape but is not compiled (`cargo test --doc --no-default-features` would not see
32//! `JsonSerializer`/`JsonError`).
33#![cfg_attr(not(feature = "json"), doc = "```ignore")]
34#![cfg_attr(feature = "json", doc = "```")]
35//! use reliar_core::{Envelope, JsonSerializer, Message, Serializer};
36//!
37//! #[derive(serde::Serialize, serde::Deserialize)]
38//! struct OrderCreated {
39//! order_id: u64,
40//! }
41//!
42//! impl Message for OrderCreated {
43//! const TYPE: &'static str = "orders.created";
44//! const VERSION: u16 = 1;
45//! }
46//!
47//! // Build a typed envelope; `message_type` and the conversation root are derived, not chosen.
48//! let envelope = Envelope::builder(OrderCreated { order_id: 42 })
49//! .tenant("acme")
50//! .build();
51//! assert_eq!(envelope.message_type.to_string(), "orders.created.v1");
52//!
53//! // Serialize the body; the serialized envelope is what storage/transport crates see.
54//! let serializer = JsonSerializer;
55//! let bytes = serializer.serialize(&envelope.body)?;
56//! let serialized = envelope.map_body(|_| bytes);
57//! assert_eq!(serialized.metadata.tenant_id.as_deref(), Some("acme"));
58//! # Ok::<(), reliar_core::JsonError>(())
59//! ```
60//!
61//! Every public error is a hand-rolled, `#[non_exhaustive]` enum with a wired
62//! [`std::error::Error::source`] — no `thiserror`, no `anyhow`. `Debug` on payload-bearing types
63//! elides the bytes; no `Display` here ever prints a payload, a header value, or a credential.
64
65#![cfg_attr(docsrs, feature(doc_cfg))]
66#![forbid(unsafe_code)]
67#![warn(missing_docs)]
68
69mod content_type;
70mod envelope;
71mod failure;
72mod headers;
73mod ids;
74mod mapper;
75mod message;
76mod metadata;
77mod publisher;
78mod serializer;
79mod settings;
80
81pub use content_type::{ContentType, ContentTypeError};
82pub use envelope::{Envelope, EnvelopeBuilder, SerializedEnvelope};
83pub use failure::{Classify, FailureKind};
84pub use headers::{HeaderError, Headers};
85pub use ids::{ConversationId, CorrelationId, IdError, MessageId, RequestId};
86pub use mapper::EnvelopeMapper;
87pub use message::{Message, MessageType};
88pub use metadata::{
89 CorrelationMetadata, DeliveryMetadata, EndpointAddress, Metadata, RoutingMetadata, TraceContext,
90};
91pub use publisher::Publisher;
92pub use serializer::Serializer;
93pub use settings::SettingsError;
94
95#[cfg(feature = "json")]
96#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
97pub use serializer::{JsonError, JsonSerializer};
98
99// The crate README's only fenced Rust block is the `JsonSerializer` quickstart; gate the whole
100// module on `json` rather than editing static markdown to carry a per-block cfg_attr.
101#[cfg(all(doctest, feature = "json"))]
102mod readme_doctests;