use std::collections::HashMap;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RustPath {
pub path: String,
}
#[derive(Clone, Debug, Default)]
pub struct ExternalMap {
type_map: HashMap<String, String>,
module_map: HashMap<String, String>,
wildcard_map: Vec<(String, String)>,
}
impl ExternalMap {
pub fn new() -> Self {
Self::default()
}
pub fn add_mapping(&mut self, mapping: &str) {
let Some((lhs, rhs)) = mapping.split_once('=') else {
return;
};
let lhs = lhs.trim();
let rhs = rhs.trim();
if lhs.ends_with('*') && rhs.ends_with('*') {
let prefix = lhs.trim_end_matches('*');
let rust_prefix = rhs.trim_end_matches('*').trim_end_matches("::");
self.wildcard_map
.push((prefix.to_string(), rust_prefix.to_string()));
self.wildcard_map
.sort_by_key(|b| std::cmp::Reverse(b.0.len()));
} else if lhs.contains(':') || lhs.contains('/') {
self.module_map.insert(lhs.to_string(), rhs.to_string());
} else {
self.type_map.insert(lhs.to_string(), rhs.to_string());
}
}
pub fn add_mappings(&mut self, mappings: &str) {
for mapping in mappings.split(',') {
let mapping = mapping.trim();
if !mapping.is_empty() {
self.add_mapping(mapping);
}
}
}
pub fn resolve(&self, type_name: &str, from_module: &str) -> Option<RustPath> {
if let Some(rust_path) = self.type_map.get(type_name) {
return Some(RustPath {
path: rust_path.clone(),
});
}
if let Some(rust_crate) = self.module_map.get(from_module) {
return Some(RustPath {
path: format!("{rust_crate}::{type_name}"),
});
}
for (prefix, rust_crate) in &self.wildcard_map {
if from_module.starts_with(prefix) {
let module_suffix = &from_module[prefix.len()..];
let rust_module = module_suffix.replace('/', "::");
if rust_module.is_empty() {
return Some(RustPath {
path: format!("{rust_crate}::{type_name}"),
});
} else {
return Some(RustPath {
path: format!("{rust_crate}::{rust_module}::{type_name}"),
});
}
}
}
None
}
pub fn resolve_type(&self, type_name: &str) -> Option<RustPath> {
self.type_map.get(type_name).map(|rust_path| RustPath {
path: rust_path.clone(),
})
}
pub fn is_empty(&self) -> bool {
self.type_map.is_empty() && self.module_map.is_empty() && self.wildcard_map.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_explicit_type_mapping() {
let mut map = ExternalMap::new();
map.add_mapping("Blob=::web_sys::Blob");
let result = map.resolve("Blob", "node:buffer");
assert_eq!(result.unwrap().path, "::web_sys::Blob");
assert!(map.resolve("Unknown", "node:buffer").is_none());
}
#[test]
fn test_module_mapping() {
let mut map = ExternalMap::new();
map.add_mapping("node:buffer=node_buffer_sys");
let result = map.resolve("Blob", "node:buffer");
assert_eq!(result.unwrap().path, "node_buffer_sys::Blob");
assert!(map.resolve("Foo", "node:http").is_none());
}
#[test]
fn test_wildcard_mapping() {
let mut map = ExternalMap::new();
map.add_mapping("node:*=node_sys::*");
let result = map.resolve("Blob", "node:buffer");
assert_eq!(result.unwrap().path, "node_sys::buffer::Blob");
let result2 = map.resolve("Server", "node:http");
assert_eq!(result2.unwrap().path, "node_sys::http::Server");
}
#[test]
fn test_explicit_overrides_wildcard() {
let mut map = ExternalMap::new();
map.add_mapping("node:*=node_sys::*");
map.add_mapping("Blob=::web_sys::Blob");
let result = map.resolve("Blob", "node:buffer");
assert_eq!(result.unwrap().path, "::web_sys::Blob");
let result2 = map.resolve("Buffer", "node:buffer");
assert_eq!(result2.unwrap().path, "node_sys::buffer::Buffer");
}
#[test]
fn test_comma_separated() {
let mut map = ExternalMap::new();
map.add_mappings("Blob=::web_sys::Blob, node:*=node_sys::*");
assert_eq!(
map.resolve("Blob", "node:buffer").unwrap().path,
"::web_sys::Blob"
);
assert_eq!(
map.resolve("Server", "node:http").unwrap().path,
"node_sys::http::Server"
);
}
#[test]
fn test_subpath_wildcard() {
let mut map = ExternalMap::new();
map.add_mapping("node:*=node_sys::*");
let result = map.resolve("ReadableStream", "node:stream/web");
assert_eq!(
result.unwrap().path,
"node_sys::stream::web::ReadableStream"
);
}
}