use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolSpec {
pub name: String,
pub version: Option<String>,
pub dependencies: Vec<DependencySpec>,
pub metadata: HashMap<String, String>,
pub auto_installable: bool,
pub priority: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DependencySpec {
pub tool_name: String,
pub version_constraint: Option<VersionConstraint>,
pub dependency_type: DependencyType,
pub description: String,
pub optional: bool,
pub platforms: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DependencyType {
Runtime,
Build,
Development,
Peer,
Optional,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionConstraint {
pub expression: String,
pub allow_prerelease: bool,
}
impl Default for ToolSpec {
fn default() -> Self {
Self {
name: String::new(),
version: None,
dependencies: Vec::new(),
metadata: HashMap::new(),
auto_installable: true,
priority: 0,
}
}
}
impl DependencySpec {
pub fn required(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
tool_name: tool_name.into(),
version_constraint: None,
dependency_type: DependencyType::Runtime,
description: description.into(),
optional: false,
platforms: vec![],
}
}
pub fn optional(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
tool_name: tool_name.into(),
version_constraint: None,
dependency_type: DependencyType::Optional,
description: description.into(),
optional: true,
platforms: vec![],
}
}
pub fn build(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
tool_name: tool_name.into(),
version_constraint: None,
dependency_type: DependencyType::Build,
description: description.into(),
optional: false,
platforms: vec![],
}
}
pub fn with_version(mut self, constraint: impl Into<String>) -> Self {
self.version_constraint = Some(VersionConstraint {
expression: constraint.into(),
allow_prerelease: false,
});
self
}
pub fn with_version_prerelease(mut self, constraint: impl Into<String>) -> Self {
self.version_constraint = Some(VersionConstraint {
expression: constraint.into(),
allow_prerelease: true,
});
self
}
pub fn for_platforms(mut self, platforms: Vec<String>) -> Self {
self.platforms = platforms;
self
}
pub fn applies_to_platform(&self, platform: &str) -> bool {
self.platforms.is_empty() || self.platforms.contains(&platform.to_string())
}
}
impl VersionConstraint {
pub fn new(expression: impl Into<String>) -> Self {
Self {
expression: expression.into(),
allow_prerelease: false,
}
}
pub fn with_prerelease(expression: impl Into<String>) -> Self {
Self {
expression: expression.into(),
allow_prerelease: true,
}
}
pub fn is_satisfied_by(&self, version: &str) -> bool {
!version.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dependency_spec_creation() {
let dep = DependencySpec::required("node", "Node.js runtime")
.with_version(">=16.0.0")
.for_platforms(vec!["linux".to_string(), "macos".to_string()]);
assert_eq!(dep.tool_name, "node");
assert_eq!(dep.dependency_type, DependencyType::Runtime);
assert!(!dep.optional);
assert!(dep.applies_to_platform("linux"));
assert!(!dep.applies_to_platform("windows"));
}
#[test]
fn test_tool_spec_default() {
let tool = ToolSpec::default();
assert!(tool.name.is_empty());
assert!(tool.dependencies.is_empty());
assert!(tool.auto_installable);
assert_eq!(tool.priority, 0);
}
#[test]
fn test_version_constraint() {
let constraint = VersionConstraint::new(">=1.0.0");
assert_eq!(constraint.expression, ">=1.0.0");
assert!(!constraint.allow_prerelease);
let constraint_pre = VersionConstraint::with_prerelease("^2.0.0-beta");
assert!(constraint_pre.allow_prerelease);
}
#[test]
fn test_dependency_types() {
let runtime_dep = DependencySpec::required("node", "Runtime dependency");
assert_eq!(runtime_dep.dependency_type, DependencyType::Runtime);
let build_dep = DependencySpec::build("gcc", "Build dependency");
assert_eq!(build_dep.dependency_type, DependencyType::Build);
let optional_dep = DependencySpec::optional("docker", "Optional dependency");
assert_eq!(optional_dep.dependency_type, DependencyType::Optional);
assert!(optional_dep.optional);
}
}