#macro-derive #redis #json #macro #derive

redis-macros

Simple macros and wrappers to redis-rs to automatically serialize and deserialize structs with serde

5 unstable releases

0.3.0 Apr 1, 2024
0.2.1 Jul 4, 2023
0.2.0 Jul 2, 2023
0.1.1 Feb 5, 2023
0.1.0 Jan 30, 2023

#196 in Encoding

Download history 967/week @ 2023-12-23 2256/week @ 2023-12-30 2781/week @ 2024-01-06 3160/week @ 2024-01-13 3338/week @ 2024-01-20 5257/week @ 2024-01-27 2725/week @ 2024-02-03 3073/week @ 2024-02-10 3393/week @ 2024-02-17 4417/week @ 2024-02-24 5217/week @ 2024-03-02 5028/week @ 2024-03-09 3866/week @ 2024-03-16 3017/week @ 2024-03-23 4954/week @ 2024-03-30 3294/week @ 2024-04-06

15,678 downloads per month
Used in vinted-rs

MIT license

22KB
61 lines

redis-macros

Simple macros and wrappers to redis-rs to automatically serialize and deserialize structs with serde.

Installation

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.3.0"
redis = { version = "0.25.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }

Basic usage

Simple usage

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.

Usage with RedisJSON

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")?;

Json wrapper with RedisJSON

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.

Using other serializer (e.g. serde-yaml)

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.

Using deadpool-redis or other crates

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.

Testing

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

Coverage

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.

Dependencies

~0–0.8MB
~21K SLoC