next.js/turbopack/crates/turbo-tasks-bytes/src/bytes.rs
bytes.rs133 lines3.3 KB
use std::{
    ops::Deref,
    str::{Utf8Error, from_utf8},
};

use anyhow::Result;
use bincode::{
    Decode, Encode,
    de::Decoder,
    enc::Encoder,
    error::{DecodeError, EncodeError},
    impl_borrow_decode,
};
use bytes::Bytes as CBytes;

/// Bytes is a thin wrapper around [bytes::Bytes], implementing easy
/// conversion to/from, bincode support, and Vc containers.
#[derive(Clone, Debug, Default)]
#[turbo_tasks::value(transparent, serialization = "custom")]
pub struct Bytes(#[turbo_tasks(trace_ignore)] CBytes);

impl Bytes {
    pub fn to_str(&self) -> Result<&'_ str, Utf8Error> {
        from_utf8(&self.0)
    }
}

impl Encode for Bytes {
    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
        self[..].encode(encoder)
    }
}

impl<Context> Decode<Context> for Bytes {
    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
        // bincode uses the same encoding for slices and vecs
        // https://docs.rs/bincode/latest/bincode/spec/index.html#linear-collections-vec-arrays-etc
        Ok(Bytes(CBytes::from(Vec::<u8>::decode(decoder)?)))
    }
}

impl_borrow_decode!(Bytes);

impl Deref for Bytes {
    type Target = CBytes;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/// Types that `impl From<CustomType> for Bytes {}`
///
/// Unfortunately, we cannot just use the more generic `Into<Bytes>` without running afoul of the
/// `From<X> for X` base case, causing conflicting impls.
pub trait IntoBytes: Into<CBytes> {}
impl IntoBytes for &'static [u8] {}
impl IntoBytes for &'static str {}
impl IntoBytes for Vec<u8> {}
impl IntoBytes for Box<[u8]> {}
impl IntoBytes for String {}

impl<T: IntoBytes> From<T> for Bytes {
    fn from(value: T) -> Self {
        Bytes(value.into())
    }
}

impl From<CBytes> for Bytes {
    fn from(value: CBytes) -> Self {
        Bytes(value)
    }
}

impl From<Bytes> for CBytes {
    fn from(value: Bytes) -> Self {
        value.0
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    impl PartialEq<&str> for Bytes {
        fn eq(&self, other: &&str) -> bool {
            self.0 == other
        }
    }

    #[test]
    fn into_bytes() {
        let s = "foo".to_string();
        assert_eq!(Bytes::from(b"foo" as &'static [u8]), "foo");
        assert_eq!(Bytes::from("foo"), "foo");
        assert_eq!(Bytes::from(s.as_bytes().to_vec()), "foo");
        assert_eq!(Bytes::from(s.as_bytes().to_vec().into_boxed_slice()), "foo");
        assert_eq!(Bytes::from(s), "foo");
    }

    #[test]
    fn bincode() {
        let s = Bytes::from("test");
        let c = bincode::config::standard();
        let decoded: Bytes = bincode::decode_from_slice(&bincode::encode_to_vec(&s, c).unwrap(), c)
            .unwrap()
            .0;
        assert_eq!(decoded, s);
    }

    #[test]
    fn from_into() {
        let b = Bytes::from("foo");
        let cb = CBytes::from("foo");
        assert_eq!(Bytes::from(cb), "foo");
        assert_eq!(CBytes::from(b), "foo");
    }

    #[test]
    fn deref() {
        let b = Bytes::from("foo");
        assert_eq!(*b, CBytes::from("foo"));
    }

    #[test]
    fn to_str() {
        let cb = Bytes::from("foo");
        assert_eq!(cb.to_str(), Ok("foo"));

        let b = Bytes::from("💩".as_bytes()[0..3].to_vec());
        assert!(b.to_str().is_err());
    }
}
Quest for Codev2.0.0
/
SIGN IN