vx_tool_standard/
lib.rs

1//! # vx-tool-standard
2//!
3//! Standard interfaces and utilities for implementing vx tools.
4//!
5//! This crate provides standardized traits and types that tool implementations
6//! should use to ensure consistency across the vx ecosystem.
7
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10use vx_core::{Platform, VxResult};
11use vx_installer::InstallConfig;
12
13/// Standard tool configuration interface
14pub trait StandardToolConfig {
15    /// Get the tool name
16    fn tool_name() -> &'static str;
17
18    /// Create installation configuration for a version
19    fn create_install_config(version: &str, install_dir: PathBuf) -> InstallConfig;
20
21    /// Get available installation methods
22    fn get_install_methods() -> Vec<String>;
23
24    /// Check if the tool supports automatic installation
25    fn supports_auto_install() -> bool;
26
27    /// Get manual installation instructions
28    fn get_manual_instructions() -> String;
29
30    /// Get tool dependencies
31    fn get_dependencies() -> Vec<ToolDependency>;
32
33    /// Get default version
34    fn get_default_version() -> &'static str;
35}
36
37/// Standard URL builder interface
38pub trait StandardUrlBuilder {
39    /// Generate download URL for a version
40    fn download_url(version: &str) -> Option<String>;
41
42    /// Get platform-specific filename
43    fn get_filename(version: &str) -> String;
44
45    /// Get platform string for downloads
46    fn get_platform_string() -> String;
47}
48
49/// Tool dependency specification
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ToolDependency {
52    /// Name of the dependency tool
53    pub tool_name: String,
54    /// Human-readable description
55    pub description: String,
56    /// Whether this dependency is required
57    pub required: bool,
58    /// Version requirement (e.g., ">=16.0.0")
59    pub version_requirement: Option<String>,
60    /// Platforms this dependency applies to
61    pub platforms: Vec<Platform>,
62}
63
64impl ToolDependency {
65    /// Create a required dependency
66    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    /// Create an optional dependency
77    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    /// Set version requirement
88    pub fn with_version(mut self, requirement: impl Into<String>) -> Self {
89        self.version_requirement = Some(requirement.into());
90        self
91    }
92
93    /// Set platform constraints
94    pub fn for_platforms(mut self, platforms: Vec<Platform>) -> Self {
95        self.platforms = platforms;
96        self
97    }
98}
99
100/// Standard tool runtime interface
101pub trait ToolRuntime {
102    /// Check if the tool is available
103    fn is_available(&self) -> impl std::future::Future<Output = VxResult<bool>> + Send;
104
105    /// Get installed version
106    fn get_version(&self) -> impl std::future::Future<Output = VxResult<Option<String>>> + Send;
107
108    /// Get tool installation path
109    fn get_path(&self) -> impl std::future::Future<Output = VxResult<Option<PathBuf>>> + Send;
110
111    /// Execute the tool with arguments
112    fn execute(&self, args: &[String]) -> impl std::future::Future<Output = VxResult<i32>> + Send;
113}
114
115/// Version parser interface
116pub trait VersionParser {
117    /// Parse version from tool output
118    fn parse_version(output: &str) -> Option<String>;
119
120    /// Validate version format
121    fn is_valid_version(version: &str) -> bool;
122
123    /// Compare two versions
124    fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering;
125}
126
127/// Platform-specific URL builder utilities
128pub struct PlatformUrlBuilder;
129
130impl PlatformUrlBuilder {
131    /// Get standard platform string for downloads
132    pub fn get_platform_string() -> String {
133        let platform = Platform::current();
134        platform.to_string()
135    }
136
137    /// Get archive extension for current platform
138    pub fn get_archive_extension() -> &'static str {
139        if cfg!(windows) {
140            "zip"
141        } else {
142            "tar.gz"
143        }
144    }
145
146    /// Get executable extension for current platform
147    pub fn get_exe_extension() -> &'static str {
148        if cfg!(windows) {
149            ".exe"
150        } else {
151            ""
152        }
153    }
154}
155
156/// Common URL building utilities
157pub struct UrlUtils;
158
159impl UrlUtils {
160    /// Build GitHub release URL
161    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    /// Build official download URL
169    pub fn official_download_url(base_url: &str, version: &str, filename: &str) -> String {
170        format!("{}/v{}/{}", base_url, version, filename)
171    }
172}
173
174/// Version utilities
175pub struct VersionUtils;
176
177impl VersionUtils {
178    /// Check if version is "latest"
179    pub fn is_latest(version: &str) -> bool {
180        version == "latest" || version == "stable"
181    }
182
183    /// Normalize version string
184    pub fn normalize_version(version: &str) -> String {
185        // Remove 'v' prefix if present
186        version.strip_prefix('v').unwrap_or(version).to_string()
187    }
188
189    /// Check if version is prerelease
190    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}