Search code examples
rustredisserde

Deserializing redis's Value in Rust (FromRedisValue)


I'm implementing a simple task queue using redis in Rust, but am struggling to deserialize the returned values from redis into my custom types.

In total I thought of 3 approches:

  1. Deserializing using serde-redis
  2. Manually implementing the FromRedisValue trait
  3. Serializing to String using serde-json > sending as string > then deserializing from string

The 3rd approach worked but feels artificial. I'd like to figure out either 1 or 2, both of which I'm failing at.

Approach 1 - serde-redis

I have a simple Task definition:

#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Task {
    pub id: u32,
    pub desc: String,
}

which I'm trying to receive into a worker:

use serde_redis::RedisDeserialize;

pub fn execute_task(conn: &mut Connection) {
    let task: Task = redis::cmd("LPOP")
        .arg("task_q")
        .query::<Task>(conn)
        .unwrap()
        .deserialize()
        .unwrap();

    println!("... executing task {} ...", task.id);
}

but I'm getting the following error:

error[E0277]: the trait bound `Task: FromRedisValue` is not satisfied
  --> src/bin/worker.rs:38:10
   |
38 |         .query::<Task>(conn)
   |          ^^^^^ the trait `FromRedisValue` is not implemented for `Task`

error[E0599]: no method named `deserialize` found for struct `Task` in the current scope
  --> src/bin/worker.rs:40:10
   |
40 |         .deserialize()
   |          ^^^^^^^^^^^ method not found in `Task`

So clearly I integrated the crate wrong, as it's not working. The documentation is super brief and the source code way over my head as a beginner - what could I be missing?

Approach 2 - manually implementing FromRedisValue

My naive approach:

impl FromRedisValue for Task {
    fn from_redis_value(v: &Value) -> RedisResult<Self> {
        let t = Task {
            id: v.id,
            desc: v.desc,
        };
        RedisResult::Ok(t)
    }

    fn from_redis_values(items: &[Value]) -> RedisResult<Vec<Self>> {
        let tasks = items
            .into_iter()
            .map(|v| Task {
                id: v.id,
                desc: v.desc,
            })
            .collect();
        RedisResult::Ok(tasks)
    }
}

The errors I'm getting:

error[E0609]: no field `id` on type `&Value`
   --> src/redis_tut.rs:203:19
    |
203 |             id: v.id,
    |                   ^^

error[E0609]: no field `desc` on type `&Value`
   --> src/redis_tut.rs:204:21
    |
204 |             desc: v.desc,
    |                     ^^^^

// ...the same for the vector implementation

So clearly redis's Value doesn't have / know of the fields I want for Task. What's the right way to do this?


Solution

  • Redis doesn't define structured serialization formats. It mostly store strings and integers. So you have to choose or define your format for your struct.

    A popular one is JSON, as you noticed, but if you just want to (de)serialize simple pairs of (id, description), it's not very readable nor convenient.

    In such a case, you can define your own format, for example the id and the description with a dash in between:

    #[derive(Debug, PartialEq)]
    pub struct Task {
        pub id: u32,
        pub desc: String,
    }
    // assume a task is defined as "<id>-<desc>"
    impl FromRedisValue for Task {
        fn from_redis_value(v: &Value) -> RedisResult<Self> {
            let v: String = from_redis_value(v)?;
            if let Some((id, desc)) = v.split_once('-') {
                if let Ok(id) = id.parse() {
                    Ok(Task {
                        id,
                        desc: desc.to_string(),
                    })
                } else {
                    Err((ErrorKind::TypeError, "bad first token").into())
                }
            } else {
                Err((ErrorKind::TypeError, "missing dash").into())
            }
        }
    }