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`, `smol-native-tls-comp`, or `smol-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 setting [io::tcp::TcpSettings] on the connection configuration objects,
38//! and set the TCP parameters in a more specific manner there.
39//!
40//! ## Connection Handling
41//!
42//! For connecting to redis you can use a client object which then can produce
43//! actual connections. Connections and clients as well as results of
44//! connections and clients are considered [ConnectionLike] objects and
45//! can be used anywhere a request is made.
46//!
47//! The full canonical way to get a connection is to create a client and
48//! to ask for a connection from it:
49//!
50//! ```rust,no_run
51//! extern crate redis;
52//!
53//! fn do_something() -> redis::RedisResult<()> {
54//! let client = redis::Client::open("redis://127.0.0.1/")?;
55//! let mut con = client.get_connection()?;
56//!
57//! /* do something here */
58//!
59//! Ok(())
60//! }
61//! ```
62//!
63//! ## Connection Pooling
64//!
65//! When using a sync connection, it is recommended to use a connection pool in order to handle
66//! disconnects or multi-threaded usage. This can be done using the `r2d2` feature.
67//!
68//! ```rust,no_run
69//! # #[cfg(feature = "r2d2")]
70//! # fn do_something() {
71//! use redis::TypedCommands;
72//!
73//! let client = redis::Client::open("redis://127.0.0.1/").unwrap();
74//! let pool = r2d2::Pool::builder().build(client).unwrap();
75//! let mut conn = pool.get().unwrap();
76//!
77//! conn.set("KEY", "VALUE").unwrap();
78//! let val = conn.get("KEY").unwrap();
79//! # }
80//! ```
81//!
82//! For async connections, connection pooling isn't necessary. The `MultiplexedConnection` is
83//! cheap to clone and can be used safely concurrently from multiple threads, so a single connection can be easily
84//! reused. For automatic reconnections consider using `ConnectionManager` with the `connection-manager` feature.
85//! Async cluster connections also don't require pooling and are thread-safe and reusable.
86//!
87//! ## Optional Features
88//!
89//! There are a few features defined that can enable additional functionality
90//! if so desired. Some of them are turned on by default.
91//!
92//! * `acl`: enables acl support (enabled by default)
93//! * `bloom`: enables support for the Bloom filter module (optional)
94//! * `tokio-comp`: enables support for async usage with the Tokio runtime (optional)
95//! * `smol-comp`: enables support for async usage with the Smol runtime (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//! * `bb8`: enables bb8 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//! * `rust_decimal`, `bigdecimal`, `num-bigint`: enables type conversions to large number representation from different crates (optional)
106//! * `uuid`: enables type conversion to UUID (optional)
107//! * `sentinel`: enables high-level interfaces for communication with Redis sentinels (optional)
108//! * `json`: enables high-level interfaces for communication with the JSON module (optional)
109//! * `search_unfinished`: enables high-level interfaces for communication with the Search module (optional) NOTE: Currently, this feature is incomplete and should be considered a work in progress.
110//! * `cache-aio`: enables **experimental** client side caching for MultiplexedConnection, ConnectionManager and async ClusterConnection (optional)
111//!
112//! ## Connection Parameters
113//!
114//! redis-rs knows different ways to define where a connection should
115//! go. The parameter to [Client::open] needs to implement the
116//! [IntoConnectionInfo] trait of which there are three implementations:
117//!
118//! * string slices in `redis://` URL format.
119//! * URL objects from the redis-url crate.
120//! * [ConnectionInfo] objects.
121//!
122//! The URL format is `redis://[<username>][:<password>@]<hostname>[:port][/[<db>][?protocol=<protocol>]]`
123//!
124//! If Unix socket support is available you can use a unix URL in this format:
125//!
126//! `redis+unix:///<path>[?db=<db>[&pass=<password>][&user=<username>][&protocol=<protocol>]]`
127//!
128//! For compatibility with some other libraries for Redis, the "unix" scheme
129//! is also supported:
130//!
131//! `unix:///<path>[?db=<db>][&pass=<password>][&user=<username>][&protocol=<protocol>]]`
132//!
133//! ## Executing Low-Level Commands
134//!
135//! To execute low-level commands you can use the [cmd::cmd] function which allows
136//! you to build redis requests. Once you have configured a command object
137//! to your liking you can send a query into any [ConnectionLike] object:
138//!
139//! ```rust,no_run
140//! fn do_something(con: &mut redis::Connection) -> redis::RedisResult<()> {
141//! redis::cmd("SET").arg("my_key").arg(42).exec(con)?;
142//! Ok(())
143//! }
144//! ```
145//!
146//! Upon querying the return value is a result object. If you do not care
147//! about the actual return value (other than that it is not a failure)
148//! you can always type annotate it to the unit type `()`.
149//!
150//! Note that commands with a sub-command (like "MEMORY USAGE", "ACL WHOAMI",
151//! "LATENCY HISTORY", etc) must specify the sub-command as a separate `arg`:
152//!
153//! ```rust,no_run
154//! fn do_something(con: &mut redis::Connection) -> redis::RedisResult<usize> {
155//! // This will result in a server error: "unknown command `MEMORY USAGE`"
156//! // because "USAGE" is technically a sub-command of "MEMORY".
157//! redis::cmd("MEMORY USAGE").arg("my_key").query::<usize>(con)?;
158//!
159//! // However, this will work as you'd expect
160//! redis::cmd("MEMORY").arg("USAGE").arg("my_key").query(con)
161//! }
162//! ```
163//!
164//! ## Executing High-Level Commands
165//!
166//! The high-level interface is similar. For it to become available you
167//! need to use the `TypedCommands` or `Commands` traits in which case all `ConnectionLike`
168//! objects the library provides will also have high-level methods which
169//! make working with the protocol easier:
170//!
171//! ```rust,no_run
172//! extern crate redis;
173//! use redis::TypedCommands;
174//!
175//! fn do_something(con: &mut redis::Connection) -> redis::RedisResult<()> {
176//! con.set("my_key", 42)?;
177//! Ok(())
178//! }
179//! ```
180//!
181//! Note that high-level commands are work in progress and many are still
182//! missing!
183//!
184//! ## Pre-typed Commands
185//!
186//! Because redis inherently is mostly type-less and the protocol is not
187//! exactly friendly to developers, this library provides flexible support
188//! for casting values to the intended results. This is driven through the [FromRedisValue] and [ToRedisArgs] traits.
189//!
190//! In most cases, you may like to use defaults provided by the library, to avoid the clutter and development overhead
191//! of specifying types for each command.
192//!
193//! The library facilitates this by providing the [commands::TypedCommands] and [commands::AsyncTypedCommands]. These traits provide functions
194//! with pre-defined and opinionated return types. For example, `set` returns `()`, avoiding the need
195//! for developers to explicitly type each call as returning `()`.
196//!
197//! ```rust,no_run
198//! use redis::TypedCommands;
199//!
200//! fn fetch_an_integer() -> redis::RedisResult<isize> {
201//! // connect to redis
202//! let client = redis::Client::open("redis://127.0.0.1/")?;
203//! let mut con = client.get_connection()?;
204//! // `set` returns a `()`, so we don't need to specify the return type manually unlike in the previous example.
205//! con.set("my_key", 42)?;
206//! // `get_int` returns Result<Option<isize>>, as the key may not be found, or some error may occur.
207//! Ok(con.get_int("my_key").unwrap().unwrap())
208//! }
209//! ```
210//!
211//! ## Custom Type Conversions
212//!
213//! In some cases, the user might want to define their own return value types to various Redis calls.
214//! The library facilitates this by providing the [commands::Commands] and [commands::AsyncCommands]
215//! as alternatives to [commands::TypedCommands] and [commands::AsyncTypedCommands] respectively.
216//!
217//! The `arg` method of the command will accept a wide range of types through
218//! the [ToRedisArgs] trait and the `query` method of a command can convert the
219//! value to what you expect the function to return through the [FromRedisValue]
220//! trait. This is quite flexible and allows vectors, tuples, hashsets, hashmaps
221//! as well as optional values:
222//!
223//! ```rust,no_run
224//! # use redis::Commands;
225//! # use std::collections::{HashMap, HashSet};
226//! # fn do_something() -> redis::RedisResult<()> {
227//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
228//! # let mut con = client.get_connection().unwrap();
229//! let count : i32 = con.get("my_counter")?;
230//! let count = con.get("my_counter").unwrap_or(0i32);
231//! let k : Option<String> = con.get("missing_key")?;
232//! let name : String = con.get("my_name")?;
233//! let bin : Vec<u8> = con.get("my_binary")?;
234//! let map : HashMap<String, i32> = con.hgetall("my_hash")?;
235//! let keys : Vec<String> = con.hkeys("my_hash")?;
236//! let mems : HashSet<i32> = con.smembers("my_set")?;
237//! let (k1, k2) : (String, String) = con.mget(&["k1", "k2"])?;
238//! # Ok(())
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
249//!
250//! ## RESP3 pubsub
251//! If you're targeting a Redis/Valkey server of version 6 or above, you can receive
252//! pubsub messages from it without creating another connection, by setting a push sender on the connection.
253//!
254//! ```rust,no_run
255//! # #[cfg(feature = "aio")]
256//! # {
257//! # use futures::prelude::*;
258//! # use redis::AsyncTypedCommands;
259//!
260//! # async fn func() -> redis::RedisResult<()> {
261//! let client = redis::Client::open("redis://127.0.0.1/?protocol=resp3").unwrap();
262//! let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
263//! let config = redis::AsyncConnectionConfig::new().set_push_sender(tx);
264//! let mut con = client.get_multiplexed_async_connection_with_config(&config).await?;
265//! con.subscribe(&["channel_1", "channel_2"]).await?;
266//!
267//! loop {
268//! println!("Received {:?}", rx.recv().await.unwrap());
269//! }
270//! # Ok(()) }
271//! # }
272//! ```
273//!
274//! sync example:
275//!
276//! ```rust,no_run
277//! # {
278//! # use redis::TypedCommands;
279//!
280//! # async fn func() -> redis::RedisResult<()> {
281//! let client = redis::Client::open("redis://127.0.0.1/?protocol=resp3").unwrap();
282//! let (tx, rx) = std::sync::mpsc::channel();
283//! let mut con = client.get_connection().unwrap();
284//! con.set_push_sender(tx);
285//! con.subscribe_resp3(&["channel_1", "channel_2"])?;
286//!
287//! loop {
288//! std::thread::sleep(std::time::Duration::from_millis(10));
289//! // the connection only reads when actively polled, so it must constantly send and receive requests.
290//! _ = con.ping().unwrap();
291//! println!("Received {:?}", rx.try_recv().unwrap());
292//! }
293//! # Ok(()) }
294//! # }
295//! ```
296//!
297//! # Iteration Protocol
298//!
299//! In addition to sending a single query, iterators are also supported. When
300//! used with regular bulk responses they don't give you much over querying and
301//! converting into a vector (both use a vector internally) but they can also
302//! be used with `SCAN` like commands in which case iteration will send more
303//! queries until the cursor is exhausted:
304//!
305//! ```rust,ignore
306//! # fn do_something() -> redis::RedisResult<()> {
307//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
308//! # let mut con = client.get_connection().unwrap();
309//! let mut iter : redis::Iter<isize> = redis::cmd("SSCAN").arg("my_set")
310//! .cursor_arg(0).clone().iter(&mut con)?;
311//! for x in iter {
312//! // do something with the item
313//! }
314//! # Ok(()) }
315//! ```
316//!
317//! As you can see the cursor argument needs to be defined with `cursor_arg`
318//! instead of `arg` so that the library knows which argument needs updating
319//! as the query is run for more items.
320//!
321//! # Pipelining
322//!
323//! In addition to simple queries you can also send command pipelines. This
324//! is provided through the `pipe` function. It works very similar to sending
325//! individual commands but you can send more than one in one go. This also
326//! allows you to ignore individual results so that matching on the end result
327//! is easier:
328//!
329//! ```rust,no_run
330//! # fn do_something() -> redis::RedisResult<()> {
331//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
332//! # let mut con = client.get_connection().unwrap();
333//! let (k1, k2) : (i32, i32) = redis::pipe()
334//! .cmd("SET").arg("key_1").arg(42).ignore()
335//! .cmd("SET").arg("key_2").arg(43).ignore()
336//! .cmd("GET").arg("key_1")
337//! .cmd("GET").arg("key_2").query(&mut con)?;
338//! # Ok(()) }
339//! ```
340//!
341//! If you want the pipeline to be wrapped in a `MULTI`/`EXEC` block you can
342//! easily do that by switching the pipeline into `atomic` mode. From the
343//! caller's point of view nothing changes, the pipeline itself will take
344//! care of the rest for you:
345//!
346//! ```rust,no_run
347//! # fn do_something() -> redis::RedisResult<()> {
348//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
349//! # let mut con = client.get_connection().unwrap();
350//! let (k1, k2) : (i32, i32) = redis::pipe()
351//! .atomic()
352//! .cmd("SET").arg("key_1").arg(42).ignore()
353//! .cmd("SET").arg("key_2").arg(43).ignore()
354//! .cmd("GET").arg("key_1")
355//! .cmd("GET").arg("key_2").query(&mut con)?;
356//! # Ok(()) }
357//! ```
358//!
359//! You can also use high-level commands on pipelines:
360//!
361//! ```rust,no_run
362//! # fn do_something() -> redis::RedisResult<()> {
363//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
364//! # let mut con = client.get_connection().unwrap();
365//! let (k1, k2) : (i32, i32) = redis::pipe()
366//! .atomic()
367//! .set("key_1", 42).ignore()
368//! .set("key_2", 43).ignore()
369//! .get("key_1")
370//! .get("key_2").query(&mut con)?;
371//! # Ok(()) }
372//! ```
373//!
374//! NOTE: Pipelines return a collection of results, even when there's only a single response.
375//! Make sure to wrap single-result pipeline responses in a collection. For example:
376//!
377//! ```rust,no_run
378//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
379//! # let mut con = client.get_connection().unwrap();
380//! let (k1,): (i32,) = redis::pipe()
381//! .cmd("SET").arg("key_1").arg(42).ignore()
382//! .cmd("GET").arg("key_1").query(&mut con).unwrap();
383//! ```
384//!
385//! # Transactions
386//!
387//! Transactions are available through atomic pipelines. In order to use
388//! them in a more simple way you can use the `transaction` function of a
389//! connection:
390//!
391//! ```rust,no_run
392//! # fn do_something() -> redis::RedisResult<()> {
393//! use redis::Commands;
394//! # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
395//! # let mut con = client.get_connection().unwrap();
396//! let key = "the_key";
397//! let (new_val,) : (isize,) = redis::transaction(&mut con, &[key], |con, pipe| {
398//! let old_val : isize = con.get(key)?;
399//! pipe
400//! .set(key, old_val + 1).ignore()
401//! .get(key).query(con)
402//! })?;
403//! println!("The incremented number is: {}", new_val);
404//! # Ok(()) }
405//! ```
406//!
407//! For more information see the `transaction` function.
408//!
409//! # PubSub
410//!
411//! Pubsub is provided through the `PubSub` connection object for sync usage, or the `aio::PubSub`
412//! for async usage.
413//!
414//! Example usage:
415//!
416//! ```rust,no_run
417//! # fn do_something() -> redis::RedisResult<()> {
418//! let client = redis::Client::open("redis://127.0.0.1/")?;
419//! let mut con = client.get_connection()?;
420//! let mut pubsub = con.as_pubsub();
421//! pubsub.subscribe(&["channel_1", "channel_2"])?;
422//!
423//! loop {
424//! let msg = pubsub.get_message()?;
425//! let payload : String = msg.get_payload()?;
426//! println!("channel '{}': {}", msg.get_channel_name(), payload);
427//! }
428//! # }
429//! ```
430//! 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.
431//!
432//! ```rust,no_run
433//! # #[cfg(feature = "aio")]
434//! use futures_util::StreamExt;
435//! # #[cfg(feature = "aio")]
436//! # async fn do_something() -> redis::RedisResult<()> {
437//! let client = redis::Client::open("redis://127.0.0.1/")?;
438//! let (mut sink, mut stream) = client.get_async_pubsub().await?.split();
439//! sink.subscribe("channel_1").await?;
440//!
441//! loop {
442//! let msg = stream.next().await.unwrap();
443//! let payload : String = msg.get_payload().unwrap();
444//! println!("channel '{}': {}", msg.get_channel_name(), payload);
445//! }
446//! # Ok(()) }
447//! ```
448//!
449#![deny(clippy::disallowed_macros)]
450#![cfg_attr(test, allow(clippy::disallowed_macros))]
451#![cfg_attr(
452 feature = "script",
453 doc = r##"
454# Scripts
455
456Lua scripts are supported through the `Script` type in a convenient
457way. It will automatically load the script if it does not exist and invoke it.
458
459Example:
460
461```rust,no_run
462# fn do_something() -> redis::RedisResult<()> {
463# let client = redis::Client::open("redis://127.0.0.1/").unwrap();
464# let mut con = client.get_connection().unwrap();
465let script = redis::Script::new(r"
466 return tonumber(ARGV[1]) + tonumber(ARGV[2]);
467");
468let result: isize = script.arg(1).arg(2).invoke(&mut con)?;
469assert_eq!(result, 3);
470# Ok(()) }
471```
472
473Scripts can also be pipelined:
474
475```rust,no_run
476# fn do_something() -> redis::RedisResult<()> {
477# let client = redis::Client::open("redis://127.0.0.1/").unwrap();
478# let mut con = client.get_connection().unwrap();
479let script = redis::Script::new(r"
480 return tonumber(ARGV[1]) + tonumber(ARGV[2]);
481");
482let (a, b): (isize, isize) = redis::pipe()
483 .invoke_script(script.arg(1).arg(2))
484 .invoke_script(script.arg(2).arg(3))
485 .query(&mut con)?;
486
487assert_eq!(a, 3);
488assert_eq!(b, 5);
489# Ok(()) }
490```
491
492Note: unlike a call to [`invoke`](ScriptInvocation::invoke), if the script isn't loaded during the pipeline operation,
493it will not automatically be loaded and retried. The script can be loaded using the
494[`load`](ScriptInvocation::load) operation.
495"##
496)]
497//!
498#![cfg_attr(
499 feature = "aio",
500 doc = r##"
501# Async
502
503In addition to the synchronous interface that's been explained above there also exists an
504asynchronous interface based on [`futures`][] and [`tokio`][] or [`smol`](https://docs.rs/smol/latest/smol/).
505 All async connections are cheap to clone, and clones can be used concurrently from multiple threads.
506
507This interface exists under the `aio` (async io) module (which requires that the `aio` feature
508is enabled) and largely mirrors the synchronous with a few concessions to make it fit the
509constraints of `futures`.
510
511```rust,no_run
512use futures::prelude::*;
513use redis::AsyncTypedCommands;
514
515# #[tokio::main]
516# async fn main() -> redis::RedisResult<()> {
517let client = redis::Client::open("redis://127.0.0.1/").unwrap();
518let mut con = client.get_multiplexed_async_connection().await?;
519
520con.set("key1", b"foo").await?;
521
522redis::cmd("SET").arg(&["key2", "bar"]).exec_async(&mut con).await?;
523
524let result = redis::cmd("MGET")
525 .arg(&["key1", "key2"])
526 .query_async(&mut con)
527 .await;
528assert_eq!(result, Ok(("foo".to_string(), b"bar".to_vec())));
529# Ok(()) }
530```
531
532## Runtime support
533The crate supports multiple runtimes, including `tokio` and `smol`. For Tokio, the crate will
534spawn tasks on the current thread runtime. For smol, the crate will spawn tasks on the the global runtime.
535It is recommended that the crate be used with support only for a single runtime. If the crate is compiled with multiple runtimes,
536the user should call [`crate::aio::prefer_tokio`] or [`crate::aio::prefer_smol`] to set the preferred runtime.
537These functions set global state which automatically chooses the correct runtime for the async connection.
538
539"##
540)]
541//!
542//! [`futures`]:https://crates.io/crates/futures
543//! [`tokio`]:https://tokio.rs
544#![cfg_attr(
545 feature = "sentinel",
546 doc = r##"
547# Sentinel
548Sentinel types allow users to connect to Redis sentinels and find primaries and replicas.
549
550```rust,no_run
551use redis::{ Commands, RedisConnectionInfo };
552use redis::sentinel::{ SentinelServerType, SentinelClient, SentinelNodeConnectionInfo };
553
554let nodes = vec!["redis://127.0.0.1:6379/", "redis://127.0.0.1:6378/", "redis://127.0.0.1:6377/"];
555let sentinel_node_connection_info = SentinelNodeConnectionInfo::default()
556 .set_tls_mode(redis::TlsMode::Insecure);
557let mut sentinel = SentinelClient::build(
558 nodes,
559 String::from("primary1"),
560 Some(sentinel_node_connection_info),
561 redis::sentinel::SentinelServerType::Master,
562)
563.unwrap();
564
565let primary = sentinel.get_connection().unwrap();
566```
567
568An async API also exists:
569
570```rust,no_run
571use futures::prelude::*;
572use redis::{ Commands, RedisConnectionInfo };
573use redis::sentinel::{ SentinelServerType, SentinelClient, SentinelNodeConnectionInfo };
574
575# #[tokio::main]
576# async fn main() -> redis::RedisResult<()> {
577let nodes = vec!["redis://127.0.0.1:6379/", "redis://127.0.0.1:6378/", "redis://127.0.0.1:6377/"];
578let sentinel_node_connection_info = SentinelNodeConnectionInfo::default()
579 .set_tls_mode(redis::TlsMode::Insecure);
580let mut sentinel = SentinelClient::build(
581 nodes,
582 String::from("primary1"),
583 Some(sentinel_node_connection_info),
584 redis::sentinel::SentinelServerType::Master,
585)
586.unwrap();
587
588let primary = sentinel.get_async_connection().await.unwrap();
589# Ok(()) }
590```
591"##
592)]
593//!
594//! # Testing
595//!
596//! The [`redis-test`](https://docs.rs/redis-test) crate provides tools for testing Redis clients.
597//! It includes a `MockRedisConnection` for unit testing without a real Redis server,
598//! as well as helpers like `RedisCluster` and `RedisSentinelCluster` to easily spin up
599//! local Redis clusters and Sentinels for integration tests.
600//!
601//! # Upgrading to version 1
602//!
603//! * Iterators are now safe by default, without an opt out. This means that the iterators return `RedisResult<Value>` instead of `Value`. See [this PR](https://github.com/redis-rs/redis-rs/pull/1641) for background. If you previously used the "safe_iterators" feature to opt-in to this behavior, just remove the feature declaration. Otherwise you will need to adjust your usage of iterators to account for potential conversion failures.
604//! * Parsing values using [FromRedisValue] no longer returns [RedisError] on failure, in order to save the users checking for various server & client errors in such scenarios. if you rely on the error type when using this trait, you will need to adjust your error handling code. [ParsingError] should only be printed, since it does not contain any user actionable info outside of its error message.
605//! * If you used the `tcp_nodelay` or `keep-alive` features, you'll need to set these values on the connection info you pass to the client use [ConnectionInfo::set_tcp_settings].
606//! * If you used the `disable-client-setinfo` features, you'll need to set [RedisConnectionInfo::skip_set_lib_name].
607//! * If you create [ConnectionInfo], [RedisConnectionInfo], or [sentinel::SentinelNodeConnectionInfo] objects explicitly, now you need to use the builder pattern setters instead of setting fields.
608//! * if you used `MultiplexedConnection::new_with_response_timeout`, it is replaced by [aio::MultiplexedConnection::new_with_config]. `Client::get_multiplexed_tokio_connection_with_response_timeouts`, `Client::get_multiplexed_tokio_connection`, `Client::create_multiplexed_tokio_connection_with_response_timeout`, `Client::create_multiplexed_tokio_connection` were replaced by [Client::get_multiplexed_async_connection_with_config].
609//! * If you're using `tokio::time::pause()` or otherwise manipulating time, you might need to opt out of timeouts using `AsyncConnectionConfig::new().set_connection_timeout(None).set_response_timeout(None)`.
610//! * Async connections now have default timeouts. If you're using blocking commands or other potentially long running commands, you should adjust the timeouts accordingly.
611//! * If you're manually setting `ConnectionManager`'s retry setting, then please re-examine the values you set. `exponential_base` has been made a f32, and `factor` was replaced by `min_delay`, in order to match the documented behavior, instead of the actual erroneous behavior of past versions.
612//! * Vector set types have been moved into the `vector_sets` module, instead of being exposed directly.
613//! * ErrorKind::TypeError was renamed ErrorKind::UnexpectedReturnType, to clarify its meaning. Also fixed some cases where it and ErrorKind::Parse were used interchangeably.
614//! * Connecting to a wildcard address (`0.0.0.0` or `::`) is now explicitly disallowed and will return an error. This change prevents connection timeouts and provides a clearer error message. This affects both standalone and cluster connections. Users relying on this behavior should now connect to a specific, non-wildcard address.
615//! * If you implemented [crate::FromRedisValue] directly, or used `FromRedisValue::from_redis_value`/`FromRedisValue::from_owned_redis_value`, notice that the trait's semantics changed - now the trait requires an owned value by default, instead of a reference. See [the PR](https://github.com/redis-rs/redis-rs/pull/1784) for details.
616//! * The implicit replacement of `GET` with `MGET` or `SET` with `MSET` has been replaced, and limited these and other commands to only take values that serialize into single redis values, as enforced by a compilation failure. Example:
617//!
618//! ```rust,no_run,compile_fail
619//! use redis::Commands;
620//! fn main() -> redis::RedisResult<()> {
621//! let client = redis::Client::open("redis://127.0.0.1/")?;
622//! let mut con = client.get_connection()?;
623//! // `get` should fail compilation, because it receives multiple values
624//! _ = con.get(["foo","bar"]);
625//! Ok(())
626//! }
627//! ```
628//!
629//!
630
631#![deny(non_camel_case_types)]
632#![warn(missing_docs)]
633#![cfg_attr(docsrs, warn(rustdoc::broken_intra_doc_links))]
634// When on docs.rs we want to show tuple variadics in the docs.
635// This currently requires internal/unstable features in Rustdoc.
636#![cfg_attr(
637 docsrs,
638 feature(doc_cfg, rustdoc_internals),
639 expect(
640 internal_features,
641 reason = "rustdoc_internals is needed for fake_variadic"
642 )
643)]
644#![cfg_attr(not(test), forbid(clippy::print_stdout))]
645#![cfg_attr(not(test), forbid(clippy::panic))]
646#![cfg_attr(not(test), forbid(clippy::infinite_loop))]
647// #![cfg_attr(not(test), forbid(clippy::cast_possible_truncation))]
648
649// public api
650#[cfg(feature = "aio")]
651pub use crate::client::AsyncConnectionConfig;
652pub use crate::client::Client;
653#[cfg(feature = "cache-aio")]
654pub use crate::cmd::CommandCacheConfig;
655pub use crate::cmd::{Arg, Cmd, Iter, cmd, pack_command, pipe};
656pub use crate::commands::{
657 Commands, ControlFlow, CopyOptions, Direction, FlushAllOptions, FlushDbOptions,
658 HashFieldExpirationOptions, HotkeysCommands, LposOptions, MSetOptions, PubSubCommands,
659 ScanOptions, SetOptions, SortedSetAddOptions, TypedCommands, UpdateCheck,
660 hotkeys::{
661 HOTKEYS_COUNT_MAX, HOTKEYS_COUNT_MIN, HotKeyEntry, HotkeysOptions, HotkeysResponse,
662 SlotRange,
663 },
664};
665pub use crate::connection::{
666 Connection, ConnectionAddr, ConnectionInfo, ConnectionLike, IntoConnectionInfo, Msg, PubSub,
667 RedisConnectionInfo, TlsMode, parse_redis_url, transaction,
668};
669pub use crate::parser::{Parser, parse_redis_value};
670pub use crate::pipeline::Pipeline;
671#[cfg(feature = "script")]
672#[cfg_attr(docsrs, doc(cfg(feature = "script")))]
673pub use crate::script::{Script, ScriptInvocation};
674#[cfg(feature = "token-based-authentication")]
675pub use crate::{
676 auth::{BasicAuth, StreamingCredentialsProvider},
677 auth_management::{RetryConfig, TokenRefreshConfig},
678};
679#[cfg(feature = "entra-id")]
680pub use {
681 crate::entra_id::{ClientCertificate, EntraIdCredentialsProvider, REDIS_SCOPE_DEFAULT},
682 azure_identity::{
683 ClientCertificateCredentialOptions, ClientSecretCredentialOptions,
684 DeveloperToolsCredentialOptions, ManagedIdentityCredentialOptions, UserAssignedId,
685 },
686};
687
688// preserve grouping and order
689#[rustfmt::skip]
690pub use crate::types::{
691 // utility functions
692 from_redis_value_ref,
693 from_redis_value,
694
695 // conversion traits
696 FromRedisValue,
697
698 // utility types
699 InfoDict,
700 NumericBehavior,
701 Expiry,
702 SetExpiry,
703 ExistenceCheck,
704 FieldExistenceCheck,
705 ExpireOption,
706 Role,
707 ReplicaInfo,
708 IntegerReplyOrNoOp,
709 ValueType,
710 RedisResult,
711 RedisWrite,
712 ToRedisArgs,
713 ToSingleRedisArg,
714 ValueComparison,
715
716 // low level values
717 Value,
718 PushKind,
719 VerbatimFormat,
720 ProtocolVersion,
721 PushInfo,
722};
723
724pub use crate::types::{calculate_value_digest, is_valid_16_bytes_hex_digest};
725
726pub use crate::errors::{
727 ErrorKind, ParsingError, RedisError, RetryMethod, ServerError, ServerErrorKind,
728 make_extension_error,
729};
730
731#[cfg(feature = "aio")]
732#[cfg_attr(docsrs, doc(cfg(feature = "aio")))]
733pub use crate::{
734 cmd::AsyncIter, commands::AsyncCommands, commands::AsyncTypedCommands,
735 parser::parse_redis_value_async, types::RedisFuture,
736};
737
738mod macros;
739mod pipeline;
740
741#[cfg(feature = "acl")]
742#[cfg_attr(docsrs, doc(cfg(feature = "acl")))]
743pub use commands::acl;
744
745#[cfg(feature = "aio")]
746#[cfg_attr(docsrs, doc(cfg(feature = "aio")))]
747pub mod aio;
748
749#[cfg(feature = "bloom")]
750#[cfg_attr(docsrs, doc(cfg(feature = "bloom")))]
751pub mod bloom;
752
753#[cfg(feature = "json")]
754#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
755pub use crate::commands::{JsonCommands, json};
756
757#[cfg(all(feature = "json", feature = "aio"))]
758#[cfg_attr(docsrs, doc(cfg(all(feature = "json", feature = "aio"))))]
759pub use crate::commands::JsonAsyncCommands;
760
761#[cfg(feature = "aio")]
762#[cfg_attr(docsrs, doc(cfg(feature = "aio")))]
763pub use crate::commands::AsyncHotkeysCommands;
764
765#[cfg(feature = "vector-sets")]
766#[cfg_attr(docsrs, doc(cfg(feature = "vector-sets")))]
767pub use crate::commands::vector_sets;
768
769#[cfg(feature = "search_unfinished")]
770#[cfg_attr(docsrs, doc(cfg(feature = "search_unfinished")))]
771pub use crate::commands::search;
772
773#[cfg(feature = "geospatial")]
774#[cfg_attr(docsrs, doc(cfg(feature = "geospatial")))]
775pub use commands::geo;
776
777#[cfg(any(feature = "connection-manager", feature = "cluster-async"))]
778mod subscription_tracker;
779
780#[cfg(feature = "cluster")]
781mod cluster_handling;
782
783#[cfg(feature = "cluster")]
784#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
785pub use cluster_handling::sync_connection as cluster;
786
787/// Routing information for cluster commands.
788#[cfg(feature = "cluster")]
789#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
790pub use cluster_handling::routing as cluster_routing;
791
792/// Pluggable read routing strategies for cluster connections.
793#[cfg(feature = "cluster")]
794#[cfg_attr(docsrs, doc(cfg(feature = "cluster")))]
795pub use cluster_handling::read_routing as cluster_read_routing;
796
797#[cfg(feature = "r2d2")]
798#[cfg_attr(docsrs, doc(cfg(feature = "r2d2")))]
799mod r2d2;
800
801#[cfg(all(feature = "bb8", feature = "aio"))]
802#[cfg_attr(docsrs, doc(cfg(all(feature = "bb8", feature = "aio"))))]
803mod bb8;
804
805#[cfg(feature = "streams")]
806#[cfg_attr(docsrs, doc(cfg(feature = "streams")))]
807pub use commands::streams;
808
809#[cfg(feature = "cluster-async")]
810#[cfg_attr(docsrs, doc(cfg(all(feature = "cluster", feature = "aio"))))]
811pub use cluster_handling::async_connection as cluster_async;
812
813#[cfg(feature = "sentinel")]
814#[cfg_attr(docsrs, doc(cfg(feature = "sentinel")))]
815pub mod sentinel;
816
817#[cfg(feature = "tls-rustls")]
818mod tls;
819
820#[cfg(feature = "tls-rustls")]
821#[cfg_attr(docsrs, doc(cfg(feature = "tls-rustls")))]
822pub use crate::tls::{ClientTlsConfig, TlsCertificates};
823
824#[cfg(feature = "cache-aio")]
825#[cfg_attr(docsrs, doc(cfg(feature = "cache-aio")))]
826pub mod caching;
827
828#[cfg(feature = "entra-id")]
829#[cfg_attr(docsrs, doc(cfg(feature = "entra-id")))]
830pub mod entra_id;
831
832#[cfg(feature = "token-based-authentication")]
833#[cfg_attr(docsrs, doc(cfg(feature = "token-based-authentication")))]
834pub mod auth;
835#[cfg(feature = "token-based-authentication")]
836#[cfg_attr(docsrs, doc(cfg(feature = "token-based-authentication")))]
837pub mod auth_management;
838
839mod client;
840mod cmd;
841mod commands;
842mod connection;
843mod errors;
844/// Module for defining I/O behavior.
845pub mod io;
846mod parser;
847mod script;
848mod types;
849
850macro_rules! check_resp3 {
851 ($protocol: expr) => {
852 if !$protocol.supports_resp3() {
853 return Err(RedisError::from((
854 crate::ErrorKind::InvalidClientConfig,
855 "RESP3 is required for this command",
856 )));
857 }
858 };
859
860 ($protocol: expr, $message: expr) => {
861 if !$protocol.supports_resp3() {
862 return Err(RedisError::from((
863 crate::ErrorKind::InvalidClientConfig,
864 $message,
865 )));
866 }
867 };
868}
869
870pub(crate) use check_resp3;
871
872#[cfg(test)]
873mod tests {
874 use super::*;
875 #[test]
876 fn test_is_send() {
877 const fn assert_send<T: Send>() {}
878
879 assert_send::<Connection>();
880 #[cfg(feature = "cluster")]
881 assert_send::<cluster::ClusterConnection>();
882 }
883}