use crate::error::{Result, ShoveError};
use serde::{Serialize, de::DeserializeOwned};
pub trait Codec<M>: Send + Sync + 'static {
const NAME: &'static str;
fn encode(value: &M) -> Result<Vec<u8>>;
fn decode(bytes: &[u8]) -> Result<M>;
fn encode_to_string(value: &M) -> Result<String> {
let bytes = Self::encode(value)?;
String::from_utf8(bytes).map_err(|e| ShoveError::Codec {
codec: Self::NAME,
source: Box::new(e),
})
}
}
pub struct JsonCodec;
impl<M> Codec<M> for JsonCodec
where
M: Serialize + DeserializeOwned + Send + Sync + 'static,
{
const NAME: &'static str = "json";
fn encode(value: &M) -> Result<Vec<u8>> {
Ok(serde_json::to_vec(value)?)
}
fn decode(bytes: &[u8]) -> Result<M> {
Ok(serde_json::from_slice(bytes)?)
}
}
pub struct RawBytesCodec;
impl Codec<Vec<u8>> for RawBytesCodec {
const NAME: &'static str = "raw";
fn encode(value: &Vec<u8>) -> Result<Vec<u8>> {
Ok(value.clone())
}
fn decode(bytes: &[u8]) -> Result<Vec<u8>> {
Ok(bytes.to_vec())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Sample {
id: String,
n: u64,
}
#[test]
fn json_codec_round_trip() {
let sample = Sample {
id: "abc".into(),
n: 42,
};
let bytes = <JsonCodec as Codec<Sample>>::encode(&sample).unwrap();
let decoded = <JsonCodec as Codec<Sample>>::decode(&bytes).unwrap();
assert_eq!(decoded, sample);
}
#[test]
fn json_codec_name_is_json() {
assert_eq!(<JsonCodec as Codec<Sample>>::NAME, "json");
}
#[test]
fn raw_bytes_codec_passthrough() {
let payload: Vec<u8> = vec![0xDE, 0xAD, 0xBE, 0xEF];
let bytes = <RawBytesCodec as Codec<Vec<u8>>>::encode(&payload).unwrap();
assert_eq!(bytes, payload);
let decoded = <RawBytesCodec as Codec<Vec<u8>>>::decode(&bytes).unwrap();
assert_eq!(decoded, payload);
}
#[test]
fn raw_bytes_codec_name_is_raw() {
assert_eq!(<RawBytesCodec as Codec<Vec<u8>>>::NAME, "raw");
}
#[test]
fn json_codec_encode_to_string_round_trips() {
let sample = Sample {
id: "abc".into(),
n: 42,
};
let s = <JsonCodec as Codec<Sample>>::encode_to_string(&sample).unwrap();
assert!(s.contains("\"abc\""));
let decoded: Sample = <JsonCodec as Codec<Sample>>::decode(s.as_bytes()).unwrap();
assert_eq!(decoded, sample);
}
#[test]
fn raw_bytes_codec_encode_to_string_surfaces_non_utf8_as_codec_error() {
let payload: Vec<u8> = vec![0xFF, 0xFE, 0xFD];
let err = <RawBytesCodec as Codec<Vec<u8>>>::encode_to_string(&payload).unwrap_err();
match err {
crate::ShoveError::Codec { codec, .. } => assert_eq!(codec, "raw"),
other => panic!("expected Codec variant, got {other:?}"),
}
}
}