1#![cfg_attr(not(feature = "std"), no_std)]
81#![cfg_attr(docsrs, feature(doc_cfg))]
82
83#[cfg(feature = "alloc")]
84extern crate alloc;
85
86#[cfg(not(feature = "std"))]
87use core as std;
88use std::{borrow, error, fmt, hash, mem, ops, ptr, str};
89
90#[derive(Copy, Clone, Eq, Ord, PartialOrd)]
96#[repr(transparent)]
97pub struct FStr<const N: usize> {
98 inner: [u8; N],
99}
100
101impl<const N: usize> FStr<N> {
102 pub const LENGTH: usize = N;
104
105 pub const fn as_str(&self) -> &str {
107 debug_assert!(str::from_utf8(&self.inner).is_ok());
108 unsafe { str::from_utf8_unchecked(&self.inner) }
110 }
111
112 pub const fn as_mut_str(&mut self) -> &mut str {
114 debug_assert!(str::from_utf8(&self.inner).is_ok());
115 unsafe { str::from_utf8_unchecked_mut(&mut self.inner) }
117 }
118
119 pub const fn as_bytes(&self) -> &[u8; N] {
121 &self.inner
122 }
123
124 pub const fn into_inner(self) -> [u8; N] {
126 self.inner
127 }
128
129 pub const fn from_inner(utf8_bytes: [u8; N]) -> Result<Self, str::Utf8Error> {
144 match str::from_utf8(&utf8_bytes) {
145 Ok(_) => Ok(Self { inner: utf8_bytes }),
146 Err(e) => Err(e),
147 }
148 }
149
150 pub const unsafe fn from_inner_unchecked(utf8_bytes: [u8; N]) -> Self {
156 debug_assert!(str::from_utf8(&utf8_bytes).is_ok());
157 Self { inner: utf8_bytes }
158 }
159
160 pub const fn from_str_unwrap(s: &str) -> Self {
172 match Self::try_from_str(s) {
173 Ok(t) => t,
174 _ => panic!("invalid byte length"),
175 }
176 }
177
178 const fn try_from_str(s: &str) -> Result<Self, LengthError> {
180 match Self::copy_slice_to_array(s.as_bytes()) {
181 Ok(inner) => Ok(unsafe { Self::from_inner_unchecked(inner) }),
183 Err(e) => Err(e),
184 }
185 }
186
187 const fn try_from_slice(s: &[u8]) -> Result<Self, FromSliceError> {
189 match Self::copy_slice_to_array(s) {
190 Ok(inner) => match Self::from_inner(inner) {
191 Ok(t) => Ok(t),
192 Err(e) => Err(FromSliceError {
193 kind: FromSliceErrorKind::Utf8(e),
194 }),
195 },
196 Err(e) => Err(FromSliceError {
197 kind: FromSliceErrorKind::Length(e),
198 }),
199 }
200 }
201
202 pub const fn from_str_lossy(s: &str, filler: u8) -> Self {
225 if N == 0 {
226 return Self::from_ascii_filler(filler); }
228 assert!(filler.is_ascii(), "filler byte must represent ASCII char");
229
230 let len = if s.len() <= N {
231 s.len()
232 } else if is_utf8_char_boundary(s.as_bytes()[N]) {
233 N
234 } else if is_utf8_char_boundary(s.as_bytes()[N - 1]) {
235 N - 1
236 } else if is_utf8_char_boundary(s.as_bytes()[N - 2]) {
237 N - 2
238 } else if is_utf8_char_boundary(s.as_bytes()[N - 3]) {
239 N - 3
240 } else {
241 unreachable!() };
243
244 let mut inner = [filler; N];
245 debug_assert!(len <= s.len() && len <= inner.len());
246 unsafe { ptr::copy_nonoverlapping(s.as_ptr(), inner.as_mut_ptr(), len) };
252
253 unsafe { Self::from_inner_unchecked(inner) }
256 }
257
258 pub const fn from_ascii_filler(filler: u8) -> Self {
273 assert!(filler.is_ascii(), "filler byte must represent ASCII char");
274 unsafe { Self::from_inner_unchecked([filler; N]) }
276 }
277
278 #[doc(hidden)]
280 #[deprecated(since = "0.2.12", note = "renamed to `from_ascii_filler`")]
281 pub const fn repeat(filler: u8) -> Self {
282 Self::from_ascii_filler(filler)
283 }
284
285 pub fn slice_to_terminator(&self, terminator: char) -> &str {
305 match self.find(terminator) {
306 Some(i) => &self[..i],
307 _ => self,
308 }
309 }
310
311 #[doc(hidden)]
313 #[deprecated(since = "0.2.13", note = "use `writer_at(0)` instead")]
314 pub fn writer(&mut self) -> Cursor<&mut Self> {
315 self.writer_at(0)
316 }
317
318 pub fn writer_at(&mut self, index: usize) -> Cursor<&mut Self> {
360 Cursor::with_position(index, self).expect("index must point to char boundary")
361 }
362
363 pub fn from_fmt(args: fmt::Arguments<'_>, filler: u8) -> Result<Self, fmt::Error> {
389 assert!(filler.is_ascii(), "filler byte must represent ASCII char");
390
391 struct Writer<'s>(&'s mut [mem::MaybeUninit<u8>]);
392
393 impl fmt::Write for Writer<'_> {
394 fn write_str(&mut self, s: &str) -> fmt::Result {
395 if s.len() <= self.0.len() {
396 let written;
397 (written, self.0) = mem::take(&mut self.0).split_at_mut(s.len());
398 written.copy_from_slice(unsafe {
400 mem::transmute::<&[u8], &[mem::MaybeUninit<u8>]>(s.as_bytes())
401 });
402 Ok(())
403 } else {
404 Err(fmt::Error)
405 }
406 }
407 }
408
409 let mut inner = [const { mem::MaybeUninit::uninit() }; N];
410 let mut w = Writer(inner.as_mut_slice());
411 if fmt::Write::write_fmt(&mut w, args).is_ok() {
412 w.0.fill(mem::MaybeUninit::new(filler)); Ok(unsafe {
417 Self::from_inner_unchecked(
418 mem::transmute_copy::<[mem::MaybeUninit<u8>; N], [u8; N]>(&inner),
419 )
420 })
421 } else {
422 const _STATIC_ASSERT: () = assert!(!mem::needs_drop::<u8>(), "u8 never needs drop");
424 Err(fmt::Error)
425 }
426 }
427}
428
429impl<const N: usize> FStr<N> {
431 const fn copy_slice_to_array(s: &[u8]) -> Result<[u8; N], LengthError> {
433 if s.len() == N {
434 Ok(unsafe { *s.as_ptr().cast::<[u8; N]>() })
436 } else {
437 Err(LengthError {
438 actual: s.len(),
439 expected: N,
440 })
441 }
442 }
443}
444
445impl<const N: usize> ops::Deref for FStr<N> {
446 type Target = str;
447
448 fn deref(&self) -> &Self::Target {
449 self.as_str()
450 }
451}
452
453impl<const N: usize> ops::DerefMut for FStr<N> {
454 fn deref_mut(&mut self) -> &mut str {
455 self.as_mut_str()
456 }
457}
458
459impl<const N: usize> Default for FStr<N> {
460 fn default() -> Self {
470 Self::from_ascii_filler(b' ')
471 }
472}
473
474impl<const N: usize> fmt::Debug for FStr<N> {
475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476 f.debug_struct(
477 match FStr::<32>::from_fmt(format_args!("FStr<{}>", N), b'\0') {
478 Ok(ref buffer) => buffer.slice_to_terminator('\0'),
479 Err(_) => "FStr", },
481 )
482 .field("inner", &self.as_str())
483 .finish()
484 }
485}
486
487impl<const N: usize> fmt::Display for FStr<N> {
488 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489 fmt::Display::fmt(self.as_str(), f)
490 }
491}
492
493impl<const N: usize> PartialEq for FStr<N> {
494 fn eq(&self, other: &FStr<N>) -> bool {
495 self.as_str().eq(other.as_str())
496 }
497}
498
499impl<const N: usize> PartialEq<str> for FStr<N> {
500 fn eq(&self, other: &str) -> bool {
501 self.as_str().eq(other)
502 }
503}
504
505impl<const N: usize> PartialEq<FStr<N>> for str {
506 fn eq(&self, other: &FStr<N>) -> bool {
507 self.eq(other.as_str())
508 }
509}
510
511impl<const N: usize> PartialEq<&str> for FStr<N> {
512 fn eq(&self, other: &&str) -> bool {
513 self.as_str().eq(*other)
514 }
515}
516
517impl<const N: usize> PartialEq<FStr<N>> for &str {
518 fn eq(&self, other: &FStr<N>) -> bool {
519 self.eq(&other.as_str())
520 }
521}
522
523impl<const N: usize> hash::Hash for FStr<N> {
524 fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
525 self.as_str().hash(hasher)
526 }
527}
528
529impl<const N: usize> borrow::Borrow<str> for FStr<N> {
530 fn borrow(&self) -> &str {
531 self.as_str()
532 }
533}
534
535impl<const N: usize> borrow::BorrowMut<str> for FStr<N> {
536 fn borrow_mut(&mut self) -> &mut str {
537 self.as_mut_str()
538 }
539}
540
541impl<const N: usize> AsRef<str> for FStr<N> {
542 fn as_ref(&self) -> &str {
543 self.as_str()
544 }
545}
546
547impl<const N: usize> AsMut<str> for FStr<N> {
548 fn as_mut(&mut self) -> &mut str {
549 self.as_mut_str()
550 }
551}
552
553impl<const N: usize> AsRef<[u8]> for FStr<N> {
554 fn as_ref(&self) -> &[u8] {
555 self.as_bytes()
556 }
557}
558
559impl<const N: usize> From<FStr<N>> for [u8; N] {
560 fn from(value: FStr<N>) -> Self {
561 value.into_inner()
562 }
563}
564
565impl<const N: usize> str::FromStr for FStr<N> {
566 type Err = LengthError;
567
568 fn from_str(s: &str) -> Result<Self, Self::Err> {
569 Self::try_from_str(s)
570 }
571}
572
573impl<const N: usize> TryFrom<[u8; N]> for FStr<N> {
574 type Error = str::Utf8Error;
575
576 fn try_from(value: [u8; N]) -> Result<Self, Self::Error> {
577 Self::from_inner(value)
578 }
579}
580
581impl<const N: usize> TryFrom<&[u8; N]> for FStr<N> {
582 type Error = str::Utf8Error;
583
584 fn try_from(value: &[u8; N]) -> Result<Self, Self::Error> {
585 Self::from_inner(*value)
586 }
587}
588
589impl<const N: usize> TryFrom<&[u8]> for FStr<N> {
590 type Error = FromSliceError;
591
592 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
593 Self::try_from_slice(value)
594 }
595}
596
597#[derive(Debug)]
632pub struct Cursor<T> {
633 inner: T,
634 pos: usize,
635}
636
637impl<T> Cursor<T> {
638 pub fn get_ref(&self) -> &T {
640 &self.inner
641 }
642
643 pub fn position(&self) -> usize {
647 self.pos
648 }
649}
650
651impl<T: AsRef<str>> Cursor<T> {
652 fn with_position(pos: usize, inner: T) -> Option<Self> {
654 match inner.as_ref().is_char_boundary(pos) {
655 true => Some(Self { inner, pos }),
656 false => None,
657 }
658 }
659}
660
661impl<T: AsMut<str>> fmt::Write for Cursor<T> {
662 fn write_str(&mut self, s: &str) -> fmt::Result {
663 match self.inner.as_mut().get_mut(self.pos..(self.pos + s.len())) {
664 Some(written) => {
665 unsafe { written.as_bytes_mut() }.copy_from_slice(s.as_bytes());
667 self.pos += written.len();
668 Ok(())
669 }
670 None => Err(fmt::Error),
671 }
672 }
673}
674
675#[derive(Copy, Eq, PartialEq, Clone, Debug)]
677pub struct LengthError {
678 actual: usize,
679 expected: usize,
680}
681
682impl fmt::Display for LengthError {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 write!(
685 f,
686 "invalid byte length of {} (expected: {})",
687 self.actual, self.expected
688 )
689 }
690}
691
692impl error::Error for LengthError {}
693
694#[derive(Copy, Eq, PartialEq, Clone, Debug)]
696pub struct FromSliceError {
697 kind: FromSliceErrorKind,
698}
699
700#[derive(Copy, Eq, PartialEq, Clone, Debug)]
701enum FromSliceErrorKind {
702 Length(LengthError),
703 Utf8(str::Utf8Error),
704}
705
706impl fmt::Display for FromSliceError {
707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708 use FromSliceErrorKind::{Length, Utf8};
709 match self.kind {
710 Length(source) => write!(f, "could not convert slice to FStr: {}", source),
711 Utf8(source) => write!(f, "could not convert slice to FStr: {}", source),
712 }
713 }
714}
715
716impl error::Error for FromSliceError {
717 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
718 match &self.kind {
719 FromSliceErrorKind::Length(source) => Some(source),
720 FromSliceErrorKind::Utf8(source) => Some(source),
721 }
722 }
723}
724
725#[inline(always)]
726const fn is_utf8_char_boundary(byte: u8) -> bool {
727 (byte as i8) >= -0x40 }
729
730#[cfg(feature = "alloc")]
731mod with_string {
732 use alloc::{borrow::ToOwned as _, string::String};
733
734 use super::{FStr, LengthError};
735
736 impl<const N: usize> From<FStr<N>> for String {
737 fn from(value: FStr<N>) -> Self {
738 value.as_str().to_owned()
739 }
740 }
741
742 impl<const N: usize> TryFrom<String> for FStr<N> {
743 type Error = LengthError;
744
745 fn try_from(value: String) -> Result<Self, Self::Error> {
746 value.parse()
747 }
748 }
749
750 impl<const N: usize> PartialEq<String> for FStr<N> {
751 fn eq(&self, other: &String) -> bool {
752 self.as_str().eq(other)
753 }
754 }
755
756 impl<const N: usize> PartialEq<FStr<N>> for String {
757 fn eq(&self, other: &FStr<N>) -> bool {
758 self.eq(other.as_str())
759 }
760 }
761}
762
763#[cfg(test)]
764mod tests {
765 use super::FStr;
766
767 #[test]
769 fn eq() {
770 let x = FStr::from_inner(*b"hello").unwrap();
771
772 assert_eq!(x, x);
773 assert_eq!(&x, &x);
774 assert_eq!(x, FStr::from_inner(*b"hello").unwrap());
775 assert_eq!(FStr::from_inner(*b"hello").unwrap(), x);
776 assert_eq!(&x, &FStr::from_inner(*b"hello").unwrap());
777 assert_eq!(&FStr::from_inner(*b"hello").unwrap(), &x);
778
779 assert_eq!(x, "hello");
780 assert_eq!("hello", x);
781 assert_eq!(&x, "hello");
782 assert_eq!("hello", &x);
783 assert_eq!(&x[..], "hello");
784 assert_eq!("hello", &x[..]);
785 assert_eq!(&x as &str, "hello");
786 assert_eq!("hello", &x as &str);
787
788 assert_ne!(x, FStr::from_inner(*b"world").unwrap());
789 assert_ne!(FStr::from_inner(*b"world").unwrap(), x);
790 assert_ne!(&x, &FStr::from_inner(*b"world").unwrap());
791 assert_ne!(&FStr::from_inner(*b"world").unwrap(), &x);
792
793 assert_ne!(x, "world");
794 assert_ne!("world", x);
795 assert_ne!(&x, "world");
796 assert_ne!("world", &x);
797 assert_ne!(&x[..], "world");
798 assert_ne!("world", &x[..]);
799 assert_ne!(&x as &str, "world");
800 assert_ne!("world", &x as &str);
801
802 #[cfg(feature = "alloc")]
803 {
804 use alloc::{borrow::ToOwned as _, string::String, string::ToString as _};
805
806 assert_eq!(x, String::from("hello"));
807 assert_eq!(String::from("hello"), x);
808
809 assert_eq!(String::from(x), String::from("hello"));
810 assert_eq!(String::from("hello"), String::from(x));
811
812 assert_ne!(x, String::from("world"));
813 assert_ne!(String::from("world"), x);
814
815 assert_ne!(String::from(x), String::from("world"));
816 assert_ne!(String::from("world"), String::from(x));
817
818 assert_eq!(x.to_owned(), String::from("hello"));
819 assert_eq!(String::from("hello"), x.to_owned());
820 assert_eq!(x.to_string(), String::from("hello"));
821 assert_eq!(String::from("hello"), x.to_string());
822 }
823 }
824
825 #[test]
827 fn from_str_lossy_edge() {
828 assert!(FStr::<0>::from_str_lossy("", b' ').is_empty());
829 assert!(FStr::<0>::from_str_lossy("pizza", b' ').is_empty());
830 assert!(FStr::<0>::from_str_lossy("π₯Ήπ₯Ή", b' ').is_empty());
831
832 assert_eq!(FStr::<1>::from_str_lossy("", b' '), " ");
833 assert_eq!(FStr::<1>::from_str_lossy("pizza", b' '), "p");
834 assert_eq!(FStr::<1>::from_str_lossy("π₯Ήπ₯Ή", b' '), " ");
835
836 assert_eq!(FStr::<2>::from_str_lossy("π₯Ήπ₯Ή", b' '), " ");
837 assert_eq!(FStr::<3>::from_str_lossy("π₯Ήπ₯Ή", b' '), " ");
838 assert_eq!(FStr::<4>::from_str_lossy("π₯Ήπ₯Ή", b' '), "π₯Ή");
839 assert_eq!(FStr::<5>::from_str_lossy("π₯Ήπ₯Ή", b' '), "π₯Ή ");
840 assert_eq!(FStr::<6>::from_str_lossy("π₯Ήπ₯Ή", b' '), "π₯Ή ");
841 assert_eq!(FStr::<7>::from_str_lossy("π₯Ήπ₯Ή", b' '), "π₯Ή ");
842 assert_eq!(FStr::<8>::from_str_lossy("π₯Ήπ₯Ή", b' '), "π₯Ήπ₯Ή");
843 assert_eq!(FStr::<9>::from_str_lossy("π₯Ήπ₯Ή", b' '), "π₯Ήπ₯Ή ");
844 }
845
846 #[test]
848 fn from_str() {
849 assert!("ceremony".parse::<FStr<4>>().is_err());
850 assert!("strategy".parse::<FStr<12>>().is_err());
851 assert!("parallel".parse::<FStr<8>>().is_ok());
852 assert_eq!("parallel".parse::<FStr<8>>().unwrap(), "parallel");
853
854 assert!("π".parse::<FStr<2>>().is_err());
855 assert!("π".parse::<FStr<6>>().is_err());
856 assert!("π".parse::<FStr<4>>().is_ok());
857 assert_eq!("π".parse::<FStr<4>>().unwrap(), "π");
858 }
859
860 #[test]
862 fn try_from_array() {
863 assert!(FStr::try_from(b"memory").is_ok());
864 assert!(FStr::try_from(*b"resort").is_ok());
865
866 assert!(FStr::try_from(&[0xff; 8]).is_err());
867 assert!(FStr::try_from([0xff; 8]).is_err());
868 }
869
870 #[test]
872 fn try_from_slice() {
873 assert!(FStr::<4>::try_from(b"memory".as_slice()).is_err());
874 assert!(FStr::<6>::try_from(b"memory".as_slice()).is_ok());
875 assert!(FStr::<8>::try_from(b"memory".as_slice()).is_err());
876
877 assert!(FStr::<7>::try_from([0xff; 8].as_slice()).is_err());
878 assert!(FStr::<8>::try_from([0xff; 8].as_slice()).is_err());
879 assert!(FStr::<9>::try_from([0xff; 8].as_slice()).is_err());
880 }
881
882 #[test]
884 fn write_str() {
885 use core::fmt::Write as _;
886
887 let mut a = FStr::<5>::from_ascii_filler(b' ');
888 assert!(write!(a.writer_at(0), "vanilla").is_err());
889 assert_eq!(a, " ");
890
891 let mut b = FStr::<7>::from_ascii_filler(b' ');
892 assert!(write!(b.writer_at(0), "vanilla").is_ok());
893 assert_eq!(b, "vanilla");
894
895 let mut c = FStr::<9>::from_ascii_filler(b' ');
896 assert!(write!(c.writer_at(0), "vanilla").is_ok());
897 assert_eq!(c, "vanilla ");
898
899 let mut d = FStr::<16>::from_ascii_filler(b'.');
900 assert!(write!(d.writer_at(0), "ππ€ͺπ±π»").is_ok());
901 assert_eq!(d, "ππ€ͺπ±π»");
902 assert!(write!(d.writer_at(0), "π₯").is_ok());
903 assert_eq!(d, "π₯π€ͺπ±π»");
904 assert!(write!(d.writer_at(0), "π₯Ίπ").is_ok());
905 assert_eq!(d, "π₯Ίππ±π»");
906 assert!(write!(d.writer_at(0), ".").is_err());
907 assert_eq!(d, "π₯Ίππ±π»");
908
909 let mut e = FStr::<12>::from_ascii_filler(b' ');
910 assert!(write!(e.writer_at(0), "{:04}/{:04}", 42, 334).is_ok());
911 assert_eq!(e, "0042/0334 ");
912
913 let mut w = e.writer_at(0);
914 assert!(write!(w, "{:02x}", 123).is_ok());
915 assert!(write!(w, "-{:04x}", 345).is_ok());
916 assert!(write!(w, "-{:04x}", 567).is_ok());
917 assert!(write!(w, "-{:04x}", 789).is_err());
918 assert_eq!(e, "7b-0159-0237");
919
920 assert!(write!(FStr::<0>::default().writer_at(0), "").is_ok());
921 assert!(write!(FStr::<0>::default().writer_at(0), " ").is_err());
922
923 assert!(write!(FStr::<5>::default().writer_at(5), "").is_ok());
924 assert!(write!(FStr::<5>::default().writer_at(5), " ").is_err());
925 }
926
927 #[test]
928 #[should_panic]
929 fn writer_at_index_middle_of_a_char() {
930 FStr::<8>::from_str_lossy("π", b' ').writer_at(1);
931 }
932
933 #[test]
934 #[should_panic]
935 fn writer_at_index_beyond_end() {
936 FStr::<5>::default().writer_at(7);
937 }
938
939 #[cfg(feature = "std")]
941 #[test]
942 fn hash_borrow() {
943 use std::collections::HashSet;
944
945 let mut s = HashSet::new();
946 s.insert(FStr::from_inner(*b"crisis").unwrap());
947 s.insert(FStr::from_inner(*b"eating").unwrap());
948 s.insert(FStr::from_inner(*b"lucent").unwrap());
949
950 assert!(s.contains("crisis"));
951 assert!(s.contains("eating"));
952 assert!(s.contains("lucent"));
953 assert!(!s.contains("system"));
954 assert!(!s.contains("unless"));
955 assert!(!s.contains("yellow"));
956
957 assert!(s.contains(&FStr::from_inner(*b"crisis").unwrap()));
958 assert!(s.contains(&FStr::from_inner(*b"eating").unwrap()));
959 assert!(s.contains(&FStr::from_inner(*b"lucent").unwrap()));
960 assert!(!s.contains(&FStr::from_inner(*b"system").unwrap()));
961 assert!(!s.contains(&FStr::from_inner(*b"unless").unwrap()));
962 assert!(!s.contains(&FStr::from_inner(*b"yellow").unwrap()));
963 }
964
965 #[cfg(feature = "alloc")]
967 #[test]
968 fn display_fmt() {
969 use alloc::format;
970
971 let a = FStr::from_inner(*b"you").unwrap();
972
973 assert_eq!(format!("{}", a), "you");
974 assert_eq!(format!("{:5}", a), "you ");
975 assert_eq!(format!("{:<6}", a), "you ");
976 assert_eq!(format!("{:-<7}", a), "you----");
977 assert_eq!(format!("{:>8}", a), " you");
978 assert_eq!(format!("{:^9}", a), " you ");
979
980 let b = FStr::from_inner(*b"junior").unwrap();
981
982 assert_eq!(format!("{}", b), "junior");
983 assert_eq!(format!("{:.3}", b), "jun");
984 assert_eq!(format!("{:5.3}", b), "jun ");
985 assert_eq!(format!("{:<6.3}", b), "jun ");
986 assert_eq!(format!("{:-<7.3}", b), "jun----");
987 assert_eq!(format!("{:>8.3}", b), " jun");
988 assert_eq!(format!("{:^9.3}", b), " jun ");
989 }
990
991 #[test]
993 fn from_fmt() {
994 let args = format_args!("vanilla");
995 assert!(FStr::<5>::from_fmt(args, b' ').is_err());
996 assert_eq!(FStr::<7>::from_fmt(args, b' ').unwrap(), "vanilla");
997 assert_eq!(FStr::<9>::from_fmt(args, b' ').unwrap(), "vanilla ");
998
999 assert_eq!(
1000 FStr::<20>::from_fmt(format_args!("{:^6}", "ππ€ͺπ±π»"), b'.').unwrap(),
1001 " ππ€ͺπ±π» .."
1002 );
1003
1004 assert_eq!(
1005 FStr::<12>::from_fmt(format_args!("{:04}/{:04}", 42, 334), b'\0').unwrap(),
1006 "0042/0334\0\0\0"
1007 );
1008
1009 assert_eq!(FStr::<0>::from_fmt(format_args!(""), b' ').unwrap(), "");
1010 assert!(FStr::<0>::from_fmt(format_args!(" "), b' ').is_err());
1011 }
1012}
1013
1014#[cfg(feature = "serde")]
1015mod with_serde {
1016 use super::{FStr, fmt};
1017 use serde::{Deserializer, Serializer, de};
1018
1019 impl<const N: usize> serde::Serialize for FStr<N> {
1020 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1021 serializer.serialize_str(self.as_str())
1022 }
1023 }
1024
1025 impl<'de, const N: usize> serde::Deserialize<'de> for FStr<N> {
1026 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1027 deserializer.deserialize_str(VisitorImpl)
1028 }
1029 }
1030
1031 struct VisitorImpl<const N: usize>;
1032
1033 impl<const N: usize> de::Visitor<'_> for VisitorImpl<N> {
1034 type Value = FStr<N>;
1035
1036 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1037 write!(formatter, "a fixed-length string")
1038 }
1039
1040 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
1041 value.parse().map_err(de::Error::custom)
1042 }
1043
1044 fn visit_bytes<E: de::Error>(self, value: &[u8]) -> Result<Self::Value, E> {
1045 if let Ok(inner) = value.try_into() {
1046 if let Ok(t) = FStr::from_inner(inner) {
1047 return Ok(t);
1048 }
1049 }
1050
1051 Err(de::Error::invalid_value(
1052 de::Unexpected::Bytes(value),
1053 &self,
1054 ))
1055 }
1056 }
1057
1058 #[test]
1059 fn ser_de() {
1060 use serde_test::Token;
1061
1062 let x = FStr::from_inner(*b"helloworld").unwrap();
1063 serde_test::assert_tokens(&x, &[Token::Str("helloworld")]);
1064 serde_test::assert_de_tokens(&x, &[Token::Bytes(b"helloworld")]);
1065
1066 let y = "ππ€ͺπ±π»".parse::<FStr<16>>().unwrap();
1067 serde_test::assert_tokens(&y, &[Token::Str("ππ€ͺπ±π»")]);
1068 serde_test::assert_de_tokens(
1069 &y,
1070 &[Token::Bytes(&[
1071 240, 159, 152, 130, 240, 159, 164, 170, 240, 159, 152, 177, 240, 159, 145, 187,
1072 ])],
1073 );
1074
1075 serde_test::assert_de_tokens_error::<FStr<5>>(
1076 &[Token::Str("helloworld")],
1077 "invalid byte length of 10 (expected: 5)",
1078 );
1079 serde_test::assert_de_tokens_error::<FStr<5>>(
1080 &[Token::Bytes(b"helloworld")],
1081 "invalid value: byte array, expected a fixed-length string",
1082 );
1083 serde_test::assert_de_tokens_error::<FStr<5>>(
1084 &[Token::Bytes(&[b'h', b'e', b'l', b'l', 240])],
1085 "invalid value: byte array, expected a fixed-length string",
1086 );
1087 }
1088}