probe_code/path_resolver/
go.rs1use super::PathResolver;
4use serde_json::Value;
5use std::path::PathBuf;
6use std::process::Command;
7
8pub struct GoPathResolver;
10
11impl Default for GoPathResolver {
12 fn default() -> Self {
13 Self::new()
14 }
15}
16
17impl GoPathResolver {
18 pub fn new() -> Self {
20 GoPathResolver
21 }
22}
23
24impl PathResolver for GoPathResolver {
25 fn prefix(&self) -> &'static str {
26 "go:"
27 }
28
29 fn split_module_and_subpath(
30 &self,
31 full_path_after_prefix: &str,
32 ) -> Result<(String, Option<String>), String> {
33 if full_path_after_prefix.is_empty() {
34 return Err("Go path cannot be empty".to_string());
35 }
36 if full_path_after_prefix.contains("..") {
37 return Err("Go path cannot contain '..'".to_string());
38 }
39
40 let path = full_path_after_prefix.trim_end_matches('/');
42
43 let parts: Vec<&str> = path.split('/').collect();
45 let is_common_external = parts.len() >= 3
46 && (parts[0] == "github.com"
47 || parts[0] == "gitlab.com"
48 || parts[0] == "bitbucket.org"
49 || (parts[0] == "golang.org" && parts[1] == "x"));
50
51 if is_common_external {
52 let module_name = parts[..3].join("/");
54 let subpath = if parts.len() > 3 {
55 Some(parts[3..].join("/")).filter(|s| !s.is_empty()) } else {
57 None
58 };
59 Ok((module_name, subpath))
60 } else {
61 if parts.len() > 1 && parts.last().unwrap().contains('.') {
64 let file_part = parts.last().unwrap();
66 let module_parts = &parts[..parts.len() - 1];
67 let module_name = module_parts.join("/");
68 Ok((module_name, Some(file_part.to_string())))
69 } else {
70 Ok((path.to_string(), None))
76 }
77 }
78 }
79
80 fn resolve(&self, module_name: &str) -> Result<PathBuf, String> {
81 if Command::new("go").arg("version").output().is_err() {
83 return Err(
84 "Go command not found. Please ensure Go is installed and in your PATH.".to_string(),
85 );
86 }
87
88 let output = Command::new("go")
90 .args(["list", "-json", module_name])
91 .output()
92 .map_err(|e| format!("Failed to execute 'go list': {e}"))?;
93
94 if !output.status.success() {
95 return Err(format!(
96 "Error running 'go list': {}",
97 String::from_utf8_lossy(&output.stderr)
98 ));
99 }
100
101 let json_str = String::from_utf8_lossy(&output.stdout);
102 let json: Value = serde_json::from_str(&json_str)
103 .map_err(|e| format!("Failed to parse JSON output from 'go list': {e}"))?;
104
105 if let Some(dir) = json["Dir"].as_str() {
107 Ok(PathBuf::from(dir))
108 } else {
109 Err(format!("No directory found for Go package: {module_name}"))
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_go_path_resolver() {
120 if Command::new("go").arg("version").output().is_err() {
122 println!("Skipping test_go_path_resolver: Go is not installed");
123 return;
124 }
125
126 let resolver = GoPathResolver::new();
127
128 let result = resolver.resolve("fmt");
130 assert!(
131 result.is_ok(),
132 "Failed to resolve 'fmt' package: {result:?}"
133 );
134
135 let path = result.unwrap();
137 assert!(path.exists(), "Path does not exist: {path:?}");
138 }
139}