Skip to main content

redis_server_wrapper/
lib.rs

1//! Type-safe wrapper for `redis-server` and `redis-cli` with builder pattern APIs.
2//!
3//! Manage Redis server processes for testing, development, and CI.
4//! No Docker required -- just `redis-server` and `redis-cli` on PATH.
5//!
6//! # Overview
7//!
8//! This crate provides Rust builders that launch real Redis processes and manage
9//! their lifecycle. Servers are started on [`RedisServer::start`] and
10//! automatically stopped when the returned handle is dropped. Three topologies
11//! are supported:
12//!
13//! | Topology | Builder | Handle |
14//! |----------|---------|--------|
15//! | Standalone | [`RedisServer`] | [`RedisServerHandle`] |
16//! | Cluster | [`RedisClusterBuilder`] | [`RedisClusterHandle`] |
17//! | Sentinel | [`RedisSentinelBuilder`] | [`RedisSentinelHandle`] |
18//!
19//! # Prerequisites
20//!
21//! `redis-server` and `redis-cli` must be on your `PATH`, or you can point to
22//! custom binaries with `.redis_server_bin()` and `.redis_cli_bin()` on any
23//! builder.
24//!
25//! # Quick Start
26//!
27//! ```no_run
28//! use redis_server_wrapper::RedisServer;
29//!
30//! # async fn example() {
31//! let server = RedisServer::new()
32//!     .port(6400)
33//!     .bind("127.0.0.1")
34//!     .start()
35//!     .await
36//!     .unwrap();
37//!
38//! assert!(server.is_alive().await);
39//! // Stopped automatically on Drop.
40//! # }
41//! ```
42//!
43//! # Configuration
44//!
45//! Every Redis configuration directive can be passed through the builder.
46//! Common options have dedicated methods; anything else goes through
47//! [`RedisServer::extra`]:
48//!
49//! ```no_run
50//! use redis_server_wrapper::{LogLevel, RedisServer};
51//!
52//! # async fn example() {
53//! let server = RedisServer::new()
54//!     .port(6400)
55//!     .bind("127.0.0.1")
56//!     .password("secret")
57//!     .loglevel(LogLevel::Warning)
58//!     .appendonly(true)
59//!     .extra("maxmemory", "256mb")
60//!     .extra("maxmemory-policy", "allkeys-lru")
61//!     .start()
62//!     .await
63//!     .unwrap();
64//! # }
65//! ```
66//!
67//! # Running Commands
68//!
69//! The handle exposes a [`RedisCli`] that you can use to run arbitrary
70//! commands against the server:
71//!
72//! ```no_run
73//! use redis_server_wrapper::RedisServer;
74//!
75//! # async fn example() {
76//! let server = RedisServer::new().port(6400).start().await.unwrap();
77//!
78//! server.run(&["SET", "key", "value"]).await.unwrap();
79//! let val = server.run(&["GET", "key"]).await.unwrap();
80//! assert_eq!(val.trim(), "value");
81//! # }
82//! ```
83//!
84//! # Cluster
85//!
86//! Spin up a Redis Cluster with automatic slot assignment. The builder starts
87//! each node, then calls `redis-cli --cluster create` to form the cluster:
88//!
89//! ```no_run
90//! use redis_server_wrapper::RedisCluster;
91//!
92//! # async fn example() {
93//! let cluster = RedisCluster::builder()
94//!     .masters(3)
95//!     .replicas_per_master(1)
96//!     .base_port(7000)
97//!     .start()
98//!     .await
99//!     .unwrap();
100//!
101//! assert!(cluster.is_healthy().await);
102//! assert_eq!(cluster.node_addrs().len(), 6);
103//! # }
104//! ```
105//!
106//! # Sentinel
107//!
108//! Start a full Sentinel topology -- master, replicas, and sentinel processes:
109//!
110//! ```no_run
111//! use redis_server_wrapper::RedisSentinel;
112//!
113//! # async fn example() {
114//! let sentinel = RedisSentinel::builder()
115//!     .master_port(6390)
116//!     .replicas(2)
117//!     .sentinels(3)
118//!     .quorum(2)
119//!     .start()
120//!     .await
121//!     .unwrap();
122//!
123//! assert!(sentinel.is_healthy().await);
124//! assert_eq!(sentinel.master_name(), "mymaster");
125//! # }
126//! ```
127//!
128//! # Error Handling
129//!
130//! All fallible operations return [`Result<T>`], which uses the crate's
131//! [`Error`] type. Variants cover server start failures, timeouts, CLI errors,
132//! and the underlying I/O errors:
133//!
134//! ```no_run
135//! use redis_server_wrapper::{Error, RedisServer};
136//!
137//! # async fn example() {
138//! match RedisServer::new().port(6400).start().await {
139//!     Ok(server) => println!("running on {}", server.addr()),
140//!     Err(Error::ServerStart { port }) => eprintln!("could not start on {port}"),
141//!     Err(e) => eprintln!("unexpected: {e}"),
142//! }
143//! # }
144//! ```
145//!
146//! # Lifecycle
147//!
148//! All handles implement [`Drop`]. When a handle goes out of scope, it sends
149//! `SHUTDOWN NOSAVE` to the corresponding Redis process. For sentinel
150//! topologies, sentinels are shut down first, then replicas and master (via
151//! their own handle drops).
152//!
153//! You can also call `.stop()` explicitly on any handle to shut down early.
154
155#[cfg(feature = "tokio")]
156pub mod cli;
157#[cfg(feature = "tokio")]
158pub mod cluster;
159pub mod error;
160#[cfg(feature = "tokio")]
161pub mod sentinel;
162#[cfg(feature = "tokio")]
163pub mod server;
164
165#[cfg(feature = "blocking")]
166pub mod blocking;
167
168#[cfg(feature = "tokio")]
169pub use cli::{OutputFormat, RedisCli, RespProtocol};
170#[cfg(feature = "tokio")]
171pub use cluster::{RedisCluster, RedisClusterBuilder, RedisClusterHandle};
172pub use error::{Error, Result};
173#[cfg(feature = "tokio")]
174pub use sentinel::{RedisSentinel, RedisSentinelBuilder, RedisSentinelHandle};
175#[cfg(feature = "tokio")]
176pub use server::{LogLevel, RedisServer, RedisServerConfig, RedisServerHandle};