warpgate_api/
virtual_path.rs1use serde::{Deserialize, Serialize};
2use std::ffi::OsStr;
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6macro_rules! inherit_methods {
7 (comparator, [$($method:ident),+ $(,)?]) => {
8 $(
9 #[doc = concat!("Inherited from [`Path::", stringify!($method), "`].")]
10 pub fn $method(&self, value: impl AsRef<Path>) -> bool {
11 self.any_path().$method(value)
12 }
13 )*
14 };
15 (getter, [$($method:ident),+ $(,)?]) => {
16 $(
17 #[doc = concat!("Inherited from [`Path::", stringify!($method), "`].")]
18 pub fn $method(&self) -> Option<&OsStr> {
19 self.any_path().$method()
20 }
21 )*
22 };
23 (setter, [$($method:ident),+ $(,)?]) => {
24 $(
25 #[doc = concat!("Inherited from [`PathBuf::", stringify!($method), "`].")]
26 pub fn $method(&mut self, value: impl AsRef<OsStr>) {
27 let path = match self {
28 Self::Real(base) => base,
29 Self::Virtual { path: base, .. } => base,
30 };
31
32 path.$method(value);
33 }
34 )*
35 };
36 ([$($method:ident),+ $(,)?]) => {
37 $(
38 #[doc = concat!("Inherited from [`Path::", stringify!($method), "`].")]
39 pub fn $method(&self) -> bool {
40 self.any_path().$method()
41 }
42 )*
43 };
44}
45
46#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
48#[serde(untagged)]
49pub enum VirtualPath {
50 Virtual {
52 path: PathBuf,
53
54 #[serde(alias = "v")]
55 virtual_prefix: PathBuf,
56
57 #[serde(alias = "r")]
58 real_prefix: PathBuf,
59 },
60
61 Real(PathBuf),
63}
64
65impl VirtualPath {
66 inherit_methods!([exists, has_root, is_absolute, is_dir, is_file, is_relative]);
67 inherit_methods!(getter, [extension, file_name, file_stem]);
68 inherit_methods!(setter, [set_extension, set_file_name]);
69 inherit_methods!(comparator, [ends_with, starts_with]);
70
71 pub fn join<P: AsRef<Path>>(&self, path: P) -> VirtualPath {
73 match self {
74 Self::Real(base) => Self::Real(base.join(path.as_ref())),
75 Self::Virtual {
76 path: base,
77 virtual_prefix,
78 real_prefix,
79 } => Self::Virtual {
80 path: base.join(path.as_ref()),
81 virtual_prefix: virtual_prefix.clone(),
82 real_prefix: real_prefix.clone(),
83 },
84 }
85 }
86
87 pub fn parent(&self) -> Option<VirtualPath> {
89 fn is_root(path: &Path) -> bool {
92 path.to_str()
93 .is_some_and(|comp| comp.is_empty() || comp == "/")
94 }
95
96 match self {
97 Self::Real(base) => base.parent().and_then(|parent| {
98 if is_root(parent) {
99 None
100 } else {
101 Some(Self::Real(parent.to_owned()))
102 }
103 }),
104 Self::Virtual {
105 path: base,
106 virtual_prefix,
107 real_prefix,
108 } => base.parent().and_then(|parent| {
109 if is_root(parent) {
110 None
111 } else {
112 Some(Self::Virtual {
113 path: parent.to_owned(),
114 virtual_prefix: virtual_prefix.clone(),
115 real_prefix: real_prefix.clone(),
116 })
117 }
118 }),
119 }
120 }
121
122 pub fn any_path(&self) -> &PathBuf {
125 match self {
126 Self::Real(path) => path,
127 Self::Virtual { path, .. } => path,
128 }
129 }
130
131 pub fn real_path(&self) -> Option<PathBuf> {
134 match self {
135 Self::Real(path) => Some(path.to_path_buf()),
136 Self::Virtual { real_prefix, .. } => {
137 self.without_prefix().map(|path| real_prefix.join(path))
138 }
139 }
140 }
141
142 pub fn real_path_string(&self) -> Option<String> {
144 self.real_path()
145 .and_then(|path| path.to_str().map(|path| path.to_owned()))
146 }
147
148 pub fn to_path_buf(&self) -> PathBuf {
151 self.any_path().to_path_buf()
152 }
153
154 pub fn virtual_path(&self) -> Option<PathBuf> {
156 match self {
157 Self::Real(_) => None,
158 Self::Virtual { path, .. } => Some(path.to_owned()),
159 }
160 }
161
162 pub fn virtual_path_string(&self) -> Option<String> {
164 self.virtual_path()
165 .and_then(|path| path.to_str().map(|path| path.to_owned()))
166 }
167
168 pub fn without_prefix(&self) -> Option<&Path> {
171 match self {
172 Self::Real(_) => None,
173 Self::Virtual {
174 path,
175 virtual_prefix,
176 ..
177 } => path.strip_prefix(virtual_prefix).ok(),
178 }
179 }
180}
181
182#[cfg(feature = "schematic")]
183impl schematic::Schematic for VirtualPath {
184 fn schema_name() -> Option<String> {
185 Some("VirtualPath".into())
186 }
187
188 fn build_schema(mut schema: schematic::SchemaBuilder) -> schematic::Schema {
189 schema.set_description("A container for WASI virtual paths that can also keep a reference to the original real path.");
190 schema.string(schematic::schema::StringType {
191 format: Some("path".into()),
192 ..Default::default()
193 })
194 }
195}
196
197impl Default for VirtualPath {
198 fn default() -> Self {
199 Self::Real(PathBuf::new())
200 }
201}
202
203impl fmt::Display for VirtualPath {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f, "{}", self.any_path().display())
206 }
207}
208
209impl AsRef<VirtualPath> for VirtualPath {
210 fn as_ref(&self) -> &VirtualPath {
211 self
212 }
213}
214
215impl AsRef<PathBuf> for VirtualPath {
216 fn as_ref(&self) -> &PathBuf {
217 self.any_path()
218 }
219}
220
221impl AsRef<Path> for VirtualPath {
222 fn as_ref(&self) -> &Path {
223 self.any_path()
224 }
225}
226
227impl AsRef<OsStr> for VirtualPath {
228 fn as_ref(&self) -> &OsStr {
229 self.any_path().as_os_str()
230 }
231}