hyperlight_js/
resolver.rs1use std::path::{Path, PathBuf};
7
8pub use oxc_resolver::{FileMetadata, FileSystem, ResolveError};
9use phf::Map;
10
11#[derive(Clone, Copy)]
30pub struct FileSystemEmbedded {
31 modules: &'static Map<&'static str, &'static str>,
32}
33
34impl FileSystemEmbedded {
35 pub const fn new(modules: &'static Map<&'static str, &'static str>) -> Self {
39 Self { modules }
40 }
41
42 fn normalize_path<'a>(&self, path: &'a Path) -> Option<std::borrow::Cow<'a, str>> {
44 let s = path.to_str()?;
45
46 if s.contains('\\') || s.starts_with("./") || s.starts_with('/') {
47 Some(std::borrow::Cow::Owned(
48 s.replace('\\', "/")
49 .trim_start_matches("./")
50 .trim_start_matches('/')
51 .to_string(),
52 ))
53 } else {
54 Some(std::borrow::Cow::Borrowed(s))
55 }
56 }
57
58 fn is_directory(&self, normalized: &str) -> bool {
61 if normalized.is_empty() {
62 return !self.modules.is_empty();
63 }
64
65 let prefix = format!("{}/", normalized);
66 self.modules.keys().any(|key| key.starts_with(&prefix))
67 }
68}
69
70impl FileSystem for FileSystemEmbedded {
71 fn new() -> Self {
72 unreachable!("Use embed_modules! macro to create FileSystemEmbedded");
73 }
74
75 fn read(&self, path: &Path) -> std::io::Result<Vec<u8>> {
76 self.read_to_string(path).map(|s| s.into_bytes())
77 }
78
79 fn read_to_string(&self, path: &Path) -> std::io::Result<String> {
80 let normalized = self.normalize_path(path).ok_or_else(|| {
81 std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid UTF-8 in path")
82 })?;
83
84 self.modules
85 .get(&normalized)
86 .map(|&content| content.to_string())
87 .ok_or_else(|| {
88 std::io::Error::new(
89 std::io::ErrorKind::NotFound,
90 format!("Module '{}' not found", normalized),
91 )
92 })
93 }
94
95 fn metadata(&self, path: &Path) -> std::io::Result<FileMetadata> {
96 let normalized = self.normalize_path(path).ok_or_else(|| {
97 std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid UTF-8 in path")
98 })?;
99
100 let is_file = self.modules.contains_key(normalized.as_ref());
101 let is_dir = self.is_directory(normalized.as_ref());
102
103 if !is_file && !is_dir {
104 return Err(std::io::Error::new(
105 std::io::ErrorKind::NotFound,
106 format!("Path '{}' not found", normalized),
107 ));
108 }
109
110 Ok(FileMetadata::new(
111 is_file, is_dir, false, ))
113 }
114
115 fn symlink_metadata(&self, path: &Path) -> std::io::Result<FileMetadata> {
116 self.metadata(path)
117 }
118
119 fn read_link(&self, _path: &Path) -> Result<PathBuf, ResolveError> {
120 Err(std::io::Error::new(
121 std::io::ErrorKind::InvalidInput,
122 "symlinks are not supported in embedded file system",
123 )
124 .into())
125 }
126
127 fn canonicalize(&self, path: &Path) -> std::io::Result<PathBuf> {
128 self.normalize_path(path)
129 .ok_or_else(|| {
130 std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid UTF-8 in path")
131 })
132 .map(|v| PathBuf::from(v.into_owned()))
133 }
134}
135
136#[macro_export]
177macro_rules! embed_modules {
178 ($($key:expr => $file:expr),* $(,)?) => {{
180 use $crate::FileSystemEmbedded;
181 use ::phf::{phf_map, Map};
182
183 static EMBEDDED_MODULES: Map<&'static str, &'static str> = phf_map! {
184 $(
185 $key => include_str!($file),
186 )*
187 };
188
189 FileSystemEmbedded::new(&EMBEDDED_MODULES)
190 }};
191
192 ($($key:expr => @inline $content:expr),* $(,)?) => {{
194 use $crate::FileSystemEmbedded;
195 use ::phf::{phf_map, Map};
196
197 static EMBEDDED_MODULES: Map<&'static str, &'static str> = phf_map! {
198 $(
199 $key => $content,
200 )*
201 };
202
203 FileSystemEmbedded::new(&EMBEDDED_MODULES)
204 }};
205}
206
207#[cfg(test)]
208mod tests {
209 use std::path::Path;
210
211 use super::*;
212
213 #[test]
214 fn test_file_read() {
215 let fs = embed_modules! {
216 "test.js" => @inline "console.log('hello');",
217 };
218
219 let content = fs.read_to_string(Path::new("test.js")).unwrap();
220 assert_eq!(content, "console.log('hello');");
221 }
222
223 #[test]
224 fn test_directory_detection() {
225 let fs = embed_modules! {
226 "foo/bar.js" => @inline "content",
227 };
228
229 let metadata = fs.metadata(Path::new("foo")).unwrap();
230 assert!(metadata.is_dir());
231 assert!(!metadata.is_file());
232 }
233
234 #[test]
235 fn test_file_metadata() {
236 let fs = embed_modules! {
237 "test.js" => @inline "content",
238 };
239
240 let metadata = fs.metadata(Path::new("test.js")).unwrap();
241 assert!(metadata.is_file());
242 assert!(!metadata.is_dir());
243 }
244
245 #[test]
246 fn test_prefix_collision() {
247 let fs = embed_modules! {
248 "foo.js" => @inline "content1",
249 "foobar.js" => @inline "content2",
250 };
251
252 assert!(fs.metadata(Path::new("foo")).is_err());
253 assert!(fs.metadata(Path::new("foo.js")).unwrap().is_file());
254 }
255
256 #[test]
257 fn test_not_found() {
258 let fs = embed_modules! {
259 "exists.js" => @inline "content",
260 };
261
262 let result = fs.read_to_string(Path::new("missing.js"));
263 assert!(result.is_err());
264 }
265}