ricecoder_orchestration/analyzers/
version_validator.rs1use crate::error::{OrchestrationError, Result};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7pub struct Version {
8 pub major: u32,
9 pub minor: u32,
10 pub patch: u32,
11}
12
13impl Version {
14 pub fn parse(version_str: &str) -> Result<Self> {
16 let parts: Vec<&str> = version_str.trim_start_matches('v').split('.').collect();
17
18 if parts.len() < 3 {
19 return Err(OrchestrationError::VersionConstraintViolation(format!(
20 "Invalid version format: {}",
21 version_str
22 )));
23 }
24
25 let major = parts[0]
26 .parse::<u32>()
27 .map_err(|_| OrchestrationError::VersionConstraintViolation(format!(
28 "Invalid major version: {}",
29 parts[0]
30 )))?;
31
32 let minor = parts[1]
33 .parse::<u32>()
34 .map_err(|_| OrchestrationError::VersionConstraintViolation(format!(
35 "Invalid minor version: {}",
36 parts[1]
37 )))?;
38
39 let patch = parts[2]
40 .parse::<u32>()
41 .map_err(|_| OrchestrationError::VersionConstraintViolation(format!(
42 "Invalid patch version: {}",
43 parts[2]
44 )))?;
45
46 Ok(Version { major, minor, patch })
47 }
48
49 pub fn version_string(&self) -> String {
51 format!("{}.{}.{}", self.major, self.minor, self.patch)
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum VersionConstraint {
58 Exact(Version),
60
61 Caret(Version),
63
64 Tilde(Version),
66
67 GreaterOrEqual(Version),
69
70 Less(Version),
72
73 Range(Version, Version),
75}
76
77impl VersionConstraint {
78 pub fn parse(constraint_str: &str) -> Result<Self> {
80 let constraint_str = constraint_str.trim();
81
82 if constraint_str.contains(" <") {
84 let parts: Vec<&str> = constraint_str.split(" <").collect();
86 if parts.len() != 2 {
87 return Err(OrchestrationError::VersionConstraintViolation(format!(
88 "Invalid range constraint: {}",
89 constraint_str
90 )));
91 }
92
93 let lower_part = parts[0].trim();
94 let lower = if lower_part.starts_with(">=") {
95 Version::parse(lower_part.strip_prefix(">=").unwrap())?
96 } else if lower_part.starts_with(">") {
97 Version::parse(lower_part.strip_prefix(">").unwrap())?
98 } else {
99 Version::parse(lower_part)?
100 };
101 let upper = Version::parse(parts[1].trim())?;
102 return Ok(VersionConstraint::Range(lower, upper));
103 }
104
105 if let Some(version_str) = constraint_str.strip_prefix('^') {
106 let version = Version::parse(version_str)?;
107 Ok(VersionConstraint::Caret(version))
108 } else if let Some(version_str) = constraint_str.strip_prefix('~') {
109 let version = Version::parse(version_str)?;
110 Ok(VersionConstraint::Tilde(version))
111 } else if let Some(version_str) = constraint_str.strip_prefix(">=") {
112 let version = Version::parse(version_str)?;
113 Ok(VersionConstraint::GreaterOrEqual(version))
114 } else if let Some(version_str) = constraint_str.strip_prefix('<') {
115 let version = Version::parse(version_str)?;
116 Ok(VersionConstraint::Less(version))
117 } else {
118 let version = Version::parse(constraint_str)?;
120 Ok(VersionConstraint::Exact(version))
121 }
122 }
123
124 pub fn is_satisfied_by(&self, version: &Version) -> bool {
126 match self {
127 VersionConstraint::Exact(v) => version == v,
128 VersionConstraint::Caret(v) => {
129 version >= v && version.major == v.major
131 }
132 VersionConstraint::Tilde(v) => {
133 version >= v && version.major == v.major && version.minor == v.minor
135 }
136 VersionConstraint::GreaterOrEqual(v) => version >= v,
137 VersionConstraint::Less(v) => version < v,
138 VersionConstraint::Range(lower, upper) => version >= lower && version < upper,
139 }
140 }
141
142 pub fn constraint_string(&self) -> String {
144 match self {
145 VersionConstraint::Exact(v) => v.version_string(),
146 VersionConstraint::Caret(v) => format!("^{}", v.version_string()),
147 VersionConstraint::Tilde(v) => format!("~{}", v.version_string()),
148 VersionConstraint::GreaterOrEqual(v) => format!(">={}", v.version_string()),
149 VersionConstraint::Less(v) => format!("<{}", v.version_string()),
150 VersionConstraint::Range(lower, upper) => {
151 format!(">={} <{}", lower.version_string(), upper.version_string())
152 }
153 }
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct VersionValidator;
160
161impl VersionValidator {
162 pub fn is_compatible(constraint: &str, new_version: &str) -> Result<bool> {
164 let constraint = VersionConstraint::parse(constraint)?;
165 let version = Version::parse(new_version)?;
166
167 Ok(constraint.is_satisfied_by(&version))
168 }
169
170 pub fn validate_update(
172 _current_version: &str,
173 new_version: &str,
174 dependent_constraints: &[&str],
175 ) -> Result<bool> {
176 let _new_ver = Version::parse(new_version)?;
177
178 for constraint_str in dependent_constraints {
180 if !Self::is_compatible(constraint_str, new_version)? {
181 return Err(OrchestrationError::VersionConstraintViolation(format!(
182 "New version {} does not satisfy constraint {}",
183 new_version, constraint_str
184 )));
185 }
186 }
187
188 Ok(true)
189 }
190
191 pub fn is_breaking_change(old_version: &str, new_version: &str) -> Result<bool> {
193 let old = Version::parse(old_version)?;
194 let new = Version::parse(new_version)?;
195
196 Ok(old.major != new.major)
198 }
199
200 pub fn find_compatible_versions(
202 constraint: &str,
203 available_versions: &[&str],
204 ) -> Result<Vec<String>> {
205 let constraint = VersionConstraint::parse(constraint)?;
206 let mut compatible = Vec::new();
207
208 for version_str in available_versions {
209 if let Ok(version) = Version::parse(version_str) {
210 if constraint.is_satisfied_by(&version) {
211 compatible.push(version_str.to_string());
212 }
213 }
214 }
215
216 Ok(compatible)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_version_parse() {
226 let v = Version::parse("1.2.3").unwrap();
227 assert_eq!(v.major, 1);
228 assert_eq!(v.minor, 2);
229 assert_eq!(v.patch, 3);
230 }
231
232 #[test]
233 fn test_version_parse_with_v_prefix() {
234 let v = Version::parse("v1.2.3").unwrap();
235 assert_eq!(v.major, 1);
236 assert_eq!(v.minor, 2);
237 assert_eq!(v.patch, 3);
238 }
239
240 #[test]
241 fn test_version_parse_invalid() {
242 assert!(Version::parse("1.2").is_err());
243 assert!(Version::parse("invalid").is_err());
244 }
245
246 #[test]
247 fn test_version_comparison() {
248 let v1 = Version::parse("1.2.3").unwrap();
249 let v2 = Version::parse("1.2.4").unwrap();
250 let v3 = Version::parse("2.0.0").unwrap();
251
252 assert!(v1 < v2);
253 assert!(v2 < v3);
254 assert!(v1 < v3);
255 }
256
257 #[test]
258 fn test_caret_constraint() {
259 let constraint = VersionConstraint::parse("^1.2.3").unwrap();
260
261 assert!(constraint.is_satisfied_by(&Version::parse("1.2.3").unwrap()));
262 assert!(constraint.is_satisfied_by(&Version::parse("1.2.4").unwrap()));
263 assert!(constraint.is_satisfied_by(&Version::parse("1.3.0").unwrap()));
264 assert!(!constraint.is_satisfied_by(&Version::parse("2.0.0").unwrap()));
265 assert!(!constraint.is_satisfied_by(&Version::parse("1.2.2").unwrap()));
266 }
267
268 #[test]
269 fn test_tilde_constraint() {
270 let constraint = VersionConstraint::parse("~1.2.3").unwrap();
271
272 assert!(constraint.is_satisfied_by(&Version::parse("1.2.3").unwrap()));
273 assert!(constraint.is_satisfied_by(&Version::parse("1.2.4").unwrap()));
274 assert!(!constraint.is_satisfied_by(&Version::parse("1.3.0").unwrap()));
275 assert!(!constraint.is_satisfied_by(&Version::parse("2.0.0").unwrap()));
276 }
277
278 #[test]
279 fn test_greater_or_equal_constraint() {
280 let constraint = VersionConstraint::parse(">=1.2.3").unwrap();
281
282 assert!(constraint.is_satisfied_by(&Version::parse("1.2.3").unwrap()));
283 assert!(constraint.is_satisfied_by(&Version::parse("1.2.4").unwrap()));
284 assert!(constraint.is_satisfied_by(&Version::parse("2.0.0").unwrap()));
285 assert!(!constraint.is_satisfied_by(&Version::parse("1.2.2").unwrap()));
286 }
287
288 #[test]
289 fn test_less_constraint() {
290 let constraint = VersionConstraint::parse("<2.0.0").unwrap();
291
292 assert!(constraint.is_satisfied_by(&Version::parse("1.2.3").unwrap()));
293 assert!(constraint.is_satisfied_by(&Version::parse("1.9.9").unwrap()));
294 assert!(!constraint.is_satisfied_by(&Version::parse("2.0.0").unwrap()));
295 assert!(!constraint.is_satisfied_by(&Version::parse("2.0.1").unwrap()));
296 }
297
298 #[test]
299 fn test_range_constraint() {
300 let constraint = VersionConstraint::parse(">=1.0.0 <2.0.0").unwrap();
302
303 assert!(constraint.is_satisfied_by(&Version::parse("1.0.0").unwrap()));
304 assert!(constraint.is_satisfied_by(&Version::parse("1.5.0").unwrap()));
305 assert!(constraint.is_satisfied_by(&Version::parse("1.9.9").unwrap()));
306 assert!(!constraint.is_satisfied_by(&Version::parse("0.9.9").unwrap()));
307 assert!(!constraint.is_satisfied_by(&Version::parse("2.0.0").unwrap()));
308 }
309
310 #[test]
311 fn test_exact_constraint() {
312 let constraint = VersionConstraint::parse("1.2.3").unwrap();
313
314 assert!(constraint.is_satisfied_by(&Version::parse("1.2.3").unwrap()));
315 assert!(!constraint.is_satisfied_by(&Version::parse("1.2.4").unwrap()));
316 assert!(!constraint.is_satisfied_by(&Version::parse("1.2.2").unwrap()));
317 }
318
319 #[test]
320 fn test_is_compatible() {
321 assert!(VersionValidator::is_compatible("^1.2.3", "1.2.4").unwrap());
322 assert!(VersionValidator::is_compatible("^1.2.3", "1.3.0").unwrap());
323 assert!(!VersionValidator::is_compatible("^1.2.3", "2.0.0").unwrap());
324 }
325
326 #[test]
327 fn test_validate_update() {
328 let constraints = vec!["^1.0.0", "~1.2.0"];
329 assert!(VersionValidator::validate_update("1.2.3", "1.2.4", &constraints).unwrap());
330 assert!(VersionValidator::validate_update("1.2.3", "1.3.0", &constraints).is_err());
331 }
332
333 #[test]
334 fn test_is_breaking_change() {
335 assert!(!VersionValidator::is_breaking_change("1.2.3", "1.2.4").unwrap());
336 assert!(!VersionValidator::is_breaking_change("1.2.3", "1.3.0").unwrap());
337 assert!(VersionValidator::is_breaking_change("1.2.3", "2.0.0").unwrap());
338 }
339
340 #[test]
341 fn test_find_compatible_versions() {
342 let available = vec!["1.0.0", "1.2.3", "1.2.4", "1.3.0", "2.0.0"];
343 let compatible = VersionValidator::find_compatible_versions("^1.2.3", &available).unwrap();
344
345 assert_eq!(compatible.len(), 3);
347 assert!(compatible.contains(&"1.2.3".to_string()));
348 assert!(compatible.contains(&"1.2.4".to_string()));
349 assert!(compatible.contains(&"1.3.0".to_string()));
350 }
351}