wasm_component_trampoline/
path.rs1use semver::Version;
2use snafu::{ResultExt, Snafu};
3use std::fmt::Display;
4use std::str::FromStr;
5
6#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
8pub struct ForeignInterfacePath {
9 package_name: String,
10 interface_name: String,
11 version: Option<Version>,
12}
13
14impl ForeignInterfacePath {
15 #[must_use]
17 pub const fn new(
18 package_name: String,
19 interface_name: String,
20 version: Option<Version>,
21 ) -> Self {
22 ForeignInterfacePath {
23 package_name,
24 interface_name,
25 version,
26 }
27 }
28
29 #[must_use]
31 pub fn package_name(&self) -> &str {
32 self.package_name.as_ref()
33 }
34
35 #[must_use]
37 pub fn interface_name(&self) -> &str {
38 &self.interface_name
39 }
40
41 #[must_use]
43 pub fn version(&self) -> Option<&Version> {
44 self.version.as_ref()
45 }
46}
47
48impl From<ForeignInterfacePath> for InterfacePath {
49 fn from(path: ForeignInterfacePath) -> Self {
50 InterfacePath {
51 package_name: Some(path.package_name),
52 interface_name: path.interface_name,
53 version: path.version,
54 }
55 }
56}
57
58impl Display for ForeignInterfacePath {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(
61 f,
62 "{}/{}{}",
63 self.package_name,
64 self.interface_name,
65 self.version
66 .as_ref()
67 .map_or(String::new(), |v| format!("@{v}"))
68 )
69 }
70}
71
72#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
75pub struct InterfacePath {
76 package_name: Option<String>,
77 interface_name: String,
78 version: Option<Version>,
79}
80
81impl InterfacePath {
82 #[must_use]
83 pub const fn new(
84 package_name: Option<String>,
85 interface_name: String,
86 version: Option<Version>,
87 ) -> Self {
88 InterfacePath {
89 package_name,
90 interface_name,
91 version,
92 }
93 }
94
95 #[must_use]
97 pub fn package_name(&self) -> Option<&str> {
98 self.package_name.as_deref()
99 }
100
101 #[must_use]
103 pub fn interface_name(&self) -> &str {
104 &self.interface_name
105 }
106
107 #[must_use]
109 pub fn version(&self) -> Option<&Version> {
110 self.version.as_ref()
111 }
112
113 #[must_use]
116 pub fn into_foreign(self) -> Option<ForeignInterfacePath> {
117 Some(ForeignInterfacePath {
118 package_name: self.package_name?,
119 interface_name: self.interface_name,
120 version: self.version,
121 })
122 }
123}
124
125impl FromStr for InterfacePath {
126 type Err = InterfacePathParseError;
127
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 let parts: Vec<&str> = s.split('/').collect();
133
134 match parts.len() {
135 1 if s.contains('@') => return Err(InterfacePathParseError::FormatError),
136 1 => {
137 return Ok(Self {
138 package_name: None,
139 interface_name: s.to_string(),
140 version: None,
141 });
142 }
143 2 => (), _ => return Err(InterfacePathParseError::FormatError),
145 }
146
147 let package_name = parts[0].to_string();
148
149 let interface_parts: Vec<&str> = parts[1].split('@').collect();
150 let interface_name = interface_parts[0].to_string();
151
152 let version = if interface_parts.len() == 2 {
153 Some(
154 Version::parse(interface_parts[1])
155 .context(interface_path_parse_error::VersionParseSnafu)?,
156 )
157 } else {
158 None
159 };
160
161 Ok(InterfacePath {
162 package_name: Some(package_name),
163 interface_name,
164 version,
165 })
166 }
167}
168
169impl Display for InterfacePath {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 write!(
172 f,
173 "{}{}{}",
174 self.package_name
175 .as_ref()
176 .map_or(String::new(), |p| format!("{p}/")),
177 self.interface_name,
178 self.version
179 .as_ref()
180 .map_or(String::new(), |v| format!("@{v}")),
181 )
182 }
183}
184
185#[derive(Snafu, Debug)]
186#[snafu(module)]
187pub enum InterfacePathParseError {
188 #[snafu(display("Invalid interface path format"))]
189 FormatError,
190
191 #[snafu(display("Invalid semantic version format: {}", source))]
192 VersionParseError { source: semver::Error },
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 const PACKAGE: &str = "package_name/interface_name@1.0.0";
199 const INTERFACE_ONLY: &str = "interface_name";
200 const PACKAGE_WITHOUT_VERSION: &str = "package_name/interface_name";
201
202 #[test]
203 fn test_path_display() {
204 for package in [PACKAGE, PACKAGE_WITHOUT_VERSION] {
205 let path = InterfacePath::from_str(package).unwrap();
206 assert_eq!(package, format!("{path}"));
207 let foreign_path = path.clone().into_foreign().unwrap();
208 assert_eq!(package, format!("{foreign_path}"));
209 }
210
211 let interface_only = InterfacePath::from_str(INTERFACE_ONLY).unwrap();
212 assert!(interface_only.clone().into_foreign().is_none());
213 }
214
215 #[test]
216 fn test_interface_path_roundtrip() {
217 let path = InterfacePath::from_str(PACKAGE).unwrap();
218 assert_eq!(path, path.clone().into_foreign().unwrap().into());
220
221 let interface_only = InterfacePath::from_str(INTERFACE_ONLY).unwrap();
222 assert_eq!(None, interface_only.clone().into_foreign());
223
224 assert_eq!(
226 path,
227 InterfacePath::new(
228 path.package_name().map(String::from),
229 path.interface_name().to_string(),
230 path.version().cloned(),
231 )
232 );
233 }
234
235 #[test]
236 fn test_foreign_interface_path_roundtrip() {
237 for package in [PACKAGE, PACKAGE_WITHOUT_VERSION] {
238 let path = InterfacePath::from_str(package).unwrap();
239 let foreign_path: ForeignInterfacePath = path.clone().into_foreign().unwrap();
240
241 assert_eq!(
242 foreign_path,
243 ForeignInterfacePath::new(
244 path.package_name().unwrap().to_string(),
245 path.interface_name().to_string(),
246 path.version().cloned()
247 )
248 );
249 }
250 }
251
252 #[test]
253 fn test_foreign_interface_path() {
254 let path = InterfacePath::from_str(PACKAGE).unwrap();
255 let foreign_path: ForeignInterfacePath = path.clone().into_foreign().unwrap();
256 assert_eq!(foreign_path.package_name(), "package_name");
257 assert_eq!(foreign_path.interface_name(), "interface_name");
258 assert_eq!(
259 foreign_path.version(),
260 Some(&Version::parse("1.0.0").unwrap())
261 );
262
263 let fp_string = foreign_path.to_string();
264 assert_eq!(PACKAGE, fp_string);
265 assert_eq!(PACKAGE, InterfacePath::from(foreign_path).to_string());
266 assert_eq!(fp_string, path.to_string());
267 }
268
269 #[test]
270 fn test_interface_path_parsing() {
271 let path = InterfacePath::from_str(PACKAGE).unwrap();
272 assert_eq!(path.package_name(), Some("package_name"));
273 assert_eq!(path.interface_name(), "interface_name");
274 assert_eq!(path.version(), Some(&Version::parse("1.0.0").unwrap()));
275 assert_eq!(path.to_string(), PACKAGE);
276
277 let path = InterfacePath::from_str("interface_name").unwrap();
278 assert_eq!(path.package_name(), None);
279 assert_eq!(path.interface_name(), "interface_name");
280 assert_eq!(path.version(), None);
281 assert_eq!(path.to_string(), "interface_name");
282
283 let path = InterfacePath::from_str("package_name/interface_name").unwrap();
284 assert_eq!(path.package_name(), Some("package_name"));
285 assert_eq!(path.interface_name(), "interface_name");
286 assert_eq!(path.version(), None);
287 assert_eq!(path.to_string(), "package_name/interface_name");
288
289 let path_err = InterfacePath::from_str("package_name/interface_name/").unwrap_err();
290 assert!(matches!(path_err, InterfacePathParseError::FormatError));
291
292 let path_err = InterfacePath::from_str("package_name/interface_name@").unwrap_err();
293 assert!(matches!(
294 path_err,
295 InterfacePathParseError::VersionParseError { .. }
296 ));
297 }
298}