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: impl AsRef<Path>) -> PathBuf {
912 let collected = path
913 .as_ref()
914 .components()
915 .filter(|c| !matches!(c, Component::CurDir))
916 .collect::<PathBuf>();
917
918 if collected.as_os_str().is_empty() {
919 PathBuf::from(".")
920 } else {
921 collected
922 }
923 }
924
925 #[cfg(windows)]
926 fn to_canonical(path: impl AsRef<Path>) -> PathBuf {
927 use std::path::Prefix;
928 let path = path.as_ref();
929
930 let mut out = PathBuf::with_capacity(path.as_os_str().len());
931 for component in path.components() {
932 use {std::path::Prefix::*, Component::*};
933 match component {
934 Prefix(component) => match component.kind() {
935 Disk(c) => {
936 let letter = char::from(c.to_ascii_uppercase());
937 out.push(format!("{letter}:"));
938 }
939 DeviceNS(name) => {
940 let name = name.to_str().unwrap();
941 out.push(format!(r"\\.\{name}"));
942 }
943 UNC(server, share) => {
944 let server = server.to_str().unwrap();
945 let share = share.to_str().unwrap();
946 out.push(format!(r"\\{server}\{share}"));
947 }
948 _ => return path.into(),
949 },
950 RootDir => out.push("\\"),
951 CurDir => continue,
952 ParentDir if out.file_name().is_some() => assert!(out.pop()),
953 ParentDir => out.push(".."),
954 Normal(name) => out.push(name),
955 }
956 }
957
958 let mut components = out.components();
959 let first = components.next();
960 let tail = components.as_path();
961
962 let is_phony = match first {
963 Some(Component::Prefix(prefix)) => {
964 matches!(prefix.kind(), Prefix::DeviceNS(_) | Prefix::UNC(_, _))
965 && tail.as_os_str() == "\\"
966 }
967 _ => false,
968 };
969 let is_blank = match first {
970 Some(Component::Prefix(prefix)) => {
971 matches!(prefix.kind(), Prefix::Disk(_)) && tail.as_os_str().is_empty()
972 }
973 Some(_) => false,
974 None => true,
975 };
976 assert!(!is_phony || !is_blank);
977
978 if is_phony {
979 let mut bytes = std::mem::take(&mut out)
980 .into_os_string()
981 .into_encoded_bytes();
982
983 bytes.pop();
984 out = unsafe { OsString::from_encoded_bytes_unchecked(bytes) }.into();
985 } else if is_blank {
986 out.push(".");
987 }
988
989 out
990 }
991
992 fn is_canonical(path: impl AsRef<Path>) -> bool {
993 let path = path.as_ref();
994 path.as_os_str() == to_canonical(path).as_os_str()
995 }
996
997 #[cfg(unix)]
998 fn is_parentless(path: impl AsRef<Path>) -> bool {
999 path.as_ref()
1000 .components()
1001 .all(|c| !matches!(c, Component::ParentDir))
1002 }
1003
1004 #[cfg(windows)]
1005 fn is_parentless(path: impl AsRef<Path>) -> bool {
1006 use Component::*;
1007 let path = path.as_ref();
1008
1009 let mut components = path.components();
1010 let start = match components.next() {
1011 Some(Prefix(it)) if it.kind().is_verbatim() => return true,
1012 Some(ParentDir) => return false,
1013 Some(Normal(_)) => 1u32,
1014 Some(_) => 0u32,
1015 _ => return true,
1016 };
1017
1018 path.components()
1019 .map(|c| match c {
1020 ParentDir => -1,
1021 Normal(_) => 1,
1022 _ => 0,
1023 })
1024 .try_fold(start, |acc, step| acc.checked_add_signed(step))
1025 .is_some()
1026 }
1027
1028 fn is_normalized(path: impl AsRef<Path>) -> bool {
1029 let path = path.as_ref();
1030 path.is_absolute() && is_canonical(path) && is_parentless(path)
1031 }
1032
1033 fn into_source<T>(error: Ec<T>) -> E {
1034 error.error
1035 }
1036
1037 #[cfg(unix)]
1038 #[test]
1039 pub fn root() {
1040 Normpath::validate("/").unwrap();
1041 NormpathBuf::try_from("/").unwrap();
1042 }
1043
1044 #[cfg(windows)]
1045 #[test]
1046 pub fn root() {
1047 let paths = ('A'..='Z').map(|c| format!(r"{c}:\"));
1048 for path in paths {
1049 Normpath::validate(&path).unwrap();
1050 NormpathBuf::try_from(path).unwrap();
1051 }
1052 }
1053
1054 #[test]
1055 pub fn empty() {
1056 assert_eq!(Normpath::validate(""), Err(E::NotAbsolute));
1057 assert_eq!(
1058 NormpathBuf::try_from("").map_err(into_source),
1059 Err(E::NotAbsolute),
1060 );
1061 }
1062
1063 fn test_normalize(path: &str) -> OsString {
1064 let in_place = NormpathBuf::normalize(path.into()).map(|p| p.into_os_string());
1065 let new = Normpath::normalize(path).map(|p| p.into_owned().into_os_string());
1066 match (in_place, new) {
1067 (Ok(in_place), Ok(new)) => {
1068 assert_eq!(in_place, new, "inconsistent on {:?}", path);
1069 in_place
1070 }
1071 (Err(ec), Err(_)) => ec.value.into(),
1072 (a, b) => panic!(
1073 "inconsistent on {:?}: in-place = {:?}, new = {:?}",
1074 path, a, b
1075 ),
1076 }
1077 }
1078
1079 #[cfg(unix)]
1080 #[test]
1081 pub fn example_normalize() {
1082 assert_eq!(test_normalize("/foo/bar"), "/foo/bar");
1083 assert_eq!(test_normalize("//foo/./..//bar//"), "/foo/../bar");
1084 assert_eq!(test_normalize("//./"), "/");
1085
1086 assert_eq!(test_normalize("foo/bar"), "foo/bar");
1087 assert_eq!(test_normalize(".//foo/.//bar/..//"), "foo/bar/..");
1088 assert_eq!(test_normalize("././/."), ".");
1089
1090 assert_eq!(test_normalize(""), ".");
1091 }
1092
1093 #[cfg(windows)]
1094 #[test]
1095 pub fn example_normalize() {
1096 assert_eq!(test_normalize(r"C:\foo\bar"), r"C:\foo\bar");
1097 assert_eq!(test_normalize(r"c:/foo/.."), r"C:\");
1098 assert_eq!(test_normalize(r"c:\/foo\..\./../bar/.."), r"C:\..");
1099
1100 assert_eq!(test_normalize(r"foo\bar"), r"foo\bar");
1101 assert_eq!(test_normalize(r".\/foo\./\../"), r".");
1102 assert_eq!(test_normalize(r".\/foo/..\bar/../..//"), r"..");
1103
1104 assert_eq!(test_normalize(r"\/.\dev\foo"), r"\\.\dev\foo");
1105 assert_eq!(test_normalize(r"\\./dev"), r"\\.\dev");
1106 assert_eq!(test_normalize(r"//./dev/foo\/bar\../"), r"\\.\dev\foo");
1107
1108 assert_eq!(test_normalize(r"\/s\s\foo\bar"), r"\\s\s\foo\bar");
1109 assert_eq!(test_normalize(r"\\s/s\foo/\.\..\bar\/"), r"\\s\s\bar");
1110 assert_eq!(test_normalize(r"//s\s/foo\../\./../\bar/.."), r"\\s\s\..");
1111
1112 assert_eq!(test_normalize(r"C:foo\bar"), r"C:foo\bar");
1113 assert_eq!(test_normalize(r"c:.\\foo\../\bar//"), r"C:bar");
1114 assert_eq!(test_normalize(r"c:.\foo\../bar/.."), r"C:.");
1115
1116 assert_eq!(test_normalize(r""), r".");
1117 assert_eq!(test_normalize(r"C:"), r"C:.");
1118
1119 assert_eq!(
1121 test_normalize(r"\\?\C:\foo/..\/bar/."),
1122 r"\\?\C:\foo/..\/bar/."
1123 );
1124 assert_eq!(
1125 test_normalize(r"\\?\UNC\s\s\foo\./..\bar/"),
1126 r"\\?\UNC\s\s\foo\./..\bar/"
1127 );
1128 }
1129
1130 #[test]
1131 pub fn normalize() {
1132 let paths = make_paths(1..64, draw::common);
1133 for path in paths.take(1024) {
1134 let reference = to_canonical(&path);
1135 let ours = test_normalize(&path);
1136
1137 assert_eq!(reference.as_os_str(), ours, "original: {path:?}");
1138 }
1139 }
1140
1141 #[cfg(unix)]
1142 fn test_push(subject: &mut NormpathBuf, reference: &mut PathBuf, component: &str) {
1143 if is_parentless(component) {
1144 if component != "." {
1145 reference.push(component);
1146 }
1147 subject.push(component).unwrap();
1148
1149 assert_eq!(subject.as_path(), reference.as_path());
1150 } else {
1151 let copy = subject.clone();
1152 let error = subject.push(component).unwrap_err();
1153 assert_eq!(error, E::ContainsParent);
1154 assert_eq!(*subject, copy);
1155 }
1156 }
1157
1158 #[cfg(windows)]
1159 fn test_push(subject: &mut NormpathBuf, reference: &mut PathBuf, component: &str) {
1160 let peek = reference.join(component);
1161 if peek.is_absolute() && is_parentless(&peek) {
1162 *reference = to_canonical(&peek);
1163 subject.push(component).unwrap();
1164
1165 assert_eq!(subject.as_path(), reference.as_path());
1166 } else {
1167 let copy = subject.clone();
1168 let error = subject.push(component).unwrap_err();
1169 if !peek.is_absolute() {
1170 assert_eq!(error, E::NotAbsolute);
1171 } else {
1172 assert_eq!(error, E::ContainsParent);
1173 }
1174 assert_eq!(*subject, copy);
1175 }
1176 }
1177
1178 #[test]
1179 pub fn push_relative() {
1180 for _ in 0..128 {
1181 let components = make_paths(1..16, draw::relative);
1182
1183 let mut path = make_root();
1184 let mut twin = path.clone().into_path_buf();
1185 for component in components.take(64) {
1186 test_push(&mut path, &mut twin, &component);
1187 }
1188 }
1189 }
1190
1191 #[test]
1192 pub fn push_absolute() {
1193 for _ in 0..128 {
1194 let components = make_paths(MIN_ABS..32, draw::absolute);
1195
1196 let mut path = make_root();
1197 let mut twin = path.clone().into_path_buf();
1198 for component in components.take(32) {
1199 test_push(&mut path, &mut twin, &component);
1200 }
1201 }
1202 }
1203
1204 #[cfg(windows)]
1205 #[test]
1206 pub fn push_partial() {
1207 for path in make_paths(MIN_ABS..64, draw::normal).take(128) {
1208 let roots = make_paths(1..32, draw::root_only);
1209
1210 let mut path = NormpathBuf::try_from(path).unwrap();
1211 let mut twin = path.clone().into_path_buf();
1212 for root in roots.take(32) {
1213 test_push(&mut path, &mut twin, &root);
1214 }
1215 }
1216
1217 for path in make_paths(MIN_ABS..64, draw::normal).take(64) {
1218 let prefixes = iter::from_fn(|| Some(draw::disk_only()));
1219
1220 let mut path = NormpathBuf::try_from(path).unwrap();
1221 let mut twin = path.clone().into_path_buf();
1222 for prefix in prefixes.take(16) {
1223 test_push(&mut path, &mut twin, &prefix);
1224 }
1225 }
1226 }
1227
1228 #[cfg(unix)]
1229 fn make_normal_paths() -> impl Iterator<Item = String> {
1230 make_paths(MIN_ABS..64, draw::normal).take(256)
1231 }
1232
1233 #[cfg(windows)]
1234 fn make_normal_paths() -> impl Iterator<Item = String> {
1235 let genuine = make_paths(MIN_ABS..64, draw::normal);
1236 let verbatim = make_paths(7..64, draw::verbatim);
1237 genuine.take(224).chain(verbatim.take(32))
1238 }
1239
1240 fn test_normal(path: &str) {
1241 let ref_validated = Normpath::validate(path).unwrap();
1242 assert!(is_normalized(ref_validated.as_path()));
1243 assert_eq!(ref_validated, Path::new(path));
1244
1245 let ref_validated_c = Normpath::validate_canonical(path).unwrap();
1246 assert_eq!(ref_validated_c, ref_validated);
1247
1248 let ref_validated_p = Normpath::validate_parentless(path).unwrap();
1249 assert_eq!(ref_validated_p, ref_validated);
1250
1251 let ref_normalized = Normpath::normalize(path).unwrap();
1252 assert_eq!(&*ref_normalized, ref_validated);
1253
1254 let buf_validated = NormpathBuf::validate(path).unwrap();
1255 assert_eq!(&buf_validated, ref_validated);
1256
1257 let buf_normalized = NormpathBuf::normalize(PathBuf::from(path)).unwrap();
1258 assert_eq!(&buf_normalized, ref_validated);
1259
1260 let buf_converted = NormpathBuf::try_from(path).unwrap();
1261 assert_eq!(&buf_converted, ref_validated);
1262 }
1263
1264 #[cfg(unix)]
1265 #[test]
1266 pub fn examples_normal() {
1267 test_normal("/");
1268 test_normal("/foo/bar");
1269 }
1270
1271 #[cfg(windows)]
1272 #[test]
1273 pub fn examples_normal() {
1274 test_normal(r"C:\");
1275 test_normal(r"C:\foo\bar");
1276
1277 test_normal(r"\\.\dev");
1278 test_normal(r"\\.\dev\foo\bar");
1279 test_normal(r"\\s\s");
1280 test_normal(r"\\s\s\foo\bar");
1281
1282 test_normal(r"\\?\C:.\../foo\bar//");
1283 test_normal(r"\\?\UNC\s\s/foo\/.\..\bar\/");
1284 }
1285
1286 #[test]
1287 pub fn normal() {
1288 for path in make_normal_paths() {
1289 test_normal(&path);
1290 }
1291 }
1292
1293 fn test_err_single(path: &str, error: E) {
1294 assert_eq!(Normpath::validate(&path), Err(error));
1295 assert_eq!(Normpath::validate_canonical(&path), Err(error));
1296 assert_eq!(Normpath::validate_parentless(&path), Err(error));
1297
1298 assert_eq!(Normpath::normalize(&path), Err(error));
1299 assert_eq!(
1300 NormpathBuf::validate(&path).map_err(into_source),
1301 Err(error),
1302 );
1303 assert_eq!(
1304 NormpathBuf::normalize(PathBuf::from(&path)).map_err(into_source),
1305 Err(error),
1306 );
1307
1308 let conv_err = NormpathBuf::try_from(path).unwrap_err();
1309 assert_eq!(conv_err.error, error);
1310 assert_eq!(conv_err.value, path);
1311 }
1312
1313 fn test_err_single_canon(path: &str, canonical: impl AsRef<Path>) {
1314 assert_eq!(Normpath::validate(&path), Err(E::NotCanonical));
1315 assert_eq!(Normpath::validate_canonical(&path), Err(E::NotCanonical));
1316 assert_eq!(Normpath::validate_parentless(&path), Err(E::NotCanonical));
1317
1318 assert_eq!(
1319 NormpathBuf::validate(&path).map_err(into_source),
1320 Err(E::NotCanonical),
1321 );
1322
1323 let ref_normalized = Normpath::normalize(&path).unwrap();
1324 assert_eq!(&*ref_normalized, canonical.as_ref());
1325
1326 let buf_normalized = NormpathBuf::normalize(PathBuf::from(&path)).unwrap();
1327 assert_eq!(&buf_normalized, canonical.as_ref());
1328
1329 let buf_converted = NormpathBuf::try_from(path.to_string()).unwrap();
1330 assert_eq!(&buf_converted, canonical.as_ref());
1331
1332 let buf_converted_ref = NormpathBuf::try_from(path).unwrap();
1333 assert_eq!(&buf_converted_ref, canonical.as_ref());
1334 }
1335
1336 fn test_err_has_canon(path: &str) {
1337 assert_eq!(Normpath::validate_canonical(path), Err(E::NotCanonical));
1338
1339 assert!(Normpath::validate(&path).is_err());
1340 assert!(Normpath::validate_parentless(&path).is_err());
1341 assert!(NormpathBuf::validate(&path).is_err());
1342 }
1343
1344 fn test_err_has_parent(path: &str) {
1345 assert_eq!(Normpath::validate_parentless(path), Err(E::ContainsParent));
1346
1347 assert!(Normpath::validate(&path).is_err());
1348 assert!(Normpath::validate_canonical(&path).is_err());
1349 assert!(NormpathBuf::validate(&path).is_err());
1350
1351 assert!(Normpath::normalize(&path).is_err());
1352 assert!(NormpathBuf::normalize(PathBuf::from(&path)).is_err());
1353 assert!(NormpathBuf::try_from(path).is_err());
1354 assert!(NormpathBuf::try_from(path.to_string()).is_err());
1355 }
1356
1357 fn test_err_has_both(path: &str) {
1358 test_err_has_canon(path);
1359 test_err_has_parent(path);
1360 }
1361
1362 #[cfg(unix)]
1363 #[test]
1364 pub fn examples_err_single_relative() {
1365 test_err_single(".", E::NotAbsolute);
1366 test_err_single("foo/bar", E::NotAbsolute);
1367 }
1368
1369 #[cfg(windows)]
1370 #[test]
1371 pub fn examples_err_single_relative() {
1372 test_err_single(".", E::NotAbsolute);
1373 test_err_single(r"foo\bar", E::NotAbsolute);
1374
1375 test_err_single(r"\", E::NotAbsolute);
1376 test_err_single(r"\foo\bar", E::NotAbsolute);
1377
1378 test_err_single(r"C:.", E::NotAbsolute);
1379 test_err_single(r"C:foo\bar", E::NotAbsolute);
1380 }
1381
1382 #[test]
1383 pub fn err_single_relative() {
1384 let paths = make_paths(1..64, draw::relative) .filter(|p| is_parentless(p) && is_canonical(p));
1386
1387 for path in paths.take(256) {
1388 test_err_single(&path, E::NotAbsolute);
1389 }
1390 }
1391
1392 #[cfg(unix)]
1393 #[test]
1394 pub fn examples_err_single_parent() {
1395 test_err_single("/..", E::ContainsParent);
1396 test_err_single("/foo/../bar", E::ContainsParent);
1397 }
1398
1399 #[cfg(windows)]
1400 #[test]
1401 pub fn examples_err_single_parent() {
1402 test_err_single(r"C:\..", E::ContainsParent);
1403
1404 test_err_single(r"\\.\dev\..", E::ContainsParent);
1405 test_err_single(r"\\s\s\..", E::ContainsParent);
1406 }
1407
1408 #[test]
1409 pub fn err_single_parent() {
1410 let paths = make_paths(MIN_ABS..64, draw::absolute)
1411 .filter(|p| !is_parentless(p) && is_canonical(p));
1412
1413 for path in paths.take(256) {
1414 test_err_single(&path, E::ContainsParent);
1415 }
1416 }
1417
1418 #[cfg(unix)]
1419 #[test]
1420 pub fn examples_err_single_canon() {
1421 test_err_single_canon("//", "/");
1422 test_err_single_canon("/.", "/");
1423 test_err_single_canon("/foo//bar", "/foo/bar");
1424 test_err_single_canon("/foo/./bar", "/foo/bar");
1425 test_err_single_canon("/foo/bar/", "/foo/bar");
1426 test_err_single_canon("/.../..../", "/.../....");
1427 }
1428
1429 #[cfg(windows)]
1430 #[test]
1431 pub fn examples_err_single_canon() {
1432 test_err_single_canon(r"c:\", r"C:\");
1433 test_err_single_canon(r"C:\\", r"C:\");
1434 test_err_single_canon(r"C:\.", r"C:\");
1435 test_err_single_canon(r"C:\foo\..", r"C:\");
1436 test_err_single_canon(r"C:/foo/bar", r"C:\foo\bar");
1437 test_err_single_canon(r"C:\foo\\bar", r"C:\foo\bar");
1438 test_err_single_canon(r"C:\foo\.\bar", r"C:\foo\bar");
1439 test_err_single_canon(r"C:\foo\bar\", r"C:\foo\bar");
1440 test_err_single_canon(r"C:/.../....", r"C:\...\....");
1441
1442 test_err_single_canon(r"\\.\dev\\", r"\\.\dev\");
1443 test_err_single_canon(r"\\.\dev\.", r"\\.\dev\");
1444 test_err_single_canon(r"\\s\s\\", r"\\s\s\");
1445 test_err_single_canon(r"\\s\s\.", r"\\s\s\");
1446 }
1447
1448 #[test]
1449 pub fn err_single_canon() {
1450 let paths = make_paths(MIN_ABS..64, draw::absolute)
1451 .filter(|p| !is_canonical(p) && is_parentless(p));
1452
1453 for path in paths.take(256) {
1454 test_err_single_canon(&path, to_canonical(&path));
1455 }
1456 }
1457
1458 #[cfg(unix)]
1459 #[test]
1460 pub fn examples_err_focus() {
1461 test_err_has_canon("");
1462
1463 test_err_has_both("/foo/../bar/");
1464 test_err_has_both("//foo/../bar");
1465
1466 test_err_has_both("foo/../bar/");
1467 test_err_has_both("./foo/../bar");
1468 }
1469
1470 #[cfg(windows)]
1471 #[test]
1472 pub fn examples_err_focus() {
1473 test_err_has_canon(r"");
1474 test_err_has_canon(r"C:");
1475
1476 test_err_has_both(r"C:\..\foo\bar\");
1477 test_err_has_both(r"C:\\foo\..\..\bar");
1478 test_err_has_both(r"C:\foo\..\..");
1479
1480 test_err_has_both(r"..\foo\bar\");
1481 test_err_has_both(r".\foo\..\..\bar");
1482 test_err_has_both(r"foo\..\..");
1483
1484 test_err_has_both(r"\\.\dev\..\foo\bar\");
1485 test_err_has_both(r"\\.\dev\\foo\..\..\bar");
1486 test_err_has_both(r"\\s\s\..\foo\bar\");
1487 test_err_has_both(r"\\s\s\\foo\..\..\bar");
1488 }
1489
1490 #[test]
1491 pub fn err_focus_canon() {
1492 let paths = make_paths(MIN_ABS..64, draw::common).filter(|p| !is_canonical(p));
1493 for path in paths.take(256) {
1494 test_err_has_canon(&path);
1495 }
1496 }
1497
1498 #[test]
1499 pub fn err_focus_parent() {
1500 let paths = make_paths(MIN_ABS..64, draw::common).filter(|p| !is_parentless(p));
1501 for path in paths.take(256) {
1502 test_err_has_parent(&path);
1503 }
1504 }
1505}