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}