Simple macros and wrappers to redis-rs to automatically serialize and deserialize structs with serde.
To install it, simply add the package redis-macros
. This package is a helper for redis
and uses serde
and serde_json
(or any other serializer), so add these too to the dependencies.
[dependencies]
redis-macros = "0.4.2"
redis = { version = "0.27" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
The simplest way to start is to derive Serialize
, Deserialize
, FromRedisValue
, ToRedisArgs
for any kind of struct... and that's it! You can now get and set these values with regular redis commands:
use redis::{Client, Commands, RedisResult};
use redis_macros::{FromRedisValue, ToRedisArgs};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
enum Address {
Street(String),
Road(String),
}
// Derive the necessary traits
#[derive(Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
struct User {
id: u32,
name: String,
addresses: Vec<Address>,
}
fn main () -> redis::RedisResult<()> {
let client = redis::Client::open("redis://localhost:6379/")?;
let mut con = client.get_connection()?;
let user = User {
id: 1,
name: "Ziggy".to_string(),
addresses: vec![
Address::Street("Downing".to_string()),
Address::Road("Abbey".to_string()),
],
};
// Just use it as you would a primitive
con.set("user", user)?;
// user and stored_user will be the same
let stored_user: User = con.get("user")?;
}
For more information, see the Basic or Async examples.
You can even use it with RedisJSON, to extract separate parts of the object.
// Use `JsonCommands`
use redis::{Client, JsonCommands, RedisResult};
// Derive FromRedisValue, ToRedisArgs to the inner struct
#[derive(Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
enum Address { /* ... */ }
// Simple usage is equivalent to set-get
con.json_set("user", "$", &user)?;
let stored_user: User = con.json_get("user", "$")?;
// But you can get deep values - don't forget to derive traits for these too!
let stored_address: Address = con.json_get("user", "$.addresses[0]")?;
For more information, see the RedisJSON example.
One issue you might be facing is that redis
already has overrides for some types, for example Vec, String and most primitives. For this you have to use the Json wrapper.
// This WON'T work
let stored_addresses: Vec<Address> = con.json_get("user", "$.addresses")?;
To deserialize Vecs and primitive types when using RedisJSON, you cannot use the regular types, because these are non-compatible with RedisJSON. However redis-macros
exports a useful wrapper struct: Json
. When using RedisJSON, you can wrap your non-structs return values into this:
use redis_macros::Json;
// Return type can be wrapped into Json
let Json(stored_name): Json<String> = con.json_get("user", "$.name")?;
// It works with Vecs as well
let Json(stored_addresses): Json<Vec<Address>> = con.json_get("user", "$.addresses")?;
// ...now stored_addresses will be equal to user.addresses
If you only use RedisJSON, you can even do away with deriving FromRedisValue
and ToRedisArgs
, and use Json
everywhere.
#[derive(Serialize, Deserialize)]
struct User { /* ... */ }
// This works with simple redis-rs
con.json_set("user", "$", &user)?;
// ...and you can get back with Json wrapper
let Json(stored_user): Json<User> = con.json_get("user", "$")?;
For more information, see the Json Wrapper and Json Wrapper Advanced examples.
In case you want to use another serializer, for example serde_yaml
, you can install it and use the derives, the same way you would. The only difference should be adding an attribute redis_serializer
under the derive, with the library you want to serialize with. You can use any Serde serializer as long as they support from_str
and to_string
methods. For the full list, see: Serde data formats.
#[derive(Debug, PartialEq, Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
#[redis_serializer(serde_yaml)]
struct User { /* ... */ }
For more information, see the YAML example.
You can still use the macros if you are using a crate that reexports the redis
traits, for example deadpool-redis. The only change you have to make is to use
the reexported redis
package explicitly:
// In the case of deadpool-redis, bring the reexported crate into scope
use deadpool_redis::redis;
// Or if you are importing multiple things from redis, use redis::self
use deadpool_redis::{redis::{self, AsyncCommands}, Config, Runtime};
For more information, see the deadpool-redis example.
You can run the unit tests on the code with cargo test
:
cargo test
For integration testing, you can run the examples. You will need a RedisJSON compatible redis-server on port 6379, redis-stack docker image is recommended:
docker run -d --rm -p 6379:6379 --name redis docker.io/redis/redis-stack
cargo test --examples
# cleanup the container
docker stop redis
For coverage, you can use grcov
. Simply install llvm-tools-preview
and grcov
if you don't have it already:
rustup component add llvm-tools-preview
cargo install grcov
You have to export a few flags to make it work properly:
export RUSTFLAGS='-Cinstrument-coverage'
export LLVM_PROFILE_FILE='.coverage/cargo-test-%p-%m.profraw'
And finally, run the tests and generate the output:
cargo test
cargo test --examples
grcov .coverage/ -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage/
Now you can open ./target/debug/coverage/index.html
, and view it in the browser to see the coverage.