use crate::content::ContentType;
use crate::error::DecodeError;
use crate::kv::KvEntry;
use crate::query::Row;
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}")))
}
}
const NO_ROW_PAYLOAD: &str = "row has no payload, the publisher must call .inline_payload() and the query must call .with_payload()";
impl Row {
pub fn decode_json<T: DeserializeOwned>(&self) -> Result<T, DecodeError> {
self.decode_with::<Json, T>()
}
pub fn decode_msgpack<T: DeserializeOwned>(&self) -> Result<T, DecodeError> {
self.decode_with::<Msgpack, T>()
}
pub fn decode_with<C, T>(&self) -> Result<T, DecodeError>
where
C: Decoder<T>,
{
let payload = self
.payload
.as_deref()
.ok_or(DecodeError::MissingPayload(NO_ROW_PAYLOAD))?;
C::decode(payload)
}
}
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_row_payload_when_decoded_with_each_codec_then_should_round_trip() {
let json_row = Row {
payload: Some(Json::encode(&body()).expect("json encode")),
..Default::default()
};
assert_eq!(json_row.decode_with::<Json, Body>().expect("json"), body());
let msgpack_row = Row {
payload: Some(Msgpack::encode(&body()).expect("msgpack encode")),
..Default::default()
};
assert_eq!(
msgpack_row.decode_with::<Msgpack, Body>().expect("msgpack"),
body()
);
let cbor_row = Row {
payload: Some(Cbor::encode(&body()).expect("cbor encode")),
..Default::default()
};
assert_eq!(cbor_row.decode_with::<Cbor, Body>().expect("cbor"), body());
}
#[cfg(feature = "bson")]
#[test]
fn given_a_bson_payload_when_decoded_then_should_round_trip() {
let bson_row = Row {
payload: Some(Bson::encode(&body()).expect("bson encode")),
..Default::default()
};
assert_eq!(bson_row.decode_with::<Bson, Body>().expect("bson"), 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_row_without_payload_when_decoded_then_should_error() {
let row = Row::default();
assert!(matches!(
row.decode_with::<Json, String>(),
Err(DecodeError::MissingPayload(_))
));
}
#[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()]);
}
}