vx_tool_rust/
rust_tool.rs

1//! Rust toolchain implementations with environment isolation
2
3use 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
10/// Macro to generate Rust tool implementations using VxTool trait
11macro_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                // For Rust tools, use GitHubVersionFetcher
42                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                // Verify installation
62                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                // Simple implementation - execute the tool directly
79                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
129// Define Rust tools using the VxTool macro
130rust_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}