1use crate::asciibyte::AsciiByte;
6use crate::int_ops::{Aligned4, Aligned8};
7use crate::ParseError;
8#[cfg(feature = "alloc")]
9use alloc::string::String;
10use core::borrow::Borrow;
11use core::fmt;
12use core::ops::Deref;
13use core::str::{self, FromStr};
14
15#[repr(transparent)]
16#[derive(#[automatically_derived]
impl<const N : usize> ::core::cmp::PartialEq for TinyAsciiStr<N> {
#[inline]
fn eq(&self, other: &TinyAsciiStr<N>) -> bool {
self.bytes == other.bytes
}
}PartialEq, #[automatically_derived]
impl<const N : usize> ::core::cmp::Eq for TinyAsciiStr<N> {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<[AsciiByte; N]>;
}
}Eq, #[automatically_derived]
impl<const N : usize> ::core::cmp::Ord for TinyAsciiStr<N> {
#[inline]
fn cmp(&self, other: &TinyAsciiStr<N>) -> ::core::cmp::Ordering {
::core::cmp::Ord::cmp(&self.bytes, &other.bytes)
}
}Ord, #[automatically_derived]
impl<const N : usize> ::core::cmp::PartialOrd for TinyAsciiStr<N> {
#[inline]
fn partial_cmp(&self, other: &TinyAsciiStr<N>)
-> ::core::option::Option<::core::cmp::Ordering> {
::core::cmp::PartialOrd::partial_cmp(&self.bytes, &other.bytes)
}
}PartialOrd, #[automatically_derived]
impl<const N : usize> ::core::marker::Copy for TinyAsciiStr<N> { }Copy, #[automatically_derived]
impl<const N : usize> ::core::clone::Clone for TinyAsciiStr<N> {
#[inline]
fn clone(&self) -> TinyAsciiStr<N> {
let _: ::core::clone::AssertParamIsClone<[AsciiByte; N]>;
*self
}
}Clone, #[automatically_derived]
impl<const N : usize> ::core::hash::Hash for TinyAsciiStr<N> {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
::core::hash::Hash::hash(&self.bytes, state)
}
}Hash)]
17pub struct TinyAsciiStr<const N: usize> {
18 bytes: [AsciiByte; N],
19}
20
21impl<const N: usize> TinyAsciiStr<N> {
22 pub const EMPTY: Self = Self {
24 bytes: [AsciiByte::B0; N],
25 };
26
27 #[inline]
28 pub const fn try_from_str(s: &str) -> Result<Self, ParseError> {
29 Self::try_from_utf8(s.as_bytes())
30 }
31
32 #[inline]
35 pub const fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
36 Self::try_from_utf8_inner(code_units, false)
37 }
38
39 #[inline]
42 pub const fn try_from_utf16(code_units: &[u16]) -> Result<Self, ParseError> {
43 Self::try_from_utf16_inner(code_units, 0, code_units.len(), false)
44 }
45
46 pub const fn from_utf8_lossy(code_units: &[u8], replacement: u8) -> Self {
58 if !(replacement > 0 && replacement < 0x80) {
{
::core::panicking::panic_fmt(format_args!("replacement must be a non-null ASCII byte (1..=127)"));
}
};assert!(
59 replacement > 0 && replacement < 0x80,
60 "replacement must be a non-null ASCII byte (1..=127)"
61 );
62 let mut out = [0; N];
63 let mut i = 0;
64 let len = if code_units.len() > N {
66 N
67 } else {
68 code_units.len()
69 };
70
71 #[expect(clippy::indexing_slicing)]
73 while i < len {
74 let b = code_units[i];
75 if b > 0 && b < 0x80 {
76 out[i] = b;
77 } else {
78 out[i] = replacement;
79 }
80 i += 1;
81 }
82
83 Self {
84 bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
86 }
87 }
88
89 pub const fn from_utf16_lossy(code_units: &[u16], replacement: u8) -> Self {
101 if !(replacement > 0 && replacement < 0x80) {
{
::core::panicking::panic_fmt(format_args!("replacement must be a non-null ASCII byte (1..=127)"));
}
};assert!(
102 replacement > 0 && replacement < 0x80,
103 "replacement must be a non-null ASCII byte (1..=127)"
104 );
105 let mut out = [0; N];
106 let mut i = 0;
107 let len = if code_units.len() > N {
109 N
110 } else {
111 code_units.len()
112 };
113
114 #[expect(clippy::indexing_slicing)]
116 while i < len {
117 let b = code_units[i];
118 if b > 0 && b < 0x80 {
119 out[i] = b as u8;
120 } else {
121 out[i] = replacement;
122 }
123 i += 1;
124 }
125
126 Self {
127 bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
129 }
130 }
131
132 pub const fn try_from_raw(raw: [u8; N]) -> Result<Self, ParseError> {
153 Self::try_from_utf8_inner(&raw, true)
154 }
155
156 pub(crate) const fn try_from_utf8_inner(
157 code_units: &[u8],
158 allow_trailing_null: bool,
159 ) -> Result<Self, ParseError> {
160 if code_units.len() > N {
161 return Err(ParseError::TooLong {
162 max: N,
163 len: code_units.len(),
164 });
165 }
166
167 let mut out = [0; N];
168 let mut i = 0;
169 let mut found_null = false;
170 #[expect(clippy::indexing_slicing)]
172 while i < code_units.len() {
173 let b = code_units[i];
174
175 if b == 0 {
176 found_null = true;
177 } else if b >= 0x80 {
178 return Err(ParseError::NonAscii);
179 } else if found_null {
180 return Err(ParseError::ContainsNull);
182 }
183 out[i] = b;
184
185 i += 1;
186 }
187
188 if !allow_trailing_null && found_null {
189 return Err(ParseError::ContainsNull);
191 }
192
193 Ok(Self {
194 bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
196 })
197 }
198
199 pub(crate) const fn try_from_utf16_inner(
200 code_units: &[u16],
201 start: usize,
202 end: usize,
203 allow_trailing_null: bool,
204 ) -> Result<Self, ParseError> {
205 let len = end - start;
206 if len > N {
207 return Err(ParseError::TooLong { max: N, len });
208 }
209
210 let mut out = [0; N];
211 let mut i = 0;
212 let mut found_null = false;
213 #[expect(clippy::indexing_slicing)]
215 while i < len {
216 let b = code_units[start + i];
217
218 if b == 0 {
219 found_null = true;
220 } else if b >= 0x80 {
221 return Err(ParseError::NonAscii);
222 } else if found_null {
223 return Err(ParseError::ContainsNull);
225 }
226 out[i] = b as u8;
227
228 i += 1;
229 }
230
231 if !allow_trailing_null && found_null {
232 return Err(ParseError::ContainsNull);
234 }
235
236 Ok(Self {
237 bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
239 })
240 }
241
242 pub fn new_unsigned_decimal(number: u32) -> Result<Self, Self> {
284 let mut bytes = [AsciiByte::B0; N];
285 let mut x = number;
286 let mut i = 0usize;
287 #[expect(clippy::indexing_slicing)] while i < N && (x != 0 || i == 0) {
289 bytes[N - i - 1] = AsciiByte::from_decimal_digit((x % 10) as u8);
290 x /= 10;
291 i += 1;
292 }
293 if i < N {
294 bytes.copy_within((N - i)..N, 0);
295 bytes[i..N].fill(AsciiByte::B0);
296 }
297 let s = Self { bytes };
298 if x != 0 {
299 Err(s)
300 } else {
301 Ok(s)
302 }
303 }
304
305 #[inline]
306 pub const fn as_str(&self) -> &str {
307 unsafe { str::from_utf8_unchecked(self.as_utf8()) }
309 }
310
311 #[inline]
312 #[must_use]
313 pub const fn len(&self) -> usize {
314 if N <= 4 {
315 Aligned4::from_ascii_bytes(&self.bytes).len()
316 } else if N <= 8 {
317 Aligned8::from_ascii_bytes(&self.bytes).len()
318 } else {
319 let mut i = 0;
320 #[expect(clippy::indexing_slicing)] while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
322 i += 1
323 }
324 i
325 }
326 }
327
328 #[inline]
329 #[must_use]
330 pub const fn is_empty(&self) -> bool {
331 self.bytes[0] as u8 == AsciiByte::B0 as u8
332 }
333
334 #[inline]
335 #[must_use]
336 pub const fn as_utf8(&self) -> &[u8] {
337 unsafe {
340 core::slice::from_raw_parts(self.bytes.as_slice().as_ptr() as *const u8, self.len())
341 }
342 }
343
344 #[inline]
345 #[must_use]
346 pub const fn all_bytes(&self) -> &[u8; N] {
347 unsafe { &*(self.bytes.as_ptr() as *const [u8; N]) }
349 }
350
351 #[inline]
352 #[must_use]
353 pub const fn resize<const M: usize>(self) -> TinyAsciiStr<M> {
358 let mut bytes = [0; M];
359 let mut i = 0;
360 #[expect(clippy::indexing_slicing)]
362 while i < M && i < N {
363 bytes[i] = self.bytes[i] as u8;
364 i += 1;
365 }
366 unsafe { TinyAsciiStr::from_utf8_unchecked(bytes) }
369 }
370
371 #[inline]
372 #[must_use]
373 pub const fn concat<const M: usize, const Q: usize>(
398 self,
399 other: TinyAsciiStr<M>,
400 ) -> TinyAsciiStr<Q> {
401 let mut result = self.resize::<Q>();
402 let mut i = self.len();
403 let mut j = 0;
404 #[expect(clippy::indexing_slicing)]
406 while i < Q && j < M {
407 result.bytes[i] = other.bytes[j];
408 i += 1;
409 j += 1;
410 }
411 result
412 }
413
414 #[must_use]
418 pub const unsafe fn from_utf8_unchecked(code_units: [u8; N]) -> Self {
419 Self {
420 bytes: AsciiByte::to_ascii_byte_array(&code_units),
421 }
422 }
423}
424
425macro_rules! check_is {
426 ($self:ident, $check_int:ident, $check_u8:ident) => {
427 if N <= 4 {
428 Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
429 } else if N <= 8 {
430 Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
431 } else {
432 let mut i = 0;
433 while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
434 if !($self.bytes[i] as u8).$check_u8() {
435 return false;
436 }
437 i += 1;
438 }
439 true
440 }
441 };
442 ($self:ident, $check_int:ident, !$check_u8_0_inv:ident, !$check_u8_1_inv:ident) => {
443 if N <= 4 {
444 Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
445 } else if N <= 8 {
446 Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
447 } else {
448 if ($self.bytes[0] as u8).$check_u8_0_inv() {
450 return false;
451 }
452 let mut i = 1;
453 while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
454 if ($self.bytes[i] as u8).$check_u8_1_inv() {
455 return false;
456 }
457 i += 1;
458 }
459 true
460 }
461 };
462 ($self:ident, $check_int:ident, $check_u8_0_inv:ident, $check_u8_1_inv:ident) => {
463 if N <= 4 {
464 Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
465 } else if N <= 8 {
466 Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
467 } else {
468 if !($self.bytes[0] as u8).$check_u8_0_inv() {
470 return false;
471 }
472 let mut i = 1;
473 while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
474 if !($self.bytes[i] as u8).$check_u8_1_inv() {
475 return false;
476 }
477 i += 1;
478 }
479 true
480 }
481 };
482}
483
484impl<const N: usize> TinyAsciiStr<N> {
485 #[inline]
502 #[must_use]
503 pub const fn is_ascii_alphabetic(&self) -> bool {
504 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_alphabetic()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_alphabetic()
} else {
let mut i = 0;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if !(self.bytes[i] as u8).is_ascii_alphabetic() { return false; }
i += 1;
}
true
}check_is!(self, is_ascii_alphabetic, is_ascii_alphabetic)
505 }
506
507 #[inline]
525 #[must_use]
526 pub const fn is_ascii_alphanumeric(&self) -> bool {
527 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_alphanumeric()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_alphanumeric()
} else {
let mut i = 0;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if !(self.bytes[i] as u8).is_ascii_alphanumeric() { return false; }
i += 1;
}
true
}check_is!(self, is_ascii_alphanumeric, is_ascii_alphanumeric)
528 }
529
530 #[inline]
546 #[must_use]
547 pub const fn is_ascii_numeric(&self) -> bool {
548 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_numeric()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_numeric()
} else {
let mut i = 0;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if !(self.bytes[i] as u8).is_ascii_digit() { return false; }
i += 1;
}
true
}check_is!(self, is_ascii_numeric, is_ascii_digit)
549 }
550
551 #[inline]
569 #[must_use]
570 pub const fn is_ascii_lowercase(&self) -> bool {
571 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_lowercase()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_lowercase()
} else {
if (self.bytes[0] as u8).is_ascii_uppercase() { return false; }
let mut i = 1;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if (self.bytes[i] as u8).is_ascii_uppercase() { return false; }
i += 1;
}
true
}check_is!(
572 self,
573 is_ascii_lowercase,
574 !is_ascii_uppercase,
575 !is_ascii_uppercase
576 )
577 }
578
579 #[inline]
598 #[must_use]
599 pub const fn is_ascii_titlecase(&self) -> bool {
600 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_titlecase()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_titlecase()
} else {
if (self.bytes[0] as u8).is_ascii_lowercase() { return false; }
let mut i = 1;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if (self.bytes[i] as u8).is_ascii_uppercase() { return false; }
i += 1;
}
true
}check_is!(
601 self,
602 is_ascii_titlecase,
603 !is_ascii_lowercase,
604 !is_ascii_uppercase
605 )
606 }
607
608 #[inline]
626 #[must_use]
627 pub const fn is_ascii_uppercase(&self) -> bool {
628 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_uppercase()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_uppercase()
} else {
if (self.bytes[0] as u8).is_ascii_lowercase() { return false; }
let mut i = 1;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if (self.bytes[i] as u8).is_ascii_lowercase() { return false; }
i += 1;
}
true
}check_is!(
629 self,
630 is_ascii_uppercase,
631 !is_ascii_lowercase,
632 !is_ascii_lowercase
633 )
634 }
635
636 #[inline]
658 #[must_use]
659 pub const fn is_ascii_alphabetic_lowercase(&self) -> bool {
660 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_alphabetic_lowercase()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_alphabetic_lowercase()
} else {
if !(self.bytes[0] as u8).is_ascii_lowercase() { return false; }
let mut i = 1;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if !(self.bytes[i] as u8).is_ascii_lowercase() { return false; }
i += 1;
}
true
}check_is!(
661 self,
662 is_ascii_alphabetic_lowercase,
663 is_ascii_lowercase,
664 is_ascii_lowercase
665 )
666 }
667
668 #[inline]
688 #[must_use]
689 pub const fn is_ascii_alphabetic_titlecase(&self) -> bool {
690 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_alphabetic_titlecase()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_alphabetic_titlecase()
} else {
if !(self.bytes[0] as u8).is_ascii_uppercase() { return false; }
let mut i = 1;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if !(self.bytes[i] as u8).is_ascii_lowercase() { return false; }
i += 1;
}
true
}check_is!(
691 self,
692 is_ascii_alphabetic_titlecase,
693 is_ascii_uppercase,
694 is_ascii_lowercase
695 )
696 }
697
698 #[inline]
720 #[must_use]
721 pub const fn is_ascii_alphabetic_uppercase(&self) -> bool {
722 if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).is_ascii_alphabetic_uppercase()
} else if N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).is_ascii_alphabetic_uppercase()
} else {
if !(self.bytes[0] as u8).is_ascii_uppercase() { return false; }
let mut i = 1;
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
if !(self.bytes[i] as u8).is_ascii_uppercase() { return false; }
i += 1;
}
true
}check_is!(
723 self,
724 is_ascii_alphabetic_uppercase,
725 is_ascii_uppercase,
726 is_ascii_uppercase
727 )
728 }
729}
730
731macro_rules! to {
732 ($self:ident, $to:ident, $later_char_to:ident $(,$first_char_to:ident)?) => {{
733 let mut i = 0;
734 if N <= 4 {
735 let aligned = Aligned4::from_ascii_bytes(&$self.bytes).$to();
736 #[expect(clippy::indexing_slicing)]
738 while i < N {
739 $self.bytes[i] = aligned[i];
740 i += 1;
741 }
742 } else if N <= 8 {
743 let aligned = Aligned8::from_ascii_bytes(&$self.bytes).$to();
744 #[expect(clippy::indexing_slicing)]
746 while i < N {
747 $self.bytes[i] = aligned[i];
748 i += 1;
749 }
750 } else {
751 while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
752 $self.bytes[i] = $self.bytes[i].$later_char_to();
753 i += 1;
754 }
755 $(
756 $self.bytes[0] = $self.bytes[0].$first_char_to();
757 )?
758 }
759 $self
760 }};
761}
762
763impl<const N: usize> TinyAsciiStr<N> {
764 #[inline]
778 #[must_use]
779 pub const fn to_ascii_lowercase(mut self) -> Self {
780 {
let mut i = 0;
if N <= 4 {
let aligned =
Aligned4::from_ascii_bytes(&self.bytes).to_ascii_lowercase();
#[expect(clippy :: indexing_slicing)]
while i < N { self.bytes[i] = aligned[i]; i += 1; }
} else if N <= 8 {
let aligned =
Aligned8::from_ascii_bytes(&self.bytes).to_ascii_lowercase();
#[expect(clippy :: indexing_slicing)]
while i < N { self.bytes[i] = aligned[i]; i += 1; }
} else {
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
self.bytes[i] = self.bytes[i].to_ascii_lowercase();
i += 1;
}
}
self
}to!(self, to_ascii_lowercase, to_ascii_lowercase)
781 }
782
783 #[inline]
798 #[must_use]
799 pub const fn to_ascii_titlecase(mut self) -> Self {
800 {
let mut i = 0;
if N <= 4 {
let aligned =
Aligned4::from_ascii_bytes(&self.bytes).to_ascii_titlecase();
#[expect(clippy :: indexing_slicing)]
while i < N { self.bytes[i] = aligned[i]; i += 1; }
} else if N <= 8 {
let aligned =
Aligned8::from_ascii_bytes(&self.bytes).to_ascii_titlecase();
#[expect(clippy :: indexing_slicing)]
while i < N { self.bytes[i] = aligned[i]; i += 1; }
} else {
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
self.bytes[i] = self.bytes[i].to_ascii_lowercase();
i += 1;
}
self.bytes[0] = self.bytes[0].to_ascii_uppercase();
}
self
}to!(
801 self,
802 to_ascii_titlecase,
803 to_ascii_lowercase,
804 to_ascii_uppercase
805 )
806 }
807
808 #[inline]
822 #[must_use]
823 pub const fn to_ascii_uppercase(mut self) -> Self {
824 {
let mut i = 0;
if N <= 4 {
let aligned =
Aligned4::from_ascii_bytes(&self.bytes).to_ascii_uppercase();
#[expect(clippy :: indexing_slicing)]
while i < N { self.bytes[i] = aligned[i]; i += 1; }
} else if N <= 8 {
let aligned =
Aligned8::from_ascii_bytes(&self.bytes).to_ascii_uppercase();
#[expect(clippy :: indexing_slicing)]
while i < N { self.bytes[i] = aligned[i]; i += 1; }
} else {
while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
self.bytes[i] = self.bytes[i].to_ascii_uppercase();
i += 1;
}
}
self
}to!(self, to_ascii_uppercase, to_ascii_uppercase)
825 }
826}
827
828impl<const N: usize> fmt::Debug for TinyAsciiStr<N> {
829 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
830 fmt::Debug::fmt(self.as_str(), f)
831 }
832}
833
834impl<const N: usize> fmt::Display for TinyAsciiStr<N> {
835 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
836 fmt::Display::fmt(self.as_str(), f)
837 }
838}
839
840impl<const N: usize> Deref for TinyAsciiStr<N> {
841 type Target = str;
842 #[inline]
843 fn deref(&self) -> &str {
844 self.as_str()
845 }
846}
847
848impl<const N: usize> Borrow<str> for TinyAsciiStr<N> {
849 #[inline]
850 fn borrow(&self) -> &str {
851 self.as_str()
852 }
853}
854
855impl<const N: usize> FromStr for TinyAsciiStr<N> {
856 type Err = ParseError;
857 #[inline]
858 fn from_str(s: &str) -> Result<Self, Self::Err> {
859 Self::try_from_str(s)
860 }
861}
862
863impl<const N: usize> PartialEq<str> for TinyAsciiStr<N> {
864 fn eq(&self, other: &str) -> bool {
865 self.deref() == other
866 }
867}
868
869impl<const N: usize> PartialEq<&str> for TinyAsciiStr<N> {
870 fn eq(&self, other: &&str) -> bool {
871 self.deref() == *other
872 }
873}
874
875#[cfg(feature = "alloc")]
876impl<const N: usize> PartialEq<String> for TinyAsciiStr<N> {
877 fn eq(&self, other: &String) -> bool {
878 self.deref() == other.deref()
879 }
880}
881
882#[cfg(feature = "alloc")]
883impl<const N: usize> PartialEq<TinyAsciiStr<N>> for String {
884 fn eq(&self, other: &TinyAsciiStr<N>) -> bool {
885 self.deref() == other.deref()
886 }
887}
888
889#[cfg(test)]
890mod test {
891 use super::*;
892 use rand::distr::Distribution;
893 use rand::distr::StandardUniform;
894 use rand::rngs::SmallRng;
895 use rand::SeedableRng;
896
897 const STRINGS: [&str; 26] = [
898 "Latn",
899 "laTn",
900 "windows",
901 "AR",
902 "Hans",
903 "macos",
904 "AT",
905 "infiniband",
906 "FR",
907 "en",
908 "Cyrl",
909 "FromIntegral",
910 "NO",
911 "419",
912 "MacintoshOSX2019",
913 "a3z",
914 "A3z",
915 "A3Z",
916 "a3Z",
917 "3A",
918 "3Z",
919 "3a",
920 "3z",
921 "@@[`{",
922 "UK",
923 "E12",
924 ];
925
926 fn gen_strings(num_strings: usize, allowed_lengths: &[usize]) -> Vec<String> {
927 use rand::seq::IndexedRandom;
928 let mut rng = SmallRng::seed_from_u64(2022);
929 let string_lengths = core::iter::repeat_with(|| *allowed_lengths.choose(&mut rng).unwrap())
931 .take(num_strings)
932 .collect::<Vec<usize>>();
933 string_lengths
934 .iter()
935 .map(|len| {
936 StandardUniform
937 .sample_iter(&mut rng)
938 .filter(|b: &u8| *b > 0 && *b < 0x80)
939 .take(*len)
940 .collect::<Vec<u8>>()
941 })
942 .map(|byte_vec| String::from_utf8(byte_vec).expect("All ASCII"))
943 .collect()
944 }
945
946 fn check_operation<T, F1, F2, const N: usize>(reference_f: F1, tinystr_f: F2)
947 where
948 F1: Fn(&str) -> T,
949 F2: Fn(TinyAsciiStr<N>) -> T,
950 T: fmt::Debug + PartialEq,
951 {
952 for s in STRINGS
953 .into_iter()
954 .map(str::to_owned)
955 .chain(gen_strings(100, &[3, 4, 5, 8, 12]))
956 {
957 let t = match TinyAsciiStr::<N>::from_str(&s) {
958 Ok(t) => t,
959 Err(ParseError::TooLong { .. }) => continue,
960 Err(e) => panic!("{}", e),
961 };
962 let expected = reference_f(&s);
963 let actual = tinystr_f(t);
964 assert_eq!(expected, actual, "TinyAsciiStr<{N}>: {s:?}");
965
966 let s_utf16: Vec<u16> = s.encode_utf16().collect();
967 let t = match TinyAsciiStr::<N>::try_from_utf16(&s_utf16) {
968 Ok(t) => t,
969 Err(ParseError::TooLong { .. }) => continue,
970 Err(e) => panic!("{}", e),
971 };
972 let expected = reference_f(&s);
973 let actual = tinystr_f(t);
974 assert_eq!(expected, actual, "TinyAsciiStr<{N}>: {s:?}");
975 }
976 }
977
978 #[test]
979 fn test_is_ascii_alphabetic() {
980 fn check<const N: usize>() {
981 check_operation(
982 |s| s.chars().all(|c| c.is_ascii_alphabetic()),
983 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic(&t),
984 )
985 }
986 check::<2>();
987 check::<3>();
988 check::<4>();
989 check::<5>();
990 check::<8>();
991 check::<16>();
992 }
993
994 #[test]
995 fn test_is_ascii_alphanumeric() {
996 fn check<const N: usize>() {
997 check_operation(
998 |s| s.chars().all(|c| c.is_ascii_alphanumeric()),
999 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphanumeric(&t),
1000 )
1001 }
1002 check::<2>();
1003 check::<3>();
1004 check::<4>();
1005 check::<5>();
1006 check::<8>();
1007 check::<16>();
1008 }
1009
1010 #[test]
1011 fn test_is_ascii_numeric() {
1012 fn check<const N: usize>() {
1013 check_operation(
1014 |s| s.chars().all(|c| c.is_ascii_digit()),
1015 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_numeric(&t),
1016 )
1017 }
1018 check::<2>();
1019 check::<3>();
1020 check::<4>();
1021 check::<5>();
1022 check::<8>();
1023 check::<16>();
1024 }
1025
1026 #[test]
1027 fn test_is_ascii_lowercase() {
1028 fn check<const N: usize>() {
1029 check_operation(
1030 |s| {
1031 s == TinyAsciiStr::<16>::try_from_str(s)
1032 .unwrap()
1033 .to_ascii_lowercase()
1034 .as_str()
1035 },
1036 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_lowercase(&t),
1037 )
1038 }
1039 check::<2>();
1040 check::<3>();
1041 check::<4>();
1042 check::<5>();
1043 check::<8>();
1044 check::<16>();
1045 }
1046
1047 #[test]
1048 fn test_is_ascii_titlecase() {
1049 fn check<const N: usize>() {
1050 check_operation(
1051 |s| {
1052 s == TinyAsciiStr::<16>::try_from_str(s)
1053 .unwrap()
1054 .to_ascii_titlecase()
1055 .as_str()
1056 },
1057 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_titlecase(&t),
1058 )
1059 }
1060 check::<2>();
1061 check::<3>();
1062 check::<4>();
1063 check::<5>();
1064 check::<8>();
1065 check::<16>();
1066 }
1067
1068 #[test]
1069 fn test_is_ascii_uppercase() {
1070 fn check<const N: usize>() {
1071 check_operation(
1072 |s| {
1073 s == TinyAsciiStr::<16>::try_from_str(s)
1074 .unwrap()
1075 .to_ascii_uppercase()
1076 .as_str()
1077 },
1078 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_uppercase(&t),
1079 )
1080 }
1081 check::<2>();
1082 check::<3>();
1083 check::<4>();
1084 check::<5>();
1085 check::<8>();
1086 check::<16>();
1087 }
1088
1089 #[test]
1090 fn test_is_ascii_alphabetic_lowercase() {
1091 fn check<const N: usize>() {
1092 check_operation(
1093 |s| {
1094 s.chars().all(|c| c.is_ascii_alphabetic()) &&
1096 s == TinyAsciiStr::<16>::try_from_str(s)
1098 .unwrap()
1099 .to_ascii_lowercase()
1100 .as_str()
1101 },
1102 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_lowercase(&t),
1103 )
1104 }
1105 check::<2>();
1106 check::<3>();
1107 check::<4>();
1108 check::<5>();
1109 check::<8>();
1110 check::<16>();
1111 }
1112
1113 #[test]
1114 fn test_is_ascii_alphabetic_titlecase() {
1115 fn check<const N: usize>() {
1116 check_operation(
1117 |s| {
1118 s.chars().all(|c| c.is_ascii_alphabetic()) &&
1120 s == TinyAsciiStr::<16>::try_from_str(s)
1122 .unwrap()
1123 .to_ascii_titlecase()
1124 .as_str()
1125 },
1126 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_titlecase(&t),
1127 )
1128 }
1129 check::<2>();
1130 check::<3>();
1131 check::<4>();
1132 check::<5>();
1133 check::<8>();
1134 check::<16>();
1135 }
1136
1137 #[test]
1138 fn test_is_ascii_alphabetic_uppercase() {
1139 fn check<const N: usize>() {
1140 check_operation(
1141 |s| {
1142 s.chars().all(|c| c.is_ascii_alphabetic()) &&
1144 s == TinyAsciiStr::<16>::try_from_str(s)
1146 .unwrap()
1147 .to_ascii_uppercase()
1148 .as_str()
1149 },
1150 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_uppercase(&t),
1151 )
1152 }
1153 check::<2>();
1154 check::<3>();
1155 check::<4>();
1156 check::<5>();
1157 check::<8>();
1158 check::<16>();
1159 }
1160
1161 #[test]
1162 fn test_to_ascii_lowercase() {
1163 fn check<const N: usize>() {
1164 check_operation(
1165 |s| {
1166 s.chars()
1167 .map(|c| c.to_ascii_lowercase())
1168 .collect::<String>()
1169 },
1170 |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_lowercase(t).as_str().to_owned(),
1171 )
1172 }
1173 check::<2>();
1174 check::<3>();
1175 check::<4>();
1176 check::<5>();
1177 check::<8>();
1178 check::<16>();
1179 }
1180
1181 #[test]
1182 fn test_to_ascii_titlecase() {
1183 fn check<const N: usize>() {
1184 check_operation(
1185 |s| {
1186 let mut r = s
1187 .chars()
1188 .map(|c| c.to_ascii_lowercase())
1189 .collect::<String>();
1190 unsafe { r.as_bytes_mut()[0].make_ascii_uppercase() };
1192 r
1193 },
1194 |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_titlecase(t).as_str().to_owned(),
1195 )
1196 }
1197 check::<2>();
1198 check::<3>();
1199 check::<4>();
1200 check::<5>();
1201 check::<8>();
1202 check::<16>();
1203 }
1204
1205 #[test]
1206 fn test_to_ascii_uppercase() {
1207 fn check<const N: usize>() {
1208 check_operation(
1209 |s| {
1210 s.chars()
1211 .map(|c| c.to_ascii_uppercase())
1212 .collect::<String>()
1213 },
1214 |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_uppercase(t).as_str().to_owned(),
1215 )
1216 }
1217 check::<2>();
1218 check::<3>();
1219 check::<4>();
1220 check::<5>();
1221 check::<8>();
1222 check::<16>();
1223 }
1224
1225 #[test]
1226 fn lossy_constructor() {
1227 assert_eq!(TinyAsciiStr::<4>::from_utf8_lossy(b"", b'?').as_str(), "");
1228 assert_eq!(
1229 TinyAsciiStr::<4>::from_utf8_lossy(b"oh\0o", b'?').as_str(),
1230 "oh?o"
1231 );
1232 assert_eq!(
1233 TinyAsciiStr::<4>::from_utf8_lossy(b"\0", b'?').as_str(),
1234 "?"
1235 );
1236 assert_eq!(
1237 TinyAsciiStr::<4>::from_utf8_lossy(b"toolong", b'?').as_str(),
1238 "tool"
1239 );
1240 assert_eq!(
1241 TinyAsciiStr::<4>::from_utf8_lossy(&[b'a', 0x80, 0xFF, b'1'], b'?').as_str(),
1242 "a??1"
1243 );
1244 }
1245
1246 #[test]
1247 #[should_panic(expected = "replacement must be a non-null ASCII byte")]
1248 fn from_utf8_lossy_rejects_non_ascii_replacement() {
1249 let _ = TinyAsciiStr::<4>::from_utf8_lossy(b"\xFF", 0xFF);
1253 }
1254
1255 #[test]
1256 #[should_panic(expected = "replacement must be a non-null ASCII byte")]
1257 fn from_utf16_lossy_rejects_non_ascii_replacement() {
1258 let _ = TinyAsciiStr::<4>::from_utf16_lossy(&[0xFFFF], 0xFF);
1259 }
1260
1261 #[test]
1262 #[should_panic(expected = "replacement must be a non-null ASCII byte")]
1263 fn from_utf8_lossy_rejects_null_replacement() {
1264 let _ = TinyAsciiStr::<4>::from_utf8_lossy(b"\xFF", 0x00);
1266 }
1267}