1#![deny(
8 warnings,
9 missing_docs,
10 trivial_numeric_casts,
11 unused_import_braces,
12 unused_qualifications
13)]
14#![doc(html_root_url = "https://docs.rs/canonical-path/2.0.2")]
15
16use std::{
17 borrow::Borrow,
18 env,
19 ffi::{OsStr, OsString},
20 fs::{Metadata, ReadDir},
21 io::{Error, ErrorKind, Result},
22 path::{Components, Display, Iter, Path, PathBuf},
23};
24
25macro_rules! impl_path {
27 () => {
28 #[inline]
30 pub fn as_path(&self) -> &Path {
31 self.0.as_ref()
32 }
33
34 #[inline]
36 pub fn as_os_str(&self) -> &OsStr {
37 self.0.as_os_str()
38 }
39
40 #[inline]
42 pub fn to_str(&self) -> Option<&str> {
43 self.0.to_str()
44 }
45
46 pub fn parent(&self) -> Result<CanonicalPathBuf> {
49 CanonicalPathBuf::new(&self.0
50 .parent()
51 .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "can't get parent of '/'"))?)
52 }
53
54 #[inline]
56 pub fn file_name(&self) -> Option<&OsStr> {
57 self.0.file_name()
58 }
59
60 #[inline]
62 pub fn starts_with<P: AsRef<Path>>(&self, base: P) -> bool {
63 self.0.starts_with(base)
64 }
65
66 #[inline]
68 pub fn ends_with<P: AsRef<Path>>(&self, child: P) -> bool {
69 self.0.ends_with(child)
70 }
71
72 #[inline]
74 pub fn file_stem(&self) -> Option<&OsStr> {
75 self.0.file_stem()
76 }
77
78 #[inline]
80 pub fn extension(&self) -> Option<&OsStr> {
81 self.0.extension()
82 }
83
84 #[inline]
86 pub fn with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> Result<CanonicalPathBuf> {
87 CanonicalPathBuf::new(&self.0.with_file_name(file_name))
88 }
89
90 #[inline]
92 pub fn with_extension<S: AsRef<OsStr>>(&self, extension: S) -> Result<CanonicalPathBuf> {
93 CanonicalPathBuf::new(&self.0.with_extension(extension))
94 }
95
96 #[inline]
98 pub fn components(&self) -> Components {
99 self.0.components()
100 }
101
102 #[inline]
105 pub fn iter(&self) -> Iter {
106 self.0.iter()
107 }
108
109 #[inline]
112 pub fn display(&self) -> Display {
113 self.0.display()
114 }
115
116 #[inline]
121 pub fn metadata(&self) -> Result<Metadata> {
122 self.0.symlink_metadata()
125 }
126
127 #[inline]
129 pub fn join<P: AsRef<Path>>(&self, path: P) -> Result<CanonicalPathBuf> {
130 CanonicalPathBuf::new(&self.0.join(path))
131 }
132
133 #[inline]
141 pub fn read_dir(&self) -> Result<ReadDir> {
142 self.0.read_dir()
143 }
144
145 #[inline]
147 pub fn exists(&self) -> bool {
148 self.0.exists()
149 }
150
151 #[inline]
153 pub fn is_file(&self) -> bool {
154 self.0.is_file()
155 }
156
157 #[inline]
159 pub fn is_dir(&self) -> bool {
160 self.0.is_file()
161 }
162 }
163}
164
165#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
171pub struct CanonicalPathBuf(PathBuf);
172
173impl CanonicalPathBuf {
174 pub fn canonicalize<P>(path: P) -> Result<Self>
176 where
177 P: AsRef<Path>,
178 {
179 Ok(CanonicalPathBuf(path.as_ref().canonicalize()?))
180 }
181
182 #[allow(clippy::new_ret_no_self)]
185 pub fn new<P>(path: P) -> Result<Self>
186 where
187 P: AsRef<Path>,
188 {
189 let p = path.as_ref();
190 let canonical_path_buf = Self::canonicalize(p)?;
191
192 if canonical_path_buf.as_path() != p {
193 return Err(Error::new(
194 ErrorKind::InvalidInput,
195 format!("non-canonical input path: {}", p.display()),
196 ));
197 }
198
199 Ok(canonical_path_buf)
200 }
201
202 #[inline]
204 pub fn as_canonical_path(&self) -> &CanonicalPath {
205 unsafe { CanonicalPath::from_path_unchecked(&self.0) }
206 }
207
208 pub fn set_file_name<S: AsRef<OsStr>>(&mut self, file_name: S) {
210 self.0.set_file_name(file_name);
211 }
212
213 pub fn set_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
219 self.0.set_extension(extension)
220 }
221
222 pub fn into_path_buf(self) -> PathBuf {
224 self.0
225 }
226
227 pub fn into_os_string(self) -> OsString {
229 self.0.into_os_string()
230 }
231
232 impl_path!();
233}
234
235impl AsRef<Path> for CanonicalPathBuf {
236 fn as_ref(&self) -> &Path {
237 self.as_path()
238 }
239}
240
241impl AsRef<CanonicalPath> for CanonicalPathBuf {
242 fn as_ref(&self) -> &CanonicalPath {
243 self.as_canonical_path()
244 }
245}
246
247impl AsRef<OsStr> for CanonicalPathBuf {
248 fn as_ref(&self) -> &OsStr {
249 self.as_os_str()
250 }
251}
252
253impl Borrow<CanonicalPath> for CanonicalPathBuf {
254 fn borrow(&self) -> &CanonicalPath {
255 self.as_canonical_path()
256 }
257}
258
259#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
265pub struct CanonicalPath(Path);
266
267impl CanonicalPath {
268 pub fn new<P>(path: &P) -> Result<&Self>
270 where
271 P: AsRef<Path> + ?Sized,
272 {
273 let p = path.as_ref();
274
275 if p != p.canonicalize()? {
284 return Err(Error::new(
285 ErrorKind::InvalidInput,
286 format!("non-canonical input path: {}", p.display()),
287 ));
288 }
289
290 Ok(unsafe { Self::from_path_unchecked(p) })
291 }
292
293 pub unsafe fn from_path_unchecked<P>(path: &P) -> &Self
299 where
300 P: AsRef<Path> + ?Sized,
301 {
302 &*(path.as_ref() as *const Path as *const CanonicalPath)
303 }
304
305 pub fn to_canonical_path_buf(&self) -> CanonicalPathBuf {
307 CanonicalPathBuf(self.0.to_owned())
308 }
309
310 impl_path!();
311}
312
313impl AsRef<Path> for CanonicalPath {
314 fn as_ref(&self) -> &Path {
315 &self.0
316 }
317}
318
319impl ToOwned for CanonicalPath {
320 type Owned = CanonicalPathBuf;
321
322 fn to_owned(&self) -> CanonicalPathBuf {
323 self.to_canonical_path_buf()
324 }
325}
326
327pub fn current_exe() -> Result<CanonicalPathBuf> {
330 let p = env::current_exe()?;
331 Ok(CanonicalPathBuf::canonicalize(p)?)
332}
333
334#[cfg(all(test, not(windows)))]
336mod tests {
337 use std::fs::File;
338 use std::os::unix::fs;
339 use std::path::PathBuf;
340
341 use super::{CanonicalPath, CanonicalPathBuf};
342 use tempfile::TempDir;
343
344 const CANONICAL_FILENAME: &str = "canonical-file";
346
347 const NON_CANONICAL_FILENAME: &str = "non-canonical-file";
349
350 struct TestFixtureDir {
352 pub tempdir: TempDir,
354
355 pub base_path: PathBuf,
357
358 pub canonical_path: PathBuf,
360
361 pub symlink_path: PathBuf,
363 }
364
365 impl TestFixtureDir {
366 pub fn new() -> Self {
367 let tempdir = TempDir::new().unwrap();
368 let base_path = tempdir.path().canonicalize().unwrap();
369
370 let canonical_path = base_path.join(CANONICAL_FILENAME);
371 File::create(&canonical_path).unwrap();
372
373 let symlink_path = base_path.join(NON_CANONICAL_FILENAME);
374 fs::symlink(&canonical_path, &symlink_path).unwrap();
375
376 Self {
377 tempdir,
378 base_path,
379 canonical_path,
380 symlink_path,
381 }
382 }
383 }
384
385 #[test]
386 fn create_canonical_path() {
387 let test_fixtures = TestFixtureDir::new();
388 let canonical_path = CanonicalPath::new(&test_fixtures.canonical_path).unwrap();
389 assert_eq!(
390 canonical_path.as_path(),
391 test_fixtures.canonical_path.as_path()
392 );
393 }
394
395 #[test]
396 fn create_canonical_path_buf() {
397 let test_fixtures = TestFixtureDir::new();
398 let canonical_path_buf = CanonicalPathBuf::new(&test_fixtures.canonical_path).unwrap();
399 assert_eq!(
400 canonical_path_buf.as_path(),
401 test_fixtures.canonical_path.as_path()
402 );
403 }
404
405 #[test]
406 fn reject_canonical_path_symlinks() {
407 let test_fixtures = TestFixtureDir::new();
408 let result = CanonicalPath::new(&test_fixtures.symlink_path);
409 assert!(result.is_err(), "symlinks aren't canonical paths!");
410 }
411
412 #[test]
413 fn reject_canonical_path_buf_symlinks() {
414 let test_fixtures = TestFixtureDir::new();
415 let result = CanonicalPathBuf::new(&test_fixtures.symlink_path);
416 assert!(result.is_err(), "symlinks aren't canonical paths!");
417 }
418}