use super::PathResolver;
use serde_json::Value;
use std::path::PathBuf;
use std::process::Command;
pub struct GoPathResolver;
impl Default for GoPathResolver {
fn default() -> Self {
Self::new()
}
}
impl GoPathResolver {
pub fn new() -> Self {
GoPathResolver
}
}
impl PathResolver for GoPathResolver {
fn prefix(&self) -> &'static str {
"go:"
}
fn split_module_and_subpath(
&self,
full_path_after_prefix: &str,
) -> Result<(String, Option<String>), String> {
if full_path_after_prefix.is_empty() {
return Err("Go path cannot be empty".to_string());
}
if full_path_after_prefix.contains("..") {
return Err("Go path cannot contain '..'".to_string());
}
let path = full_path_after_prefix.trim_end_matches('/');
let parts: Vec<&str> = path.split('/').collect();
let is_common_external = parts.len() >= 3
&& (parts[0] == "github.com"
|| parts[0] == "gitlab.com"
|| parts[0] == "bitbucket.org"
|| (parts[0] == "golang.org" && parts[1] == "x"));
if is_common_external {
let module_name = parts[..3].join("/");
let subpath = if parts.len() > 3 {
Some(parts[3..].join("/")).filter(|s| !s.is_empty()) } else {
None
};
Ok((module_name, subpath))
} else {
if parts.len() > 1 && parts.last().unwrap().contains('.') {
let file_part = parts.last().unwrap();
let module_parts = &parts[..parts.len() - 1];
let module_name = module_parts.join("/");
Ok((module_name, Some(file_part.to_string())))
} else {
Ok((path.to_string(), None))
}
}
}
fn resolve(&self, module_name: &str) -> Result<PathBuf, String> {
if Command::new("go").arg("version").output().is_err() {
return Err(
"Go command not found. Please ensure Go is installed and in your PATH.".to_string(),
);
}
let output = Command::new("go")
.args(["list", "-json", module_name])
.output()
.map_err(|e| format!("Failed to execute 'go list': {e}"))?;
if !output.status.success() {
return Err(format!(
"Error running 'go list': {}",
String::from_utf8_lossy(&output.stderr)
));
}
let json_str = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(&json_str)
.map_err(|e| format!("Failed to parse JSON output from 'go list': {e}"))?;
if let Some(dir) = json["Dir"].as_str() {
Ok(PathBuf::from(dir))
} else {
Err(format!("No directory found for Go package: {module_name}"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_go_path_resolver() {
if Command::new("go").arg("version").output().is_err() {
println!("Skipping test_go_path_resolver: Go is not installed");
return;
}
let resolver = GoPathResolver::new();
let result = resolver.resolve("fmt");
assert!(
result.is_ok(),
"Failed to resolve 'fmt' package: {result:?}"
);
let path = result.unwrap();
assert!(path.exists(), "Path does not exist: {path:?}");
}
}