redis-server-wrapper 0.2.0

Type-safe wrapper for redis-server and redis-cli with builder pattern APIs
Documentation

redis-server-wrapper

Crates.io docs.rs CI License

Type-safe Rust wrapper for redis-server and redis-cli with builder pattern APIs.

Manage Redis server processes for testing, development, and CI without Docker -- just redis-server and redis-cli on PATH.

Features

  • Single server -- start/stop with builder pattern, auto-cleanup on drop
  • Cluster -- spin up N-master clusters with optional replicas
  • Sentinel -- full sentinel topology (master + replicas + sentinels)
  • Custom binaries -- point to any redis-server/redis-cli path
  • Arbitrary config -- pass any Redis directive via .extra(key, value)

Prerequisites

redis-server and redis-cli must be available on your PATH (or specify custom paths with .redis_server_bin() and .redis_cli_bin() on any builder).

Installation

Add to Cargo.toml for async use (the default):

[dev-dependencies]
redis-server-wrapper = "0.1"

The tokio feature is enabled by default. To use the synchronous blocking API instead, disable default features and enable blocking:

[dev-dependencies]
redis-server-wrapper = { version = "0.1", default-features = false, features = ["blocking"] }

To use both async and blocking APIs together:

[dev-dependencies]
redis-server-wrapper = { version = "0.1", features = ["blocking"] }

Usage

The async API requires tokio. See the Blocking API section for synchronous use.

Single Server

use redis_server_wrapper::RedisServer;

let server = RedisServer::new()
    .port(6400)
    .bind("127.0.0.1")
    .start()
    .await
    .unwrap();

assert!(server.is_alive().await);
// Stopped automatically on drop.

The server process is stopped via SHUTDOWN NOSAVE when the handle is dropped. Call server.detach() to consume the handle without stopping the process.

Configuration

Common options have dedicated builder methods. Anything else can be passed as a raw Redis directive with .extra(key, value):

use redis_server_wrapper::{LogLevel, RedisServer};

let server = RedisServer::new()
    .port(6400)
    .bind("127.0.0.1")
    .password("secret")
    .loglevel(LogLevel::Warning)
    .appendonly(true)
    .extra("maxmemory", "256mb")
    .extra("maxmemory-policy", "allkeys-lru")
    .start()
    .await
    .unwrap();

Running Commands

The handle exposes a RedisCli you can use to run arbitrary commands against the server:

use redis_server_wrapper::RedisServer;

let server = RedisServer::new().port(6400).start().await.unwrap();

server.run(&["SET", "key", "value"]).await.unwrap();
let val = server.run(&["GET", "key"]).await.unwrap();
assert_eq!(val.trim(), "value");

You can also get a RedisCli instance directly from the handle:

use redis_server_wrapper::RedisServer;

let server = RedisServer::new().port(6400).start().await.unwrap();
let cli = server.cli();
let pong = cli.ping().await;
assert!(pong);

Cluster

use redis_server_wrapper::RedisCluster;

let cluster = RedisCluster::builder()
    .masters(3)
    .replicas_per_master(1)
    .base_port(7000)
    .start()
    .await
    .unwrap();

assert!(cluster.is_healthy().await);

Sentinel

use redis_server_wrapper::RedisSentinel;

let sentinel = RedisSentinel::builder()
    .master_port(6390)
    .replicas(2)
    .sentinels(3)
    .start()
    .await
    .unwrap();

assert!(sentinel.is_healthy().await);

Error Handling

All fallible operations return Result<T, Error>. The Error enum covers server start failures, timeouts, CLI errors, and underlying I/O errors:

use redis_server_wrapper::{Error, RedisServer};

match RedisServer::new().port(6400).start().await {
    Ok(server) => println!("running on {}", server.addr()),
    Err(Error::ServerStart { port }) => eprintln!("could not start on port {port}"),
    Err(Error::BinaryNotFound { binary }) => eprintln!("{binary} not found on PATH"),
    Err(e) => eprintln!("unexpected: {e}"),
}

Blocking API

Enable the blocking feature for synchronous wrappers that require no async runtime:

[dev-dependencies]
redis-server-wrapper = { version = "0.1", features = ["blocking"] }

The blocking module mirrors the async API. Every operation blocks the calling thread until it completes. Handles own a long-lived tokio::runtime::Runtime so that the underlying async Drop implementation keeps working correctly.

use redis_server_wrapper::blocking::RedisServer;

let server = RedisServer::new()
    .port(6400)
    .bind("127.0.0.1")
    .start()
    .unwrap();

assert!(server.is_alive());
// Stopped automatically on drop.

Use server.detach() in the blocking API for the same keep-running behavior.

Cluster and Sentinel work the same way:

use redis_server_wrapper::blocking::{RedisCluster, RedisSentinel};

let cluster = RedisCluster::builder()
    .masters(3)
    .base_port(7000)
    .start()
    .unwrap();

assert!(cluster.is_healthy());

let sentinel = RedisSentinel::builder()
    .master_port(6390)
    .replicas(2)
    .sentinels(3)
    .start()
    .unwrap();

assert!(sentinel.is_healthy());

Examples

The crate ships a runnable example that demonstrates various server configurations:

cargo run --example redis-run

License

Licensed under either of

at your option.