warpgate_api/
virtual_path.rs

1use serde::{Deserialize, Serialize};
2use std::ffi::OsStr;
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6#[macro_export]
7macro_rules! inherit_methods {
8    (comparator, [$($method:ident),+ $(,)?]) => {
9        $(
10            #[doc = concat!("Inherited from [`PathBuf.", stringify!($method), "`].")]
11            pub fn $method(&self, value: impl AsRef<Path>) -> bool {
12                self.any_path().$method(value)
13            }
14        )*
15    };
16    (getter, [$($method:ident),+ $(,)?]) => {
17        $(
18            #[doc = concat!("Inherited from [`PathBuf.", stringify!($method), "`].")]
19            pub fn $method(&self) -> Option<&OsStr> {
20                self.any_path().$method()
21            }
22        )*
23    };
24    (setter, [$($method:ident),+ $(,)?]) => {
25        $(
26            #[doc = concat!("Inherited from [`PathBuf.", stringify!($method), "`].")]
27            pub fn $method(&mut self, value: impl AsRef<OsStr>) {
28                let path = match self {
29                    Self::Real(base) => base,
30                    Self::Virtual { path: base, .. } => base,
31                };
32
33                path.$method(value);
34            }
35        )*
36    };
37    ([$($method:ident),+ $(,)?]) => {
38        $(
39            #[doc = concat!("Inherited from [`PathBuf.", stringify!($method), "`].")]
40            pub fn $method(&self) -> bool {
41                self.any_path().$method()
42            }
43        )*
44    };
45}
46
47/// A container for WASI virtual paths that can also keep a reference to the original real path.
48#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
49#[serde(untagged)]
50pub enum VirtualPath {
51    /// A virtual path with prefixes to determine a real path.
52    Virtual {
53        path: PathBuf,
54
55        #[serde(alias = "v")]
56        virtual_prefix: PathBuf,
57
58        #[serde(alias = "r")]
59        real_prefix: PathBuf,
60    },
61
62    /// Only a real path. Could not be matched with a virtual prefix.
63    Real(PathBuf),
64}
65
66impl VirtualPath {
67    inherit_methods!([exists, has_root, is_absolute, is_dir, is_file, is_relative]);
68    inherit_methods!(getter, [extension, file_name, file_stem]);
69    inherit_methods!(setter, [set_extension, set_file_name]);
70    inherit_methods!(comparator, [ends_with, starts_with]);
71
72    /// Append the path part and return a new [`VirtualPath`] instance.
73    pub fn join<P: AsRef<Path>>(&self, path: P) -> VirtualPath {
74        match self {
75            Self::Real(base) => Self::Real(base.join(path.as_ref())),
76            Self::Virtual {
77                path: base,
78                virtual_prefix,
79                real_prefix,
80            } => Self::Virtual {
81                path: base.join(path.as_ref()),
82                virtual_prefix: virtual_prefix.clone(),
83                real_prefix: real_prefix.clone(),
84            },
85        }
86    }
87
88    /// Return the parent directory as a new [`VirtualPath`] instance.
89    pub fn parent(&self) -> Option<VirtualPath> {
90        match self {
91            Self::Real(base) => base.parent().map(|parent| Self::Real(parent.to_owned())),
92            Self::Virtual {
93                path: base,
94                virtual_prefix,
95                real_prefix,
96            } => base.parent().map(|parent| Self::Virtual {
97                path: parent.to_owned(),
98                virtual_prefix: virtual_prefix.clone(),
99                real_prefix: real_prefix.clone(),
100            }),
101        }
102    }
103
104    /// Return any path available, either virtual or real, regardless of any
105    /// conditions. This is primarily used for debugging.
106    pub fn any_path(&self) -> &PathBuf {
107        match self {
108            Self::Real(path) => path,
109            Self::Virtual { path, .. } => path,
110        }
111    }
112
113    /// Return the original real path. If we don't have access to prefixes,
114    /// or removing prefix fails, or the real path doesn't exist, returns `None`.
115    pub fn real_path(&self) -> Option<PathBuf> {
116        let path = match self {
117            Self::Real(path) => Some(path.to_path_buf()),
118            Self::Virtual { real_prefix, .. } => {
119                self.without_prefix().map(|path| real_prefix.join(path))
120            }
121        };
122
123        path.and_then(|path| {
124            if path.is_absolute() && path.exists() {
125                Some(path.to_owned())
126            } else {
127                None
128            }
129        })
130    }
131
132    /// Return the virtual path. If a real path only, returns `None`.
133    pub fn virtual_path(&self) -> Option<PathBuf> {
134        match self {
135            Self::Real(_) => None,
136            Self::Virtual { path, .. } => Some(path.to_owned()),
137        }
138    }
139
140    /// Return the current path without a virtual prefix.
141    /// If we don't have access to prefixes, returns `None`.
142    pub fn without_prefix(&self) -> Option<&Path> {
143        match self {
144            Self::Real(_) => None,
145            Self::Virtual {
146                path,
147                virtual_prefix,
148                ..
149            } => path.strip_prefix(virtual_prefix).ok(),
150        }
151    }
152}
153
154#[cfg(feature = "schematic")]
155impl schematic::Schematic for VirtualPath {
156    fn schema_name() -> Option<String> {
157        Some("VirtualPath".into())
158    }
159
160    fn build_schema(mut schema: schematic::SchemaBuilder) -> schematic::Schema {
161        schema.set_description("A container for WASI virtual paths that can also keep a reference to the original real path.");
162        schema.string(schematic::schema::StringType {
163            format: Some("path".into()),
164            ..Default::default()
165        })
166    }
167}
168
169impl Default for VirtualPath {
170    fn default() -> Self {
171        Self::Real(PathBuf::new())
172    }
173}
174
175impl fmt::Display for VirtualPath {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        write!(f, "{}", self.any_path().display())
178    }
179}
180
181impl AsRef<VirtualPath> for VirtualPath {
182    fn as_ref(&self) -> &VirtualPath {
183        self
184    }
185}
186
187impl AsRef<PathBuf> for VirtualPath {
188    fn as_ref(&self) -> &PathBuf {
189        self.any_path()
190    }
191}
192
193impl AsRef<Path> for VirtualPath {
194    fn as_ref(&self) -> &Path {
195        self.any_path()
196    }
197}