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::FromStr;
14
15#[repr(transparent)]
16#[derive(#[automatically_derived]impl<const N : usize>
::core::marker::StructuralPartialEq for TinyAsciiStr<N> {
}
#[automatically_derived]impl<const N : usize> ::core::cmp::PartialEq for
TinyAsciiStr<N> {
#[inline]
fn eq(&self, other: &Self) -> 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: &Self) -> ::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: &Self)
-> ::core::option::Option<::core::cmp::Ordering> {
::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
}
}PartialOrd, #[automatically_derived]impl<const N : usize> ::core::marker::Copy for
TinyAsciiStr<N> {
}Copy, #[automatically_derived]#[doc(hidden)]unsafe impl<const N : usize>
::core::clone::TrivialClone for TinyAsciiStr<N> {
}
#[automatically_derived]impl<const N : usize> ::core::clone::Clone for
TinyAsciiStr<N> {
#[inline]
fn clone(&self) -> Self {
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 pub const fn default() -> Self {
28 Self::EMPTY
29 }
30
31 #[inline]
32 pub const fn try_from_str(s: &str) -> Result<Self, ParseError> {
33 Self::try_from_utf8(s.as_bytes())
34 }
35
36 #[inline]
39 pub const fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
40 Self::try_from_utf8_inner(code_units, false)
41 }
42
43 #[inline]
46 pub const fn try_from_utf16(code_units: &[u16]) -> Result<Self, ParseError> {
47 Self::try_from_utf16_inner(code_units, 0, code_units.len(), false)
48 }
49
50 pub const fn from_utf8_lossy(code_units: &[u8], replacement: u8) -> Self {
62 if !(replacement > 0 && replacement < 0x80) {
{
::core::panicking::panic_fmt(format_args!("replacement must be a non-null ASCII byte (1..=127)"));
}
};assert!(
63 replacement > 0 && replacement < 0x80,
64 "replacement must be a non-null ASCII byte (1..=127)"
65 );
66 let mut out = [0; N];
67 let mut i = 0;
68 let len = if code_units.len() > N {
70 N
71 } else {
72 code_units.len()
73 };
74
75 #[expect(clippy::indexing_slicing)]
77 while i < len {
78 let b = code_units[i];
79 if b > 0 && b < 0x80 {
80 out[i] = b;
81 } else {
82 out[i] = replacement;
83 }
84 i += 1;
85 }
86
87 Self {
88 bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
90 }
91 }
92
93 pub const fn from_utf16_lossy(code_units: &[u16], replacement: u8) -> Self {
105 if !(replacement > 0 && replacement < 0x80) {
{
::core::panicking::panic_fmt(format_args!("replacement must be a non-null ASCII byte (1..=127)"));
}
};assert!(
106 replacement > 0 && replacement < 0x80,
107 "replacement must be a non-null ASCII byte (1..=127)"
108 );
109 let mut out = [0; N];
110 let mut i = 0;
111 let len = if code_units.len() > N {
113 N
114 } else {
115 code_units.len()
116 };
117
118 #[expect(clippy::indexing_slicing)]
120 while i < len {
121 let b = code_units[i];
122 if b > 0 && b < 0x80 {
123 out[i] = b as u8;
124 } else {
125 out[i] = replacement;
126 }
127 i += 1;
128 }
129
130 Self {
131 bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
133 }
134 }
135
136 pub const fn try_from_raw(raw: [u8; N]) -> Result<Self, ParseError> {
157 Self::try_from_utf8_inner(&raw, true)
158 }
159
160 pub(crate) const fn try_from_utf8_inner(
161 code_units: &[u8],
162 allow_trailing_null: bool,
163 ) -> Result<Self, ParseError> {
164 if code_units.len() > N {
165 return Err(ParseError::TooLong {
166 max: N,
167 len: code_units.len(),
168 });
169 }
170
171 let mut out = [0; N];
172 let mut i = 0;
173 let mut found_null = false;
174 #[expect(clippy::indexing_slicing)]
176 while i < code_units.len() {
177 let b = code_units[i];
178
179 if b == 0 {
180 found_null = true;
181 } else if b >= 0x80 {
182 return Err(ParseError::NonAscii);
183 } else if found_null {
184 return Err(ParseError::ContainsNull);
186 }
187 out[i] = b;
188
189 i += 1;
190 }
191
192 if !allow_trailing_null && found_null {
193 return Err(ParseError::ContainsNull);
195 }
196
197 Ok(Self {
198 bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
200 })
201 }
202
203 pub(crate) const fn try_from_utf16_inner(
204 code_units: &[u16],
205 start: usize,
206 end: usize,
207 allow_trailing_null: bool,
208 ) -> Result<Self, ParseError> {
209 let len = end - start;
210 if len > N {
211 return Err(ParseError::TooLong { max: N, len });
212 }
213
214 let mut out = [0; N];
215 let mut i = 0;
216 let mut found_null = false;
217 #[expect(clippy::indexing_slicing)]
219 while i < len {
220 let b = code_units[start + i];
221
222 if b == 0 {
223 found_null = true;
224 } else if b >= 0x80 {
225 return Err(ParseError::NonAscii);
226 } else if found_null {
227 return Err(ParseError::ContainsNull);
229 }
230 out[i] = b as u8;
231
232 i += 1;
233 }
234
235 if !allow_trailing_null && found_null {
236 return Err(ParseError::ContainsNull);
238 }
239
240 Ok(Self {
241 bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
243 })
244 }
245
246 pub fn new_unsigned_decimal(number: u32) -> Result<Self, Self> {
288 let mut bytes = [AsciiByte::B0; N];
289 let mut x = number;
290 let mut i = 0usize;
291 #[expect(clippy::indexing_slicing)] while i < N && (x != 0 || i == 0) {
293 bytes[N - i - 1] = AsciiByte::from_decimal_digit((x % 10) as u8);
294 x /= 10;
295 i += 1;
296 }
297 if i < N {
298 bytes.copy_within((N - i)..N, 0);
299 bytes[i..N].fill(AsciiByte::B0);
300 }
301 let s = Self { bytes };
302 if x != 0 {
303 Err(s)
304 } else {
305 Ok(s)
306 }
307 }
308
309 #[inline]
310 pub const fn as_str(&self) -> &str {
311 unsafe { core::str::from_utf8_unchecked(self.as_utf8()) }
313 }
314
315 #[inline]
316 #[must_use]
317 pub const fn len(&self) -> usize {
318 if N <= 4 {
319 Aligned4::from_ascii_bytes(&self.bytes).len()
320 } else if N <= 8 {
321 Aligned8::from_ascii_bytes(&self.bytes).len()
322 } else {
323 let mut i = 0;
324 #[expect(clippy::indexing_slicing)] while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
326 i += 1
327 }
328 i
329 }
330 }
331
332 #[inline]
333 #[must_use]
334 pub const fn is_empty(&self) -> bool {
335 self.bytes[0] as u8 == AsciiByte::B0 as u8
336 }
337
338 #[inline]
339 #[must_use]
340 pub const fn as_utf8(&self) -> &[u8] {
341 unsafe {
344 core::slice::from_raw_parts(self.bytes.as_slice().as_ptr() as *const u8, self.len())
345 }
346 }
347
348 #[inline]
349 #[must_use]
350 pub const fn all_bytes(&self) -> &[u8; N] {
351 unsafe { &*(self.bytes.as_ptr() as *const [u8; N]) }
353 }
354
355 #[inline]
356 #[must_use]
357 pub const fn resize<const M: usize>(self) -> TinyAsciiStr<M> {
362 let mut bytes = [0; M];
363 let mut i = 0;
364 #[expect(clippy::indexing_slicing)]
366 while i < M && i < N {
367 bytes[i] = self.bytes[i] as u8;
368 i += 1;
369 }
370 unsafe { TinyAsciiStr::from_utf8_unchecked(bytes) }
373 }
374
375 #[inline]
376 #[must_use]
377 pub const fn concat<const M: usize, const Q: usize>(
402 self,
403 other: TinyAsciiStr<M>,
404 ) -> TinyAsciiStr<Q> {
405 let mut result = self.resize::<Q>();
406 let mut i = self.len();
407 let mut j = 0;
408 #[expect(clippy::indexing_slicing)]
410 while i < Q && j < M {
411 result.bytes[i] = other.bytes[j];
412 i += 1;
413 j += 1;
414 }
415 result
416 }
417
418 #[must_use]
422 pub const unsafe fn from_utf8_unchecked(code_units: [u8; N]) -> Self {
423 Self {
424 bytes: AsciiByte::to_ascii_byte_array(&code_units),
425 }
426 }
427}
428
429macro_rules! check_is {
430 ($self:ident, $check_int:ident, $check_u8:ident) => {
431 if N <= 4 {
432 Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
433 } else if N <= 8 {
434 Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
435 } else {
436 let mut i = 0;
437 while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
438 if !($self.bytes[i] as u8).$check_u8() {
439 return false;
440 }
441 i += 1;
442 }
443 true
444 }
445 };
446 ($self:ident, $check_int:ident, !$check_u8_0_inv:ident, !$check_u8_1_inv:ident) => {
447 if N <= 4 {
448 Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
449 } else if N <= 8 {
450 Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
451 } else {
452 if ($self.bytes[0] as u8).$check_u8_0_inv() {
454 return false;
455 }
456 let mut i = 1;
457 while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
458 if ($self.bytes[i] as u8).$check_u8_1_inv() {
459 return false;
460 }
461 i += 1;
462 }
463 true
464 }
465 };
466 ($self:ident, $check_int:ident, $check_u8_0_inv:ident, $check_u8_1_inv:ident) => {
467 if N <= 4 {
468 Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
469 } else if N <= 8 {
470 Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
471 } else {
472 if !($self.bytes[0] as u8).$check_u8_0_inv() {
474 return false;
475 }
476 let mut i = 1;
477 while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
478 if !($self.bytes[i] as u8).$check_u8_1_inv() {
479 return false;
480 }
481 i += 1;
482 }
483 true
484 }
485 };
486}
487
488impl<const N: usize> TinyAsciiStr<N> {
489 #[inline]
506 #[must_use]
507 pub const fn is_ascii_alphabetic(&self) -> bool {
508 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)
509 }
510
511 #[inline]
529 #[must_use]
530 pub const fn is_ascii_alphanumeric(&self) -> bool {
531 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)
532 }
533
534 #[inline]
550 #[must_use]
551 pub const fn is_ascii_numeric(&self) -> bool {
552 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)
553 }
554
555 #[inline]
573 #[must_use]
574 pub const fn is_ascii_lowercase(&self) -> bool {
575 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!(
576 self,
577 is_ascii_lowercase,
578 !is_ascii_uppercase,
579 !is_ascii_uppercase
580 )
581 }
582
583 #[inline]
602 #[must_use]
603 pub const fn is_ascii_titlecase(&self) -> bool {
604 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!(
605 self,
606 is_ascii_titlecase,
607 !is_ascii_lowercase,
608 !is_ascii_uppercase
609 )
610 }
611
612 #[inline]
630 #[must_use]
631 pub const fn is_ascii_uppercase(&self) -> bool {
632 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!(
633 self,
634 is_ascii_uppercase,
635 !is_ascii_lowercase,
636 !is_ascii_lowercase
637 )
638 }
639
640 #[inline]
662 #[must_use]
663 pub const fn is_ascii_alphabetic_lowercase(&self) -> bool {
664 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!(
665 self,
666 is_ascii_alphabetic_lowercase,
667 is_ascii_lowercase,
668 is_ascii_lowercase
669 )
670 }
671
672 #[inline]
692 #[must_use]
693 pub const fn is_ascii_alphabetic_titlecase(&self) -> bool {
694 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!(
695 self,
696 is_ascii_alphabetic_titlecase,
697 is_ascii_uppercase,
698 is_ascii_lowercase
699 )
700 }
701
702 #[inline]
724 #[must_use]
725 pub const fn is_ascii_alphabetic_uppercase(&self) -> bool {
726 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!(
727 self,
728 is_ascii_alphabetic_uppercase,
729 is_ascii_uppercase,
730 is_ascii_uppercase
731 )
732 }
733}
734
735macro_rules! to {
736 ($self:ident, $to:ident, $later_char_to:ident $(,$first_char_to:ident)?) => {{
737 let mut i = 0;
738 if N <= 4 {
739 let aligned = Aligned4::from_ascii_bytes(&$self.bytes).$to();
740 #[expect(clippy::indexing_slicing)]
742 while i < N {
743 $self.bytes[i] = aligned[i];
744 i += 1;
745 }
746 } else if N <= 8 {
747 let aligned = Aligned8::from_ascii_bytes(&$self.bytes).$to();
748 #[expect(clippy::indexing_slicing)]
750 while i < N {
751 $self.bytes[i] = aligned[i];
752 i += 1;
753 }
754 } else {
755 while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
756 $self.bytes[i] = $self.bytes[i].$later_char_to();
757 i += 1;
758 }
759 $(
760 $self.bytes[0] = $self.bytes[0].$first_char_to();
761 )?
762 }
763 $self
764 }};
765}
766
767impl<const N: usize> TinyAsciiStr<N> {
768 #[inline]
782 #[must_use]
783 pub const fn to_ascii_lowercase(mut self) -> Self {
784 {
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)
785 }
786
787 #[inline]
802 #[must_use]
803 pub const fn to_ascii_titlecase(mut self) -> Self {
804 {
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!(
805 self,
806 to_ascii_titlecase,
807 to_ascii_lowercase,
808 to_ascii_uppercase
809 )
810 }
811
812 #[inline]
826 #[must_use]
827 pub const fn to_ascii_uppercase(mut self) -> Self {
828 {
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)
829 }
830}
831
832impl<const N: usize> fmt::Debug for TinyAsciiStr<N> {
833 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
834 fmt::Debug::fmt(self.as_str(), f)
835 }
836}
837
838impl<const N: usize> fmt::Display for TinyAsciiStr<N> {
839 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
840 fmt::Display::fmt(self.as_str(), f)
841 }
842}
843
844impl<const N: usize> Deref for TinyAsciiStr<N> {
845 type Target = str;
846 #[inline]
847 fn deref(&self) -> &str {
848 self.as_str()
849 }
850}
851
852impl<const N: usize> Default for TinyAsciiStr<N> {
853 fn default() -> Self {
854 Self::default()
855 }
856}
857
858impl<const N: usize> Borrow<str> for TinyAsciiStr<N> {
859 #[inline]
860 fn borrow(&self) -> &str {
861 self.as_str()
862 }
863}
864
865impl<const N: usize> FromStr for TinyAsciiStr<N> {
866 type Err = ParseError;
867 #[inline]
868 fn from_str(s: &str) -> Result<Self, Self::Err> {
869 Self::try_from_str(s)
870 }
871}
872
873impl<const N: usize> PartialEq<str> for TinyAsciiStr<N> {
874 fn eq(&self, other: &str) -> bool {
875 self.deref() == other
876 }
877}
878
879impl<const N: usize> PartialEq<&str> for TinyAsciiStr<N> {
880 fn eq(&self, other: &&str) -> bool {
881 self.deref() == *other
882 }
883}
884
885#[cfg(feature = "alloc")]
886impl<const N: usize> PartialEq<String> for TinyAsciiStr<N> {
887 fn eq(&self, other: &String) -> bool {
888 self.deref() == other.deref()
889 }
890}
891
892#[cfg(feature = "alloc")]
893impl<const N: usize> PartialEq<TinyAsciiStr<N>> for String {
894 fn eq(&self, other: &TinyAsciiStr<N>) -> bool {
895 self.deref() == other.deref()
896 }
897}
898
899#[cfg(test)]
900mod test {
901 use super::*;
902 use rand::distr::Distribution;
903 use rand::distr::StandardUniform;
904 use rand::rngs::SmallRng;
905 use rand::SeedableRng;
906
907 const STRINGS: [&str; 26] = [
908 "Latn",
909 "laTn",
910 "windows",
911 "AR",
912 "Hans",
913 "macos",
914 "AT",
915 "infiniband",
916 "FR",
917 "en",
918 "Cyrl",
919 "FromIntegral",
920 "NO",
921 "419",
922 "MacintoshOSX2019",
923 "a3z",
924 "A3z",
925 "A3Z",
926 "a3Z",
927 "3A",
928 "3Z",
929 "3a",
930 "3z",
931 "@@[`{",
932 "UK",
933 "E12",
934 ];
935
936 fn gen_strings(num_strings: usize, allowed_lengths: &[usize]) -> Vec<String> {
937 use rand::seq::IndexedRandom;
938 let mut rng = SmallRng::seed_from_u64(2022);
939 let string_lengths = core::iter::repeat_with(|| *allowed_lengths.choose(&mut rng).unwrap())
941 .take(num_strings)
942 .collect::<Vec<usize>>();
943 string_lengths
944 .iter()
945 .map(|len| {
946 StandardUniform
947 .sample_iter(&mut rng)
948 .filter(|b: &u8| *b > 0 && *b < 0x80)
949 .take(*len)
950 .collect::<Vec<u8>>()
951 })
952 .map(|byte_vec| String::from_utf8(byte_vec).expect("All ASCII"))
953 .collect()
954 }
955
956 fn check_operation<T, F1, F2, const N: usize>(reference_f: F1, tinystr_f: F2)
957 where
958 F1: Fn(&str) -> T,
959 F2: Fn(TinyAsciiStr<N>) -> T,
960 T: fmt::Debug + PartialEq,
961 {
962 for s in STRINGS
963 .into_iter()
964 .map(str::to_owned)
965 .chain(gen_strings(100, &[3, 4, 5, 8, 12]))
966 {
967 let t = match TinyAsciiStr::<N>::from_str(&s) {
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 let s_utf16: Vec<u16> = s.encode_utf16().collect();
977 let t = match TinyAsciiStr::<N>::try_from_utf16(&s_utf16) {
978 Ok(t) => t,
979 Err(ParseError::TooLong { .. }) => continue,
980 Err(e) => panic!("{}", e),
981 };
982 let expected = reference_f(&s);
983 let actual = tinystr_f(t);
984 assert_eq!(expected, actual, "TinyAsciiStr<{N}>: {s:?}");
985 }
986 }
987
988 #[test]
989 fn test_is_ascii_alphabetic() {
990 fn check<const N: usize>() {
991 check_operation(
992 |s| s.chars().all(|c| c.is_ascii_alphabetic()),
993 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic(&t),
994 )
995 }
996 check::<2>();
997 check::<3>();
998 check::<4>();
999 check::<5>();
1000 check::<8>();
1001 check::<16>();
1002 }
1003
1004 #[test]
1005 fn test_is_ascii_alphanumeric() {
1006 fn check<const N: usize>() {
1007 check_operation(
1008 |s| s.chars().all(|c| c.is_ascii_alphanumeric()),
1009 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphanumeric(&t),
1010 )
1011 }
1012 check::<2>();
1013 check::<3>();
1014 check::<4>();
1015 check::<5>();
1016 check::<8>();
1017 check::<16>();
1018 }
1019
1020 #[test]
1021 fn test_is_ascii_numeric() {
1022 fn check<const N: usize>() {
1023 check_operation(
1024 |s| s.chars().all(|c| c.is_ascii_digit()),
1025 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_numeric(&t),
1026 )
1027 }
1028 check::<2>();
1029 check::<3>();
1030 check::<4>();
1031 check::<5>();
1032 check::<8>();
1033 check::<16>();
1034 }
1035
1036 #[test]
1037 fn test_is_ascii_lowercase() {
1038 fn check<const N: usize>() {
1039 check_operation(
1040 |s| {
1041 s == TinyAsciiStr::<16>::try_from_str(s)
1042 .unwrap()
1043 .to_ascii_lowercase()
1044 .as_str()
1045 },
1046 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_lowercase(&t),
1047 )
1048 }
1049 check::<2>();
1050 check::<3>();
1051 check::<4>();
1052 check::<5>();
1053 check::<8>();
1054 check::<16>();
1055 }
1056
1057 #[test]
1058 fn test_is_ascii_titlecase() {
1059 fn check<const N: usize>() {
1060 check_operation(
1061 |s| {
1062 s == TinyAsciiStr::<16>::try_from_str(s)
1063 .unwrap()
1064 .to_ascii_titlecase()
1065 .as_str()
1066 },
1067 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_titlecase(&t),
1068 )
1069 }
1070 check::<2>();
1071 check::<3>();
1072 check::<4>();
1073 check::<5>();
1074 check::<8>();
1075 check::<16>();
1076 }
1077
1078 #[test]
1079 fn test_is_ascii_uppercase() {
1080 fn check<const N: usize>() {
1081 check_operation(
1082 |s| {
1083 s == TinyAsciiStr::<16>::try_from_str(s)
1084 .unwrap()
1085 .to_ascii_uppercase()
1086 .as_str()
1087 },
1088 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_uppercase(&t),
1089 )
1090 }
1091 check::<2>();
1092 check::<3>();
1093 check::<4>();
1094 check::<5>();
1095 check::<8>();
1096 check::<16>();
1097 }
1098
1099 #[test]
1100 fn test_is_ascii_alphabetic_lowercase() {
1101 fn check<const N: usize>() {
1102 check_operation(
1103 |s| {
1104 s.chars().all(|c| c.is_ascii_alphabetic()) &&
1106 s == TinyAsciiStr::<16>::try_from_str(s)
1108 .unwrap()
1109 .to_ascii_lowercase()
1110 .as_str()
1111 },
1112 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_lowercase(&t),
1113 )
1114 }
1115 check::<2>();
1116 check::<3>();
1117 check::<4>();
1118 check::<5>();
1119 check::<8>();
1120 check::<16>();
1121 }
1122
1123 #[test]
1124 fn test_is_ascii_alphabetic_titlecase() {
1125 fn check<const N: usize>() {
1126 check_operation(
1127 |s| {
1128 s.chars().all(|c| c.is_ascii_alphabetic()) &&
1130 s == TinyAsciiStr::<16>::try_from_str(s)
1132 .unwrap()
1133 .to_ascii_titlecase()
1134 .as_str()
1135 },
1136 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_titlecase(&t),
1137 )
1138 }
1139 check::<2>();
1140 check::<3>();
1141 check::<4>();
1142 check::<5>();
1143 check::<8>();
1144 check::<16>();
1145 }
1146
1147 #[test]
1148 fn test_is_ascii_alphabetic_uppercase() {
1149 fn check<const N: usize>() {
1150 check_operation(
1151 |s| {
1152 s.chars().all(|c| c.is_ascii_alphabetic()) &&
1154 s == TinyAsciiStr::<16>::try_from_str(s)
1156 .unwrap()
1157 .to_ascii_uppercase()
1158 .as_str()
1159 },
1160 |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_uppercase(&t),
1161 )
1162 }
1163 check::<2>();
1164 check::<3>();
1165 check::<4>();
1166 check::<5>();
1167 check::<8>();
1168 check::<16>();
1169 }
1170
1171 #[test]
1172 fn test_to_ascii_lowercase() {
1173 fn check<const N: usize>() {
1174 check_operation(
1175 |s| {
1176 s.chars()
1177 .map(|c| c.to_ascii_lowercase())
1178 .collect::<String>()
1179 },
1180 |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_lowercase(t).as_str().to_owned(),
1181 )
1182 }
1183 check::<2>();
1184 check::<3>();
1185 check::<4>();
1186 check::<5>();
1187 check::<8>();
1188 check::<16>();
1189 }
1190
1191 #[test]
1192 fn test_to_ascii_titlecase() {
1193 fn check<const N: usize>() {
1194 check_operation(
1195 |s| {
1196 let mut r = s
1197 .chars()
1198 .map(|c| c.to_ascii_lowercase())
1199 .collect::<String>();
1200 unsafe { r.as_bytes_mut()[0].make_ascii_uppercase() };
1202 r
1203 },
1204 |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_titlecase(t).as_str().to_owned(),
1205 )
1206 }
1207 check::<2>();
1208 check::<3>();
1209 check::<4>();
1210 check::<5>();
1211 check::<8>();
1212 check::<16>();
1213 }
1214
1215 #[test]
1216 fn test_to_ascii_uppercase() {
1217 fn check<const N: usize>() {
1218 check_operation(
1219 |s| {
1220 s.chars()
1221 .map(|c| c.to_ascii_uppercase())
1222 .collect::<String>()
1223 },
1224 |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_uppercase(t).as_str().to_owned(),
1225 )
1226 }
1227 check::<2>();
1228 check::<3>();
1229 check::<4>();
1230 check::<5>();
1231 check::<8>();
1232 check::<16>();
1233 }
1234
1235 #[test]
1236 fn lossy_constructor() {
1237 assert_eq!(TinyAsciiStr::<4>::from_utf8_lossy(b"", b'?').as_str(), "");
1238 assert_eq!(
1239 TinyAsciiStr::<4>::from_utf8_lossy(b"oh\0o", b'?').as_str(),
1240 "oh?o"
1241 );
1242 assert_eq!(
1243 TinyAsciiStr::<4>::from_utf8_lossy(b"\0", b'?').as_str(),
1244 "?"
1245 );
1246 assert_eq!(
1247 TinyAsciiStr::<4>::from_utf8_lossy(b"toolong", b'?').as_str(),
1248 "tool"
1249 );
1250 assert_eq!(
1251 TinyAsciiStr::<4>::from_utf8_lossy(&[b'a', 0x80, 0xFF, b'1'], b'?').as_str(),
1252 "a??1"
1253 );
1254 }
1255
1256 #[test]
1257 #[should_panic(expected = "replacement must be a non-null ASCII byte")]
1258 fn from_utf8_lossy_rejects_non_ascii_replacement() {
1259 let _ = TinyAsciiStr::<4>::from_utf8_lossy(b"\xFF", 0xFF);
1263 }
1264
1265 #[test]
1266 #[should_panic(expected = "replacement must be a non-null ASCII byte")]
1267 fn from_utf16_lossy_rejects_non_ascii_replacement() {
1268 let _ = TinyAsciiStr::<4>::from_utf16_lossy(&[0xFFFF], 0xFF);
1269 }
1270
1271 #[test]
1272 #[should_panic(expected = "replacement must be a non-null ASCII byte")]
1273 fn from_utf8_lossy_rejects_null_replacement() {
1274 let _ = TinyAsciiStr::<4>::from_utf8_lossy(b"\xFF", 0x00);
1276 }
1277}