1use core::fmt;
17use std::error;
18use std::path::{Path, PathBuf};
19use std::prelude::v1::*;
20
21use crate::{Component, RelativePathBuf};
22
23mod sealed {
26 use std::path::{Path, PathBuf};
27
28 pub trait Sealed {}
29
30 impl Sealed for Path {}
31 impl Sealed for PathBuf {}
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct RelativeToError {
38 kind: RelativeToErrorKind,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[non_exhaustive]
44enum RelativeToErrorKind {
45 NonUtf8,
47 PrefixMismatch,
49 AmbiguousTraversal,
55 IllegalComponent,
62}
63
64impl fmt::Display for RelativeToError {
65 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
66 match self.kind {
67 RelativeToErrorKind::NonUtf8 => "path contains non-utf8 component".fmt(fmt),
68 RelativeToErrorKind::PrefixMismatch => {
69 "paths contain different absolute prefixes".fmt(fmt)
70 }
71 RelativeToErrorKind::AmbiguousTraversal => {
72 "path traversal cannot be determined".fmt(fmt)
73 }
74 RelativeToErrorKind::IllegalComponent => "path contains illegal components".fmt(fmt),
75 }
76 }
77}
78
79impl error::Error for RelativeToError {}
80
81impl From<RelativeToErrorKind> for RelativeToError {
82 #[inline]
83 fn from(kind: RelativeToErrorKind) -> Self {
84 Self { kind }
85 }
86}
87
88pub trait PathExt: sealed::Sealed {
93 fn relative_to<P>(&self, root: P) -> Result<RelativePathBuf, RelativeToError>
123 where
124 P: AsRef<Path>;
125}
126
127impl PathExt for Path {
128 fn relative_to<P>(&self, root: P) -> Result<RelativePathBuf, RelativeToError>
129 where
130 P: AsRef<Path>,
131 {
132 use std::path::Component::{CurDir, Normal, ParentDir, Prefix, RootDir};
133
134 fn std_to_c(c: std::path::Component<'_>) -> Result<Component<'_>, RelativeToError> {
137 Ok(match c {
138 CurDir => Component::CurDir,
139 ParentDir => Component::ParentDir,
140 Normal(n) => Component::Normal(n.to_str().ok_or(RelativeToErrorKind::NonUtf8)?),
141 _ => return Err(RelativeToErrorKind::IllegalComponent.into()),
142 })
143 }
144
145 let root = root.as_ref();
146 let mut a_it = self.components();
147 let mut b_it = root.components();
148
149 let (a_head, b_head) = loop {
154 match (a_it.next(), b_it.next()) {
155 (Some(RootDir), Some(RootDir)) => (),
156 (Some(Prefix(a)), Some(Prefix(b))) if a == b => (),
157 (Some(Prefix(_) | RootDir), _) | (_, Some(Prefix(_) | RootDir)) => {
158 return Err(RelativeToErrorKind::PrefixMismatch.into());
159 }
160 (None, None) => break (None, None),
161 (a, b) if a != b => break (a, b),
162 _ => (),
163 }
164 };
165
166 let mut a_it = a_head.into_iter().chain(a_it);
167 let mut b_it = b_head.into_iter().chain(b_it);
168 let mut buf = RelativePathBuf::new();
169
170 loop {
171 let a = if let Some(a) = a_it.next() {
172 a
173 } else {
174 for _ in b_it {
175 buf.push(Component::ParentDir);
176 }
177
178 break;
179 };
180
181 match b_it.next() {
182 Some(CurDir) => buf.push(std_to_c(a)?),
183 Some(ParentDir) => {
184 return Err(RelativeToErrorKind::AmbiguousTraversal.into());
185 }
186 root => {
187 if root.is_some() {
188 buf.push(Component::ParentDir);
189 }
190
191 for comp in b_it {
192 match comp {
193 ParentDir => {
194 if !buf.pop() {
195 return Err(RelativeToErrorKind::AmbiguousTraversal.into());
196 }
197 }
198 CurDir => (),
199 _ => buf.push(Component::ParentDir),
200 }
201 }
202
203 buf.push(std_to_c(a)?);
204
205 for c in a_it {
206 buf.push(std_to_c(c)?);
207 }
208
209 break;
210 }
211 }
212 }
213
214 Ok(buf)
215 }
216}
217
218impl PathExt for PathBuf {
219 #[inline]
220 fn relative_to<P>(&self, root: P) -> Result<RelativePathBuf, RelativeToError>
221 where
222 P: AsRef<Path>,
223 {
224 self.as_path().relative_to(root)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use std::path::Path;
231
232 use super::{PathExt, RelativeToErrorKind};
233 use crate::{RelativePathBuf, RelativeToError};
234
235 macro_rules! assert_relative_to {
236 ($path:expr, $base:expr, Ok($expected:expr) $(,)?) => {
237 assert_eq!(
238 Path::new($path).relative_to($base),
239 Ok(RelativePathBuf::from($expected))
240 );
241 };
242
243 ($path:expr, $base:expr, Err($expected:ident) $(,)?) => {
244 assert_eq!(
245 Path::new($path).relative_to($base),
246 Err(RelativeToError::from(RelativeToErrorKind::$expected))
247 );
248 };
249 }
250
251 #[cfg(windows)]
252 macro_rules! abs {
253 ($path:expr) => {
254 Path::new(concat!("C:\\", $path))
255 };
256 }
257
258 #[cfg(not(windows))]
259 macro_rules! abs {
260 ($path:expr) => {
261 Path::new(concat!("/", $path))
262 };
263 }
264
265 #[test]
266 #[cfg(windows)]
267 fn test_different_prefixes() {
268 assert_relative_to!("C:\\repo", "D:\\repo", Err(PrefixMismatch),);
269 assert_relative_to!("C:\\repo", "C:\\repo", Ok(""));
270 assert_relative_to!(
271 "\\\\server\\share\\repo",
272 "\\\\server2\\share\\repo",
273 Err(PrefixMismatch),
274 );
275 }
276
277 #[test]
278 fn test_absolute() {
279 assert_relative_to!(abs!("foo"), abs!("bar"), Ok("../foo"));
280 assert_relative_to!("foo", "bar", Ok("../foo"));
281 assert_relative_to!(abs!("foo"), "bar", Err(PrefixMismatch));
282 assert_relative_to!("foo", abs!("bar"), Err(PrefixMismatch));
283 }
284
285 #[test]
286 fn test_identity() {
287 assert_relative_to!(".", ".", Ok(""));
288 assert_relative_to!("../foo", "../foo", Ok(""));
289 assert_relative_to!("./foo", "./foo", Ok(""));
290 assert_relative_to!("/foo", "/foo", Ok(""));
291 assert_relative_to!("foo", "foo", Ok(""));
292
293 assert_relative_to!("../foo/bar/baz", "../foo/bar/baz", Ok(""));
294 assert_relative_to!("foo/bar/baz", "foo/bar/baz", Ok(""));
295 }
296
297 #[test]
298 fn test_subset() {
299 assert_relative_to!("foo", "fo", Ok("../foo"));
300 assert_relative_to!("fo", "foo", Ok("../fo"));
301 }
302
303 #[test]
304 fn test_empty() {
305 assert_relative_to!("", "", Ok(""));
306 assert_relative_to!("foo", "", Ok("foo"));
307 assert_relative_to!("", "foo", Ok(".."));
308 }
309
310 #[test]
311 fn test_relative() {
312 assert_relative_to!("../foo", "../bar", Ok("../foo"));
313 assert_relative_to!("../foo", "../foo/bar/baz", Ok("../.."));
314 assert_relative_to!("../foo/bar/baz", "../foo", Ok("bar/baz"));
315
316 assert_relative_to!("foo/bar/baz", "foo", Ok("bar/baz"));
317 assert_relative_to!("foo/bar/baz", "foo/bar", Ok("baz"));
318 assert_relative_to!("foo/bar/baz", "foo/bar/baz", Ok(""));
319 assert_relative_to!("foo/bar/baz", "foo/bar/baz/", Ok(""));
320
321 assert_relative_to!("foo/bar/baz/", "foo", Ok("bar/baz"));
322 assert_relative_to!("foo/bar/baz/", "foo/bar", Ok("baz"));
323 assert_relative_to!("foo/bar/baz/", "foo/bar/baz", Ok(""));
324 assert_relative_to!("foo/bar/baz/", "foo/bar/baz/", Ok(""));
325
326 assert_relative_to!("foo/bar/baz", "foo/", Ok("bar/baz"));
327 assert_relative_to!("foo/bar/baz", "foo/bar/", Ok("baz"));
328 assert_relative_to!("foo/bar/baz", "foo/bar/baz", Ok(""));
329 }
330
331 #[test]
332 fn test_current_directory() {
333 assert_relative_to!(".", "foo", Ok("../."));
334 assert_relative_to!("foo", ".", Ok("foo"));
335 assert_relative_to!("/foo", "/.", Ok("foo"));
336 }
337
338 #[test]
339 fn assert_does_not_skip_parents() {
340 assert_relative_to!("some/path", "some/foo/baz/path", Ok("../../../path"));
341 assert_relative_to!("some/path", "some/foo/bar/../baz/path", Ok("../../../path"));
342 }
343
344 #[test]
345 fn test_ambiguous_paths() {
346 assert_relative_to!(".", "../..", Err(AmbiguousTraversal));
349 assert_relative_to!(".", "a/../..", Err(AmbiguousTraversal));
350 assert_relative_to!("../a/..", "../a/../b", Ok(".."));
352 assert_relative_to!("../a/../b", "../a/..", Ok("b"));
353 }
354}