1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use super::*;

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

use ::alloc::sync::Arc;

use core::borrow::Borrow;

/// The error type for errors that get returned when encoding or decoding str based structs fails.
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum StringTransformError {
  /// Returned when the buffer is too small to encode.
  #[cfg_attr(feature = "std", error(
    "buffer is too small, use `Transformable::encoded_len` to pre-allocate a buffer with enough space"
  ))]
  EncodeBufferTooSmall,
  /// Returned when the decoding meet corruption.
  #[cfg_attr(feature = "std", error("not enough bytes to decode"))]
  NotEnoughBytes,
  /// Returned when the decoding meet utf8 error.
  #[cfg_attr(feature = "std", error("{0}"))]
  Utf8Error(#[cfg_attr(feature = "std", from)] core::str::Utf8Error),
}

#[cfg(all(not(feature = "std"), feature = "alloc"))]
impl core::convert::From<core::str::Utf8Error> for StringTransformError {
  fn from(err: core::str::Utf8Error) -> Self {
    Self::Utf8Error(err)
  }
}

#[cfg(all(not(feature = "std"), feature = "alloc"))]
impl core::fmt::Display for StringTransformError {
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    match self {
      Self::EncodeBufferTooSmall => write!(
        f,
        "buffer is too small, use `Transformable::encoded_len` to pre-allocate a buffer with enough space"
      ),
      Self::NotEnoughBytes => write!(f, "not enough bytes to decode"),
      Self::Utf8Error(val) => write!(f, "{val}"),
    }
  }
}

macro_rules! impl_string {
  ($ty: ty => $test_fn:ident($init: expr)) => {
    impl Transformable for $ty {
      type Error = StringTransformError;

      fn encode(&self, dst: &mut [u8]) -> Result<usize, Self::Error> {
        let src: &str = self.borrow();
        encode_bytes(src.as_bytes(), dst).map_err(|_| Self::Error::EncodeBufferTooSmall)
      }

      #[cfg(feature = "std")]
      #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
      fn encode_to_writer<W: std::io::Write>(&self, dst: &mut W) -> std::io::Result<usize> {
        let src: &str = self.borrow();
        encode_bytes_to(src.as_bytes(), dst)
      }

      #[cfg(feature = "async")]
      #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
      async fn encode_to_async_writer<W: futures_util::io::AsyncWrite + Send + Unpin>(
        &self,
        dst: &mut W,
      ) -> std::io::Result<usize> {
        let src: &str = self.borrow();
        encode_bytes_to_async(src.as_bytes(), dst).await
      }

      fn encoded_len(&self) -> usize {
        let src: &str = self.borrow();
        encoded_bytes_len(src.as_bytes())
      }

      fn decode(src: &[u8]) -> Result<(usize, Self), Self::Error>
      where
        Self: Sized,
      {
        decode_bytes(src)
          .map_err(|_| Self::Error::NotEnoughBytes)
          .and_then(|(readed, bytes)| {
            core::str::from_utf8(bytes.as_ref())
              .map(|s| (readed, Self::from(s)))
              .map_err(Into::into)
          })
      }

      #[cfg(feature = "std")]
      #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
      fn decode_from_reader<R: std::io::Read>(src: &mut R) -> std::io::Result<(usize, Self)>
      where
        Self: Sized,
      {
        decode_bytes_from(src).and_then(|(readed, bytes)| {
          core::str::from_utf8(bytes.as_ref())
            .map(|s| (readed, Self::from(s)))
            .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
        })
      }

      #[cfg(feature = "async")]
      #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
      async fn decode_from_async_reader<R: futures_util::io::AsyncRead + Send + Unpin>(
        src: &mut R,
      ) -> std::io::Result<(usize, Self)>
      where
        Self: Sized,
      {
        decode_bytes_from_async(src)
          .await
          .and_then(|(readed, bytes)| {
            core::str::from_utf8(bytes.as_ref())
              .map(|s| (readed, Self::from(s)))
              .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
          })
      }
    }

    test_transformable!($ty => $test_fn($init));
  };
}

impl_string!(String => test_string_transformable(String::from("hello world")));

#[cfg(feature = "smol_str")]
impl_string!(smol_str::SmolStr => test_smol_str_transformable(smol_str::SmolStr::from("hello world")));

impl_string!(Box<str> => test_box_str_transformable(Box::from("hello world")));

impl_string!(Arc<str> => test_arc_str_transformable(Arc::from("hello world")));