1use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10use vx_core::{Platform, VxResult};
11use vx_installer::InstallConfig;
12
13pub trait StandardToolConfig {
15 fn tool_name() -> &'static str;
17
18 fn create_install_config(version: &str, install_dir: PathBuf) -> InstallConfig;
20
21 fn get_install_methods() -> Vec<String>;
23
24 fn supports_auto_install() -> bool;
26
27 fn get_manual_instructions() -> String;
29
30 fn get_dependencies() -> Vec<ToolDependency>;
32
33 fn get_default_version() -> &'static str;
35}
36
37pub trait StandardUrlBuilder {
39 fn download_url(version: &str) -> Option<String>;
41
42 fn get_filename(version: &str) -> String;
44
45 fn get_platform_string() -> String;
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ToolDependency {
52 pub tool_name: String,
54 pub description: String,
56 pub required: bool,
58 pub version_requirement: Option<String>,
60 pub platforms: Vec<Platform>,
62}
63
64impl ToolDependency {
65 pub fn required(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
67 Self {
68 tool_name: tool_name.into(),
69 description: description.into(),
70 required: true,
71 version_requirement: None,
72 platforms: vec![],
73 }
74 }
75
76 pub fn optional(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
78 Self {
79 tool_name: tool_name.into(),
80 description: description.into(),
81 required: false,
82 version_requirement: None,
83 platforms: vec![],
84 }
85 }
86
87 pub fn with_version(mut self, requirement: impl Into<String>) -> Self {
89 self.version_requirement = Some(requirement.into());
90 self
91 }
92
93 pub fn for_platforms(mut self, platforms: Vec<Platform>) -> Self {
95 self.platforms = platforms;
96 self
97 }
98}
99
100pub trait ToolRuntime {
102 fn is_available(&self) -> impl std::future::Future<Output = VxResult<bool>> + Send;
104
105 fn get_version(&self) -> impl std::future::Future<Output = VxResult<Option<String>>> + Send;
107
108 fn get_path(&self) -> impl std::future::Future<Output = VxResult<Option<PathBuf>>> + Send;
110
111 fn execute(&self, args: &[String]) -> impl std::future::Future<Output = VxResult<i32>> + Send;
113}
114
115pub trait VersionParser {
117 fn parse_version(output: &str) -> Option<String>;
119
120 fn is_valid_version(version: &str) -> bool;
122
123 fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering;
125}
126
127pub struct PlatformUrlBuilder;
129
130impl PlatformUrlBuilder {
131 pub fn get_platform_string() -> String {
133 let platform = Platform::current();
134 platform.to_string()
135 }
136
137 pub fn get_archive_extension() -> &'static str {
139 if cfg!(windows) {
140 "zip"
141 } else {
142 "tar.gz"
143 }
144 }
145
146 pub fn get_exe_extension() -> &'static str {
148 if cfg!(windows) {
149 ".exe"
150 } else {
151 ""
152 }
153 }
154}
155
156pub struct UrlUtils;
158
159impl UrlUtils {
160 pub fn github_release_url(owner: &str, repo: &str, version: &str, filename: &str) -> String {
162 format!(
163 "https://github.com/{}/{}/releases/download/{}/{}",
164 owner, repo, version, filename
165 )
166 }
167
168 pub fn official_download_url(base_url: &str, version: &str, filename: &str) -> String {
170 format!("{}/v{}/{}", base_url, version, filename)
171 }
172}
173
174pub struct VersionUtils;
176
177impl VersionUtils {
178 pub fn is_latest(version: &str) -> bool {
180 version == "latest" || version == "stable"
181 }
182
183 pub fn normalize_version(version: &str) -> String {
185 version.strip_prefix('v').unwrap_or(version).to_string()
187 }
188
189 pub fn is_prerelease(version: &str) -> bool {
191 version.contains('-')
192 && (version.contains("alpha")
193 || version.contains("beta")
194 || version.contains("rc")
195 || version.contains("pre"))
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_tool_dependency_creation() {
205 let dep = ToolDependency::required("node", "Node.js runtime").with_version(">=16.0.0");
206
207 assert_eq!(dep.tool_name, "node");
208 assert!(dep.required);
209 assert_eq!(dep.version_requirement, Some(">=16.0.0".to_string()));
210 }
211
212 #[test]
213 fn test_platform_url_builder() {
214 let platform = PlatformUrlBuilder::get_platform_string();
215 assert!(!platform.is_empty());
216
217 let ext = PlatformUrlBuilder::get_archive_extension();
218 assert!(ext == "zip" || ext == "tar.gz");
219 }
220
221 #[test]
222 fn test_url_utils() {
223 let url = UrlUtils::github_release_url("owner", "repo", "v1.0.0", "file.zip");
224 assert_eq!(
225 url,
226 "https://github.com/owner/repo/releases/download/v1.0.0/file.zip"
227 );
228 }
229
230 #[test]
231 fn test_version_utils() {
232 assert!(VersionUtils::is_latest("latest"));
233 assert!(VersionUtils::is_latest("stable"));
234 assert!(!VersionUtils::is_latest("1.0.0"));
235
236 assert_eq!(VersionUtils::normalize_version("v1.0.0"), "1.0.0");
237 assert_eq!(VersionUtils::normalize_version("1.0.0"), "1.0.0");
238
239 assert!(VersionUtils::is_prerelease("1.0.0-beta.1"));
240 assert!(!VersionUtils::is_prerelease("1.0.0"));
241 }
242}