const_format/wrapper_types/
ascii_str.rs

1// use crate::fmt::Error;
2
3#[cfg(feature = "fmt")]
4use crate::fmt::{Error, Formatter};
5
6use core::fmt::{self, Display};
7
8////////////////////////////////////////////////////////////////////////////////
9
10/// An ascii string slice.
11///
12/// You can also construct an `AsciiStr` at compile-time with the [`ascii_str`] macro,
13/// erroring at compile if the constant isn't ascii.
14///
15/// # Example
16///
17/// ```rust
18/// use const_format::wrapper_types::{AsciiStr, NotAsciiError};
19/// use const_format::ascii_str;
20///
21/// const HELLO: AsciiStr = unwrap_ascii(AsciiStr::new(b"hello"));
22/// const EURO: AsciiStr = unwrap_ascii(AsciiStr::new("foo €".as_bytes()));
23///
24/// assert_eq!(HELLO.as_str(), "hello");
25/// assert_eq!(EURO.as_str(), "<error>");
26/// assert_eq!(AsciiStr::new("foo €".as_bytes()), Err(NotAsciiError{invalid_from: 4}));
27///
28/// const fn unwrap_ascii(res: Result<AsciiStr<'_>, NotAsciiError>) -> AsciiStr<'_> {
29///     match res {
30///         Ok(x) => x,
31///         Err(_) => ascii_str!("<error>"),
32///     }
33/// }
34///
35/// ```
36///
37/// [`ascii_str`]: ./macro.ascii_str.html
38///
39#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))]
40#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
41pub struct AsciiStr<'a>(&'a [u8]);
42
43impl<'a> AsciiStr<'a> {
44    /// Constructs this  AsciiStr from a possibly non-ascii str slice.
45    ///
46    /// Returns a `NonAsciiError` error on the first non-ascii byte.
47    ///
48    /// # Example
49    ///
50    /// ```rust
51    /// use const_format::wrapper_types::{AsciiStr, NotAsciiError};
52    ///
53    /// let ok = AsciiStr::from_str("foo bar").unwrap();
54    ///
55    /// assert_eq!(ok.as_str(), "foo bar");
56    /// assert_eq!(AsciiStr::from_str("foo bar ½"), Err(NotAsciiError{invalid_from: 8}));
57    ///
58    /// ```
59    #[inline(always)]
60    pub const fn from_str(s: &'a str) -> Result<Self, NotAsciiError> {
61        Self::new(s.as_bytes())
62    }
63
64    /// Constructs this  AsciiStr from a possibly non-ascii byte slice.
65    ///
66    /// Returns a `NonAsciiError` error on the first non-ascii byte.
67    ///
68    /// # Example
69    ///
70    /// ```rust
71    /// use const_format::wrapper_types::{AsciiStr, NotAsciiError};
72    ///
73    /// let ok = AsciiStr::new(b"foo bar").unwrap();
74    ///
75    /// assert_eq!(ok.as_str(), "foo bar");
76    /// assert_eq!(AsciiStr::new(b"foo bar \x80"), Err(NotAsciiError{invalid_from: 8}));
77    ///
78    /// ```
79    pub const fn new(s: &'a [u8]) -> Result<Self, NotAsciiError> {
80        __for_range! {i in 0..s.len()=>
81            if s[i] > 127 {
82                return Err(NotAsciiError{invalid_from: i});
83            }
84        }
85        Ok(AsciiStr(s))
86    }
87
88    /// Constructs an empty `AsciiStr`
89    ///
90    /// # Example
91    ///
92    /// ```rust
93    /// use const_format::AsciiStr;
94    ///
95    /// assert_eq!(AsciiStr::empty().as_str(), "");
96    /// ```
97    pub const fn empty() -> Self {
98        Self(&[])
99    }
100
101    /// Queries the length of the `AsciiStr`
102    ///
103    /// # Example
104    ///
105    /// ```rust
106    /// use const_format::{AsciiStr, ascii_str};
107    ///
108    /// assert_eq!(AsciiStr::empty().len(), 0);
109    /// assert_eq!(ascii_str!("hello").len(), 5);
110    /// ```
111    #[inline(always)]
112    pub const fn len(self) -> usize {
113        self.0.len()
114    }
115
116    /// Queries whether this `AsciiStr` is empty.
117    ///
118    /// # Example
119    ///
120    /// ```rust
121    /// use const_format::{AsciiStr, ascii_str};
122    ///
123    /// assert_eq!(AsciiStr::empty().is_empty(), true);
124    /// assert_eq!(ascii_str!("hello").is_empty(), false);
125    /// ```
126    #[inline(always)]
127    pub const fn is_empty(self) -> bool {
128        self.0.is_empty()
129    }
130
131    /// Accessor for the wrapped ascii string.
132    ///
133    /// # Example
134    ///
135    /// ```rust
136    /// use const_format::{AsciiStr, ascii_str};
137    ///
138    /// assert_eq!(AsciiStr::empty().as_bytes(), b"");
139    /// assert_eq!(ascii_str!("hello").as_bytes(), b"hello");
140    /// ```
141    #[inline(always)]
142    pub const fn as_bytes(self) -> &'a [u8] {
143        self.0
144    }
145
146    /// Accessor for the wrapped ascii string.
147    ///
148    /// # Example
149    ///
150    /// ```rust
151    /// use const_format::{AsciiStr, ascii_str};
152    ///
153    /// assert_eq!(AsciiStr::empty().as_str(), "");
154    /// assert_eq!(ascii_str!("hello").as_str(), "hello");
155    /// ```
156    #[inline]
157    pub fn as_str(self) -> &'a str {
158        unsafe { core::str::from_utf8_unchecked(self.0) }
159    }
160}
161
162////////////////////////////////////////////////////////////////////////////////
163
164/// Error from [`AsciiStr`] constructor, caused by a byte not being valid ascii.
165///
166/// [`AsciiStr`]: ../struct.AsciiStr.html
167#[derive(Debug, Copy, Clone, PartialEq)]
168pub struct NotAsciiError {
169    /// The first non-ascii byte in the byte slice.
170    pub invalid_from: usize,
171}
172
173impl Display for NotAsciiError {
174    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
175        fmt.write_str("error: the input bytes were not valid ascii")
176    }
177}
178
179////////////////////////////////////////////////////////////////////////////////
180
181#[cfg(feature = "fmt")]
182impl_fmt! {
183    impl AsciiStr<'_>;
184
185    ///
186    pub const fn const_display_fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
187        f.write_ascii(*self)
188    }
189
190    ///
191    pub const fn const_debug_fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
192        f.write_ascii_debug(*self)
193    }
194}
195
196////////////////////////////////////////////////////////////////////////////////
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[cfg(feature = "fmt")]
203    use crate::fmt::ComputeStrLength;
204
205    use arrayvec::ArrayString;
206
207    #[test]
208    fn basic() {
209        {
210            let ok = AsciiStr::new("hello!".as_bytes()).unwrap();
211            assert_eq!(ok.as_bytes(), "hello!".as_bytes());
212            assert_eq!(ok.as_str(), "hello!");
213        }
214        {
215            let err = AsciiStr::from_str("Φοο!").unwrap_err();
216            assert_eq!(err.invalid_from, 0)
217        }
218        {
219            let err = AsciiStr::from_str("hello Φοο!").unwrap_err();
220            assert_eq!(err.invalid_from, 6)
221        }
222    }
223
224    // This doesn't use unsafe code
225    #[cfg(not(miri))]
226    #[test]
227    fn only_ascii_constructible() {
228        let mut string = ArrayString::<[u8; 1024]>::new();
229        let min = '\u{20}';
230        let max = '\u{80}';
231        assert!(!max.is_ascii());
232        for end in min..=max {
233            for start in min..=end {
234                string.clear();
235                for n in start..=end {
236                    string.push(n);
237                }
238                let res = AsciiStr::new(string.as_bytes());
239                assert_eq!(res.is_ok(), string.as_bytes().is_ascii());
240
241                if let Ok(ascii) = res {
242                    assert_eq!(ascii.as_bytes(), string.as_bytes());
243                }
244            }
245        }
246    }
247
248    #[cfg(feature = "fmt")]
249    #[test]
250    fn formatting() {
251        use crate::fmt::{FormattingFlags, NumberFormatting, StrWriter};
252
253        const fn inner_debug(
254            ascii: AsciiStr<'_>,
255            writer: &mut StrWriter,
256            flags: FormattingFlags,
257        ) -> Result<usize, Error> {
258            try_!(ascii.const_debug_fmt(&mut writer.make_formatter(flags)));
259
260            let mut str_len = ComputeStrLength::new();
261            try_!(ascii.const_debug_fmt(&mut str_len.make_formatter(flags)));
262
263            Ok(str_len.len())
264        }
265
266        const fn inner_display(
267            ascii: AsciiStr<'_>,
268            writer: &mut StrWriter,
269            flags: FormattingFlags,
270        ) -> Result<usize, Error> {
271            try_!(ascii.const_display_fmt(&mut writer.make_formatter(flags)));
272
273            let mut str_len = ComputeStrLength::new();
274            try_!(ascii.const_display_fmt(&mut str_len.make_formatter(flags)));
275
276            Ok(str_len.len())
277        }
278
279        fn test_case(
280            ascii: AsciiStr<'_>,
281            writer: &mut StrWriter,
282            flags: FormattingFlags,
283            expected_debug: &str,
284            expected_display: &str,
285        ) {
286            writer.clear();
287            let len = inner_debug(ascii, writer, flags).unwrap();
288
289            assert_eq!(writer.as_str(), expected_debug);
290            assert_eq!(writer.len(), len, "{}", writer.as_str());
291
292            writer.clear();
293            let len = inner_display(ascii, writer, flags).unwrap();
294
295            assert_eq!(writer.as_str(), expected_display);
296            assert_eq!(writer.len(), len, "{}", writer.as_str());
297        }
298
299        let writer: &mut StrWriter = &mut StrWriter::new([0; 128]);
300
301        let foo = AsciiStr::new("\0\x10hello\tworld\n".as_bytes()).unwrap();
302
303        for num_fmt in NumberFormatting::ALL.iter().copied() {
304            for is_alt in [false, true].iter().copied() {
305                let flag = FormattingFlags::NEW
306                    .set_num_fmt(num_fmt)
307                    .set_alternate(is_alt);
308                test_case(
309                    foo,
310                    writer,
311                    flag,
312                    "\"\\x00\\x10hello\\tworld\\n\"",
313                    foo.as_str(),
314                );
315            }
316        }
317    }
318}