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::error::{Error, ErrorKind};
9use crate::normalize::{normalize_ci_from_normalized_cs, normalize_cs};
10use crate::os::os_compatible_from_normalized_cs;
11use crate::utils::SubstringOrOwned;
12
13pub type PathElementCS<'a> = PathElementGeneric<'a, CaseSensitive>;
23
24pub type PathElementCI<'a> = PathElementGeneric<'a, CaseInsensitive>;
34
35pub type PathElement<'a> = PathElementGeneric<'a, CaseSensitivity>;
47
48#[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>
94where
95 for<'s> CaseSensitivity: From<&'s S1> + From<&'s S2>,
96{
97 fn eq(&self, other: &PathElementGeneric<'a, S2>) -> bool {
98 assert_eq!(
99 CaseSensitivity::from(&self.case_sensitivity),
100 CaseSensitivity::from(&other.case_sensitivity),
101 "comparing PathElements with different case sensitivity"
102 );
103 self.normalized() == other.normalized()
104 }
105}
106
107impl<S> Eq for PathElementGeneric<'_, S> where for<'s> CaseSensitivity: From<&'s S> {}
109
110impl<'a, S1, S2> PartialOrd<PathElementGeneric<'a, S2>> for PathElementGeneric<'_, S1>
113where
114 for<'s> CaseSensitivity: From<&'s S1> + From<&'s S2>,
115{
116 fn partial_cmp(&self, other: &PathElementGeneric<'a, S2>) -> Option<core::cmp::Ordering> {
117 if CaseSensitivity::from(&self.case_sensitivity)
118 != CaseSensitivity::from(&other.case_sensitivity)
119 {
120 return None;
121 }
122 Some(self.normalized().cmp(other.normalized()))
123 }
124}
125
126impl<S> Ord for PathElementGeneric<'_, S>
134where
135 for<'s> CaseSensitivity: From<&'s S>,
136{
137 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
138 assert_eq!(
139 CaseSensitivity::from(&self.case_sensitivity),
140 CaseSensitivity::from(&other.case_sensitivity),
141 "comparing PathElements with different case sensitivity"
142 );
143 self.normalized().cmp(other.normalized())
144 }
145}
146
147impl core::hash::Hash for PathElementCS<'_> {
150 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
151 self.normalized().hash(state);
152 }
153}
154
155impl core::hash::Hash for PathElementCI<'_> {
158 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
159 self.normalized().hash(state);
160 }
161}
162
163impl<'a> PathElementCS<'a> {
164 pub fn new(original: impl Into<Cow<'a, str>>) -> Result<Self> {
177 Self::with_case_sensitivity(original, CaseSensitive)
178 }
179
180 pub fn from_bytes(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
197 Self::from_bytes_with_case_sensitivity(original, CaseSensitive)
198 }
199
200 #[cfg(feature = "std")]
215 pub fn from_os_str(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
216 Self::from_os_str_with_case_sensitivity(original, CaseSensitive)
217 }
218}
219
220impl<'a> PathElementCI<'a> {
221 pub fn new(original: impl Into<Cow<'a, str>>) -> Result<Self> {
234 Self::with_case_sensitivity(original, CaseInsensitive)
235 }
236
237 pub fn from_bytes(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
251 Self::from_bytes_with_case_sensitivity(original, CaseInsensitive)
252 }
253
254 #[cfg(feature = "std")]
261 pub fn from_os_str(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
262 Self::from_os_str_with_case_sensitivity(original, CaseInsensitive)
263 }
264}
265
266impl<'a> PathElementGeneric<'a, CaseSensitivity> {
267 pub fn new(
283 original: impl Into<Cow<'a, str>>,
284 case_sensitivity: impl Into<CaseSensitivity>,
285 ) -> Result<Self> {
286 Self::with_case_sensitivity(original, case_sensitivity)
287 }
288
289 pub fn from_bytes(
296 original: impl Into<Cow<'a, [u8]>>,
297 case_sensitivity: impl Into<CaseSensitivity>,
298 ) -> Result<Self> {
299 Self::from_bytes_with_case_sensitivity(original, case_sensitivity)
300 }
301
302 #[cfg(feature = "std")]
309 pub fn from_os_str(
310 original: impl Into<Cow<'a, OsStr>>,
311 case_sensitivity: impl Into<CaseSensitivity>,
312 ) -> Result<Self> {
313 Self::from_os_str_with_case_sensitivity(original, case_sensitivity)
314 }
315
316 pub fn new_cs(original: impl Into<Cow<'a, str>>) -> Result<Self> {
322 Self::with_case_sensitivity(original, CaseSensitive)
323 }
324
325 pub fn new_ci(original: impl Into<Cow<'a, str>>) -> Result<Self> {
331 Self::with_case_sensitivity(original, CaseInsensitive)
332 }
333
334 pub fn from_bytes_cs(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
341 Self::from_bytes_with_case_sensitivity(original, CaseSensitive)
342 }
343
344 pub fn from_bytes_ci(original: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
351 Self::from_bytes_with_case_sensitivity(original, CaseInsensitive)
352 }
353
354 #[cfg(feature = "std")]
361 pub fn from_os_str_cs(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
362 Self::from_os_str_with_case_sensitivity(original, CaseSensitive)
363 }
364
365 #[cfg(feature = "std")]
372 pub fn from_os_str_ci(original: impl Into<Cow<'a, OsStr>>) -> Result<Self> {
373 Self::from_os_str_with_case_sensitivity(original, CaseInsensitive)
374 }
375}
376
377impl<'a, S> PathElementGeneric<'a, S>
378where
379 for<'s> CaseSensitivity: From<&'s S>,
380{
381 pub fn from_bytes_with_case_sensitivity(
393 original: impl Into<Cow<'a, [u8]>>,
394 case_sensitivity: impl Into<S>,
395 ) -> Result<Self> {
396 let utf8_err =
397 |bytes: &[u8]| Error::new(ErrorKind::InvalidUtf8, String::from_utf8_lossy(bytes));
398 let cow_str: Cow<'a, str> = match original.into() {
399 Cow::Borrowed(b) => Cow::Borrowed(core::str::from_utf8(b).map_err(|_| utf8_err(b))?),
400 Cow::Owned(v) => Cow::Owned(String::from_utf8(v).map_err(|e| utf8_err(e.as_bytes()))?),
401 };
402 Self::with_case_sensitivity(cow_str, case_sensitivity)
403 }
404
405 #[cfg(feature = "std")]
413 pub fn from_os_str_with_case_sensitivity(
414 original: impl Into<Cow<'a, OsStr>>,
415 case_sensitivity: impl Into<S>,
416 ) -> Result<Self> {
417 let cow_bytes: Cow<'a, [u8]> = match original.into() {
418 Cow::Borrowed(os) => Cow::Borrowed(os.as_encoded_bytes()),
419 Cow::Owned(os) => Cow::Owned(os.into_encoded_bytes()),
420 };
421 Self::from_bytes_with_case_sensitivity(cow_bytes, case_sensitivity)
422 }
423
424 pub fn with_case_sensitivity(
434 original: impl Into<Cow<'a, str>>,
435 case_sensitivity: impl Into<S>,
436 ) -> Result<Self> {
437 let original = original.into();
438 let case_sensitivity = case_sensitivity.into();
439 let cs = CaseSensitivity::from(&case_sensitivity);
440
441 let cs_normalized = match normalize_cs(&original) {
442 Ok(v) => v,
443 Err(kind) => return Err(Error::new(kind, original)),
444 };
445 let normalized = match cs {
446 CaseSensitivity::Sensitive => SubstringOrOwned::new(&cs_normalized, &original),
447 CaseSensitivity::Insensitive => {
448 SubstringOrOwned::new(&normalize_ci_from_normalized_cs(&cs_normalized), &original)
449 }
450 };
451 let os_str = match os_compatible_from_normalized_cs(&cs_normalized) {
452 Ok(v) => v,
453 Err(kind) => return Err(Error::new(kind, original)),
454 };
455 let os_compatible = SubstringOrOwned::new(&os_str, &original);
456 Ok(Self {
457 original,
458 normalized,
459 os_compatible,
460 case_sensitivity,
461 })
462 }
463
464 pub fn case_sensitivity(&self) -> CaseSensitivity {
473 CaseSensitivity::from(&self.case_sensitivity)
474 }
475}
476
477impl<'a, S> PathElementGeneric<'a, S> {
478 pub fn original(&self) -> &str {
488 &self.original
489 }
490
491 pub fn into_original(self) -> Cow<'a, str> {
500 self.original
501 }
502
503 pub fn is_normalized(&self) -> bool {
514 self.normalized.is_identity(&self.original)
515 }
516
517 pub fn normalized(&self) -> &str {
533 self.normalized.as_ref(&self.original)
534 }
535
536 pub fn into_normalized(self) -> Cow<'a, str> {
549 self.normalized.into_cow(self.original)
550 }
551
552 pub fn is_os_compatible(&self) -> bool {
561 self.os_compatible.is_identity(&self.original)
562 }
563
564 pub fn os_compatible(&self) -> &str {
573 self.os_compatible.as_ref(&self.original)
574 }
575
576 pub fn into_os_compatible(self) -> Cow<'a, str> {
586 self.os_compatible.into_cow(self.original)
587 }
588
589 #[cfg(feature = "std")]
599 pub fn os_str(&self) -> &OsStr {
600 OsStr::new(self.os_compatible())
601 }
602
603 #[cfg(feature = "std")]
614 pub fn into_os_str(self) -> Cow<'a, OsStr> {
615 match self.into_os_compatible() {
616 Cow::Borrowed(s) => Cow::Borrowed(OsStr::new(s)),
617 Cow::Owned(s) => Cow::Owned(OsString::from(s)),
618 }
619 }
620
621 pub fn is_borrowed(&self) -> bool {
634 matches!(self.original, Cow::Borrowed(_))
635 }
636
637 pub fn is_owned(&self) -> bool {
650 matches!(self.original, Cow::Owned(_))
651 }
652
653 pub fn into_owned(self) -> PathElementGeneric<'static, S> {
665 PathElementGeneric {
666 original: Cow::Owned(self.original.into_owned()),
667 normalized: self.normalized,
668 os_compatible: self.os_compatible,
669 case_sensitivity: self.case_sensitivity,
670 }
671 }
672}
673
674impl<'a> From<PathElementCS<'a>> for PathElement<'a> {
678 fn from(pe: PathElementCS<'a>) -> Self {
679 Self {
680 original: pe.original,
681 normalized: pe.normalized,
682 os_compatible: pe.os_compatible,
683 case_sensitivity: CaseSensitivity::Sensitive,
684 }
685 }
686}
687
688impl<'a> From<PathElementCI<'a>> for PathElement<'a> {
690 fn from(pe: PathElementCI<'a>) -> Self {
691 Self {
692 original: pe.original,
693 normalized: pe.normalized,
694 os_compatible: pe.os_compatible,
695 case_sensitivity: CaseSensitivity::Insensitive,
696 }
697 }
698}
699
700impl<'a> TryFrom<PathElement<'a>> for PathElementCS<'a> {
705 type Error = PathElementCI<'a>;
706
707 fn try_from(pe: PathElement<'a>) -> core::result::Result<Self, Self::Error> {
708 if pe.case_sensitivity == CaseSensitivity::Sensitive {
709 Ok(Self {
710 original: pe.original,
711 normalized: pe.normalized,
712 os_compatible: pe.os_compatible,
713 case_sensitivity: CaseSensitive,
714 })
715 } else {
716 Err(Self::Error {
717 original: pe.original,
718 normalized: pe.normalized,
719 os_compatible: pe.os_compatible,
720 case_sensitivity: CaseInsensitive,
721 })
722 }
723 }
724}
725
726impl<'a> TryFrom<PathElement<'a>> for PathElementCI<'a> {
731 type Error = PathElementCS<'a>;
732
733 fn try_from(pe: PathElement<'a>) -> core::result::Result<Self, Self::Error> {
734 if pe.case_sensitivity == CaseSensitivity::Insensitive {
735 Ok(Self {
736 original: pe.original,
737 normalized: pe.normalized,
738 os_compatible: pe.os_compatible,
739 case_sensitivity: CaseInsensitive,
740 })
741 } else {
742 Err(Self::Error {
743 original: pe.original,
744 normalized: pe.normalized,
745 os_compatible: pe.os_compatible,
746 case_sensitivity: CaseSensitive,
747 })
748 }
749 }
750}
751
752#[cfg(test)]
753mod tests {
754 use alloc::borrow::Cow;
755 use alloc::string::ToString;
756 use alloc::vec;
757 use alloc::vec::Vec;
758
759 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
760 use wasm_bindgen_test::wasm_bindgen_test as test;
761
762 use super::{PathElement, PathElementCI, PathElementCS};
763 use crate::ErrorKind;
764 use crate::case_sensitivity::{CaseInsensitive, CaseSensitive, CaseSensitivity};
765 use crate::normalize::{normalize_ci_from_normalized_cs, normalize_cs};
766 use crate::os::os_compatible_from_normalized_cs;
767
768 #[test]
771 fn path_element_cs_matches_freestanding() {
772 let input = "Hello";
773 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
774 assert_eq!(pe.original(), input);
775 assert_eq!(pe.normalized(), normalize_cs(input).unwrap().as_ref());
776 assert_eq!(
777 pe.os_compatible(),
778 os_compatible_from_normalized_cs(&normalize_cs(input).unwrap())
779 .unwrap()
780 .as_ref()
781 );
782 }
783
784 #[test]
785 fn path_element_ci_matches_freestanding() {
786 let input = "Hello";
787 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
788 assert_eq!(pe.original(), "Hello");
789 assert_eq!(pe.normalized(), "hello");
790 }
791
792 #[test]
795 fn path_element_cs_os_compatible_platform_dependent() {
796 let input = "nul.e\u{0301}";
797 let pe = PathElementCS::new(input).unwrap();
798 assert_eq!(pe.original(), "nul.e\u{0301}");
799 assert_eq!(pe.normalized(), "nul.\u{00E9}");
800 #[cfg(target_os = "windows")]
801 assert_eq!(pe.os_compatible(), "\u{FF4E}ul.\u{00E9}");
802 #[cfg(target_vendor = "apple")]
803 assert_eq!(pe.os_compatible(), "nul.e\u{0301}");
804 #[cfg(not(any(target_os = "windows", target_vendor = "apple")))]
805 assert_eq!(pe.os_compatible(), "nul.\u{00E9}");
806 }
807
808 #[test]
809 fn path_element_cs_nfc_matches_freestanding() {
810 let input = "e\u{0301}.txt";
811 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
812 assert_eq!(pe.normalized(), normalize_cs(input).unwrap().as_ref());
813 }
814
815 #[test]
816 fn path_element_ci_casefold_matches_freestanding() {
817 let input = "Hello.txt";
818 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
819 let cs = normalize_cs(input).unwrap();
820 assert_eq!(
821 pe.normalized(),
822 normalize_ci_from_normalized_cs(&cs).as_ref()
823 );
824 }
825
826 #[test]
827 fn path_element_cs_normalized_borrows_from_original() {
828 let input = "hello.txt";
829 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
830 assert_eq!(pe.normalized(), "hello.txt");
831 assert!(core::ptr::eq(pe.normalized().as_ptr(), input.as_ptr()));
832 }
833
834 #[test]
835 fn path_element_cs_into_normalized_borrows() {
836 let input = "hello.txt";
837 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
838 let norm = pe.into_normalized();
839 assert!(matches!(norm, Cow::Borrowed(_)));
840 assert_eq!(norm, "hello.txt");
841 }
842
843 #[test]
844 fn path_element_cs_into_os_compatible_borrows() {
845 let input = "hello.txt";
846 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
847 let pres = pe.into_os_compatible();
848 assert!(matches!(pres, Cow::Borrowed(_)));
849 assert_eq!(pres.as_ref(), "hello.txt");
850 }
851
852 #[test]
853 fn path_element_ci_normalized_borrows_when_already_folded() {
854 let input = "hello.txt";
855 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
856 assert!(core::ptr::eq(pe.normalized().as_ptr(), input.as_ptr()));
857 }
858
859 #[test]
860 fn path_element_ci_into_normalized_borrows_when_already_folded() {
861 let input = "hello.txt";
862 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
863 let norm = pe.into_normalized();
864 assert!(matches!(norm, Cow::Borrowed(_)));
865 assert_eq!(norm, "hello.txt");
866 }
867
868 #[test]
869 fn path_element_ci_into_os_compatible_borrows_when_already_folded() {
870 let input = "hello.txt";
871 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
872 let pres = pe.into_os_compatible();
873 assert!(matches!(pres, Cow::Borrowed(_)));
874 assert_eq!(pres.as_ref(), "hello.txt");
875 }
876
877 #[test]
878 fn path_element_cs_trimmed_borrows_suffix() {
879 let input = " hello.txt";
880 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
881 assert_eq!(pe.normalized(), "hello.txt");
882 assert!(core::ptr::eq(pe.normalized().as_ptr(), input[3..].as_ptr()));
883 }
884
885 #[test]
886 fn path_element_into_original_returns_original() {
887 let input = " Hello.txt ";
888 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
889 let orig = pe.into_original();
890 assert!(matches!(orig, Cow::Borrowed(_)));
891 assert_eq!(orig, input);
892 }
893
894 #[test]
895 fn path_element_is_normalized_when_unchanged() {
896 let pe = PathElementCS::new("hello.txt").unwrap();
897 assert!(pe.is_normalized());
898 }
899
900 #[test]
901 fn path_element_is_not_normalized_when_trimmed() {
902 let pe = PathElementCS::new(" hello.txt ").unwrap();
903 assert!(!pe.is_normalized());
904 }
905
906 #[test]
907 fn path_element_is_not_normalized_when_trailing_whitespace_trimmed() {
908 let pe = PathElementCS::new("hello.txt ").unwrap();
910 assert!(!pe.is_normalized());
911 assert_eq!(pe.normalized(), "hello.txt");
912 }
913
914 #[test]
915 fn path_element_is_not_normalized_when_casefolded() {
916 let pe = PathElementCI::new("Hello.txt").unwrap();
917 assert!(!pe.is_normalized());
918 }
919
920 #[test]
921 fn path_element_ci_is_normalized_when_already_folded() {
922 let pe = PathElementCI::new("hello.txt").unwrap();
923 assert!(pe.is_normalized());
924 }
925
926 #[test]
927 fn path_element_is_os_compatible_ascii() {
928 let pe = PathElementCS::new("hello.txt").unwrap();
929 assert!(pe.is_os_compatible());
930 }
931
932 #[test]
933 fn path_element_is_not_os_compatible_trailing_whitespace_ci() {
934 let pe = PathElementCI::new("hello.txt ").unwrap();
937 assert!(!pe.is_os_compatible());
938 }
939
940 #[test]
941 fn path_element_is_not_os_compatible_trailing_whitespace_cs() {
942 let pe = PathElementCS::new("hello.txt ").unwrap();
945 assert!(!pe.is_os_compatible());
946 }
947
948 #[test]
949 fn path_element_is_os_compatible_nfc_input() {
950 let pe = PathElementCS::new("\u{00E9}.txt").unwrap();
954 #[cfg(target_vendor = "apple")]
955 assert!(!pe.is_os_compatible());
956 #[cfg(not(target_vendor = "apple"))]
957 assert!(pe.is_os_compatible());
958 }
959
960 #[test]
961 fn path_element_is_os_compatible_nfd_input() {
962 let pe = PathElementCS::new("e\u{0301}.txt").unwrap();
965 #[cfg(target_vendor = "apple")]
966 assert!(pe.is_os_compatible());
967 #[cfg(not(target_vendor = "apple"))]
968 assert!(!pe.is_os_compatible());
969 }
970
971 #[test]
972 fn path_element_is_os_compatible_nfd_input_ci() {
973 let pe = PathElementCI::new("e\u{0301}.txt").unwrap();
978 #[cfg(target_vendor = "apple")]
979 assert!(pe.is_os_compatible());
980 #[cfg(not(target_vendor = "apple"))]
981 assert!(!pe.is_os_compatible());
982 }
983
984 #[test]
985 fn path_element_is_os_compatible_nfc_input_ci() {
986 let pe = PathElementCI::new("\u{00E9}.txt").unwrap();
989 #[cfg(target_vendor = "apple")]
990 assert!(!pe.is_os_compatible());
991 #[cfg(not(target_vendor = "apple"))]
992 assert!(pe.is_os_compatible());
993 }
994
995 #[test]
996 fn path_element_is_not_os_compatible_reserved_on_windows() {
997 let pe = PathElementCS::new("nul.txt").unwrap();
998 #[cfg(target_os = "windows")]
999 assert!(!pe.is_os_compatible());
1000 #[cfg(not(target_os = "windows"))]
1001 assert!(pe.is_os_compatible());
1002 }
1003
1004 #[test]
1005 fn path_element_borrowed_is_borrowed() {
1006 let pe = PathElementCS::new(Cow::Borrowed("hello.txt")).unwrap();
1007 assert!(pe.is_borrowed());
1008 assert!(!pe.is_owned());
1009 }
1010
1011 #[test]
1012 fn path_element_owned_is_owned() {
1013 let pe = PathElementCS::new(Cow::Owned("hello.txt".to_string())).unwrap();
1014 assert!(pe.is_owned());
1015 assert!(!pe.is_borrowed());
1016 }
1017
1018 #[test]
1019 fn path_element_into_owned_is_owned() {
1020 let pe = PathElementCS::new(Cow::Borrowed("hello.txt")).unwrap();
1021 let owned = pe.into_owned();
1022 assert!(owned.is_owned());
1023 }
1024
1025 #[test]
1026 fn path_element_into_owned_preserves_values() {
1027 let input = "Hello";
1028 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
1029 let owned = pe.into_owned();
1030 assert_eq!(owned.original(), "Hello");
1031 assert_eq!(owned.normalized(), "hello");
1032 }
1033
1034 #[test]
1035 fn new_rejects_empty() {
1036 assert_eq!(PathElementCS::new("").unwrap_err().kind, ErrorKind::Empty);
1037 }
1038
1039 #[test]
1040 fn new_rejects_dot() {
1041 assert_eq!(
1042 PathElementCS::new(".").unwrap_err().kind,
1043 ErrorKind::CurrentDirectoryMarker
1044 );
1045 }
1046
1047 #[test]
1048 fn new_rejects_dotdot() {
1049 assert_eq!(
1050 PathElementCS::new("..").unwrap_err().kind,
1051 ErrorKind::ParentDirectoryMarker
1052 );
1053 }
1054
1055 #[test]
1056 fn new_rejects_slash() {
1057 assert_eq!(
1058 PathElementCS::new("a/b").unwrap_err().kind,
1059 ErrorKind::ContainsForwardSlash
1060 );
1061 }
1062
1063 #[test]
1064 fn new_rejects_null() {
1065 assert_eq!(
1066 PathElementCS::new("\0").unwrap_err().kind,
1067 ErrorKind::ContainsNullByte
1068 );
1069 }
1070
1071 #[test]
1072 fn new_rejects_unassigned() {
1073 assert_eq!(
1074 PathElementCS::new("\u{0378}").unwrap_err().kind,
1075 ErrorKind::ContainsUnassignedChar
1076 );
1077 }
1078
1079 #[test]
1082 fn path_element_eq_same_cs() {
1083 let a = PathElementCS::new("hello.txt").unwrap();
1084 let b = PathElementCS::new("hello.txt").unwrap();
1085 assert_eq!(a, b);
1086 }
1087
1088 #[test]
1089 fn path_element_eq_different_original_same_normalized_cs() {
1090 let a = PathElementCS::new(" hello.txt ").unwrap();
1091 let b = PathElementCS::new("hello.txt").unwrap();
1092 assert_ne!(a.original(), b.original());
1093 assert_eq!(a, b);
1094 }
1095
1096 #[test]
1097 fn path_element_ne_different_case_cs() {
1098 let a = PathElementCS::new("Hello.txt").unwrap();
1099 let b = PathElementCS::new("hello.txt").unwrap();
1100 assert_ne!(a, b);
1101 }
1102
1103 #[test]
1104 fn path_element_eq_different_case_ci() {
1105 let a = PathElementCI::new("Hello.txt").unwrap();
1106 let b = PathElementCI::new("hello.txt").unwrap();
1107 assert_eq!(a, b);
1108 }
1109
1110 #[test]
1111 fn path_element_eq_nfc_nfd_cs() {
1112 let a = PathElementCS::new("\u{00E9}.txt").unwrap();
1113 let b = PathElementCS::new("e\u{0301}.txt").unwrap();
1114 assert_eq!(a, b);
1115 }
1116
1117 #[test]
1118 fn path_element_eq_cross_lifetime() {
1119 let owned = PathElementCS::new("hello.txt").unwrap().into_owned();
1120 let input = "hello.txt";
1121 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1122 assert_eq!(owned, borrowed);
1123 assert_eq!(borrowed, owned);
1124 }
1125
1126 #[test]
1127 #[should_panic(expected = "different case sensitivity")]
1128 fn path_element_eq_panics_on_mixed_dynamic_case_sensitivity() {
1129 let a = PathElement::new("hello", CaseSensitive).unwrap();
1130 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1131 let _ = a == b;
1132 }
1133
1134 #[test]
1137 fn path_element_ord_alphabetical_cs() {
1138 let a = PathElementCS::new("apple").unwrap();
1139 let b = PathElementCS::new("banana").unwrap();
1140 assert!(a < b);
1141 assert!(b > a);
1142 }
1143
1144 #[test]
1145 fn path_element_ord_equal_cs() {
1146 let a = PathElementCS::new("hello").unwrap();
1147 let b = PathElementCS::new("hello").unwrap();
1148 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1149 }
1150
1151 #[test]
1152 fn path_element_ord_case_ci() {
1153 let a = PathElementCI::new("Apple").unwrap();
1154 let b = PathElementCI::new("apple").unwrap();
1155 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1156 }
1157
1158 #[test]
1159 fn path_element_partial_ord_cross_lifetime() {
1160 let owned = PathElementCS::new("apple").unwrap().into_owned();
1161 let input = "banana";
1162 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1163 assert!(owned < borrowed);
1164 }
1165
1166 #[test]
1167 fn path_element_partial_ord_none_on_mixed_dynamic_case_sensitivity() {
1168 let a = PathElement::new("hello", CaseSensitive).unwrap();
1169 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1170 assert_eq!(a.partial_cmp(&b), None);
1171 }
1172
1173 #[test]
1174 fn path_element_ord_sortable() {
1175 let mut elems: Vec<_> = ["cherry", "apple", "banana"]
1176 .iter()
1177 .map(|s| PathElementCS::new(Cow::Borrowed(*s)).unwrap())
1178 .collect();
1179 elems.sort();
1180 let names: Vec<_> = elems.iter().map(PathElementCS::normalized).collect();
1181 assert_eq!(names, &["apple", "banana", "cherry"]);
1182 }
1183
1184 #[test]
1185 fn path_element_ord_ci_sortable() {
1186 let mut elems: Vec<_> = ["Cherry", "apple", "BANANA"]
1187 .iter()
1188 .map(|s| PathElementCI::new(Cow::Borrowed(*s)).unwrap())
1189 .collect();
1190 elems.sort();
1191 let names: Vec<_> = elems.iter().map(PathElementCI::normalized).collect();
1192 assert_eq!(names, &["apple", "banana", "cherry"]);
1193 }
1194
1195 #[test]
1198 fn from_cs_into_dynamic() {
1199 let pe = PathElementCS::new("hello").unwrap();
1200 let dyn_pe: PathElement<'_> = pe.into();
1201 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1202 assert_eq!(dyn_pe.normalized(), "hello");
1203 }
1204
1205 #[test]
1206 fn from_ci_into_dynamic() {
1207 let pe = PathElementCI::new("Hello").unwrap();
1208 let dyn_pe: PathElement<'_> = pe.into();
1209 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1210 assert_eq!(dyn_pe.normalized(), "hello");
1211 }
1212
1213 #[test]
1214 fn try_from_dynamic_to_cs() {
1215 let pe = PathElement::new("hello", CaseSensitive).unwrap();
1216 let cs_pe: PathElementCS<'_> = pe.try_into().unwrap();
1217 assert_eq!(cs_pe.normalized(), "hello");
1218 }
1219
1220 #[test]
1221 fn try_from_dynamic_to_cs_wrong_variant() {
1222 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1223 let err: PathElementCI<'_> = PathElementCS::try_from(pe).unwrap_err();
1224 assert_eq!(err.original, "Hello");
1225 assert_eq!(err.normalized(), "hello");
1226 assert_eq!(err.os_compatible(), "Hello");
1227 }
1228
1229 #[test]
1230 fn try_from_dynamic_to_ci() {
1231 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1232 let ci_pe: PathElementCI<'_> = pe.try_into().unwrap();
1233 assert_eq!(ci_pe.normalized(), "hello");
1234 }
1235
1236 #[test]
1239 fn dyn_new_cs() {
1240 let pe = PathElement::new_cs("Hello.txt").unwrap();
1241 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1242 assert_eq!(pe.normalized(), "Hello.txt");
1243 }
1244
1245 #[test]
1246 fn dyn_new_ci() {
1247 let pe = PathElement::new_ci("Hello.txt").unwrap();
1248 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1249 assert_eq!(pe.normalized(), "hello.txt");
1250 }
1251
1252 #[test]
1253 fn dyn_new_cs_matches_typed() {
1254 let dyn_pe = PathElement::new_cs("Hello.txt").unwrap();
1255 let cs_pe = PathElementCS::new("Hello.txt").unwrap();
1256 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1257 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1258 }
1259
1260 #[test]
1261 fn dyn_new_ci_matches_typed() {
1262 let dyn_pe = PathElement::new_ci("Hello.txt").unwrap();
1263 let ci_pe = PathElementCI::new("Hello.txt").unwrap();
1264 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1265 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1266 }
1267
1268 #[test]
1271 fn case_sensitivity_cs() {
1272 let pe = PathElementCS::new("hello").unwrap();
1273 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1274 }
1275
1276 #[test]
1277 fn case_sensitivity_ci() {
1278 let pe = PathElementCI::new("hello").unwrap();
1279 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1280 }
1281
1282 #[test]
1283 fn case_sensitivity_dyn() {
1284 let cs = PathElement::new("hello", CaseSensitive).unwrap();
1285 let ci = PathElement::new("hello", CaseInsensitive).unwrap();
1286 assert_eq!(cs.case_sensitivity(), CaseSensitivity::Sensitive);
1287 assert_eq!(ci.case_sensitivity(), CaseSensitivity::Insensitive);
1288 }
1289
1290 #[test]
1293 fn partial_ord_dyn_same_case_sensitivity() {
1294 let a = PathElement::new("apple", CaseSensitive).unwrap();
1295 let b = PathElement::new("banana", CaseSensitive).unwrap();
1296 assert!(a < b);
1297 }
1298
1299 #[test]
1300 fn partial_ord_dyn_none_on_mismatch() {
1301 let a = PathElement::new("hello", CaseSensitive).unwrap();
1302 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1303 assert_eq!(a.partial_cmp(&b), None);
1304 }
1305
1306 #[test]
1309 fn try_from_dynamic_to_ci_wrong_variant() {
1310 let pe = PathElement::new("Hello", CaseSensitive).unwrap();
1311 let err: PathElementCS<'_> = PathElementCI::try_from(pe).unwrap_err();
1312 assert_eq!(err.original, "Hello");
1313 assert_eq!(err.normalized(), "Hello");
1314 assert_eq!(err.os_compatible(), "Hello");
1315 }
1316
1317 #[test]
1320 fn into_owned_preserves_cs_case_sensitivity() {
1321 let pe = PathElementCS::new("hello").unwrap();
1322 let owned = pe.into_owned();
1323 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Sensitive);
1324 }
1325
1326 #[test]
1327 fn into_owned_preserves_dyn_case_sensitivity() {
1328 let pe = PathElement::new("hello", CaseInsensitive).unwrap();
1329 let owned = pe.into_owned();
1330 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Insensitive);
1331 }
1332
1333 #[test]
1336 fn eq_cs_vs_dyn_same_case_sensitivity() {
1337 let cs = PathElementCS::new("hello").unwrap();
1338 let dyn_cs = PathElement::new_cs("hello").unwrap();
1339 assert_eq!(cs, dyn_cs);
1340 assert_eq!(dyn_cs, cs);
1341 }
1342
1343 #[test]
1344 fn eq_ci_vs_dyn_same_case_sensitivity() {
1345 let ci = PathElementCI::new("Hello").unwrap();
1346 let dyn_ci = PathElement::new_ci("hello").unwrap();
1347 assert_eq!(ci, dyn_ci);
1348 assert_eq!(dyn_ci, ci);
1349 }
1350
1351 #[test]
1352 #[should_panic(expected = "different case sensitivity")]
1353 fn eq_cs_vs_ci_panics() {
1354 let cs = PathElementCS::new("hello").unwrap();
1355 let ci = PathElementCI::new("hello").unwrap();
1356 let _ = cs == ci;
1357 }
1358
1359 #[test]
1360 #[should_panic(expected = "different case sensitivity")]
1361 fn eq_cs_vs_dyn_ci_panics() {
1362 let cs = PathElementCS::new("hello").unwrap();
1363 let dyn_ci = PathElement::new_ci("hello").unwrap();
1364 let _ = cs == dyn_ci;
1365 }
1366
1367 #[test]
1370 fn partial_ord_cs_vs_dyn_same_case_sensitivity() {
1371 let cs = PathElementCS::new("apple").unwrap();
1372 let dyn_cs = PathElement::new_cs("banana").unwrap();
1373 assert!(cs < dyn_cs);
1374 assert!(dyn_cs > cs);
1375 }
1376
1377 #[test]
1378 fn partial_ord_cs_vs_ci_none() {
1379 let cs = PathElementCS::new("hello").unwrap();
1380 let ci = PathElementCI::new("hello").unwrap();
1381 assert_eq!(cs.partial_cmp(&ci), None);
1382 assert_eq!(ci.partial_cmp(&cs), None);
1383 }
1384
1385 #[test]
1386 fn partial_ord_cs_vs_dyn_ci_none() {
1387 let cs = PathElementCS::new("hello").unwrap();
1388 let dyn_ci = PathElement::new_ci("hello").unwrap();
1389 assert_eq!(cs.partial_cmp(&dyn_ci), None);
1390 assert_eq!(dyn_ci.partial_cmp(&cs), None);
1391 }
1392
1393 #[test]
1396 fn from_bytes_cs_borrowed_matches_new() {
1397 let pe_bytes = PathElementCS::from_bytes(b"hello.txt" as &[u8]).unwrap();
1398 let pe_str = PathElementCS::new("hello.txt").unwrap();
1399 assert_eq!(pe_bytes.original(), pe_str.original());
1400 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1401 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1402 }
1403
1404 #[test]
1405 fn from_bytes_ci_borrowed_matches_new() {
1406 let pe_bytes = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1407 let pe_str = PathElementCI::new("Hello.txt").unwrap();
1408 assert_eq!(pe_bytes.original(), pe_str.original());
1409 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1410 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1411 }
1412
1413 #[test]
1414 fn from_bytes_owned_matches_new() {
1415 let pe_bytes = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1416 let pe_str = PathElementCS::new("hello.txt").unwrap();
1417 assert_eq!(pe_bytes.original(), pe_str.original());
1418 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1419 }
1420
1421 #[test]
1422 fn from_bytes_borrowed_preserves_borrow() {
1423 let input: &[u8] = b"hello.txt";
1424 let pe = PathElementCS::from_bytes(input).unwrap();
1425 let orig = pe.into_original();
1426 assert!(matches!(orig, Cow::Borrowed(_)));
1427 }
1428
1429 #[test]
1430 fn from_bytes_owned_is_owned() {
1431 let pe = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1432 assert!(pe.is_owned());
1433 }
1434
1435 #[test]
1436 fn from_bytes_invalid_utf8_borrowed_rejected() {
1437 let input: &[u8] = &[0x68, 0x69, 0xFF]; let err = PathElementCS::from_bytes(input).unwrap_err();
1439 assert_eq!(err.kind, ErrorKind::InvalidUtf8);
1440 }
1441
1442 #[test]
1443 fn from_bytes_invalid_utf8_owned_rejected() {
1444 let input = vec![0x68, 0x69, 0xFF];
1445 let err = PathElementCS::from_bytes(input).unwrap_err();
1446 assert_eq!(err.kind, ErrorKind::InvalidUtf8);
1447 }
1448
1449 #[test]
1450 fn from_bytes_dynamic_case_sensitivity() {
1451 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseInsensitive).unwrap();
1452 assert_eq!(pe.normalized(), "hello.txt");
1453 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1454 }
1455
1456 #[test]
1457 fn from_bytes_cs_matches_typed() {
1458 let input: &[u8] = b"Hello.txt";
1459 let dyn_pe = PathElement::from_bytes_cs(input).unwrap();
1460 let cs_pe = PathElementCS::from_bytes(input).unwrap();
1461 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1462 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1463 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1464 }
1465
1466 #[test]
1467 fn from_bytes_ci_matches_typed() {
1468 let input: &[u8] = b"Hello.txt";
1469 let dyn_pe = PathElement::from_bytes_ci(input).unwrap();
1470 let ci_pe = PathElementCI::from_bytes(input).unwrap();
1471 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1472 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1473 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1474 }
1475
1476 #[test]
1477 fn from_bytes_with_case_sensitivity_cs() {
1478 let pe = PathElementCS::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1479 assert_eq!(pe.normalized(), "Hello.txt");
1480 }
1481
1482 #[test]
1483 fn from_bytes_with_case_sensitivity_ci() {
1484 let pe = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1485 assert_eq!(pe.normalized(), "hello.txt");
1486 }
1487
1488 #[test]
1489 fn from_bytes_rejects_empty() {
1490 assert_eq!(
1491 PathElementCS::from_bytes(b"" as &[u8]).unwrap_err().kind,
1492 ErrorKind::Empty
1493 );
1494 }
1495
1496 #[test]
1497 fn from_bytes_rejects_dot() {
1498 assert_eq!(
1499 PathElementCS::from_bytes(b"." as &[u8]).unwrap_err().kind,
1500 ErrorKind::CurrentDirectoryMarker
1501 );
1502 }
1503
1504 #[test]
1505 fn from_bytes_rejects_dotdot() {
1506 assert_eq!(
1507 PathElementCS::from_bytes(b".." as &[u8]).unwrap_err().kind,
1508 ErrorKind::ParentDirectoryMarker
1509 );
1510 }
1511
1512 #[test]
1513 fn from_bytes_rejects_slash() {
1514 assert_eq!(
1515 PathElementCS::from_bytes(b"a/b" as &[u8]).unwrap_err().kind,
1516 ErrorKind::ContainsForwardSlash
1517 );
1518 }
1519
1520 #[test]
1521 fn from_bytes_rejects_null() {
1522 assert_eq!(
1523 PathElementCS::from_bytes(b"\0" as &[u8]).unwrap_err().kind,
1524 ErrorKind::ContainsNullByte
1525 );
1526 }
1527
1528 #[test]
1529 fn from_bytes_rejects_unassigned() {
1530 assert_eq!(
1531 PathElementCS::from_bytes("\u{0378}".as_bytes())
1532 .unwrap_err()
1533 .kind,
1534 ErrorKind::ContainsUnassignedChar
1535 );
1536 }
1537
1538 #[test]
1539 fn from_bytes_dynamic_sensitive() {
1540 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseSensitive).unwrap();
1541 assert_eq!(pe.normalized(), "Hello.txt");
1542 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1543 }
1544
1545 #[test]
1548 fn from_bytes_overlong_null() {
1549 let input: &[u8] = &[0x61, 0xC0, 0x80, 0x62];
1551 assert_eq!(
1552 PathElementCS::from_bytes(input).unwrap_err().kind,
1553 ErrorKind::InvalidUtf8
1554 );
1555 }
1556
1557 #[test]
1558 fn from_bytes_surrogate_bytes_rejected() {
1559 let input: &[u8] = &[0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80];
1560 assert_eq!(
1561 PathElementCS::from_bytes(input).unwrap_err().kind,
1562 ErrorKind::InvalidUtf8
1563 );
1564 }
1565
1566 #[test]
1567 fn from_bytes_lone_high_surrogate_rejected() {
1568 let input: &[u8] = &[0x61, 0xED, 0xA0, 0x80, 0x62];
1569 assert_eq!(
1570 PathElementCS::from_bytes(input).unwrap_err().kind,
1571 ErrorKind::InvalidUtf8
1572 );
1573 }
1574
1575 #[test]
1576 fn from_bytes_lone_low_surrogate_rejected() {
1577 let input: &[u8] = &[0x61, 0xED, 0xB0, 0x80, 0x62];
1578 assert_eq!(
1579 PathElementCS::from_bytes(input).unwrap_err().kind,
1580 ErrorKind::InvalidUtf8
1581 );
1582 }
1583
1584 #[test]
1585 fn from_bytes_overlong_null_only() {
1586 let input: &[u8] = &[0xC0, 0x80];
1587 assert_eq!(
1588 PathElementCS::from_bytes(input).unwrap_err().kind,
1589 ErrorKind::InvalidUtf8
1590 );
1591 }
1592
1593 #[test]
1594 fn from_bytes_invalid_byte_rejected() {
1595 let input: &[u8] = &[0x68, 0x69, 0xFF];
1596 assert_eq!(
1597 PathElementCS::from_bytes(input).unwrap_err().kind,
1598 ErrorKind::InvalidUtf8
1599 );
1600 }
1601
1602 #[test]
1605 fn os_compatible_supplementary_unchanged() {
1606 let pe = PathElementCS::new("file_😀.txt").unwrap();
1607 assert_eq!(pe.os_compatible(), "file_😀.txt");
1608 }
1609
1610 #[test]
1611 fn os_compatible_supplementary_roundtrip() {
1612 let pe = PathElementCS::new("file_😀.txt").unwrap();
1613 let pe2 = PathElementCS::new(pe.os_compatible()).unwrap();
1614 assert_eq!(pe.normalized(), pe2.normalized());
1615 }
1616
1617 #[test]
1618 fn os_compatible_multiple_supplementary() {
1619 let pe = PathElementCS::new("𐀀_𝄞_😀").unwrap();
1620 assert_eq!(pe.os_compatible(), "𐀀_𝄞_😀");
1621 }
1622}
1623
1624#[cfg(all(test, feature = "std"))]
1625mod os_str_tests {
1626 use std::borrow::Cow;
1627 use std::ffi::{OsStr, OsString};
1628
1629 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
1630 use wasm_bindgen_test::wasm_bindgen_test as test;
1631
1632 use crate::ErrorKind;
1633 use crate::case_sensitivity::{CaseInsensitive, CaseSensitivity};
1634 use crate::path_element::{PathElement, PathElementCI, PathElementCS};
1635
1636 #[test]
1637 fn from_os_str_borrowed_matches_new() {
1638 let input = OsStr::new("hello.txt");
1639 let from_os = PathElementCS::from_os_str(input).unwrap();
1640 let from_new = PathElementCS::new("hello.txt").unwrap();
1641 assert_eq!(from_os.original(), from_new.original());
1642 assert_eq!(from_os.normalized(), from_new.normalized());
1643 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1644 }
1645
1646 #[test]
1647 fn from_os_str_owned_matches_new() {
1648 let input = OsString::from("Hello.txt");
1649 let from_os = PathElementCI::from_os_str(input).unwrap();
1650 let from_new = PathElementCI::new("Hello.txt").unwrap();
1651 assert_eq!(from_os.original(), from_new.original());
1652 assert_eq!(from_os.normalized(), from_new.normalized());
1653 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1654 }
1655
1656 #[test]
1657 fn from_os_str_borrowed_preserves_borrow() {
1658 let input = OsStr::new("hello.txt");
1659 let pe = PathElementCS::from_os_str(input).unwrap();
1660 let orig = pe.into_original();
1661 assert!(matches!(orig, Cow::Borrowed(_)));
1662 }
1663
1664 #[test]
1665 fn from_os_str_rejects_empty() {
1666 assert_eq!(
1667 PathElementCS::from_os_str(OsStr::new("")).unwrap_err().kind,
1668 ErrorKind::Empty
1669 );
1670 }
1671
1672 #[test]
1673 fn from_os_str_rejects_dot() {
1674 assert_eq!(
1675 PathElementCS::from_os_str(OsStr::new("."))
1676 .unwrap_err()
1677 .kind,
1678 ErrorKind::CurrentDirectoryMarker
1679 );
1680 }
1681
1682 #[test]
1683 fn from_os_str_rejects_dotdot() {
1684 assert_eq!(
1685 PathElementCS::from_os_str(OsStr::new(".."))
1686 .unwrap_err()
1687 .kind,
1688 ErrorKind::ParentDirectoryMarker
1689 );
1690 }
1691
1692 #[test]
1693 fn from_os_str_rejects_slash() {
1694 assert_eq!(
1695 PathElementCS::from_os_str(OsStr::new("a/b"))
1696 .unwrap_err()
1697 .kind,
1698 ErrorKind::ContainsForwardSlash
1699 );
1700 }
1701
1702 #[test]
1703 fn from_os_str_rejects_null() {
1704 assert_eq!(
1705 PathElementCS::from_os_str(OsStr::new("\0"))
1706 .unwrap_err()
1707 .kind,
1708 ErrorKind::ContainsNullByte
1709 );
1710 }
1711
1712 #[test]
1713 fn from_os_str_rejects_unassigned() {
1714 assert_eq!(
1715 PathElementCS::from_os_str(OsStr::new("\u{0378}"))
1716 .unwrap_err()
1717 .kind,
1718 ErrorKind::ContainsUnassignedChar
1719 );
1720 }
1721
1722 #[cfg(unix)]
1723 #[test]
1724 fn from_os_str_invalid_utf8_borrowed_rejected() {
1725 use std::os::unix::ffi::OsStrExt;
1726 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]);
1727 assert_eq!(
1728 PathElementCS::from_os_str(input).unwrap_err().kind,
1729 ErrorKind::InvalidUtf8
1730 );
1731 }
1732
1733 #[cfg(unix)]
1734 #[test]
1735 fn from_os_str_invalid_utf8_owned_rejected() {
1736 use std::os::unix::ffi::OsStrExt;
1737 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]).to_os_string();
1738 assert_eq!(
1739 PathElementCS::from_os_str(input).unwrap_err().kind,
1740 ErrorKind::InvalidUtf8
1741 );
1742 }
1743
1744 #[cfg(unix)]
1745 #[test]
1746 fn from_os_str_surrogate_bytes_rejected() {
1747 use std::os::unix::ffi::OsStrExt;
1748 let input = OsStr::from_bytes(&[0x68, 0xED, 0xA0, 0x80, 0x69]);
1750 assert_eq!(
1751 PathElementCS::from_os_str(input).unwrap_err().kind,
1752 ErrorKind::InvalidUtf8
1753 );
1754 }
1755
1756 #[cfg(windows)]
1757 #[test]
1758 fn from_os_str_invalid_utf8_borrowed_rejected() {
1759 use std::os::windows::ffi::OsStringExt;
1760 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1761 assert_eq!(
1762 PathElementCS::from_os_str(input.as_os_str())
1763 .unwrap_err()
1764 .kind,
1765 ErrorKind::InvalidUtf8
1766 );
1767 }
1768
1769 #[cfg(windows)]
1770 #[test]
1771 fn from_os_str_invalid_utf8_owned_rejected() {
1772 use std::os::windows::ffi::OsStringExt;
1773 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1774 assert_eq!(
1775 PathElementCS::from_os_str(input).unwrap_err().kind,
1776 ErrorKind::InvalidUtf8
1777 );
1778 }
1779
1780 #[test]
1781 fn os_str_returns_os_compatible() {
1782 let pe = PathElementCS::new("hello.txt").unwrap();
1783 assert_eq!(pe.os_str(), OsStr::new(pe.os_compatible()));
1784 }
1785
1786 #[test]
1787 fn into_os_str_returns_os_compatible() {
1788 let pe = PathElementCS::new("hello.txt").unwrap();
1789 let expected = pe.os_compatible().to_owned();
1790 let result = pe.into_os_str();
1791 assert_eq!(result, OsStr::new(&expected));
1792 }
1793
1794 #[test]
1795 fn into_os_str_borrows_when_no_transformation() {
1796 let input = OsStr::new("hello.txt");
1797 let pe = PathElementCS::from_os_str(input).unwrap();
1798 let result = pe.into_os_str();
1799 assert!(matches!(result, Cow::Borrowed(_)));
1800 assert_eq!(result, OsStr::new("hello.txt"));
1801 }
1802
1803 #[test]
1804 fn into_os_str_ci_borrows_when_already_folded() {
1805 let input = OsStr::new("hello.txt");
1806 let pe = PathElementCI::from_os_str(input).unwrap();
1807 let result = pe.into_os_str();
1808 assert!(matches!(result, Cow::Borrowed(_)));
1809 assert_eq!(result, OsStr::new("hello.txt"));
1810 }
1811
1812 #[test]
1815 fn into_os_str_owned_when_nfc_transforms() {
1816 let input = OsStr::new("e\u{0301}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1818 let result = pe.into_os_str();
1819 #[cfg(target_vendor = "apple")]
1820 assert!(matches!(result, Cow::Borrowed(_)));
1821 #[cfg(not(target_vendor = "apple"))]
1822 assert!(matches!(result, Cow::Owned(_)));
1823 }
1824
1825 #[test]
1827 fn into_os_str_owned_when_nfd_transforms() {
1828 let input = OsStr::new("\u{00E9}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1830 let result = pe.into_os_str();
1831 #[cfg(target_vendor = "apple")]
1832 assert!(matches!(result, Cow::Owned(_)));
1833 #[cfg(not(target_vendor = "apple"))]
1834 assert!(matches!(result, Cow::Borrowed(_)));
1835 }
1836
1837 #[test]
1838 fn from_os_str_cs_matches_typed() {
1839 let input = OsStr::new("Hello.txt");
1840 let dyn_pe = PathElement::from_os_str_cs(input).unwrap();
1841 let cs_pe = PathElementCS::from_os_str(input).unwrap();
1842 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1843 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1844 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1845 }
1846
1847 #[test]
1848 fn from_os_str_ci_matches_typed() {
1849 let input = OsStr::new("Hello.txt");
1850 let dyn_pe = PathElement::from_os_str_ci(input).unwrap();
1851 let ci_pe = PathElementCI::from_os_str(input).unwrap();
1852 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1853 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1854 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1855 }
1856
1857 #[test]
1858 fn from_os_str_dynamic_case_sensitivity() {
1859 let input = OsStr::new("Hello.txt");
1860 let pe = PathElement::from_os_str(input, CaseInsensitive).unwrap();
1861 assert_eq!(pe.normalized(), "hello.txt");
1862 }
1863}