kaptein_viewmodel/
versioned.rs1use std::fmt;
13
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22pub struct ApiVersion {
23 pub major: u32,
24 pub minor: u32,
25}
26
27impl ApiVersion {
28 pub const fn new(major: u32, minor: u32) -> Self {
29 Self { major, minor }
30 }
31}
32
33impl fmt::Display for ApiVersion {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 if self.minor == 0 {
36 write!(f, "v{}", self.major)
37 } else {
38 write!(f, "v{}.{}", self.major, self.minor)
39 }
40 }
41}
42
43pub fn parse_api_version(s: &str) -> Option<ApiVersion> {
47 let s = s.trim().strip_prefix('v').unwrap_or(s.trim());
48 if s.is_empty() {
49 return None;
50 }
51 let mut parts = s.split('.');
52 let major: u32 = parts.next()?.parse().ok()?;
53 let minor: u32 = parts.next().map(|p| p.parse().unwrap_or(0)).unwrap_or(0);
54 if parts.next().is_some() {
55 return None; }
57 Some(ApiVersion { major, minor })
58}
59
60pub fn is_compatible(supported: ApiVersion, requested: ApiVersion) -> bool {
66 requested.major == supported.major
67}
68
69pub const MCP_API_VERSION: ApiVersion = ApiVersion::new(1, 0);
73
74pub const MCP_VERSION_META_KEY: &str = "io.kaptein/apiVersion";
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn parse_accepts_forms() {
83 assert_eq!(parse_api_version("1"), Some(ApiVersion::new(1, 0)));
84 assert_eq!(parse_api_version("v1"), Some(ApiVersion::new(1, 0)));
85 assert_eq!(parse_api_version("1.2"), Some(ApiVersion::new(1, 2)));
86 assert_eq!(parse_api_version("v1.2"), Some(ApiVersion::new(1, 2)));
87 assert_eq!(parse_api_version(" v2 "), Some(ApiVersion::new(2, 0)));
88 }
89
90 #[test]
91 fn parse_rejects_malformed() {
92 assert_eq!(parse_api_version(""), None);
93 assert_eq!(parse_api_version("v"), None);
94 assert_eq!(parse_api_version("abc"), None);
95 assert_eq!(parse_api_version("1.2.3"), None);
96 assert_eq!(parse_api_version("v1.2.3"), None);
97 }
98
99 #[test]
100 fn same_major_is_compatible() {
101 assert!(is_compatible(ApiVersion::new(1, 0), ApiVersion::new(1, 9)));
102 assert!(is_compatible(ApiVersion::new(1, 9), ApiVersion::new(1, 0)));
103 }
104
105 #[test]
106 fn different_major_is_incompatible() {
107 assert!(!is_compatible(ApiVersion::new(1, 0), ApiVersion::new(2, 0)));
108 assert!(!is_compatible(ApiVersion::new(2, 0), ApiVersion::new(1, 0)));
109 }
110
111 #[test]
112 fn display_omits_zero_minor() {
113 assert_eq!(ApiVersion::new(1, 0).to_string(), "v1");
114 assert_eq!(ApiVersion::new(1, 2).to_string(), "v1.2");
115 }
116}