warpgate_api/
virtual_path.rs

1use 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/// A container for WASI virtual paths that can also keep a reference to the original real path.
47#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
48#[serde(untagged)]
49pub enum VirtualPath {
50    /// A virtual path with prefixes to determine a real path.
51    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    /// Only a real path. Could not be matched with a virtual prefix.
62    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    /// Append the path part and return a new [`VirtualPath`] instance.
72    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    /// Return the parent directory as a new [`VirtualPath`] instance.
88    pub fn parent(&self) -> Option<VirtualPath> {
89        // If at the root (`/`), then we have gone outside the allowed
90        // virtual paths, so there's no parent to use!
91        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    /// Return any path available, either virtual or real, regardless of any
123    /// conditions. This is primarily used for debugging.
124    pub fn any_path(&self) -> &PathBuf {
125        match self {
126            Self::Real(path) => path,
127            Self::Virtual { path, .. } => path,
128        }
129    }
130
131    /// Return the original real path. If we don't have access to prefixes,
132    /// or removing prefix fails, returns `None`.
133    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    /// Return the original real path as a string.
143    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    /// Convert the virtual path into a [`PathBuf`] instance. This *does not*
149    /// convert it into a real path.
150    pub fn to_path_buf(&self) -> PathBuf {
151        self.any_path().to_path_buf()
152    }
153
154    /// Return the virtual path. If a real path only, returns `None`.
155    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    /// Return the virtual path as a string.
163    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    /// Return the current path without a virtual prefix.
169    /// If we don't have access to prefixes, returns `None`.
170    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}