use serde::{Serialize, de::DeserializeOwned};
use std::error::Error;
use thiserror::Error;
pub trait Codec {
fn encode<T>(&self, value: &T) -> Result<Vec<u8>, EncodeError>
where
T: Serialize;
fn decode<T>(&self, payload: &[u8]) -> Result<T, PayloadError>
where
T: DeserializeOwned;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Cbor;
impl Codec for Cbor {
fn encode<T>(&self, value: &T) -> Result<Vec<u8>, EncodeError>
where
T: Serialize,
{
let mut payload = Vec::new();
ciborium::into_writer(value, &mut payload).map_err(EncodeError::new)?;
Ok(payload)
}
fn decode<T>(&self, payload: &[u8]) -> Result<T, PayloadError>
where
T: DeserializeOwned,
{
ciborium::from_reader(payload).map_err(PayloadError::new)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Json;
impl Codec for Json {
fn encode<T>(&self, value: &T) -> Result<Vec<u8>, EncodeError>
where
T: Serialize,
{
serde_json::to_vec(value).map_err(EncodeError::new)
}
fn decode<T>(&self, payload: &[u8]) -> Result<T, PayloadError>
where
T: DeserializeOwned,
{
serde_json::from_slice(payload).map_err(PayloadError::new)
}
}
#[derive(Debug, Error)]
#[error("value not encodable")]
pub struct EncodeError(#[source] Box<dyn Error + Send + Sync>);
impl EncodeError {
pub fn new<E>(error: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
Self(error.into())
}
}
#[derive(Debug, Error)]
#[error("payload not decodable")]
pub struct PayloadError(#[source] Box<dyn Error + Send + Sync>);
impl PayloadError {
pub fn new<E>(error: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
Self(error.into())
}
}
#[cfg(test)]
mod tests {
use crate::persistence::codec::{Cbor, Codec, Json};
use serde::{Deserialize, Serialize};
use std::error::Error;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Value {
name: String,
count: u64,
}
#[test]
fn both_codecs_round_trip() {
let value = value();
let payload = Cbor.encode(&value).expect("the value is CBOR encodable");
assert_eq!(
Cbor.decode::<Value>(&payload)
.expect("the payload is CBOR decodable"),
value
);
let payload = Json.encode(&value).expect("the value is JSON encodable");
assert_eq!(
Json.decode::<Value>(&payload)
.expect("the payload is JSON decodable"),
value
);
}
#[test]
fn json_encodes_readable_text() {
let payload = Json.encode(&value()).expect("the value is JSON encodable");
let text = String::from_utf8(payload).expect("JSON output is UTF-8");
assert!(text.contains("\"name\""), "got {text}");
assert!(text.contains("\"tellus\""), "got {text}");
}
#[test]
fn a_payload_of_another_codec_does_not_decode() {
let payload = Json.encode(&value()).expect("the value is JSON encodable");
assert!(
Cbor.decode::<Value>(&payload).is_err(),
"CBOR must not decode a JSON payload"
);
let payload = Cbor.encode(&value()).expect("the value is CBOR encodable");
assert!(
Json.decode::<Value>(&payload).is_err(),
"JSON must not decode a CBOR payload"
);
}
#[test]
fn an_empty_payload_does_not_decode() {
assert!(Cbor.decode::<Value>(&[]).is_err());
assert!(Json.decode::<Value>(&[]).is_err());
}
#[test]
fn the_codec_error_is_the_source() {
let error = Cbor
.decode::<Value>(&[])
.expect_err("an empty payload must not decode");
assert_eq!(error.to_string(), "payload not decodable");
let source = error.source().expect("the codec error must be the source");
assert_ne!(
source.to_string(),
error.to_string(),
"the source must carry the codec's own text, not repeat this message"
);
}
fn value() -> Value {
Value {
name: "tellus".to_string(),
count: 42,
}
}
}