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!(
1043 *PathElementCS::new("").unwrap_err().kind(),
1044 ErrorKind::Empty
1045 );
1046 }
1047
1048 #[test]
1049 fn new_rejects_dot() {
1050 assert_eq!(
1051 *PathElementCS::new(".").unwrap_err().kind(),
1052 ErrorKind::CurrentDirectoryMarker
1053 );
1054 }
1055
1056 #[test]
1057 fn new_rejects_dotdot() {
1058 assert_eq!(
1059 *PathElementCS::new("..").unwrap_err().kind(),
1060 ErrorKind::ParentDirectoryMarker
1061 );
1062 }
1063
1064 #[test]
1065 fn new_rejects_slash() {
1066 assert_eq!(
1067 *PathElementCS::new("a/b").unwrap_err().kind(),
1068 ErrorKind::ContainsForwardSlash
1069 );
1070 }
1071
1072 #[test]
1073 fn new_rejects_null() {
1074 assert_eq!(
1075 *PathElementCS::new("\0").unwrap_err().kind(),
1076 ErrorKind::ContainsNullByte
1077 );
1078 }
1079
1080 #[test]
1081 fn new_rejects_unassigned() {
1082 assert_eq!(
1083 *PathElementCS::new("\u{0378}").unwrap_err().kind(),
1084 ErrorKind::ContainsUnassignedChar
1085 );
1086 }
1087
1088 #[test]
1091 fn path_element_eq_same_cs() {
1092 let a = PathElementCS::new("hello.txt").unwrap();
1093 let b = PathElementCS::new("hello.txt").unwrap();
1094 assert_eq!(a, b);
1095 }
1096
1097 #[test]
1098 fn path_element_eq_different_original_same_normalized_cs() {
1099 let a = PathElementCS::new(" hello.txt ").unwrap();
1100 let b = PathElementCS::new("hello.txt").unwrap();
1101 assert_ne!(a.original(), b.original());
1102 assert_eq!(a, b);
1103 }
1104
1105 #[test]
1106 fn path_element_ne_different_case_cs() {
1107 let a = PathElementCS::new("Hello.txt").unwrap();
1108 let b = PathElementCS::new("hello.txt").unwrap();
1109 assert_ne!(a, b);
1110 }
1111
1112 #[test]
1113 fn path_element_eq_different_case_ci() {
1114 let a = PathElementCI::new("Hello.txt").unwrap();
1115 let b = PathElementCI::new("hello.txt").unwrap();
1116 assert_eq!(a, b);
1117 }
1118
1119 #[test]
1120 fn path_element_eq_nfc_nfd_cs() {
1121 let a = PathElementCS::new("\u{00E9}.txt").unwrap();
1122 let b = PathElementCS::new("e\u{0301}.txt").unwrap();
1123 assert_eq!(a, b);
1124 }
1125
1126 #[test]
1127 fn path_element_eq_cross_lifetime() {
1128 let owned = PathElementCS::new("hello.txt").unwrap().into_owned();
1129 let input = "hello.txt";
1130 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1131 assert_eq!(owned, borrowed);
1132 assert_eq!(borrowed, owned);
1133 }
1134
1135 #[test]
1136 #[should_panic(expected = "different case sensitivity")]
1137 fn path_element_eq_panics_on_mixed_dynamic_case_sensitivity() {
1138 let a = PathElement::new("hello", CaseSensitive).unwrap();
1139 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1140 let _ = a == b;
1141 }
1142
1143 #[test]
1146 fn path_element_ord_alphabetical_cs() {
1147 let a = PathElementCS::new("apple").unwrap();
1148 let b = PathElementCS::new("banana").unwrap();
1149 assert!(a < b);
1150 assert!(b > a);
1151 }
1152
1153 #[test]
1154 fn path_element_ord_equal_cs() {
1155 let a = PathElementCS::new("hello").unwrap();
1156 let b = PathElementCS::new("hello").unwrap();
1157 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1158 }
1159
1160 #[test]
1161 fn path_element_ord_case_ci() {
1162 let a = PathElementCI::new("Apple").unwrap();
1163 let b = PathElementCI::new("apple").unwrap();
1164 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1165 }
1166
1167 #[test]
1168 fn path_element_partial_ord_cross_lifetime() {
1169 let owned = PathElementCS::new("apple").unwrap().into_owned();
1170 let input = "banana";
1171 let borrowed = PathElementCS::new(Cow::Borrowed(input)).unwrap();
1172 assert!(owned < borrowed);
1173 }
1174
1175 #[test]
1176 fn path_element_partial_ord_none_on_mixed_dynamic_case_sensitivity() {
1177 let a = PathElement::new("hello", CaseSensitive).unwrap();
1178 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1179 assert_eq!(a.partial_cmp(&b), None);
1180 }
1181
1182 #[test]
1183 fn path_element_ord_sortable() {
1184 let mut elems: Vec<_> = ["cherry", "apple", "banana"]
1185 .iter()
1186 .map(|s| PathElementCS::new(Cow::Borrowed(*s)).unwrap())
1187 .collect();
1188 elems.sort();
1189 let names: Vec<_> = elems.iter().map(PathElementCS::normalized).collect();
1190 assert_eq!(names, &["apple", "banana", "cherry"]);
1191 }
1192
1193 #[test]
1194 fn path_element_ord_ci_sortable() {
1195 let mut elems: Vec<_> = ["Cherry", "apple", "BANANA"]
1196 .iter()
1197 .map(|s| PathElementCI::new(Cow::Borrowed(*s)).unwrap())
1198 .collect();
1199 elems.sort();
1200 let names: Vec<_> = elems.iter().map(PathElementCI::normalized).collect();
1201 assert_eq!(names, &["apple", "banana", "cherry"]);
1202 }
1203
1204 #[test]
1207 fn from_cs_into_dynamic() {
1208 let pe = PathElementCS::new("hello").unwrap();
1209 let dyn_pe: PathElement<'_> = pe.into();
1210 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1211 assert_eq!(dyn_pe.normalized(), "hello");
1212 }
1213
1214 #[test]
1215 fn from_ci_into_dynamic() {
1216 let pe = PathElementCI::new("Hello").unwrap();
1217 let dyn_pe: PathElement<'_> = pe.into();
1218 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1219 assert_eq!(dyn_pe.normalized(), "hello");
1220 }
1221
1222 #[test]
1223 fn try_from_dynamic_to_cs() {
1224 let pe = PathElement::new("hello", CaseSensitive).unwrap();
1225 let cs_pe: PathElementCS<'_> = pe.try_into().unwrap();
1226 assert_eq!(cs_pe.normalized(), "hello");
1227 }
1228
1229 #[test]
1230 fn try_from_dynamic_to_cs_wrong_variant() {
1231 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1232 let err: PathElementCI<'_> = PathElementCS::try_from(pe).unwrap_err();
1233 assert_eq!(err.original(), "Hello");
1234 assert_eq!(err.normalized(), "hello");
1235 assert_eq!(err.os_compatible(), "Hello");
1236 }
1237
1238 #[test]
1239 fn try_from_dynamic_to_ci() {
1240 let pe = PathElement::new("Hello", CaseInsensitive).unwrap();
1241 let ci_pe: PathElementCI<'_> = pe.try_into().unwrap();
1242 assert_eq!(ci_pe.normalized(), "hello");
1243 }
1244
1245 #[test]
1248 fn dyn_new_cs() {
1249 let pe = PathElement::new_cs("Hello.txt").unwrap();
1250 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1251 assert_eq!(pe.normalized(), "Hello.txt");
1252 }
1253
1254 #[test]
1255 fn dyn_new_ci() {
1256 let pe = PathElement::new_ci("Hello.txt").unwrap();
1257 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1258 assert_eq!(pe.normalized(), "hello.txt");
1259 }
1260
1261 #[test]
1262 fn dyn_new_cs_matches_typed() {
1263 let dyn_pe = PathElement::new_cs("Hello.txt").unwrap();
1264 let cs_pe = PathElementCS::new("Hello.txt").unwrap();
1265 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1266 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1267 }
1268
1269 #[test]
1270 fn dyn_new_ci_matches_typed() {
1271 let dyn_pe = PathElement::new_ci("Hello.txt").unwrap();
1272 let ci_pe = PathElementCI::new("Hello.txt").unwrap();
1273 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1274 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1275 }
1276
1277 #[test]
1280 fn case_sensitivity_cs() {
1281 let pe = PathElementCS::new("hello").unwrap();
1282 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1283 }
1284
1285 #[test]
1286 fn case_sensitivity_ci() {
1287 let pe = PathElementCI::new("hello").unwrap();
1288 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1289 }
1290
1291 #[test]
1292 fn case_sensitivity_dyn() {
1293 let cs = PathElement::new("hello", CaseSensitive).unwrap();
1294 let ci = PathElement::new("hello", CaseInsensitive).unwrap();
1295 assert_eq!(cs.case_sensitivity(), CaseSensitivity::Sensitive);
1296 assert_eq!(ci.case_sensitivity(), CaseSensitivity::Insensitive);
1297 }
1298
1299 #[test]
1302 fn partial_ord_dyn_same_case_sensitivity() {
1303 let a = PathElement::new("apple", CaseSensitive).unwrap();
1304 let b = PathElement::new("banana", CaseSensitive).unwrap();
1305 assert!(a < b);
1306 }
1307
1308 #[test]
1309 fn partial_ord_dyn_none_on_mismatch() {
1310 let a = PathElement::new("hello", CaseSensitive).unwrap();
1311 let b = PathElement::new("hello", CaseInsensitive).unwrap();
1312 assert_eq!(a.partial_cmp(&b), None);
1313 }
1314
1315 #[test]
1318 fn try_from_dynamic_to_ci_wrong_variant() {
1319 let pe = PathElement::new("Hello", CaseSensitive).unwrap();
1320 let err: PathElementCS<'_> = PathElementCI::try_from(pe).unwrap_err();
1321 assert_eq!(err.original(), "Hello");
1322 assert_eq!(err.normalized(), "Hello");
1323 assert_eq!(err.os_compatible(), "Hello");
1324 }
1325
1326 #[test]
1329 fn into_owned_preserves_cs_case_sensitivity() {
1330 let pe = PathElementCS::new("hello").unwrap();
1331 let owned = pe.into_owned();
1332 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Sensitive);
1333 }
1334
1335 #[test]
1336 fn into_owned_preserves_dyn_case_sensitivity() {
1337 let pe = PathElement::new("hello", CaseInsensitive).unwrap();
1338 let owned = pe.into_owned();
1339 assert_eq!(owned.case_sensitivity(), CaseSensitivity::Insensitive);
1340 }
1341
1342 #[test]
1345 fn eq_cs_vs_dyn_same_case_sensitivity() {
1346 let cs = PathElementCS::new("hello").unwrap();
1347 let dyn_cs = PathElement::new_cs("hello").unwrap();
1348 assert_eq!(cs, dyn_cs);
1349 assert_eq!(dyn_cs, cs);
1350 }
1351
1352 #[test]
1353 fn eq_ci_vs_dyn_same_case_sensitivity() {
1354 let ci = PathElementCI::new("Hello").unwrap();
1355 let dyn_ci = PathElement::new_ci("hello").unwrap();
1356 assert_eq!(ci, dyn_ci);
1357 assert_eq!(dyn_ci, ci);
1358 }
1359
1360 #[test]
1361 #[should_panic(expected = "different case sensitivity")]
1362 fn eq_cs_vs_ci_panics() {
1363 let cs = PathElementCS::new("hello").unwrap();
1364 let ci = PathElementCI::new("hello").unwrap();
1365 let _ = cs == ci;
1366 }
1367
1368 #[test]
1369 #[should_panic(expected = "different case sensitivity")]
1370 fn eq_cs_vs_dyn_ci_panics() {
1371 let cs = PathElementCS::new("hello").unwrap();
1372 let dyn_ci = PathElement::new_ci("hello").unwrap();
1373 let _ = cs == dyn_ci;
1374 }
1375
1376 #[test]
1379 fn partial_ord_cs_vs_dyn_same_case_sensitivity() {
1380 let cs = PathElementCS::new("apple").unwrap();
1381 let dyn_cs = PathElement::new_cs("banana").unwrap();
1382 assert!(cs < dyn_cs);
1383 assert!(dyn_cs > cs);
1384 }
1385
1386 #[test]
1387 fn partial_ord_cs_vs_ci_none() {
1388 let cs = PathElementCS::new("hello").unwrap();
1389 let ci = PathElementCI::new("hello").unwrap();
1390 assert_eq!(cs.partial_cmp(&ci), None);
1391 assert_eq!(ci.partial_cmp(&cs), None);
1392 }
1393
1394 #[test]
1395 fn partial_ord_cs_vs_dyn_ci_none() {
1396 let cs = PathElementCS::new("hello").unwrap();
1397 let dyn_ci = PathElement::new_ci("hello").unwrap();
1398 assert_eq!(cs.partial_cmp(&dyn_ci), None);
1399 assert_eq!(dyn_ci.partial_cmp(&cs), None);
1400 }
1401
1402 #[test]
1405 fn from_bytes_cs_borrowed_matches_new() {
1406 let pe_bytes = PathElementCS::from_bytes(b"hello.txt" as &[u8]).unwrap();
1407 let pe_str = PathElementCS::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_ci_borrowed_matches_new() {
1415 let pe_bytes = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1416 let pe_str = PathElementCI::new("Hello.txt").unwrap();
1417 assert_eq!(pe_bytes.original(), pe_str.original());
1418 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1419 assert_eq!(pe_bytes.os_compatible(), pe_str.os_compatible());
1420 }
1421
1422 #[test]
1423 fn from_bytes_owned_matches_new() {
1424 let pe_bytes = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1425 let pe_str = PathElementCS::new("hello.txt").unwrap();
1426 assert_eq!(pe_bytes.original(), pe_str.original());
1427 assert_eq!(pe_bytes.normalized(), pe_str.normalized());
1428 }
1429
1430 #[test]
1431 fn from_bytes_borrowed_preserves_borrow() {
1432 let input: &[u8] = b"hello.txt";
1433 let pe = PathElementCS::from_bytes(input).unwrap();
1434 let orig = pe.into_original();
1435 assert!(matches!(orig, Cow::Borrowed(_)));
1436 }
1437
1438 #[test]
1439 fn from_bytes_owned_is_owned() {
1440 let pe = PathElementCS::from_bytes(b"hello.txt".to_vec()).unwrap();
1441 assert!(pe.is_owned());
1442 }
1443
1444 #[test]
1445 fn from_bytes_invalid_utf8_borrowed_rejected() {
1446 let input: &[u8] = &[0x68, 0x69, 0xFF]; let err = PathElementCS::from_bytes(input).unwrap_err();
1448 assert_eq!(*err.kind(), ErrorKind::InvalidUtf8);
1449 }
1450
1451 #[test]
1452 fn from_bytes_invalid_utf8_owned_rejected() {
1453 let input = vec![0x68, 0x69, 0xFF];
1454 let err = PathElementCS::from_bytes(input).unwrap_err();
1455 assert_eq!(*err.kind(), ErrorKind::InvalidUtf8);
1456 }
1457
1458 #[test]
1459 fn from_bytes_dynamic_case_sensitivity() {
1460 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseInsensitive).unwrap();
1461 assert_eq!(pe.normalized(), "hello.txt");
1462 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Insensitive);
1463 }
1464
1465 #[test]
1466 fn from_bytes_cs_matches_typed() {
1467 let input: &[u8] = b"Hello.txt";
1468 let dyn_pe = PathElement::from_bytes_cs(input).unwrap();
1469 let cs_pe = PathElementCS::from_bytes(input).unwrap();
1470 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1471 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1472 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1473 }
1474
1475 #[test]
1476 fn from_bytes_ci_matches_typed() {
1477 let input: &[u8] = b"Hello.txt";
1478 let dyn_pe = PathElement::from_bytes_ci(input).unwrap();
1479 let ci_pe = PathElementCI::from_bytes(input).unwrap();
1480 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1481 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1482 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1483 }
1484
1485 #[test]
1486 fn from_bytes_with_case_sensitivity_cs() {
1487 let pe = PathElementCS::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1488 assert_eq!(pe.normalized(), "Hello.txt");
1489 }
1490
1491 #[test]
1492 fn from_bytes_with_case_sensitivity_ci() {
1493 let pe = PathElementCI::from_bytes(b"Hello.txt" as &[u8]).unwrap();
1494 assert_eq!(pe.normalized(), "hello.txt");
1495 }
1496
1497 #[test]
1498 fn from_bytes_rejects_empty() {
1499 assert_eq!(
1500 *PathElementCS::from_bytes(b"" as &[u8]).unwrap_err().kind(),
1501 ErrorKind::Empty
1502 );
1503 }
1504
1505 #[test]
1506 fn from_bytes_rejects_dot() {
1507 assert_eq!(
1508 *PathElementCS::from_bytes(b"." as &[u8]).unwrap_err().kind(),
1509 ErrorKind::CurrentDirectoryMarker
1510 );
1511 }
1512
1513 #[test]
1514 fn from_bytes_rejects_dotdot() {
1515 assert_eq!(
1516 *PathElementCS::from_bytes(b".." as &[u8])
1517 .unwrap_err()
1518 .kind(),
1519 ErrorKind::ParentDirectoryMarker
1520 );
1521 }
1522
1523 #[test]
1524 fn from_bytes_rejects_slash() {
1525 assert_eq!(
1526 *PathElementCS::from_bytes(b"a/b" as &[u8])
1527 .unwrap_err()
1528 .kind(),
1529 ErrorKind::ContainsForwardSlash
1530 );
1531 }
1532
1533 #[test]
1534 fn from_bytes_rejects_null() {
1535 assert_eq!(
1536 *PathElementCS::from_bytes(b"\0" as &[u8])
1537 .unwrap_err()
1538 .kind(),
1539 ErrorKind::ContainsNullByte
1540 );
1541 }
1542
1543 #[test]
1544 fn from_bytes_rejects_unassigned() {
1545 assert_eq!(
1546 *PathElementCS::from_bytes("\u{0378}".as_bytes())
1547 .unwrap_err()
1548 .kind(),
1549 ErrorKind::ContainsUnassignedChar
1550 );
1551 }
1552
1553 #[test]
1554 fn from_bytes_dynamic_sensitive() {
1555 let pe = PathElement::from_bytes(b"Hello.txt" as &[u8], CaseSensitive).unwrap();
1556 assert_eq!(pe.normalized(), "Hello.txt");
1557 assert_eq!(pe.case_sensitivity(), CaseSensitivity::Sensitive);
1558 }
1559
1560 #[test]
1563 fn from_bytes_overlong_null() {
1564 let input: &[u8] = &[0x61, 0xC0, 0x80, 0x62];
1566 assert_eq!(
1567 *PathElementCS::from_bytes(input).unwrap_err().kind(),
1568 ErrorKind::InvalidUtf8
1569 );
1570 }
1571
1572 #[test]
1573 fn from_bytes_surrogate_bytes_rejected() {
1574 let input: &[u8] = &[0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80];
1575 assert_eq!(
1576 *PathElementCS::from_bytes(input).unwrap_err().kind(),
1577 ErrorKind::InvalidUtf8
1578 );
1579 }
1580
1581 #[test]
1582 fn from_bytes_lone_high_surrogate_rejected() {
1583 let input: &[u8] = &[0x61, 0xED, 0xA0, 0x80, 0x62];
1584 assert_eq!(
1585 *PathElementCS::from_bytes(input).unwrap_err().kind(),
1586 ErrorKind::InvalidUtf8
1587 );
1588 }
1589
1590 #[test]
1591 fn from_bytes_lone_low_surrogate_rejected() {
1592 let input: &[u8] = &[0x61, 0xED, 0xB0, 0x80, 0x62];
1593 assert_eq!(
1594 *PathElementCS::from_bytes(input).unwrap_err().kind(),
1595 ErrorKind::InvalidUtf8
1596 );
1597 }
1598
1599 #[test]
1600 fn from_bytes_overlong_null_only() {
1601 let input: &[u8] = &[0xC0, 0x80];
1602 assert_eq!(
1603 *PathElementCS::from_bytes(input).unwrap_err().kind(),
1604 ErrorKind::InvalidUtf8
1605 );
1606 }
1607
1608 #[test]
1609 fn from_bytes_invalid_byte_rejected() {
1610 let input: &[u8] = &[0x68, 0x69, 0xFF];
1611 assert_eq!(
1612 *PathElementCS::from_bytes(input).unwrap_err().kind(),
1613 ErrorKind::InvalidUtf8
1614 );
1615 }
1616
1617 #[test]
1620 fn os_compatible_supplementary_unchanged() {
1621 let pe = PathElementCS::new("file_😀.txt").unwrap();
1622 assert_eq!(pe.os_compatible(), "file_😀.txt");
1623 }
1624
1625 #[test]
1626 fn os_compatible_supplementary_roundtrip() {
1627 let pe = PathElementCS::new("file_😀.txt").unwrap();
1628 let pe2 = PathElementCS::new(pe.os_compatible()).unwrap();
1629 assert_eq!(pe.normalized(), pe2.normalized());
1630 }
1631
1632 #[test]
1633 fn os_compatible_multiple_supplementary() {
1634 let pe = PathElementCS::new("𐀀_𝄞_😀").unwrap();
1635 assert_eq!(pe.os_compatible(), "𐀀_𝄞_😀");
1636 }
1637}
1638
1639#[cfg(all(test, feature = "std"))]
1640mod os_str_tests {
1641 use std::borrow::Cow;
1642 use std::ffi::{OsStr, OsString};
1643
1644 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
1645 use wasm_bindgen_test::wasm_bindgen_test as test;
1646
1647 use crate::ErrorKind;
1648 use crate::case_sensitivity::{CaseInsensitive, CaseSensitivity};
1649 use crate::path_element::{PathElement, PathElementCI, PathElementCS};
1650
1651 #[test]
1652 fn from_os_str_borrowed_matches_new() {
1653 let input = OsStr::new("hello.txt");
1654 let from_os = PathElementCS::from_os_str(input).unwrap();
1655 let from_new = PathElementCS::new("hello.txt").unwrap();
1656 assert_eq!(from_os.original(), from_new.original());
1657 assert_eq!(from_os.normalized(), from_new.normalized());
1658 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1659 }
1660
1661 #[test]
1662 fn from_os_str_owned_matches_new() {
1663 let input = OsString::from("Hello.txt");
1664 let from_os = PathElementCI::from_os_str(input).unwrap();
1665 let from_new = PathElementCI::new("Hello.txt").unwrap();
1666 assert_eq!(from_os.original(), from_new.original());
1667 assert_eq!(from_os.normalized(), from_new.normalized());
1668 assert_eq!(from_os.os_compatible(), from_new.os_compatible());
1669 }
1670
1671 #[test]
1672 fn from_os_str_borrowed_preserves_borrow() {
1673 let input = OsStr::new("hello.txt");
1674 let pe = PathElementCS::from_os_str(input).unwrap();
1675 let orig = pe.into_original();
1676 assert!(matches!(orig, Cow::Borrowed(_)));
1677 }
1678
1679 #[test]
1680 fn from_os_str_rejects_empty() {
1681 assert_eq!(
1682 *PathElementCS::from_os_str(OsStr::new(""))
1683 .unwrap_err()
1684 .kind(),
1685 ErrorKind::Empty
1686 );
1687 }
1688
1689 #[test]
1690 fn from_os_str_rejects_dot() {
1691 assert_eq!(
1692 *PathElementCS::from_os_str(OsStr::new("."))
1693 .unwrap_err()
1694 .kind(),
1695 ErrorKind::CurrentDirectoryMarker
1696 );
1697 }
1698
1699 #[test]
1700 fn from_os_str_rejects_dotdot() {
1701 assert_eq!(
1702 *PathElementCS::from_os_str(OsStr::new(".."))
1703 .unwrap_err()
1704 .kind(),
1705 ErrorKind::ParentDirectoryMarker
1706 );
1707 }
1708
1709 #[test]
1710 fn from_os_str_rejects_slash() {
1711 assert_eq!(
1712 *PathElementCS::from_os_str(OsStr::new("a/b"))
1713 .unwrap_err()
1714 .kind(),
1715 ErrorKind::ContainsForwardSlash
1716 );
1717 }
1718
1719 #[test]
1720 fn from_os_str_rejects_null() {
1721 assert_eq!(
1722 *PathElementCS::from_os_str(OsStr::new("\0"))
1723 .unwrap_err()
1724 .kind(),
1725 ErrorKind::ContainsNullByte
1726 );
1727 }
1728
1729 #[test]
1730 fn from_os_str_rejects_unassigned() {
1731 assert_eq!(
1732 *PathElementCS::from_os_str(OsStr::new("\u{0378}"))
1733 .unwrap_err()
1734 .kind(),
1735 ErrorKind::ContainsUnassignedChar
1736 );
1737 }
1738
1739 #[cfg(unix)]
1740 #[test]
1741 fn from_os_str_invalid_utf8_borrowed_rejected() {
1742 use std::os::unix::ffi::OsStrExt;
1743 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]);
1744 assert_eq!(
1745 *PathElementCS::from_os_str(input).unwrap_err().kind(),
1746 ErrorKind::InvalidUtf8
1747 );
1748 }
1749
1750 #[cfg(unix)]
1751 #[test]
1752 fn from_os_str_invalid_utf8_owned_rejected() {
1753 use std::os::unix::ffi::OsStrExt;
1754 let input = OsStr::from_bytes(&[0x68, 0x69, 0xFF]).to_os_string();
1755 assert_eq!(
1756 *PathElementCS::from_os_str(input).unwrap_err().kind(),
1757 ErrorKind::InvalidUtf8
1758 );
1759 }
1760
1761 #[cfg(unix)]
1762 #[test]
1763 fn from_os_str_surrogate_bytes_rejected() {
1764 use std::os::unix::ffi::OsStrExt;
1765 let input = OsStr::from_bytes(&[0x68, 0xED, 0xA0, 0x80, 0x69]);
1767 assert_eq!(
1768 *PathElementCS::from_os_str(input).unwrap_err().kind(),
1769 ErrorKind::InvalidUtf8
1770 );
1771 }
1772
1773 #[cfg(windows)]
1774 #[test]
1775 fn from_os_str_invalid_utf8_borrowed_rejected() {
1776 use std::os::windows::ffi::OsStringExt;
1777 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1778 assert_eq!(
1779 *PathElementCS::from_os_str(input.as_os_str())
1780 .unwrap_err()
1781 .kind(),
1782 ErrorKind::InvalidUtf8
1783 );
1784 }
1785
1786 #[cfg(windows)]
1787 #[test]
1788 fn from_os_str_invalid_utf8_owned_rejected() {
1789 use std::os::windows::ffi::OsStringExt;
1790 let input = OsString::from_wide(&[0x68, 0xD800, 0x69]);
1791 assert_eq!(
1792 *PathElementCS::from_os_str(input).unwrap_err().kind(),
1793 ErrorKind::InvalidUtf8
1794 );
1795 }
1796
1797 #[test]
1800 fn os_str_returns_os_compatible() {
1801 let pe = PathElementCI::new("H\tllo").unwrap();
1802 assert_eq!(pe.os_str(), OsStr::new("H\u{2409}llo"));
1803 }
1804
1805 #[test]
1806 fn into_os_str_returns_os_compatible() {
1807 let pe = PathElementCI::new("H\tllo").unwrap();
1808 let result = pe.into_os_str();
1809 assert_eq!(result, OsStr::new("H\u{2409}llo"));
1810 }
1811
1812 #[test]
1813 fn into_os_str_borrows_when_no_transformation() {
1814 let input = OsStr::new("hello.txt");
1815 let pe = PathElementCS::from_os_str(input).unwrap();
1816 let result = pe.into_os_str();
1817 assert!(matches!(result, Cow::Borrowed(_)));
1818 assert_eq!(result, OsStr::new("hello.txt"));
1819 }
1820
1821 #[test]
1822 fn into_os_str_ci_borrows_when_already_folded() {
1823 let input = OsStr::new("hello.txt");
1824 let pe = PathElementCI::from_os_str(input).unwrap();
1825 let result = pe.into_os_str();
1826 assert!(matches!(result, Cow::Borrowed(_)));
1827 assert_eq!(result, OsStr::new("hello.txt"));
1828 }
1829
1830 #[test]
1833 fn into_os_str_owned_when_nfc_transforms() {
1834 let input = OsStr::new("e\u{0301}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1836 let result = pe.into_os_str();
1837 #[cfg(target_vendor = "apple")]
1838 assert!(matches!(result, Cow::Borrowed(_)));
1839 #[cfg(not(target_vendor = "apple"))]
1840 assert!(matches!(result, Cow::Owned(_)));
1841 }
1842
1843 #[test]
1845 fn into_os_str_owned_when_nfd_transforms() {
1846 let input = OsStr::new("\u{00E9}.txt"); let pe = PathElementCS::from_os_str(input).unwrap();
1848 let result = pe.into_os_str();
1849 #[cfg(target_vendor = "apple")]
1850 assert!(matches!(result, Cow::Owned(_)));
1851 #[cfg(not(target_vendor = "apple"))]
1852 assert!(matches!(result, Cow::Borrowed(_)));
1853 }
1854
1855 #[test]
1856 fn from_os_str_cs_matches_typed() {
1857 let input = OsStr::new("Hello.txt");
1858 let dyn_pe = PathElement::from_os_str_cs(input).unwrap();
1859 let cs_pe = PathElementCS::from_os_str(input).unwrap();
1860 assert_eq!(dyn_pe.normalized(), cs_pe.normalized());
1861 assert_eq!(dyn_pe.os_compatible(), cs_pe.os_compatible());
1862 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Sensitive);
1863 }
1864
1865 #[test]
1866 fn from_os_str_ci_matches_typed() {
1867 let input = OsStr::new("Hello.txt");
1868 let dyn_pe = PathElement::from_os_str_ci(input).unwrap();
1869 let ci_pe = PathElementCI::from_os_str(input).unwrap();
1870 assert_eq!(dyn_pe.normalized(), ci_pe.normalized());
1871 assert_eq!(dyn_pe.os_compatible(), ci_pe.os_compatible());
1872 assert_eq!(dyn_pe.case_sensitivity(), CaseSensitivity::Insensitive);
1873 }
1874
1875 #[test]
1876 fn from_os_str_dynamic_case_sensitivity() {
1877 let input = OsStr::new("Hello.txt");
1878 let pe = PathElement::from_os_str(input, CaseInsensitive).unwrap();
1879 assert_eq!(pe.normalized(), "hello.txt");
1880 }
1881}