Skip to main content

magnetar/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Apache Pulsar client driver for Rust.
4//!
5//! Public façade for the magnetar workspace. Re-exports the sans-io core
6//! ([`magnetar_proto`]) plus the selected runtime engine, and provides an
7//! ergonomic [`PulsarClient`] entry point that wires the protocol layer to
8//! the tokio engine by default.
9//!
10//! ```no_run
11//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
12//! use magnetar::{OutgoingMessage, PulsarClient};
13//!
14//! let client = PulsarClient::builder()
15//!     .service_url("pulsar://localhost:6650")
16//!     .build()
17//!     .await?;
18//!
19//! let producer = client.producer("persistent://public/default/orders").create().await?;
20//! producer
21//!     .send(OutgoingMessage::with_payload(b"hello".as_slice()).into())
22//!     .await?;
23//!
24//! let consumer = client
25//!     .consumer("persistent://public/default/orders")
26//!     .subscription("worker")
27//!     .subscribe()
28//!     .await?;
29//! let msg = consumer.receive().await?;
30//! consumer.ack(msg.message_id).await?;
31//! # Ok(()) }
32//! ```
33//!
34//! ## Feature flags
35//!
36//! - `tokio` (default): pull in the tokio engine.
37//! - `moonpool`: pull in the moonpool engine.
38//! - `admin`: re-export [`magnetar_admin`] under [`admin`] for the REST admin client.
39//! - `auth-oauth2`, `auth-sasl`, `auth-athenz`: pluggable auth providers.
40//! - `encryption`: PIP-4 end-to-end encryption.
41//! - `opentelemetry` (default off): inject/extract W3C `traceparent`/`tracestate` into message
42//!   properties (ADR-0053).
43
44#![warn(unreachable_pub)]
45#![forbid(unsafe_code)]
46
47#[cfg(feature = "admin")]
48pub use magnetar_admin as admin;
49pub use magnetar_proto as proto;
50pub use magnetar_proto::conn::{ConnectionConfig, OpOutcome};
51// Re-export the most commonly used protocol types at the top level so users
52// don't have to remember which crate they live in.
53pub use magnetar_proto::{
54    AuthProvider, Backoff, ConnectionEvent, ConsumerHandle, MessageId, OperationRetryConfig,
55    ProducerHandle, ProtocolError, RequestId, SequenceId, SupervisorConfig,
56};
57#[cfg(feature = "moonpool")]
58pub use magnetar_runtime_moonpool as runtime_moonpool;
59#[cfg(feature = "tokio")]
60pub use magnetar_runtime_tokio as runtime_tokio;
61
62mod engine;
63#[cfg(feature = "moonpool")]
64pub use engine::MoonpoolEngine;
65#[cfg(feature = "tokio")]
66pub use engine::TokioEngine;
67#[cfg(feature = "tokio")]
68pub use engine::{
69    BrokerMetadataApi, ConsumerApi, CreateProducerApi, OpenProducerFut, OperationDeadline,
70    ProducerApi, ReceiveBatchFut, ReceiveOptFut, SubscribeApi, SubscribeFut, TopicListChange,
71    WatchTopicListFut,
72};
73pub use engine::{Engine, MessageDecryptorApi, MessageEncryptorApi, NoEncryption, TransactionApi};
74
75#[cfg(feature = "tokio")]
76mod auto_update_task;
77#[cfg(feature = "tokio")]
78mod builders;
79#[cfg(feature = "tokio")]
80mod client;
81#[cfg(feature = "tokio")]
82mod client_builder;
83#[cfg(feature = "tokio")]
84mod consumer_listener;
85#[cfg(feature = "tokio")]
86mod consumer_template;
87#[cfg(feature = "moonpool")]
88mod moonpool_client;
89#[cfg(feature = "tokio")]
90mod multi_topics;
91#[cfg(feature = "tokio")]
92mod partitioned_consumer;
93#[cfg(feature = "tokio")]
94mod partitioned_producer;
95#[cfg(feature = "tokio")]
96mod pattern_consumer;
97#[cfg(feature = "tokio")]
98mod table_view;
99#[cfg(feature = "tokio")]
100mod transaction;
101#[cfg(feature = "tokio")]
102mod typed;
103#[cfg(feature = "tokio")]
104pub use builders::{ConsumerBuilder, ProducerBuilder, ReaderBuilder};
105#[cfg(feature = "tokio")]
106pub use client::{
107    ConsumerInterceptor, IncomingMessage, MemoryLimit, MemoryLimitPolicy, MessageBuilder,
108    OutgoingMessage, ProducerExt, ProducerInterceptor, PulsarClient, PulsarError, Reader,
109    SeekTarget, ack_cumulative_with_interceptors, ack_with_interceptors, receive_with_interceptors,
110    send_with_interceptors,
111};
112#[cfg(feature = "tokio")]
113pub use client_builder::ClientBuilder;
114#[cfg(feature = "tokio")]
115pub use consumer_listener::{
116    ConsumerEvent, ConsumerEventListener, ConsumerEventListenerHandle, MessageListener,
117    MessageListenerHandle, WrapperMessageListener, WrapperReceiver, spawn_consumer_event_listener,
118    spawn_message_listener, spawn_wrapper_message_listener,
119};
120#[cfg(feature = "tokio")]
121pub use multi_topics::{MultiTopicsConsumer, MultiTopicsConsumerBuilder, MultiTopicsMessage};
122#[cfg(feature = "tokio")]
123pub use partitioned_consumer::{PartitionedConsumer, PartitionedConsumerBuilder};
124#[cfg(feature = "tokio")]
125pub use partitioned_producer::{
126    JavaStringHashHasher, MessageRouter, MessageRoutingMode, Murmur3HashHasher,
127    PartitionedMessageBuilder, PartitionedProducer, PartitionedProducerBuilder, java_string_hash,
128    murmur3_32_hash,
129};
130#[cfg(feature = "tokio")]
131pub use pattern_consumer::{
132    PatternConsumer, PatternConsumerBuilder, PatternMessage, ReconcileReport,
133};
134#[cfg(feature = "tokio")]
135pub use table_view::{
136    TableView, TableViewBuilder, TableViewListener, TypedTableView, TypedTableViewBuilder,
137};
138#[cfg(feature = "tokio")]
139pub use transaction::{Transaction, TxnState};
140#[cfg(feature = "tokio")]
141pub use typed::{
142    TypedConsumer, TypedConsumerBuilder, TypedMessage, TypedMessageBuilder, TypedMessageListener,
143    TypedProducer, TypedProducerBuilder,
144};
145
146// PIP-4 encryption bridge: implement the runtime's MessageEncryptor / MessageDecryptor traits
147// for magnetar-messagecrypto::MessageCrypto. Behind the `encryption` feature so the heavy
148// `aws-lc-rs` dep is opt-in.
149#[cfg(all(feature = "tokio", feature = "encryption"))]
150mod crypto_bridge;
151#[cfg(all(feature = "tokio", feature = "encryption"))]
152pub use crypto_bridge::MessageCryptoBridge;
153
154/// OpenTelemetry context propagation for Pulsar messages. Behind
155/// `feature = "opentelemetry"` (default off). When enabled, the current span
156/// context is injected into outgoing message properties at every tokio send
157/// boundary (producer send, retry-letter `reconsume_later`, and DLQ republish).
158/// Use [`otel::extract_context`] on the consumer side to recover the parent
159/// context from a received message.
160#[cfg(feature = "opentelemetry")]
161pub mod otel;
162
163/// Inject the current OpenTelemetry span context (`traceparent` / `tracestate`)
164/// into `properties` at a send boundary. A no-op unless the `opentelemetry`
165/// feature is enabled (ADR-0053). Routing every send / retry / DLQ call site
166/// through this shim keeps them feature-agnostic — no per-site `#[cfg]`, and the
167/// bindings they mutate are never flagged `unused_mut` in a build without the
168/// feature.
169///
170/// Gated on `tokio` because every call site lives on the tokio engine surface;
171/// without `tokio` there are no callers, so the shim is simply absent.
172#[cfg(all(feature = "tokio", feature = "opentelemetry"))]
173#[inline]
174pub(crate) fn inject_otel_context(properties: &mut Vec<(String, String)>) {
175    otel::inject_context(properties);
176}
177
178/// No-op variant compiled when `tokio` is on but `opentelemetry` is off (ADR-0053).
179#[cfg(all(feature = "tokio", not(feature = "opentelemetry")))]
180#[inline]
181#[allow(clippy::ptr_arg)]
182pub(crate) fn inject_otel_context(properties: &mut Vec<(String, String)>) {
183    let _ = properties;
184}
185
186/// **Experimental** — PIP-466 V5 client surface (ADR-0032). Behind
187/// `feature = "experimental-v5-client"` (default off). The wire
188/// protocol is unchanged; V5 wraps the v4 surface with `Duration`-typed
189/// timeouts and a Stream/Queue consumer split.
190#[cfg(feature = "experimental-v5-client")]
191pub mod v5;
192
193/// **Experimental** — PIP-460 scalable-topic surface (ADR-0093). Behind
194/// `feature = "scalable-topics"` (default off). Exposes the
195/// [`scalable::ScalableTopicsApi`] engine hook and the
196/// [`scalable::StreamConsumer`] (StreamConsumer-only, drops on DAG change).
197/// No broker ships PIP-460 today; e2e is gated on a Pulsar 5.0 RC.
198#[cfg(all(feature = "tokio", feature = "scalable-topics"))]
199pub mod scalable;
200#[cfg(all(feature = "tokio", feature = "scalable-topics"))]
201pub use engine::{ScalableEvent, ScalableLookup, ScalableTopicsApi};
202
203#[cfg(test)]
204mod tests {
205    #[test]
206    fn proto_reexport_compiles() {
207        let _conn = crate::proto::Connection::new(
208            crate::proto::ConnectionConfig::default(),
209            std::sync::Arc::new(std::time::SystemTime::now),
210        );
211    }
212
213    #[cfg(feature = "tokio")]
214    #[test]
215    fn builder_compiles() {
216        let _ = crate::PulsarClient::builder().service_url("pulsar://localhost:6650");
217    }
218
219    /// Compile-time witness that the [`crate::TransactionApi`] trait is
220    /// object-safe-compatible (all methods return `Pin<Box<dyn Future + Send>>`)
221    /// AND that the engine's `ClientState` satisfies the bound — both
222    /// properties are load-bearing for the D1 façade lift; if either
223    /// regresses the generic `impl<E: Engine> PulsarClient<E> where
224    /// E::ClientState: TransactionApi` will fail to compile.
225    /// Runs at compile time only — no broker round-trip, no I/O.
226    fn assert_transaction_api_bound<T: crate::TransactionApi>() {}
227
228    #[cfg(feature = "tokio")]
229    #[test]
230    fn transaction_api_is_implemented_by_tokio_client() {
231        // Statically assert the bound; this entire function body is
232        // dead at runtime — the assertion fires at typeck.
233        assert_transaction_api_bound::<magnetar_runtime_tokio::Client>();
234    }
235
236    #[cfg(feature = "moonpool")]
237    #[test]
238    fn transaction_api_is_implemented_by_moonpool_client() {
239        // Mirror of the tokio bound check; asserts the moonpool side of
240        // the D1 lift train compiles. ADR-0026 §D1.
241        // The runtime `Client<P>` now serves as the engine's
242        // `ClientState` (Task #54).
243        assert_transaction_api_bound::<
244            magnetar_runtime_moonpool::Client<moonpool_core::TokioProviders>,
245        >();
246    }
247
248    /// Phase 1 of the Producer/Consumer foundational lift — see
249    /// ADR-0026 §D1. Each bound check fires at typeck; if either
250    /// runtime's `Producer` / `Consumer` regresses the bound, the
251    /// seven dependent façade lifts won't compile.
252    #[cfg(feature = "tokio")]
253    fn assert_producer_api_bound<T: crate::ProducerApi>() {}
254
255    #[cfg(feature = "tokio")]
256    fn assert_consumer_api_bound<T: crate::ConsumerApi>() {}
257
258    #[cfg(feature = "tokio")]
259    #[test]
260    fn producer_api_is_implemented_by_tokio_producer() {
261        assert_producer_api_bound::<magnetar_runtime_tokio::Producer>();
262    }
263
264    #[cfg(feature = "tokio")]
265    #[test]
266    fn consumer_api_is_implemented_by_tokio_consumer() {
267        assert_consumer_api_bound::<magnetar_runtime_tokio::Consumer>();
268    }
269
270    #[cfg(all(feature = "tokio", feature = "moonpool"))]
271    #[test]
272    fn producer_api_is_implemented_by_moonpool_producer() {
273        // Use `TokioProviders` to materialise the generic `Producer<P>`.
274        assert_producer_api_bound::<
275            magnetar_runtime_moonpool::Producer<moonpool_core::TokioProviders>,
276        >();
277    }
278
279    #[cfg(all(feature = "tokio", feature = "moonpool"))]
280    #[test]
281    fn consumer_api_is_implemented_by_moonpool_consumer() {
282        assert_consumer_api_bound::<
283            magnetar_runtime_moonpool::Consumer<moonpool_core::TokioProviders>,
284        >();
285    }
286
287    /// Compile-time witnesses for the Builder genericity extension
288    /// traits added alongside the Producer/Consumer foundation lift.
289    /// Each runtime's `Client` type satisfies both `SubscribeApi` and
290    /// `CreateProducerApi` so the upcoming Builder lift can dispatch
291    /// through them.
292    #[cfg(feature = "tokio")]
293    fn assert_subscribe_api_bound<T: crate::SubscribeApi>() {}
294
295    #[cfg(feature = "tokio")]
296    fn assert_create_producer_api_bound<T: crate::CreateProducerApi>() {}
297
298    #[cfg(feature = "tokio")]
299    #[test]
300    fn subscribe_api_is_implemented_by_tokio_client() {
301        assert_subscribe_api_bound::<magnetar_runtime_tokio::Client>();
302    }
303
304    #[cfg(feature = "tokio")]
305    #[test]
306    fn create_producer_api_is_implemented_by_tokio_client() {
307        assert_create_producer_api_bound::<magnetar_runtime_tokio::Client>();
308    }
309
310    #[cfg(all(feature = "tokio", feature = "moonpool"))]
311    #[test]
312    fn subscribe_api_is_implemented_by_moonpool_client() {
313        assert_subscribe_api_bound::<
314            magnetar_runtime_moonpool::Client<moonpool_core::TokioProviders>,
315        >();
316    }
317
318    #[cfg(all(feature = "tokio", feature = "moonpool"))]
319    #[test]
320    fn create_producer_api_is_implemented_by_moonpool_client() {
321        assert_create_producer_api_bound::<
322            magnetar_runtime_moonpool::Client<moonpool_core::TokioProviders>,
323        >();
324    }
325}