1use std::{
2 borrow::Cow,
3 collections::TryReserveError,
4 ffi::{OsStr, OsString},
5 path::{Components, Path, PathBuf, PrefixComponent},
6 str::FromStr,
7};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use super::{
13 imp,
14 trivial::{cast_box_unchecked, cast_ref_unchecked, ConvertError, Error, Normpath, NormpathBuf},
15};
16
17macro_rules! delegate {
18 ($platform:ident => $expr:expr) => {{
19 #[cfg($platform)]
20 {
21 Some($expr)
22 }
23 #[cfg(not($platform))]
24 {
25 None
26 }
27 }};
28}
29
30impl Normpath {
31 #[cfg(any(unix, docsrs))]
35 #[cfg_attr(docsrs, doc(cfg(unix)))]
36 #[must_use]
37 #[inline]
38 pub fn unix_root() -> &'static Self {
39 unsafe { cast_ref_unchecked(Path::new("/")) }
41 }
42
43 #[must_use]
51 #[inline]
52 pub fn root() -> Option<&'static Self> {
53 delegate!(unix => Self::unix_root())
54 }
55
56 #[inline]
85 pub fn validate<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<&Self, Error> {
86 imp::validate(Path::new(path))
87 }
88
89 #[inline]
128 pub fn validate_canonical<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<&Self, Error> {
129 imp::validate_canonical(Path::new(path))
130 }
131
132 #[inline]
188 pub fn validate_parentless<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<&Self, Error> {
189 imp::validate_parentless(Path::new(path))
190 }
191
192 #[inline]
247 pub fn normalize<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<Cow<'_, Self>, Error> {
248 imp::normalize_new_cow(Path::new(path))
249 }
250
251 #[must_use]
271 #[inline]
272 pub unsafe fn new_unchecked<S: AsRef<OsStr> + ?Sized>(path: &S) -> &Self {
273 let path = Path::new(path.as_ref());
274 unsafe { cast_ref_unchecked(path) }
275 }
276
277 #[must_use]
279 #[inline]
280 #[allow(clippy::len_without_is_empty)]
281 pub fn len(&self) -> usize {
282 self.0.as_os_str().len()
283 }
284
285 #[must_use]
287 #[inline]
288 pub fn as_path(&self) -> &Path {
289 &self.0
290 }
291
292 #[must_use]
299 #[inline]
300 pub fn parent(&self) -> Option<&Self> {
301 let parent = self.0.parent()?;
302 Some(unsafe { cast_ref_unchecked(parent) })
304 }
305
306 #[cfg(any(windows, docsrs))]
327 #[cfg_attr(docsrs, doc(cfg(windows)))]
328 pub fn windows_split_components(&self) -> (PrefixComponent<'_>, Components<'_>) {
329 use std::path::Component::Prefix;
330
331 let mut components = self.0.components();
332 let Some(Prefix(prefix)) = components.next() else {
333 unreachable!()
334 };
335
336 (prefix, components)
337 }
338
339 #[cfg(any(windows, docsrs))]
341 #[cfg_attr(docsrs, doc(cfg(windows)))]
342 #[must_use]
343 #[inline]
344 pub fn windows_prefix(&self) -> PrefixComponent<'_> {
345 self.windows_split_components().0
346 }
347
348 #[must_use]
376 #[inline]
377 pub fn split_components(&self) -> Option<(PrefixComponent<'_>, Components<'_>)> {
378 delegate!(windows => self.windows_split_components())
379 }
380
381 #[must_use]
388 #[inline]
389 pub fn prefix(&self) -> Option<PrefixComponent<'_>> {
390 delegate!(windows => self.windows_prefix())
391 }
392
393 #[inline]
406 pub fn checked_join<P: AsRef<Path>>(&self, path: P) -> Result<NormpathBuf, Error> {
407 let mut buf = self.0.to_path_buf();
408 imp::push(&mut buf, path.as_ref())?;
409
410 Ok(NormpathBuf(buf))
411 }
412
413 #[must_use]
444 #[inline]
445 pub fn quick_strip_prefix<P: AsRef<Path>>(&self, base: P) -> Option<&Path> {
446 imp::strip(&self.0, base.as_ref())
447 }
448
449 #[must_use]
478 #[inline]
479 pub fn quick_starts_with<P: AsRef<Path>>(&self, base: P) -> bool {
480 imp::strip(&self.0, base.as_ref()).is_some()
481 }
482}
483
484impl NormpathBuf {
485 #[cfg(unix)]
487 #[cfg_attr(docsrs, doc(cfg(unix)))]
488 #[must_use]
489 #[inline]
490 pub fn root() -> Self {
491 Self(PathBuf::from("/"))
492 }
493
494 #[inline]
524 pub fn validate<P>(path: P) -> Result<Self, ConvertError<P>>
525 where
526 P: AsRef<OsStr> + Into<OsString>,
527 {
528 match imp::validate(Path::new(&path)) {
529 Ok(_) => Ok(Self(PathBuf::from(path.into()))),
530 Err(e) => Err(ConvertError::new(e, path)),
531 }
532 }
533
534 #[inline]
587 pub fn normalize(mut path: PathBuf) -> Result<Self, ConvertError<PathBuf>> {
588 match imp::normalize(&mut path) {
589 Ok(_) => Ok(Self(path)),
590 Err(e) => Err(ConvertError::new(e, path)),
591 }
592 }
593
594 #[must_use]
614 #[inline]
615 pub unsafe fn new_unchecked<S: Into<OsString>>(path: S) -> Self {
616 Self(PathBuf::from(path.into()))
617 }
618
619 #[inline]
634 pub fn push<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
635 imp::push(&mut self.0, path.as_ref())
636 }
637
638 #[inline]
644 pub fn pop(&mut self) -> bool {
645 self.0.pop()
646 }
647
648 #[must_use]
650 #[inline]
651 pub fn as_normpath(&self) -> &Normpath {
652 self.as_ref()
653 }
654
655 #[must_use]
657 #[inline]
658 pub fn into_boxed_path(self) -> Box<Normpath> {
659 let value = self.0.into_boxed_path();
660 unsafe { cast_box_unchecked(value) }
662 }
663
664 #[must_use]
667 #[inline]
668 pub fn into_path_buf(self) -> PathBuf {
669 self.0
670 }
671
672 #[must_use]
674 #[inline]
675 pub fn into_os_string(self) -> OsString {
676 self.0.into_os_string()
677 }
678
679 #[inline]
682 pub fn capacity(&self) -> usize {
683 self.0.capacity()
684 }
685
686 #[inline]
689 pub fn reserve(&mut self, additional: usize) {
690 self.0.reserve(additional)
691 }
692
693 #[inline]
696 pub fn reserve_exact(&mut self, additional: usize) {
697 self.0.reserve_exact(additional)
698 }
699
700 #[inline]
703 pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
704 self.0.try_reserve(additional)
705 }
706
707 #[inline]
710 pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
711 self.0.try_reserve_exact(additional)
712 }
713
714 #[inline]
717 pub fn shrink_to_fit(&mut self) {
718 self.0.shrink_to_fit()
719 }
720
721 #[inline]
724 pub fn shrink_to(&mut self, min_capacity: usize) {
725 self.0.shrink_to(min_capacity)
726 }
727}
728
729macro_rules! impl_try_from {
730 (owned over <$($life:lifetime)?> $t:ty) => {
731 impl<$($life)?> TryFrom<$t> for NormpathBuf {
732 type Error = ConvertError<$t>;
733
734 fn try_from(value: $t) -> Result<Self, Self::Error> {
740 imp::normalize_new_buf(value)
741 }
742 }
743 };
744 (&$life:lifetime $t:ty) => {
745 impl<$life> TryFrom<&$life $t> for NormpathBuf {
746 type Error = ConvertError<&$life $t>;
747
748 fn try_from(value: &$life $t) -> Result<Self, Self::Error> {
751 imp::normalize_new_buf(PathBuf::from(value))
752 .map_err(|e| ConvertError::new(e.error, value))
753 }
754 }
755 };
756}
757
758impl_try_from!(owned over <> String);
759impl_try_from!(owned over <> OsString);
760impl_try_from!(owned over <> PathBuf);
761impl_try_from!(owned over <> Box<Path>);
762impl_try_from!(owned over <'a> Cow<'a, Path>);
763
764impl_try_from!(&'a str);
765impl_try_from!(&'a String);
766impl_try_from!(&'a OsStr);
767impl_try_from!(&'a OsString);
768impl_try_from!(&'a Path);
769impl_try_from!(&'a PathBuf);
770
771#[cfg(feature = "serde")]
772#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
773impl Serialize for Normpath {
774 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
775 self.as_path().serialize(serializer)
776 }
777}
778
779#[cfg(feature = "serde")]
780#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
781impl Serialize for NormpathBuf {
782 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
783 self.as_path().serialize(serializer)
784 }
785}
786
787impl FromStr for NormpathBuf {
788 type Err = ConvertError<String>;
789
790 fn from_str(s: &str) -> Result<Self, Self::Err> {
791 imp::normalize_new_buf(s.to_string())
792 }
793}
794
795impl FromStr for Box<Normpath> {
796 type Err = ConvertError<String>;
797
798 fn from_str(s: &str) -> Result<Self, Self::Err> {
799 imp::normalize_new_buf(s.to_string()).map(|p| p.into_boxed_path())
800 }
801}
802
803#[cfg(feature = "serde")]
804#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
805impl<'a, 'de: 'a> Deserialize<'de> for &'a Normpath {
806 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
807 let path = <&Path>::deserialize(deserializer)?;
808 imp::validate(path).map_err(serde::de::Error::custom)
809 }
810}
811
812#[cfg(feature = "serde")]
813#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
814impl<'de> Deserialize<'de> for NormpathBuf {
815 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
816 let path = PathBuf::deserialize(deserializer)?;
817 imp::normalize_new_buf(path).map_err(serde::de::Error::custom)
818 }
819}
820
821#[cfg(feature = "serde")]
822#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
823impl<'de> Deserialize<'de> for Box<Normpath> {
824 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
825 let path = PathBuf::deserialize(deserializer)?;
826 imp::normalize_new_buf(path)
827 .map(|p| p.into_boxed_path())
828 .map_err(serde::de::Error::custom)
829 }
830}
831
832#[must_use]
872#[inline]
873pub fn canonicalize_lexically(path: PathBuf) -> PathBuf {
874 NormpathBuf::normalize(path)
875 .map(|it| it.into_path_buf())
876 .unwrap_or_else(|e| e.value)
877}
878
879#[cfg(test)]
880mod tests {
881 use std::{iter, ops::RangeBounds, path::Component};
882
883 use fastrand::Rng;
884
885 use crate::draw;
886
887 use super::{ConvertError as Ec, Error as E, *};
888
889 const MIN_ABS: usize = if cfg!(unix) { 1 } else { 3 };
890
891 #[cfg(unix)]
892 fn make_root() -> NormpathBuf {
893 NormpathBuf::root()
894 }
895
896 #[cfg(windows)]
897 fn make_root() -> NormpathBuf {
898 let letter = char::from(fastrand::u8(b'A'..=b'Z'));
899 NormpathBuf::try_from(format!(r"{letter}:\")).unwrap()
900 }
901
902 fn make_paths(
903 bound: impl RangeBounds<usize> + Clone,
904 mut draw: impl FnMut(usize) -> String,
905 ) -> impl Iterator<Item = String> {
906 let mut rng = Rng::new();
907 iter::from_fn(move || Some(draw(rng.usize(bound.clone()))))
908 }
909
910 #[cfg(unix)]
911 fn to_canonical(path: &Path) -> PathBuf {
912 path.components()
913 .filter(|c| !matches!(c, Component::CurDir))
914 .collect()
915 }
916
917 #[cfg(windows)]
918 fn to_canonical(path: &Path) -> PathBuf {
919 use std::path::Prefix;
920
921 let mut out = PathBuf::with_capacity(path.as_os_str().len());
922 for component in path.components() {
923 use {std::path::Prefix::*, Component::*};
924 match component {
925 Prefix(component) => match component.kind() {
926 Disk(c) => {
927 let letter = char::from(c.to_ascii_uppercase());
928 out.push(format!("{letter}:"));
929 }
930 DeviceNS(name) => {
931 let name = name.to_str().unwrap();
932 out.push(format!(r"\\.\{name}"));
933 }
934 UNC(server, share) => {
935 let server = server.to_str().unwrap();
936 let share = share.to_str().unwrap();
937 out.push(format!(r"\\{server}\{share}"));
938 }
939 _ => return path.into(),
940 },
941 RootDir => out.push("\\"),
942 CurDir => continue,
943 ParentDir if out.file_name().is_some() => assert!(out.pop()),
944 ParentDir => out.push(".."),
945 Normal(name) => out.push(name),
946 }
947 }
948
949 let mut components = out.components();
950 let first = components.next();
951 let tail = components.as_path();
952
953 let is_phony = match first {
954 Some(Component::Prefix(prefix)) => {
955 matches!(prefix.kind(), Prefix::DeviceNS(_) | Prefix::UNC(_, _))
956 && tail.as_os_str() == "\\"
957 }
958 _ => false,
959 };
960
961 if is_phony {
962 let mut bytes = std::mem::take(&mut out)
963 .into_os_string()
964 .into_encoded_bytes();
965
966 bytes.pop();
967 out = unsafe { OsString::from_encoded_bytes_unchecked(bytes) }.into();
968 }
969
970 out
971 }
972
973 fn is_canonical(path: &Path) -> bool {
974 path.as_os_str() == to_canonical(path).as_os_str()
975 }
976
977 #[cfg(unix)]
978 fn is_parentless(path: &Path) -> bool {
979 path.components()
980 .all(|c| !matches!(c, Component::ParentDir))
981 }
982
983 #[cfg(windows)]
984 fn is_parentless(path: &Path) -> bool {
985 use Component::*;
986
987 let mut components = path.components();
988 let start = match components.next() {
989 Some(Prefix(it)) if it.kind().is_verbatim() => return true,
990 Some(ParentDir) => return false,
991 Some(Normal(_)) => 1u32,
992 Some(_) => 0u32,
993 _ => return true,
994 };
995
996 path.components()
997 .map(|c| match c {
998 ParentDir => -1,
999 Normal(_) => 1,
1000 _ => 0,
1001 })
1002 .try_fold(start, |acc, step| acc.checked_add_signed(step))
1003 .is_some()
1004 }
1005
1006 fn is_normalized(path: &Path) -> bool {
1007 path.is_absolute() && is_canonical(path) && is_parentless(path)
1008 }
1009
1010 fn into_source<T>(error: Ec<T>) -> E {
1011 error.error
1012 }
1013
1014 #[cfg(unix)]
1015 #[test]
1016 pub fn root() {
1017 Normpath::validate("/").unwrap();
1018 NormpathBuf::try_from("/").unwrap();
1019 }
1020
1021 #[cfg(windows)]
1022 #[test]
1023 pub fn root() {
1024 let paths = ('A'..='Z').map(|c| format!(r"{c}:\"));
1025 for path in paths {
1026 Normpath::validate(&path).unwrap();
1027 NormpathBuf::try_from(path).unwrap();
1028 }
1029 }
1030
1031 #[test]
1032 pub fn empty() {
1033 assert_eq!(Normpath::validate(""), Err(E::NotAbsolute));
1034 assert_eq!(
1035 NormpathBuf::try_from("").map_err(into_source),
1036 Err(E::NotAbsolute),
1037 );
1038 }
1039
1040 fn test_normalize(path: &str) -> OsString {
1041 let in_place = NormpathBuf::normalize(path.into()).map(|p| p.into_os_string());
1042 let new = Normpath::normalize(path).map(|p| p.into_owned().into_os_string());
1043 match (in_place, new) {
1044 (Ok(in_place), Ok(new)) => {
1045 assert_eq!(in_place, new, "inconsistent on {:?}", path);
1046 in_place
1047 }
1048 (Err(ec), Err(_)) => ec.value.into(),
1049 (a, b) => panic!(
1050 "inconsistent on {:?}: in-place = {:?}, new = {:?}",
1051 path, a, b
1052 ),
1053 }
1054 }
1055
1056 #[cfg(unix)]
1057 #[test]
1058 pub fn example_normalize() {
1059 assert_eq!(test_normalize("/foo/bar"), "/foo/bar");
1060 assert_eq!(test_normalize("//foo/./..//bar//"), "/foo/../bar");
1061
1062 assert_eq!(test_normalize("foo/bar"), "foo/bar");
1063 assert_eq!(test_normalize(".//foo/.//bar/..//"), "foo/bar/..");
1064 }
1065
1066 #[cfg(windows)]
1067 #[test]
1068 pub fn example_normalize() {
1069 assert_eq!(test_normalize(r"C:\foo\bar"), r"C:\foo\bar");
1070 assert_eq!(test_normalize(r"c:/foo/.."), r"C:\");
1071 assert_eq!(test_normalize(r"c:\/foo\..\./../bar/.."), r"C:\..");
1072
1073 assert_eq!(test_normalize(r"foo\bar"), r"foo\bar");
1074 assert_eq!(test_normalize(r".\/foo\./\../"), r"");
1075 assert_eq!(test_normalize(r".\/foo/..\bar/../..//"), r"..");
1076
1077 assert_eq!(test_normalize(r"\/.\dev\foo"), r"\\.\dev\foo");
1078 assert_eq!(test_normalize(r"\\./dev"), r"\\.\dev");
1079 assert_eq!(test_normalize(r"//./dev/foo\/bar\../"), r"\\.\dev\foo");
1080
1081 assert_eq!(test_normalize(r"\/s\s\foo\bar"), r"\\s\s\foo\bar");
1082 assert_eq!(test_normalize(r"\\s/s\foo/\.\..\bar\/"), r"\\s\s\bar");
1083 assert_eq!(test_normalize(r"//s\s/foo\../\./../\bar/.."), r"\\s\s\..");
1084
1085 assert_eq!(test_normalize(r"C:foo\bar"), r"C:foo\bar");
1086 assert_eq!(test_normalize(r"c:.\\foo\../\bar//"), r"C:bar");
1087 assert_eq!(test_normalize(r"c:.\foo\../bar/.."), r"C:");
1088
1089 assert_eq!(
1091 test_normalize(r"\\?\C:\foo/..\/bar/."),
1092 r"\\?\C:\foo/..\/bar/."
1093 );
1094 assert_eq!(
1095 test_normalize(r"\\?\UNC\s\s\foo\./..\bar/"),
1096 r"\\?\UNC\s\s\foo\./..\bar/"
1097 );
1098 }
1099
1100 #[test]
1101 pub fn normalize() {
1102 let paths = make_paths(1..64, draw::common);
1103 for path in paths.take(1024) {
1104 let reference = to_canonical(Path::new(&path));
1105 let ours = test_normalize(&path);
1106
1107 assert_eq!(reference.as_os_str(), ours, "original: {path:?}");
1108 }
1109 }
1110
1111 #[cfg(unix)]
1112 fn test_push(subject: &mut NormpathBuf, reference: &mut PathBuf, component: &str) {
1113 if is_parentless(component.as_ref()) {
1114 reference.push(component);
1115 subject.push(component).unwrap();
1116
1117 assert_eq!(subject.as_path(), reference.as_path());
1118 } else {
1119 let copy = subject.clone();
1120 let error = subject.push(component).unwrap_err();
1121 assert_eq!(error, E::ContainsParent);
1122 assert_eq!(*subject, copy);
1123 }
1124 }
1125
1126 #[cfg(windows)]
1127 fn test_push(subject: &mut NormpathBuf, reference: &mut PathBuf, component: &str) {
1128 let peek = reference.join(component);
1129 if peek.is_absolute() && is_parentless(&peek) {
1130 *reference = to_canonical(&peek);
1131 subject.push(component).unwrap();
1132
1133 assert_eq!(subject.as_path(), reference.as_path());
1134 } else {
1135 let copy = subject.clone();
1136 let error = subject.push(component).unwrap_err();
1137 if !peek.is_absolute() {
1138 assert_eq!(error, E::NotAbsolute);
1139 } else {
1140 assert_eq!(error, E::ContainsParent);
1141 }
1142 assert_eq!(*subject, copy);
1143 }
1144 }
1145
1146 #[test]
1147 pub fn push_relative() {
1148 for _ in 0..128 {
1149 let components = make_paths(1..16, draw::relative);
1150
1151 let mut path = make_root();
1152 let mut twin = path.clone().into_path_buf();
1153 for component in components.take(64) {
1154 test_push(&mut path, &mut twin, &component);
1155 }
1156 }
1157 }
1158
1159 #[test]
1160 pub fn push_absolute() {
1161 for _ in 0..128 {
1162 let components = make_paths(MIN_ABS..32, draw::absolute);
1163
1164 let mut path = make_root();
1165 let mut twin = path.clone().into_path_buf();
1166 for component in components.take(32) {
1167 test_push(&mut path, &mut twin, &component);
1168 }
1169 }
1170 }
1171
1172 #[cfg(windows)]
1173 #[test]
1174 pub fn push_partial() {
1175 for path in make_paths(MIN_ABS..64, draw::normal).take(128) {
1176 let roots = make_paths(1..32, draw::root_only);
1177
1178 let mut path = NormpathBuf::try_from(path).unwrap();
1179 let mut twin = path.clone().into_path_buf();
1180 for root in roots.take(32) {
1181 test_push(&mut path, &mut twin, &root);
1182 }
1183 }
1184
1185 for path in make_paths(MIN_ABS..64, draw::normal).take(64) {
1186 let prefixes = iter::from_fn(|| Some(draw::disk_only()));
1187
1188 let mut path = NormpathBuf::try_from(path).unwrap();
1189 let mut twin = path.clone().into_path_buf();
1190 for prefix in prefixes.take(16) {
1191 test_push(&mut path, &mut twin, &prefix);
1192 }
1193 }
1194 }
1195
1196 #[cfg(unix)]
1197 fn make_normal_paths() -> impl Iterator<Item = String> {
1198 make_paths(MIN_ABS..64, draw::normal).take(256)
1199 }
1200
1201 #[cfg(windows)]
1202 fn make_normal_paths() -> impl Iterator<Item = String> {
1203 let genuine = make_paths(MIN_ABS..64, draw::normal);
1204 let verbatim = make_paths(7..64, draw::verbatim);
1205 genuine.take(224).chain(verbatim.take(32))
1206 }
1207
1208 fn test_normal(path: &str) {
1209 let ref_validated = Normpath::validate(path).unwrap();
1210 assert!(is_normalized(ref_validated.as_path()));
1211 assert_eq!(ref_validated, Path::new(path));
1212
1213 let ref_validated_c = Normpath::validate_canonical(path).unwrap();
1214 assert_eq!(ref_validated_c, ref_validated);
1215
1216 let ref_validated_p = Normpath::validate_parentless(path).unwrap();
1217 assert_eq!(ref_validated_p, ref_validated);
1218
1219 let ref_normalized = Normpath::normalize(path).unwrap();
1220 assert_eq!(&*ref_normalized, ref_validated);
1221
1222 let buf_validated = NormpathBuf::validate(path).unwrap();
1223 assert_eq!(&buf_validated, ref_validated);
1224
1225 let buf_normalized = NormpathBuf::normalize(PathBuf::from(path)).unwrap();
1226 assert_eq!(&buf_normalized, ref_validated);
1227
1228 let buf_converted = NormpathBuf::try_from(path).unwrap();
1229 assert_eq!(&buf_converted, ref_validated);
1230 }
1231
1232 #[cfg(unix)]
1233 #[test]
1234 pub fn examples_normal() {
1235 test_normal("/");
1236 test_normal("/foo/bar");
1237 }
1238
1239 #[cfg(windows)]
1240 #[test]
1241 pub fn examples_normal() {
1242 test_normal(r"C:\");
1243 test_normal(r"C:\foo\bar");
1244
1245 test_normal(r"\\.\dev");
1246 test_normal(r"\\.\dev\foo\bar");
1247 test_normal(r"\\s\s");
1248 test_normal(r"\\s\s\foo\bar");
1249
1250 test_normal(r"\\?\C:.\../foo\bar//");
1251 test_normal(r"\\?\UNC\s\s/foo\/.\..\bar\/");
1252 }
1253
1254 #[test]
1255 pub fn normal() {
1256 for path in make_normal_paths() {
1257 test_normal(&path);
1258 }
1259 }
1260
1261 fn test_err_single(path: &str, error: E) {
1262 assert_eq!(Normpath::validate(&path), Err(error));
1263 assert_eq!(Normpath::validate_canonical(&path), Err(error));
1264 assert_eq!(Normpath::validate_parentless(&path), Err(error));
1265
1266 assert_eq!(
1267 NormpathBuf::validate(&path).map_err(into_source),
1268 Err(error),
1269 );
1270 assert_eq!(
1271 NormpathBuf::normalize(PathBuf::from(&path)).map_err(into_source),
1272 Err(error),
1273 );
1274
1275 let conv_err = NormpathBuf::try_from(path).unwrap_err();
1276 assert_eq!(conv_err.error, error);
1277 assert_eq!(conv_err.value, path);
1278 }
1279
1280 fn test_err_noncanonical(path: &str, canonical: impl AsRef<Path>) {
1281 assert_eq!(Normpath::validate(&path), Err(E::NotCanonical));
1282 assert_eq!(Normpath::validate_canonical(&path), Err(E::NotCanonical));
1283 assert_eq!(Normpath::validate_parentless(&path), Err(E::NotCanonical));
1284
1285 assert_eq!(
1286 NormpathBuf::validate(&path).map_err(into_source),
1287 Err(E::NotCanonical),
1288 );
1289
1290 let ref_normalized = Normpath::normalize(&path).unwrap();
1291 assert_eq!(&*ref_normalized, canonical.as_ref());
1292
1293 let buf_normalized = NormpathBuf::normalize(PathBuf::from(&path)).unwrap();
1294 assert_eq!(&buf_normalized, canonical.as_ref());
1295
1296 let buf_converted = NormpathBuf::try_from(path.to_string()).unwrap();
1297 assert_eq!(&buf_converted, canonical.as_ref());
1298 }
1299
1300 #[cfg(unix)]
1301 #[test]
1302 pub fn examples_err_single_relative() {
1303 test_err_single("", E::NotAbsolute);
1304 test_err_single("foo/bar", E::NotAbsolute);
1305 }
1306
1307 #[cfg(windows)]
1308 #[test]
1309 pub fn examples_err_single_relative() {
1310 test_err_single("", E::NotAbsolute);
1311 test_err_single(r"foo\bar", E::NotAbsolute);
1312
1313 test_err_single(r"\", E::NotAbsolute);
1314 test_err_single(r"\foo\bar", E::NotAbsolute);
1315
1316 test_err_single(r"C:", E::NotAbsolute);
1317 test_err_single(r"C:foo\bar", E::NotAbsolute);
1318 }
1319
1320 #[test]
1321 pub fn err_single_relative() {
1322 let paths = make_paths(1..64, draw::relative)
1323 .filter(|p| is_parentless(p.as_ref()) && is_canonical(p.as_ref()));
1324
1325 for path in paths.take(256) {
1326 test_err_single(&path, E::NotAbsolute);
1327 }
1328 }
1329
1330 #[cfg(unix)]
1331 #[test]
1332 pub fn examples_err_single_parent() {
1333 test_err_single("/..", E::ContainsParent);
1334 test_err_single("/foo/../bar", E::ContainsParent);
1335 }
1336
1337 #[cfg(windows)]
1338 #[test]
1339 pub fn examples_err_single_parent() {
1340 test_err_single(r"C:\..", E::ContainsParent);
1341
1342 test_err_single(r"\\.\dev\..", E::ContainsParent);
1343 test_err_single(r"\\s\s\..", E::ContainsParent);
1344 }
1345
1346 #[test]
1347 pub fn err_single_parent() {
1348 let paths = make_paths(MIN_ABS..64, draw::absolute)
1349 .filter(|p| !is_parentless(Path::new(p)) && is_canonical(Path::new(p)));
1350
1351 for path in paths.take(256) {
1352 test_err_single(&path, E::ContainsParent);
1353 }
1354 }
1355
1356 #[cfg(unix)]
1357 #[test]
1358 pub fn examples_err_single_noncanonical() {
1359 test_err_noncanonical("//", "/");
1360 test_err_noncanonical("/.", "/");
1361 test_err_noncanonical("/foo//bar", "/foo/bar");
1362 test_err_noncanonical("/foo/./bar", "/foo/bar");
1363 test_err_noncanonical("/foo/bar/", "/foo/bar");
1364 test_err_noncanonical("/.../..../", "/.../....");
1365 }
1366
1367 #[cfg(windows)]
1368 #[test]
1369 pub fn examples_err_single_noncanonical() {
1370 test_err_noncanonical(r"c:\", r"C:\");
1371 test_err_noncanonical(r"C:\\", r"C:\");
1372 test_err_noncanonical(r"C:\.", r"C:\");
1373 test_err_noncanonical(r"C:\foo\..", r"C:\");
1374 test_err_noncanonical(r"C:/foo/bar", r"C:\foo\bar");
1375 test_err_noncanonical(r"C:\foo\\bar", r"C:\foo\bar");
1376 test_err_noncanonical(r"C:\foo\.\bar", r"C:\foo\bar");
1377 test_err_noncanonical(r"C:\foo\bar\", r"C:\foo\bar");
1378 test_err_noncanonical(r"C:/.../....", r"C:\...\....");
1379
1380 test_err_noncanonical(r"\\.\dev\\", r"\\.\dev\");
1381 test_err_noncanonical(r"\\.\dev\.", r"\\.\dev\");
1382 test_err_noncanonical(r"\\s\s\\", r"\\s\s\");
1383 test_err_noncanonical(r"\\s\s\.", r"\\s\s\");
1384 }
1385
1386 #[test]
1387 pub fn err_single_noncanonical() {
1388 let paths = make_paths(MIN_ABS..64, draw::absolute)
1389 .filter(|p| !is_canonical(Path::new(p)) && is_parentless(Path::new(p)));
1390
1391 for path in paths.take(256) {
1392 test_err_noncanonical(&path, to_canonical(path.as_ref()));
1393 }
1394 }
1395
1396 fn test_err_preference(path: &str) {
1397 assert_eq!(Normpath::validate_canonical(path), Err(E::NotCanonical));
1398 assert_eq!(Normpath::validate_parentless(path), Err(E::ContainsParent));
1399 }
1400
1401 #[cfg(unix)]
1402 #[test]
1403 pub fn examples_err_preference() {
1404 test_err_preference("/foo/../bar/");
1405 test_err_preference("//foo/../bar");
1406 }
1407
1408 #[cfg(windows)]
1409 #[test]
1410 pub fn examples_err_preference() {
1411 test_err_preference(r"C:\..\foo\bar\");
1412 test_err_preference(r"C:\\foo\..\..\bar");
1413 test_err_preference(r"C:\foo\..\..");
1414
1415 test_err_preference(r"\\.\dev\..\foo\bar\");
1416 test_err_preference(r"\\.\dev\\foo\..\..\bar");
1417 test_err_preference(r"\\s\s\..\foo\bar\");
1418 test_err_preference(r"\\s\s\\foo\..\..\bar");
1419 }
1420
1421 #[test]
1422 pub fn err_preference() {
1423 let paths = make_paths(MIN_ABS..64, draw::absolute)
1424 .filter(|p| !is_canonical(Path::new(p)) && !is_parentless(Path::new(p)));
1425
1426 for path in paths.take(256) {
1427 test_err_preference(&path);
1428 }
1429 }
1430}