1use alloc::format;
4use alloc::string::ToString;
5use alloc::vec::Vec;
6
7use serde::de::DeserializeOwned;
8use serde::Serialize;
9
10use pamoja_core::{Error, Result};
11
12use crate::Codec;
13
14#[derive(Clone, Copy, Debug, Default)]
33pub struct CborCodec;
34
35impl<T> Codec<T> for CborCodec
36where
37 T: Serialize + DeserializeOwned,
38{
39 fn encode(&self, value: &T) -> Result<Vec<u8>> {
40 let mut buffer = Vec::new();
41 ciborium::into_writer(value, &mut buffer).map_err(write_error)?;
42 Ok(buffer)
43 }
44
45 fn decode(&self, bytes: &[u8]) -> Result<T> {
46 ciborium::from_reader(bytes).map_err(read_error)
47 }
48}
49
50pub(crate) fn read_error<T>(error: ciborium::de::Error<T>) -> Error {
53 Error::Codec(match error {
54 ciborium::de::Error::Io(_) => "the CBOR ends part-way through a value".to_string(),
55 ciborium::de::Error::Syntax(offset) => format!("the CBOR is malformed at byte {offset}"),
56 ciborium::de::Error::Semantic(Some(offset), message) => {
57 format!("{message}, at byte {offset}")
58 }
59 ciborium::de::Error::Semantic(None, message) => message,
60 ciborium::de::Error::RecursionLimitExceeded => {
61 "the CBOR nests too deeply to read".to_string()
62 }
63 })
64}
65
66pub(crate) fn write_error<T>(error: ciborium::ser::Error<T>) -> Error {
69 Error::Codec(match error {
70 ciborium::ser::Error::Io(_) => "the CBOR could not be written".to_string(),
71 ciborium::ser::Error::Value(message) => message,
72 })
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn round_trips_a_tuple() {
81 let codec = CborCodec;
82 let value = (42u32, true, vec![1u8, 2, 3]);
83 let encoded = codec.encode(&value).expect("encode");
84 let decoded: (u32, bool, Vec<u8>) = codec.decode(&encoded).expect("decode");
85 assert_eq!(decoded, value);
86 }
87
88 #[test]
89 fn decoding_truncated_input_is_a_codec_error() {
90 let codec = CborCodec;
91 let result: Result<(u32, u32)> = codec.decode(&[]);
92 assert!(matches!(result, Err(Error::Codec(_))));
93 }
94}