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
12pub type PathElementCS<'a> = PathElementGeneric<'a, CaseSensitive>;
22
23pub type PathElementCI<'a> = PathElementGeneric<'a, CaseInsensitive>;
33
34pub type PathElement<'a> = PathElementGeneric<'a, CaseSensitivity>;
46
47#[derive(Clone)]
67pub struct PathElementGeneric<'a, S> {
68 original: Cow<'a, str>,
69 normalized: SubstringOrOwned,
71 os_compatible: SubstringOrOwned,
73 case_sensitivity: S,
74}
75
76impl<S: core::fmt::Debug> core::fmt::Debug for PathElementGeneric<'_, S> {
77 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78 f.debug_struct("PathElement")
79 .field("original", &self.original())
80 .field("normalized", &self.normalized())
81 .field("os_compatible", &self.os_compatible())
82 .field("case_sensitivity", &self.case_sensitivity)
83 .finish()
84 }
85}
86
87impl<'a, S1, S2> PartialEq<PathElementGeneric<'a, S2>> for PathElementGeneric<'_, S1>
93where
94 for<'s> CaseSensitivity: From<&'s S1> + From<&'s S2>,
95{
96 fn eq(&self, other: &PathElementGeneric<'a, S2>) -> bool {
97 assert_eq!(
98 CaseSensitivity::from(&self.case_sensitivity),
99 CaseSensitivity::from(&other.case_sensitivity),
100 "comparing PathElements with different case sensitivity"
101 );
102 self.normalized() == other.normalized()
103 }
104}
105
106impl<S> Eq for PathElementGeneric<'_, S> where for<'s> CaseSensitivity: From<&'s S> {}
108
109impl<'a, S1, S2> PartialOrd<PathElementGeneric<'a, S2>> for PathElementGeneric<'_, S1>
112where
113 for<'s> CaseSensitivity: From<&'s S1> + From<&'s S2>,
114{
115 fn partial_cmp(&self, other: &PathElementGeneric<'a, S2>) -> Option<core::cmp::Ordering> {
116 if CaseSensitivity::from(&self.case_sensitivity)
117 != CaseSensitivity::from(&other.case_sensitivity)
118 {
119 return None;
120 }
121 Some(self.normalized().cmp(other.normalized()))
122 }
123}
124
125impl<S> Ord for PathElementGeneric<'_, S>
133where
134 for<'s> CaseSensitivity: From<&'s S>,
135{
136 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
137 assert_eq!(
138 CaseSensitivity::from(&self.case_sensitivity),
139 CaseSensitivity::from(&other.case_sensitivity),
140 "comparing PathElements with different case sensitivity"
141 );
142 self.normalized().cmp(other.normalized())
143 }
144}
145
146impl core::hash::Hash for PathElementCS<'_> {
149 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
150 self.normalized().hash(state);
151 }
152}
153
154impl core::hash::Hash for PathElementCI<'_> {
157 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
158 self.normalized().hash(state);
159 }
160}
161
162impl<'a> PathElementCS<'a> {
163 pub fn new(original: impl Into<Cow<'a, str>>) -> Result<Self> {
176 Self::with_case_sensitivity(original, CaseSensitive)
177 }
178
179 pub fn from_bytes(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
199 Self::from_bytes_with_case_sensitivity(original, CaseSensitive)
200 }
201
202 #[cfg(feature = "std")]
219 pub fn from_os_str(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
220 Self::from_os_str_with_case_sensitivity(original, CaseSensitive)
221 }
222}
223
224impl<'a> PathElementCI<'a> {
225 pub fn new(original: impl Into<Cow<'a, str>>) -> Result<Self> {
238 Self::with_case_sensitivity(original, CaseInsensitive)
239 }
240
241 pub fn from_bytes(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
257 Self::from_bytes_with_case_sensitivity(original, CaseInsensitive)
258 }
259
260 #[cfg(feature = "std")]
269 pub fn from_os_str(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
270 Self::from_os_str_with_case_sensitivity(original, CaseInsensitive)
271 }
272}
273
274impl<'a> PathElementGeneric<'a, CaseSensitivity> {
275 pub fn new(
290 original: impl Into<Cow<'a, str>>,
291 case_sensitivity: impl Into<CaseSensitivity>,
292 ) -> Result<Self> {
293 Self::with_case_sensitivity(original, case_sensitivity)
294 }
295
296 pub fn from_bytes(
305 original: impl Into<Cow<'a, [u8]>>,
306 case_sensitivity: impl Into<CaseSensitivity>,
307 ) -> Result<Self> {
308 Self::from_bytes_with_case_sensitivity(original, case_sensitivity)
309 }
310
311 #[cfg(feature = "std")]
320 pub fn from_os_str(
321 original: impl Into<Cow<'a, OsStr>>,
322 case_sensitivity: impl Into<CaseSensitivity>,
323 ) -> Result<Self> {
324 Self::from_os_str_with_case_sensitivity(original, case_sensitivity)
325 }
326
327 pub fn new_cs(original: impl Into<Cow<'a, str>>) -> Result<Self> {
332 Self::with_case_sensitivity(original, CaseSensitive)
333 }
334
335 pub fn new_ci(original: impl Into<Cow<'a, str>>) -> Result<Self> {
340 Self::with_case_sensitivity(original, CaseInsensitive)
341 }
342
343 pub fn from_bytes_cs(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
351 Self::from_bytes_with_case_sensitivity(original, CaseSensitive)
352 }
353
354 pub fn from_bytes_ci(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
362 Self::from_bytes_with_case_sensitivity(original, CaseInsensitive)
363 }
364
365 #[cfg(feature = "std")]
373 pub fn from_os_str_cs(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
374 Self::from_os_str_with_case_sensitivity(original, CaseSensitive)
375 }
376
377 #[cfg(feature = "std")]
385 pub fn from_os_str_ci(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
386 Self::from_os_str_with_case_sensitivity(original, CaseInsensitive)
387 }
388}
389
390impl<'a, S> PathElementGeneric<'a, S>
391where
392 for<'s> CaseSensitivity: From<&'s S>,
393{
394 pub fn from_bytes_with_case_sensitivity(
408 original: impl Into<Cow<'a, [u8]>>,
409 case_sensitivity: impl Into<S>,
410 ) -> Result<Self> {
411 let cow_str = match original.into() {
412 Cow::Borrowed(b) => String::from_utf8_lossy(b),
413 Cow::Owned(v) => match String::from_utf8_lossy(&v) {
415 Cow::Borrowed(_) => unsafe { Cow::Owned(String::from_utf8_unchecked(v)) },
417 Cow::Owned(s) => Cow::Owned(s),
418 },
419 };
420 Self::with_case_sensitivity(cow_str, case_sensitivity)
421 }
422
423 #[cfg(feature = "std")]
433 pub fn from_os_str_with_case_sensitivity(
434 original: impl Into<Cow<'a, OsStr>>,
435 case_sensitivity: impl Into<S>,
436 ) -> Result<Self> {
437 let cow_bytes: Cow<'a, [u8]> = match original.into() {
438 Cow::Borrowed(os) => Cow::Borrowed(os.as_encoded_bytes()),
439 Cow::Owned(os) => Cow::Owned(os.into_encoded_bytes()),
440 };
441 Self::from_bytes_with_case_sensitivity(cow_bytes, case_sensitivity)
442 }
443
444 pub fn with_case_sensitivity(
454 original: impl Into<Cow<'a, str>>,
455 case_sensitivity: impl Into<S>,
456 ) -> Result<Self> {
457 let original = original.into();
458 let case_sensitivity = case_sensitivity.into();
459 let cs = CaseSensitivity::from(&case_sensitivity);
460
461 let cs_normalized = match normalize_cs(&original) {
462 Ok(v) => v,
463 Err(kind) => return Err(kind.into_error(original)),
464 };
465 let normalized = match cs {
466 CaseSensitivity::Sensitive => SubstringOrOwned::new(&cs_normalized, &original),
467 CaseSensitivity::Insensitive => {
468 SubstringOrOwned::new(&normalize_ci_from_normalized_cs(&cs_normalized), &original)
469 }
470 };
471 let os_str = match os_compatible_from_normalized_cs(&cs_normalized) {
472 Ok(v) => v,
473 Err(kind) => return Err(kind.into_error(original)),
474 };
475 let os_compatible = SubstringOrOwned::new(&os_str, &original);
476 Ok(Self {
477 original,
478 normalized,
479 os_compatible,
480 case_sensitivity,
481 })
482 }
483
484 pub fn case_sensitivity(&self) -> CaseSensitivity {
493 CaseSensitivity::from(&self.case_sensitivity)
494 }
495}
496
497impl<'a, S> PathElementGeneric<'a, S> {
498 pub fn original(&self) -> &str {
508 &self.original
509 }
510
511 pub fn into_original(self) -> Cow<'a, str> {
520 self.original
521 }
522
523 pub fn is_normalized(&self) -> bool {
534 self.normalized.is_identity(&self.original)
535 }
536
537 pub fn normalized(&self) -> &str {
553 self.normalized.as_ref(&self.original)
554 }
555
556 pub fn into_normalized(self) -> Cow<'a, str> {
569 self.normalized.into_cow(self.original)
570 }
571
572 pub fn is_os_compatible(&self) -> bool {
581 self.os_compatible.is_identity(&self.original)
582 }
583
584 pub fn os_compatible(&self) -> &str {
593 self.os_compatible.as_ref(&self.original)
594 }
595
596 pub fn into_os_compatible(self) -> Cow<'a, str> {
606 self.os_compatible.into_cow(self.original)
607 }
608
609 #[cfg(feature = "std")]
619 pub fn os_str(&self) -> &OsStr {
620 OsStr::new(self.os_compatible())
621 }
622
623 #[cfg(feature = "std")]
634 pub fn into_os_str(self) -> Cow<'a, OsStr> {
635 match self.into_os_compatible() {
636 Cow::Borrowed(s) => Cow::Borrowed(OsStr::new(s)),
637 Cow::Owned(s) => Cow::Owned(OsString::from(s)),
638 }
639 }
640
641 pub fn is_borrowed(&self) -> bool {
654 matches!(self.original, Cow::Borrowed(_))
655 }
656
657 pub fn is_owned(&self) -> bool {
670 matches!(self.original, Cow::Owned(_))
671 }
672
673 pub fn into_owned(self) -> PathElementGeneric<'static, S> {
685 PathElementGeneric {
686 original: Cow::Owned(self.original.into_owned()),
687 normalized: self.normalized,
688 os_compatible: self.os_compatible,
689 case_sensitivity: self.case_sensitivity,
690 }
691 }
692}
693
694impl<'a> From<PathElementCS<'a>> for PathElement<'a> {
698 fn from(pe: PathElementCS<'a>) -> Self {
699 Self {
700 original: pe.original,
701 normalized: pe.normalized,
702 os_compatible: pe.os_compatible,
703 case_sensitivity: CaseSensitivity::Sensitive,
704 }
705 }
706}
707
708impl<'a> From<PathElementCI<'a>> for PathElement<'a> {
710 fn from(pe: PathElementCI<'a>) -> Self {
711 Self {
712 original: pe.original,
713 normalized: pe.normalized,
714 os_compatible: pe.os_compatible,
715 case_sensitivity: CaseSensitivity::Insensitive,
716 }
717 }
718}
719
720impl<'a> TryFrom<PathElement<'a>> for PathElementCS<'a> {
725 type Error = PathElementCI<'a>;
726
727 fn try_from(pe: PathElement<'a>) -> core::result::Result<Self, Self::Error> {
728 if pe.case_sensitivity == CaseSensitivity::Sensitive {
729 Ok(Self {
730 original: pe.original,
731 normalized: pe.normalized,
732 os_compatible: pe.os_compatible,
733 case_sensitivity: CaseSensitive,
734 })
735 } else {
736 Err(Self::Error {
737 original: pe.original,
738 normalized: pe.normalized,
739 os_compatible: pe.os_compatible,
740 case_sensitivity: CaseInsensitive,
741 })
742 }
743 }
744}
745
746impl<'a> TryFrom<PathElement<'a>> for PathElementCI<'a> {
751 type Error = PathElementCS<'a>;
752
753 fn try_from(pe: PathElement<'a>) -> core::result::Result<Self, Self::Error> {
754 if pe.case_sensitivity == CaseSensitivity::Insensitive {
755 Ok(Self {
756 original: pe.original,
757 normalized: pe.normalized,
758 os_compatible: pe.os_compatible,
759 case_sensitivity: CaseInsensitive,
760 })
761 } else {
762 Err(Self::Error {
763 original: pe.original,
764 normalized: pe.normalized,
765 os_compatible: pe.os_compatible,
766 case_sensitivity: CaseSensitive,
767 })
768 }
769 }
770}
771
772#[cfg(test)]
773mod tests {
774 use alloc::borrow::Cow;
775 use alloc::string::ToString;
776 use alloc::vec::Vec;
777
778 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
779 use wasm_bindgen_test::wasm_bindgen_test as test;
780
781 use super::{PathElement, PathElementCI, PathElementCS};
782 use crate::case_sensitivity::{CaseInsensitive, CaseSensitive, CaseSensitivity};
783 use crate::normalize::{normalize_ci_from_normalized_cs, normalize_cs};
784 use crate::os::os_compatible_from_normalized_cs;
785
786 #[test]
791 fn path_element_cs_matches_freestanding() {
792 let input = "H\tllo";
793 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
794 assert_eq!(pe.original(), input);
795 assert_eq!(pe.normalized(), normalize_cs(input).unwrap().as_ref());
796 assert_eq!(
797 pe.os_compatible(),
798 os_compatible_from_normalized_cs(&normalize_cs(input).unwrap())
799 .unwrap()
800 .as_ref()
801 );
802 }
803
804 #[test]
807 fn path_element_ci_matches_freestanding() {
808 let input = "H\tllo";
809 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
810 assert_eq!(pe.original(), "H\tllo");
811 assert_eq!(pe.normalized(), "h\u{2409}llo");
812 assert_eq!(pe.os_compatible(), "H\u{2409}llo");
813 }
814
815 #[test]
818 fn path_element_cs_os_compatible_platform_dependent() {
819 let input = "nul.e\u{0301}";
820 let pe = PathElementCS::new(input).unwrap();
821 assert_eq!(pe.original(), "nul.e\u{0301}");
822 assert_eq!(pe.normalized(), "nul.\u{00E9}");
823 #[cfg(target_os = "windows")]
824 assert_eq!(pe.os_compatible(), "\u{FF4E}ul.\u{00E9}");
825 #[cfg(target_vendor = "apple")]
826 assert_eq!(pe.os_compatible(), "nul.e\u{0301}");
827 #[cfg(not(any(target_os = "windows", target_vendor = "apple")))]
828 assert_eq!(pe.os_compatible(), "nul.\u{00E9}");
829 }
830
831 #[test]
832 fn path_element_cs_nfc_matches_freestanding() {
833 let input = "e\u{0301}.txt";
834 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
835 assert_eq!(pe.normalized(), normalize_cs(input).unwrap().as_ref());
836 }
837
838 #[test]
839 fn path_element_ci_casefold_matches_freestanding() {
840 let input = "Hello.txt";
841 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
842 let cs = normalize_cs(input).unwrap();
843 assert_eq!(
844 pe.normalized(),
845 normalize_ci_from_normalized_cs(&cs).as_ref()
846 );
847 }
848
849 #[test]
850 fn path_element_cs_normalized_borrows_from_original() {
851 let input = "hello.txt";
852 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
853 assert_eq!(pe.normalized(), "hello.txt");
854 assert!(core::ptr::eq(pe.normalized().as_ptr(), input.as_ptr()));
855 }
856
857 #[test]
858 fn path_element_cs_into_normalized_borrows() {
859 let input = "hello.txt";
860 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
861 let norm = pe.into_normalized();
862 assert!(matches!(norm, Cow::Borrowed(_)));
863 assert_eq!(norm, "hello.txt");
864 }
865
866 #[test]
867 fn path_element_cs_into_os_compatible_borrows() {
868 let input = "hello.txt";
869 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
870 let pres = pe.into_os_compatible();
871 assert!(matches!(pres, Cow::Borrowed(_)));
872 assert_eq!(pres.as_ref(), "hello.txt");
873 }
874
875 #[test]
876 fn path_element_ci_normalized_borrows_when_already_folded() {
877 let input = "hello.txt";
878 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
879 assert!(core::ptr::eq(pe.normalized().as_ptr(), input.as_ptr()));
880 }
881
882 #[test]
883 fn path_element_ci_into_normalized_borrows_when_already_folded() {
884 let input = "hello.txt";
885 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
886 let norm = pe.into_normalized();
887 assert!(matches!(norm, Cow::Borrowed(_)));
888 assert_eq!(norm, "hello.txt");
889 }
890
891 #[test]
892 fn path_element_ci_into_os_compatible_borrows_when_already_folded() {
893 let input = "hello.txt";
894 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
895 let pres = pe.into_os_compatible();
896 assert!(matches!(pres, Cow::Borrowed(_)));
897 assert_eq!(pres.as_ref(), "hello.txt");
898 }
899
900 #[test]
901 fn path_element_cs_trimmed_borrows_suffix() {
902 let input = " hello.txt";
903 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
904 assert_eq!(pe.normalized(), "hello.txt");
905 assert!(core::ptr::eq(pe.normalized().as_ptr(), input[3..].as_ptr()));
906 }
907
908 #[test]
909 fn path_element_into_original_returns_original() {
910 let input = " Hello.txt ";
911 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
912 let orig = pe.into_original();
913 assert!(matches!(orig, Cow::Borrowed(_)));
914 assert_eq!(orig, input);
915 }
916
917 #[test]
918 fn path_element_is_normalized_when_unchanged() {
919 let pe = PathElementCS::new("hello.txt").unwrap();
920 assert!(pe.is_normalized());
921 }
922
923 #[test]
924 fn path_element_is_not_normalized_when_trimmed() {
925 let pe = PathElementCS::new(" hello.txt ").unwrap();
926 assert!(!pe.is_normalized());
927 }
928
929 #[test]
930 fn path_element_is_not_normalized_when_trailing_whitespace_trimmed() {
931 let pe = PathElementCS::new("hello.txt ").unwrap();
933 assert!(!pe.is_normalized());
934 assert_eq!(pe.normalized(), "hello.txt");
935 }
936
937 #[test]
938 fn path_element_is_not_normalized_when_casefolded() {
939 let pe = PathElementCI::new("Hello.txt").unwrap();
940 assert!(!pe.is_normalized());
941 }
942
943 #[test]
944 fn path_element_ci_is_normalized_when_already_folded() {
945 let pe = PathElementCI::new("hello.txt").unwrap();
946 assert!(pe.is_normalized());
947 }
948
949 #[test]
950 fn path_element_is_os_compatible_ascii() {
951 let pe = PathElementCS::new("hello.txt").unwrap();
952 assert!(pe.is_os_compatible());
953 }
954
955 #[test]
956 fn path_element_is_not_os_compatible_trailing_whitespace_ci() {
957 let pe = PathElementCI::new("hello.txt ").unwrap();
960 assert!(!pe.is_os_compatible());
961 }
962
963 #[test]
964 fn path_element_is_not_os_compatible_trailing_whitespace_cs() {
965 let pe = PathElementCS::new("hello.txt ").unwrap();
968 assert!(!pe.is_os_compatible());
969 }
970
971 #[test]
972 fn path_element_is_os_compatible_nfc_input() {
973 let pe = PathElementCS::new("\u{00E9}.txt").unwrap();
977 #[cfg(target_vendor = "apple")]
978 assert!(!pe.is_os_compatible());
979 #[cfg(not(target_vendor = "apple"))]
980 assert!(pe.is_os_compatible());
981 }
982
983 #[test]
984 fn path_element_is_os_compatible_nfd_input() {
985 let pe = PathElementCS::new("e\u{0301}.txt").unwrap();
988 #[cfg(target_vendor = "apple")]
989 assert!(pe.is_os_compatible());
990 #[cfg(not(target_vendor = "apple"))]
991 assert!(!pe.is_os_compatible());
992 }
993
994 #[test]
995 fn path_element_is_os_compatible_nfd_input_ci() {
996 let pe = PathElementCI::new("e\u{0301}.txt").unwrap();
1001 #[cfg(target_vendor = "apple")]
1002 assert!(pe.is_os_compatible());
1003 #[cfg(not(target_vendor = "apple"))]
1004 assert!(!pe.is_os_compatible());
1005 }
1006
1007 #[test]
1008 fn path_element_is_os_compatible_nfc_input_ci() {
1009 let pe = PathElementCI::new("\u{00E9}.txt").unwrap();
1012 #[cfg(target_vendor = "apple")]
1013 assert!(!pe.is_os_compatible());
1014 #[cfg(not(target_vendor = "apple"))]
1015 assert!(pe.is_os_compatible());
1016 }
1017
1018 #[test]
1019 fn path_element_is_not_os_compatible_reserved_on_windows() {
1020 let pe = PathElementCS::new("nul.txt").unwrap();
1021 #[cfg(target_os = "windows")]
1022 assert!(!pe.is_os_compatible());
1023 #[cfg(not(target_os = "windows"))]
1024 assert!(pe.is_os_compatible());
1025 }
1026
1027 #[test]
1028 fn path_element_borrowed_is_borrowed() {
1029 let pe = PathElementCS::new(Cow::Borrowed("hello.txt")).unwrap();
1030 assert!(pe.is_borrowed());
1031 assert!(!pe.is_owned());
1032 }
1033
1034 #[test]
1035 fn path_element_owned_is_owned() {
1036 let pe = PathElementCS::new(Cow::Owned("hello.txt".to_string())).unwrap();
1037 assert!(pe.is_owned());
1038 assert!(!pe.is_borrowed());
1039 }
1040
1041 #[test]
1042 fn path_element_into_owned_is_owned() {
1043 let pe = PathElementCS::new(Cow::Borrowed("hello.txt")).unwrap();
1044 let owned = pe.into_owned();
1045 assert!(owned.is_owned());
1046 }
1047
1048 #[test]
1049 fn path_element_into_owned_preserves_values() {
1050 let input = "H\tllo";
1051 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
1052 let owned = pe.into_owned();
1053 assert_eq!(owned.original(), "H\tllo");
1054 assert_eq!(owned.normalized(), "h\u{2409}llo");
1055 assert_eq!(owned.os_compatible(), "H\u{2409}llo");
1056 }
1057
1058 #[test]
1059 fn path_element_rejects_invalid() {
1060 assert!(PathElementCS::new("").is_err());
1061 assert!(PathElementCS::new(".").is_err());
1062 assert!(PathElementCS::new("..").is_err());
1063 assert!(PathElementCS::new("a/b").is_err());
1064 assert!(PathElementCS::new("\0").is_err());
1065 assert!(PathElementCS::new("a\0b").is_err());
1066 }
1067
1068 #[test]
1071 fn path_element_eq_same_cs() {
1072 let a = PathElementCS::new("hello.txt").unwrap();
1073 let b = PathElementCS::new("hello.txt").unwrap();
1074 assert_eq!(a, b);
1075 }
1076
1077 #[test]
1078 fn path_element_eq_different_original_same_normalized_cs() {
1079 let a = PathElementCS::new(" hello.txt ").unwrap();
1080 let b = PathElementCS::new("hello.txt").unwrap();
1081 assert_ne!(a.original(), b.original());
1082 assert_eq!(a, b);
1083 }
1084
1085 #[test]
1086 fn path_element_ne_different_case_cs() {
1087 let a = PathElementCS::new("Hello.txt").unwrap();
1088 let b = PathElementCS::new("hello.txt").unwrap();
1089 assert_ne!(a, b);
1090 }
1091
1092 #[test]
1093 fn path_element_eq_different_case_ci() {
1094 let a = PathElementCI::new("Hello.txt").unwrap();
1095 let b = PathElementCI::new("hello.txt").unwrap();
1096 assert_eq!(a, b);
1097 }
1098
1099 #[test]
1100 fn path_element_eq_nfc_nfd_cs() {
1101 let a = PathElementCS::new("\u{00E9}.txt").unwrap();
1102 let b = PathElementCS::new("e\u{0301}.txt").unwrap();
1103 assert_eq!(a, b);
1104 }
1105
1106 #[test]
1107 fn path_element_eq_cross_lifetime() {
1108 let owned = PathElementCS::new("hello.txt").unwrap().into_owned();
1109 let input = "hello.txt";
1110 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1111 assert_eq!(owned, borrowed);
1112 assert_eq!(borrowed, owned);
1113 }
1114
1115 #[test]
1116 #[should_panic(expected = "different case sensitivity")]
1117 fn path_element_eq_panics_on_mixed_dynamic_case_sensitivity() {
1118 let a = PathElement::new("hello", CaseSensitive).unwrap();
1119 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1120 let _ = a == b;
1121 }
1122
1123 #[test]
1126 fn path_element_ord_alphabetical_cs() {
1127 let a = PathElementCS::new("apple").unwrap();
1128 let b = PathElementCS::new("banana").unwrap();
1129 assert!(a < b);
1130 assert!(b > a);
1131 }
1132
1133 #[test]
1134 fn path_element_ord_equal_cs() {
1135 let a = PathElementCS::new("hello").unwrap();
1136 let b = PathElementCS::new("hello").unwrap();
1137 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1138 }
1139
1140 #[test]
1141 fn path_element_ord_case_ci() {
1142 let a = PathElementCI::new("Apple").unwrap();
1143 let b = PathElementCI::new("apple").unwrap();
1144 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1145 }
1146
1147 #[test]
1148 fn path_element_partial_ord_cross_lifetime() {
1149 let owned = PathElementCS::new("apple").unwrap().into_owned();
1150 let input = "banana";
1151 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1152 assert!(owned < borrowed);
1153 }
1154
1155 #[test]
1156 fn path_element_partial_ord_none_on_mixed_dynamic_case_sensitivity() {
1157 let a = PathElement::new("hello", CaseSensitive).unwrap();
1158 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1159 assert_eq!(a.partial_cmp(&b), None);
1160 }
1161
1162 #[test]
1163 fn path_element_ord_sortable() {
1164 let mut elems: Vec<_> = ["cherry", "apple", "banana"]
1165 .iter()
1166 .map(|s| PathElementCS::new(Cow::Borrowed(*s)).unwrap())
1167 .collect();
1168 elems.sort();
1169 let names: Vec<_> = elems.iter().map(PathElementCS::normalized).collect();
1170 assert_eq!(names, &["apple", "banana", "cherry"]);
1171 }
1172
1173 #[test]
1174 fn path_element_ord_ci_sortable() {
1175 let mut elems: Vec<_> = ["Cherry", "apple", "BANANA"]
1176 .iter()
1177 .map(|s| PathElementCI::new(Cow::Borrowed(*s)).unwrap())
1178 .collect();
1179 elems.sort();
1180 let names: Vec<_> = elems.iter().map(PathElementCI::normalized).collect();
1181 assert_eq!(names, &["apple", "banana", "cherry"]);
1182 }
1183
1184 #[test]
1187 fn from_cs_into_dynamic() {
1188 let pe = PathElementCS::new("hello").unwrap();
1189 let dyn_pe: PathElement<'_> = pe.into();
1190 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1191 assert_eq!(dyn_pe.normalized(), "hello");
1192 }
1193
1194 #[test]
1195 fn from_ci_into_dynamic() {
1196 let pe = PathElementCI::new("Hello").unwrap();
1197 let dyn_pe: PathElement<'_> = pe.into();
1198 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1199 assert_eq!(dyn_pe.normalized(), "hello");
1200 }
1201
1202 #[test]
1203 fn try_from_dynamic_to_cs() {
1204 let pe = PathElement::new("hello", CaseSensitive).unwrap();
1205 let cs_pe: PathElementCS<'_> = pe.try_into().unwrap();
1206 assert_eq!(cs_pe.normalized(), "hello");
1207 }
1208
1209 #[test]
1210 fn try_from_dynamic_to_cs_wrong_variant() {
1211 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1212 let err: PathElementCI<'_> = PathElementCS::try_from(pe).unwrap_err();
1213 assert_eq!(err.original(), "Hello");
1214 assert_eq!(err.normalized(), "hello");
1215 assert_eq!(err.os_compatible(), "Hello");
1216 }
1217
1218 #[test]
1219 fn try_from_dynamic_to_ci() {
1220 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1221 let ci_pe: PathElementCI<'_> = pe.try_into().unwrap();
1222 assert_eq!(ci_pe.normalized(), "hello");
1223 }
1224
1225 #[test]
1228 fn dyn_new_cs() {
1229 let pe = PathElement::new_cs("Hello.txt").unwrap();
1230 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1231 assert_eq!(pe.normalized(), "Hello.txt");
1232 }
1233
1234 #[test]
1235 fn dyn_new_ci() {
1236 let pe = PathElement::new_ci("Hello.txt").unwrap();
1237 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1238 assert_eq!(pe.normalized(), "hello.txt");
1239 }
1240
1241 #[test]
1242 fn dyn_new_cs_matches_typed() {
1243 let dyn_pe = PathElement::new_cs("Hello.txt").unwrap();
1244 let cs_pe = PathElementCS::new("Hello.txt").unwrap();
1245 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1246 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1247 }
1248
1249 #[test]
1250 fn dyn_new_ci_matches_typed() {
1251 let dyn_pe = PathElement::new_ci("Hello.txt").unwrap();
1252 let ci_pe = PathElementCI::new("Hello.txt").unwrap();
1253 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1254 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1255 }
1256
1257 #[test]
1260 fn case_sensitivity_cs() {
1261 let pe = PathElementCS::new("hello").unwrap();
1262 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1263 }
1264
1265 #[test]
1266 fn case_sensitivity_ci() {
1267 let pe = PathElementCI::new("hello").unwrap();
1268 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1269 }
1270
1271 #[test]
1272 fn case_sensitivity_dyn() {
1273 let cs = PathElement::new("hello", CaseSensitive).unwrap();
1274 let ci = PathElement::new("hello", CaseInsensitive).unwrap();
1275 assert_eq!(cs.case_sensitivity(), CaseSensitivity::Sensitive);
1276 assert_eq!(ci.case_sensitivity(), CaseSensitivity::Insensitive);
1277 }
1278
1279 #[test]
1282 fn partial_ord_dyn_same_case_sensitivity() {
1283 let a = PathElement::new("apple", CaseSensitive).unwrap();
1284 let b = PathElement::new("banana", CaseSensitive).unwrap();
1285 assert!(a < b);
1286 }
1287
1288 #[test]
1289 fn partial_ord_dyn_none_on_mismatch() {
1290 let a = PathElement::new("hello", CaseSensitive).unwrap();
1291 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1292 assert_eq!(a.partial_cmp(&b), None);
1293 }
1294
1295 #[test]
1298 fn try_from_dynamic_to_ci_wrong_variant() {
1299 let pe = PathElement::new("Hello", CaseSensitive).unwrap();
1300 let err: PathElementCS<'_> = PathElementCI::try_from(pe).unwrap_err();
1301 assert_eq!(err.original(), "Hello");
1302 assert_eq!(err.normalized(), "Hello");
1303 assert_eq!(err.os_compatible(), "Hello");
1304 }
1305
1306 #[test]
1309 fn into_owned_preserves_cs_case_sensitivity() {
1310 let pe = PathElementCS::new("hello").unwrap();
1311 let owned = pe.into_owned();
1312 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Sensitive);
1313 }
1314
1315 #[test]
1316 fn into_owned_preserves_dyn_case_sensitivity() {
1317 let pe = PathElement::new("hello", CaseInsensitive).unwrap();
1318 let owned = pe.into_owned();
1319 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Insensitive);
1320 }
1321
1322 #[test]
1325 fn eq_cs_vs_dyn_same_case_sensitivity() {
1326 let cs = PathElementCS::new("hello").unwrap();
1327 let dyn_cs = PathElement::new_cs("hello").unwrap();
1328 assert_eq!(cs, dyn_cs);
1329 assert_eq!(dyn_cs, cs);
1330 }
1331
1332 #[test]
1333 fn eq_ci_vs_dyn_same_case_sensitivity() {
1334 let ci = PathElementCI::new("Hello").unwrap();
1335 let dyn_ci = PathElement::new_ci("hello").unwrap();
1336 assert_eq!(ci, dyn_ci);
1337 assert_eq!(dyn_ci, ci);
1338 }
1339
1340 #[test]
1341 #[should_panic(expected = "different case sensitivity")]
1342 fn eq_cs_vs_ci_panics() {
1343 let cs = PathElementCS::new("hello").unwrap();
1344 let ci = PathElementCI::new("hello").unwrap();
1345 let _ = cs == ci;
1346 }
1347
1348 #[test]
1349 #[should_panic(expected = "different case sensitivity")]
1350 fn eq_cs_vs_dyn_ci_panics() {
1351 let cs = PathElementCS::new("hello").unwrap();
1352 let dyn_ci = PathElement::new_ci("hello").unwrap();
1353 let _ = cs == dyn_ci;
1354 }
1355
1356 #[test]
1359 fn partial_ord_cs_vs_dyn_same_case_sensitivity() {
1360 let cs = PathElementCS::new("apple").unwrap();
1361 let dyn_cs = PathElement::new_cs("banana").unwrap();
1362 assert!(cs < dyn_cs);
1363 assert!(dyn_cs > cs);
1364 }
1365
1366 #[test]
1367 fn partial_ord_cs_vs_ci_none() {
1368 let cs = PathElementCS::new("hello").unwrap();
1369 let ci = PathElementCI::new("hello").unwrap();
1370 assert_eq!(cs.partial_cmp(&ci), None);
1371 assert_eq!(ci.partial_cmp(&cs), None);
1372 }
1373
1374 #[test]
1375 fn partial_ord_cs_vs_dyn_ci_none() {
1376 let cs = PathElementCS::new("hello").unwrap();
1377 let dyn_ci = PathElement::new_ci("hello").unwrap();
1378 assert_eq!(cs.partial_cmp(&dyn_ci), None);
1379 assert_eq!(dyn_ci.partial_cmp(&cs), None);
1380 }
1381
1382 #[cfg(feature = "std")]
1385 mod os_str_tests {
1386 use std::borrow::Cow;
1387 use std::ffi::{OsStr, OsString};
1388
1389 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
1390 use wasm_bindgen_test::wasm_bindgen_test as test;
1391
1392 use crate::case_sensitivity::{CaseInsensitive, CaseSensitivity};
1393 use crate::path_element::{PathElement, PathElementCI, PathElementCS};
1394
1395 #[test]
1396 fn from_os_str_borrowed_matches_new() {
1397 let input = OsStr::new("hello.txt");
1398 let from_os = PathElementCS::from_os_str(input).unwrap();
1399 let from_new = PathElementCS::new("hello.txt").unwrap();
1400 assert_eq!(from_os.original(), from_new.original());
1401 assert_eq!(from_os.normalized(), from_new.normalized());
1402 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1403 }
1404
1405 #[test]
1406 fn from_os_str_owned_matches_new() {
1407 let input = OsString::from("Hello.txt");
1408 let from_os = PathElementCI::from_os_str(input).unwrap();
1409 let from_new = PathElementCI::new("Hello.txt").unwrap();
1410 assert_eq!(from_os.original(), from_new.original());
1411 assert_eq!(from_os.normalized(), from_new.normalized());
1412 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1413 }
1414
1415 #[test]
1416 fn from_os_str_borrowed_preserves_borrow() {
1417 let input = OsStr::new("hello.txt");
1418 let pe = PathElementCS::from_os_str(input).unwrap();
1419 let orig = pe.into_original();
1420 assert!(matches!(orig, Cow::Borrowed(_)));
1421 }
1422
1423 #[cfg(unix)]
1424 #[test]
1425 fn from_os_str_invalid_utf8_borrowed() {
1426 use std::os::unix::ffi::OsStrExt;
1427 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]); let pe = PathElementCS::from_os_str(input).unwrap();
1429 assert_eq!(pe.original(), "hi\u{FFFD}");
1430 }
1431
1432 #[cfg(unix)]
1433 #[test]
1434 fn from_os_str_invalid_utf8_owned() {
1435 use std::os::unix::ffi::OsStrExt;
1436 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]).to_os_string();
1437 let pe = PathElementCS::from_os_str(input).unwrap();
1438 assert_eq!(pe.original(), "hi\u{FFFD}");
1439 }
1440
1441 #[cfg(windows)]
1442 #[test]
1443 fn from_os_str_invalid_utf8_borrowed() {
1444 use std::os::windows::ffi::OsStringExt;
1445 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1447 let pe = PathElementCS::from_os_str(input.as_os_str()).unwrap();
1448 assert_eq!(pe.original(), "h\u{FFFD}\u{FFFD}\u{FFFD}i");
1449 }
1450
1451 #[cfg(windows)]
1452 #[test]
1453 fn from_os_str_invalid_utf8_owned() {
1454 use std::os::windows::ffi::OsStringExt;
1455 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1456 let pe = PathElementCS::from_os_str(input).unwrap();
1457 assert_eq!(pe.original(), "h\u{FFFD}\u{FFFD}\u{FFFD}i");
1458 }
1459
1460 #[test]
1463 fn os_str_returns_os_compatible() {
1464 let pe = PathElementCI::new("H\tllo").unwrap();
1465 assert_eq!(pe.os_str(), OsStr::new("H\u{2409}llo"));
1466 }
1467
1468 #[test]
1469 fn into_os_str_returns_os_compatible() {
1470 let pe = PathElementCI::new("H\tllo").unwrap();
1471 let result = pe.into_os_str();
1472 assert_eq!(result, OsStr::new("H\u{2409}llo"));
1473 }
1474
1475 #[test]
1476 fn into_os_str_borrows_when_no_transformation() {
1477 let input = OsStr::new("hello.txt");
1478 let pe = PathElementCS::from_os_str(input).unwrap();
1479 let result = pe.into_os_str();
1480 assert!(matches!(result, Cow::Borrowed(_)));
1481 assert_eq!(result, OsStr::new("hello.txt"));
1482 }
1483
1484 #[test]
1485 fn into_os_str_ci_borrows_when_already_folded() {
1486 let input = OsStr::new("hello.txt");
1487 let pe = PathElementCI::from_os_str(input).unwrap();
1488 let result = pe.into_os_str();
1489 assert!(matches!(result, Cow::Borrowed(_)));
1490 assert_eq!(result, OsStr::new("hello.txt"));
1491 }
1492
1493 #[test]
1496 fn into_os_str_owned_when_nfc_transforms() {
1497 let input = OsStr::new("e\u{0301}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1499 let result = pe.into_os_str();
1500 #[cfg(target_vendor = "apple")]
1501 assert!(matches!(result, Cow::Borrowed(_)));
1502 #[cfg(not(target_vendor = "apple"))]
1503 assert!(matches!(result, Cow::Owned(_)));
1504 }
1505
1506 #[test]
1508 fn into_os_str_owned_when_nfd_transforms() {
1509 let input = OsStr::new("\u{00E9}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1511 let result = pe.into_os_str();
1512 #[cfg(target_vendor = "apple")]
1513 assert!(matches!(result, Cow::Owned(_)));
1514 #[cfg(not(target_vendor = "apple"))]
1515 assert!(matches!(result, Cow::Borrowed(_)));
1516 }
1517
1518 #[test]
1519 fn from_os_str_cs_matches_typed() {
1520 let input = OsStr::new("Hello.txt");
1521 let dyn_pe = PathElement::from_os_str_cs(input).unwrap();
1522 let cs_pe = PathElementCS::from_os_str(input).unwrap();
1523 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1524 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1525 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1526 }
1527
1528 #[test]
1529 fn from_os_str_ci_matches_typed() {
1530 let input = OsStr::new("Hello.txt");
1531 let dyn_pe = PathElement::from_os_str_ci(input).unwrap();
1532 let ci_pe = PathElementCI::from_os_str(input).unwrap();
1533 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1534 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1535 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1536 }
1537
1538 #[test]
1539 fn from_os_str_dynamic_case_sensitivity() {
1540 let input = OsStr::new("Hello.txt");
1541 let pe = PathElement::from_os_str(input, CaseInsensitive).unwrap();
1542 assert_eq!(pe.normalized(), "hello.txt");
1543 }
1544 }
1545
1546 mod from_bytes_tests {
1549 use alloc::borrow::Cow;
1550 use alloc::vec;
1551
1552 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
1553 use wasm_bindgen_test::wasm_bindgen_test as test;
1554
1555 use crate::case_sensitivity::{CaseInsensitive, CaseSensitive, CaseSensitivity};
1556 use crate::path_element::{PathElement, PathElementCI, PathElementCS};
1557
1558 #[test]
1559 fn from_bytes_cs_borrowed_matches_new() {
1560 let pe_bytes = PathElementCS::from_bytes(b"hello.txt" as &[u8]).unwrap();
1561 let pe_str = PathElementCS::new("hello.txt").unwrap();
1562 assert_eq!(pe_bytes.original(), pe_str.original());
1563 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1564 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1565 }
1566
1567 #[test]
1568 fn from_bytes_ci_borrowed_matches_new() {
1569 let pe_bytes = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1570 let pe_str = PathElementCI::new("Hello.txt").unwrap();
1571 assert_eq!(pe_bytes.original(), pe_str.original());
1572 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1573 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1574 }
1575
1576 #[test]
1577 fn from_bytes_owned_matches_new() {
1578 let pe_bytes = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1579 let pe_str = PathElementCS::new("hello.txt").unwrap();
1580 assert_eq!(pe_bytes.original(), pe_str.original());
1581 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1582 }
1583
1584 #[test]
1585 fn from_bytes_borrowed_preserves_borrow() {
1586 let input: &[u8] = b"hello.txt";
1587 let pe = PathElementCS::from_bytes(input).unwrap();
1588 let orig = pe.into_original();
1589 assert!(matches!(orig, Cow::Borrowed(_)));
1590 }
1591
1592 #[test]
1593 fn from_bytes_owned_is_owned() {
1594 let pe = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1595 assert!(pe.is_owned());
1596 }
1597
1598 #[test]
1599 fn from_bytes_invalid_utf8_borrowed_uses_replacement() {
1600 let input: &[u8] = &[0x68, 0x69, 0xFF]; let pe = PathElementCS::from_bytes(input).unwrap();
1602 assert_eq!(pe.original(), "hi\u{FFFD}");
1603 }
1604
1605 #[test]
1606 fn from_bytes_invalid_utf8_owned_uses_replacement() {
1607 let input = vec![0x68, 0x69, 0xFF];
1608 let pe = PathElementCS::from_bytes(input).unwrap();
1609 assert_eq!(pe.original(), "hi\u{FFFD}");
1610 }
1611
1612 #[test]
1613 fn from_bytes_dynamic_case_sensitivity() {
1614 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseInsensitive).unwrap();
1615 assert_eq!(pe.normalized(), "hello.txt");
1616 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1617 }
1618
1619 #[test]
1620 fn from_bytes_cs_matches_typed() {
1621 let input: &[u8] = b"Hello.txt";
1622 let dyn_pe = PathElement::from_bytes_cs(input).unwrap();
1623 let cs_pe = PathElementCS::from_bytes(input).unwrap();
1624 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1625 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1626 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1627 }
1628
1629 #[test]
1630 fn from_bytes_ci_matches_typed() {
1631 let input: &[u8] = b"Hello.txt";
1632 let dyn_pe = PathElement::from_bytes_ci(input).unwrap();
1633 let ci_pe = PathElementCI::from_bytes(input).unwrap();
1634 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1635 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1636 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1637 }
1638
1639 #[test]
1640 fn from_bytes_with_case_sensitivity_cs() {
1641 let pe = PathElementCS::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1642 assert_eq!(pe.normalized(), "Hello.txt");
1643 }
1644
1645 #[test]
1646 fn from_bytes_with_case_sensitivity_ci() {
1647 let pe = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1648 assert_eq!(pe.normalized(), "hello.txt");
1649 }
1650
1651 #[test]
1652 fn from_bytes_rejects_empty() {
1653 assert!(PathElementCS::from_bytes(b"" as &[u8]).is_err());
1654 }
1655
1656 #[test]
1657 fn from_bytes_rejects_dot() {
1658 assert!(PathElementCS::from_bytes(b"." as &[u8]).is_err());
1659 }
1660
1661 #[test]
1662 fn from_bytes_rejects_dotdot() {
1663 assert!(PathElementCS::from_bytes(b".." as &[u8]).is_err());
1664 }
1665
1666 #[test]
1667 fn from_bytes_rejects_slash() {
1668 assert!(PathElementCS::from_bytes(b"a/b" as &[u8]).is_err());
1669 }
1670
1671 #[test]
1672 fn from_bytes_rejects_null() {
1673 assert!(PathElementCS::from_bytes(b"\0" as &[u8]).is_err());
1674 }
1675
1676 #[test]
1677 fn from_bytes_dynamic_sensitive() {
1678 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseSensitive).unwrap();
1679 assert_eq!(pe.normalized(), "Hello.txt");
1680 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1681 }
1682
1683 #[test]
1686 fn from_bytes_overlong_null() {
1687 let input: &[u8] = &[0x61, 0xC0, 0x80, 0x62]; let pe = PathElementCS::from_bytes(input).unwrap();
1690 assert_eq!(pe.original(), "a\u{FFFD}\u{FFFD}b");
1691 }
1692
1693 #[test]
1694 fn from_bytes_surrogate_bytes_replaced() {
1695 let input: &[u8] = &[0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80];
1698 let pe = PathElementCS::from_bytes(input).unwrap();
1699 assert!(pe.original().contains('\u{FFFD}'));
1700 }
1701
1702 #[test]
1703 fn from_bytes_lone_high_surrogate_replaced() {
1704 let input: &[u8] = &[0x61, 0xED, 0xA0, 0x80, 0x62];
1705 let pe = PathElementCS::from_bytes(input).unwrap();
1706 assert_eq!(pe.original(), "a\u{FFFD}\u{FFFD}\u{FFFD}b");
1707 }
1708
1709 #[test]
1710 fn from_bytes_lone_low_surrogate_replaced() {
1711 let input: &[u8] = &[0x61, 0xED, 0xB0, 0x80, 0x62];
1712 let pe = PathElementCS::from_bytes(input).unwrap();
1713 assert_eq!(pe.original(), "a\u{FFFD}\u{FFFD}\u{FFFD}b");
1714 }
1715
1716 #[test]
1717 fn from_bytes_overlong_null_only() {
1718 let input: &[u8] = &[0xC0, 0x80];
1719 let pe = PathElementCS::from_bytes(input).unwrap();
1720 assert_eq!(pe.original(), "\u{FFFD}\u{FFFD}");
1721 }
1722
1723 #[test]
1724 fn from_bytes_invalid_byte_replaced() {
1725 let input: &[u8] = &[0x68, 0x69, 0xFF]; let pe = PathElementCS::from_bytes(input).unwrap();
1727 assert_eq!(pe.original(), "hi\u{FFFD}");
1728 }
1729 }
1730
1731 #[test]
1734 fn os_compatible_supplementary_unchanged() {
1735 let pe = PathElementCS::new("file_😀.txt").unwrap();
1736 assert_eq!(pe.os_compatible(), "file_😀.txt");
1737 }
1738
1739 #[test]
1740 fn os_compatible_supplementary_roundtrip() {
1741 let pe = PathElementCS::new("file_😀.txt").unwrap();
1742 let pe2 = PathElementCS::new(pe.os_compatible()).unwrap();
1743 assert_eq!(pe.normalized(), pe2.normalized());
1744 }
1745
1746 #[test]
1747 fn os_compatible_multiple_supplementary() {
1748 let pe = PathElementCS::new("𐀀_𝄞_😀").unwrap();
1749 assert_eq!(pe.os_compatible(), "𐀀_𝄞_😀");
1750 }
1751}