redis/lib.rs
1//! redis-rs is a Rust implementation of a client library for Redis. It exposes
2//! a general purpose interface to Redis and also provides specific helpers for
3//! commonly used functionality.
4//!
5//! The crate is called `redis` and you can depend on it via cargo:
6//!
7//! ```ini
8//! [dependencies.redis]
9//! version = "*"
10//! ```
11//!
12//! If you want to use the git version:
13//!
14//! ```ini
15//! [dependencies.redis]
16//! git = "https://github.com/redis-rs/redis-rs.git"
17//! ```
18//!
19//! # Basic Operation
20//!
21//! redis-rs exposes two API levels: a low- and a high-level part.
22//! The high-level part does not expose all the functionality of redis and
23//! might take some liberties in how it speaks the protocol. The low-level
24//! part of the API allows you to express any request on the redis level.
25//! You can fluently switch between both API levels at any point.
26//!
27//! # TLS / SSL
28//!
29//! The user can enable TLS support using either RusTLS or native support (usually OpenSSL),
30//! using the `tls-rustls` or `tls-native-tls` features respectively. In order to enable TLS
31//! for async usage, the user must enable matching features for their runtime - either `tokio-native-tls-comp`,
32//! `tokio-rustls-comp`, `async-std-native-tls-comp`, or `async-std-rustls-comp`. Additionally, the
33//! `tls-rustls-webpki-roots` allows usage of of webpki-roots for the root certificate store.
34//!
35//! # TCP settings
36//!
37//! The user can set parameters of the underlying TCP connection by using the `tcp_nodelay` and `keep-alive` features.
38//! Alternatively, users of async connections can set [crate::io::tcp::TcpSettings] on the connection configuration objects,
39//! and set the TCP parameters in a more specific manner there.
40//!
41//! ## Connection Handling
42//!
43//! For connecting to redis you can use a client object which then can produce
44//! actual connections. Connections and clients as well as results of
45//! connections and clients are considered `ConnectionLike` objects and
46//! can be used anywhere a request is made.
47//!
48//! The full canonical way to get a connection is to create a client and
49//! to ask for a connection from it:
50//!
51//! ```rust,no_run
52//! extern crate redis;
53//!
54//! fn do_something() -> redis::RedisResult<()> {
55//! let client = redis::Client::open("redis://127.0.0.1/")?;
56//! let mut con = client.get_connection()?;
57//!
58//! /* do something here */
59//!
60//! Ok(())
61//! }
62//! ```
63//!
64//! ## Connection Pooling
65//!
66//! When using a sync connection, it is recommended to use a connection pool in order to handle
67//! disconnects or multi-threaded usage. This can be done using the `r2d2` feature.
68//!
69//! ```rust,no_run
70//! # #[cfg(feature = "r2d2")]
71//! # fn do_something() {
72//! use redis::Commands;
73//!
74//! let client = redis::Client::open("redis://127.0.0.1/").unwrap();
75//! let pool = r2d2::Pool::builder().build(client).unwrap();
76//! let mut conn = pool.get().unwrap();
77//!
78//! let _: () = conn.set("KEY", "VALUE").unwrap();
79//! let val: String = conn.get("KEY").unwrap();
80//! # }
81//! ```
82//!
83//! For async connections, connection pooling isn't necessary. The `MultiplexedConnection` is
84//! cloneable and can be used safely from multiple threads, so a single connection can be easily
85//! reused. For automatic reconnections consider using `ConnectionManager` with the `connection-manager` feature.
86//! Async cluster connections also don't require pooling and are thread-safe and reusable.
87//!
88//! ## Optional Features
89//!
90//! There are a few features defined that can enable additional functionality
91//! if so desired. Some of them are turned on by default.
92//!
93//! * `acl`: enables acl support (enabled by default)
94//! * `tokio-comp`: enables support for async usage with the Tokio runtime (optional)
95//! * `async-std-comp`: enables support for async usage with any runtime which is async-std compliant, such as Smol. (optional)
96//! * `geospatial`: enables geospatial support (enabled by default)
97//! * `script`: enables script support (enabled by default)
98//! * `streams`: enables high-level interface for interaction with Redis streams (enabled by default)
99//! * `r2d2`: enables r2d2 connection pool support (optional)
100//! * `ahash`: enables ahash map/set support & uses ahash internally (+7-10% performance) (optional)
101//! * `cluster`: enables redis cluster support (optional)
102//! * `cluster-async`: enables async redis cluster support (optional)
103//! * `connection-manager`: enables support for automatic reconnection (optional)
104//! * `keep-alive`: enables keep-alive option on socket by means of `socket2` crate (enabled by default)
105//! * `tcp_nodelay`: enables the no-delay flag on communication sockets (optional)
106//! * `rust_decimal`, `bigdecimal`, `num-bigint`: enables type conversions to large number representation from different crates (optional)
107//! * `uuid`: enables type conversion to UUID (optional)
108//! * `sentinel`: enables high-level interfaces for communication with Redis sentinels (optional)
109//! * `json`: enables high-level interfaces for communication with the JSON module (optional)
110//! * `cache-aio`: enables **experimental** client side caching for MultiplexedConnection (optional)
111//! * `disable-client-setinfo`: disables the `CLIENT SETINFO` handshake during connection initialization
112//!
113//! ## Connection Parameters
114//!
115//! redis-rs knows different ways to define where a connection should
116//! go. The parameter to `Client::open` needs to implement the
117//! `IntoConnectionInfo` trait of which there are three implementations:
118//!
119//! * string slices in `redis://` URL format.
120//! * URL objects from the redis-url crate.
121//! * `ConnectionInfo` objects.
122//!
123//! The URL format is `redis://[<username>][:<password>@]<hostname>[:port][/[<db>][?protocol=<protocol>]]`
124//!
125//! If Unix socket support is available you can use a unix URL in this format:
126//!
127//! `redis+unix:///<path>[?db=<db>[&pass=<password>][&user=<username>][&protocol=<protocol>]]`
128//!
129//! For compatibility with some other libraries for Redis, the "unix" scheme
130//! is also supported:
131//!
132//! `unix:///<path>[?db=<db>][&pass=<password>][&user=<username>][&protocol=<protocol>]]`
133//!
134//! ## Executing Low-Level Commands
135//!
136//! To execute low-level commands you can use the `cmd` function which allows
137//! you to build redis requests. Once you have configured a command object
138//! to your liking you can send a query into any `ConnectionLike` object:
139//!
140//! ```rust,no_run
141//! fn do_something(con: &mut redis::Connection) -> redis::RedisResult<()> {
142//! redis::cmd("SET").arg("my_key").arg(42).exec(con)?;
143//! Ok(())
144//! }
145//! ```
146//!
147//! Upon querying the return value is a result object. If you do not care
148//! about the actual return value (other than that it is not a failure)
149//! you can always type annotate it to the unit type `()`.
150//!
151//! Note that commands with a sub-command (like "MEMORY USAGE", "ACL WHOAMI",
152//! "LATENCY HISTORY", etc) must specify the sub-command as a separate `arg`:
153//!
154//! ```rust,no_run
155//! fn do_something(con: &mut redis::Connection) -> redis::RedisResult<usize> {
156//! // This will result in a server error: "unknown command `MEMORY USAGE`"
157//! // because "USAGE" is technically a sub-command of "MEMORY".
158//! redis::cmd("MEMORY USAGE").arg("my_key").query::<usize>(con)?;
159//!
160//! // However, this will work as you'd expect
161//! redis::cmd("MEMORY").arg("USAGE").arg("my_key").query(con)
162//! }
163//! ```
164//!
165//! ## Executing High-Level Commands
166//!
167//! The high-level interface is similar. For it to become available you
168//! need to use the `Commands` trait in which case all `ConnectionLike`
169//! objects the library provides will also have high-level methods which
170//! make working with the protocol easier:
171//!
172//! ```rust,no_run
173//! extern crate redis;
174//! use redis::Commands;
175//!
176//! fn do_something(con: &mut redis::Connection) -> redis::RedisResult<()> {
177//! let _: () = con.set("my_key", 42)?;
178//! Ok(())
179//! }
180//! ```
181//!
182//! Note that high-level commands are work in progress and many are still
183//! missing!
184//!
185//! ## Type Conversions
186//!
187//! Because redis inherently is mostly type-less and the protocol is not
188//! exactly friendly to developers, this library provides flexible support
189//! for casting values to the intended results. This is driven through the `FromRedisValue` and `ToRedisArgs` traits.
190//!
191//! The `arg` method of the command will accept a wide range of types through
192//! the `ToRedisArgs` trait and the `query` method of a command can convert the
193//! value to what you expect the function to return through the `FromRedisValue`
194//! trait. This is quite flexible and allows vectors, tuples, hashsets, hashmaps
195//! as well as optional values:
196//!
197//! ```rust,no_run
198//! # use redis::Commands;
199//! # use std::collections::{HashMap, HashSet};
200//! # fn do_something() -> redis::RedisResult<()> {
201//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
202//! # let mut con = client.get_connection().unwrap();
203//! let count : i32 = con.get("my_counter")?;
204//! let count = con.get("my_counter").unwrap_or(0i32);
205//! let k : Option<String> = con.get("missing_key")?;
206//! let name : String = con.get("my_name")?;
207//! let bin : Vec<u8> = con.get("my_binary")?;
208//! let map : HashMap<String, i32> = con.hgetall("my_hash")?;
209//! let keys : Vec<String> = con.hkeys("my_hash")?;
210//! let mems : HashSet<i32> = con.smembers("my_set")?;
211//! let (k1, k2) : (String, String) = con.get(&["k1", "k2"])?;
212//! # Ok(())
213//! # }
214//! ```
215//!
216//! # RESP3 support
217//! Since Redis / Valkey version 6, a newer communication protocol called RESP3 is supported.
218//! Using this protocol allows the user both to receive a more varied `Value` results, for users
219//! who use the low-level `Value` type, and to receive out of band messages on the same connection. This allows the user to receive PubSub
220//! messages on the same connection, instead of creating a new PubSub connection (see "RESP3 async pubsub").
221//!
222//! # Iteration Protocol
223//!
224//! In addition to sending a single query, iterators are also supported. When
225//! used with regular bulk responses they don't give you much over querying and
226//! converting into a vector (both use a vector internally) but they can also
227//! be used with `SCAN` like commands in which case iteration will send more
228//! queries until the cursor is exhausted:
229//!
230//! ```rust,ignore
231//! # fn do_something() -> redis::RedisResult<()> {
232//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
233//! # let mut con = client.get_connection().unwrap();
234//! let mut iter : redis::Iter<isize> = redis::cmd("SSCAN").arg("my_set")
235//! .cursor_arg(0).clone().iter(&mut con)?;
236//! for x in iter {
237//! // do something with the item
238//! }
239//! # Ok(()) }
240//! ```
241//!
242//! As you can see the cursor argument needs to be defined with `cursor_arg`
243//! instead of `arg` so that the library knows which argument needs updating
244//! as the query is run for more items.
245//!
246//! # Pipelining
247//!
248//! In addition to simple queries you can also send command pipelines. This
249//! is provided through the `pipe` function. It works very similar to sending
250//! individual commands but you can send more than one in one go. This also
251//! allows you to ignore individual results so that matching on the end result
252//! is easier:
253//!
254//! ```rust,no_run
255//! # fn do_something() -> redis::RedisResult<()> {
256//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
257//! # let mut con = client.get_connection().unwrap();
258//! let (k1, k2) : (i32, i32) = redis::pipe()
259//! .cmd("SET").arg("key_1").arg(42).ignore()
260//! .cmd("SET").arg("key_2").arg(43).ignore()
261//! .cmd("GET").arg("key_1")
262//! .cmd("GET").arg("key_2").query(&mut con)?;
263//! # Ok(()) }
264//! ```
265//!
266//! If you want the pipeline to be wrapped in a `MULTI`/`EXEC` block you can
267//! easily do that by switching the pipeline into `atomic` mode. From the
268//! caller's point of view nothing changes, the pipeline itself will take
269//! care of the rest for you:
270//!
271//! ```rust,no_run
272//! # fn do_something() -> redis::RedisResult<()> {
273//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
274//! # let mut con = client.get_connection().unwrap();
275//! let (k1, k2) : (i32, i32) = redis::pipe()
276//! .atomic()
277//! .cmd("SET").arg("key_1").arg(42).ignore()
278//! .cmd("SET").arg("key_2").arg(43).ignore()
279//! .cmd("GET").arg("key_1")
280//! .cmd("GET").arg("key_2").query(&mut con)?;
281//! # Ok(()) }
282//! ```
283//!
284//! You can also use high-level commands on pipelines:
285//!
286//! ```rust,no_run
287//! # fn do_something() -> redis::RedisResult<()> {
288//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
289//! # let mut con = client.get_connection().unwrap();
290//! let (k1, k2) : (i32, i32) = redis::pipe()
291//! .atomic()
292//! .set("key_1", 42).ignore()
293//! .set("key_2", 43).ignore()
294//! .get("key_1")
295//! .get("key_2").query(&mut con)?;
296//! # Ok(()) }
297//! ```
298//!
299//! # Transactions
300//!
301//! Transactions are available through atomic pipelines. In order to use
302//! them in a more simple way you can use the `transaction` function of a
303//! connection:
304//!
305//! ```rust,no_run
306//! # fn do_something() -> redis::RedisResult<()> {
307//! use redis::Commands;
308//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
309//! # let mut con = client.get_connection().unwrap();
310//! let key = "the_key";
311//! let (new_val,) : (isize,) = redis::transaction(&mut con, &[key], |con, pipe| {
312//! let old_val : isize = con.get(key)?;
313//! pipe
314//! .set(key, old_val + 1).ignore()
315//! .get(key).query(con)
316//! })?;
317//! println!("The incremented number is: {}", new_val);
318//! # Ok(()) }
319//! ```
320//!
321//! For more information see the `transaction` function.
322//!
323//! # PubSub
324//!
325//! Pubsub is provided through the `PubSub` connection object for sync usage, or the `aio::PubSub`
326//! for async usage.
327//!
328//! Example usage:
329//!
330//! ```rust,no_run
331//! # fn do_something() -> redis::RedisResult<()> {
332//! let client = redis::Client::open("redis://127.0.0.1/")?;
333//! let mut con = client.get_connection()?;
334//! let mut pubsub = con.as_pubsub();
335//! pubsub.subscribe(&["channel_1", "channel_2"])?;
336//!
337//! loop {
338//! let msg = pubsub.get_message()?;
339//! let payload : String = msg.get_payload()?;
340//! println!("channel '{}': {}", msg.get_channel_name(), payload);
341//! }
342//! # }
343//! ```
344//! In order to update subscriptions while concurrently waiting for messages, the async PubSub can be split into separate sink & stream components. The sink can be receive subscription requests while the stream is awaited for messages.
345//!
346//! ```rust,no_run
347//! # #[cfg(feature = "aio")]
348//! use futures_util::StreamExt;
349//! # #[cfg(feature = "aio")]
350//! # async fn do_something() -> redis::RedisResult<()> {
351//! let client = redis::Client::open("redis://127.0.0.1/")?;
352//! let (mut sink, mut stream) = client.get_async_pubsub().await?.split();
353//! sink.subscribe("channel_1").await?;
354//!
355//! loop {
356//! let msg = stream.next().await.unwrap();
357//! let payload : String = msg.get_payload().unwrap();
358//! println!("channel '{}': {}", msg.get_channel_name(), payload);
359//! }
360//! # Ok(()) }
361//! ```
362//!
363//! ## RESP3 async pubsub
364//! If you're targeting a Redis/Valkey server of version 6 or above, you can receive
365//! pubsub messages from it without creating another connection, by setting a push sender on the connection.
366//!
367//! ```rust,no_run
368//! # #[cfg(feature = "aio")]
369//! # {
370//! # use futures::prelude::*;
371//! # use redis::AsyncCommands;
372//!
373//! # async fn func() -> redis::RedisResult<()> {
374//! let client = redis::Client::open("redis://127.0.0.1/?protocol=resp3").unwrap();
375//! let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
376//! let config = redis::AsyncConnectionConfig::new().set_push_sender(tx);
377//! let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
378//! con.subscribe(&["channel_1", "channel_2"]).await?;
379//!
380//! loop {
381//! println!("Received {:?}", rx.recv().await.unwrap());
382//! }
383//! # Ok(()) }
384//! # }
385//! ```
386//!
387#![cfg_attr(
388 feature = "script",
389 doc = r##"
390# Scripts
391
392Lua scripts are supported through the `Script` type in a convenient
393way. It will automatically load the script if it does not exist and invoke it.
394
395Example:
396
397```rust,no_run
398# fn do_something() -> redis::RedisResult<()> {
399# let client = redis::Client::open("redis://127.0.0.1/").unwrap();
400# let mut con = client.get_connection().unwrap();
401let script = redis::Script::new(r"
402 return tonumber(ARGV[1]) + tonumber(ARGV[2]);
403");
404let result: isize = script.arg(1).arg(2).invoke(&mut con)?;
405assert_eq!(result, 3);
406# Ok(()) }
407```
408
409Scripts can also be pipelined:
410
411```rust,no_run
412# fn do_something() -> redis::RedisResult<()> {
413# let client = redis::Client::open("redis://127.0.0.1/").unwrap();
414# let mut con = client.get_connection().unwrap();
415let script = redis::Script::new(r"
416 return tonumber(ARGV[1]) + tonumber(ARGV[2]);
417");
418let (a, b): (isize, isize) = redis::pipe()
419 .invoke_script(script.arg(1).arg(2))
420 .invoke_script(script.arg(2).arg(3))
421 .query(&mut con)?;
422
423assert_eq!(a, 3);
424assert_eq!(b, 5);
425# Ok(()) }
426```
427
428Note: unlike a call to [`invoke`](ScriptInvocation::invoke), if the script isn't loaded during the pipeline operation,
429it will not automatically be loaded and retried. The script can be loaded using the
430[`load`](ScriptInvocation::load) operation.
431"##
432)]
433//!
434#![cfg_attr(
435 feature = "aio",
436 doc = r##"
437# Async
438
439In addition to the synchronous interface that's been explained above there also exists an
440asynchronous interface based on [`futures`][] and [`tokio`][], or [`async-std`][].
441
442This interface exists under the `aio` (async io) module (which requires that the `aio` feature
443is enabled) and largely mirrors the synchronous with a few concessions to make it fit the
444constraints of `futures`.
445
446```rust,no_run
447use futures::prelude::*;
448use redis::AsyncCommands;
449
450# #[tokio::main]
451# async fn main() -> redis::RedisResult<()> {
452let client = redis::Client::open("redis://127.0.0.1/").unwrap();
453let mut con = client.get_multiplexed_async_connection().await?;
454
455let _: () = con.set("key1", b"foo").await?;
456
457redis::cmd("SET").arg(&["key2", "bar"]).exec_async(&mut con).await?;
458
459let result = redis::cmd("MGET")
460 .arg(&["key1", "key2"])
461 .query_async(&mut con)
462 .await;
463assert_eq!(result, Ok(("foo".to_string(), b"bar".to_vec())));
464# Ok(()) }
465```
466"##
467)]
468//!
469//! [`futures`]:https://crates.io/crates/futures
470//! [`tokio`]:https://tokio.rs
471//! [`async-std`]:https://async.rs/
472#![cfg_attr(
473 feature = "sentinel",
474 doc = r##"
475# Sentinel
476Sentinel types allow users to connect to Redis sentinels and find primaries and replicas.
477
478```rust,no_run
479use redis::{ Commands, RedisConnectionInfo };
480use redis::sentinel::{ SentinelServerType, SentinelClient, SentinelNodeConnectionInfo };
481
482let nodes = vec!["redis://127.0.0.1:6379/", "redis://127.0.0.1:6378/", "redis://127.0.0.1:6377/"];
483let mut sentinel = SentinelClient::build(
484 nodes,
485 String::from("primary1"),
486 Some(SentinelNodeConnectionInfo {
487 tls_mode: Some(redis::TlsMode::Insecure),
488 redis_connection_info: None,
489 }),
490 redis::sentinel::SentinelServerType::Master,
491)
492.unwrap();
493
494let primary = sentinel.get_connection().unwrap();
495```
496
497An async API also exists:
498
499```rust,no_run
500use futures::prelude::*;
501use redis::{ Commands, RedisConnectionInfo };
502use redis::sentinel::{ SentinelServerType, SentinelClient, SentinelNodeConnectionInfo };
503
504# #[tokio::main]
505# async fn main() -> redis::RedisResult<()> {
506let nodes = vec!["redis://127.0.0.1:6379/", "redis://127.0.0.1:6378/", "redis://127.0.0.1:6377/"];
507let mut sentinel = SentinelClient::build(
508 nodes,
509 String::from("primary1"),
510 Some(SentinelNodeConnectionInfo {
511 tls_mode: Some(redis::TlsMode::Insecure),
512 redis_connection_info: None,
513 }),
514 redis::sentinel::SentinelServerType::Master,
515)
516.unwrap();
517
518let primary = sentinel.get_async_connection().await.unwrap();
519# Ok(()) }
520"##
521)]
522//!
523
524#![deny(non_camel_case_types)]
525#![warn(missing_docs)]
526#![cfg_attr(docsrs, warn(rustdoc::broken_intra_doc_links))]
527#![cfg_attr(docsrs, feature(doc_cfg))]
528
529// public api
530#[cfg(feature = "aio")]
531pub use crate::client::AsyncConnectionConfig;
532pub use crate::client::Client;
533#[cfg(feature = "cache-aio")]
534pub use crate::cmd::CommandCacheConfig;
535pub use crate::cmd::{cmd, pack_command, pipe, Arg, Cmd, Iter};
536pub use crate::commands::{
537 Commands, ControlFlow, Direction, FlushAllOptions, FlushDbOptions, LposOptions, PubSubCommands,
538 ScanOptions, SetOptions,
539};
540pub use crate::connection::{
541 parse_redis_url, transaction, Connection, ConnectionAddr, ConnectionInfo, ConnectionLike,
542 IntoConnectionInfo, Msg, PubSub, RedisConnectionInfo, TlsMode,
543};
544pub use crate::parser::{parse_redis_value, Parser};
545pub use crate::pipeline::Pipeline;
546
547#[cfg(feature = "script")]
548#[cfg_attr(docsrs, doc(cfg(feature = "script")))]
549pub use crate::script::{Script, ScriptInvocation};
550
551// preserve grouping and order
552#[rustfmt::skip]
553pub use crate::types::{
554 // utility functions
555 from_redis_value,
556 from_owned_redis_value,
557
558 // error kinds
559 ErrorKind,
560 RetryMethod,
561
562 // conversion traits
563 FromRedisValue,
564
565 // utility types
566 InfoDict,
567 NumericBehavior,
568 Expiry,
569 SetExpiry,
570 ExistenceCheck,
571 ExpireOption,
572 Role,
573 ReplicaInfo,
574
575 // error and result types
576 RedisError,
577 RedisResult,
578 RedisWrite,
579 ToRedisArgs,
580
581 // low level values
582 Value,
583 PushKind,
584 VerbatimFormat,
585 ProtocolVersion,
586 PushInfo
587};
588
589#[cfg(feature = "aio")]
590#[cfg_attr(docsrs, doc(cfg(feature = "aio")))]
591pub use crate::{
592 cmd::AsyncIter, commands::AsyncCommands, parser::parse_redis_value_async, types::RedisFuture,
593};
594
595mod macros;
596mod pipeline;
597
598#[cfg(feature = "acl")]
599#[cfg_attr(docsrs, doc(cfg(feature = "acl")))]
600pub mod acl;
601
602#[cfg(feature = "aio")]
603#[cfg_attr(docsrs, doc(cfg(feature = "aio")))]
604pub mod aio;
605
606#[cfg(feature = "json")]
607#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
608pub use crate::commands::JsonCommands;
609
610#[cfg(all(feature = "json", feature = "aio"))]
611#[cfg_attr(docsrs, doc(cfg(all(feature = "json", feature = "aio"))))]
612pub use crate::commands::JsonAsyncCommands;
613
614#[cfg(feature = "geospatial")]
615#[cfg_attr(docsrs, doc(cfg(feature = "geospatial")))]
616pub mod geo;
617
618#[cfg(any(feature = "connection-manager", feature = "cluster-async"))]
619mod subscription_tracker;
620
621#[cfg(feature = "cluster")]
622mod cluster_topology;
623
624#[cfg(feature = "cluster")]
625#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
626pub mod cluster;
627
628#[cfg(feature = "cluster")]
629#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
630mod cluster_client;
631
632#[cfg(feature = "cluster")]
633#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
634mod cluster_pipeline;
635
636/// Routing information for cluster commands.
637#[cfg(feature = "cluster")]
638#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
639pub mod cluster_routing;
640
641#[cfg(feature = "r2d2")]
642#[cfg_attr(docsrs, doc(cfg(feature = "r2d2")))]
643mod r2d2;
644
645#[cfg(all(feature = "bb8", feature = "aio"))]
646#[cfg_attr(docsrs, doc(cfg(all(feature = "bb8", feature = "aio"))))]
647mod bb8;
648
649#[cfg(feature = "streams")]
650#[cfg_attr(docsrs, doc(cfg(feature = "streams")))]
651pub mod streams;
652
653#[cfg(feature = "cluster-async")]
654#[cfg_attr(docsrs, doc(cfg(all(feature = "cluster", feature = "aio"))))]
655pub mod cluster_async;
656
657#[cfg(feature = "sentinel")]
658#[cfg_attr(docsrs, doc(cfg(feature = "sentinel")))]
659pub mod sentinel;
660
661#[cfg(feature = "tls-rustls")]
662mod tls;
663
664#[cfg(feature = "tls-rustls")]
665#[cfg_attr(docsrs, doc(cfg(feature = "tls-rustls")))]
666pub use crate::tls::{ClientTlsConfig, TlsCertificates};
667
668#[cfg(feature = "cache-aio")]
669#[cfg_attr(docsrs, doc(cfg(feature = "cache-aio")))]
670pub mod caching;
671
672mod client;
673mod cmd;
674mod commands;
675mod connection;
676/// Module for defining I/O behavior.
677pub mod io;
678mod parser;
679mod script;
680mod types;