mod go;
mod javascript;
mod rust;
use std::path::{Path, PathBuf};
pub use go::GoPathResolver;
pub use javascript::JavaScriptPathResolver;
pub use rust::RustPathResolver;
pub trait PathResolver {
fn prefix(&self) -> &'static str;
fn split_module_and_subpath(
&self,
full_path_after_prefix: &str,
) -> Result<(String, Option<String>), String>;
fn resolve(&self, module_name: &str) -> Result<PathBuf, String>;
}
pub fn resolve_path(path: &str) -> Result<PathBuf, String> {
let resolvers: Vec<Box<dyn PathResolver>> = vec![
Box::new(GoPathResolver::new()),
Box::new(JavaScriptPathResolver::new()),
Box::new(RustPathResolver::new()),
];
if let Some(dep_path) = path.strip_prefix("/dep/") {
let parts: Vec<&str> = dep_path.splitn(2, '/').collect();
if parts.is_empty() {
return Err("Invalid /dep/ path: missing language identifier".to_string());
}
let lang_id = parts[0];
let remainder = parts.get(1).unwrap_or(&"");
let prefix = match lang_id {
"go" => "go:",
"js" => "js:",
"rust" => "rust:",
_ => {
return Err(format!(
"Unknown language identifier in /dep/ path: {lang_id}"
))
}
};
for resolver in &resolvers {
if resolver.prefix() == prefix {
let (module_name, subpath_opt) =
resolver.split_module_and_subpath(remainder).map_err(|e| {
format!("Failed to parse path '{remainder}' for prefix '{prefix}': {e}")
})?;
let module_base_path = resolver.resolve(&module_name).map_err(|e| {
format!("Failed to resolve module '{module_name}' for prefix '{prefix}': {e}")
})?;
let final_path = match subpath_opt {
Some(sub) if !sub.is_empty() => {
let relative_subpath = Path::new(&sub)
.strip_prefix("/")
.unwrap_or_else(|_| Path::new(&sub));
module_base_path.join(relative_subpath)
}
_ => module_base_path, };
return Ok(final_path);
}
}
return Err(format!("No resolver found for language: {lang_id}"));
}
for resolver in resolvers {
let prefix = resolver.prefix();
if !prefix.ends_with(':') {
eprintln!("Warning: PathResolver prefix '{prefix}' does not end with ':'");
continue;
}
if let Some(full_path_after_prefix) = path.strip_prefix(prefix) {
let (module_name, subpath_opt) = resolver
.split_module_and_subpath(full_path_after_prefix)
.map_err(|e| {
format!(
"Failed to parse path '{full_path_after_prefix}' for prefix '{prefix}': {e}"
)
})?;
let module_base_path = resolver.resolve(&module_name).map_err(|e| {
format!("Failed to resolve module '{module_name}' for prefix '{prefix}': {e}")
})?;
let final_path = match subpath_opt {
Some(sub) if !sub.is_empty() => {
let relative_subpath = Path::new(&sub)
.strip_prefix("/")
.unwrap_or_else(|_| Path::new(&sub));
module_base_path.join(relative_subpath)
}
_ => module_base_path, };
return Ok(final_path);
}
}
Ok(PathBuf::from(path))
}
#[cfg(test)]
mod tests {
use super::*;
use std::process::Command;
#[test]
fn test_resolve_path_regular() {
let path = "/some/regular/path";
let result = resolve_path(path);
assert!(result.is_ok());
assert_eq!(result.unwrap(), PathBuf::from(path));
}
#[test]
fn test_resolve_path_dep_prefix() {
if Command::new("go").arg("version").output().is_err() {
println!("Skipping test_resolve_path_dep_prefix: Go is not installed");
return;
}
let result = resolve_path("/dep/go/fmt");
let traditional_result = resolve_path("go:fmt");
assert!(
result.is_ok(),
"Failed to resolve '/dep/go/fmt': {result:?}"
);
assert!(
traditional_result.is_ok(),
"Failed to resolve 'go:fmt': {traditional_result:?}"
);
assert_eq!(result.unwrap(), traditional_result.unwrap());
}
#[test]
fn test_invalid_dep_path() {
let result = resolve_path("/dep/");
assert!(result.is_err());
let result = resolve_path("/dep/unknown/package");
assert!(result.is_err());
}
}