feat(db): serialize to JSON on write and deserialize from JSON on read

Previously, our Redis abstraction only worked with strings because it
only dealt with the somewhat nebulous concept of message metadata. Now
that we also want to use it for concrete types, it makes sense to bake
in automatic serialization from and deserialization to those types. This
frees consumers from the responsibility of having to serialize to a
Redis-friendly format manually.

This change achieves that aim by making the db methods generic. `db.set`
gets a type parameter that is `Serialize` and the getters get one that
is `DeserializeOwned`.
This commit is contained in:
Phil Booth 2018-10-05 18:55:27 +01:00
Родитель 6a43ce4048
Коммит 31da83bb39
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 36FBB106F9C32516
6 изменённых файлов: 36 добавлений и 11 удалений

1
API.md
Просмотреть файл

@ -35,6 +35,7 @@
- `code: 500, errno: 117`: Db Error
- `code: 500, errno: 118`: Not Implemeneted
- `code: 500, errno: 119`: HMAC error
- `code: 500, errno: 120`: JSON error
The following errors include additional response properties:

Просмотреть файл

@ -201,6 +201,10 @@ pub enum AppErrorKind {
/// An error occured while hashing a value.
#[fail(display = "HMAC error: {}", _0)]
HmacError(String),
/// An error occured while serializing or deserializing JSON.
#[fail(display = "JSON error: {}", _0)]
JsonError(String),
}
impl AppErrorKind {
@ -252,6 +256,8 @@ impl AppErrorKind {
AppErrorKind::HmacError(_) => Some(119),
AppErrorKind::JsonError(_) => Some(120),
AppErrorKind::BadRequest
| AppErrorKind::NotFound
| AppErrorKind::MethodNotAllowed

Просмотреть файл

@ -16,6 +16,8 @@ use std::fmt::{self, Display, Formatter};
use hmac::{crypto_mac::InvalidKeyLength, Hmac, Mac};
use redis::{Client as RedisClient, Commands, RedisError};
use serde::{de::DeserializeOwned, ser::Serialize};
use serde_json;
use sha2::Sha256;
use app_errors::{AppError, AppErrorKind, AppResult};
@ -45,30 +47,45 @@ impl Client {
}
/// Read data.
pub fn get(&self, key: &str, data_type: DataType) -> AppResult<String> {
pub fn get<D>(&self, key: &str, data_type: DataType) -> AppResult<D>
where
D: DeserializeOwned,
{
let key = self.generate_key(key, data_type)?;
self.client.get(key.as_str()).map_err(From::from)
self.client
.get(key.as_str())
.map_err(From::from)
.and_then(|value: String| serde_json::from_str(&value).map_err(From::from))
}
/// Read and delete data.
pub fn consume(&self, key: &str, data_type: DataType) -> AppResult<String> {
pub fn consume<D>(&self, key: &str, data_type: DataType) -> AppResult<D>
where
D: DeserializeOwned,
{
let key = self.generate_key(key, data_type)?;
let key_str = key.as_str();
self.client
.get(key_str)
.map(|metadata| {
.map_err(From::from)
.and_then(|value: String| {
self.client.del::<&str, u8>(key_str).ok();
metadata
}).map_err(From::from)
serde_json::from_str(&value).map_err(From::from)
})
}
/// Store data.
///
/// Any data previously stored for the key
/// will be clobbered.
pub fn set(&self, key: &str, data: &str, data_type: DataType) -> AppResult<()> {
pub fn set<D>(&self, key: &str, data: &D, data_type: DataType) -> AppResult<()>
where
D: Serialize,
{
let key = self.generate_key(key, data_type)?;
self.client.set(key.as_str(), data).map_err(From::from)
self.client
.set(key.as_str(), serde_json::to_string(data)?)
.map_err(From::from)
}
fn generate_key(&self, key: &str, data_type: DataType) -> AppResult<String> {

Просмотреть файл

@ -41,6 +41,7 @@ impl MessageData {
/// Any data previously stored for the message id
/// will be replaced.
pub fn set(&self, message_id: &str, metadata: &str) -> AppResult<()> {
self.client.set(message_id, metadata, DataType::MessageData)
self.client
.set(message_id, &metadata, DataType::MessageData)
}
}

Просмотреть файл

@ -31,7 +31,7 @@ fn set() {
.unwrap();
assert!(!key_exists, "unhashed key should not exist in redis");
let value: String = test.redis_client.get(test.internal_key.as_str()).unwrap();
assert_eq!(value, "wibble");
assert_eq!(value, "\"wibble\"");
}
}

Просмотреть файл

@ -222,6 +222,6 @@ impl From<DeleteMessageError> for AppError {
impl From<JsonError> for AppError {
fn from(error: JsonError) -> AppError {
AppErrorKind::QueueError(format!("JSON error: {:?}", error)).into()
AppErrorKind::JsonError(format!("JSON error: {:?}", error)).into()
}
}