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, ConnectionManager and async ClusterConnection (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//! ## Pre-typed Commands
218//!
219//! In some cases, you may not have a desired return type for a high-level command, and would
220//! instead like to use defaults provided by the library, to avoid the clutter and development overhead
221//! of specifying types for each command.
222//!
223//! The library facilitates this by providing the `TypedCommands` and `AsyncTypedCommands`
224//! as alternatives to `Commands` and `AsyncCommands` respectively. These traits provide functions
225//! with pre-defined and opinionated return types. For example, `set` returns `()`, avoiding the need
226//! for developers to explicitly type each call as returning `()`.
227//!
228//! ```rust,no_run
229//! use redis::TypedCommands;
230//!
231//! fn fetch_an_integer() -> redis::RedisResult<isize> {
232//!     // connect to redis
233//!     let client = redis::Client::open("redis://127.0.0.1/")?;
234//!     let mut con = client.get_connection()?;
235//!     // `set` returns a `()`, so we don't need to specify the return type manually unlike in the previous example.
236//!     con.set("my_key", 42)?;
237//!     // `get_int` returns Result<Option<isize>>, as the key may not be found, or some error may occur.
238//!     Ok(con.get_int("my_key").unwrap().unwrap())
239//! }
240//! ```
241//!
242//! # RESP3 support
243//! Since Redis / Valkey version 6, a newer communication protocol called RESP3 is supported.
244//! Using this protocol allows the user both to receive a more varied `Value` results, for users
245//! 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
246//! messages on the same connection, instead of creating a new PubSub connection (see "RESP3 async pubsub").
247//!
248//! # Iteration Protocol
249//!
250//! In addition to sending a single query, iterators are also supported.  When
251//! used with regular bulk responses they don't give you much over querying and
252//! converting into a vector (both use a vector internally) but they can also
253//! be used with `SCAN` like commands in which case iteration will send more
254//! queries until the cursor is exhausted:
255//!
256//! ```rust,ignore
257//! # fn do_something() -> redis::RedisResult<()> {
258//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
259//! # let mut con = client.get_connection().unwrap();
260//! let mut iter : redis::Iter<isize> = redis::cmd("SSCAN").arg("my_set")
261//!     .cursor_arg(0).clone().iter(&mut con)?;
262//! for x in iter {
263//!     // do something with the item
264//! }
265//! # Ok(()) }
266//! ```
267//!
268//! As you can see the cursor argument needs to be defined with `cursor_arg`
269//! instead of `arg` so that the library knows which argument needs updating
270//! as the query is run for more items.
271//!
272//! # Pipelining
273//!
274//! In addition to simple queries you can also send command pipelines.  This
275//! is provided through the `pipe` function.  It works very similar to sending
276//! individual commands but you can send more than one in one go.  This also
277//! allows you to ignore individual results so that matching on the end result
278//! is easier:
279//!
280//! ```rust,no_run
281//! # fn do_something() -> redis::RedisResult<()> {
282//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
283//! # let mut con = client.get_connection().unwrap();
284//! let (k1, k2) : (i32, i32) = redis::pipe()
285//!     .cmd("SET").arg("key_1").arg(42).ignore()
286//!     .cmd("SET").arg("key_2").arg(43).ignore()
287//!     .cmd("GET").arg("key_1")
288//!     .cmd("GET").arg("key_2").query(&mut con)?;
289//! # Ok(()) }
290//! ```
291//!
292//! If you want the pipeline to be wrapped in a `MULTI`/`EXEC` block you can
293//! easily do that by switching the pipeline into `atomic` mode.  From the
294//! caller's point of view nothing changes, the pipeline itself will take
295//! care of the rest for you:
296//!
297//! ```rust,no_run
298//! # fn do_something() -> redis::RedisResult<()> {
299//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
300//! # let mut con = client.get_connection().unwrap();
301//! let (k1, k2) : (i32, i32) = redis::pipe()
302//!     .atomic()
303//!     .cmd("SET").arg("key_1").arg(42).ignore()
304//!     .cmd("SET").arg("key_2").arg(43).ignore()
305//!     .cmd("GET").arg("key_1")
306//!     .cmd("GET").arg("key_2").query(&mut con)?;
307//! # Ok(()) }
308//! ```
309//!
310//! You can also use high-level commands on pipelines:
311//!
312//! ```rust,no_run
313//! # fn do_something() -> redis::RedisResult<()> {
314//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
315//! # let mut con = client.get_connection().unwrap();
316//! let (k1, k2) : (i32, i32) = redis::pipe()
317//!     .atomic()
318//!     .set("key_1", 42).ignore()
319//!     .set("key_2", 43).ignore()
320//!     .get("key_1")
321//!     .get("key_2").query(&mut con)?;
322//! # Ok(()) }
323//! ```
324//!
325//! # Transactions
326//!
327//! Transactions are available through atomic pipelines.  In order to use
328//! them in a more simple way you can use the `transaction` function of a
329//! connection:
330//!
331//! ```rust,no_run
332//! # fn do_something() -> redis::RedisResult<()> {
333//! use redis::Commands;
334//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
335//! # let mut con = client.get_connection().unwrap();
336//! let key = "the_key";
337//! let (new_val,) : (isize,) = redis::transaction(&mut con, &[key], |con, pipe| {
338//!     let old_val : isize = con.get(key)?;
339//!     pipe
340//!         .set(key, old_val + 1).ignore()
341//!         .get(key).query(con)
342//! })?;
343//! println!("The incremented number is: {}", new_val);
344//! # Ok(()) }
345//! ```
346//!
347//! For more information see the `transaction` function.
348//!
349//! # PubSub
350//!
351//! Pubsub is provided through the `PubSub` connection object for sync usage, or the `aio::PubSub`
352//! for async usage.
353//!
354//! Example usage:
355//!
356//! ```rust,no_run
357//! # fn do_something() -> redis::RedisResult<()> {
358//! let client = redis::Client::open("redis://127.0.0.1/")?;
359//! let mut con = client.get_connection()?;
360//! let mut pubsub = con.as_pubsub();
361//! pubsub.subscribe(&["channel_1", "channel_2"])?;
362//!
363//! loop {
364//!     let msg = pubsub.get_message()?;
365//!     let payload : String = msg.get_payload()?;
366//!     println!("channel '{}': {}", msg.get_channel_name(), payload);
367//! }
368//! # }
369//! ```
370//! 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.
371//!
372//! ```rust,no_run
373//! # #[cfg(feature = "aio")]
374//! use futures_util::StreamExt;
375//! # #[cfg(feature = "aio")]
376//! # async fn do_something() -> redis::RedisResult<()> {
377//! let client = redis::Client::open("redis://127.0.0.1/")?;
378//! let (mut sink, mut stream) = client.get_async_pubsub().await?.split();
379//! sink.subscribe("channel_1").await?;
380//!
381//! loop {
382//!     let msg = stream.next().await.unwrap();
383//!     let payload : String = msg.get_payload().unwrap();
384//!     println!("channel '{}': {}", msg.get_channel_name(), payload);
385//! }
386//! # Ok(()) }
387//! ```
388//!
389//! ## RESP3 async pubsub
390//! If you're targeting a Redis/Valkey server of version 6 or above, you can receive
391//! pubsub messages from it without creating another connection, by setting a push sender on the connection.
392//!
393//! ```rust,no_run
394//! # #[cfg(feature = "aio")]
395//! # {
396//! # use futures::prelude::*;
397//! # use redis::AsyncCommands;
398//!
399//! # async fn func() -> redis::RedisResult<()> {
400//! let client = redis::Client::open("redis://127.0.0.1/?protocol=resp3").unwrap();
401//! let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
402//! let config = redis::AsyncConnectionConfig::new().set_push_sender(tx);
403//! let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
404//! con.subscribe(&["channel_1", "channel_2"]).await?;
405//!
406//! loop {
407//!   println!("Received {:?}", rx.recv().await.unwrap());
408//! }
409//! # Ok(()) }
410//! # }
411//! ```
412//!
413#![cfg_attr(
414    feature = "script",
415    doc = r##"
416# Scripts
417
418Lua scripts are supported through the `Script` type in a convenient
419way.  It will automatically load the script if it does not exist and invoke it.
420
421Example:
422
423```rust,no_run
424# fn do_something() -> redis::RedisResult<()> {
425# let client = redis::Client::open("redis://127.0.0.1/").unwrap();
426# let mut con = client.get_connection().unwrap();
427let script = redis::Script::new(r"
428    return tonumber(ARGV[1]) + tonumber(ARGV[2]);
429");
430let result: isize = script.arg(1).arg(2).invoke(&mut con)?;
431assert_eq!(result, 3);
432# Ok(()) }
433```
434
435Scripts can also be pipelined:
436
437```rust,no_run
438# fn do_something() -> redis::RedisResult<()> {
439# let client = redis::Client::open("redis://127.0.0.1/").unwrap();
440# let mut con = client.get_connection().unwrap();
441let script = redis::Script::new(r"
442    return tonumber(ARGV[1]) + tonumber(ARGV[2]);
443");
444let (a, b): (isize, isize) = redis::pipe()
445    .invoke_script(script.arg(1).arg(2))
446    .invoke_script(script.arg(2).arg(3))
447    .query(&mut con)?;
448
449assert_eq!(a, 3);
450assert_eq!(b, 5);
451# Ok(()) }
452```
453
454Note: unlike a call to [`invoke`](ScriptInvocation::invoke), if the script isn't loaded during the pipeline operation,
455it will not automatically be loaded and retried. The script can be loaded using the
456[`load`](ScriptInvocation::load) operation.
457"##
458)]
459//!
460#![cfg_attr(
461    feature = "aio",
462    doc = r##"
463# Async
464
465In addition to the synchronous interface that's been explained above there also exists an
466asynchronous interface based on [`futures`][] and [`tokio`][], [`smol`](https://docs.rs/smol/latest/smol/), or [`async-std`][].
467
468This interface exists under the `aio` (async io) module (which requires that the `aio` feature
469is enabled) and largely mirrors the synchronous with a few concessions to make it fit the
470constraints of `futures`.
471
472```rust,no_run
473use futures::prelude::*;
474use redis::AsyncCommands;
475
476# #[tokio::main]
477# async fn main() -> redis::RedisResult<()> {
478let client = redis::Client::open("redis://127.0.0.1/").unwrap();
479let mut con = client.get_multiplexed_async_connection().await?;
480
481let _: () = con.set("key1", b"foo").await?;
482
483redis::cmd("SET").arg(&["key2", "bar"]).exec_async(&mut con).await?;
484
485let result = redis::cmd("MGET")
486 .arg(&["key1", "key2"])
487 .query_async(&mut con)
488 .await;
489assert_eq!(result, Ok(("foo".to_string(), b"bar".to_vec())));
490# Ok(()) }
491```
492
493## Runtime support
494The crate supports multiple runtimes, including `tokio`, `async-std`, and `smol`. For Tokio, the crate will
495spawn tasks on the current thread runtime. For async-std & smol, the crate will spawn tasks on the the global runtime.
496It is recommended that the crate be used with support only for a single runtime. If the crate is compiled with multiple runtimes,
497the user should call [`crate::aio::prefer_tokio`], [`crate::aio::prefer_async_std`] or [`crate::aio::prefer_smol`] to set the preferred runtime.
498These functions set global state which automatically chooses the correct runtime for the async connection.
499
500"##
501)]
502//!
503//! [`futures`]:https://crates.io/crates/futures
504//! [`tokio`]:https://tokio.rs
505//! [`async-std`]:https://async.rs/
506#![cfg_attr(
507    feature = "sentinel",
508    doc = r##"
509# Sentinel
510Sentinel types allow users to connect to Redis sentinels and find primaries and replicas.
511
512```rust,no_run
513use redis::{ Commands, RedisConnectionInfo };
514use redis::sentinel::{ SentinelServerType, SentinelClient, SentinelNodeConnectionInfo };
515
516let nodes = vec!["redis://127.0.0.1:6379/", "redis://127.0.0.1:6378/", "redis://127.0.0.1:6377/"];
517let mut sentinel = SentinelClient::build(
518    nodes,
519    String::from("primary1"),
520    Some(SentinelNodeConnectionInfo {
521        tls_mode: Some(redis::TlsMode::Insecure),
522        redis_connection_info: None,
523    }),
524    redis::sentinel::SentinelServerType::Master,
525)
526.unwrap();
527
528let primary = sentinel.get_connection().unwrap();
529```
530
531An async API also exists:
532
533```rust,no_run
534use futures::prelude::*;
535use redis::{ Commands, RedisConnectionInfo };
536use redis::sentinel::{ SentinelServerType, SentinelClient, SentinelNodeConnectionInfo };
537
538# #[tokio::main]
539# async fn main() -> redis::RedisResult<()> {
540let nodes = vec!["redis://127.0.0.1:6379/", "redis://127.0.0.1:6378/", "redis://127.0.0.1:6377/"];
541let mut sentinel = SentinelClient::build(
542    nodes,
543    String::from("primary1"),
544    Some(SentinelNodeConnectionInfo {
545        tls_mode: Some(redis::TlsMode::Insecure),
546        redis_connection_info: None,
547    }),
548    redis::sentinel::SentinelServerType::Master,
549)
550.unwrap();
551
552let primary = sentinel.get_async_connection().await.unwrap();
553# Ok(()) }
554"##
555)]
556//!
557
558#![deny(non_camel_case_types)]
559#![warn(missing_docs)]
560#![cfg_attr(docsrs, warn(rustdoc::broken_intra_doc_links))]
561// When on docs.rs we want to show tuple variadics in the docs.
562// This currently requires internal/unstable features in Rustdoc.
563#![cfg_attr(
564    docsrs,
565    feature(doc_cfg, rustdoc_internals),
566    expect(
567        internal_features,
568        reason = "rustdoc_internals is needed for fake_variadic"
569    )
570)]
571
572// public api
573#[cfg(feature = "aio")]
574pub use crate::client::AsyncConnectionConfig;
575pub use crate::client::Client;
576#[cfg(feature = "cache-aio")]
577pub use crate::cmd::CommandCacheConfig;
578pub use crate::cmd::{cmd, pack_command, pipe, Arg, Cmd, Iter};
579pub use crate::commands::{
580    Commands, ControlFlow, CopyOptions, Direction, FlushAllOptions, FlushDbOptions,
581    HashFieldExpirationOptions, LposOptions, PubSubCommands, ScanOptions, SetOptions,
582    TypedCommands,
583};
584pub use crate::connection::{
585    parse_redis_url, transaction, Connection, ConnectionAddr, ConnectionInfo, ConnectionLike,
586    IntoConnectionInfo, Msg, PubSub, RedisConnectionInfo, TlsMode,
587};
588pub use crate::parser::{parse_redis_value, Parser};
589pub use crate::pipeline::Pipeline;
590
591#[cfg(feature = "script")]
592#[cfg_attr(docsrs, doc(cfg(feature = "script")))]
593pub use crate::script::{Script, ScriptInvocation};
594
595// preserve grouping and order
596#[rustfmt::skip]
597pub use crate::types::{
598    // utility functions
599    from_redis_value,
600    from_owned_redis_value,
601    make_extension_error,
602
603    // error kinds
604    ErrorKind,
605    RetryMethod,
606
607    // conversion traits
608    FromRedisValue,
609
610    // utility types
611    InfoDict,
612    NumericBehavior,
613    Expiry,
614    SetExpiry,
615    ExistenceCheck,
616    FieldExistenceCheck,
617    ExpireOption,
618    Role,
619    ReplicaInfo,
620    IntegerReplyOrNoOp,
621	ValueType,
622
623    // error and result types
624    RedisError,
625    RedisResult,
626    RedisWrite,
627    ToRedisArgs,
628
629    // low level values
630    Value,
631    PushKind,
632    VerbatimFormat,
633    ProtocolVersion,
634    PushInfo,
635};
636
637#[cfg(feature = "aio")]
638#[cfg_attr(docsrs, doc(cfg(feature = "aio")))]
639pub use crate::{
640    cmd::AsyncIter, commands::AsyncCommands, commands::AsyncTypedCommands,
641    parser::parse_redis_value_async, types::RedisFuture,
642};
643
644mod macros;
645mod pipeline;
646
647#[cfg(feature = "acl")]
648#[cfg_attr(docsrs, doc(cfg(feature = "acl")))]
649pub mod acl;
650
651#[cfg(feature = "aio")]
652#[cfg_attr(docsrs, doc(cfg(feature = "aio")))]
653pub mod aio;
654
655#[cfg(feature = "json")]
656#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
657pub use crate::commands::JsonCommands;
658
659#[cfg(all(feature = "json", feature = "aio"))]
660#[cfg_attr(docsrs, doc(cfg(all(feature = "json", feature = "aio"))))]
661pub use crate::commands::JsonAsyncCommands;
662
663#[cfg(feature = "geospatial")]
664#[cfg_attr(docsrs, doc(cfg(feature = "geospatial")))]
665pub mod geo;
666
667#[cfg(any(feature = "connection-manager", feature = "cluster-async"))]
668mod subscription_tracker;
669
670#[cfg(feature = "cluster")]
671mod cluster_topology;
672
673#[cfg(feature = "cluster")]
674#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
675pub mod cluster;
676
677#[cfg(feature = "cluster")]
678#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
679mod cluster_client;
680
681#[cfg(feature = "cluster")]
682#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
683mod cluster_pipeline;
684
685/// Routing information for cluster commands.
686#[cfg(feature = "cluster")]
687#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
688pub mod cluster_routing;
689
690#[cfg(feature = "r2d2")]
691#[cfg_attr(docsrs, doc(cfg(feature = "r2d2")))]
692mod r2d2;
693
694#[cfg(all(feature = "bb8", feature = "aio"))]
695#[cfg_attr(docsrs, doc(cfg(all(feature = "bb8", feature = "aio"))))]
696mod bb8;
697
698#[cfg(feature = "streams")]
699#[cfg_attr(docsrs, doc(cfg(feature = "streams")))]
700pub mod streams;
701
702#[cfg(feature = "cluster-async")]
703#[cfg_attr(docsrs, doc(cfg(all(feature = "cluster", feature = "aio"))))]
704pub mod cluster_async;
705
706#[cfg(feature = "sentinel")]
707#[cfg_attr(docsrs, doc(cfg(feature = "sentinel")))]
708pub mod sentinel;
709
710#[cfg(feature = "tls-rustls")]
711mod tls;
712
713#[cfg(feature = "tls-rustls")]
714#[cfg_attr(docsrs, doc(cfg(feature = "tls-rustls")))]
715pub use crate::tls::{ClientTlsConfig, TlsCertificates};
716
717#[cfg(feature = "cache-aio")]
718#[cfg_attr(docsrs, doc(cfg(feature = "cache-aio")))]
719pub mod caching;
720
721mod client;
722mod cmd;
723mod commands;
724mod connection;
725/// Module for defining I/O behavior.
726pub mod io;
727mod parser;
728mod script;
729mod types;
730
731#[cfg(test)]
732mod tests {
733    use super::*;
734    #[test]
735    fn test_is_send() {
736        const fn assert_send<T: Send>() {}
737
738        assert_send::<Connection>();
739        #[cfg(feature = "cluster")]
740        assert_send::<cluster::ClusterConnection>();
741    }
742}