turbomcp_protocol/
versioning.rs

1//! # Protocol Versioning and Compatibility
2//!
3//! This module provides comprehensive protocol version management and compatibility
4//! checking for MCP implementations.
5
6use serde::{Deserialize, Serialize};
7use std::cmp::Ordering;
8use std::fmt;
9use std::str::FromStr;
10
11/// Version manager for handling protocol versions
12#[derive(Debug, Clone)]
13pub struct VersionManager {
14    /// Supported versions in order of preference
15    supported_versions: Vec<Version>,
16    /// Current protocol version
17    current_version: Version,
18}
19
20/// Semantic version representation
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct Version {
23    /// Year component
24    pub year: u16,
25    /// Month component  
26    pub month: u8,
27    /// Day component
28    pub day: u8,
29}
30
31/// Version compatibility result
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum VersionCompatibility {
34    /// Versions are fully compatible
35    Compatible,
36    /// Versions are compatible with warnings
37    CompatibleWithWarnings(Vec<String>),
38    /// Versions are incompatible
39    Incompatible(String),
40}
41
42/// Version requirement specification
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum VersionRequirement {
45    /// Exact version match
46    Exact(Version),
47    /// Minimum version required
48    Minimum(Version),
49    /// Maximum version supported
50    Maximum(Version),
51    /// Version range (inclusive)
52    Range(Version, Version),
53    /// Any version from the list
54    Any(Vec<Version>),
55}
56
57impl Version {
58    /// Create a new version
59    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        // Basic month/day validation
69        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    /// Get the current MCP protocol version
81    pub fn current() -> Self {
82        Self {
83            year: 2025,
84            month: 6,
85            day: 18,
86        }
87    }
88
89    /// Check if this version is newer than another
90    pub fn is_newer_than(&self, other: &Version) -> bool {
91        self > other
92    }
93
94    /// Check if this version is older than another
95    pub fn is_older_than(&self, other: &Version) -> bool {
96        self < other
97    }
98
99    /// Check if this version is compatible with another
100    pub fn is_compatible_with(&self, other: &Version) -> bool {
101        // For MCP, we consider versions compatible if they're the same
102        // or if the difference is minor (same year)
103        self.year == other.year
104    }
105
106    /// Get version as a date string (YYYY-MM-DD)
107    pub fn to_date_string(&self) -> String {
108        format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
109    }
110
111    /// Parse version from date string
112    pub fn from_date_string(s: &str) -> Result<Self, VersionError> {
113        s.parse()
114    }
115
116    /// Get all known MCP versions
117    pub fn known_versions() -> Vec<Version> {
118        vec![
119            Version::new(2025, 6, 18).unwrap(), // Current
120            Version::new(2024, 11, 5).unwrap(), // Previous
121            Version::new(2024, 6, 25).unwrap(), // Older
122        ]
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    /// Create a new version manager
170    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)); // Sort newest first
177
178        let current_version = versions[0].clone();
179
180        Ok(Self {
181            supported_versions: versions,
182            current_version,
183        })
184    }
185
186    /// Create a version manager with default MCP versions
187    pub fn with_default_versions() -> Self {
188        Self::new(Version::known_versions()).unwrap()
189    }
190    /// Get the current version
191    pub fn current_version(&self) -> &Version {
192        &self.current_version
193    }
194
195    /// Get all supported versions
196    pub fn supported_versions(&self) -> &[Version] {
197        &self.supported_versions
198    }
199
200    /// Check if a version is supported
201    pub fn is_version_supported(&self, version: &Version) -> bool {
202        self.supported_versions.contains(version)
203    }
204
205    /// Find the best compatible version for a client request
206    pub fn negotiate_version(&self, client_versions: &[Version]) -> Option<Version> {
207        // Find the newest version that both client and server support
208        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    /// Check compatibility between two versions
218    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        // Check if versions are in the same year (considered compatible)
228        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        // Major version difference
236        let reason =
237            format!("Incompatible versions: client={client_version}, server={server_version}");
238        VersionCompatibility::Incompatible(reason)
239    }
240
241    /// Get the minimum supported version
242    pub fn minimum_version(&self) -> &Version {
243        self.supported_versions.last().unwrap() // Last because sorted newest first
244    }
245
246    /// Get the maximum supported version  
247    pub fn maximum_version(&self) -> &Version {
248        &self.supported_versions[0] // First because sorted newest first
249    }
250
251    /// Check if a version requirement is satisfied
252    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    /// Create an exact version requirement
275    pub fn exact(version: Version) -> Self {
276        Self::Exact(version)
277    }
278
279    /// Create a minimum version requirement
280    pub fn minimum(version: Version) -> Self {
281        Self::Minimum(version)
282    }
283
284    /// Create a maximum version requirement
285    pub fn maximum(version: Version) -> Self {
286        Self::Maximum(version)
287    }
288
289    /// Create a version range requirement
290    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    /// Create an "any of" requirement
298    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    /// Check if a version satisfies this requirement
306    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/// Version-related errors
318#[derive(Debug, Clone, thiserror::Error)]
319pub enum VersionError {
320    /// Invalid version format
321    #[error("Invalid version format: {0}")]
322    InvalidFormat(String),
323    /// Invalid year
324    #[error("Invalid year: {0}")]
325    InvalidYear(String),
326    /// Invalid month
327    #[error("Invalid month: {0} (must be 1-12)")]
328    InvalidMonth(u8),
329    /// Invalid day
330    #[error("Invalid day: {0} (must be 1-31)")]
331    InvalidDay(u8),
332    /// No supported versions
333    #[error("No supported versions provided")]
334    NoSupportedVersions,
335    /// Invalid version range
336    #[error("Invalid version range: {0} > {1}")]
337    InvalidRange(Version, Version),
338    /// Empty version list
339    #[error("Empty version list")]
340    EmptyVersionList,
341}
342
343/// Utility functions for version management
344pub mod utils {
345    use super::*;
346
347    /// Parse multiple versions from strings
348    pub fn parse_versions(version_strings: &[&str]) -> Result<Vec<Version>, VersionError> {
349        version_strings.iter().map(|s| s.parse()).collect()
350    }
351
352    /// Find the newest version in a list
353    pub fn newest_version(versions: &[Version]) -> Option<&Version> {
354        versions.iter().max()
355    }
356
357    /// Find the oldest version in a list
358    pub fn oldest_version(versions: &[Version]) -> Option<&Version> {
359        versions.iter().min()
360    }
361
362    /// Check if all versions in a list are compatible with each other
363    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    /// Get a human-readable description of version compatibility
373    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        // Invalid month should fail
398        assert!(Version::new(2025, 13, 18).is_err());
399
400        // Invalid day should fail
401        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        // Invalid format should fail
410        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(); // Same year
430        let v3 = Version::new(2024, 6, 18).unwrap(); // Different year
431
432        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        // Same year - compatible with warnings
489        let compat = manager.check_compatibility(&v1, &v2);
490        assert!(matches!(
491            compat,
492            VersionCompatibility::CompatibleWithWarnings(_)
493        ));
494
495        // Different year - incompatible
496        let compat = manager.check_compatibility(&v1, &v3);
497        assert!(matches!(compat, VersionCompatibility::Incompatible(_)));
498
499        // Exact match - compatible
500        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}