1use crate::model::VolumeLimits;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use thiserror::Error;
7
8#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct PortablePath(String);
12
13impl PortablePath {
14 pub const ROOT: &'static str = "/";
16
17 pub fn parse(value: &str, limits: VolumeLimits) -> Result<Self, PathError> {
25 let path_bytes = u32::try_from(value.len()).map_err(|_| PathError::PathTooLong)?;
26 if path_bytes > limits.maximum_path_bytes {
27 return Err(PathError::PathTooLong);
28 }
29 if value == Self::ROOT {
30 return Ok(Self(value.to_owned()));
31 }
32 if !value.starts_with('/') {
33 return Err(PathError::NotAbsolute);
34 }
35 if value.ends_with('/') {
36 return Err(PathError::TrailingSeparator);
37 }
38
39 let mut depth = 0_u16;
40 for component in value.strip_prefix('/').unwrap_or(value).split('/') {
41 if component.is_empty() {
42 return Err(PathError::EmptyComponent);
43 }
44 if component == "." {
45 return Err(PathError::CurrentComponent);
46 }
47 if component == ".." {
48 return Err(PathError::ParentComponent);
49 }
50 if component.as_bytes().contains(&0) {
51 return Err(PathError::Nul);
52 }
53 let component_bytes =
54 u32::try_from(component.len()).map_err(|_| PathError::ComponentTooLong)?;
55 if component_bytes > limits.maximum_component_bytes {
56 return Err(PathError::ComponentTooLong);
57 }
58 depth = depth.checked_add(1).ok_or(PathError::TooDeep)?;
59 if depth > limits.maximum_path_depth {
60 return Err(PathError::TooDeep);
61 }
62 }
63 Ok(Self(value.to_owned()))
64 }
65
66 #[must_use]
68 pub fn as_str(&self) -> &str {
69 &self.0
70 }
71
72 #[must_use]
74 pub fn is_within(&self, ancestor: &Self) -> bool {
75 if ancestor.0 == Self::ROOT {
76 return true;
77 }
78 self.0 == ancestor.0
79 || self
80 .0
81 .strip_prefix(&ancestor.0)
82 .is_some_and(|suffix| suffix.starts_with('/'))
83 }
84
85 #[must_use]
87 pub fn depth(&self) -> usize {
88 if self.0 == Self::ROOT {
89 0
90 } else {
91 self.0
92 .strip_prefix('/')
93 .unwrap_or(&self.0)
94 .split('/')
95 .count()
96 }
97 }
98
99 pub fn components(&self) -> impl Iterator<Item = &str> {
101 self.0
102 .strip_prefix('/')
103 .unwrap_or_default()
104 .split('/')
105 .filter(|component| !component.is_empty())
106 }
107}
108
109impl fmt::Debug for PortablePath {
110 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
111 formatter
112 .debug_tuple("PortablePath")
113 .field(&self.0)
114 .finish()
115 }
116}
117
118impl fmt::Display for PortablePath {
119 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120 formatter.write_str(&self.0)
121 }
122}
123
124#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
126pub enum PathError {
127 #[error("portable path must be absolute")]
129 NotAbsolute,
130 #[error("portable path must not end in a separator")]
132 TrailingSeparator,
133 #[error("portable path contains an empty component")]
135 EmptyComponent,
136 #[error("portable path contains a current-directory component")]
138 CurrentComponent,
139 #[error("portable path contains a parent component")]
141 ParentComponent,
142 #[error("portable path contains NUL")]
144 Nul,
145 #[error("portable path exceeds its byte bound")]
147 PathTooLong,
148 #[error("portable path component exceeds its byte bound")]
150 ComponentTooLong,
151 #[error("portable path exceeds its depth bound")]
153 TooDeep,
154}
155
156#[cfg(test)]
157#[path = "tests/path.rs"]
158mod tests;