1use std::borrow::Borrow;
2use std::borrow::BorrowMut;
3use std::borrow::Cow;
4use std::cmp;
5use std::fmt;
6use std::hash::Hash;
7use std::hash::Hasher;
8use std::ops;
9use std::rc::Rc;
10use std::str::from_utf8_unchecked;
11use std::str::from_utf8_unchecked_mut;
12use std::sync::Arc;
13
14use crate::DisplayExt;
15
16mod error;
17#[cfg(feature = "serde")]
18mod with_serde;
19
20pub use error::InsertError;
21pub use error::RemoveError;
22pub use error::ReplaceRangeError;
23pub use error::SplitOffError;
24pub use error::TryFromBytesError;
25pub use error::TryFromStrError;
26
27#[derive(Clone, Copy)]
28#[repr(C)]
29pub struct PascalString<const CAPACITY: usize> {
30 len: u8,
31 data: [u8; CAPACITY],
32}
33
34macro_rules! snapshot_as_str {
40 ($ps:expr, $buf:ident, $name:ident) => {
41 let len = $ps.len();
42 let mut $buf = [0_u8; CAPACITY];
43 $buf[..len].copy_from_slice(&$ps.data[..len]);
44 let $name = unsafe { from_utf8_unchecked(&$buf[..len]) };
46 };
47}
48
49impl<const CAPACITY: usize> PascalString<CAPACITY> {
50 #[inline]
51 fn validate_range_bounds(&self, start: usize, end: usize) -> Result<(), ReplaceRangeError> {
52 let len = self.len();
53 if start > end || end > len {
54 return Err(ReplaceRangeError::OutOfBounds { start, end, len });
55 }
56 if !self.is_char_boundary(start) {
57 return Err(ReplaceRangeError::NotCharBoundary { idx: start });
58 }
59 if !self.is_char_boundary(end) {
60 return Err(ReplaceRangeError::NotCharBoundary { idx: end });
61 }
62 Ok(())
63 }
64
65 #[inline]
66 fn try_replace_range_bounds_impl(
67 &mut self,
68 start: usize,
69 end: usize,
70 replace_with: &str,
71 ) -> Result<(), ReplaceRangeError> {
72 self.validate_range_bounds(start, end)?;
73
74 let len = self.len();
75 let range_len = end - start;
76 let repl_len = replace_with.len();
77 let new_len = len - range_len + repl_len;
78 if new_len > CAPACITY {
79 return Err(ReplaceRangeError::TooLong);
80 }
81
82 if repl_len > range_len {
83 let delta = repl_len - range_len;
84 self.data.copy_within(end..len, end + delta);
85 } else if repl_len < range_len {
86 let new_tail_start = start + repl_len;
87 self.data.copy_within(end..len, new_tail_start);
88 self.data[new_len..len].fill(0);
90 }
91
92 self.data[start..start + repl_len].copy_from_slice(replace_with.as_bytes());
93 self.len = new_len as u8;
94 Ok(())
95 }
96
97 #[inline]
101 pub fn try_replace_range_bounds(
102 &mut self,
103 start: usize,
104 end: usize,
105 replace_with: &str,
106 ) -> Result<(), ReplaceRangeError> {
107 self.try_replace_range_bounds_impl(start, end, replace_with)
108 }
109
110 #[inline]
116 pub fn try_replace_range_bounds_truncated<'s>(
117 &mut self,
118 start: usize,
119 end: usize,
120 replace_with: &'s str,
121 ) -> Result<&'s str, ReplaceRangeError> {
122 self.validate_range_bounds(start, end)?;
123
124 let len = self.len();
125 let range_len = end - start;
126 let max_repl_len = CAPACITY - (len - range_len);
127 if replace_with.len() <= max_repl_len {
128 self.try_replace_range_bounds_impl(start, end, replace_with)?;
129 return Ok("");
130 }
131
132 let mut prefix_len = 0;
133 for c in replace_with.chars() {
134 let l = c.len_utf8();
135 if prefix_len + l > max_repl_len {
136 break;
137 }
138 prefix_len += l;
139 }
140
141 let (prefix, remainder) = replace_with.split_at(prefix_len);
142 self.try_replace_range_bounds_impl(start, end, prefix)?;
143 Ok(remainder)
144 }
145
146 #[inline]
148 pub fn replace_range_bounds_expect_capacity(
149 &mut self,
150 start: usize,
151 end: usize,
152 replace_with: &str,
153 ) {
154 self.try_replace_range_bounds(start, end, replace_with)
155 .expect("PascalString replace_range failed");
156 }
157
158 #[inline]
160 pub fn replace_range_bounds_truncated<'s>(
161 &mut self,
162 start: usize,
163 end: usize,
164 replace_with: &'s str,
165 ) -> &'s str {
166 self.try_replace_range_bounds_truncated(start, end, replace_with)
167 .expect("invalid range or char boundary")
168 }
169 pub const CAPACITY: usize = {
170 assert!(
171 CAPACITY <= u8::MAX as usize,
172 "PascalString max capacity is 255"
173 );
174 CAPACITY
175 };
176
177 #[inline(always)]
178 pub const fn new() -> Self {
179 let _ = Self::CAPACITY;
182
183 Self {
184 len: 0,
185 data: [0; CAPACITY],
186 }
187 }
188
189 pub const fn try_from_str_const(string: &str) -> Option<Self> {
195 let _ = Self::CAPACITY;
196
197 if string.len() > CAPACITY {
198 return None;
199 }
200 let mut this = PascalString {
201 len: string.len() as u8,
202 data: [0; CAPACITY],
203 };
204 let bytes = string.as_bytes();
205 let mut i = 0;
206 while i < string.len() {
207 this.data[i] = bytes[i];
208 i += 1;
209 }
210 Some(this)
211 }
212
213 #[inline]
218 pub fn from_str_truncated(string: &str) -> Self {
219 let _ = Self::CAPACITY;
220
221 if let Ok(ps) = Self::try_from(string) {
222 return ps;
223 }
224
225 let mut ps = Self::new();
226 ps.push_str_truncated(string);
227 ps
228 }
229
230 #[inline(always)]
231 pub const fn into_inner(self) -> (u8, [u8; CAPACITY]) {
232 (self.len, self.data)
233 }
234
235 #[inline(always)]
236 pub const fn capacity(&self) -> usize {
237 CAPACITY
238 }
239
240 #[inline(always)]
241 pub const fn len(&self) -> usize {
242 self.len as usize
243 }
244
245 #[inline(always)]
246 pub const fn is_empty(&self) -> bool {
247 self.len == 0
248 }
249
250 #[inline(always)]
251 pub fn as_str(&self) -> &str {
252 self
253 }
254
255 #[inline]
260 #[must_use]
261 pub fn as_bytes(&self) -> &[u8] {
262 self.as_str().as_bytes()
263 }
264
265 #[inline(always)]
266 pub fn as_mut_str(&mut self) -> &mut str {
267 self
268 }
269
270 #[inline(always)]
271 #[deprecated(
272 note = "Use `as_mut_str()` (this method name suggests `&mut str` but returns `&str`)."
273 )]
274 pub fn as_str_mut(&mut self) -> &str {
275 self
276 }
277
278 #[inline]
279 pub fn try_push_str(&mut self, string: &str) -> Result<(), TryFromStrError> {
280 let len = self.len();
281 let new_len = len + string.len();
282
283 if new_len > CAPACITY {
284 return Err(TryFromStrError::TooLong);
285 }
286
287 self.data[len..new_len].copy_from_slice(string.as_bytes());
288 self.len = new_len as u8;
289
290 Ok(())
291 }
292
293 #[inline]
294 pub fn try_push(&mut self, ch: char) -> Result<(), TryFromStrError> {
295 self.try_push_str(ch.encode_utf8(&mut [0; 4]))
297 }
298
299 #[inline]
303 #[deprecated(
304 note = "PascalString is fixed-capacity; prefer `try_push_str`, `push_str_truncated`, or `push_str_expect_capacity`."
305 )]
306 pub fn push_str(&mut self, string: &str) {
307 self.push_str_expect_capacity(string);
308 }
309
310 #[inline]
314 #[deprecated(
315 note = "PascalString is fixed-capacity; prefer `try_push`, `push_str_truncated`, or `push_expect_capacity`."
316 )]
317 pub fn push(&mut self, ch: char) {
318 self.push_expect_capacity(ch);
319 }
320
321 #[inline]
323 pub fn push_str_expect_capacity(&mut self, string: &str) {
324 self.try_push_str(string)
325 .expect("PascalString capacity exceeded");
326 }
327
328 #[inline]
330 pub fn push_expect_capacity(&mut self, ch: char) {
331 self.try_push(ch).expect("PascalString capacity exceeded");
332 }
333
334 #[inline]
338 pub fn try_insert_str(&mut self, idx: usize, string: &str) -> Result<(), InsertError> {
339 let len = self.len();
340 if idx > len {
341 return Err(InsertError::OutOfBounds { idx, len });
342 }
343 if !self.is_char_boundary(idx) {
344 return Err(InsertError::NotCharBoundary { idx });
345 }
346
347 let insert_len = string.len();
348 let new_len = len + insert_len;
349 if new_len > CAPACITY {
350 return Err(InsertError::TooLong);
351 }
352
353 self.data.copy_within(idx..len, idx + insert_len);
355 self.data[idx..idx + insert_len].copy_from_slice(string.as_bytes());
357 self.len = new_len as u8;
358 Ok(())
359 }
360
361 #[inline]
367 pub fn try_insert_str_truncated<'s>(
368 &mut self,
369 idx: usize,
370 string: &'s str,
371 ) -> Result<&'s str, InsertError> {
372 let len = self.len();
373 if idx > len {
374 return Err(InsertError::OutOfBounds { idx, len });
375 }
376 if !self.is_char_boundary(idx) {
377 return Err(InsertError::NotCharBoundary { idx });
378 }
379
380 let available = CAPACITY.saturating_sub(len);
381 if available >= string.len() {
382 self.try_insert_str(idx, string)?;
383 return Ok("");
384 }
385
386 let mut prefix_len = 0;
387 for c in string.chars() {
388 let l = c.len_utf8();
389 if prefix_len + l > available {
390 break;
391 }
392 prefix_len += l;
393 }
394
395 let (prefix, remainder) = string.split_at(prefix_len);
396 self.try_insert_str(idx, prefix)?;
398 Ok(remainder)
399 }
400
401 #[inline]
405 pub fn insert_str_truncated<'s>(&mut self, idx: usize, string: &'s str) -> &'s str {
406 self.try_insert_str_truncated(idx, string)
407 .expect("invalid index or char boundary")
408 }
409
410 #[inline]
414 pub fn insert_str_expect_capacity(&mut self, idx: usize, string: &str) {
415 self.try_insert_str(idx, string)
416 .expect("PascalString insert failed");
417 }
418
419 #[inline]
421 #[deprecated(
422 note = "PascalString is fixed-capacity; prefer `try_insert_str`, `try_insert_str_truncated`, or `insert_str_expect_capacity`."
423 )]
424 pub fn insert_str(&mut self, idx: usize, string: &str) {
425 self.insert_str_expect_capacity(idx, string);
426 }
427
428 #[inline]
432 pub fn try_insert(&mut self, idx: usize, ch: char) -> Result<(), InsertError> {
433 let mut buf = [0_u8; 4];
434 let s = ch.encode_utf8(&mut buf);
435 self.try_insert_str(idx, s)
436 }
437
438 #[inline]
440 pub fn insert_expect_capacity(&mut self, idx: usize, ch: char) {
441 self.try_insert(idx, ch)
442 .expect("PascalString insert failed");
443 }
444
445 #[inline]
447 #[deprecated(
448 note = "PascalString is fixed-capacity; prefer `try_insert`, `try_insert_str_truncated`, or `insert_expect_capacity`."
449 )]
450 pub fn insert(&mut self, idx: usize, ch: char) {
451 self.insert_expect_capacity(idx, ch);
452 }
453
454 #[inline]
461 pub fn remove(&mut self, idx: usize) -> char {
462 let len = self.len();
463 assert!(idx < len, "index out of bounds");
464 assert!(self.is_char_boundary(idx), "index is not a char boundary");
465
466 let ch = self.as_str()[idx..].chars().next().expect("idx < len");
467 let ch_len = ch.len_utf8();
468
469 self.data.copy_within(idx + ch_len..len, idx);
471 let new_len = len - ch_len;
472 self.len = new_len as u8;
473
474 self.data[new_len..len].fill(0);
476
477 ch
478 }
479
480 #[inline]
484 pub fn try_remove(&mut self, idx: usize) -> Result<char, RemoveError> {
485 let len = self.len();
486 if idx >= len {
487 return Err(RemoveError::OutOfBounds { idx, len });
488 }
489 if !self.is_char_boundary(idx) {
490 return Err(RemoveError::NotCharBoundary { idx });
491 }
492 Ok(self.remove(idx))
493 }
494
495 #[inline]
497 pub fn push_str_truncated<'s>(&mut self, string: &'s str) -> &'s str {
498 if self.try_push_str(string).is_ok() {
499 return "";
500 }
501
502 let mut new_len = self.len();
506 for c in string.chars() {
507 let len = c.len_utf8();
508 if new_len + len > CAPACITY {
509 break;
510 };
511 new_len += len;
512 }
513
514 let pos = new_len - self.len();
515 let (substring, remainder) = string.split_at(pos);
516 self.try_push_str(substring).unwrap();
517
518 remainder
519 }
520
521 #[inline]
522 pub fn truncate(&mut self, new_len: usize) {
523 if new_len <= self.len() {
524 assert!(self.is_char_boundary(new_len));
525 self.len = new_len as u8;
526 }
527 }
528
529 #[inline]
530 pub fn pop(&mut self) -> Option<char> {
531 let ch = self.chars().next_back()?;
532 let newlen = self.len() - ch.len_utf8();
533 self.len = newlen as u8;
534 Some(ch)
535 }
536
537 #[inline]
538 pub fn clear(&mut self) {
539 self.len = 0;
540 }
541
542 #[inline]
547 pub fn retain<F>(&mut self, mut f: F)
548 where
549 F: FnMut(char) -> bool,
550 {
551 snapshot_as_str!(self, src, s);
552 let len = s.len();
553
554 let mut write = 0_usize;
555 let mut buf = [0_u8; 4];
556 for ch in s.chars() {
557 if f(ch) {
558 let part = ch.encode_utf8(&mut buf);
559 let bytes = part.as_bytes();
560 let end = write + bytes.len();
561 self.data[write..end].copy_from_slice(bytes);
563 write = end;
564 }
565 }
566
567 self.data[write..len].fill(0);
569 self.len = write as u8;
570 }
571
572 #[inline]
582 #[must_use]
583 pub fn split_off(&mut self, at: usize) -> PascalString<CAPACITY> {
584 assert!(
585 self.as_str().is_char_boundary(at),
586 "split_off: index {at} is not a char boundary (len={})",
587 self.len()
588 );
589 let tail = &self.as_str()[at..];
592 let result = PascalString::try_from(tail).expect("tail fits in same capacity");
594 self.truncate(at);
595 result
596 }
597
598 #[inline]
602 pub fn try_split_off(&mut self, at: usize) -> Result<PascalString<CAPACITY>, SplitOffError> {
603 let len = self.len();
604 if at > len {
605 return Err(SplitOffError::OutOfBounds { at, len });
606 }
607 if !self.as_str().is_char_boundary(at) {
608 return Err(SplitOffError::NotCharBoundary { at });
609 }
610 let tail = &self.as_str()[at..];
612 let result = PascalString::try_from(tail).expect("tail fits in same capacity");
613 self.truncate(at);
614 Ok(result)
615 }
616
617 #[inline]
624 pub fn remove_matches(&mut self, pat: &str) {
625 if pat.is_empty() || self.is_empty() {
626 return;
627 }
628
629 snapshot_as_str!(self, src, s);
630 let len = s.len();
631
632 let mut read = 0_usize;
633 let mut write = 0_usize;
634
635 while read < len {
636 if s[read..].starts_with(pat) {
637 read += pat.len();
638 } else {
639 let ch_len = s[read..]
641 .chars()
642 .next()
643 .expect("read < len implies at least one char")
644 .len_utf8();
645 self.data[write..write + ch_len].copy_from_slice(&src[read..read + ch_len]);
646 write += ch_len;
647 read += ch_len;
648 }
649 }
650
651 self.data[write..len].fill(0);
653 self.len = write as u8;
654 }
655}
656
657impl<const CAPACITY: usize> Default for PascalString<CAPACITY> {
660 #[inline(always)]
661 fn default() -> Self {
662 let _ = Self::CAPACITY;
663
664 Self::new()
665 }
666}
667
668impl<T: ops::Deref<Target = str> + ?Sized, const CAPACITY: usize> PartialEq<T>
669 for PascalString<CAPACITY>
670{
671 #[inline(always)]
672 fn eq(&self, other: &T) -> bool {
673 self.as_str().eq(other.deref())
674 }
675}
676
677macro_rules! impl_reverse_eq_for_str_types {
678 ($($t:ty),*) => {
679 $(
680 impl<const CAPACITY: usize> PartialEq<PascalString<CAPACITY>> for $t {
681 #[inline(always)]
682 fn eq(&self, other: &PascalString<CAPACITY>) -> bool {
683 let a: &str = self.as_ref();
684 let b = other.as_str();
685 a.eq(b)
686 }
687 }
688
689 impl<const CAPACITY: usize> PartialEq<PascalString<CAPACITY>> for &$t {
690 #[inline(always)]
691 fn eq(&self, other: &PascalString<CAPACITY>) -> bool {
692 let a: &str = self.as_ref();
693 let b = other.as_str();
694 a.eq(b)
695 }
696 }
697
698 impl<const CAPACITY: usize> PartialEq<PascalString<CAPACITY>> for &mut $t {
699 #[inline(always)]
700 fn eq(&self, other: &PascalString<CAPACITY>) -> bool {
701 let a: &str = self.as_ref();
702 let b = other.as_str();
703 a.eq(b)
704 }
705 }
706 )*
707 };
708}
709
710impl_reverse_eq_for_str_types!(String, str, Cow<'_, str>, Box<str>, Rc<str>, Arc<str>);
711
712impl<const CAPACITY: usize> Eq for PascalString<CAPACITY> {}
713
714impl<T: ops::Deref<Target = str>, const CAPACITY: usize> PartialOrd<T> for PascalString<CAPACITY> {
715 #[inline(always)]
716 fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
717 self.as_str().partial_cmp(other.deref())
718 }
719}
720
721impl<const CAPACITY: usize> Ord for PascalString<CAPACITY> {
722 #[inline(always)]
723 fn cmp(&self, other: &Self) -> cmp::Ordering {
724 self.as_str().cmp(other.as_str())
725 }
726}
727
728impl<const CAPACITY: usize> Hash for PascalString<CAPACITY> {
729 #[inline(always)]
730 fn hash<H: Hasher>(&self, state: &mut H) {
731 self.as_str().hash(state)
732 }
733}
734
735impl<const CAPACITY: usize> fmt::Debug for PascalString<CAPACITY> {
738 #[inline(always)]
739 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
740 let name: PascalString<39> = format_args!("PascalString<{CAPACITY}>")
741 .try_to_fmt()
742 .unwrap_or_else(|_| "PascalString<?>".to_fmt());
743 f.debug_tuple(&name).field(&self.as_str()).finish()
744 }
745}
746
747impl<const CAPACITY: usize> fmt::Display for PascalString<CAPACITY> {
748 #[inline(always)]
749 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
750 self.as_str().fmt(f)
751 }
752}
753
754impl<const CAPACITY: usize> ops::Deref for PascalString<CAPACITY> {
757 type Target = str;
758
759 #[inline(always)]
760 fn deref(&self) -> &Self::Target {
761 let bytes = unsafe { self.data.get_unchecked(..self.len()) };
763 unsafe { from_utf8_unchecked(bytes) }
765 }
766}
767
768impl<const CAPACITY: usize> ops::DerefMut for PascalString<CAPACITY> {
769 #[inline(always)]
770 fn deref_mut(&mut self) -> &mut Self::Target {
771 let len = self.len();
772 let bytes = unsafe { self.data.get_unchecked_mut(..len) };
774 unsafe { from_utf8_unchecked_mut(bytes) }
776 }
777}
778
779impl<const CAPACITY: usize> Borrow<str> for PascalString<CAPACITY> {
780 #[inline(always)]
781 fn borrow(&self) -> &str {
782 self
783 }
784}
785
786impl<const CAPACITY: usize> AsRef<str> for PascalString<CAPACITY> {
787 #[inline(always)]
788 fn as_ref(&self) -> &str {
789 self
790 }
791}
792
793impl<const CAPACITY: usize> AsRef<[u8]> for PascalString<CAPACITY> {
794 #[inline(always)]
795 fn as_ref(&self) -> &[u8] {
796 self.as_bytes()
797 }
798}
799
800impl<const CAPACITY: usize> AsMut<str> for PascalString<CAPACITY> {
801 #[inline(always)]
802 fn as_mut(&mut self) -> &mut str {
803 self
804 }
805}
806
807impl<const CAPACITY: usize> BorrowMut<str> for PascalString<CAPACITY> {
808 #[inline(always)]
809 fn borrow_mut(&mut self) -> &mut str {
810 self
811 }
812}
813
814impl<'a, const CAPACITY: usize> TryFrom<&'a [u8]> for PascalString<CAPACITY> {
817 type Error = TryFromBytesError;
818
819 #[inline]
820 fn try_from(bytes: &'a [u8]) -> Result<PascalString<CAPACITY>, Self::Error> {
821 let _ = Self::CAPACITY;
822
823 let string = core::str::from_utf8(bytes)?;
824 Ok(Self::try_from(string)?)
825 }
826}
827
828impl<'a, const CAPACITY: usize> TryFrom<&'a mut str> for PascalString<CAPACITY> {
829 type Error = TryFromStrError;
830
831 #[inline]
832 fn try_from(value: &'a mut str) -> Result<PascalString<CAPACITY>, Self::Error> {
833 Self::try_from(&*value)
834 }
835}
836
837impl<'a, const CAPACITY: usize> TryFrom<&'a str> for PascalString<CAPACITY> {
838 type Error = TryFromStrError;
839
840 #[inline]
841 fn try_from(value: &'a str) -> Result<PascalString<CAPACITY>, Self::Error> {
842 let _ = Self::CAPACITY;
843
844 let bytes = value.as_bytes();
845 let len = bytes.len();
846
847 if len > CAPACITY {
848 return Err(TryFromStrError::TooLong);
849 }
850
851 let data = match <[u8; CAPACITY]>::try_from(bytes).ok() {
852 Some(data) => data,
853 None => {
854 let mut data = [0; CAPACITY];
855 data[..len].copy_from_slice(bytes);
856 data
857 }
858 };
859
860 let len = len as u8;
861
862 Ok(PascalString { len, data })
863 }
864}
865
866impl<const CAPACITY: usize> TryFrom<char> for PascalString<CAPACITY> {
867 type Error = TryFromStrError;
868
869 #[inline]
870 fn try_from(value: char) -> Result<PascalString<CAPACITY>, Self::Error> {
871 let _ = Self::CAPACITY;
872
873 Self::try_from(value.encode_utf8(&mut [0; 4]))
874 }
875}
876
877impl<const CAPACITY: usize> std::str::FromStr for PascalString<CAPACITY> {
878 type Err = TryFromStrError;
879
880 #[inline]
881 fn from_str(s: &str) -> Result<Self, Self::Err> {
882 Self::try_from(s)
883 }
884}
885
886impl<const CAPACITY: usize> fmt::Write for PascalString<CAPACITY> {
889 #[inline]
890 fn write_str(&mut self, s: &str) -> fmt::Result {
891 self.try_push_str(s).map_err(|_| fmt::Error)
892 }
893}
894
895#[cfg(test)]
898mod tests {
899 use std::mem;
900
901 use super::*;
902
903 #[test]
904 fn test_eq() {
905 use std::fmt::Write;
906
907 let s = String::from("abc");
908 let ps = PascalString::<4>::try_from("abc").unwrap();
909
910 assert_eq!(ps, s);
911 let s = String::from("abcd");
915 let mut ps = PascalString::<4>::new();
916 write!(&mut ps, "abcd").unwrap();
917
918 assert_eq!(ps, s);
919 }
920
921 #[test]
922 fn test_ord() {
923 let ps1 = PascalString::<4>::try_from("abc").unwrap();
924 let ps2 = PascalString::<4>::try_from("abcd").unwrap();
925
926 assert!(ps1 < ps2);
927 assert!(ps1 <= ps2);
928 assert!(ps2 > ps1);
929 assert!(ps2 >= ps1);
930 }
931
932 #[test]
933 fn test_size() {
934 assert_eq!(mem::size_of::<PascalString<0>>(), 1);
935 assert_eq!(mem::size_of::<PascalString<1>>(), 2);
936 assert_eq!(mem::size_of::<PascalString<2>>(), 3);
937 assert_eq!(mem::size_of::<PascalString<3>>(), 4);
938 assert_eq!(mem::size_of::<PascalString<4>>(), 5);
939 }
940
941 fn _test_max_size() {
944 let _x: PascalString<256> = PascalString::default();
956 let _x: PascalString<256> = PascalString::new();
957 let _x: PascalString<256> = PascalString::try_from("").unwrap();
958 let _x: PascalString<256> = PascalString::from_str_truncated("");
959 }
960
961 #[test]
962 fn test_deref() {
963 let ps = PascalString::<3>::try_from("abc").unwrap();
964 let map: std::collections::HashSet<_> = ["abc"].into_iter().collect();
965 assert!(map.contains(&*ps));
966 }
967
968 #[test]
969 fn test_try_push_str_too_long_does_not_modify() {
970 let mut ps = PascalString::<4>::try_from("ab").unwrap();
971 assert_eq!(ps.as_str(), "ab");
972
973 let err = ps.try_push_str("cde").unwrap_err();
974 assert_eq!(err, TryFromStrError::TooLong);
975 assert_eq!(ps.as_str(), "ab");
976 }
977
978 #[test]
979 fn test_try_push_char_too_long_does_not_modify() {
980 let mut ps = PascalString::<3>::new();
981 ps.try_push('€').unwrap(); assert_eq!(ps.as_str(), "€");
983
984 let err = ps.try_push('a').unwrap_err(); assert_eq!(err, TryFromStrError::TooLong);
986 assert_eq!(ps.as_str(), "€");
987 }
988
989 #[test]
990 fn test_push_str_truncated_respects_utf8_boundary() {
991 let mut ps = PascalString::<4>::new();
992
993 let remainder = ps.push_str_truncated("€ab");
995 assert_eq!(ps.as_str(), "€a");
996 assert_eq!(remainder, "b");
997 }
998
999 #[test]
1000 fn test_from_str_truncated_truncates_on_boundary() {
1001 let ps = PascalString::<4>::from_str_truncated("€ab");
1002 assert_eq!(ps.as_str(), "€a");
1003 assert_eq!(ps.len(), 4);
1004 }
1005
1006 #[test]
1007 fn test_truncate_requires_char_boundary() {
1008 let mut ps = PascalString::<4>::new();
1009 ps.try_push('€').unwrap(); ps.try_push('a').unwrap(); assert_eq!(ps.as_str(), "€a");
1012
1013 let result = std::panic::catch_unwind(move || {
1015 let mut ps = ps;
1016 ps.truncate(1);
1017 });
1018 assert!(result.is_err());
1019 }
1020
1021 #[test]
1022 fn test_try_from_bytes_invalid_utf8() {
1023 let err = PascalString::<8>::try_from(&[0xff_u8][..]).unwrap_err();
1024 match err {
1025 TryFromBytesError::Utf8Error(_) => {}
1026 _ => panic!("expected Utf8Error, got: {err:?}"),
1027 }
1028 }
1029
1030 #[test]
1031 fn test_try_from_bytes_too_long() {
1032 let err = PascalString::<2>::try_from(&b"abc"[..]).unwrap_err();
1033 assert_eq!(err, TryFromBytesError::TooLong);
1034 }
1035
1036 #[test]
1037 fn test_capacity_zero_behavior() {
1038 let mut ps = PascalString::<0>::new();
1039 assert_eq!(ps.len(), 0);
1040 assert!(ps.is_empty());
1041 assert_eq!(ps.as_str(), "");
1042
1043 assert_eq!(ps.try_push_str(""), Ok(()));
1044 assert_eq!(ps.try_push_str("a"), Err(TryFromStrError::TooLong));
1045
1046 let rem = ps.push_str_truncated("hello");
1047 assert_eq!(ps.as_str(), "");
1048 assert_eq!(rem, "hello");
1049
1050 assert_eq!(PascalString::<0>::from_str_truncated("hello").as_str(), "");
1051 assert!(PascalString::<0>::try_from("").is_ok());
1052 assert_eq!(
1053 PascalString::<0>::try_from("a").unwrap_err(),
1054 TryFromStrError::TooLong
1055 );
1056 }
1057
1058 #[test]
1059 fn test_into_inner_invariants() {
1060 let ps = PascalString::<4>::try_from("ab").unwrap();
1061 let (len, data) = ps.into_inner();
1062 assert_eq!(len, 2);
1063 assert_eq!(&data[..2], b"ab");
1064 assert_eq!(&data[2..], &[0, 0]);
1065 }
1066
1067 #[test]
1068 fn test_as_mut_str_allows_in_place_mutation() {
1069 let mut ps = PascalString::<4>::try_from("ab").unwrap();
1070 ps.as_mut_str().make_ascii_uppercase();
1071 assert_eq!(ps.as_str(), "AB");
1072 }
1073
1074 #[test]
1075 fn test_retain_filters_in_place() {
1076 let mut ps = PascalString::<8>::try_from("a1b2c3").unwrap();
1077 ps.retain(|ch| ch.is_ascii_alphabetic());
1078 assert_eq!(ps.as_str(), "abc");
1079 }
1080
1081 #[test]
1082 fn test_push_str_panics_on_overflow() {
1083 let result = std::panic::catch_unwind(|| {
1084 let mut ps = PascalString::<4>::new();
1085 ps.push_str_expect_capacity("abcde");
1086 });
1087 assert!(result.is_err());
1088 }
1089
1090 #[test]
1091 fn test_insert_str_and_remove_unicode_boundaries() {
1092 let mut ps = PascalString::<8>::try_from("ab").unwrap();
1093 ps.insert_str_expect_capacity(1, "€"); assert_eq!(ps.as_str(), "a€b");
1095
1096 let removed = ps.remove(1);
1097 assert_eq!(removed, '€');
1098 assert_eq!(ps.as_str(), "ab");
1099 }
1100
1101 #[test]
1102 fn test_try_insert_str_too_long_does_not_modify() {
1103 let mut ps = PascalString::<4>::try_from("ab").unwrap();
1104 let err = ps.try_insert_str(1, "€").unwrap_err(); assert_eq!(err, InsertError::TooLong);
1106 assert_eq!(ps.as_str(), "ab");
1107 }
1108
1109 #[test]
1110 fn test_try_replace_range_bounds_truncated() {
1111 let mut ps = PascalString::<4>::try_from("ab").unwrap();
1112 let rem = ps.try_replace_range_bounds_truncated(1, 1, "cde").unwrap();
1114 assert_eq!(ps.as_str(), "acdb");
1115 assert_eq!(rem, "e");
1116 }
1117
1118 #[test]
1119 fn test_try_from_str_const() {
1120 const PS: Option<PascalString<4>> = PascalString::<4>::try_from_str_const("ab");
1121 let ps = PS.unwrap();
1122 assert_eq!(ps.as_str(), "ab");
1123
1124 const TOO_LONG: Option<PascalString<2>> = PascalString::<2>::try_from_str_const("abc");
1125 assert!(TOO_LONG.is_none());
1126 }
1127
1128 #[test]
1131 fn test_split_off_at_middle() {
1132 let mut ps = PascalString::<8>::try_from("abcdef").unwrap();
1133 let tail = ps.split_off(3);
1134 assert_eq!(ps.as_str(), "abc");
1135 assert_eq!(tail.as_str(), "def");
1136 }
1137
1138 #[test]
1139 fn test_split_off_at_zero() {
1140 let mut ps = PascalString::<8>::try_from("hello").unwrap();
1141 let tail = ps.split_off(0);
1142 assert_eq!(ps.as_str(), "");
1143 assert_eq!(tail.as_str(), "hello");
1144 }
1145
1146 #[test]
1147 fn test_split_off_at_len() {
1148 let mut ps = PascalString::<8>::try_from("hello").unwrap();
1149 let len = ps.len();
1150 let tail = ps.split_off(len);
1151 assert_eq!(ps.as_str(), "hello");
1152 assert_eq!(tail.as_str(), "");
1153 }
1154
1155 #[test]
1156 fn test_split_off_multibyte_boundary() {
1157 let mut ps = PascalString::<8>::try_from("€ab").unwrap();
1159 let tail = ps.split_off(3); assert_eq!(ps.as_str(), "€");
1161 assert_eq!(tail.as_str(), "ab");
1162 }
1163
1164 #[test]
1165 #[should_panic]
1166 fn test_split_off_panics_on_non_char_boundary() {
1167 let mut ps = PascalString::<8>::try_from("€ab").unwrap();
1168 let _ = ps.split_off(1); }
1170
1171 #[test]
1172 #[should_panic]
1173 fn test_split_off_panics_at_gt_len() {
1174 let mut ps = PascalString::<8>::try_from("abc").unwrap();
1175 let _ = ps.split_off(10);
1176 }
1177
1178 #[test]
1181 fn test_try_split_off_at_middle() {
1182 let mut ps = PascalString::<8>::try_from("abcdef").unwrap();
1183 let tail = ps.try_split_off(3).unwrap();
1184 assert_eq!(ps.as_str(), "abc");
1185 assert_eq!(tail.as_str(), "def");
1186 }
1187
1188 #[test]
1189 fn test_try_split_off_at_zero() {
1190 let mut ps = PascalString::<8>::try_from("hello").unwrap();
1191 let tail = ps.try_split_off(0).unwrap();
1192 assert_eq!(ps.as_str(), "");
1193 assert_eq!(tail.as_str(), "hello");
1194 }
1195
1196 #[test]
1197 fn test_try_split_off_at_len() {
1198 let mut ps = PascalString::<8>::try_from("hello").unwrap();
1199 let len = ps.len();
1200 let tail = ps.try_split_off(len).unwrap();
1201 assert_eq!(ps.as_str(), "hello");
1202 assert_eq!(tail.as_str(), "");
1203 }
1204
1205 #[test]
1206 fn test_try_split_off_multibyte_boundary() {
1207 let mut ps = PascalString::<8>::try_from("€ab").unwrap();
1208 let tail = ps.try_split_off(3).unwrap();
1209 assert_eq!(ps.as_str(), "€");
1210 assert_eq!(tail.as_str(), "ab");
1211 }
1212
1213 #[test]
1214 fn test_try_split_off_err_not_char_boundary() {
1215 let mut ps = PascalString::<8>::try_from("€ab").unwrap();
1216 let err = ps.try_split_off(1).unwrap_err();
1217 assert_eq!(err, SplitOffError::NotCharBoundary { at: 1 });
1218 assert_eq!(ps.as_str(), "€ab");
1220 }
1221
1222 #[test]
1223 fn test_try_split_off_err_out_of_bounds() {
1224 let mut ps = PascalString::<8>::try_from("abc").unwrap();
1225 let err = ps.try_split_off(10).unwrap_err();
1226 assert_eq!(err, SplitOffError::OutOfBounds { at: 10, len: 3 });
1227 assert_eq!(ps.as_str(), "abc");
1228 }
1229
1230 #[test]
1233 fn test_remove_matches_single() {
1234 let mut ps = PascalString::<16>::try_from("hello world").unwrap();
1235 ps.remove_matches("world");
1236 assert_eq!(ps.as_str(), "hello ");
1237 }
1238
1239 #[test]
1240 fn test_remove_matches_multiple() {
1241 let mut ps = PascalString::<16>::try_from("abcabcabc").unwrap();
1242 ps.remove_matches("abc");
1243 assert_eq!(ps.as_str(), "");
1244 }
1245
1246 #[test]
1247 fn test_remove_matches_no_match() {
1248 let mut ps = PascalString::<16>::try_from("hello").unwrap();
1249 ps.remove_matches("xyz");
1250 assert_eq!(ps.as_str(), "hello");
1251 }
1252
1253 #[test]
1254 fn test_remove_matches_empty_pat() {
1255 let mut ps = PascalString::<8>::try_from("hello").unwrap();
1256 ps.remove_matches("");
1257 assert_eq!(ps.as_str(), "hello");
1258 }
1259
1260 #[test]
1261 fn test_remove_matches_equals_whole_string() {
1262 let mut ps = PascalString::<8>::try_from("hello").unwrap();
1263 ps.remove_matches("hello");
1264 assert_eq!(ps.as_str(), "");
1265 }
1266
1267 #[test]
1268 fn test_remove_matches_multibyte() {
1269 let mut ps = PascalString::<16>::try_from("a€b€c").unwrap();
1270 ps.remove_matches("€");
1271 assert_eq!(ps.as_str(), "abc");
1272 }
1273
1274 #[test]
1275 fn test_remove_matches_non_overlapping() {
1276 let mut ps = PascalString::<8>::try_from("aaa").unwrap();
1278 ps.remove_matches("aa");
1279 assert_eq!(ps.as_str(), "a");
1280 }
1281}