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//! # Error Handling
130//!
131//! All fallible operations return [`Result<T>`], which uses the crate's
132//! [`Error`] type. Variants cover server start failures, timeouts, CLI errors,
133//! and the underlying I/O errors:
134//!
135//! ```no_run
136//! use redis_server_wrapper::{Error, RedisServer};
137//!
138//! # async fn example() {
139//! match RedisServer::new().port(6400).start().await {
140//! Ok(server) => println!("running on {}", server.addr()),
141//! Err(Error::ServerStart { port }) => eprintln!("could not start on {port}"),
142//! Err(e) => eprintln!("unexpected: {e}"),
143//! }
144//! # }
145//! ```
146//!
147//! # Lifecycle
148//!
149//! All handles implement [`Drop`]. When a handle goes out of scope, it sends
150//! `SHUTDOWN NOSAVE` to the corresponding Redis process. For sentinel
151//! topologies, sentinels are shut down first, then replicas and master (via
152//! their own handle drops).
153//!
154//! You can also call `.stop()` explicitly on any handle to shut down early, or
155//! `.detach()` on a server handle to consume it without stopping the process.
156
157#[cfg(feature = "tokio")]
158pub mod cli;
159#[cfg(feature = "tokio")]
160pub mod cluster;
161pub mod error;
162#[cfg(feature = "tokio")]
163pub mod sentinel;
164#[cfg(feature = "tokio")]
165pub mod server;
166
167#[cfg(feature = "blocking")]
168pub mod blocking;
169
170#[cfg(feature = "tokio")]
171pub use cli::{OutputFormat, RedisCli, RespProtocol};
172#[cfg(feature = "tokio")]
173pub use cluster::{RedisCluster, RedisClusterBuilder, RedisClusterHandle};
174pub use error::{Error, Result};
175#[cfg(feature = "tokio")]
176pub use sentinel::{RedisSentinel, RedisSentinelBuilder, RedisSentinelHandle};
177#[cfg(feature = "tokio")]
178pub use server::{LogLevel, RedisServer, RedisServerConfig, RedisServerHandle};