redis-macros 0.4.3

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

Simple macros and wrappers to [redis-rs](https://github.com/redis-rs/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](#using-other-serializer-eg-serde-yaml)), so add these too to the dependencies.

```toml
[dependencies]
redis-macros = "0.4.3"
redis = { version = "0.27" }
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:

```rust
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](./examples/derive_basic.rs) or [Async](./examples/derive_async.rs) examples.

### Usage with RedisJSON

You can even use it with RedisJSON, to extract separate parts of the object.

```rust
// 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](./examples/derive_redisjson.rs) 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](#json-wrapper-with-redisjson).

```rust
// 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:

```rust
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.

```rust
#[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](./examples/json_wrapper_basic.rs) and [Json Wrapper Advanced](./examples/json_wrapper_modify.rs) 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](https://serde.rs/#data-formats).

```rust
#[derive(Debug, PartialEq, Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
#[redis_serializer(serde_yaml)]
struct User { /* ... */ }
```

For more information, see the [YAML](./examples/derive_yaml.rs) 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](https://github.com/bikeshedder/deadpool). The only change you have to make is to `use` the reexported `redis` package explicitly:

```rust
// 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](./examples/derive_deadpool.rs) example.

## Testing

You can run the unit tests on the code with `cargo test`:

```sh
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](https://hub.docker.com/r/redis/redis-stack) is recommended:

```sh
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:

```sh
rustup component add llvm-tools-preview
cargo install grcov
```

You have to export a few flags to make it work properly:

```sh
export RUSTFLAGS='-Cinstrument-coverage'
export LLVM_PROFILE_FILE='.coverage/cargo-test-%p-%m.profraw'
```

And finally, run the tests and generate the output:

```sh
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.