rust_rabbit/
lib.rs

1//! # rust-rabbit 🐰
2//!
3//! A **simple, reliable** RabbitMQ client library for Rust.
4//! Focus on core functionality with minimal configuration.
5//!
6//! ## Features
7//!
8//! - **🚀 Simple API**: Just Publisher and Consumer with essential methods
9//! - **🔄 Flexible Retry**: Exponential, linear, or custom retry mechanisms  
10//! - **🛠️ Auto-Setup**: Automatic queue/exchange declaration and binding
11//! - **⚡ Built-in Reliability**: Default ACK behavior with error handling
12//!
13//! ## Quick Start
14//!
15//! ### Publisher
16//!
17//! ```rust,no_run
18//! use rust_rabbit::{Connection, Publisher, PublishOptions};
19//! use serde::Serialize;
20//!
21//! #[derive(Serialize)]
22//! struct Order {
23//!     id: u32,
24//!     amount: f64,
25//! }
26//!
27//! #[tokio::main]
28//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
29//!     let connection = Connection::new("amqp://localhost:5672").await?;
30//!     let publisher = Publisher::new(connection);
31//!     
32//!     let order = Order { id: 123, amount: 99.99 };
33//!     
34//!     // Publish to exchange
35//!     publisher.publish_to_exchange("orders", "new.order", &order, None).await?;
36//!     
37//!     // Publish directly to queue
38//!     publisher.publish_to_queue("order_queue", &order, None).await?;
39//!     
40//!     Ok(())
41//! }
42//! ```
43//!
44//! ### Consumer with Retry
45//!
46//! ```rust,no_run
47//! use rust_rabbit::{Connection, Consumer, RetryConfig};
48//! use serde::Deserialize;
49//!
50//! #[derive(Deserialize)]
51//! struct Order {
52//!     id: u32,
53//!     amount: f64,
54//! }
55//!
56//! #[tokio::main]
57//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
58//!     let connection = Connection::new("amqp://localhost:5672").await?;
59//!     
60//!     let consumer = Consumer::builder(connection, "order_queue")
61//!         .retry(RetryConfig::exponential_default()) // 1s->2s->4s->8s->16s
62//!         .bind_to_exchange("orders")
63//!         .concurrency(5)
64//!         .build()
65//!         .await?;
66//!     
67//!     consumer.consume(|order: Order| async move {
68//!         println!("Processing order {}: ${}", order.id, order.amount);
69//!         // Your business logic here
70//!         Ok(()) // ACK message
71//!     }).await?;
72//!     
73//!     Ok(())
74//! }
75//! ```
76//!
77//! ## Retry Configurations
78//!
79//! ```rust,no_run
80//! use rust_rabbit::RetryConfig;
81//! use std::time::Duration;
82//!
83//! // Exponential: 1s -> 2s -> 4s -> 8s -> 16s (5 retries)
84//! let exponential = RetryConfig::exponential_default();
85//!
86//! // Custom exponential: 2s -> 4s -> 8s -> 16s -> 32s (with cap at 60s)
87//! let custom_exp = RetryConfig::exponential(5, Duration::from_secs(2), Duration::from_secs(60));
88//!
89//! // Linear: 10s -> 10s -> 10s (3 retries)  
90//! let linear = RetryConfig::linear(3, Duration::from_secs(10));
91//!
92//! // Custom delays: 1s -> 5s -> 30s
93//! let custom = RetryConfig::custom(vec![
94//!     Duration::from_secs(1),
95//!     Duration::from_secs(5),
96//!     Duration::from_secs(30),
97//! ]);
98//!
99//! // No retries
100//! let no_retry = RetryConfig::no_retry();
101//! ```
102
103// Re-export main types for easy access
104pub use connection::{Connection, ConnectionBuilder, ConnectionConfig};
105pub use consumer::{Consumer, ConsumerBuilder, Message};
106pub use error::{Result, RustRabbitError};
107pub use publisher::{PublishOptions, Publisher};
108pub use retry::{RetryConfig, RetryMechanism};
109
110// Internal modules
111mod connection;
112mod consumer;
113mod error;
114mod publisher;
115mod retry;
116
117/// Prelude module for convenient imports
118pub mod prelude {
119    pub use crate::{
120        Connection, Consumer, ConsumerBuilder, Message, PublishOptions, Publisher, Result,
121        RetryConfig, RetryMechanism, RustRabbitError,
122    };
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use serde::{Deserialize, Serialize};
129    use std::time::Duration;
130
131    #[derive(Debug, Serialize, Deserialize, PartialEq)]
132    #[allow(dead_code)]
133    struct TestMessage {
134        id: u32,
135        content: String,
136    }
137
138    #[tokio::test]
139    async fn test_api_compilation() {
140        // This test ensures the API compiles correctly
141        // Real integration tests would require a RabbitMQ instance
142
143        let _connection_result = Connection::new("amqp://localhost:5672").await;
144
145        // Test retry configurations
146        let _exponential = RetryConfig::exponential_default();
147        let _linear = RetryConfig::linear(3, Duration::from_secs(5));
148        let _custom = RetryConfig::custom(vec![Duration::from_secs(1), Duration::from_secs(5)]);
149        let _no_retry = RetryConfig::no_retry();
150    }
151
152    #[test]
153    fn test_basic_api_exists() {
154        // Test that our main types exist and can be referenced
155        use crate::prelude::*;
156
157        // This is a compile-time test - if it compiles, our API is accessible
158        let _: Option<Connection> = None;
159        let _: Option<Publisher> = None;
160        let _: Option<Consumer> = None;
161        let _: Option<RetryConfig> = None;
162
163        // Test that we can create basic configs
164        let _retry = RetryConfig::exponential_default();
165        let _options = PublishOptions::new();
166    }
167
168    #[test]
169    fn test_retry_config_calculations() {
170        let config = RetryConfig::exponential(5, Duration::from_secs(1), Duration::from_secs(30));
171
172        assert_eq!(config.calculate_delay(0), Some(Duration::from_secs(1)));
173        assert_eq!(config.calculate_delay(1), Some(Duration::from_secs(2)));
174        assert_eq!(config.calculate_delay(2), Some(Duration::from_secs(4)));
175        assert_eq!(config.calculate_delay(3), Some(Duration::from_secs(8)));
176        assert_eq!(config.calculate_delay(4), Some(Duration::from_secs(16)));
177        // Attempt 5 exceeds max_retries (5), so should return None
178        assert_eq!(config.calculate_delay(5), None);
179    }
180
181    #[test]
182    fn test_retry_config_linear() {
183        let config = RetryConfig::linear(3, Duration::from_secs(5));
184
185        assert_eq!(config.max_retries, 3);
186        assert_eq!(config.calculate_delay(0), Some(Duration::from_secs(5)));
187        assert_eq!(config.calculate_delay(1), Some(Duration::from_secs(5)));
188        assert_eq!(config.calculate_delay(2), Some(Duration::from_secs(5)));
189        assert_eq!(config.calculate_delay(3), None); // Exceeds max_retries
190    }
191
192    #[test]
193    fn test_retry_config_custom() {
194        let delays = vec![
195            Duration::from_secs(1),
196            Duration::from_secs(3),
197            Duration::from_secs(7),
198        ];
199        let config = RetryConfig::custom(delays.clone());
200
201        assert_eq!(config.max_retries, 3);
202        assert_eq!(config.calculate_delay(0), Some(Duration::from_secs(1)));
203        assert_eq!(config.calculate_delay(1), Some(Duration::from_secs(3)));
204        assert_eq!(config.calculate_delay(2), Some(Duration::from_secs(7)));
205        assert_eq!(config.calculate_delay(3), None); // Exceeds max_retries
206    }
207
208    #[test]
209    fn test_publish_options() {
210        let options = PublishOptions::new()
211            .mandatory()
212            .with_expiration("60000")
213            .with_priority(5);
214
215        assert!(options.mandatory);
216        assert_eq!(options.expiration, Some("60000".to_string()));
217        assert_eq!(options.priority, Some(5));
218    }
219}