use crate::content::ContentType;
use crate::error::DecodeError;
use crate::kv::KvEntry;
use serde::Serialize;
use serde::de::DeserializeOwned;
pub trait Codec<T: ?Sized> {
fn content_type() -> ContentType;
fn encode(value: &T) -> Result<Vec<u8>, DecodeError>;
}
pub trait Decoder<T> {
fn decode(payload: &[u8]) -> Result<T, DecodeError>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Json;
impl<T: Serialize + ?Sized> Codec<T> for Json {
fn content_type() -> ContentType {
ContentType::Json
}
fn encode(value: &T) -> Result<Vec<u8>, DecodeError> {
serde_json::to_vec(value)
.map_err(|error| DecodeError::Encode(format!("encode JSON payload: {error}")))
}
}
impl<T: DeserializeOwned> Decoder<T> for Json {
fn decode(payload: &[u8]) -> Result<T, DecodeError> {
serde_json::from_slice(payload)
.map_err(|error| DecodeError::Decode(format!("decode JSON payload: {error}")))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Msgpack;
impl<T: Serialize + ?Sized> Codec<T> for Msgpack {
fn content_type() -> ContentType {
ContentType::Msgpack
}
fn encode(value: &T) -> Result<Vec<u8>, DecodeError> {
rmp_serde::to_vec_named(value)
.map_err(|error| DecodeError::Encode(format!("encode msgpack payload: {error}")))
}
}
impl<T: DeserializeOwned> Decoder<T> for Msgpack {
fn decode(payload: &[u8]) -> Result<T, DecodeError> {
rmp_serde::from_slice(payload)
.map_err(|error| DecodeError::Decode(format!("decode msgpack payload: {error}")))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Cbor;
impl<T: Serialize + ?Sized> Codec<T> for Cbor {
fn content_type() -> ContentType {
ContentType::Cbor
}
fn encode(value: &T) -> Result<Vec<u8>, DecodeError> {
let mut buffer = Vec::new();
ciborium::into_writer(value, &mut buffer)
.map_err(|error| DecodeError::Encode(format!("encode CBOR payload: {error}")))?;
Ok(buffer)
}
}
impl<T: DeserializeOwned> Decoder<T> for Cbor {
fn decode(payload: &[u8]) -> Result<T, DecodeError> {
ciborium::from_reader(payload)
.map_err(|error| DecodeError::Decode(format!("decode CBOR payload: {error}")))
}
}
#[cfg(feature = "bson")]
#[derive(Clone, Copy, Debug, Default)]
pub struct Bson;
#[cfg(feature = "bson")]
impl<T: Serialize> Codec<T> for Bson {
fn content_type() -> ContentType {
ContentType::Bson
}
fn encode(value: &T) -> Result<Vec<u8>, DecodeError> {
bson::serialize_to_vec(value)
.map_err(|error| DecodeError::Encode(format!("encode BSON payload: {error}")))
}
}
#[cfg(feature = "bson")]
impl<T: DeserializeOwned> Decoder<T> for Bson {
fn decode(payload: &[u8]) -> Result<T, DecodeError> {
bson::deserialize_from_slice(payload)
.map_err(|error| DecodeError::Decode(format!("decode BSON payload: {error}")))
}
}
impl KvEntry {
pub fn decode_value<T: DeserializeOwned>(&self) -> Result<T, DecodeError> {
self.decode_value_with::<Json, T>()
}
pub fn decode_value_with<C, T>(&self) -> Result<T, DecodeError>
where
C: Decoder<T>,
{
C::decode(&self.value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
struct Body {
id: u32,
name: String,
}
fn body() -> Body {
Body {
id: 7,
name: "alice".to_owned(),
}
}
#[test]
fn given_a_body_when_encoded_with_each_codec_then_should_round_trip() {
let json = Json::encode(&body()).expect("json encode");
let decoded: Body = Json::decode(&json).expect("json");
assert_eq!(decoded, body());
let msgpack = Msgpack::encode(&body()).expect("msgpack encode");
let decoded: Body = Msgpack::decode(&msgpack).expect("msgpack");
assert_eq!(decoded, body());
let cbor = Cbor::encode(&body()).expect("cbor encode");
let decoded: Body = Cbor::decode(&cbor).expect("cbor");
assert_eq!(decoded, body());
}
#[cfg(feature = "bson")]
#[test]
fn given_a_bson_payload_when_decoded_then_should_round_trip() {
let bson = Bson::encode(&body()).expect("bson encode");
let decoded: Body = Bson::decode(&bson).expect("bson");
assert_eq!(decoded, body());
assert_eq!(<Bson as Codec<Body>>::content_type(), ContentType::Bson);
}
#[test]
fn given_each_codec_when_encoding_then_should_advertise_its_content_type() {
assert_eq!(<Json as Codec<str>>::content_type(), ContentType::Json);
assert_eq!(
<Msgpack as Codec<str>>::content_type(),
ContentType::Msgpack
);
assert_eq!(<Cbor as Codec<str>>::content_type(), ContentType::Cbor);
}
#[test]
fn given_a_msgpack_value_when_round_tripped_through_the_entry_then_should_decode_back() {
let encoded = Msgpack::encode(&vec!["a", "b"]).expect("encode");
let entry = KvEntry {
key: b"k".to_vec(),
value: encoded,
expires_at_micros: None,
version: 0,
scope: None,
source: None,
};
let decoded: Vec<String> = entry.decode_value_with::<Msgpack, _>().expect("decode");
assert_eq!(decoded, vec!["a".to_owned(), "b".to_owned()]);
}
}