1use std::collections::HashMap;
17
18#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct RustPath {
21 pub path: String,
23}
24
25#[derive(Clone, Debug, Default)]
27pub struct ExternalMap {
28 type_map: HashMap<String, String>,
30 module_map: HashMap<String, String>,
32 wildcard_map: Vec<(String, String)>,
35}
36
37impl ExternalMap {
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn add_mapping(&mut self, mapping: &str) {
49 let Some((lhs, rhs)) = mapping.split_once('=') else {
50 return;
51 };
52 let lhs = lhs.trim();
53 let rhs = rhs.trim();
54
55 if lhs.ends_with('*') && rhs.ends_with('*') {
56 let prefix = lhs.trim_end_matches('*');
58 let rust_prefix = rhs.trim_end_matches('*').trim_end_matches("::");
59 self.wildcard_map
60 .push((prefix.to_string(), rust_prefix.to_string()));
61 self.wildcard_map
63 .sort_by_key(|b| std::cmp::Reverse(b.0.len()));
64 } else if lhs.contains(':') || lhs.contains('/') {
65 self.module_map.insert(lhs.to_string(), rhs.to_string());
67 } else {
68 self.type_map.insert(lhs.to_string(), rhs.to_string());
70 }
71 }
72
73 pub fn add_mappings(&mut self, mappings: &str) {
75 for mapping in mappings.split(',') {
76 let mapping = mapping.trim();
77 if !mapping.is_empty() {
78 self.add_mapping(mapping);
79 }
80 }
81 }
82
83 pub fn resolve(&self, type_name: &str, from_module: &str) -> Option<RustPath> {
87 if let Some(rust_path) = self.type_map.get(type_name) {
89 return Some(RustPath {
90 path: rust_path.clone(),
91 });
92 }
93
94 if let Some(rust_crate) = self.module_map.get(from_module) {
96 return Some(RustPath {
97 path: format!("{rust_crate}::{type_name}"),
98 });
99 }
100
101 for (prefix, rust_crate) in &self.wildcard_map {
103 if from_module.starts_with(prefix) {
104 let module_suffix = &from_module[prefix.len()..];
105 let rust_module = module_suffix.replace('/', "::");
106 if rust_module.is_empty() {
107 return Some(RustPath {
108 path: format!("{rust_crate}::{type_name}"),
109 });
110 } else {
111 return Some(RustPath {
112 path: format!("{rust_crate}::{rust_module}::{type_name}"),
113 });
114 }
115 }
116 }
117
118 None
119 }
120
121 pub fn resolve_type(&self, type_name: &str) -> Option<RustPath> {
123 self.type_map.get(type_name).map(|rust_path| RustPath {
124 path: rust_path.clone(),
125 })
126 }
127
128 pub fn is_empty(&self) -> bool {
130 self.type_map.is_empty() && self.module_map.is_empty() && self.wildcard_map.is_empty()
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_explicit_type_mapping() {
140 let mut map = ExternalMap::new();
141 map.add_mapping("Blob=::web_sys::Blob");
142
143 let result = map.resolve("Blob", "node:buffer");
144 assert_eq!(result.unwrap().path, "::web_sys::Blob");
145
146 assert!(map.resolve("Unknown", "node:buffer").is_none());
148 }
149
150 #[test]
151 fn test_module_mapping() {
152 let mut map = ExternalMap::new();
153 map.add_mapping("node:buffer=node_buffer_sys");
154
155 let result = map.resolve("Blob", "node:buffer");
156 assert_eq!(result.unwrap().path, "node_buffer_sys::Blob");
157
158 assert!(map.resolve("Foo", "node:http").is_none());
160 }
161
162 #[test]
163 fn test_wildcard_mapping() {
164 let mut map = ExternalMap::new();
165 map.add_mapping("node:*=node_sys::*");
166
167 let result = map.resolve("Blob", "node:buffer");
168 assert_eq!(result.unwrap().path, "node_sys::buffer::Blob");
169
170 let result2 = map.resolve("Server", "node:http");
171 assert_eq!(result2.unwrap().path, "node_sys::http::Server");
172 }
173
174 #[test]
175 fn test_explicit_overrides_wildcard() {
176 let mut map = ExternalMap::new();
177 map.add_mapping("node:*=node_sys::*");
178 map.add_mapping("Blob=::web_sys::Blob");
179
180 let result = map.resolve("Blob", "node:buffer");
182 assert_eq!(result.unwrap().path, "::web_sys::Blob");
183
184 let result2 = map.resolve("Buffer", "node:buffer");
186 assert_eq!(result2.unwrap().path, "node_sys::buffer::Buffer");
187 }
188
189 #[test]
190 fn test_comma_separated() {
191 let mut map = ExternalMap::new();
192 map.add_mappings("Blob=::web_sys::Blob, node:*=node_sys::*");
193
194 assert_eq!(
195 map.resolve("Blob", "node:buffer").unwrap().path,
196 "::web_sys::Blob"
197 );
198 assert_eq!(
199 map.resolve("Server", "node:http").unwrap().path,
200 "node_sys::http::Server"
201 );
202 }
203
204 #[test]
205 fn test_subpath_wildcard() {
206 let mut map = ExternalMap::new();
207 map.add_mapping("node:*=node_sys::*");
208
209 let result = map.resolve("ReadableStream", "node:stream/web");
210 assert_eq!(
211 result.unwrap().path,
212 "node_sys::stream::web::ReadableStream"
213 );
214 }
215}