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