redis_oxide/
lib.rs

1//! High-performance async Redis client for Rust
2//!
3//! `redis-oxide` is a Redis client library similar to StackExchange.Redis for .NET.
4//! It automatically detects whether you're connecting to a standalone Redis server
5//! or a Redis Cluster, and handles MOVED/ASK redirects transparently.
6//!
7//! # Features
8//!
9//! - 🚀 **Automatic topology detection**: Auto-recognizes Standalone Redis or Redis Cluster
10//! - 🔄 **MOVED/ASK redirect handling**: Automatically handles slot migrations in cluster mode
11//! - 🏊 **Flexible connection strategies**: Supports both Multiplexed connections and Connection Pools
12//! - 🛡️ **Type-safe command builders**: Safe API with builder pattern
13//! - ⚡ **Async/await**: Fully asynchronous with Tokio runtime
14//! - 🔌 **Automatic reconnection**: Reconnects with exponential backoff
15//! - 📊 **Comprehensive error handling**: Detailed and clear error types
16//! - ✅ **High test coverage**: Extensive unit and integration tests
17//!
18//! # Quick Start
19//!
20//! ## Basic Connection (Standalone)
21//!
22//! ```no_run
23//! use redis_oxide::{Client, ConnectionConfig};
24//!
25//! #[tokio::main]
26//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
27//!     // Create configuration
28//!     let config = ConnectionConfig::new("redis://localhost:6379");
29//!     
30//!     // Connect (automatically detects topology)
31//!     let client = Client::connect(config).await?;
32//!     
33//!     // SET and GET
34//!     client.set("mykey", "Hello, Redis!").await?;
35//!     if let Some(value) = client.get("mykey").await? {
36//!         println!("Value: {}", value);
37//!     }
38//!     
39//!     Ok(())
40//! }
41//! ```
42//!
43//! ## Redis Cluster Connection
44//!
45//! ```no_run
46//! use redis_oxide::{Client, ConnectionConfig};
47//!
48//! #[tokio::main]
49//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
50//!     // Provide multiple seed nodes
51//!     let config = ConnectionConfig::new(
52//!         "redis://node1:7000,node2:7001,node3:7002"
53//!     );
54//!     
55//!     let client = Client::connect(config).await?;
56//!     
57//!     // Client automatically handles MOVED redirects
58//!     client.set("key", "value").await?;
59//!     
60//!     Ok(())
61//! }
62//! ```
63//!
64//! ## Handling MOVED Redirects
65//!
66//! When encountering a MOVED error (e.g., `MOVED 9916 10.90.6.213:6002`), the library will:
67//!
68//! 1. ✅ Parse the error message and extract slot number and target node
69//! 2. ✅ Automatically update slot mapping
70//! 3. ✅ Create new connection to target node if needed
71//! 4. ✅ Automatically retry the command (up to `max_redirects` times)
72//!
73//! ```no_run
74//! use redis_oxide::{Client, ConnectionConfig};
75//!
76//! # #[tokio::main]
77//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
78//! let config = ConnectionConfig::new("redis://cluster:7000")
79//!     .with_max_redirects(5); // Allow up to 5 redirects
80//!
81//! let client = Client::connect(config).await?;
82//!
83//! // If encountering "MOVED 9916 10.90.6.213:6002",
84//! // client automatically retries command to 10.90.6.213:6002
85//! let value = client.get("mykey").await?;
86//! # Ok(())
87//! # }
88//! ```
89//!
90//! # Supported Commands
91//!
92//! ## String Operations
93//!
94//! ```no_run
95//! # use redis_oxide::{Client, ConnectionConfig};
96//! # use std::time::Duration;
97//! # #[tokio::main]
98//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
99//! # let config = ConnectionConfig::new("redis://localhost:6379");
100//! # let client = Client::connect(config).await?;
101//! // GET
102//! let value: Option<String> = client.get("key").await?;
103//!
104//! // SET
105//! client.set("key", "value").await?;
106//!
107//! // SET with expiration
108//! client.set_ex("key", "value", Duration::from_secs(60)).await?;
109//!
110//! // SET NX (only if key doesn't exist)
111//! let set: bool = client.set_nx("key", "value").await?;
112//!
113//! // DELETE
114//! let deleted: i64 = client.del(vec!["key1".to_string(), "key2".to_string()]).await?;
115//!
116//! // EXISTS
117//! let exists: i64 = client.exists(vec!["key".to_string()]).await?;
118//!
119//! // EXPIRE
120//! client.expire("key", Duration::from_secs(60)).await?;
121//!
122//! // TTL
123//! let ttl: Option<i64> = client.ttl("key").await?;
124//!
125//! // INCR/DECR
126//! let new_value: i64 = client.incr("counter").await?;
127//! let new_value: i64 = client.decr("counter").await?;
128//!
129//! // INCRBY/DECRBY
130//! let new_value: i64 = client.incr_by("counter", 10).await?;
131//! let new_value: i64 = client.decr_by("counter", 5).await?;
132//! # Ok(())
133//! # }
134//! ```
135//!
136//! # Configuration
137//!
138//! ## Connection Configuration
139//!
140//! ```no_run
141//! use redis_oxide::{ConnectionConfig, TopologyMode};
142//! use std::time::Duration;
143//!
144//! let config = ConnectionConfig::new("redis://localhost:6379")
145//!     .with_password("secret")           // Password (optional)
146//!     .with_database(0)                  // Database number
147//!     .with_connect_timeout(Duration::from_secs(5))
148//!     .with_operation_timeout(Duration::from_secs(30))
149//!     .with_topology_mode(TopologyMode::Auto) // Auto, Standalone, or Cluster
150//!     .with_max_redirects(3);            // Max retries for cluster redirects
151//! ```
152//!
153//! ## Pool Configuration
154//!
155//! ### Multiplexed Connection (Default)
156//!
157//! Uses a single connection shared between multiple tasks via mpsc channel. Suitable for most use cases.
158//!
159//! ```no_run
160//! use redis_oxide::{ConnectionConfig, PoolConfig, PoolStrategy};
161//!
162//! let mut config = ConnectionConfig::new("redis://localhost:6379");
163//! config.pool = PoolConfig {
164//!     strategy: PoolStrategy::Multiplexed,
165//!     ..Default::default()
166//! };
167//! ```
168//!
169//! ### Connection Pool
170//!
171//! Uses multiple connections. Suitable for very high workload with many concurrent requests.
172//!
173//! ```no_run
174//! use redis_oxide::{ConnectionConfig, PoolConfig, PoolStrategy};
175//! use std::time::Duration;
176//!
177//! let pool_config = PoolConfig {
178//!     strategy: PoolStrategy::Pool,
179//!     max_size: 20,                      // Max 20 connections
180//!     min_idle: 5,                       // Keep at least 5 idle connections
181//!     connection_timeout: Duration::from_secs(5),
182//! };
183//!
184//! let mut config = ConnectionConfig::new("redis://localhost:6379");
185//! config.pool = pool_config;
186//! ```
187
188#![allow(unknown_lints)]
189#![allow(clippy::unnecessary_literal_bound)]
190#![deny(warnings)]
191#![deny(clippy::all)]
192//#![deny(clippy::pedantic)]  // Temporarily disabled due to clippy::unnecessary_literal_bound incompatibility
193#![deny(clippy::nursery)]
194#![allow(clippy::cargo)] // Allow cargo lints for now
195#![allow(rustdoc::broken_intra_doc_links)] // Allow broken doc links for now
196#![allow(rustdoc::invalid_html_tags)] // Allow invalid HTML tags for now
197#![allow(clippy::needless_raw_string_hashes)] // Allow raw string hashes
198#![allow(clippy::missing_panics_doc)] // Allow missing panics doc for now
199#![allow(clippy::option_if_let_else)] // Allow option if let else for readability
200#![allow(clippy::needless_pass_by_value)] // Allow pass by value for API design
201#![allow(clippy::only_used_in_recursion)] // Allow for recursive functions
202#![allow(clippy::should_implement_trait)] // Allow for custom method names
203#![allow(clippy::cast_precision_loss)] // Allow precision loss for performance metrics
204#![allow(clippy::suboptimal_flops)] // Allow for readability
205#![allow(clippy::single_match_else)] // Allow for readability
206#![allow(clippy::redundant_pattern_matching)] // Allow for explicit patterns
207#![allow(clippy::if_same_then_else)] // Allow for optimization code
208#![allow(clippy::match_same_arms)] // Allow for protocol handling
209#![allow(clippy::too_many_lines)] // Allow for complex protocol functions
210#![allow(clippy::significant_drop_in_scrutinee)] // Allow for async code patterns
211#![allow(clippy::needless_borrows_for_generic_args)] // Allow for API consistency
212#![allow(clippy::map_unwrap_or)] // Allow for readability
213#![allow(clippy::ignored_unit_patterns)] // Allow for explicit patterns
214#![allow(clippy::manual_map)] // Allow for readability
215#![allow(clippy::needless_pass_by_ref_mut)] // Allow for API consistency
216#![allow(clippy::unused_self)] // Allow for trait implementations
217#![warn(missing_docs)]
218#![allow(clippy::missing_errors_doc)]
219#![allow(clippy::must_use_candidate)]
220#![allow(clippy::missing_const_for_fn)]
221#![allow(clippy::uninlined_format_args)]
222#![allow(clippy::future_not_send)]
223#![allow(clippy::significant_drop_tightening)]
224#![allow(clippy::doc_markdown)]
225#![allow(clippy::cast_possible_truncation)]
226#![allow(clippy::cast_sign_loss)]
227#![allow(clippy::cast_lossless)]
228#![allow(clippy::return_self_not_must_use)]
229#![allow(clippy::large_enum_variant)]
230#![allow(clippy::implicit_clone)]
231#![allow(clippy::manual_let_else)]
232#![allow(clippy::unnecessary_wraps)]
233#![allow(clippy::unused_async)]
234#![allow(clippy::module_name_repetitions)]
235
236pub mod client;
237pub mod cluster;
238pub mod commands;
239pub mod connection;
240pub mod pipeline;
241pub mod pool;
242pub mod pool_optimized;
243pub mod protocol;
244pub mod pubsub;
245pub mod script;
246pub mod sentinel;
247pub mod streams;
248pub mod transaction;
249
250pub use client::Client;
251pub mod core;
252pub use pipeline::{Pipeline, PipelineResult};
253pub use pubsub::{PubSubMessage, Publisher, Subscriber};
254pub use script::{Script, ScriptManager};
255pub use sentinel::{MasterInfo, SentinelClient, SentinelConfig, SentinelEndpoint};
256pub use streams::{
257    ConsumerGroupInfo, ConsumerInfo, PendingMessage, ReadOptions, StreamEntry, StreamInfo,
258    StreamRange,
259};
260pub use transaction::{Transaction, TransactionResult};
261
262pub use crate::core::{
263    config::{ConnectionConfig, PoolConfig, PoolStrategy, ProtocolVersion, TopologyMode},
264    error::{RedisError, RedisResult},
265    types::{NodeInfo, RedisValue, SlotRange},
266    value::RespValue,
267};
268
269// Re-export protocol types
270pub use crate::protocol::{ProtocolNegotiation, ProtocolNegotiator, Resp3Value};