redis_server_wrapper/lib.rs
1#![warn(missing_docs)]
2//! Type-safe wrapper for `redis-server` and `redis-cli` with builder pattern APIs.
3//!
4//! Manage Redis server processes for testing, development, and CI.
5//! No Docker required -- just `redis-server` and `redis-cli` on PATH.
6//!
7//! # Overview
8//!
9//! This crate provides Rust builders that launch real Redis processes and manage
10//! their lifecycle. Servers are started on [`RedisServer::start`] and
11//! automatically stopped when the returned handle is dropped. Three topologies
12//! are supported:
13//!
14//! | Topology | Builder | Handle |
15//! |----------|---------|--------|
16//! | Standalone | [`RedisServer`] | [`RedisServerHandle`] |
17//! | Cluster | [`RedisClusterBuilder`] | [`RedisClusterHandle`] |
18//! | Sentinel | [`RedisSentinelBuilder`] | [`RedisSentinelHandle`] |
19//!
20//! # Prerequisites
21//!
22//! `redis-server` and `redis-cli` must be on your `PATH`, or you can point to
23//! custom binaries with `.redis_server_bin()` and `.redis_cli_bin()` on any
24//! builder.
25//!
26//! # Quick Start
27//!
28//! ```no_run
29//! use redis_server_wrapper::RedisServer;
30//!
31//! # async fn example() {
32//! let server = RedisServer::new()
33//! .port(6400)
34//! .bind("127.0.0.1")
35//! .start()
36//! .await
37//! .unwrap();
38//!
39//! assert!(server.is_alive().await);
40//! // Stopped automatically on Drop.
41//! # }
42//! ```
43//!
44//! # Configuration
45//!
46//! Every Redis configuration directive can be passed through the builder.
47//! Common options have dedicated methods; anything else goes through
48//! [`RedisServer::extra`]:
49//!
50//! ```no_run
51//! use redis_server_wrapper::{LogLevel, RedisServer};
52//!
53//! # async fn example() {
54//! let server = RedisServer::new()
55//! .port(6400)
56//! .bind("127.0.0.1")
57//! .password("secret")
58//! .loglevel(LogLevel::Warning)
59//! .appendonly(true)
60//! .extra("maxmemory", "256mb")
61//! .extra("maxmemory-policy", "allkeys-lru")
62//! .start()
63//! .await
64//! .unwrap();
65//! # }
66//! ```
67//!
68//! # Running Commands
69//!
70//! The handle exposes a [`RedisCli`] that you can use to run arbitrary
71//! commands against the server:
72//!
73//! ```no_run
74//! use redis_server_wrapper::RedisServer;
75//!
76//! # async fn example() {
77//! let server = RedisServer::new().port(6400).start().await.unwrap();
78//!
79//! server.run(&["SET", "key", "value"]).await.unwrap();
80//! let val = server.run(&["GET", "key"]).await.unwrap();
81//! assert_eq!(val.trim(), "value");
82//! # }
83//! ```
84//!
85//! # Cluster
86//!
87//! Spin up a Redis Cluster with automatic slot assignment. The builder starts
88//! each node, then calls `redis-cli --cluster create` to form the cluster:
89//!
90//! ```no_run
91//! use redis_server_wrapper::RedisCluster;
92//!
93//! # async fn example() {
94//! let cluster = RedisCluster::builder()
95//! .masters(3)
96//! .replicas_per_master(1)
97//! .base_port(7000)
98//! .start()
99//! .await
100//! .unwrap();
101//!
102//! assert!(cluster.is_healthy().await);
103//! assert_eq!(cluster.node_addrs().len(), 6);
104//! # }
105//! ```
106//!
107//! # Sentinel
108//!
109//! Start a full Sentinel topology -- master, replicas, and sentinel processes:
110//!
111//! ```no_run
112//! use redis_server_wrapper::RedisSentinel;
113//!
114//! # async fn example() {
115//! let sentinel = RedisSentinel::builder()
116//! .master_port(6390)
117//! .replicas(2)
118//! .sentinels(3)
119//! .quorum(2)
120//! .start()
121//! .await
122//! .unwrap();
123//!
124//! assert!(sentinel.is_healthy().await);
125//! assert_eq!(sentinel.master_name(), "mymaster");
126//! # }
127//! ```
128//!
129//! # Chaos / Fault Injection
130//!
131//! The [`chaos`] module simulates process-level failures using POSIX signals --
132//! freezing, killing, and partitioning nodes -- for testing how clients handle
133//! timeouts and failovers. [`fault_proxy`] operates at the byte level instead,
134//! injecting delay, mid-frame drops, and chunked writes into the TCP connection
135//! itself via [`fault_proxy::FaultProxy`]:
136//!
137//! ```no_run
138//! use redis_server_wrapper::{RedisServer, chaos};
139//! use std::time::Duration;
140//!
141//! # async fn example() {
142//! let server = RedisServer::new().port(6400).start().await.unwrap();
143//!
144//! // Freeze the node for 2 seconds, then resume it automatically.
145//! chaos::pause_node(&server, Duration::from_secs(2));
146//! # }
147//! ```
148//!
149//! # Error Handling
150//!
151//! All fallible operations return [`Result<T>`], which uses the crate's
152//! [`Error`] type. Variants cover server start failures, timeouts, CLI errors,
153//! and the underlying I/O errors:
154//!
155//! ```no_run
156//! use redis_server_wrapper::{Error, RedisServer};
157//!
158//! # async fn example() {
159//! match RedisServer::new().port(6400).start().await {
160//! Ok(server) => println!("running on {}", server.addr()),
161//! Err(Error::ServerStart { port }) => eprintln!("could not start on {port}"),
162//! Err(e) => eprintln!("unexpected: {e}"),
163//! }
164//! # }
165//! ```
166//!
167//! # Lifecycle
168//!
169//! All handles implement [`Drop`]. When a handle goes out of scope, it sends
170//! `SHUTDOWN NOSAVE` to the corresponding Redis process. For sentinel
171//! topologies, sentinels are shut down first, then replicas and master (via
172//! their own handle drops).
173//!
174//! You can also call `.stop()` explicitly on any handle to shut down early, or
175//! `.detach()` on a server handle to consume it without stopping the process.
176//!
177//! # Feature Flags
178//!
179//! | Feature | Default | Description |
180//! |---------|---------|-------------|
181//! | `tokio` | yes | Async API. Enables [`RedisServer`], [`RedisCluster`], [`RedisSentinel`]. |
182//! | `blocking` | no | Synchronous wrappers in [`blocking`]. Implies `tokio`. Enable with `features = ["blocking"]`. |
183//! | `test-tls` | no | TLS certificate generation helpers in [`tls`]. Requires `rcgen`. |
184//!
185//! To use only the blocking (sync) API without pulling in async code directly:
186//! ```toml
187//! [dev-dependencies]
188//! redis-server-wrapper = { version = "*", default-features = false, features = ["blocking"] }
189//! ```
190//!
191//! # Platform Support
192//!
193//! This crate only supports Unix-like platforms (Linux, macOS, BSD). Process
194//! lifecycle management in the [`process`] and [`chaos`] modules relies on
195//! POSIX signals (`SIGTERM`, `SIGKILL`, `SIGSTOP`, `SIGCONT`) and Unix
196//! utilities (`kill`, `lsof`) that have no equivalent on Windows. Building on
197//! a non-Unix target fails at compile time rather than silently leaking
198//! processes at runtime.
199
200#[cfg(not(unix))]
201compile_error!(
202 "redis-server-wrapper only supports Unix-like platforms (Linux, macOS, BSD). \
203 Process lifecycle management relies on POSIX signals (SIGTERM/SIGKILL/SIGSTOP/SIGCONT) \
204 and Unix utilities (`kill`, `lsof`) that have no equivalent on Windows."
205);
206
207pub mod chaos;
208#[cfg(feature = "tokio")]
209pub mod cli;
210#[cfg(feature = "tokio")]
211pub mod cluster;
212pub mod error;
213#[cfg(feature = "tokio")]
214pub mod fault_proxy;
215pub mod process;
216#[cfg(feature = "tokio")]
217pub mod sentinel;
218#[cfg(feature = "tokio")]
219pub mod server;
220pub mod stack;
221
222#[cfg(feature = "blocking")]
223pub mod blocking;
224
225#[cfg(feature = "test-tls")]
226pub mod tls;
227
228#[cfg(feature = "tokio")]
229pub use cli::{IpPreference, OutputFormat, RedisCli, RespProtocol};
230#[cfg(feature = "tokio")]
231pub use cluster::{NodeContext, RedisCluster, RedisClusterBuilder, RedisClusterHandle};
232pub use error::{Error, Result};
233#[cfg(feature = "tokio")]
234pub use fault_proxy::{Delay, Direction, FaultProxy};
235#[cfg(feature = "tokio")]
236pub use sentinel::{RedisSentinel, RedisSentinelBuilder, RedisSentinelHandle};
237#[cfg(feature = "tokio")]
238pub use server::{
239 AppendFsync, LogLevel, RedisServer, RedisServerConfig, RedisServerHandle, ReplDisklessLoad,
240 SavePolicy,
241};