redis_oxide/lib.rs
1//! High-performance async Redis client for Rust
2//!
3//! `redis-oxide` is a comprehensive Redis client library for Rust.
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//! - **Cross-platform support**: Works on Linux, macOS, and Windows
18//! - **Pub/Sub Support**: Built-in publish/subscribe messaging
19//! - **Streams Support**: Full Redis Streams functionality for event sourcing
20//! - **Lua Scripting**: Execute Lua scripts with automatic EVALSHA caching
21//! - **Sentinel Support**: High availability with Redis Sentinel
22//! - **Transaction Support**: MULTI/EXEC transaction handling
23//! - **Pipeline Support**: Batch multiple commands for improved performance
24//! - **RESP2/RESP3 Protocol Support**: Full support for both protocol versions
25//! - **Connection Pooling**: Configurable connection pooling strategies
26//! - **Multiplexed Connections**: Single connection shared across tasks
27//! - **Hash Operations**: Complete set of hash data type operations
28//! - **List Operations**: Complete set of list data type operations
29//! - **Set Operations**: Complete set of set data type operations
30//! - **Sorted Set Operations**: Complete set of sorted set data type operations
31//! - **String Operations**: Complete set of string data type operations
32//! - **HyperLogLog Operations**: Support for probabilistic data structures
33//! - **Geo Operations**: Support for geospatial data types
34//! - **Performance Optimizations**: Memory pooling and protocol optimizations
35//! - **Monitoring & Metrics**: Built-in observability features
36//! - **Configurable Timeouts**: Connect, operation, and redirect timeout controls
37//! - **Authentication Support**: Password and access control support
38//!
39//! # ๐ฆ Installation
40//!
41//! Add this to your `Cargo.toml`:
42//!
43//! ```toml
44//! [dependencies]
45//! redis-oxide = "0.2.2"
46//! tokio = { version = "1.0", features = ["full"] }
47//! ```
48//!
49//! # ๐ ๏ธ Quick Start
50//!
51//! ## Basic Connection (Standalone)
52//!
53//! ```no_run
54//! use redis_oxide::{Client, ConnectionConfig};
55//!
56//! #[tokio::main]
57//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
58//!     // Create configuration
59//!     let config = ConnectionConfig::new("redis://localhost:6379");
60//!     
61//!     // Connect (automatically detects topology)
62//!     let client = Client::connect(config).await?;
63//!     
64//!     // SET and GET
65//!     client.set("mykey", "Hello, Redis!").await?;
66//!     if let Some(value) = client.get("mykey").await? {
67//!         println!("Value: {}", value);
68//!     }
69//!     
70//!     Ok(())
71//! }
72//! ```
73//!
74//! ## Redis Cluster Connection
75//!
76//! ```no_run
77//! use redis_oxide::{Client, ConnectionConfig};
78//!
79//! #[tokio::main]
80//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
81//!     // Provide multiple seed nodes
82//!     let config = ConnectionConfig::new(
83//!         "redis://node1:7000,node2:7001,node3:7002"
84//!     );
85//!     
86//!     let client = Client::connect(config).await?;
87//!     
88//!     // Client automatically handles MOVED redirects
89//!     client.set("key", "value").await?;
90//!     
91//!     Ok(())
92//! }
93//! ```
94//!
95//! # ๐ฏ Supported Operations
96//!
97//! ## String Operations
98//!
99//! ```no_run
100//! # use redis_oxide::{Client, ConnectionConfig};
101//! # use std::time::Duration;
102//! # #[tokio::main]
103//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
104//! # let config = ConnectionConfig::new("redis://localhost:6379");
105//! # let client = Client::connect(config).await?;
106//! // GET
107//! let value: Option<String> = client.get("key").await?;
108//!
109//! // SET
110//! client.set("key", "value").await?;
111//!
112//! // SET with expiration
113//! client.set_ex("key", "value", Duration::from_secs(60)).await?;
114//!
115//! // SET NX (only if key doesn't exist)
116//! let set: bool = client.set_nx("key", "value").await?;
117//! assert!(set);
118//!
119//! // DELETE
120//! let deleted: i64 = client.del(vec!["key1".to_string(), "key2".to_string()]).await?;
121//!
122//! // EXISTS
123//! let exists: i64 = client.exists(vec!["key".to_string()]).await?;
124//!
125//! // EXPIRE
126//! client.expire("key", Duration::from_secs(60)).await?;
127//!
128//! // TTL
129//! let ttl: Option<i64> = client.ttl("key").await?;
130//!
131//! // INCR/DECR
132//! let new_value: i64 = client.incr("counter").await?;
133//! let new_value: i64 = client.decr("counter").await?;
134//!
135//! // INCRBY/DECRBY
136//! let new_value: i64 = client.incr_by("counter", 10).await?;
137//! let new_value: i64 = client.decr_by("counter", 5).await?;
138//! # Ok(())
139//! # }
140//! ```
141//!
142//! ## Hash Operations
143//!
144//! ```no_run,ignore
145//! # use redis_oxide::{Client, ConnectionConfig};
146//! # #[tokio::main]
147//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
148//! # let config = ConnectionConfig::new("redis://localhost:6379");
149//! # let client = Client::connect(config).await?;
150//! // HSET
151//! client.hset("myhash", "field1", "value1").await?;
152//! client.hset("myhash", "field2", "value2").await?;
153//!
154//! // HGET
155//! if let Some(value) = client.hget("myhash", "field1").await? {
156//!     println!("field1: {}", value);
157//! }
158//!
159//! // HGETALL
160//! let all_fields = client.hgetall("myhash").await?;
161//! println!("All hash fields: {:?}", all_fields);
162//!
163//! // HMGET (multiple get)
164//! let values = client.hmget("myhash", vec!["field1".to_string(), "field2".to_string()]).await?;
165//! println!("Multiple fields: {:?}", values);
166//!
167//! // HMSET (multiple set)
168//! use std::collections::HashMap;
169//! let mut fields = HashMap::new();
170//! fields.insert("field3".to_string(), "value3".to_string());
171//! fields.insert("field4".to_string(), "value4".to_string());
172//! client.hmset("myhash", fields).await?;
173//!
174//! // HDEL
175//! let deleted: i64 = client.hdel("myhash", vec!["field1".to_string(), "field2".to_string()]).await?;
176//!
177//! // HEXISTS
178//! let exists: bool = client.hexists("myhash", "field1").await?;
179//!
180//! // HLEN
181//! let len: i64 = client.hlen("myhash").await?;
182//! # Ok(())
183//! # }
184//! ```
185//!
186//! ## List Operations
187//!
188//! ```no_run
189//! # use redis_oxide::{Client, ConnectionConfig};
190//! # #[tokio::main]
191//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
192//! # let config = ConnectionConfig::new("redis://localhost:6379");
193//! # let client = Client::connect(config).await?;
194//! // LPUSH
195//! client.lpush("mylist", vec!["item1".to_string(), "item2".to_string()]).await?;
196//!
197//! // RPUSH
198//! client.rpush("mylist", vec!["item3".to_string(), "item4".to_string()]).await?;
199//!
200//! // LRANGE
201//! let items = client.lrange("mylist", 0, -1).await?;
202//! println!("List items: {:?}", items);
203//!
204//! // LLEN
205//! let len: i64 = client.llen("mylist").await?;
206//!
207//! // LINDEX
208//! if let Some(item) = client.lindex("mylist", 0).await? {
209//!     println!("First item: {}", item);
210//! }
211//!
212//! // LPOP
213//! if let Some(item) = client.lpop("mylist").await? {
214//!     println!("Popped item: {}", item);
215//! }
216//! # Ok(())
217//! # }
218//! ```
219//!
220//! ## Set Operations
221//!
222//! ```no_run
223//! # use redis_oxide::{Client, ConnectionConfig};
224//! # #[tokio::main]
225//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
226//! # let config = ConnectionConfig::new("redis://localhost:6379");
227//! # let client = Client::connect(config).await?;
228//! // SADD
229//! client.sadd("myset", vec!["member1".to_string(), "member2".to_string()]).await?;
230//!
231//! // SMEMBERS
232//! let members = client.smembers("myset").await?;
233//! println!("Set members: {:?}", members);
234//!
235//! // SISMEMBER
236//! let is_member: bool = client.sismember("myset", "member1").await?;
237//!
238//! // SREM
239//! let removed: i64 = client.srem("myset", vec!["member1".to_string()]).await?;
240//!
241//! // SCARD
242//! let count: i64 = client.scard("myset").await?;
243//! # Ok(())
244//! # }
245//! ```
246//!
247//! ## Sorted Set Operations
248//!
249//! ```no_run
250//! # use redis_oxide::{Client, ConnectionConfig};
251//! # #[tokio::main]
252//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
253//! # let config = ConnectionConfig::new("redis://localhost:6379");
254//! # let client = Client::connect(config).await?;
255//! use std::collections::HashMap;
256//!
257//! // ZADD
258//! let mut members = HashMap::new();
259//! members.insert("member1".to_string(), 10.0);
260//! members.insert("member2".to_string(), 20.0);
261//! client.zadd("mysortedset", members).await?;
262//!
263//! // ZRANGE
264//! let members = client.zrange("mysortedset", 0, -1).await?;
265//! println!("Sorted set members: {:?}", members);
266//!
267//! // ZSCORE
268//! if let Some(score) = client.zscore("mysortedset", "member1").await? {
269//!     println!("Member1 score: {}", score);
270//! }
271//!
272//! // ZCARD
273//! let count: i64 = client.zcard("mysortedset").await?;
274//! # Ok(())
275//! # }
276//! ```
277//!
278//! ## HyperLogLog Operations (Not Yet Implemented)
279//!
280//! HyperLogLog operations (PFADD, PFCOUNT, PFMERGE) are planned for future implementation.
281//!
282//! ## Geo Operations (Not Yet Implemented)
283//!
284//! Geo operations (GEOADD, GEODIST, GEOPOS) are planned for future implementation.
285//!
286//! ## Connection Pool Configuration
287//!
288//! ```no_run
289//! use redis_oxide::{Client, ConnectionConfig, PoolConfig, PoolStrategy};
290//! use std::time::Duration;
291//!
292//! # #[tokio::main]
293//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
294//! let mut config = ConnectionConfig::new("redis://localhost:6379");
295//! config.pool = PoolConfig {
296//!     strategy: PoolStrategy::Pool,
297//!     max_size: 20,                      // Max 20 connections
298//!     min_idle: 5,                       // Keep at least 5 idle connections
299//!     connection_timeout: Duration::from_secs(5),
300//! };
301//!
302//! let client = Client::connect(config).await?;
303//! # Ok(())
304//! # }
305//! ```
306//!
307//! ## Multiplexed Connection (Default)
308//!
309//! ```no_run
310//! use redis_oxide::{Client, ConnectionConfig, PoolConfig, PoolStrategy};
311//!
312//! # #[tokio::main]
313//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
314//! let mut config = ConnectionConfig::new("redis://localhost:6379");
315//! config.pool = PoolConfig {
316//!     strategy: PoolStrategy::Multiplexed,
317//!     ..Default::default()
318//! };
319//!
320//! let client = Client::connect(config).await?;
321//! # Ok(())
322//! # }
323//! ```
324//!
325//! ## Transactions
326//!
327//! ```no_run
328//! use redis_oxide::{Client, ConnectionConfig};
329//!
330//! # #[tokio::main]
331//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
332//! # let config = ConnectionConfig::new("redis://localhost:6379");
333//! # let client = Client::connect(config).await?;
334//! // Start a transaction
335//! let mut transaction = client.transaction().await?;
336//!
337//! // Add commands to the transaction (no await for adding commands)
338//! transaction.set("key1", "value1");
339//! transaction.set("key2", "value2");
340//! transaction.incr("counter");
341//!
342//! // Execute the transaction
343//! let results = transaction.exec().await?;
344//! println!("Transaction results: {:?}", results);
345//! # Ok(())
346//! # }
347//! ```
348//!
349//! ## Pipelines
350//!
351//! ```no_run
352//! use redis_oxide::{Client, ConnectionConfig};
353//!
354//! # #[tokio::main]
355//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
356//! # let config = ConnectionConfig::new("redis://localhost:6379");
357//! # let client = Client::connect(config).await?;
358//! // Create a pipeline
359//! let mut pipeline = client.pipeline();
360//!
361//! // Add commands to the pipeline (no await for adding commands)
362//! pipeline.set("key1", "value1");
363//! pipeline.set("key2", "value2");
364//! pipeline.get("key1");
365//! pipeline.incr("counter");
366//!
367//! // Execute all commands at once
368//! let results = pipeline.execute().await?;
369//! println!("Pipeline results: {:?}", results);
370//! # Ok(())
371//! # }
372//! ```
373//!
374//! ## Pub/Sub Messaging
375//!
376//! ```no_run
377//! use redis_oxide::{Client, ConnectionConfig};
378//! use futures::StreamExt;
379//!
380//! # #[tokio::main]
381//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
382//! # let config = ConnectionConfig::new("redis://localhost:6379");
383//! # let client = Client::connect(config).await?;
384//! // Publisher example
385//! let publisher = client.publisher().await?;
386//!
387//! let subscribers = publisher.publish("news", "Breaking news!").await?;
388//! println!("Message sent to {} subscribers", subscribers);
389//!
390//! // Subscriber example
391//! let mut subscriber = client.subscriber().await?;
392//! subscriber.subscribe(vec!["news".to_string(), "updates".to_string()]).await?;
393//!
394//! // Listen for messages
395//! while let Some(message) = subscriber.next_message().await? {
396//!     println!("Received: {} on channel {}", message.payload, message.channel);
397//! }
398//! # Ok(())
399//! # }
400//! ```
401//!
402//! ## Redis Streams
403//!
404//! ```no_run
405//! use redis_oxide::{Client, ConnectionConfig, StreamRange, ReadOptions};
406//! use std::collections::HashMap;
407//!
408//! # #[tokio::main]
409//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
410//! # let config = ConnectionConfig::new("redis://localhost:6379");
411//! # let client = Client::connect(config).await?;
412//! // Create and add entries to a stream
413//! let mut fields = HashMap::new();
414//! fields.insert("user_id".to_string(), "123".to_string());
415//! fields.insert("action".to_string(), "login".to_string());
416//!
417//! let entry_id = client.xadd("events", "*", fields).await?;
418//! println!("Added entry: {}", entry_id);
419//!
420//! // Read from stream with range
421//! let entries = client.xrange("events", "-", "+", Some(10)).await?;
422//! for entry in entries {
423//!     println!("Entry {}: {:?}", entry.id, entry.fields);
424//! }
425//!
426//! // Read from stream with options
427//! let entries = client.xread(vec![("events".to_string(), "0-0".to_string())], Some(5), Some(std::time::Duration::from_millis(100))).await?;
428//! # Ok(())
429//! # }
430//! ```
431//!
432//! ## Consumer Groups (Redis Streams)
433//!
434//! ```no_run
435//! use redis_oxide::{Client, ConnectionConfig};
436//!
437//! # #[tokio::main]
438//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
439//! # let config = ConnectionConfig::new("redis://localhost:6379");
440//! # let client = Client::connect(config).await?;
441//! // Create a consumer group
442//! client.xgroup_create("events", "processors", "$", true).await?;
443//!
444//! // Read from the group
445//! let messages = client.xreadgroup(
446//!     "processors",
447//!     "worker-1",
448//!     vec![("events".to_string(), ">".to_string())],
449//!     Some(1),
450//!     Some(std::time::Duration::from_secs(1))
451//! ).await?;
452//!
453//! for (stream, entries) in messages {
454//!     for entry in entries {
455//!         println!("Processing {}: {:?}", entry.id, entry.fields);
456//!         // Process the message...
457//!         
458//!         // Acknowledge the message
459//!         client.xack("events", "processors", vec![entry.id]).await?;
460//!     }
461//! }
462//! # Ok(())
463//! # }
464//! ```
465//!
466//! ## Lua Scripting
467//!
468//! ```no_run
469//! use redis_oxide::{Client, ConnectionConfig, Script};
470//!
471//! # #[tokio::main]
472//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
473//! # let config = ConnectionConfig::new("redis://localhost:6379");
474//! # let client = Client::connect(config).await?;
475//! // Execute a simple Lua script
476//! let script = "return redis.call('GET', KEYS[1])";
477//! let result: Option<String> = client.eval(script, vec!["mykey".to_string()], vec![]).await?;
478//! println!("Result: {:?}", result);
479//!
480//! // Script with arguments
481//! let script = r#"
482//!     local current = redis.call('GET', KEYS[1]) or 0
483//!     local increment = tonumber(ARGV[1])
484//!     local new_value = tonumber(current) + increment
485//!     redis.call('SET', KEYS[1], new_value)
486//!     return new_value
487//! "#;
488//!
489//! let result: i64 = client.eval(
490//!     script,
491//!     vec!["counter".to_string()],
492//!     vec!["5".to_string()]
493//! ).await?;
494//! println!("New counter value: {}", result);
495//!
496//! // Using Script with automatic EVALSHA caching
497//! let script = Script::new(r#"
498//!     local key = KEYS[1]
499//!     local value = ARGV[1]
500//!     redis.call('SET', key, value)
501//!     return redis.call('GET', key)
502//! "#);
503//!
504//! let result: String = script.execute(
505//!     &client,
506//!     vec!["mykey".to_string()],
507//!     vec!["myvalue".to_string()]
508//! ).await?;
509//! println!("Result: {}", result);
510//! # Ok(())
511//! # }
512//! ```
513//!
514//! ## Sentinel Support
515//!
516//! ```no_run
517//! use redis_oxide::{Client, ConnectionConfig, SentinelConfig};
518//!
519//! # #[tokio::main]
520//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
521//! let sentinel_config = SentinelConfig::new("mymaster")
522//!     .add_sentinel("127.0.0.1:26379")
523//!     .add_sentinel("127.0.0.1:26380")
524//!     .add_sentinel("127.0.0.1:26381")
525//!     .with_password("sentinel_password");
526//!
527//! let config = ConnectionConfig::new_with_sentinel(sentinel_config);
528//! let client = Client::connect(config).await?;
529//!
530//! // Client automatically connects to current master
531//! client.set("key", "value").await?;
532//! # Ok(())
533//! # }
534//! ```
535//!
536//! ## RESP2/RESP3 Protocol Support
537//!
538//! ```no_run
539//! use redis_oxide::{Client, ConnectionConfig, ProtocolVersion};
540//! use std::collections::HashMap;
541//!
542//! # #[tokio::main]
543//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
544//! // RESP2 (default)
545//! let config_resp2 = ConnectionConfig::new("redis://localhost:6379")
546//!     .with_protocol_version(ProtocolVersion::Resp2);
547//! let client_resp2 = Client::connect(config_resp2).await?;
548//!
549//! // RESP3 (Redis 6.0+)
550//! let config_resp3 = ConnectionConfig::new("redis://localhost:6379")
551//!     .with_protocol_version(ProtocolVersion::Resp3);
552//! let client_resp3 = Client::connect(config_resp3).await?;
553//!
554//! // RESP3 allows more complex data types
555//! let mut map = HashMap::new();
556//! map.insert("field1".to_string(), "value1".to_string());
557//! map.insert("field2".to_string(), "value2".to_string());
558//! client_resp3.hmset("myhash", map).await?;
559//! # Ok(())
560//! # }
561//! ```
562//!
563//! # โ๏ธ Configuration Options
564//!
565//! ## Connection Configuration
566//!
567//! ```no_run,ignore
568//! use redis_oxide::{ConnectionConfig, TopologyMode, ProtocolVersion};
569//! use std::time::Duration;
570//!
571//! let config = ConnectionConfig::new("redis://localhost:6379")
572//!     .with_password("secret")           // Password (optional)
573//!     .with_database(0)                  // Database number
574//!     .with_connect_timeout(Duration::from_secs(5))
575//!     .with_operation_timeout(Duration::from_secs(30))
576//!     .with_topology_mode(TopologyMode::Auto) // Auto, Standalone, or Cluster
577//!     .with_protocol_version(ProtocolVersion::Resp3)  // Use RESP3 protocol
578//!     .with_max_redirects(3)             // Max retries for cluster redirects
579//! ```
580//!
581//! # ๐ Performance Features
582//!
583//! ## Optimized Memory Pooling
584//!
585//! The library includes optimized memory pooling for frequently allocated objects
586//! to reduce allocation overhead and improve performance.
587//!
588//! ## Connection Multiplexing
589//!
590//! The multiplexed connection strategy shares a single connection across multiple
591//! tasks using an efficient message passing system, providing excellent performance
592//! for most use cases.
593//!
594//! ## Async/Await Support
595//!
596//! Fully async implementation using Tokio runtime for maximum performance and
597//! resource efficiency.
598//!
599//! # ๐งช Testing
600//!
601//! To run the tests, you'll need a Redis server running on `localhost:6379`:
602//!
603//! ```bash
604//! # Run all tests
605//! cargo test
606//!
607//! # Run integration tests specifically
608//! cargo test --test integration_tests
609//!
610//! # Run tests with Redis server
611//! docker run --rm -p 6379:6379 redis:7
612//! cargo test
613//! ```
614//!
615//! # ๐ License
616//!
617//! This project is licensed under either of the following, at your option:
618//!
619//! - Apache License, Version 2.0, ([LICENSE-APACHE](https://github.com/nghiaphamln/redis-oxide/blob/main/LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
620//! - MIT license ([LICENSE-MIT](https://github.com/nghiaphamln/redis-oxide/blob/main/LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
621
622#![allow(unknown_lints)]
623#![allow(clippy::unnecessary_literal_bound)]
624#![deny(warnings)]
625#![deny(clippy::all)]
626//#![deny(clippy::pedantic)]  // Temporarily disabled due to clippy::unnecessary_literal_bound incompatibility
627#![deny(clippy::nursery)]
628#![allow(clippy::cargo)] // Allow cargo lints for now
629#![allow(rustdoc::broken_intra_doc_links)] // Allow broken doc links for now
630#![allow(rustdoc::invalid_html_tags)] // Allow invalid HTML tags for now
631#![allow(clippy::needless_raw_string_hashes)] // Allow raw string hashes
632#![allow(clippy::missing_panics_doc)] // Allow missing panics doc for now
633#![allow(clippy::option_if_let_else)] // Allow option if let else for readability
634#![allow(clippy::needless_pass_by_value)] // Allow pass by value for API design
635#![allow(clippy::only_used_in_recursion)] // Allow for recursive functions
636#![allow(
637    clippy::similar_names,
638    clippy::approx_constant,
639    clippy::float_cmp,
640    clippy::uninlined_format_args
641)] // Allow in tests
642#![allow(clippy::should_implement_trait)] // Allow for custom method names
643#![allow(clippy::cast_precision_loss)] // Allow precision loss for performance metrics
644#![allow(clippy::suboptimal_flops)] // Allow for readability
645#![allow(clippy::single_match_else)] // Allow for readability
646#![allow(clippy::redundant_pattern_matching)] // Allow for explicit patterns
647#![allow(clippy::if_same_then_else)] // Allow for optimization code
648#![allow(clippy::match_same_arms)] // Allow for protocol handling
649#![allow(clippy::too_many_lines)] // Allow for complex protocol functions
650#![allow(clippy::significant_drop_in_scrutinee)] // Allow for async code patterns
651#![allow(clippy::needless_borrows_for_generic_args)] // Allow for API consistency
652#![allow(clippy::map_unwrap_or)] // Allow for readability
653#![allow(clippy::ignored_unit_patterns)] // Allow for explicit patterns
654#![allow(clippy::manual_map)] // Allow for readability
655#![allow(clippy::needless_pass_by_ref_mut)] // Allow for API consistency
656#![allow(clippy::unused_self)] // Allow for trait implementations
657#![warn(missing_docs)]
658#![allow(clippy::missing_errors_doc)]
659#![allow(clippy::must_use_candidate)]
660#![allow(clippy::missing_const_for_fn)]
661#![allow(clippy::uninlined_format_args)]
662#![allow(clippy::future_not_send)]
663#![allow(clippy::significant_drop_tightening)]
664#![allow(clippy::doc_markdown)]
665#![allow(clippy::cast_possible_truncation)]
666#![allow(clippy::cast_sign_loss)]
667#![allow(clippy::cast_lossless)]
668#![allow(clippy::return_self_not_must_use)]
669#![allow(clippy::large_enum_variant)]
670#![allow(clippy::implicit_clone)]
671#![allow(clippy::manual_let_else)]
672#![allow(clippy::unnecessary_wraps)]
673#![allow(clippy::unused_async)]
674#![allow(clippy::module_name_repetitions)]
675
676pub mod client;
677pub mod cluster;
678pub mod commands;
679pub mod connection;
680pub mod pipeline;
681pub mod pool;
682pub mod pool_optimized;
683pub mod protocol;
684pub mod pubsub;
685pub mod script;
686pub mod sentinel;
687pub mod streams;
688pub mod transaction;
689
690pub use client::Client;
691pub mod core;
692pub use pipeline::{Pipeline, PipelineResult};
693pub use pubsub::{PubSubMessage, Publisher, Subscriber};
694pub use script::{Script, ScriptManager};
695pub use sentinel::{MasterInfo, SentinelClient, SentinelConfig, SentinelEndpoint};
696pub use streams::{
697    ConsumerGroupInfo, ConsumerInfo, PendingMessage, ReadOptions, StreamEntry, StreamInfo,
698    StreamRange,
699};
700pub use transaction::{Transaction, TransactionResult};
701
702pub use crate::core::{
703    config::{ConnectionConfig, PoolConfig, PoolStrategy, ProtocolVersion, TopologyMode},
704    error::{RedisError, RedisResult},
705    types::{NodeInfo, RedisValue, SlotRange},
706    value::RespValue,
707};
708
709// Re-export protocol types
710pub use crate::protocol::{ProtocolNegotiation, ProtocolNegotiator, Resp3Value};