tinycbor 0.12.2

A tiny CBOR codec library.
Documentation
//! Text.
use crate::{
    CborLen, Decode, Decoder, Encode, Encoder, InvalidHeader, TEXT, container, info_of, primitive,
    type_of,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// The text is not valid UTF-8.
pub struct InvalidUtf8;

impl core::fmt::Display for InvalidUtf8 {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "invalid utf-8 string")
    }
}

impl From<core::str::Utf8Error> for InvalidUtf8 {
    fn from(_: core::str::Utf8Error) -> Self {
        InvalidUtf8
    }
}

impl core::error::Error for InvalidUtf8 {}

/// Decodes a definite-length string.
///
/// If you need to also support indefinite-length byte strings, consider using an allocated type
/// such as `String` or `Box<str>`.
impl<'a, 'b> Decode<'b> for &'a str
where
    'b: 'a,
{
    type Error = container::Error<InvalidUtf8>;

    fn decode(d: &mut Decoder<'b>) -> Result<Self, Self::Error> {
        let b = d.peek().map_err(primitive::Error::from)?;
        if TEXT != type_of(b) || info_of(b) == 31 {
            return Err(InvalidHeader.into());
        }
        let n = d.length()?;
        let s = d.read_slice(n)?;
        core::str::from_utf8(s).map_err(|_| container::Error::Content(InvalidUtf8))
    }
}

impl Encode for str {
    fn encode<W: embedded_io::Write>(&self, e: &mut Encoder<W>) -> Result<(), W::Error> {
        e.type_len(TEXT, self.len() as u64)?;
        e.put(self.as_bytes())
    }
}

impl CborLen for str {
    fn cbor_len(&self) -> usize {
        let n = self.len();
        n.cbor_len() + n
    }
}

/// Decodes a byte string, supporting both definite-length and indefinite-length encodings.
#[cfg(feature = "alloc")]
impl<'a> Decode<'a> for alloc::string::String {
    type Error = container::Error<InvalidUtf8>;
    fn decode(d: &mut Decoder<'a>) -> Result<Self, Self::Error> {
        d.str_iter()?.collect()
    }
}

#[cfg(feature = "alloc")]
impl Encode for alloc::string::String {
    fn encode<W: embedded_io::Write>(&self, e: &mut Encoder<W>) -> Result<(), W::Error> {
        self.as_str().encode(e)
    }
}

#[cfg(feature = "alloc")]
impl CborLen for alloc::string::String {
    fn cbor_len(&self) -> usize {
        self.as_str().cbor_len()
    }
}

/// Decodes a byte string, supporting both definite-length and indefinite-length encodings.
#[cfg(feature = "alloc")]
impl<'a> Decode<'a> for alloc::boxed::Box<str> {
    type Error = container::Error<InvalidUtf8>;
    fn decode(d: &mut Decoder<'a>) -> Result<Self, Self::Error> {
        d.str_iter()?.collect()
    }
}

// === Decode Only Types ===

/// Decodes a definite-length string as a `&Path`.
///
/// If you need to also support indefinite-length byte strings, consider using an allocated type
/// such as `PathBuf` or `Box<Path>`.
#[cfg(feature = "std")]
impl<'b> Decode<'b> for &'b std::path::Path {
    type Error = container::Error<InvalidUtf8>;

    fn decode(d: &mut Decoder<'b>) -> Result<Self, Self::Error> {
        <&'b str>::decode(d).map(std::path::Path::new)
    }
}

#[cfg(feature = "std")]
impl<'b> Decode<'b> for Box<std::path::Path> {
    type Error = container::Error<InvalidUtf8>;

    fn decode(d: &mut Decoder<'b>) -> Result<Self, Self::Error> {
        std::path::PathBuf::decode(d).map(std::path::PathBuf::into_boxed_path)
    }
}

#[cfg(feature = "std")]
impl<'b> Decode<'b> for std::path::PathBuf {
    type Error = container::Error<InvalidUtf8>;

    fn decode(d: &mut Decoder<'b>) -> Result<Self, Self::Error> {
        <&'b std::path::Path>::decode(d).map(std::path::Path::to_path_buf)
    }
}

#[cfg(test)]
mod tests {
    use crate::{InvalidHeader, container, string::InvalidUtf8, test};

    #[test]
    fn empty() {
        assert!(test::<&str>("", &[0x60]).unwrap());
        #[cfg(feature = "alloc")]
        {
            use alloc::{boxed::Box, string::String};

            assert!(test::<String>(String::new(), &[0x60]).unwrap());
            assert!(test::<Box<str>>(Box::<str>::from(""), &[0x60]).unwrap());
        }
    }

    #[test]
    fn invalid_utf8() {
        let cbor = [0x62, 0xff, 0xff];
        let err = test::<&str>("", &cbor).unwrap_err();
        assert_eq!(err, container::Error::Content(InvalidUtf8));

        #[cfg(feature = "alloc")]
        {
            use alloc::{boxed::Box, string::String};

            let err = test::<String>(String::new(), &cbor).unwrap_err();
            assert_eq!(err, container::Error::Content(InvalidUtf8));

            let err = test::<Box<str>>(Box::<str>::from(""), &cbor).unwrap_err();
            assert_eq!(err, container::Error::Content(InvalidUtf8));
        }
    }

    #[test]
    fn definite() {
        let cbor = [0x65, 0x68, 0x65, 0x6c, 0x6c, 0x6f];
        let result = "hello";

        assert!(test::<&str>(result, &cbor).unwrap());

        #[cfg(feature = "alloc")]
        {
            use alloc::{boxed::Box, string::String};

            assert!(test(String::from(result), &cbor).unwrap());
            assert!(test(Box::<str>::from(result), &cbor).unwrap());
        }
    }

    #[test]
    fn definite_unicode() {
        let cbor = [0x69, 0xf0, 0x9f, 0x98, 0x80, 0x20, 0xf0, 0x9f, 0xa6, 0x80];
        let result = "😀 🦀";

        assert!(test::<&str>(result, &cbor).unwrap());

        #[cfg(feature = "alloc")]
        {
            use alloc::{boxed::Box, string::String};

            assert!(test(String::from(result), &cbor).unwrap());
            assert!(test(Box::<str>::from(result), &cbor).unwrap());
        }
    }

    #[test]
    fn indefinite() {
        let err = test::<&str>("", &[0x7F, 0x60, 0xFF]).unwrap_err();
        assert_eq!(err, InvalidHeader.into());

        #[cfg(feature = "alloc")]
        {
            use alloc::{boxed::Box, string::String};

            let cbor = [0x7f, 0x62, 0x68, 0x65, 0x63, 0x6c, 0x6c, 0x6f, 0xff];
            let result = "hello";

            assert!(!test(String::from(result), &cbor).unwrap());
            assert!(!test(Box::<str>::from(result), &cbor).unwrap());

            let cbor = [0x7f, 0x60, 0x60, 0x60, 0x60, 0xff];

            assert!(!test::<String>(String::new(), &cbor).unwrap());
            assert!(!test::<Box<str>>(Box::<str>::from(""), &cbor).unwrap());

            let cbor = [0x7f, 0xff];

            assert!(!test::<String>(String::new(), &cbor).unwrap());
            assert!(!test::<Box<str>>(Box::<str>::from(""), &cbor).unwrap());
        }
    }

    #[test]
    #[cfg(feature = "std")]
    fn path() {
        use crate::{Decode, Decoder};
        use std::path::{Path, PathBuf};

        let cbor = [0x68, 0x2f, 0x74, 0x6d, 0x70, 0x2f, 0x66, 0x6f, 0x6f];
        let result = "/tmp/foo";

        assert_eq!(
            <&Path>::decode(&mut Decoder(&cbor)).unwrap(),
            Path::new(result)
        );
        assert_eq!(
            PathBuf::decode(&mut Decoder(&cbor)).unwrap(),
            PathBuf::from(result)
        );
        assert_eq!(
            Box::<Path>::decode(&mut Decoder(&cbor)).unwrap(),
            PathBuf::from(result).into_boxed_path()
        );
    }
}