1use crate::validator::path_history::{BoundaryChecked, Canonicalized, PathHistory, Raw};
3use crate::{Result, StrictPathError};
4use std::cmp::Ordering;
5use std::ffi::OsStr;
6use std::fmt;
7use std::hash::{Hash, Hasher};
8use std::marker::PhantomData;
9use std::path::{Path, PathBuf};
10use std::sync::Arc;
11
12#[derive(Clone)]
29pub struct StrictPath<Marker = ()> {
30 path: PathHistory<((Raw, Canonicalized), BoundaryChecked)>,
31 boundary: Arc<crate::PathBoundary<Marker>>,
32 _marker: PhantomData<Marker>,
33}
34
35impl<Marker> StrictPath<Marker> {
36 pub fn with_boundary<P: AsRef<Path>>(root: P) -> Result<Self> {
41 let boundary = crate::PathBoundary::try_new(root)?;
42 boundary.strict_join("")
43 }
44
45 pub fn with_boundary_create<P: AsRef<Path>>(root: P) -> Result<Self> {
49 let boundary = crate::PathBoundary::try_new_create(root)?;
50 boundary.strict_join("")
51 }
52 pub(crate) fn new(
53 boundary: Arc<crate::PathBoundary<Marker>>,
54 validated_path: PathHistory<((Raw, Canonicalized), BoundaryChecked)>,
55 ) -> Self {
56 Self {
57 path: validated_path,
58 boundary,
59 _marker: PhantomData,
60 }
61 }
62
63 #[inline]
64 pub(crate) fn boundary(&self) -> &crate::PathBoundary<Marker> {
65 &self.boundary
66 }
67
68 #[inline]
69 pub(crate) fn path(&self) -> &Path {
70 &self.path
71 }
72
73 #[inline]
76 pub fn strictpath_to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
77 self.path.to_string_lossy()
78 }
79
80 #[inline]
84 pub fn strictpath_to_str(&self) -> Option<&str> {
85 self.path.to_str()
86 }
87
88 #[inline]
92 pub fn interop_path(&self) -> &OsStr {
93 self.path.as_os_str()
94 }
95
96 #[inline]
98 pub fn strictpath_display(&self) -> std::path::Display<'_> {
99 self.path.display()
100 }
101
102 #[inline]
106 pub fn unstrict(self) -> PathBuf {
107 self.path.into_inner()
108 }
109
110 #[inline]
112 pub fn virtualize(self) -> crate::path::virtual_path::VirtualPath<Marker> {
113 crate::path::virtual_path::VirtualPath::new(self)
114 }
115
116 #[inline]
121 pub fn try_into_boundary(self) -> crate::PathBoundary<Marker> {
122 self.boundary.as_ref().clone()
124 }
125
126 #[inline]
132 pub fn try_into_boundary_create(self) -> crate::PathBoundary<Marker> {
133 let boundary = self.boundary.as_ref().clone();
134 if !boundary.exists() {
135 let _ = std::fs::create_dir_all(boundary.as_ref());
137 }
138 boundary
139 }
140
141 #[inline]
146 pub fn strict_join<P: AsRef<Path>>(&self, path: P) -> Result<Self> {
147 let new_systempath = self.path.join(path);
148 self.boundary.strict_join(new_systempath)
149 }
150
151 pub fn strictpath_parent(&self) -> Result<Option<Self>> {
153 match self.path.parent() {
154 Some(p) => match self.boundary.strict_join(p) {
155 Ok(p) => Ok(Some(p)),
156 Err(e) => Err(e),
157 },
158 None => Ok(None),
159 }
160 }
161
162 #[inline]
164 pub fn strictpath_with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> Result<Self> {
165 let new_systempath = self.path.with_file_name(file_name);
166 self.boundary.strict_join(new_systempath)
167 }
168
169 pub fn strictpath_with_extension<S: AsRef<OsStr>>(&self, extension: S) -> Result<Self> {
171 let system_path = &self.path;
172 if system_path.file_name().is_none() {
173 return Err(StrictPathError::path_escapes_boundary(
174 self.path.to_path_buf(),
175 self.boundary.path().to_path_buf(),
176 ));
177 }
178 let new_systempath = system_path.with_extension(extension);
179 self.boundary.strict_join(new_systempath)
180 }
181
182 #[inline]
184 pub fn strictpath_file_name(&self) -> Option<&OsStr> {
185 self.path.file_name()
186 }
187
188 #[inline]
190 pub fn strictpath_file_stem(&self) -> Option<&OsStr> {
191 self.path.file_stem()
192 }
193
194 #[inline]
196 pub fn strictpath_extension(&self) -> Option<&OsStr> {
197 self.path.extension()
198 }
199
200 #[inline]
202 pub fn strictpath_starts_with<P: AsRef<Path>>(&self, p: P) -> bool {
203 self.path.starts_with(p.as_ref())
204 }
205
206 #[inline]
208 pub fn strictpath_ends_with<P: AsRef<Path>>(&self, p: P) -> bool {
209 self.path.ends_with(p.as_ref())
210 }
211
212 pub fn exists(&self) -> bool {
214 self.path.exists()
215 }
216
217 pub fn is_file(&self) -> bool {
219 self.path.is_file()
220 }
221
222 pub fn is_dir(&self) -> bool {
224 self.path.is_dir()
225 }
226
227 pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
229 std::fs::metadata(&self.path)
230 }
231
232 pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
238 std::fs::read_dir(&self.path)
239 }
240
241 pub fn read_to_string(&self) -> std::io::Result<String> {
243 std::fs::read_to_string(&self.path)
244 }
245
246 #[deprecated(since = "0.1.0-alpha.5", note = "Use read() instead")]
248 pub fn read_bytes(&self) -> std::io::Result<Vec<u8>> {
249 std::fs::read(&self.path)
250 }
251
252 #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
254 pub fn write_bytes(&self, data: &[u8]) -> std::io::Result<()> {
255 std::fs::write(&self.path, data)
256 }
257
258 #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
260 pub fn write_string(&self, data: &str) -> std::io::Result<()> {
261 std::fs::write(&self.path, data)
262 }
263
264 #[inline]
266 pub fn read(&self) -> std::io::Result<Vec<u8>> {
267 std::fs::read(&self.path)
268 }
269
270 #[inline]
273 pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> std::io::Result<()> {
274 std::fs::write(&self.path, contents)
275 }
276
277 pub fn create_dir_all(&self) -> std::io::Result<()> {
279 std::fs::create_dir_all(&self.path)
280 }
281
282 pub fn create_dir(&self) -> std::io::Result<()> {
287 std::fs::create_dir(&self.path)
288 }
289
290 pub fn create_parent_dir(&self) -> std::io::Result<()> {
295 match self.strictpath_parent() {
296 Ok(Some(parent)) => parent.create_dir(),
297 Ok(None) => Ok(()),
298 Err(StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
299 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
300 }
301 }
302
303 pub fn create_parent_dir_all(&self) -> std::io::Result<()> {
307 match self.strictpath_parent() {
308 Ok(Some(parent)) => parent.create_dir_all(),
309 Ok(None) => Ok(()),
310 Err(StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
311 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
312 }
313 }
314
315 pub fn strict_symlink(&self, link_path: &Self) -> std::io::Result<()> {
322 if self.boundary.path() != link_path.boundary.path() {
323 let err = StrictPathError::path_escapes_boundary(
324 link_path.path().to_path_buf(),
325 self.boundary.path().to_path_buf(),
326 );
327 return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
328 }
329
330 #[cfg(unix)]
331 {
332 std::os::unix::fs::symlink(self.path(), link_path.path())?;
333 }
334
335 #[cfg(windows)]
336 {
337 create_windows_symlink(self.path(), link_path.path())?;
338 }
339
340 Ok(())
341 }
342
343 pub fn strict_hard_link(&self, link_path: &Self) -> std::io::Result<()> {
348 if self.boundary.path() != link_path.boundary.path() {
349 let err = StrictPathError::path_escapes_boundary(
350 link_path.path().to_path_buf(),
351 self.boundary.path().to_path_buf(),
352 );
353 return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
354 }
355
356 std::fs::hard_link(self.path(), link_path.path())?;
357
358 Ok(())
359 }
360
361 pub fn strict_rename<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<()> {
368 let dest_ref = dest.as_ref();
369
370 let dest_path = if dest_ref.is_absolute() {
372 match self.boundary.strict_join(dest_ref) {
373 Ok(p) => p,
374 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
375 }
376 } else {
377 let parent = match self.strictpath_parent() {
378 Ok(Some(p)) => p,
379 Ok(None) => match self.boundary.strict_join("") {
380 Ok(root) => root,
381 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
382 },
383 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
384 };
385 match parent.strict_join(dest_ref) {
386 Ok(p) => p,
387 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
388 }
389 };
390
391 std::fs::rename(self.path(), dest_path.path())
392 }
393
394 pub fn strict_copy<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<u64> {
405 let dest_ref = dest.as_ref();
406
407 let dest_path = if dest_ref.is_absolute() {
409 match self.boundary.strict_join(dest_ref) {
410 Ok(p) => p,
411 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
412 }
413 } else {
414 let parent = match self.strictpath_parent() {
415 Ok(Some(p)) => p,
416 Ok(None) => match self.boundary.strict_join("") {
417 Ok(root) => root,
418 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
419 },
420 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
421 };
422 match parent.strict_join(dest_ref) {
423 Ok(p) => p,
424 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
425 }
426 };
427
428 std::fs::copy(self.path(), dest_path.path())
429 }
430
431 pub fn remove_file(&self) -> std::io::Result<()> {
433 std::fs::remove_file(&self.path)
434 }
435
436 pub fn remove_dir(&self) -> std::io::Result<()> {
438 std::fs::remove_dir(&self.path)
439 }
440
441 pub fn remove_dir_all(&self) -> std::io::Result<()> {
443 std::fs::remove_dir_all(&self.path)
444 }
445}
446
447#[cfg(feature = "serde")]
448impl<Marker> serde::Serialize for StrictPath<Marker> {
449 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
450 where
451 S: serde::Serializer,
452 {
453 serializer.serialize_str(self.strictpath_to_string_lossy().as_ref())
454 }
455}
456
457impl<Marker> fmt::Debug for StrictPath<Marker> {
458 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459 f.debug_struct("StrictPath")
460 .field("path", &self.path)
461 .field("boundary", &self.boundary.path())
462 .field("marker", &std::any::type_name::<Marker>())
463 .finish()
464 }
465}
466
467#[cfg(windows)]
468fn create_windows_symlink(src: &Path, link: &Path) -> std::io::Result<()> {
469 use std::os::windows::fs::{symlink_dir, symlink_file};
470
471 match std::fs::metadata(src) {
472 Ok(metadata) => {
473 if metadata.is_dir() {
474 symlink_dir(src, link)
475 } else {
476 symlink_file(src, link)
477 }
478 }
479 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
480 match symlink_file(src, link) {
481 Ok(()) => Ok(()),
482 Err(file_err) => {
483 if let Some(code) = file_err.raw_os_error() {
484 const ERROR_DIRECTORY: i32 = 267; if code == ERROR_DIRECTORY {
486 return symlink_dir(src, link);
487 }
488 }
489 Err(file_err)
490 }
491 }
492 }
493 Err(err) => Err(err),
494 }
495}
496
497impl<Marker> PartialEq for StrictPath<Marker> {
498 #[inline]
499 fn eq(&self, other: &Self) -> bool {
500 self.path.as_ref() == other.path.as_ref()
501 }
502}
503
504impl<Marker> Eq for StrictPath<Marker> {}
505
506impl<Marker> Hash for StrictPath<Marker> {
507 #[inline]
508 fn hash<H: Hasher>(&self, state: &mut H) {
509 self.path.hash(state);
510 }
511}
512
513impl<Marker> PartialOrd for StrictPath<Marker> {
514 #[inline]
515 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
516 Some(self.cmp(other))
517 }
518}
519
520impl<Marker> Ord for StrictPath<Marker> {
521 #[inline]
522 fn cmp(&self, other: &Self) -> Ordering {
523 self.path.cmp(&other.path)
524 }
525}
526
527impl<T: AsRef<Path>, Marker> PartialEq<T> for StrictPath<Marker> {
528 fn eq(&self, other: &T) -> bool {
529 self.path.as_ref() == other.as_ref()
530 }
531}
532
533impl<T: AsRef<Path>, Marker> PartialOrd<T> for StrictPath<Marker> {
534 fn partial_cmp(&self, other: &T) -> Option<Ordering> {
535 Some(self.path.as_ref().cmp(other.as_ref()))
536 }
537}
538
539impl<Marker> PartialEq<crate::path::virtual_path::VirtualPath<Marker>> for StrictPath<Marker> {
540 #[inline]
541 fn eq(&self, other: &crate::path::virtual_path::VirtualPath<Marker>) -> bool {
542 self.path.as_ref() == other.interop_path()
543 }
544}