use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
pub type VxResult<T> = Result<T, VxError>;
#[derive(thiserror::Error, Debug)]
pub enum VxError {
#[error("Tool '{tool}' not found")]
ToolNotFound { tool: String },
#[error("Version '{version}' not found for tool '{tool}'")]
VersionNotFound { tool: String, version: String },
#[error("Installation failed for '{tool}': {reason}")]
InstallationFailed { tool: String, reason: String },
#[error("Execution failed: {message}")]
ExecutionFailed { message: String },
#[error("Configuration error: {message}")]
ConfigError { message: String },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Other error: {0}")]
Other(#[from] anyhow::Error),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Platform {
pub os: String,
pub arch: String,
}
impl Platform {
pub fn current() -> Self {
Self {
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
}
}
}
impl std::fmt::Display for Platform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let platform_str = match (self.os.as_str(), self.arch.as_str()) {
("windows", "x86_64") => "win-x64".to_string(),
("windows", "aarch64") => "win-arm64".to_string(),
("macos", "x86_64") => "darwin-x64".to_string(),
("macos", "aarch64") => "darwin-arm64".to_string(),
("linux", "x86_64") => "linux-x64".to_string(),
("linux", "aarch64") => "linux-arm64".to_string(),
_ => format!("{}-{}", self.os, self.arch),
};
write!(f, "{}", platform_str)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Version {
pub version: String,
pub prerelease: bool,
pub metadata: HashMap<String, String>,
}
impl Version {
pub fn new(version: impl Into<String>) -> Self {
Self {
version: version.into(),
prerelease: false,
metadata: HashMap::new(),
}
}
pub fn prerelease(version: impl Into<String>) -> Self {
Self {
version: version.into(),
prerelease: true,
metadata: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolSpec {
pub name: String,
pub description: String,
pub platforms: Vec<Platform>,
pub versions: Vec<Version>,
pub install_methods: Vec<String>,
pub dependencies: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstallConfig {
pub tool: String,
pub version: String,
pub platform: Platform,
pub install_dir: PathBuf,
pub download_url: Option<String>,
pub method: InstallMethod,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InstallMethod {
Archive { format: ArchiveFormat },
Binary,
PackageManager { manager: String },
Custom { script: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ArchiveFormat {
Zip,
TarGz,
TarXz,
}
#[derive(Debug, Clone)]
pub struct ExecutionContext {
pub working_dir: PathBuf,
pub env_vars: HashMap<String, String>,
pub args: Vec<String>,
}
impl Default for ExecutionContext {
fn default() -> Self {
Self {
working_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
env_vars: HashMap::new(),
args: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct ExecutionResult {
pub exit_code: i32,
pub duration: std::time::Duration,
pub success: bool,
}
#[async_trait]
pub trait ToolManager: Send + Sync {
async fn is_available(&self, tool: &str) -> VxResult<bool>;
async fn get_version(&self, tool: &str) -> VxResult<Option<Version>>;
async fn install(&self, config: &InstallConfig) -> VxResult<()>;
async fn execute(&self, tool: &str, context: &ExecutionContext) -> VxResult<ExecutionResult>;
async fn list_tools(&self) -> VxResult<Vec<String>>;
}
#[async_trait]
pub trait ToolResolver: Send + Sync {
async fn resolve(&self, tool: &str) -> VxResult<ToolSpec>;
async fn get_install_config(&self, tool: &str, version: &str) -> VxResult<InstallConfig>;
}
#[async_trait]
pub trait VersionManager: Send + Sync {
async fn list_versions(&self, tool: &str) -> VxResult<Vec<Version>>;
async fn get_latest(&self, tool: &str) -> VxResult<Version>;
fn satisfies(&self, version: &Version, constraint: &str) -> bool;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VxConfig {
pub install_dir: PathBuf,
pub cache_dir: PathBuf,
pub platform: Platform,
pub registries: Vec<String>,
pub tools: HashMap<String, serde_json::Value>,
}
impl Default for VxConfig {
fn default() -> Self {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let vx_dir = home_dir.join(".vx");
Self {
install_dir: vx_dir.join("tools"),
cache_dir: vx_dir.join("cache"),
platform: Platform::current(),
registries: vec!["https://registry.vx.dev".to_string()],
tools: HashMap::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_platform_current() {
let platform = Platform::current();
assert!(!platform.os.is_empty());
assert!(!platform.arch.is_empty());
}
#[test]
fn test_platform_to_string() {
let platform = Platform {
os: "linux".to_string(),
arch: "x86_64".to_string(),
};
assert_eq!(platform.to_string(), "linux-x64");
}
#[test]
fn test_version_creation() {
let version = Version::new("1.0.0");
assert_eq!(version.version, "1.0.0");
assert!(!version.prerelease);
let prerelease = Version::prerelease("2.0.0-beta.1");
assert!(prerelease.prerelease);
}
#[test]
fn test_vx_config_default() {
let config = VxConfig::default();
assert!(config.install_dir.to_string_lossy().contains(".vx"));
assert!(!config.registries.is_empty());
}
}