1use alloc::borrow::Cow;
2use alloc::string::String;
3#[cfg(feature = "std")]
4use std::ffi::{OsStr, OsString};
5
6use crate::Result;
7use crate::case_sensitivity::{CaseInsensitive, CaseSensitive, CaseSensitivity};
8use crate::normalize::{normalize_ci_from_normalized_cs, normalize_cs};
9use crate::os::os_compatible_from_normalized_cs;
10use crate::utils::SubstringOrOwned;
11
12fn build_fields(
15 original: &str,
16 cs: CaseSensitivity,
17) -> Result<(SubstringOrOwned, SubstringOrOwned)> {
18 let with_original = |kind: crate::ErrorKind| kind.into_error(String::from(original));
19
20 let cs_normalized = normalize_cs(original).map_err(&with_original)?;
21 let normalized = match cs {
22 CaseSensitivity::Sensitive => SubstringOrOwned::new(&cs_normalized, original),
23 CaseSensitivity::Insensitive => {
24 SubstringOrOwned::new(&normalize_ci_from_normalized_cs(&cs_normalized), original)
25 }
26 };
27 let os_str = os_compatible_from_normalized_cs(&cs_normalized).map_err(&with_original)?;
28 let os_compatible = SubstringOrOwned::new(&os_str, original);
29 Ok((normalized, os_compatible))
30}
31
32pub type PathElementCS<'a> = PathElementGeneric<'a, CaseSensitive>;
36
37pub type PathElementCI<'a> = PathElementGeneric<'a, CaseInsensitive>;
41
42pub type PathElement<'a> = PathElementGeneric<'a, CaseSensitivity>;
48
49#[derive(Clone)]
68pub struct PathElementGeneric<'a, S> {
69 original: Cow<'a, str>,
70 normalized: SubstringOrOwned,
72 os_compatible: SubstringOrOwned,
74 case_sensitivity: S,
75}
76
77impl<S: core::fmt::Debug> core::fmt::Debug for PathElementGeneric<'_, S> {
78 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79 f.debug_struct("PathElement")
80 .field("original", &self.original())
81 .field("normalized", &self.normalized())
82 .field("os_compatible", &self.os_compatible())
83 .field("case_sensitivity", &self.case_sensitivity)
84 .finish()
85 }
86}
87
88impl<'a, S1, S2> PartialEq<PathElementGeneric<'a, S2>> for PathElementGeneric<'_, S1>
95where
96 for<'s> CaseSensitivity: From<&'s S1> + From<&'s S2>,
97{
98 fn eq(&self, other: &PathElementGeneric<'a, S2>) -> bool {
99 assert_eq!(
100 CaseSensitivity::from(&self.case_sensitivity),
101 CaseSensitivity::from(&other.case_sensitivity),
102 "comparing PathElements with different case sensitivity"
103 );
104 self.normalized() == other.normalized()
105 }
106}
107
108impl<S> Eq for PathElementGeneric<'_, S> where for<'s> CaseSensitivity: From<&'s S> {}
110
111impl<'a, S1, S2> PartialOrd<PathElementGeneric<'a, S2>> for PathElementGeneric<'_, S1>
114where
115 for<'s> CaseSensitivity: From<&'s S1> + From<&'s S2>,
116{
117 fn partial_cmp(&self, other: &PathElementGeneric<'a, S2>) -> Option<core::cmp::Ordering> {
118 if CaseSensitivity::from(&self.case_sensitivity)
119 != CaseSensitivity::from(&other.case_sensitivity)
120 {
121 return None;
122 }
123 Some(self.normalized().cmp(other.normalized()))
124 }
125}
126
127impl<S> Ord for PathElementGeneric<'_, S>
136where
137 for<'s> CaseSensitivity: From<&'s S>,
138{
139 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
140 assert_eq!(
141 CaseSensitivity::from(&self.case_sensitivity),
142 CaseSensitivity::from(&other.case_sensitivity),
143 "comparing PathElements with different case sensitivity"
144 );
145 self.normalized().cmp(other.normalized())
146 }
147}
148
149impl core::hash::Hash for PathElementCS<'_> {
152 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
153 self.normalized().hash(state);
154 }
155}
156
157impl core::hash::Hash for PathElementCI<'_> {
160 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
161 self.normalized().hash(state);
162 }
163}
164
165impl<'a> PathElementCS<'a> {
166 pub fn new(original: impl Into<Cow<'a, str>>) -> Result<Self> {
180 Self::with_case_sensitivity(original, CaseSensitive)
181 }
182
183 pub fn from_bytes(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
193 Self::from_bytes_with_case_sensitivity(original, CaseSensitive)
194 }
195
196 #[cfg(feature = "std")]
206 pub fn from_os_str(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
207 Self::from_os_str_with_case_sensitivity(original, CaseSensitive)
208 }
209}
210
211impl<'a> PathElementCI<'a> {
212 pub fn new(original: impl Into<Cow<'a, str>>) -> Result<Self> {
226 Self::with_case_sensitivity(original, CaseInsensitive)
227 }
228
229 pub fn from_bytes(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
239 Self::from_bytes_with_case_sensitivity(original, CaseInsensitive)
240 }
241
242 #[cfg(feature = "std")]
252 pub fn from_os_str(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
253 Self::from_os_str_with_case_sensitivity(original, CaseInsensitive)
254 }
255}
256
257impl<'a> PathElementGeneric<'a, CaseSensitivity> {
258 pub fn new(
264 original: impl Into<Cow<'a, str>>,
265 case_sensitivity: impl Into<CaseSensitivity>,
266 ) -> Result<Self> {
267 Self::with_case_sensitivity(original, case_sensitivity)
268 }
269
270 pub fn from_bytes(
280 original: impl Into<Cow<'a, [u8]>>,
281 case_sensitivity: impl Into<CaseSensitivity>,
282 ) -> Result<Self> {
283 Self::from_bytes_with_case_sensitivity(original, case_sensitivity)
284 }
285
286 #[cfg(feature = "std")]
296 pub fn from_os_str(
297 original: impl Into<Cow<'a, OsStr>>,
298 case_sensitivity: impl Into<CaseSensitivity>,
299 ) -> Result<Self> {
300 Self::from_os_str_with_case_sensitivity(original, case_sensitivity)
301 }
302
303 pub fn new_cs(original: impl Into<Cow<'a, str>>) -> Result<Self> {
309 Self::with_case_sensitivity(original, CaseSensitive)
310 }
311
312 pub fn new_ci(original: impl Into<Cow<'a, str>>) -> Result<Self> {
318 Self::with_case_sensitivity(original, CaseInsensitive)
319 }
320
321 pub fn from_bytes_cs(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
330 Self::from_bytes_with_case_sensitivity(original, CaseSensitive)
331 }
332
333 pub fn from_bytes_ci(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
342 Self::from_bytes_with_case_sensitivity(original, CaseInsensitive)
343 }
344
345 #[cfg(feature = "std")]
354 pub fn from_os_str_cs(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
355 Self::from_os_str_with_case_sensitivity(original, CaseSensitive)
356 }
357
358 #[cfg(feature = "std")]
367 pub fn from_os_str_ci(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
368 Self::from_os_str_with_case_sensitivity(original, CaseInsensitive)
369 }
370}
371
372impl<'a, S> PathElementGeneric<'a, S>
373where
374 for<'s> CaseSensitivity: From<&'s S>,
375{
376 pub fn from_bytes_with_case_sensitivity(
391 original: impl Into<Cow<'a, [u8]>>,
392 case_sensitivity: impl Into<S>,
393 ) -> Result<Self> {
394 let cow_str = match original.into() {
395 Cow::Borrowed(b) => String::from_utf8_lossy(b),
396 Cow::Owned(v) => match String::from_utf8_lossy(&v) {
398 Cow::Borrowed(_) => unsafe { Cow::Owned(String::from_utf8_unchecked(v)) },
400 Cow::Owned(s) => Cow::Owned(s),
401 },
402 };
403 Self::with_case_sensitivity(cow_str, case_sensitivity)
404 }
405
406 #[cfg(feature = "std")]
417 pub fn from_os_str_with_case_sensitivity(
418 original: impl Into<Cow<'a, OsStr>>,
419 case_sensitivity: impl Into<S>,
420 ) -> Result<Self> {
421 let cow_bytes: Cow<'a, [u8]> = match original.into() {
422 Cow::Borrowed(os) => Cow::Borrowed(os.as_encoded_bytes()),
423 Cow::Owned(os) => Cow::Owned(os.into_encoded_bytes()),
424 };
425 Self::from_bytes_with_case_sensitivity(cow_bytes, case_sensitivity)
426 }
427
428 pub fn with_case_sensitivity(
439 original: impl Into<Cow<'a, str>>,
440 case_sensitivity: impl Into<S>,
441 ) -> Result<Self> {
442 let original = original.into();
443 let case_sensitivity = case_sensitivity.into();
444 let cs = CaseSensitivity::from(&case_sensitivity);
445 let (normalized, os_compatible) = build_fields(&original, cs)?;
446 Ok(Self {
447 original,
448 normalized,
449 os_compatible,
450 case_sensitivity,
451 })
452 }
453
454 pub fn case_sensitivity(&self) -> CaseSensitivity {
456 CaseSensitivity::from(&self.case_sensitivity)
457 }
458}
459
460impl<'a, S> PathElementGeneric<'a, S> {
461 pub fn original(&self) -> &str {
471 &self.original
472 }
473
474 pub fn into_original(self) -> Cow<'a, str> {
476 self.original
477 }
478
479 pub fn is_normalized(&self) -> bool {
490 self.normalized.is_identity(&self.original)
491 }
492
493 pub fn normalized(&self) -> &str {
499 self.normalized.as_ref(&self.original)
500 }
501
502 pub fn into_normalized(self) -> Cow<'a, str> {
507 self.normalized.into_cow(self.original)
508 }
509
510 pub fn is_os_compatible(&self) -> bool {
512 self.os_compatible.is_identity(&self.original)
513 }
514
515 pub fn os_compatible(&self) -> &str {
524 self.os_compatible.as_ref(&self.original)
525 }
526
527 pub fn into_os_compatible(self) -> Cow<'a, str> {
529 self.os_compatible.into_cow(self.original)
530 }
531
532 #[cfg(feature = "std")]
534 pub fn os_str(&self) -> &OsStr {
535 OsStr::new(self.os_compatible())
536 }
537
538 #[cfg(feature = "std")]
540 pub fn into_os_str(self) -> Cow<'a, OsStr> {
541 match self.into_os_compatible() {
542 Cow::Borrowed(s) => Cow::Borrowed(OsStr::new(s)),
543 Cow::Owned(s) => Cow::Owned(OsString::from(s)),
544 }
545 }
546
547 pub fn is_borrowed(&self) -> bool {
560 matches!(self.original, Cow::Borrowed(_))
561 }
562
563 pub fn is_owned(&self) -> bool {
565 matches!(self.original, Cow::Owned(_))
566 }
567
568 pub fn into_owned(self) -> PathElementGeneric<'static, S> {
571 PathElementGeneric {
572 original: Cow::Owned(self.original.into_owned()),
573 normalized: self.normalized,
574 os_compatible: self.os_compatible,
575 case_sensitivity: self.case_sensitivity,
576 }
577 }
578}
579
580impl<'a> From<PathElementCS<'a>> for PathElement<'a> {
584 fn from(pe: PathElementCS<'a>) -> Self {
585 PathElementGeneric {
586 original: pe.original,
587 normalized: pe.normalized,
588 os_compatible: pe.os_compatible,
589 case_sensitivity: CaseSensitivity::Sensitive,
590 }
591 }
592}
593
594impl<'a> From<PathElementCI<'a>> for PathElement<'a> {
596 fn from(pe: PathElementCI<'a>) -> Self {
597 PathElementGeneric {
598 original: pe.original,
599 normalized: pe.normalized,
600 os_compatible: pe.os_compatible,
601 case_sensitivity: CaseSensitivity::Insensitive,
602 }
603 }
604}
605
606impl<'a> TryFrom<PathElement<'a>> for PathElementCS<'a> {
611 type Error = PathElementCI<'a>;
612
613 fn try_from(pe: PathElement<'a>) -> core::result::Result<Self, Self::Error> {
614 if pe.case_sensitivity == CaseSensitivity::Sensitive {
615 Ok(PathElementGeneric {
616 original: pe.original,
617 normalized: pe.normalized,
618 os_compatible: pe.os_compatible,
619 case_sensitivity: CaseSensitive,
620 })
621 } else {
622 Err(PathElementGeneric {
623 original: pe.original,
624 normalized: pe.normalized,
625 os_compatible: pe.os_compatible,
626 case_sensitivity: CaseInsensitive,
627 })
628 }
629 }
630}
631
632impl<'a> TryFrom<PathElement<'a>> for PathElementCI<'a> {
637 type Error = PathElementCS<'a>;
638
639 fn try_from(pe: PathElement<'a>) -> core::result::Result<Self, Self::Error> {
640 if pe.case_sensitivity == CaseSensitivity::Insensitive {
641 Ok(PathElementGeneric {
642 original: pe.original,
643 normalized: pe.normalized,
644 os_compatible: pe.os_compatible,
645 case_sensitivity: CaseInsensitive,
646 })
647 } else {
648 Err(PathElementGeneric {
649 original: pe.original,
650 normalized: pe.normalized,
651 os_compatible: pe.os_compatible,
652 case_sensitivity: CaseSensitive,
653 })
654 }
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use alloc::borrow::Cow;
661 use alloc::string::ToString;
662 use alloc::vec::Vec;
663
664 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
665 use wasm_bindgen_test::wasm_bindgen_test as test;
666
667 use super::{PathElement, PathElementCI, PathElementCS};
668 use crate::case_sensitivity::{CaseInsensitive, CaseSensitive, CaseSensitivity};
669 use crate::normalize::{normalize_ci_from_normalized_cs, normalize_cs};
670 use crate::os::os_compatible_from_normalized_cs;
671
672 #[test]
677 fn path_element_cs_matches_freestanding() {
678 let input = "H\tllo";
679 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
680 assert_eq!(pe.original(), input);
681 assert_eq!(pe.normalized(), normalize_cs(input).unwrap().as_ref());
682 assert_eq!(
683 pe.os_compatible(),
684 os_compatible_from_normalized_cs(&normalize_cs(input).unwrap())
685 .unwrap()
686 .as_ref()
687 );
688 }
689
690 #[test]
693 fn path_element_ci_matches_freestanding() {
694 let input = "H\tllo";
695 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
696 assert_eq!(pe.original(), "H\tllo");
697 assert_eq!(pe.normalized(), "h\u{2409}llo");
698 assert_eq!(pe.os_compatible(), "H\u{2409}llo");
699 }
700
701 #[test]
704 fn path_element_cs_os_compatible_platform_dependent() {
705 let input = "nul.e\u{0301}";
706 let pe = PathElementCS::new(input).unwrap();
707 assert_eq!(pe.original(), "nul.e\u{0301}");
708 assert_eq!(pe.normalized(), "nul.\u{00E9}");
709 #[cfg(target_os = "windows")]
710 assert_eq!(pe.os_compatible(), "\u{FF4E}ul.\u{00E9}");
711 #[cfg(target_vendor = "apple")]
712 assert_eq!(pe.os_compatible(), "nul.e\u{0301}");
713 #[cfg(not(any(target_os = "windows", target_vendor = "apple")))]
714 assert_eq!(pe.os_compatible(), "nul.\u{00E9}");
715 }
716
717 #[test]
718 fn path_element_cs_nfc_matches_freestanding() {
719 let input = "e\u{0301}.txt";
720 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
721 assert_eq!(pe.normalized(), normalize_cs(input).unwrap().as_ref());
722 }
723
724 #[test]
725 fn path_element_ci_casefold_matches_freestanding() {
726 let input = "Hello.txt";
727 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
728 let cs = normalize_cs(input).unwrap();
729 assert_eq!(
730 pe.normalized(),
731 normalize_ci_from_normalized_cs(&cs).as_ref()
732 );
733 }
734
735 #[test]
736 fn path_element_cs_normalized_borrows_from_original() {
737 let input = "hello.txt";
738 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
739 assert_eq!(pe.normalized(), "hello.txt");
740 assert!(core::ptr::eq(pe.normalized().as_ptr(), input.as_ptr()));
741 }
742
743 #[test]
744 fn path_element_cs_into_normalized_borrows() {
745 let input = "hello.txt";
746 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
747 let norm = pe.into_normalized();
748 assert!(matches!(norm, Cow::Borrowed(_)));
749 assert_eq!(norm, "hello.txt");
750 }
751
752 #[test]
753 fn path_element_cs_into_os_compatible_borrows() {
754 let input = "hello.txt";
755 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
756 let pres = pe.into_os_compatible();
757 assert!(matches!(pres, Cow::Borrowed(_)));
758 assert_eq!(pres.as_ref(), "hello.txt");
759 }
760
761 #[test]
762 fn path_element_ci_normalized_borrows_when_already_folded() {
763 let input = "hello.txt";
764 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
765 assert!(core::ptr::eq(pe.normalized().as_ptr(), input.as_ptr()));
766 }
767
768 #[test]
769 fn path_element_ci_into_normalized_borrows_when_already_folded() {
770 let input = "hello.txt";
771 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
772 let norm = pe.into_normalized();
773 assert!(matches!(norm, Cow::Borrowed(_)));
774 assert_eq!(norm, "hello.txt");
775 }
776
777 #[test]
778 fn path_element_ci_into_os_compatible_borrows_when_already_folded() {
779 let input = "hello.txt";
780 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
781 let pres = pe.into_os_compatible();
782 assert!(matches!(pres, Cow::Borrowed(_)));
783 assert_eq!(pres.as_ref(), "hello.txt");
784 }
785
786 #[test]
787 fn path_element_cs_trimmed_borrows_suffix() {
788 let input = " hello.txt";
789 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
790 assert_eq!(pe.normalized(), "hello.txt");
791 assert!(core::ptr::eq(pe.normalized().as_ptr(), input[3..].as_ptr()));
792 }
793
794 #[test]
795 fn path_element_into_original_returns_original() {
796 let input = " Hello.txt ";
797 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
798 let orig = pe.into_original();
799 assert!(matches!(orig, Cow::Borrowed(_)));
800 assert_eq!(orig, input);
801 }
802
803 #[test]
804 fn path_element_is_normalized_when_unchanged() {
805 let pe = PathElementCS::new("hello.txt").unwrap();
806 assert!(pe.is_normalized());
807 }
808
809 #[test]
810 fn path_element_is_not_normalized_when_trimmed() {
811 let pe = PathElementCS::new(" hello.txt ").unwrap();
812 assert!(!pe.is_normalized());
813 }
814
815 #[test]
816 fn path_element_is_not_normalized_when_trailing_whitespace_trimmed() {
817 let pe = PathElementCS::new("hello.txt ").unwrap();
819 assert!(!pe.is_normalized());
820 assert_eq!(pe.normalized(), "hello.txt");
821 }
822
823 #[test]
824 fn path_element_is_not_normalized_when_casefolded() {
825 let pe = PathElementCI::new("Hello.txt").unwrap();
826 assert!(!pe.is_normalized());
827 }
828
829 #[test]
830 fn path_element_ci_is_normalized_when_already_folded() {
831 let pe = PathElementCI::new("hello.txt").unwrap();
832 assert!(pe.is_normalized());
833 }
834
835 #[test]
836 fn path_element_is_os_compatible_ascii() {
837 let pe = PathElementCS::new("hello.txt").unwrap();
838 assert!(pe.is_os_compatible());
839 }
840
841 #[test]
842 fn path_element_is_not_os_compatible_trailing_whitespace_ci() {
843 let pe = PathElementCI::new("hello.txt ").unwrap();
846 assert!(!pe.is_os_compatible());
847 }
848
849 #[test]
850 fn path_element_is_not_os_compatible_trailing_whitespace_cs() {
851 let pe = PathElementCS::new("hello.txt ").unwrap();
854 assert!(!pe.is_os_compatible());
855 }
856
857 #[test]
858 fn path_element_is_os_compatible_nfc_input() {
859 let pe = PathElementCS::new("\u{00E9}.txt").unwrap();
863 #[cfg(target_vendor = "apple")]
864 assert!(!pe.is_os_compatible());
865 #[cfg(not(target_vendor = "apple"))]
866 assert!(pe.is_os_compatible());
867 }
868
869 #[test]
870 fn path_element_is_os_compatible_nfd_input() {
871 let pe = PathElementCS::new("e\u{0301}.txt").unwrap();
874 #[cfg(target_vendor = "apple")]
875 assert!(pe.is_os_compatible());
876 #[cfg(not(target_vendor = "apple"))]
877 assert!(!pe.is_os_compatible());
878 }
879
880 #[test]
881 fn path_element_is_os_compatible_nfd_input_ci() {
882 let pe = PathElementCI::new("e\u{0301}.txt").unwrap();
887 #[cfg(target_vendor = "apple")]
888 assert!(pe.is_os_compatible());
889 #[cfg(not(target_vendor = "apple"))]
890 assert!(!pe.is_os_compatible());
891 }
892
893 #[test]
894 fn path_element_is_os_compatible_nfc_input_ci() {
895 let pe = PathElementCI::new("\u{00E9}.txt").unwrap();
898 #[cfg(target_vendor = "apple")]
899 assert!(!pe.is_os_compatible());
900 #[cfg(not(target_vendor = "apple"))]
901 assert!(pe.is_os_compatible());
902 }
903
904 #[test]
905 fn path_element_is_not_os_compatible_reserved_on_windows() {
906 let pe = PathElementCS::new("nul.txt").unwrap();
907 #[cfg(target_os = "windows")]
908 assert!(!pe.is_os_compatible());
909 #[cfg(not(target_os = "windows"))]
910 assert!(pe.is_os_compatible());
911 }
912
913 #[test]
914 fn path_element_borrowed_is_borrowed() {
915 let pe = PathElementCS::new(Cow::Borrowed("hello.txt")).unwrap();
916 assert!(pe.is_borrowed());
917 assert!(!pe.is_owned());
918 }
919
920 #[test]
921 fn path_element_owned_is_owned() {
922 let pe = PathElementCS::new(Cow::Owned("hello.txt".to_string())).unwrap();
923 assert!(pe.is_owned());
924 assert!(!pe.is_borrowed());
925 }
926
927 #[test]
928 fn path_element_into_owned_is_owned() {
929 let pe = PathElementCS::new(Cow::Borrowed("hello.txt")).unwrap();
930 let owned = pe.into_owned();
931 assert!(owned.is_owned());
932 }
933
934 #[test]
935 fn path_element_into_owned_preserves_values() {
936 let input = "H\tllo";
937 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
938 let owned = pe.into_owned();
939 assert_eq!(owned.original(), "H\tllo");
940 assert_eq!(owned.normalized(), "h\u{2409}llo");
941 assert_eq!(owned.os_compatible(), "H\u{2409}llo");
942 }
943
944 #[test]
945 fn path_element_rejects_invalid() {
946 assert!(PathElementCS::new("").is_err());
947 assert!(PathElementCS::new(".").is_err());
948 assert!(PathElementCS::new("..").is_err());
949 assert!(PathElementCS::new("a/b").is_err());
950 assert!(PathElementCS::new("\0").is_err());
951 assert!(PathElementCS::new("a\0b").is_err());
952 }
953
954 #[test]
957 fn path_element_eq_same_cs() {
958 let a = PathElementCS::new("hello.txt").unwrap();
959 let b = PathElementCS::new("hello.txt").unwrap();
960 assert_eq!(a, b);
961 }
962
963 #[test]
964 fn path_element_eq_different_original_same_normalized_cs() {
965 let a = PathElementCS::new(" hello.txt ").unwrap();
966 let b = PathElementCS::new("hello.txt").unwrap();
967 assert_ne!(a.original(), b.original());
968 assert_eq!(a, b);
969 }
970
971 #[test]
972 fn path_element_ne_different_case_cs() {
973 let a = PathElementCS::new("Hello.txt").unwrap();
974 let b = PathElementCS::new("hello.txt").unwrap();
975 assert_ne!(a, b);
976 }
977
978 #[test]
979 fn path_element_eq_different_case_ci() {
980 let a = PathElementCI::new("Hello.txt").unwrap();
981 let b = PathElementCI::new("hello.txt").unwrap();
982 assert_eq!(a, b);
983 }
984
985 #[test]
986 fn path_element_eq_nfc_nfd_cs() {
987 let a = PathElementCS::new("\u{00E9}.txt").unwrap();
988 let b = PathElementCS::new("e\u{0301}.txt").unwrap();
989 assert_eq!(a, b);
990 }
991
992 #[test]
993 fn path_element_eq_cross_lifetime() {
994 let owned = PathElementCS::new("hello.txt").unwrap().into_owned();
995 let input = "hello.txt";
996 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
997 assert_eq!(owned, borrowed);
998 assert_eq!(borrowed, owned);
999 }
1000
1001 #[test]
1002 #[should_panic(expected = "different case sensitivity")]
1003 fn path_element_eq_panics_on_mixed_dynamic_case_sensitivity() {
1004 let a = PathElement::new("hello", CaseSensitive).unwrap();
1005 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1006 let _ = a == b;
1007 }
1008
1009 #[test]
1012 fn path_element_ord_alphabetical_cs() {
1013 let a = PathElementCS::new("apple").unwrap();
1014 let b = PathElementCS::new("banana").unwrap();
1015 assert!(a < b);
1016 assert!(b > a);
1017 }
1018
1019 #[test]
1020 fn path_element_ord_equal_cs() {
1021 let a = PathElementCS::new("hello").unwrap();
1022 let b = PathElementCS::new("hello").unwrap();
1023 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1024 }
1025
1026 #[test]
1027 fn path_element_ord_case_ci() {
1028 let a = PathElementCI::new("Apple").unwrap();
1029 let b = PathElementCI::new("apple").unwrap();
1030 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1031 }
1032
1033 #[test]
1034 fn path_element_partial_ord_cross_lifetime() {
1035 let owned = PathElementCS::new("apple").unwrap().into_owned();
1036 let input = "banana";
1037 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1038 assert!(owned < borrowed);
1039 }
1040
1041 #[test]
1042 fn path_element_partial_ord_none_on_mixed_dynamic_case_sensitivity() {
1043 let a = PathElement::new("hello", CaseSensitive).unwrap();
1044 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1045 assert_eq!(a.partial_cmp(&b), None);
1046 }
1047
1048 #[test]
1049 fn path_element_ord_sortable() {
1050 let mut elems: Vec<_> = ["cherry", "apple", "banana"]
1051 .iter()
1052 .map(|s| PathElementCS::new(Cow::Borrowed(*s)).unwrap())
1053 .collect();
1054 elems.sort();
1055 let names: Vec<_> = elems.iter().map(PathElementCS::normalized).collect();
1056 assert_eq!(names, &["apple", "banana", "cherry"]);
1057 }
1058
1059 #[test]
1060 fn path_element_ord_ci_sortable() {
1061 let mut elems: Vec<_> = ["Cherry", "apple", "BANANA"]
1062 .iter()
1063 .map(|s| PathElementCI::new(Cow::Borrowed(*s)).unwrap())
1064 .collect();
1065 elems.sort();
1066 let names: Vec<_> = elems.iter().map(PathElementCI::normalized).collect();
1067 assert_eq!(names, &["apple", "banana", "cherry"]);
1068 }
1069
1070 #[test]
1073 fn from_cs_into_dynamic() {
1074 let pe = PathElementCS::new("hello").unwrap();
1075 let dyn_pe: PathElement<'_> = pe.into();
1076 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1077 assert_eq!(dyn_pe.normalized(), "hello");
1078 }
1079
1080 #[test]
1081 fn from_ci_into_dynamic() {
1082 let pe = PathElementCI::new("Hello").unwrap();
1083 let dyn_pe: PathElement<'_> = pe.into();
1084 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1085 assert_eq!(dyn_pe.normalized(), "hello");
1086 }
1087
1088 #[test]
1089 fn try_from_dynamic_to_cs() {
1090 let pe = PathElement::new("hello", CaseSensitive).unwrap();
1091 let cs_pe: PathElementCS<'_> = pe.try_into().unwrap();
1092 assert_eq!(cs_pe.normalized(), "hello");
1093 }
1094
1095 #[test]
1096 fn try_from_dynamic_to_cs_wrong_variant() {
1097 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1098 let err: PathElementCI<'_> = PathElementCS::try_from(pe).unwrap_err();
1099 assert_eq!(err.original(), "Hello");
1100 assert_eq!(err.normalized(), "hello");
1101 assert_eq!(err.os_compatible(), "Hello");
1102 }
1103
1104 #[test]
1105 fn try_from_dynamic_to_ci() {
1106 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1107 let ci_pe: PathElementCI<'_> = pe.try_into().unwrap();
1108 assert_eq!(ci_pe.normalized(), "hello");
1109 }
1110
1111 #[test]
1114 fn dyn_new_cs() {
1115 let pe = PathElement::new_cs("Hello.txt").unwrap();
1116 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1117 assert_eq!(pe.normalized(), "Hello.txt");
1118 }
1119
1120 #[test]
1121 fn dyn_new_ci() {
1122 let pe = PathElement::new_ci("Hello.txt").unwrap();
1123 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1124 assert_eq!(pe.normalized(), "hello.txt");
1125 }
1126
1127 #[test]
1128 fn dyn_new_cs_matches_typed() {
1129 let dyn_pe = PathElement::new_cs("Hello.txt").unwrap();
1130 let cs_pe = PathElementCS::new("Hello.txt").unwrap();
1131 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1132 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1133 }
1134
1135 #[test]
1136 fn dyn_new_ci_matches_typed() {
1137 let dyn_pe = PathElement::new_ci("Hello.txt").unwrap();
1138 let ci_pe = PathElementCI::new("Hello.txt").unwrap();
1139 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1140 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1141 }
1142
1143 #[test]
1146 fn case_sensitivity_cs() {
1147 let pe = PathElementCS::new("hello").unwrap();
1148 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1149 }
1150
1151 #[test]
1152 fn case_sensitivity_ci() {
1153 let pe = PathElementCI::new("hello").unwrap();
1154 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1155 }
1156
1157 #[test]
1158 fn case_sensitivity_dyn() {
1159 let cs = PathElement::new("hello", CaseSensitive).unwrap();
1160 let ci = PathElement::new("hello", CaseInsensitive).unwrap();
1161 assert_eq!(cs.case_sensitivity(), CaseSensitivity::Sensitive);
1162 assert_eq!(ci.case_sensitivity(), CaseSensitivity::Insensitive);
1163 }
1164
1165 #[test]
1168 fn partial_ord_dyn_same_case_sensitivity() {
1169 let a = PathElement::new("apple", CaseSensitive).unwrap();
1170 let b = PathElement::new("banana", CaseSensitive).unwrap();
1171 assert!(a < b);
1172 }
1173
1174 #[test]
1175 fn partial_ord_dyn_none_on_mismatch() {
1176 let a = PathElement::new("hello", CaseSensitive).unwrap();
1177 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1178 assert_eq!(a.partial_cmp(&b), None);
1179 }
1180
1181 #[test]
1184 fn try_from_dynamic_to_ci_wrong_variant() {
1185 let pe = PathElement::new("Hello", CaseSensitive).unwrap();
1186 let err: PathElementCS<'_> = PathElementCI::try_from(pe).unwrap_err();
1187 assert_eq!(err.original(), "Hello");
1188 assert_eq!(err.normalized(), "Hello");
1189 assert_eq!(err.os_compatible(), "Hello");
1190 }
1191
1192 #[test]
1195 fn into_owned_preserves_cs_case_sensitivity() {
1196 let pe = PathElementCS::new("hello").unwrap();
1197 let owned = pe.into_owned();
1198 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Sensitive);
1199 }
1200
1201 #[test]
1202 fn into_owned_preserves_dyn_case_sensitivity() {
1203 let pe = PathElement::new("hello", CaseInsensitive).unwrap();
1204 let owned = pe.into_owned();
1205 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Insensitive);
1206 }
1207
1208 #[test]
1211 fn eq_cs_vs_dyn_same_case_sensitivity() {
1212 let cs = PathElementCS::new("hello").unwrap();
1213 let dyn_cs = PathElement::new_cs("hello").unwrap();
1214 assert_eq!(cs, dyn_cs);
1215 assert_eq!(dyn_cs, cs);
1216 }
1217
1218 #[test]
1219 fn eq_ci_vs_dyn_same_case_sensitivity() {
1220 let ci = PathElementCI::new("Hello").unwrap();
1221 let dyn_ci = PathElement::new_ci("hello").unwrap();
1222 assert_eq!(ci, dyn_ci);
1223 assert_eq!(dyn_ci, ci);
1224 }
1225
1226 #[test]
1227 #[should_panic(expected = "different case sensitivity")]
1228 fn eq_cs_vs_ci_panics() {
1229 let cs = PathElementCS::new("hello").unwrap();
1230 let ci = PathElementCI::new("hello").unwrap();
1231 let _ = cs == ci;
1232 }
1233
1234 #[test]
1235 #[should_panic(expected = "different case sensitivity")]
1236 fn eq_cs_vs_dyn_ci_panics() {
1237 let cs = PathElementCS::new("hello").unwrap();
1238 let dyn_ci = PathElement::new_ci("hello").unwrap();
1239 let _ = cs == dyn_ci;
1240 }
1241
1242 #[test]
1245 fn partial_ord_cs_vs_dyn_same_case_sensitivity() {
1246 let cs = PathElementCS::new("apple").unwrap();
1247 let dyn_cs = PathElement::new_cs("banana").unwrap();
1248 assert!(cs < dyn_cs);
1249 assert!(dyn_cs > cs);
1250 }
1251
1252 #[test]
1253 fn partial_ord_cs_vs_ci_none() {
1254 let cs = PathElementCS::new("hello").unwrap();
1255 let ci = PathElementCI::new("hello").unwrap();
1256 assert_eq!(cs.partial_cmp(&ci), None);
1257 assert_eq!(ci.partial_cmp(&cs), None);
1258 }
1259
1260 #[test]
1261 fn partial_ord_cs_vs_dyn_ci_none() {
1262 let cs = PathElementCS::new("hello").unwrap();
1263 let dyn_ci = PathElement::new_ci("hello").unwrap();
1264 assert_eq!(cs.partial_cmp(&dyn_ci), None);
1265 assert_eq!(dyn_ci.partial_cmp(&cs), None);
1266 }
1267
1268 #[cfg(feature = "std")]
1271 mod os_str_tests {
1272 use std::borrow::Cow;
1273 use std::ffi::{OsStr, OsString};
1274
1275 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
1276 use wasm_bindgen_test::wasm_bindgen_test as test;
1277
1278 use crate::case_sensitivity::{CaseInsensitive, CaseSensitivity};
1279 use crate::path_element::{PathElement, PathElementCI, PathElementCS};
1280
1281 #[test]
1282 fn from_os_str_borrowed_matches_new() {
1283 let input = OsStr::new("hello.txt");
1284 let from_os = PathElementCS::from_os_str(input).unwrap();
1285 let from_new = PathElementCS::new("hello.txt").unwrap();
1286 assert_eq!(from_os.original(), from_new.original());
1287 assert_eq!(from_os.normalized(), from_new.normalized());
1288 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1289 }
1290
1291 #[test]
1292 fn from_os_str_owned_matches_new() {
1293 let input = OsString::from("Hello.txt");
1294 let from_os = PathElementCI::from_os_str(input).unwrap();
1295 let from_new = PathElementCI::new("Hello.txt").unwrap();
1296 assert_eq!(from_os.original(), from_new.original());
1297 assert_eq!(from_os.normalized(), from_new.normalized());
1298 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1299 }
1300
1301 #[test]
1302 fn from_os_str_borrowed_preserves_borrow() {
1303 let input = OsStr::new("hello.txt");
1304 let pe = PathElementCS::from_os_str(input).unwrap();
1305 let orig = pe.into_original();
1306 assert!(matches!(orig, Cow::Borrowed(_)));
1307 }
1308
1309 #[cfg(unix)]
1310 #[test]
1311 fn from_os_str_invalid_utf8_borrowed() {
1312 use std::os::unix::ffi::OsStrExt;
1313 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]); let pe = PathElementCS::from_os_str(input).unwrap();
1315 assert_eq!(pe.original(), "hi\u{FFFD}");
1316 }
1317
1318 #[cfg(unix)]
1319 #[test]
1320 fn from_os_str_invalid_utf8_owned() {
1321 use std::os::unix::ffi::OsStrExt;
1322 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]).to_os_string();
1323 let pe = PathElementCS::from_os_str(input).unwrap();
1324 assert_eq!(pe.original(), "hi\u{FFFD}");
1325 }
1326
1327 #[cfg(windows)]
1328 #[test]
1329 fn from_os_str_invalid_utf8_borrowed() {
1330 use std::os::windows::ffi::OsStringExt;
1331 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1333 let pe = PathElementCS::from_os_str(input.as_os_str()).unwrap();
1334 assert_eq!(pe.original(), "h\u{FFFD}\u{FFFD}\u{FFFD}i");
1335 }
1336
1337 #[cfg(windows)]
1338 #[test]
1339 fn from_os_str_invalid_utf8_owned() {
1340 use std::os::windows::ffi::OsStringExt;
1341 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1342 let pe = PathElementCS::from_os_str(input).unwrap();
1343 assert_eq!(pe.original(), "h\u{FFFD}\u{FFFD}\u{FFFD}i");
1344 }
1345
1346 #[test]
1349 fn os_str_returns_os_compatible() {
1350 let pe = PathElementCI::new("H\tllo").unwrap();
1351 assert_eq!(pe.os_str(), OsStr::new("H\u{2409}llo"));
1352 }
1353
1354 #[test]
1355 fn into_os_str_returns_os_compatible() {
1356 let pe = PathElementCI::new("H\tllo").unwrap();
1357 let result = pe.into_os_str();
1358 assert_eq!(result, OsStr::new("H\u{2409}llo"));
1359 }
1360
1361 #[test]
1362 fn into_os_str_borrows_when_no_transformation() {
1363 let input = OsStr::new("hello.txt");
1364 let pe = PathElementCS::from_os_str(input).unwrap();
1365 let result = pe.into_os_str();
1366 assert!(matches!(result, Cow::Borrowed(_)));
1367 assert_eq!(result, OsStr::new("hello.txt"));
1368 }
1369
1370 #[test]
1371 fn into_os_str_ci_borrows_when_already_folded() {
1372 let input = OsStr::new("hello.txt");
1373 let pe = PathElementCI::from_os_str(input).unwrap();
1374 let result = pe.into_os_str();
1375 assert!(matches!(result, Cow::Borrowed(_)));
1376 assert_eq!(result, OsStr::new("hello.txt"));
1377 }
1378
1379 #[test]
1382 fn into_os_str_owned_when_nfc_transforms() {
1383 let input = OsStr::new("e\u{0301}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1385 let result = pe.into_os_str();
1386 #[cfg(target_vendor = "apple")]
1387 assert!(matches!(result, Cow::Borrowed(_)));
1388 #[cfg(not(target_vendor = "apple"))]
1389 assert!(matches!(result, Cow::Owned(_)));
1390 }
1391
1392 #[test]
1394 fn into_os_str_owned_when_nfd_transforms() {
1395 let input = OsStr::new("\u{00E9}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1397 let result = pe.into_os_str();
1398 #[cfg(target_vendor = "apple")]
1399 assert!(matches!(result, Cow::Owned(_)));
1400 #[cfg(not(target_vendor = "apple"))]
1401 assert!(matches!(result, Cow::Borrowed(_)));
1402 }
1403
1404 #[test]
1405 fn from_os_str_cs_matches_typed() {
1406 let input = OsStr::new("Hello.txt");
1407 let dyn_pe = PathElement::from_os_str_cs(input).unwrap();
1408 let cs_pe = PathElementCS::from_os_str(input).unwrap();
1409 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1410 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1411 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1412 }
1413
1414 #[test]
1415 fn from_os_str_ci_matches_typed() {
1416 let input = OsStr::new("Hello.txt");
1417 let dyn_pe = PathElement::from_os_str_ci(input).unwrap();
1418 let ci_pe = PathElementCI::from_os_str(input).unwrap();
1419 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1420 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1421 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1422 }
1423
1424 #[test]
1425 fn from_os_str_dynamic_case_sensitivity() {
1426 let input = OsStr::new("Hello.txt");
1427 let pe = PathElement::from_os_str(input, CaseInsensitive).unwrap();
1428 assert_eq!(pe.normalized(), "hello.txt");
1429 }
1430 }
1431
1432 mod from_bytes_tests {
1435 use alloc::borrow::Cow;
1436 use alloc::vec;
1437
1438 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
1439 use wasm_bindgen_test::wasm_bindgen_test as test;
1440
1441 use crate::case_sensitivity::{CaseInsensitive, CaseSensitive, CaseSensitivity};
1442 use crate::path_element::{PathElement, PathElementCI, PathElementCS};
1443
1444 #[test]
1445 fn from_bytes_cs_borrowed_matches_new() {
1446 let pe_bytes = PathElementCS::from_bytes(b"hello.txt" as &[u8]).unwrap();
1447 let pe_str = PathElementCS::new("hello.txt").unwrap();
1448 assert_eq!(pe_bytes.original(), pe_str.original());
1449 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1450 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1451 }
1452
1453 #[test]
1454 fn from_bytes_ci_borrowed_matches_new() {
1455 let pe_bytes = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1456 let pe_str = PathElementCI::new("Hello.txt").unwrap();
1457 assert_eq!(pe_bytes.original(), pe_str.original());
1458 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1459 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1460 }
1461
1462 #[test]
1463 fn from_bytes_owned_matches_new() {
1464 let pe_bytes = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1465 let pe_str = PathElementCS::new("hello.txt").unwrap();
1466 assert_eq!(pe_bytes.original(), pe_str.original());
1467 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1468 }
1469
1470 #[test]
1471 fn from_bytes_borrowed_preserves_borrow() {
1472 let input: &[u8] = b"hello.txt";
1473 let pe = PathElementCS::from_bytes(input).unwrap();
1474 let orig = pe.into_original();
1475 assert!(matches!(orig, Cow::Borrowed(_)));
1476 }
1477
1478 #[test]
1479 fn from_bytes_owned_is_owned() {
1480 let pe = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1481 assert!(pe.is_owned());
1482 }
1483
1484 #[test]
1485 fn from_bytes_invalid_utf8_borrowed_uses_replacement() {
1486 let input: &[u8] = &[0x68, 0x69, 0xFF]; let pe = PathElementCS::from_bytes(input).unwrap();
1488 assert_eq!(pe.original(), "hi\u{FFFD}");
1489 }
1490
1491 #[test]
1492 fn from_bytes_invalid_utf8_owned_uses_replacement() {
1493 let input = vec![0x68, 0x69, 0xFF];
1494 let pe = PathElementCS::from_bytes(input).unwrap();
1495 assert_eq!(pe.original(), "hi\u{FFFD}");
1496 }
1497
1498 #[test]
1499 fn from_bytes_dynamic_case_sensitivity() {
1500 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseInsensitive).unwrap();
1501 assert_eq!(pe.normalized(), "hello.txt");
1502 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1503 }
1504
1505 #[test]
1506 fn from_bytes_cs_matches_typed() {
1507 let input: &[u8] = b"Hello.txt";
1508 let dyn_pe = PathElement::from_bytes_cs(input).unwrap();
1509 let cs_pe = PathElementCS::from_bytes(input).unwrap();
1510 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1511 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1512 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1513 }
1514
1515 #[test]
1516 fn from_bytes_ci_matches_typed() {
1517 let input: &[u8] = b"Hello.txt";
1518 let dyn_pe = PathElement::from_bytes_ci(input).unwrap();
1519 let ci_pe = PathElementCI::from_bytes(input).unwrap();
1520 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1521 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1522 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1523 }
1524
1525 #[test]
1526 fn from_bytes_with_case_sensitivity_cs() {
1527 let pe = PathElementCS::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1528 assert_eq!(pe.normalized(), "Hello.txt");
1529 }
1530
1531 #[test]
1532 fn from_bytes_with_case_sensitivity_ci() {
1533 let pe = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1534 assert_eq!(pe.normalized(), "hello.txt");
1535 }
1536
1537 #[test]
1538 fn from_bytes_rejects_empty() {
1539 assert!(PathElementCS::from_bytes(b"" as &[u8]).is_err());
1540 }
1541
1542 #[test]
1543 fn from_bytes_rejects_dot() {
1544 assert!(PathElementCS::from_bytes(b"." as &[u8]).is_err());
1545 }
1546
1547 #[test]
1548 fn from_bytes_rejects_dotdot() {
1549 assert!(PathElementCS::from_bytes(b".." as &[u8]).is_err());
1550 }
1551
1552 #[test]
1553 fn from_bytes_rejects_slash() {
1554 assert!(PathElementCS::from_bytes(b"a/b" as &[u8]).is_err());
1555 }
1556
1557 #[test]
1558 fn from_bytes_rejects_null() {
1559 assert!(PathElementCS::from_bytes(b"\0" as &[u8]).is_err());
1560 }
1561
1562 #[test]
1563 fn from_bytes_dynamic_sensitive() {
1564 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseSensitive).unwrap();
1565 assert_eq!(pe.normalized(), "Hello.txt");
1566 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1567 }
1568
1569 #[test]
1572 fn from_bytes_overlong_null() {
1573 let input: &[u8] = &[0x61, 0xC0, 0x80, 0x62]; let pe = PathElementCS::from_bytes(input).unwrap();
1576 assert_eq!(pe.original(), "a\u{FFFD}\u{FFFD}b");
1577 }
1578
1579 #[test]
1580 fn from_bytes_surrogate_bytes_replaced() {
1581 let input: &[u8] = &[0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80];
1584 let pe = PathElementCS::from_bytes(input).unwrap();
1585 assert!(pe.original().contains('\u{FFFD}'));
1586 }
1587
1588 #[test]
1589 fn from_bytes_lone_high_surrogate_replaced() {
1590 let input: &[u8] = &[0x61, 0xED, 0xA0, 0x80, 0x62];
1591 let pe = PathElementCS::from_bytes(input).unwrap();
1592 assert_eq!(pe.original(), "a\u{FFFD}\u{FFFD}\u{FFFD}b");
1593 }
1594
1595 #[test]
1596 fn from_bytes_lone_low_surrogate_replaced() {
1597 let input: &[u8] = &[0x61, 0xED, 0xB0, 0x80, 0x62];
1598 let pe = PathElementCS::from_bytes(input).unwrap();
1599 assert_eq!(pe.original(), "a\u{FFFD}\u{FFFD}\u{FFFD}b");
1600 }
1601
1602 #[test]
1603 fn from_bytes_overlong_null_only() {
1604 let input: &[u8] = &[0xC0, 0x80];
1605 let pe = PathElementCS::from_bytes(input).unwrap();
1606 assert_eq!(pe.original(), "\u{FFFD}\u{FFFD}");
1607 }
1608
1609 #[test]
1610 fn from_bytes_invalid_byte_replaced() {
1611 let input: &[u8] = &[0x68, 0x69, 0xFF]; let pe = PathElementCS::from_bytes(input).unwrap();
1613 assert_eq!(pe.original(), "hi\u{FFFD}");
1614 }
1615 }
1616
1617 #[test]
1620 fn os_compatible_supplementary_unchanged() {
1621 let pe = PathElementCS::new("file_😀.txt").unwrap();
1622 assert_eq!(pe.os_compatible(), "file_😀.txt");
1623 }
1624
1625 #[test]
1626 fn os_compatible_supplementary_roundtrip() {
1627 let pe = PathElementCS::new("file_😀.txt").unwrap();
1628 let pe2 = PathElementCS::new(pe.os_compatible()).unwrap();
1629 assert_eq!(pe.normalized(), pe2.normalized());
1630 }
1631
1632 #[test]
1633 fn os_compatible_multiple_supplementary() {
1634 let pe = PathElementCS::new("𐀀_𝄞_😀").unwrap();
1635 assert_eq!(pe.os_compatible(), "𐀀_𝄞_😀");
1636 }
1637}