1use serde::{Deserialize, Serialize};
7use std::cmp::Ordering;
8use std::fmt;
9use std::str::FromStr;
10
11#[derive(Debug, Clone)]
13pub struct VersionManager {
14 supported_versions: Vec<Version>,
16 current_version: Version,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct Version {
23 pub year: u16,
25 pub month: u8,
27 pub day: u8,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum VersionCompatibility {
34 Compatible,
36 CompatibleWithWarnings(Vec<String>),
38 Incompatible(String),
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum VersionRequirement {
45 Exact(Version),
47 Minimum(Version),
49 Maximum(Version),
51 Range(Version, Version),
53 Any(Vec<Version>),
55}
56
57impl Version {
58 pub fn new(year: u16, month: u8, day: u8) -> Result<Self, VersionError> {
60 if !(1..=12).contains(&month) {
61 return Err(VersionError::InvalidMonth(month));
62 }
63
64 if !(1..=31).contains(&day) {
65 return Err(VersionError::InvalidDay(day));
66 }
67
68 if month == 2 && day > 29 {
70 return Err(VersionError::InvalidDay(day));
71 }
72
73 if matches!(month, 4 | 6 | 9 | 11) && day > 30 {
74 return Err(VersionError::InvalidDay(day));
75 }
76
77 Ok(Self { year, month, day })
78 }
79
80 pub fn current() -> Self {
82 Self {
83 year: 2025,
84 month: 6,
85 day: 18,
86 }
87 }
88
89 pub fn is_newer_than(&self, other: &Version) -> bool {
91 self > other
92 }
93
94 pub fn is_older_than(&self, other: &Version) -> bool {
96 self < other
97 }
98
99 pub fn is_compatible_with(&self, other: &Version) -> bool {
101 self.year == other.year
104 }
105
106 pub fn to_date_string(&self) -> String {
108 format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
109 }
110
111 pub fn from_date_string(s: &str) -> Result<Self, VersionError> {
113 s.parse()
114 }
115
116 pub fn known_versions() -> Vec<Version> {
118 vec![
119 Version::new(2025, 6, 18).unwrap(), Version::new(2024, 11, 5).unwrap(), Version::new(2024, 6, 25).unwrap(), ]
123 }
124}
125
126impl fmt::Display for Version {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 write!(f, "{}", self.to_date_string())
129 }
130}
131
132impl FromStr for Version {
133 type Err = VersionError;
134
135 fn from_str(s: &str) -> Result<Self, Self::Err> {
136 let parts: Vec<&str> = s.split('-').collect();
137
138 if parts.len() != 3 {
139 return Err(VersionError::InvalidFormat(s.to_string()));
140 }
141
142 let year = parts[0]
143 .parse::<u16>()
144 .map_err(|_| VersionError::InvalidYear(parts[0].to_string()))?;
145 let month = parts[1]
146 .parse::<u8>()
147 .map_err(|_| VersionError::InvalidMonth(parts[1].parse().unwrap_or(0)))?;
148 let day = parts[2]
149 .parse::<u8>()
150 .map_err(|_| VersionError::InvalidDay(parts[2].parse().unwrap_or(0)))?;
151
152 Self::new(year, month, day)
153 }
154}
155
156impl PartialOrd for Version {
157 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
158 Some(self.cmp(other))
159 }
160}
161
162impl Ord for Version {
163 fn cmp(&self, other: &Self) -> Ordering {
164 (self.year, self.month, self.day).cmp(&(other.year, other.month, other.day))
165 }
166}
167
168impl VersionManager {
169 pub fn new(supported_versions: Vec<Version>) -> Result<Self, VersionError> {
171 if supported_versions.is_empty() {
172 return Err(VersionError::NoSupportedVersions);
173 }
174
175 let mut versions = supported_versions;
176 versions.sort_by(|a, b| b.cmp(a)); let current_version = versions[0].clone();
179
180 Ok(Self {
181 supported_versions: versions,
182 current_version,
183 })
184 }
185
186 pub fn with_default_versions() -> Self {
188 Self::new(Version::known_versions()).unwrap()
189 }
190 pub fn current_version(&self) -> &Version {
192 &self.current_version
193 }
194
195 pub fn supported_versions(&self) -> &[Version] {
197 &self.supported_versions
198 }
199
200 pub fn is_version_supported(&self, version: &Version) -> bool {
202 self.supported_versions.contains(version)
203 }
204
205 pub fn negotiate_version(&self, client_versions: &[Version]) -> Option<Version> {
207 for server_version in &self.supported_versions {
209 if client_versions.contains(server_version) {
210 return Some(server_version.clone());
211 }
212 }
213
214 None
215 }
216
217 pub fn check_compatibility(
219 &self,
220 client_version: &Version,
221 server_version: &Version,
222 ) -> VersionCompatibility {
223 if client_version == server_version {
224 return VersionCompatibility::Compatible;
225 }
226
227 if client_version.year == server_version.year {
229 let warning = format!(
230 "Version mismatch but compatible: client={client_version}, server={server_version}"
231 );
232 return VersionCompatibility::CompatibleWithWarnings(vec![warning]);
233 }
234
235 let reason =
237 format!("Incompatible versions: client={client_version}, server={server_version}");
238 VersionCompatibility::Incompatible(reason)
239 }
240
241 pub fn minimum_version(&self) -> &Version {
243 self.supported_versions.last().unwrap() }
245
246 pub fn maximum_version(&self) -> &Version {
248 &self.supported_versions[0] }
250
251 pub fn satisfies_requirement(
253 &self,
254 version: &Version,
255 requirement: &VersionRequirement,
256 ) -> bool {
257 match requirement {
258 VersionRequirement::Exact(required) => version == required,
259 VersionRequirement::Minimum(min) => version >= min,
260 VersionRequirement::Maximum(max) => version <= max,
261 VersionRequirement::Range(min, max) => version >= min && version <= max,
262 VersionRequirement::Any(versions) => versions.contains(version),
263 }
264 }
265}
266
267impl Default for VersionManager {
268 fn default() -> Self {
269 Self::with_default_versions()
270 }
271}
272
273impl VersionRequirement {
274 pub fn exact(version: Version) -> Self {
276 Self::Exact(version)
277 }
278
279 pub fn minimum(version: Version) -> Self {
281 Self::Minimum(version)
282 }
283
284 pub fn maximum(version: Version) -> Self {
286 Self::Maximum(version)
287 }
288
289 pub fn range(min: Version, max: Version) -> Result<Self, VersionError> {
291 if min > max {
292 return Err(VersionError::InvalidRange(min, max));
293 }
294 Ok(Self::Range(min, max))
295 }
296
297 pub fn any(versions: Vec<Version>) -> Result<Self, VersionError> {
299 if versions.is_empty() {
300 return Err(VersionError::EmptyVersionList);
301 }
302 Ok(Self::Any(versions))
303 }
304
305 pub fn is_satisfied_by(&self, version: &Version) -> bool {
307 match self {
308 Self::Exact(required) => version == required,
309 Self::Minimum(min) => version >= min,
310 Self::Maximum(max) => version <= max,
311 Self::Range(min, max) => version >= min && version <= max,
312 Self::Any(versions) => versions.contains(version),
313 }
314 }
315}
316
317#[derive(Debug, Clone, thiserror::Error)]
319pub enum VersionError {
320 #[error("Invalid version format: {0}")]
322 InvalidFormat(String),
323 #[error("Invalid year: {0}")]
325 InvalidYear(String),
326 #[error("Invalid month: {0} (must be 1-12)")]
328 InvalidMonth(u8),
329 #[error("Invalid day: {0} (must be 1-31)")]
331 InvalidDay(u8),
332 #[error("No supported versions provided")]
334 NoSupportedVersions,
335 #[error("Invalid version range: {0} > {1}")]
337 InvalidRange(Version, Version),
338 #[error("Empty version list")]
340 EmptyVersionList,
341}
342
343pub mod utils {
345 use super::*;
346
347 pub fn parse_versions(version_strings: &[&str]) -> Result<Vec<Version>, VersionError> {
349 version_strings.iter().map(|s| s.parse()).collect()
350 }
351
352 pub fn newest_version(versions: &[Version]) -> Option<&Version> {
354 versions.iter().max()
355 }
356
357 pub fn oldest_version(versions: &[Version]) -> Option<&Version> {
359 versions.iter().min()
360 }
361
362 pub fn are_all_compatible(versions: &[Version]) -> bool {
364 if versions.len() < 2 {
365 return true;
366 }
367
368 let first = &versions[0];
369 versions.iter().all(|v| first.is_compatible_with(v))
370 }
371
372 pub fn compatibility_description(compatibility: &VersionCompatibility) -> String {
374 match compatibility {
375 VersionCompatibility::Compatible => "Fully compatible".to_string(),
376 VersionCompatibility::CompatibleWithWarnings(warnings) => {
377 format!("Compatible with warnings: {}", warnings.join(", "))
378 }
379 VersionCompatibility::Incompatible(reason) => {
380 format!("Incompatible: {reason}")
381 }
382 }
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_version_creation() {
392 let version = Version::new(2025, 6, 18).unwrap();
393 assert_eq!(version.year, 2025);
394 assert_eq!(version.month, 6);
395 assert_eq!(version.day, 18);
396
397 assert!(Version::new(2025, 13, 18).is_err());
399
400 assert!(Version::new(2025, 6, 32).is_err());
402 }
403
404 #[test]
405 fn test_version_parsing() {
406 let version: Version = "2025-06-18".parse().unwrap();
407 assert_eq!(version, Version::new(2025, 6, 18).unwrap());
408
409 assert!("2025/06/18".parse::<Version>().is_err());
411 assert!("invalid".parse::<Version>().is_err());
412 }
413
414 #[test]
415 fn test_version_comparison() {
416 let v1 = Version::new(2025, 6, 18).unwrap();
417 let v2 = Version::new(2024, 11, 5).unwrap();
418 let v3 = Version::new(2025, 6, 18).unwrap();
419
420 assert!(v1 > v2);
421 assert!(v1.is_newer_than(&v2));
422 assert!(v2.is_older_than(&v1));
423 assert_eq!(v1, v3);
424 }
425
426 #[test]
427 fn test_version_compatibility() {
428 let v1 = Version::new(2025, 6, 18).unwrap();
429 let v2 = Version::new(2025, 12, 1).unwrap(); let v3 = Version::new(2024, 6, 18).unwrap(); assert!(v1.is_compatible_with(&v2));
433 assert!(!v1.is_compatible_with(&v3));
434 }
435
436 #[test]
437 fn test_version_manager() {
438 let versions = vec![
439 Version::new(2025, 6, 18).unwrap(),
440 Version::new(2024, 11, 5).unwrap(),
441 ];
442
443 let manager = VersionManager::new(versions).unwrap();
444
445 assert_eq!(
446 manager.current_version(),
447 &Version::new(2025, 6, 18).unwrap()
448 );
449 assert!(manager.is_version_supported(&Version::new(2024, 11, 5).unwrap()));
450 assert!(!manager.is_version_supported(&Version::new(2023, 1, 1).unwrap()));
451 }
452
453 #[test]
454 fn test_version_negotiation() {
455 let manager = VersionManager::default();
456
457 let client_versions = vec![
458 Version::new(2024, 11, 5).unwrap(),
459 Version::new(2025, 6, 18).unwrap(),
460 ];
461
462 let negotiated = manager.negotiate_version(&client_versions);
463 assert_eq!(negotiated, Some(Version::new(2025, 6, 18).unwrap()));
464 }
465
466 #[test]
467 fn test_version_requirements() {
468 let version = Version::new(2025, 6, 18).unwrap();
469
470 let exact_req = VersionRequirement::exact(version.clone());
471 assert!(exact_req.is_satisfied_by(&version));
472
473 let min_req = VersionRequirement::minimum(Version::new(2024, 1, 1).unwrap());
474 assert!(min_req.is_satisfied_by(&version));
475
476 let max_req = VersionRequirement::maximum(Version::new(2024, 1, 1).unwrap());
477 assert!(!max_req.is_satisfied_by(&version));
478 }
479
480 #[test]
481 fn test_compatibility_checking() {
482 let manager = VersionManager::default();
483
484 let v1 = Version::new(2025, 6, 18).unwrap();
485 let v2 = Version::new(2025, 12, 1).unwrap();
486 let v3 = Version::new(2024, 1, 1).unwrap();
487
488 let compat = manager.check_compatibility(&v1, &v2);
490 assert!(matches!(
491 compat,
492 VersionCompatibility::CompatibleWithWarnings(_)
493 ));
494
495 let compat = manager.check_compatibility(&v1, &v3);
497 assert!(matches!(compat, VersionCompatibility::Incompatible(_)));
498
499 let compat = manager.check_compatibility(&v1, &v1);
501 assert_eq!(compat, VersionCompatibility::Compatible);
502 }
503
504 #[test]
505 fn test_utils() {
506 let versions = utils::parse_versions(&["2025-06-18", "2024-11-05"]).unwrap();
507 assert_eq!(versions.len(), 2);
508
509 let newest = utils::newest_version(&versions);
510 assert_eq!(newest, Some(&Version::new(2025, 6, 18).unwrap()));
511
512 let oldest = utils::oldest_version(&versions);
513 assert_eq!(oldest, Some(&Version::new(2024, 11, 5).unwrap()));
514 }
515}