vx_tool_rust/
rust_tool.rs1use crate::config::RustUrlBuilder;
4use anyhow::Result;
5use std::collections::HashMap;
6use vx_plugin::{ToolContext, ToolExecutionResult, VersionInfo, VxTool};
7use vx_tool_standard::StandardUrlBuilder;
8use vx_version::{GitHubVersionFetcher, VersionFetcher};
9
10macro_rules! rust_vx_tool {
12 ($name:ident, $cmd:literal, $desc:literal) => {
13 #[derive(Debug, Clone)]
14 pub struct $name {
15 _url_builder: RustUrlBuilder,
16 }
17
18 impl $name {
19 pub fn new() -> Self {
20 Self {
21 _url_builder: RustUrlBuilder::new(),
22 }
23 }
24 }
25
26 #[async_trait::async_trait]
27 impl VxTool for $name {
28 fn name(&self) -> &str {
29 $cmd
30 }
31
32 fn description(&self) -> &str {
33 $desc
34 }
35
36 fn aliases(&self) -> Vec<&str> {
37 vec![]
38 }
39
40 async fn fetch_versions(&self, include_prerelease: bool) -> Result<Vec<VersionInfo>> {
41 let fetcher = GitHubVersionFetcher::new("rust-lang", "rust");
43 fetcher
44 .fetch_versions(include_prerelease)
45 .await
46 .map_err(|e| anyhow::anyhow!("Failed to fetch versions: {}", e))
47 }
48
49 async fn install_version(&self, version: &str, force: bool) -> Result<()> {
50 if !force && self.is_version_installed(version).await? {
51 return Err(anyhow::anyhow!(
52 "Version {} of {} is already installed",
53 version,
54 self.name()
55 ));
56 }
57
58 let install_dir = self.get_version_install_dir(version);
59 let _exe_path = self.default_install_workflow(version, &install_dir).await?;
60
61 if !self.is_version_installed(version).await? {
63 return Err(anyhow::anyhow!(
64 "Installation verification failed for {} version {}",
65 self.name(),
66 version
67 ));
68 }
69
70 Ok(())
71 }
72
73 async fn execute(
74 &self,
75 args: &[String],
76 context: &ToolContext,
77 ) -> Result<ToolExecutionResult> {
78 let mut cmd = std::process::Command::new($cmd);
80 cmd.args(args);
81
82 if let Some(cwd) = &context.working_directory {
83 cmd.current_dir(cwd);
84 }
85
86 for (key, value) in &context.environment_variables {
87 cmd.env(key, value);
88 }
89
90 let status = cmd
91 .status()
92 .map_err(|e| anyhow::anyhow!("Failed to execute {}: {}", $cmd, e))?;
93
94 Ok(ToolExecutionResult {
95 exit_code: status.code().unwrap_or(1),
96 stdout: None,
97 stderr: None,
98 })
99 }
100
101 async fn get_download_url(&self, version: &str) -> Result<Option<String>> {
102 Ok(RustUrlBuilder::download_url(version))
103 }
104
105 fn metadata(&self) -> HashMap<String, String> {
106 let mut meta = HashMap::new();
107 meta.insert(
108 "homepage".to_string(),
109 "https://www.rust-lang.org/".to_string(),
110 );
111 meta.insert("ecosystem".to_string(), "rust".to_string());
112 meta.insert(
113 "repository".to_string(),
114 "https://github.com/rust-lang/rust".to_string(),
115 );
116 meta.insert("license".to_string(), "MIT OR Apache-2.0".to_string());
117 meta
118 }
119 }
120
121 impl Default for $name {
122 fn default() -> Self {
123 Self::new()
124 }
125 }
126 };
127}
128
129rust_vx_tool!(CargoTool, "cargo", "Rust package manager and build tool");
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_cargo_tool_creation() {
138 let tool = CargoTool::new();
139 assert_eq!(tool.name(), "cargo");
140 assert!(!tool.description().is_empty());
141 assert!(tool.aliases().is_empty());
142 }
143
144 #[test]
145 fn test_rust_tool_metadata() {
146 let tool = CargoTool::new();
147 let metadata = tool.metadata();
148
149 assert!(metadata.contains_key("homepage"));
150 assert!(metadata.contains_key("ecosystem"));
151 assert_eq!(metadata.get("ecosystem"), Some(&"rust".to_string()));
152 }
153}