1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ToolSpec {
9 pub name: String,
11 pub version: Option<String>,
13 pub dependencies: Vec<DependencySpec>,
15 pub metadata: HashMap<String, String>,
17 pub auto_installable: bool,
19 pub priority: i32,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct DependencySpec {
26 pub tool_name: String,
28 pub version_constraint: Option<VersionConstraint>,
30 pub dependency_type: DependencyType,
32 pub description: String,
34 pub optional: bool,
36 pub platforms: Vec<String>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub enum DependencyType {
43 Runtime,
45 Build,
47 Development,
49 Peer,
51 Optional,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct VersionConstraint {
58 pub expression: String,
60 pub allow_prerelease: bool,
62}
63
64impl Default for ToolSpec {
65 fn default() -> Self {
66 Self {
67 name: String::new(),
68 version: None,
69 dependencies: Vec::new(),
70 metadata: HashMap::new(),
71 auto_installable: true,
72 priority: 0,
73 }
74 }
75}
76
77impl DependencySpec {
78 pub fn required(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
80 Self {
81 tool_name: tool_name.into(),
82 version_constraint: None,
83 dependency_type: DependencyType::Runtime,
84 description: description.into(),
85 optional: false,
86 platforms: vec![],
87 }
88 }
89
90 pub fn optional(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
92 Self {
93 tool_name: tool_name.into(),
94 version_constraint: None,
95 dependency_type: DependencyType::Optional,
96 description: description.into(),
97 optional: true,
98 platforms: vec![],
99 }
100 }
101
102 pub fn build(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
104 Self {
105 tool_name: tool_name.into(),
106 version_constraint: None,
107 dependency_type: DependencyType::Build,
108 description: description.into(),
109 optional: false,
110 platforms: vec![],
111 }
112 }
113
114 pub fn with_version(mut self, constraint: impl Into<String>) -> Self {
116 self.version_constraint = Some(VersionConstraint {
117 expression: constraint.into(),
118 allow_prerelease: false,
119 });
120 self
121 }
122
123 pub fn with_version_prerelease(mut self, constraint: impl Into<String>) -> Self {
125 self.version_constraint = Some(VersionConstraint {
126 expression: constraint.into(),
127 allow_prerelease: true,
128 });
129 self
130 }
131
132 pub fn for_platforms(mut self, platforms: Vec<String>) -> Self {
134 self.platforms = platforms;
135 self
136 }
137
138 pub fn applies_to_platform(&self, platform: &str) -> bool {
140 self.platforms.is_empty() || self.platforms.contains(&platform.to_string())
141 }
142}
143
144impl VersionConstraint {
145 pub fn new(expression: impl Into<String>) -> Self {
147 Self {
148 expression: expression.into(),
149 allow_prerelease: false,
150 }
151 }
152
153 pub fn with_prerelease(expression: impl Into<String>) -> Self {
155 Self {
156 expression: expression.into(),
157 allow_prerelease: true,
158 }
159 }
160
161 pub fn is_satisfied_by(&self, version: &str) -> bool {
163 !version.is_empty()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_dependency_spec_creation() {
175 let dep = DependencySpec::required("node", "Node.js runtime")
176 .with_version(">=16.0.0")
177 .for_platforms(vec!["linux".to_string(), "macos".to_string()]);
178
179 assert_eq!(dep.tool_name, "node");
180 assert_eq!(dep.dependency_type, DependencyType::Runtime);
181 assert!(!dep.optional);
182 assert!(dep.applies_to_platform("linux"));
183 assert!(!dep.applies_to_platform("windows"));
184 }
185
186 #[test]
187 fn test_tool_spec_default() {
188 let tool = ToolSpec::default();
189 assert!(tool.name.is_empty());
190 assert!(tool.dependencies.is_empty());
191 assert!(tool.auto_installable);
192 assert_eq!(tool.priority, 0);
193 }
194
195 #[test]
196 fn test_version_constraint() {
197 let constraint = VersionConstraint::new(">=1.0.0");
198 assert_eq!(constraint.expression, ">=1.0.0");
199 assert!(!constraint.allow_prerelease);
200
201 let constraint_pre = VersionConstraint::with_prerelease("^2.0.0-beta");
202 assert!(constraint_pre.allow_prerelease);
203 }
204
205 #[test]
206 fn test_dependency_types() {
207 let runtime_dep = DependencySpec::required("node", "Runtime dependency");
208 assert_eq!(runtime_dep.dependency_type, DependencyType::Runtime);
209
210 let build_dep = DependencySpec::build("gcc", "Build dependency");
211 assert_eq!(build_dep.dependency_type, DependencyType::Build);
212
213 let optional_dep = DependencySpec::optional("docker", "Optional dependency");
214 assert_eq!(optional_dep.dependency_type, DependencyType::Optional);
215 assert!(optional_dep.optional);
216 }
217}