use std::collections::BTreeMap;
use zerocbor::{ToCbor, Value};
#[track_caller]
fn assert_matches_ciborium<T>(value: &T)
where
T: ToCbor + serde::Serialize + std::fmt::Debug,
{
let ours = zerocbor::to_cbor_vec(value).expect("zerocbor failed to encode");
let mut theirs = Vec::new();
ciborium::into_writer(value, &mut theirs).expect("ciborium failed to encode");
assert_eq!(ours, theirs, "encodings differ for {value:?}");
let mut exact = vec![0; ours.len()];
let written = zerocbor::to_cbor(value, &mut exact).unwrap();
assert_eq!(written, ours.len());
assert_eq!(exact, ours);
let mut short = vec![0; ours.len() - 1];
assert!(matches!(
zerocbor::to_cbor(value, &mut short),
Err(zerocbor::Error::BufferTooSmall)
));
#[cfg(feature = "std")]
{
let mut stream = Vec::new();
zerocbor::write_cbor(&mut stream, value).unwrap();
assert_eq!(stream, ours, "stream encoding differs for {value:?}");
}
}
macro_rules! against_ciborium {
($($value:expr),* $(,)?) => {$(
assert_matches_ciborium(&$value);
)*};
}
#[test]
fn scalars_match_ciborium() {
against_ciborium!(
0u64,
23,
24,
255,
256,
65535,
65536,
u32::MAX as u64,
u32::MAX as u64 + 1,
u64::MAX
);
against_ciborium!(0i64, -1, -24, -25, i64::MIN, i64::MAX);
against_ciborium!(true, false, ());
against_ciborium!("hello", "a longer string that needs a two byte head");
}
#[test]
fn containers_match_ciborium() {
against_ciborium!(
Vec::<u64>::new(),
vec![1u64, 2, 3],
vec![0u8, 23, 24, 255],
vec![String::from("a"), String::from("b")],
vec![Some(1u64), None, Some(3)],
BTreeMap::from([(1u64, 2u64), (3u64, 4u64)]),
BTreeMap::from([(String::from("key"), vec![1u64, 2])]),
);
let many: Vec<u64> = (0..1000).map(|i| i * 7).collect();
assert_matches_ciborium(&many);
for len in [0, 23, 24, 255, 256, 65_535, 65_536, 100_000] {
assert_matches_ciborium(&"x".repeat(len));
}
struct ByteString<'a>(&'a [u8]);
impl zerocbor::ToCbor for ByteString<'_> {
fn write<W: zerocbor::Write>(&self, writer: &mut W) -> zerocbor::Result<()> {
writer.write_binary(self.0)
}
}
let ours = zerocbor::to_cbor_vec(&ByteString(&[1u8, 2, 3][..])).unwrap();
let mut theirs = Vec::new();
ciborium::into_writer(&serde_bytes::ByteBuf::from(vec![1u8, 2, 3]), &mut theirs).unwrap();
assert_eq!(ours, theirs);
}
#[test]
fn floats_match_ciborium_at_the_narrowest_width() {
assert_eq!(zerocbor::to_cbor_vec(&0.0f64).unwrap(), vec![0xf9, 0, 0]);
assert_eq!(
zerocbor::to_cbor_vec(&-0.0f64).unwrap(),
vec![0xf9, 0x80, 0x00]
);
assert_eq!(zerocbor::from_cbor::<f64>(&[0xf9, 0, 0]).unwrap(), 0.0);
for value in [
0.0f64,
-0.0,
1.0,
std::f64::consts::PI,
f64::INFINITY,
f64::MIN,
] {
assert_matches_ciborium(&value);
}
let mut narrow = Vec::new();
ciborium::into_writer(&1.5f64, &mut narrow).unwrap();
assert_eq!(narrow, vec![0xf9, 0x3e, 0x00], "ciborium narrows 1.5");
assert_eq!(zerocbor::from_cbor::<f64>(&narrow).unwrap(), 1.5);
assert_eq!(zerocbor::from_cbor::<f32>(&narrow).unwrap(), 1.5);
}
#[test]
fn the_dynamic_value_type_round_trips_and_is_understood() {
let original = (
Value::Integer(1),
Value::Text("two".into()),
Value::Float(3.5),
Value::Bool(true),
Value::Null,
);
let ours = zerocbor::to_cbor_vec(&original).unwrap();
let mut theirs = Vec::new();
ciborium::into_writer(
&(1i64, "two".to_string(), 3.5f64, true, Option::<u8>::None),
&mut theirs,
)
.unwrap();
let _: serde::de::IgnoredAny = ciborium::from_reader(ours.as_slice())
.unwrap_or_else(|e| panic!("ciborium rejected our Value encoding: {e}"));
let decoded: (Value<'_>, Value<'_>, Value<'_>, Value<'_>, Value<'_>) =
zerocbor::from_cbor(&ours).unwrap();
assert_eq!(decoded, original, "Value did not round trip");
let from_theirs: (Value<'_>, Value<'_>, Value<'_>, Value<'_>, Value<'_>) =
zerocbor::from_cbor(&theirs).unwrap();
assert_eq!(from_theirs, original);
}
#[test]
fn malformed_input_is_rejected_rather_than_misread() {
let full = zerocbor::to_cbor_vec(&vec![1u64, 2, 3, 4, 5, 6, 7, 8]).unwrap();
for len in 0..full.len() {
let result = zerocbor::from_cbor::<Vec<u64>>(&full[..len]);
assert!(
result.is_err(),
"a {len}-byte prefix decoded as {:?}",
result.ok(),
);
}
assert!(zerocbor::from_cbor::<Vec<u64>>(&full).is_ok());
for byte in 0x40u8..=0xff {
if byte & 0xe0 == 0x00 {
continue; }
let result = zerocbor::from_cbor::<u64>(&[byte]);
assert!(result.is_err(), "0x{byte:02x} was accepted as a u64");
}
}