1use crate::validator::path_history::{BoundaryChecked, Canonicalized, PathHistory, Raw};
2use crate::{Result, StrictPathError};
3use std::cmp::Ordering;
4use std::ffi::OsStr;
5use std::fmt;
6use std::hash::{Hash, Hasher};
7use std::marker::PhantomData;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10
11#[derive(Clone)]
20pub struct StrictPath<Marker = ()> {
21 path: PathHistory<((Raw, Canonicalized), BoundaryChecked)>,
22 boundary: Arc<crate::PathBoundary<Marker>>,
23 _marker: PhantomData<Marker>,
24}
25
26impl<Marker> StrictPath<Marker> {
27 pub fn with_boundary<P: AsRef<Path>>(dir_path: P) -> Result<Self> {
41 let boundary = crate::PathBoundary::try_new(dir_path)?;
42 boundary.strict_join("")
43 }
44
45 pub fn with_boundary_create<P: AsRef<Path>>(dir_path: P) -> Result<Self> {
48 let boundary = crate::PathBoundary::try_new_create(dir_path)?;
49 boundary.strict_join("")
50 }
51 pub(crate) fn new(
52 boundary: Arc<crate::PathBoundary<Marker>>,
53 validated_path: PathHistory<((Raw, Canonicalized), BoundaryChecked)>,
54 ) -> Self {
55 Self {
56 path: validated_path,
57 boundary,
58 _marker: PhantomData,
59 }
60 }
61
62 #[inline]
63 pub(crate) fn boundary(&self) -> &crate::PathBoundary<Marker> {
64 &self.boundary
65 }
66
67 #[inline]
68 pub(crate) fn path(&self) -> &Path {
69 &self.path
70 }
71
72 #[inline]
75 pub fn strictpath_to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
76 self.path.to_string_lossy()
77 }
78
79 #[inline]
82 pub fn strictpath_to_str(&self) -> Option<&str> {
83 self.path.to_str()
84 }
85
86 #[inline]
89 pub fn interop_path(&self) -> &OsStr {
90 self.path.as_os_str()
91 }
92
93 #[inline]
96 pub fn strictpath_display(&self) -> std::path::Display<'_> {
97 self.path.display()
98 }
99
100 #[inline]
103 pub fn unstrict(self) -> PathBuf {
104 self.path.into_inner()
105 }
106
107 #[inline]
110 pub fn virtualize(self) -> crate::path::virtual_path::VirtualPath<Marker> {
111 crate::path::virtual_path::VirtualPath::new(self)
112 }
113
114 #[inline]
117 pub fn try_into_boundary(self) -> crate::PathBoundary<Marker> {
118 self.boundary.as_ref().clone()
120 }
121
122 #[inline]
125 pub fn try_into_boundary_create(self) -> crate::PathBoundary<Marker> {
126 let boundary = self.boundary.as_ref().clone();
127 if !boundary.exists() {
128 let _ = std::fs::create_dir_all(boundary.as_ref());
130 }
131 boundary
132 }
133
134 #[inline]
150 pub fn strict_join<P: AsRef<Path>>(&self, path: P) -> Result<Self> {
151 let new_systempath = self.path.join(path);
152 self.boundary.strict_join(new_systempath)
153 }
154
155 pub fn strictpath_parent(&self) -> Result<Option<Self>> {
158 match self.path.parent() {
159 Some(p) => match self.boundary.strict_join(p) {
160 Ok(p) => Ok(Some(p)),
161 Err(e) => Err(e),
162 },
163 None => Ok(None),
164 }
165 }
166
167 #[inline]
170 pub fn strictpath_with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> Result<Self> {
171 let new_systempath = self.path.with_file_name(file_name);
172 self.boundary.strict_join(new_systempath)
173 }
174
175 pub fn strictpath_with_extension<S: AsRef<OsStr>>(&self, extension: S) -> Result<Self> {
178 let system_path = &self.path;
179 if system_path.file_name().is_none() {
180 return Err(StrictPathError::path_escapes_boundary(
181 self.path.to_path_buf(),
182 self.boundary.path().to_path_buf(),
183 ));
184 }
185 let new_systempath = system_path.with_extension(extension);
186 self.boundary.strict_join(new_systempath)
187 }
188
189 #[inline]
191 pub fn strictpath_file_name(&self) -> Option<&OsStr> {
192 self.path.file_name()
193 }
194
195 #[inline]
197 pub fn strictpath_file_stem(&self) -> Option<&OsStr> {
198 self.path.file_stem()
199 }
200
201 #[inline]
203 pub fn strictpath_extension(&self) -> Option<&OsStr> {
204 self.path.extension()
205 }
206
207 #[inline]
209 pub fn strictpath_starts_with<P: AsRef<Path>>(&self, p: P) -> bool {
210 self.path.starts_with(p.as_ref())
211 }
212
213 #[inline]
215 pub fn strictpath_ends_with<P: AsRef<Path>>(&self, p: P) -> bool {
216 self.path.ends_with(p.as_ref())
217 }
218
219 pub fn exists(&self) -> bool {
221 self.path.exists()
222 }
223
224 pub fn is_file(&self) -> bool {
226 self.path.is_file()
227 }
228
229 pub fn is_dir(&self) -> bool {
231 self.path.is_dir()
232 }
233
234 pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
236 std::fs::metadata(&self.path)
237 }
238
239 pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
242 std::fs::read_dir(&self.path)
243 }
244
245 pub fn read_to_string(&self) -> std::io::Result<String> {
247 std::fs::read_to_string(&self.path)
248 }
249
250 #[deprecated(since = "0.1.0-alpha.5", note = "Use read() instead")]
252 pub fn read_bytes(&self) -> std::io::Result<Vec<u8>> {
253 std::fs::read(&self.path)
254 }
255
256 #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
258 pub fn write_bytes(&self, data: &[u8]) -> std::io::Result<()> {
259 std::fs::write(&self.path, data)
260 }
261
262 #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
264 pub fn write_string(&self, data: &str) -> std::io::Result<()> {
265 std::fs::write(&self.path, data)
266 }
267
268 #[inline]
270 pub fn read(&self) -> std::io::Result<Vec<u8>> {
271 std::fs::read(&self.path)
272 }
273
274 #[inline]
277 pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> std::io::Result<()> {
278 std::fs::write(&self.path, contents)
279 }
280
281 pub fn create_dir_all(&self) -> std::io::Result<()> {
283 std::fs::create_dir_all(&self.path)
284 }
285
286 pub fn create_dir(&self) -> std::io::Result<()> {
291 std::fs::create_dir(&self.path)
292 }
293
294 pub fn create_parent_dir(&self) -> std::io::Result<()> {
297 match self.strictpath_parent() {
298 Ok(Some(parent)) => parent.create_dir(),
299 Ok(None) => Ok(()),
300 Err(StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
301 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
302 }
303 }
304
305 pub fn create_parent_dir_all(&self) -> std::io::Result<()> {
308 match self.strictpath_parent() {
309 Ok(Some(parent)) => parent.create_dir_all(),
310 Ok(None) => Ok(()),
311 Err(StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
312 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
313 }
314 }
315
316 pub fn strict_symlink(&self, link_path: &Self) -> std::io::Result<()> {
320 if self.boundary.path() != link_path.boundary.path() {
321 let err = StrictPathError::path_escapes_boundary(
322 link_path.path().to_path_buf(),
323 self.boundary.path().to_path_buf(),
324 );
325 return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
326 }
327
328 #[cfg(unix)]
329 {
330 std::os::unix::fs::symlink(self.path(), link_path.path())?;
331 }
332
333 #[cfg(windows)]
334 {
335 create_windows_symlink(self.path(), link_path.path())?;
336 }
337
338 Ok(())
339 }
340
341 pub fn strict_hard_link(&self, link_path: &Self) -> std::io::Result<()> {
344 if self.boundary.path() != link_path.boundary.path() {
345 let err = StrictPathError::path_escapes_boundary(
346 link_path.path().to_path_buf(),
347 self.boundary.path().to_path_buf(),
348 );
349 return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
350 }
351
352 std::fs::hard_link(self.path(), link_path.path())?;
353
354 Ok(())
355 }
356
357 pub fn strict_rename<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<()> {
361 let dest_ref = dest.as_ref();
362
363 let dest_path = if dest_ref.is_absolute() {
365 match self.boundary.strict_join(dest_ref) {
366 Ok(p) => p,
367 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
368 }
369 } else {
370 let parent = match self.strictpath_parent() {
371 Ok(Some(p)) => p,
372 Ok(None) => match self.boundary.strict_join("") {
373 Ok(root) => root,
374 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
375 },
376 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
377 };
378 match parent.strict_join(dest_ref) {
379 Ok(p) => p,
380 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
381 }
382 };
383
384 std::fs::rename(self.path(), dest_path.path())
385 }
386
387 pub fn strict_copy<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<u64> {
391 let dest_ref = dest.as_ref();
392
393 let dest_path = if dest_ref.is_absolute() {
395 match self.boundary.strict_join(dest_ref) {
396 Ok(p) => p,
397 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
398 }
399 } else {
400 let parent = match self.strictpath_parent() {
401 Ok(Some(p)) => p,
402 Ok(None) => match self.boundary.strict_join("") {
403 Ok(root) => root,
404 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
405 },
406 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
407 };
408 match parent.strict_join(dest_ref) {
409 Ok(p) => p,
410 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
411 }
412 };
413
414 std::fs::copy(self.path(), dest_path.path())
415 }
416
417 pub fn remove_file(&self) -> std::io::Result<()> {
420 std::fs::remove_file(&self.path)
421 }
422
423 pub fn remove_dir(&self) -> std::io::Result<()> {
426 std::fs::remove_dir(&self.path)
427 }
428
429 pub fn remove_dir_all(&self) -> std::io::Result<()> {
432 std::fs::remove_dir_all(&self.path)
433 }
434}
435
436#[cfg(feature = "serde")]
437impl<Marker> serde::Serialize for StrictPath<Marker> {
438 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
439 where
440 S: serde::Serializer,
441 {
442 serializer.serialize_str(self.strictpath_to_string_lossy().as_ref())
443 }
444}
445
446impl<Marker> fmt::Debug for StrictPath<Marker> {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 f.debug_struct("StrictPath")
449 .field("path", &self.path)
450 .field("boundary", &self.boundary.path())
451 .field("marker", &std::any::type_name::<Marker>())
452 .finish()
453 }
454}
455
456#[cfg(windows)]
457fn create_windows_symlink(src: &Path, link: &Path) -> std::io::Result<()> {
458 use std::os::windows::fs::{symlink_dir, symlink_file};
459
460 match std::fs::metadata(src) {
461 Ok(metadata) => {
462 if metadata.is_dir() {
463 symlink_dir(src, link)
464 } else {
465 symlink_file(src, link)
466 }
467 }
468 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
469 match symlink_file(src, link) {
470 Ok(()) => Ok(()),
471 Err(file_err) => {
472 if let Some(code) = file_err.raw_os_error() {
473 const ERROR_DIRECTORY: i32 = 267; if code == ERROR_DIRECTORY {
475 return symlink_dir(src, link);
476 }
477 }
478 Err(file_err)
479 }
480 }
481 }
482 Err(err) => Err(err),
483 }
484}
485
486impl<Marker> PartialEq for StrictPath<Marker> {
487 #[inline]
488 fn eq(&self, other: &Self) -> bool {
489 self.path.as_ref() == other.path.as_ref()
490 }
491}
492
493impl<Marker> Eq for StrictPath<Marker> {}
494
495impl<Marker> Hash for StrictPath<Marker> {
496 #[inline]
497 fn hash<H: Hasher>(&self, state: &mut H) {
498 self.path.hash(state);
499 }
500}
501
502impl<Marker> PartialOrd for StrictPath<Marker> {
503 #[inline]
504 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
505 Some(self.cmp(other))
506 }
507}
508
509impl<Marker> Ord for StrictPath<Marker> {
510 #[inline]
511 fn cmp(&self, other: &Self) -> Ordering {
512 self.path.cmp(&other.path)
513 }
514}
515
516impl<T: AsRef<Path>, Marker> PartialEq<T> for StrictPath<Marker> {
517 fn eq(&self, other: &T) -> bool {
518 self.path.as_ref() == other.as_ref()
519 }
520}
521
522impl<T: AsRef<Path>, Marker> PartialOrd<T> for StrictPath<Marker> {
523 fn partial_cmp(&self, other: &T) -> Option<Ordering> {
524 Some(self.path.as_ref().cmp(other.as_ref()))
525 }
526}
527
528impl<Marker> PartialEq<crate::path::virtual_path::VirtualPath<Marker>> for StrictPath<Marker> {
529 #[inline]
530 fn eq(&self, other: &crate::path::virtual_path::VirtualPath<Marker>) -> bool {
531 self.path.as_ref() == other.interop_path()
532 }
533}