use core::str;
use yo_common::{Code, Error, Result};
use yo_shape::Shape;
pub trait Encode: Shape {
fn encode<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R;
}
pub trait Decode: Encode + Sized {
type Ref<'a>;
fn decode(bytes: &[u8]) -> Result<Self>;
fn view(bytes: &[u8]) -> Result<Self::Ref<'_>>;
}
fn wrong_len(what: &str, want: usize, got: usize) -> Error {
Error::fmt(
Code::Corrupt,
format_args!("a {what} in this collection is {got} bytes and should be {want}"),
)
}
macro_rules! fixed {
($($t:ty),* $(,)?) => {
$(
impl Encode for $t {
#[inline]
fn encode<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(&self.to_le_bytes())
}
}
impl Decode for $t {
type Ref<'a> = $t;
#[inline]
fn decode(bytes: &[u8]) -> Result<$t> {
let want = size_of::<$t>();
let array = bytes
.try_into()
.map_err(|_| wrong_len(stringify!($t), want, bytes.len()))?;
Ok(<$t>::from_le_bytes(array))
}
#[inline]
fn view(bytes: &[u8]) -> Result<$t> {
<$t as Decode>::decode(bytes)
}
}
)*
};
}
fixed!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64);
impl Encode for bool {
#[inline]
fn encode<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(&[u8::from(*self)])
}
}
impl Decode for bool {
type Ref<'a> = bool;
#[inline]
fn decode(bytes: &[u8]) -> Result<bool> {
match bytes {
[0] => Ok(false),
[1] => Ok(true),
[_] => Err(Error::new(
Code::Corrupt,
"a bool in this collection is neither 0 nor 1",
)),
other => Err(wrong_len("bool", 1, other.len())),
}
}
#[inline]
fn view(bytes: &[u8]) -> Result<bool> {
<bool as Decode>::decode(bytes)
}
}
impl Encode for str {
#[inline]
fn encode<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(self.as_bytes())
}
}
impl Encode for String {
#[inline]
fn encode<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(self.as_bytes())
}
}
impl Decode for String {
type Ref<'a> = &'a str;
fn decode(bytes: &[u8]) -> Result<String> {
<String as Decode>::view(bytes).map(ToOwned::to_owned)
}
#[inline]
fn view(bytes: &[u8]) -> Result<&str> {
str::from_utf8(bytes).map_err(|e| {
Error::fmt(
Code::Corrupt,
format_args!("a str in this collection is not UTF-8: {e}"),
)
})
}
}
impl Encode for [u8] {
#[inline]
fn encode<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(self)
}
}
impl Encode for Vec<u8> {
#[inline]
fn encode<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(self)
}
}
impl Decode for Vec<u8> {
type Ref<'a> = &'a [u8];
#[inline]
fn decode(bytes: &[u8]) -> Result<Vec<u8>> {
Ok(bytes.to_vec())
}
#[inline]
fn view(bytes: &[u8]) -> Result<&[u8]> {
Ok(bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bytes_of(v: &(impl Encode + ?Sized)) -> Vec<u8> {
v.encode(<[u8]>::to_vec)
}
#[test]
fn fixed_widths_are_little_endian_and_their_own_size() {
assert_eq!(bytes_of(&1u32), vec![1, 0, 0, 0]);
assert_eq!(bytes_of(&-2i16), vec![0xfe, 0xff]);
assert_eq!(bytes_of(&1.5f64), 1.5f64.to_le_bytes().to_vec());
assert_eq!(bytes_of(&true), vec![1]);
assert_eq!(u64::decode(&bytes_of(&9u64)).unwrap(), 9);
assert_eq!(f32::decode(&bytes_of(&0.5f32)).unwrap(), 0.5);
assert!(bool::decode(&bytes_of(&false)).unwrap().eq(&false));
}
#[test]
fn text_and_bytes_pass_straight_through() {
assert_eq!(bytes_of("hello"), b"hello".to_vec());
assert_eq!(String::view(b"hello").unwrap(), "hello");
assert_eq!(Vec::<u8>::view(b"\x00\xff").unwrap(), b"\x00\xff");
}
#[test]
fn the_wrong_number_of_bytes_is_corruption() {
let e = u64::decode(b"1234").expect_err("four bytes is not a u64");
assert_eq!(e.code(), Code::Corrupt);
assert_eq!(
e.message(),
"a u64 in this collection is 4 bytes and should be 8"
);
assert_eq!(
bool::decode(&[2]).expect_err("2 is not a bool").code(),
Code::Corrupt
);
assert_eq!(
bool::decode(&[0, 0])
.expect_err("two bytes is not a bool")
.code(),
Code::Corrupt
);
assert!(
String::decode(&[0xff, 0xfe])
.expect_err("that is not UTF-8")
.message()
.contains("not UTF-8")
);
}
}