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::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]| ErrorKind::InvalidUtf8.into_error(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(kind.into_error(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(kind.into_error(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]
773 fn path_element_cs_matches_freestanding() {
774 let input = "H\tllo";
775 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
776 assert_eq!(pe.original(), input);
777 assert_eq!(pe.normalized(), normalize_cs(input).unwrap().as_ref());
778 assert_eq!(
779 pe.os_compatible(),
780 os_compatible_from_normalized_cs(&normalize_cs(input).unwrap())
781 .unwrap()
782 .as_ref()
783 );
784 }
785
786 #[test]
789 fn path_element_ci_matches_freestanding() {
790 let input = "H\tllo";
791 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
792 assert_eq!(pe.original(), "H\tllo");
793 assert_eq!(pe.normalized(), "h\u{2409}llo");
794 assert_eq!(pe.os_compatible(), "H\u{2409}llo");
795 }
796
797 #[test]
800 fn path_element_cs_os_compatible_platform_dependent() {
801 let input = "nul.e\u{0301}";
802 let pe = PathElementCS::new(input).unwrap();
803 assert_eq!(pe.original(), "nul.e\u{0301}");
804 assert_eq!(pe.normalized(), "nul.\u{00E9}");
805 #[cfg(target_os = "windows")]
806 assert_eq!(pe.os_compatible(), "\u{FF4E}ul.\u{00E9}");
807 #[cfg(target_vendor = "apple")]
808 assert_eq!(pe.os_compatible(), "nul.e\u{0301}");
809 #[cfg(not(any(target_os = "windows", target_vendor = "apple")))]
810 assert_eq!(pe.os_compatible(), "nul.\u{00E9}");
811 }
812
813 #[test]
814 fn path_element_cs_nfc_matches_freestanding() {
815 let input = "e\u{0301}.txt";
816 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
817 assert_eq!(pe.normalized(), normalize_cs(input).unwrap().as_ref());
818 }
819
820 #[test]
821 fn path_element_ci_casefold_matches_freestanding() {
822 let input = "Hello.txt";
823 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
824 let cs = normalize_cs(input).unwrap();
825 assert_eq!(
826 pe.normalized(),
827 normalize_ci_from_normalized_cs(&cs).as_ref()
828 );
829 }
830
831 #[test]
832 fn path_element_cs_normalized_borrows_from_original() {
833 let input = "hello.txt";
834 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
835 assert_eq!(pe.normalized(), "hello.txt");
836 assert!(core::ptr::eq(pe.normalized().as_ptr(), input.as_ptr()));
837 }
838
839 #[test]
840 fn path_element_cs_into_normalized_borrows() {
841 let input = "hello.txt";
842 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
843 let norm = pe.into_normalized();
844 assert!(matches!(norm, Cow::Borrowed(_)));
845 assert_eq!(norm, "hello.txt");
846 }
847
848 #[test]
849 fn path_element_cs_into_os_compatible_borrows() {
850 let input = "hello.txt";
851 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
852 let pres = pe.into_os_compatible();
853 assert!(matches!(pres, Cow::Borrowed(_)));
854 assert_eq!(pres.as_ref(), "hello.txt");
855 }
856
857 #[test]
858 fn path_element_ci_normalized_borrows_when_already_folded() {
859 let input = "hello.txt";
860 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
861 assert!(core::ptr::eq(pe.normalized().as_ptr(), input.as_ptr()));
862 }
863
864 #[test]
865 fn path_element_ci_into_normalized_borrows_when_already_folded() {
866 let input = "hello.txt";
867 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
868 let norm = pe.into_normalized();
869 assert!(matches!(norm, Cow::Borrowed(_)));
870 assert_eq!(norm, "hello.txt");
871 }
872
873 #[test]
874 fn path_element_ci_into_os_compatible_borrows_when_already_folded() {
875 let input = "hello.txt";
876 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
877 let pres = pe.into_os_compatible();
878 assert!(matches!(pres, Cow::Borrowed(_)));
879 assert_eq!(pres.as_ref(), "hello.txt");
880 }
881
882 #[test]
883 fn path_element_cs_trimmed_borrows_suffix() {
884 let input = " hello.txt";
885 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
886 assert_eq!(pe.normalized(), "hello.txt");
887 assert!(core::ptr::eq(pe.normalized().as_ptr(), input[3..].as_ptr()));
888 }
889
890 #[test]
891 fn path_element_into_original_returns_original() {
892 let input = " Hello.txt ";
893 let pe = PathElementCS::new(Cow::Borrowed(input)).unwrap();
894 let orig = pe.into_original();
895 assert!(matches!(orig, Cow::Borrowed(_)));
896 assert_eq!(orig, input);
897 }
898
899 #[test]
900 fn path_element_is_normalized_when_unchanged() {
901 let pe = PathElementCS::new("hello.txt").unwrap();
902 assert!(pe.is_normalized());
903 }
904
905 #[test]
906 fn path_element_is_not_normalized_when_trimmed() {
907 let pe = PathElementCS::new(" hello.txt ").unwrap();
908 assert!(!pe.is_normalized());
909 }
910
911 #[test]
912 fn path_element_is_not_normalized_when_trailing_whitespace_trimmed() {
913 let pe = PathElementCS::new("hello.txt ").unwrap();
915 assert!(!pe.is_normalized());
916 assert_eq!(pe.normalized(), "hello.txt");
917 }
918
919 #[test]
920 fn path_element_is_not_normalized_when_casefolded() {
921 let pe = PathElementCI::new("Hello.txt").unwrap();
922 assert!(!pe.is_normalized());
923 }
924
925 #[test]
926 fn path_element_ci_is_normalized_when_already_folded() {
927 let pe = PathElementCI::new("hello.txt").unwrap();
928 assert!(pe.is_normalized());
929 }
930
931 #[test]
932 fn path_element_is_os_compatible_ascii() {
933 let pe = PathElementCS::new("hello.txt").unwrap();
934 assert!(pe.is_os_compatible());
935 }
936
937 #[test]
938 fn path_element_is_not_os_compatible_trailing_whitespace_ci() {
939 let pe = PathElementCI::new("hello.txt ").unwrap();
942 assert!(!pe.is_os_compatible());
943 }
944
945 #[test]
946 fn path_element_is_not_os_compatible_trailing_whitespace_cs() {
947 let pe = PathElementCS::new("hello.txt ").unwrap();
950 assert!(!pe.is_os_compatible());
951 }
952
953 #[test]
954 fn path_element_is_os_compatible_nfc_input() {
955 let pe = PathElementCS::new("\u{00E9}.txt").unwrap();
959 #[cfg(target_vendor = "apple")]
960 assert!(!pe.is_os_compatible());
961 #[cfg(not(target_vendor = "apple"))]
962 assert!(pe.is_os_compatible());
963 }
964
965 #[test]
966 fn path_element_is_os_compatible_nfd_input() {
967 let pe = PathElementCS::new("e\u{0301}.txt").unwrap();
970 #[cfg(target_vendor = "apple")]
971 assert!(pe.is_os_compatible());
972 #[cfg(not(target_vendor = "apple"))]
973 assert!(!pe.is_os_compatible());
974 }
975
976 #[test]
977 fn path_element_is_os_compatible_nfd_input_ci() {
978 let pe = PathElementCI::new("e\u{0301}.txt").unwrap();
983 #[cfg(target_vendor = "apple")]
984 assert!(pe.is_os_compatible());
985 #[cfg(not(target_vendor = "apple"))]
986 assert!(!pe.is_os_compatible());
987 }
988
989 #[test]
990 fn path_element_is_os_compatible_nfc_input_ci() {
991 let pe = PathElementCI::new("\u{00E9}.txt").unwrap();
994 #[cfg(target_vendor = "apple")]
995 assert!(!pe.is_os_compatible());
996 #[cfg(not(target_vendor = "apple"))]
997 assert!(pe.is_os_compatible());
998 }
999
1000 #[test]
1001 fn path_element_is_not_os_compatible_reserved_on_windows() {
1002 let pe = PathElementCS::new("nul.txt").unwrap();
1003 #[cfg(target_os = "windows")]
1004 assert!(!pe.is_os_compatible());
1005 #[cfg(not(target_os = "windows"))]
1006 assert!(pe.is_os_compatible());
1007 }
1008
1009 #[test]
1010 fn path_element_borrowed_is_borrowed() {
1011 let pe = PathElementCS::new(Cow::Borrowed("hello.txt")).unwrap();
1012 assert!(pe.is_borrowed());
1013 assert!(!pe.is_owned());
1014 }
1015
1016 #[test]
1017 fn path_element_owned_is_owned() {
1018 let pe = PathElementCS::new(Cow::Owned("hello.txt".to_string())).unwrap();
1019 assert!(pe.is_owned());
1020 assert!(!pe.is_borrowed());
1021 }
1022
1023 #[test]
1024 fn path_element_into_owned_is_owned() {
1025 let pe = PathElementCS::new(Cow::Borrowed("hello.txt")).unwrap();
1026 let owned = pe.into_owned();
1027 assert!(owned.is_owned());
1028 }
1029
1030 #[test]
1031 fn path_element_into_owned_preserves_values() {
1032 let input = "H\tllo";
1033 let pe = PathElementCI::new(Cow::Borrowed(input)).unwrap();
1034 let owned = pe.into_owned();
1035 assert_eq!(owned.original(), "H\tllo");
1036 assert_eq!(owned.normalized(), "h\u{2409}llo");
1037 assert_eq!(owned.os_compatible(), "H\u{2409}llo");
1038 }
1039
1040 #[test]
1041 fn new_rejects_empty() {
1042 assert_eq!(PathElementCS::new("").unwrap_err().kind(), ErrorKind::Empty);
1043 }
1044
1045 #[test]
1046 fn new_rejects_dot() {
1047 assert_eq!(
1048 PathElementCS::new(".").unwrap_err().kind(),
1049 ErrorKind::CurrentDirectoryMarker
1050 );
1051 }
1052
1053 #[test]
1054 fn new_rejects_dotdot() {
1055 assert_eq!(
1056 PathElementCS::new("..").unwrap_err().kind(),
1057 ErrorKind::ParentDirectoryMarker
1058 );
1059 }
1060
1061 #[test]
1062 fn new_rejects_slash() {
1063 assert_eq!(
1064 PathElementCS::new("a/b").unwrap_err().kind(),
1065 ErrorKind::ContainsForwardSlash
1066 );
1067 }
1068
1069 #[test]
1070 fn new_rejects_null() {
1071 assert_eq!(
1072 PathElementCS::new("\0").unwrap_err().kind(),
1073 ErrorKind::ContainsNullByte
1074 );
1075 }
1076
1077 #[test]
1078 fn new_rejects_unassigned() {
1079 assert_eq!(
1080 PathElementCS::new("\u{0378}").unwrap_err().kind(),
1081 ErrorKind::ContainsUnassignedChar
1082 );
1083 }
1084
1085 #[test]
1088 fn path_element_eq_same_cs() {
1089 let a = PathElementCS::new("hello.txt").unwrap();
1090 let b = PathElementCS::new("hello.txt").unwrap();
1091 assert_eq!(a, b);
1092 }
1093
1094 #[test]
1095 fn path_element_eq_different_original_same_normalized_cs() {
1096 let a = PathElementCS::new(" hello.txt ").unwrap();
1097 let b = PathElementCS::new("hello.txt").unwrap();
1098 assert_ne!(a.original(), b.original());
1099 assert_eq!(a, b);
1100 }
1101
1102 #[test]
1103 fn path_element_ne_different_case_cs() {
1104 let a = PathElementCS::new("Hello.txt").unwrap();
1105 let b = PathElementCS::new("hello.txt").unwrap();
1106 assert_ne!(a, b);
1107 }
1108
1109 #[test]
1110 fn path_element_eq_different_case_ci() {
1111 let a = PathElementCI::new("Hello.txt").unwrap();
1112 let b = PathElementCI::new("hello.txt").unwrap();
1113 assert_eq!(a, b);
1114 }
1115
1116 #[test]
1117 fn path_element_eq_nfc_nfd_cs() {
1118 let a = PathElementCS::new("\u{00E9}.txt").unwrap();
1119 let b = PathElementCS::new("e\u{0301}.txt").unwrap();
1120 assert_eq!(a, b);
1121 }
1122
1123 #[test]
1124 fn path_element_eq_cross_lifetime() {
1125 let owned = PathElementCS::new("hello.txt").unwrap().into_owned();
1126 let input = "hello.txt";
1127 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1128 assert_eq!(owned, borrowed);
1129 assert_eq!(borrowed, owned);
1130 }
1131
1132 #[test]
1133 #[should_panic(expected = "different case sensitivity")]
1134 fn path_element_eq_panics_on_mixed_dynamic_case_sensitivity() {
1135 let a = PathElement::new("hello", CaseSensitive).unwrap();
1136 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1137 let _ = a == b;
1138 }
1139
1140 #[test]
1143 fn path_element_ord_alphabetical_cs() {
1144 let a = PathElementCS::new("apple").unwrap();
1145 let b = PathElementCS::new("banana").unwrap();
1146 assert!(a < b);
1147 assert!(b > a);
1148 }
1149
1150 #[test]
1151 fn path_element_ord_equal_cs() {
1152 let a = PathElementCS::new("hello").unwrap();
1153 let b = PathElementCS::new("hello").unwrap();
1154 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1155 }
1156
1157 #[test]
1158 fn path_element_ord_case_ci() {
1159 let a = PathElementCI::new("Apple").unwrap();
1160 let b = PathElementCI::new("apple").unwrap();
1161 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1162 }
1163
1164 #[test]
1165 fn path_element_partial_ord_cross_lifetime() {
1166 let owned = PathElementCS::new("apple").unwrap().into_owned();
1167 let input = "banana";
1168 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1169 assert!(owned < borrowed);
1170 }
1171
1172 #[test]
1173 fn path_element_partial_ord_none_on_mixed_dynamic_case_sensitivity() {
1174 let a = PathElement::new("hello", CaseSensitive).unwrap();
1175 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1176 assert_eq!(a.partial_cmp(&b), None);
1177 }
1178
1179 #[test]
1180 fn path_element_ord_sortable() {
1181 let mut elems: Vec<_> = ["cherry", "apple", "banana"]
1182 .iter()
1183 .map(|s| PathElementCS::new(Cow::Borrowed(*s)).unwrap())
1184 .collect();
1185 elems.sort();
1186 let names: Vec<_> = elems.iter().map(PathElementCS::normalized).collect();
1187 assert_eq!(names, &["apple", "banana", "cherry"]);
1188 }
1189
1190 #[test]
1191 fn path_element_ord_ci_sortable() {
1192 let mut elems: Vec<_> = ["Cherry", "apple", "BANANA"]
1193 .iter()
1194 .map(|s| PathElementCI::new(Cow::Borrowed(*s)).unwrap())
1195 .collect();
1196 elems.sort();
1197 let names: Vec<_> = elems.iter().map(PathElementCI::normalized).collect();
1198 assert_eq!(names, &["apple", "banana", "cherry"]);
1199 }
1200
1201 #[test]
1204 fn from_cs_into_dynamic() {
1205 let pe = PathElementCS::new("hello").unwrap();
1206 let dyn_pe: PathElement<'_> = pe.into();
1207 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1208 assert_eq!(dyn_pe.normalized(), "hello");
1209 }
1210
1211 #[test]
1212 fn from_ci_into_dynamic() {
1213 let pe = PathElementCI::new("Hello").unwrap();
1214 let dyn_pe: PathElement<'_> = pe.into();
1215 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1216 assert_eq!(dyn_pe.normalized(), "hello");
1217 }
1218
1219 #[test]
1220 fn try_from_dynamic_to_cs() {
1221 let pe = PathElement::new("hello", CaseSensitive).unwrap();
1222 let cs_pe: PathElementCS<'_> = pe.try_into().unwrap();
1223 assert_eq!(cs_pe.normalized(), "hello");
1224 }
1225
1226 #[test]
1227 fn try_from_dynamic_to_cs_wrong_variant() {
1228 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1229 let err: PathElementCI<'_> = PathElementCS::try_from(pe).unwrap_err();
1230 assert_eq!(err.original(), "Hello");
1231 assert_eq!(err.normalized(), "hello");
1232 assert_eq!(err.os_compatible(), "Hello");
1233 }
1234
1235 #[test]
1236 fn try_from_dynamic_to_ci() {
1237 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1238 let ci_pe: PathElementCI<'_> = pe.try_into().unwrap();
1239 assert_eq!(ci_pe.normalized(), "hello");
1240 }
1241
1242 #[test]
1245 fn dyn_new_cs() {
1246 let pe = PathElement::new_cs("Hello.txt").unwrap();
1247 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1248 assert_eq!(pe.normalized(), "Hello.txt");
1249 }
1250
1251 #[test]
1252 fn dyn_new_ci() {
1253 let pe = PathElement::new_ci("Hello.txt").unwrap();
1254 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1255 assert_eq!(pe.normalized(), "hello.txt");
1256 }
1257
1258 #[test]
1259 fn dyn_new_cs_matches_typed() {
1260 let dyn_pe = PathElement::new_cs("Hello.txt").unwrap();
1261 let cs_pe = PathElementCS::new("Hello.txt").unwrap();
1262 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1263 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1264 }
1265
1266 #[test]
1267 fn dyn_new_ci_matches_typed() {
1268 let dyn_pe = PathElement::new_ci("Hello.txt").unwrap();
1269 let ci_pe = PathElementCI::new("Hello.txt").unwrap();
1270 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1271 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1272 }
1273
1274 #[test]
1277 fn case_sensitivity_cs() {
1278 let pe = PathElementCS::new("hello").unwrap();
1279 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1280 }
1281
1282 #[test]
1283 fn case_sensitivity_ci() {
1284 let pe = PathElementCI::new("hello").unwrap();
1285 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1286 }
1287
1288 #[test]
1289 fn case_sensitivity_dyn() {
1290 let cs = PathElement::new("hello", CaseSensitive).unwrap();
1291 let ci = PathElement::new("hello", CaseInsensitive).unwrap();
1292 assert_eq!(cs.case_sensitivity(), CaseSensitivity::Sensitive);
1293 assert_eq!(ci.case_sensitivity(), CaseSensitivity::Insensitive);
1294 }
1295
1296 #[test]
1299 fn partial_ord_dyn_same_case_sensitivity() {
1300 let a = PathElement::new("apple", CaseSensitive).unwrap();
1301 let b = PathElement::new("banana", CaseSensitive).unwrap();
1302 assert!(a < b);
1303 }
1304
1305 #[test]
1306 fn partial_ord_dyn_none_on_mismatch() {
1307 let a = PathElement::new("hello", CaseSensitive).unwrap();
1308 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1309 assert_eq!(a.partial_cmp(&b), None);
1310 }
1311
1312 #[test]
1315 fn try_from_dynamic_to_ci_wrong_variant() {
1316 let pe = PathElement::new("Hello", CaseSensitive).unwrap();
1317 let err: PathElementCS<'_> = PathElementCI::try_from(pe).unwrap_err();
1318 assert_eq!(err.original(), "Hello");
1319 assert_eq!(err.normalized(), "Hello");
1320 assert_eq!(err.os_compatible(), "Hello");
1321 }
1322
1323 #[test]
1326 fn into_owned_preserves_cs_case_sensitivity() {
1327 let pe = PathElementCS::new("hello").unwrap();
1328 let owned = pe.into_owned();
1329 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Sensitive);
1330 }
1331
1332 #[test]
1333 fn into_owned_preserves_dyn_case_sensitivity() {
1334 let pe = PathElement::new("hello", CaseInsensitive).unwrap();
1335 let owned = pe.into_owned();
1336 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Insensitive);
1337 }
1338
1339 #[test]
1342 fn eq_cs_vs_dyn_same_case_sensitivity() {
1343 let cs = PathElementCS::new("hello").unwrap();
1344 let dyn_cs = PathElement::new_cs("hello").unwrap();
1345 assert_eq!(cs, dyn_cs);
1346 assert_eq!(dyn_cs, cs);
1347 }
1348
1349 #[test]
1350 fn eq_ci_vs_dyn_same_case_sensitivity() {
1351 let ci = PathElementCI::new("Hello").unwrap();
1352 let dyn_ci = PathElement::new_ci("hello").unwrap();
1353 assert_eq!(ci, dyn_ci);
1354 assert_eq!(dyn_ci, ci);
1355 }
1356
1357 #[test]
1358 #[should_panic(expected = "different case sensitivity")]
1359 fn eq_cs_vs_ci_panics() {
1360 let cs = PathElementCS::new("hello").unwrap();
1361 let ci = PathElementCI::new("hello").unwrap();
1362 let _ = cs == ci;
1363 }
1364
1365 #[test]
1366 #[should_panic(expected = "different case sensitivity")]
1367 fn eq_cs_vs_dyn_ci_panics() {
1368 let cs = PathElementCS::new("hello").unwrap();
1369 let dyn_ci = PathElement::new_ci("hello").unwrap();
1370 let _ = cs == dyn_ci;
1371 }
1372
1373 #[test]
1376 fn partial_ord_cs_vs_dyn_same_case_sensitivity() {
1377 let cs = PathElementCS::new("apple").unwrap();
1378 let dyn_cs = PathElement::new_cs("banana").unwrap();
1379 assert!(cs < dyn_cs);
1380 assert!(dyn_cs > cs);
1381 }
1382
1383 #[test]
1384 fn partial_ord_cs_vs_ci_none() {
1385 let cs = PathElementCS::new("hello").unwrap();
1386 let ci = PathElementCI::new("hello").unwrap();
1387 assert_eq!(cs.partial_cmp(&ci), None);
1388 assert_eq!(ci.partial_cmp(&cs), None);
1389 }
1390
1391 #[test]
1392 fn partial_ord_cs_vs_dyn_ci_none() {
1393 let cs = PathElementCS::new("hello").unwrap();
1394 let dyn_ci = PathElement::new_ci("hello").unwrap();
1395 assert_eq!(cs.partial_cmp(&dyn_ci), None);
1396 assert_eq!(dyn_ci.partial_cmp(&cs), None);
1397 }
1398
1399 #[test]
1402 fn from_bytes_cs_borrowed_matches_new() {
1403 let pe_bytes = PathElementCS::from_bytes(b"hello.txt" as &[u8]).unwrap();
1404 let pe_str = PathElementCS::new("hello.txt").unwrap();
1405 assert_eq!(pe_bytes.original(), pe_str.original());
1406 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1407 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1408 }
1409
1410 #[test]
1411 fn from_bytes_ci_borrowed_matches_new() {
1412 let pe_bytes = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1413 let pe_str = PathElementCI::new("Hello.txt").unwrap();
1414 assert_eq!(pe_bytes.original(), pe_str.original());
1415 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1416 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1417 }
1418
1419 #[test]
1420 fn from_bytes_owned_matches_new() {
1421 let pe_bytes = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1422 let pe_str = PathElementCS::new("hello.txt").unwrap();
1423 assert_eq!(pe_bytes.original(), pe_str.original());
1424 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1425 }
1426
1427 #[test]
1428 fn from_bytes_borrowed_preserves_borrow() {
1429 let input: &[u8] = b"hello.txt";
1430 let pe = PathElementCS::from_bytes(input).unwrap();
1431 let orig = pe.into_original();
1432 assert!(matches!(orig, Cow::Borrowed(_)));
1433 }
1434
1435 #[test]
1436 fn from_bytes_owned_is_owned() {
1437 let pe = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1438 assert!(pe.is_owned());
1439 }
1440
1441 #[test]
1442 fn from_bytes_invalid_utf8_borrowed_rejected() {
1443 let input: &[u8] = &[0x68, 0x69, 0xFF]; let err = PathElementCS::from_bytes(input).unwrap_err();
1445 assert_eq!(err.kind(), ErrorKind::InvalidUtf8);
1446 }
1447
1448 #[test]
1449 fn from_bytes_invalid_utf8_owned_rejected() {
1450 let input = vec![0x68, 0x69, 0xFF];
1451 let err = PathElementCS::from_bytes(input).unwrap_err();
1452 assert_eq!(err.kind(), ErrorKind::InvalidUtf8);
1453 }
1454
1455 #[test]
1456 fn from_bytes_dynamic_case_sensitivity() {
1457 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseInsensitive).unwrap();
1458 assert_eq!(pe.normalized(), "hello.txt");
1459 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1460 }
1461
1462 #[test]
1463 fn from_bytes_cs_matches_typed() {
1464 let input: &[u8] = b"Hello.txt";
1465 let dyn_pe = PathElement::from_bytes_cs(input).unwrap();
1466 let cs_pe = PathElementCS::from_bytes(input).unwrap();
1467 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1468 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1469 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1470 }
1471
1472 #[test]
1473 fn from_bytes_ci_matches_typed() {
1474 let input: &[u8] = b"Hello.txt";
1475 let dyn_pe = PathElement::from_bytes_ci(input).unwrap();
1476 let ci_pe = PathElementCI::from_bytes(input).unwrap();
1477 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1478 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1479 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1480 }
1481
1482 #[test]
1483 fn from_bytes_with_case_sensitivity_cs() {
1484 let pe = PathElementCS::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1485 assert_eq!(pe.normalized(), "Hello.txt");
1486 }
1487
1488 #[test]
1489 fn from_bytes_with_case_sensitivity_ci() {
1490 let pe = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1491 assert_eq!(pe.normalized(), "hello.txt");
1492 }
1493
1494 #[test]
1495 fn from_bytes_rejects_empty() {
1496 assert_eq!(
1497 PathElementCS::from_bytes(b"" as &[u8]).unwrap_err().kind(),
1498 ErrorKind::Empty
1499 );
1500 }
1501
1502 #[test]
1503 fn from_bytes_rejects_dot() {
1504 assert_eq!(
1505 PathElementCS::from_bytes(b"." as &[u8]).unwrap_err().kind(),
1506 ErrorKind::CurrentDirectoryMarker
1507 );
1508 }
1509
1510 #[test]
1511 fn from_bytes_rejects_dotdot() {
1512 assert_eq!(
1513 PathElementCS::from_bytes(b".." as &[u8])
1514 .unwrap_err()
1515 .kind(),
1516 ErrorKind::ParentDirectoryMarker
1517 );
1518 }
1519
1520 #[test]
1521 fn from_bytes_rejects_slash() {
1522 assert_eq!(
1523 PathElementCS::from_bytes(b"a/b" as &[u8])
1524 .unwrap_err()
1525 .kind(),
1526 ErrorKind::ContainsForwardSlash
1527 );
1528 }
1529
1530 #[test]
1531 fn from_bytes_rejects_null() {
1532 assert_eq!(
1533 PathElementCS::from_bytes(b"\0" as &[u8])
1534 .unwrap_err()
1535 .kind(),
1536 ErrorKind::ContainsNullByte
1537 );
1538 }
1539
1540 #[test]
1541 fn from_bytes_rejects_unassigned() {
1542 assert_eq!(
1543 PathElementCS::from_bytes("\u{0378}".as_bytes())
1544 .unwrap_err()
1545 .kind(),
1546 ErrorKind::ContainsUnassignedChar
1547 );
1548 }
1549
1550 #[test]
1551 fn from_bytes_dynamic_sensitive() {
1552 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseSensitive).unwrap();
1553 assert_eq!(pe.normalized(), "Hello.txt");
1554 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1555 }
1556
1557 #[test]
1560 fn from_bytes_overlong_null() {
1561 let input: &[u8] = &[0x61, 0xC0, 0x80, 0x62];
1563 assert_eq!(
1564 PathElementCS::from_bytes(input).unwrap_err().kind(),
1565 ErrorKind::InvalidUtf8
1566 );
1567 }
1568
1569 #[test]
1570 fn from_bytes_surrogate_bytes_rejected() {
1571 let input: &[u8] = &[0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80];
1572 assert_eq!(
1573 PathElementCS::from_bytes(input).unwrap_err().kind(),
1574 ErrorKind::InvalidUtf8
1575 );
1576 }
1577
1578 #[test]
1579 fn from_bytes_lone_high_surrogate_rejected() {
1580 let input: &[u8] = &[0x61, 0xED, 0xA0, 0x80, 0x62];
1581 assert_eq!(
1582 PathElementCS::from_bytes(input).unwrap_err().kind(),
1583 ErrorKind::InvalidUtf8
1584 );
1585 }
1586
1587 #[test]
1588 fn from_bytes_lone_low_surrogate_rejected() {
1589 let input: &[u8] = &[0x61, 0xED, 0xB0, 0x80, 0x62];
1590 assert_eq!(
1591 PathElementCS::from_bytes(input).unwrap_err().kind(),
1592 ErrorKind::InvalidUtf8
1593 );
1594 }
1595
1596 #[test]
1597 fn from_bytes_overlong_null_only() {
1598 let input: &[u8] = &[0xC0, 0x80];
1599 assert_eq!(
1600 PathElementCS::from_bytes(input).unwrap_err().kind(),
1601 ErrorKind::InvalidUtf8
1602 );
1603 }
1604
1605 #[test]
1606 fn from_bytes_invalid_byte_rejected() {
1607 let input: &[u8] = &[0x68, 0x69, 0xFF];
1608 assert_eq!(
1609 PathElementCS::from_bytes(input).unwrap_err().kind(),
1610 ErrorKind::InvalidUtf8
1611 );
1612 }
1613
1614 #[test]
1617 fn os_compatible_supplementary_unchanged() {
1618 let pe = PathElementCS::new("file_😀.txt").unwrap();
1619 assert_eq!(pe.os_compatible(), "file_😀.txt");
1620 }
1621
1622 #[test]
1623 fn os_compatible_supplementary_roundtrip() {
1624 let pe = PathElementCS::new("file_😀.txt").unwrap();
1625 let pe2 = PathElementCS::new(pe.os_compatible()).unwrap();
1626 assert_eq!(pe.normalized(), pe2.normalized());
1627 }
1628
1629 #[test]
1630 fn os_compatible_multiple_supplementary() {
1631 let pe = PathElementCS::new("𐀀_𝄞_😀").unwrap();
1632 assert_eq!(pe.os_compatible(), "𐀀_𝄞_😀");
1633 }
1634}
1635
1636#[cfg(all(test, feature = "std"))]
1637mod os_str_tests {
1638 use std::borrow::Cow;
1639 use std::ffi::{OsStr, OsString};
1640
1641 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
1642 use wasm_bindgen_test::wasm_bindgen_test as test;
1643
1644 use crate::ErrorKind;
1645 use crate::case_sensitivity::{CaseInsensitive, CaseSensitivity};
1646 use crate::path_element::{PathElement, PathElementCI, PathElementCS};
1647
1648 #[test]
1649 fn from_os_str_borrowed_matches_new() {
1650 let input = OsStr::new("hello.txt");
1651 let from_os = PathElementCS::from_os_str(input).unwrap();
1652 let from_new = PathElementCS::new("hello.txt").unwrap();
1653 assert_eq!(from_os.original(), from_new.original());
1654 assert_eq!(from_os.normalized(), from_new.normalized());
1655 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1656 }
1657
1658 #[test]
1659 fn from_os_str_owned_matches_new() {
1660 let input = OsString::from("Hello.txt");
1661 let from_os = PathElementCI::from_os_str(input).unwrap();
1662 let from_new = PathElementCI::new("Hello.txt").unwrap();
1663 assert_eq!(from_os.original(), from_new.original());
1664 assert_eq!(from_os.normalized(), from_new.normalized());
1665 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1666 }
1667
1668 #[test]
1669 fn from_os_str_borrowed_preserves_borrow() {
1670 let input = OsStr::new("hello.txt");
1671 let pe = PathElementCS::from_os_str(input).unwrap();
1672 let orig = pe.into_original();
1673 assert!(matches!(orig, Cow::Borrowed(_)));
1674 }
1675
1676 #[test]
1677 fn from_os_str_rejects_empty() {
1678 assert_eq!(
1679 PathElementCS::from_os_str(OsStr::new(""))
1680 .unwrap_err()
1681 .kind(),
1682 ErrorKind::Empty
1683 );
1684 }
1685
1686 #[test]
1687 fn from_os_str_rejects_dot() {
1688 assert_eq!(
1689 PathElementCS::from_os_str(OsStr::new("."))
1690 .unwrap_err()
1691 .kind(),
1692 ErrorKind::CurrentDirectoryMarker
1693 );
1694 }
1695
1696 #[test]
1697 fn from_os_str_rejects_dotdot() {
1698 assert_eq!(
1699 PathElementCS::from_os_str(OsStr::new(".."))
1700 .unwrap_err()
1701 .kind(),
1702 ErrorKind::ParentDirectoryMarker
1703 );
1704 }
1705
1706 #[test]
1707 fn from_os_str_rejects_slash() {
1708 assert_eq!(
1709 PathElementCS::from_os_str(OsStr::new("a/b"))
1710 .unwrap_err()
1711 .kind(),
1712 ErrorKind::ContainsForwardSlash
1713 );
1714 }
1715
1716 #[test]
1717 fn from_os_str_rejects_null() {
1718 assert_eq!(
1719 PathElementCS::from_os_str(OsStr::new("\0"))
1720 .unwrap_err()
1721 .kind(),
1722 ErrorKind::ContainsNullByte
1723 );
1724 }
1725
1726 #[test]
1727 fn from_os_str_rejects_unassigned() {
1728 assert_eq!(
1729 PathElementCS::from_os_str(OsStr::new("\u{0378}"))
1730 .unwrap_err()
1731 .kind(),
1732 ErrorKind::ContainsUnassignedChar
1733 );
1734 }
1735
1736 #[cfg(unix)]
1737 #[test]
1738 fn from_os_str_invalid_utf8_borrowed_rejected() {
1739 use std::os::unix::ffi::OsStrExt;
1740 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]);
1741 assert_eq!(
1742 PathElementCS::from_os_str(input).unwrap_err().kind(),
1743 ErrorKind::InvalidUtf8
1744 );
1745 }
1746
1747 #[cfg(unix)]
1748 #[test]
1749 fn from_os_str_invalid_utf8_owned_rejected() {
1750 use std::os::unix::ffi::OsStrExt;
1751 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]).to_os_string();
1752 assert_eq!(
1753 PathElementCS::from_os_str(input).unwrap_err().kind(),
1754 ErrorKind::InvalidUtf8
1755 );
1756 }
1757
1758 #[cfg(unix)]
1759 #[test]
1760 fn from_os_str_surrogate_bytes_rejected() {
1761 use std::os::unix::ffi::OsStrExt;
1762 let input = OsStr::from_bytes(&[0x68, 0xED, 0xA0, 0x80, 0x69]);
1764 assert_eq!(
1765 PathElementCS::from_os_str(input).unwrap_err().kind(),
1766 ErrorKind::InvalidUtf8
1767 );
1768 }
1769
1770 #[cfg(windows)]
1771 #[test]
1772 fn from_os_str_invalid_utf8_borrowed_rejected() {
1773 use std::os::windows::ffi::OsStringExt;
1774 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1775 assert_eq!(
1776 PathElementCS::from_os_str(input.as_os_str())
1777 .unwrap_err()
1778 .kind(),
1779 ErrorKind::InvalidUtf8
1780 );
1781 }
1782
1783 #[cfg(windows)]
1784 #[test]
1785 fn from_os_str_invalid_utf8_owned_rejected() {
1786 use std::os::windows::ffi::OsStringExt;
1787 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1788 assert_eq!(
1789 PathElementCS::from_os_str(input).unwrap_err().kind(),
1790 ErrorKind::InvalidUtf8
1791 );
1792 }
1793
1794 #[test]
1797 fn os_str_returns_os_compatible() {
1798 let pe = PathElementCI::new("H\tllo").unwrap();
1799 assert_eq!(pe.os_str(), OsStr::new("H\u{2409}llo"));
1800 }
1801
1802 #[test]
1803 fn into_os_str_returns_os_compatible() {
1804 let pe = PathElementCI::new("H\tllo").unwrap();
1805 let result = pe.into_os_str();
1806 assert_eq!(result, OsStr::new("H\u{2409}llo"));
1807 }
1808
1809 #[test]
1810 fn into_os_str_borrows_when_no_transformation() {
1811 let input = OsStr::new("hello.txt");
1812 let pe = PathElementCS::from_os_str(input).unwrap();
1813 let result = pe.into_os_str();
1814 assert!(matches!(result, Cow::Borrowed(_)));
1815 assert_eq!(result, OsStr::new("hello.txt"));
1816 }
1817
1818 #[test]
1819 fn into_os_str_ci_borrows_when_already_folded() {
1820 let input = OsStr::new("hello.txt");
1821 let pe = PathElementCI::from_os_str(input).unwrap();
1822 let result = pe.into_os_str();
1823 assert!(matches!(result, Cow::Borrowed(_)));
1824 assert_eq!(result, OsStr::new("hello.txt"));
1825 }
1826
1827 #[test]
1830 fn into_os_str_owned_when_nfc_transforms() {
1831 let input = OsStr::new("e\u{0301}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1833 let result = pe.into_os_str();
1834 #[cfg(target_vendor = "apple")]
1835 assert!(matches!(result, Cow::Borrowed(_)));
1836 #[cfg(not(target_vendor = "apple"))]
1837 assert!(matches!(result, Cow::Owned(_)));
1838 }
1839
1840 #[test]
1842 fn into_os_str_owned_when_nfd_transforms() {
1843 let input = OsStr::new("\u{00E9}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1845 let result = pe.into_os_str();
1846 #[cfg(target_vendor = "apple")]
1847 assert!(matches!(result, Cow::Owned(_)));
1848 #[cfg(not(target_vendor = "apple"))]
1849 assert!(matches!(result, Cow::Borrowed(_)));
1850 }
1851
1852 #[test]
1853 fn from_os_str_cs_matches_typed() {
1854 let input = OsStr::new("Hello.txt");
1855 let dyn_pe = PathElement::from_os_str_cs(input).unwrap();
1856 let cs_pe = PathElementCS::from_os_str(input).unwrap();
1857 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1858 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1859 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1860 }
1861
1862 #[test]
1863 fn from_os_str_ci_matches_typed() {
1864 let input = OsStr::new("Hello.txt");
1865 let dyn_pe = PathElement::from_os_str_ci(input).unwrap();
1866 let ci_pe = PathElementCI::from_os_str(input).unwrap();
1867 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1868 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1869 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1870 }
1871
1872 #[test]
1873 fn from_os_str_dynamic_case_sensitivity() {
1874 let input = OsStr::new("Hello.txt");
1875 let pe = PathElement::from_os_str(input, CaseInsensitive).unwrap();
1876 assert_eq!(pe.normalized(), "hello.txt");
1877 }
1878}