1use crate::error::ConfigError;
7use std::fmt;
8use std::str::FromStr;
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
33pub enum ApiVersion {
34 V2024_01,
36 V2024_04,
38 V2024_07,
40 V2024_10,
42 V2025_01,
44 V2025_04,
46 V2025_07,
48 V2025_10,
50 V2026_01,
52 Unstable,
54 Custom(String),
56}
57
58impl ApiVersion {
59 #[must_use]
63 pub const fn latest() -> Self {
64 Self::V2026_01
65 }
66
67 #[must_use]
71 pub const fn is_stable(&self) -> bool {
72 !matches!(self, Self::Unstable | Self::Custom(_))
73 }
74
75 #[must_use]
90 pub fn supported_versions() -> Vec<Self> {
91 vec![
92 Self::V2025_04,
93 Self::V2025_07,
94 Self::V2025_10,
95 Self::V2026_01,
96 ]
97 }
98
99 #[must_use]
114 pub const fn minimum_supported() -> Self {
115 Self::V2025_04
116 }
117
118 #[must_use]
135 pub fn is_supported(&self) -> bool {
136 match self {
137 Self::Unstable => true,
138 Self::Custom(_) => true, _ => *self >= Self::minimum_supported(),
140 }
141 }
142
143 #[must_use]
160 pub fn is_deprecated(&self) -> bool {
161 match self {
162 Self::Unstable => false,
163 Self::Custom(_) => false,
164 _ => *self < Self::minimum_supported(),
165 }
166 }
167
168 const fn ordinal(&self) -> u32 {
172 match self {
173 Self::V2024_01 => 1,
174 Self::V2024_04 => 2,
175 Self::V2024_07 => 3,
176 Self::V2024_10 => 4,
177 Self::V2025_01 => 5,
178 Self::V2025_04 => 6,
179 Self::V2025_07 => 7,
180 Self::V2025_10 => 8,
181 Self::V2026_01 => 9,
182 Self::Unstable => 100, Self::Custom(_) => 101, }
185 }
186}
187
188impl PartialOrd for ApiVersion {
189 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
190 Some(self.cmp(other))
191 }
192}
193
194impl Ord for ApiVersion {
195 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
196 match (self, other) {
197 (Self::Custom(a), Self::Custom(b)) => a.cmp(b),
199 _ => self.ordinal().cmp(&other.ordinal()),
201 }
202 }
203}
204
205impl fmt::Display for ApiVersion {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 let version_str = match self {
208 Self::V2024_01 => "2024-01",
209 Self::V2024_04 => "2024-04",
210 Self::V2024_07 => "2024-07",
211 Self::V2024_10 => "2024-10",
212 Self::V2025_01 => "2025-01",
213 Self::V2025_04 => "2025-04",
214 Self::V2025_07 => "2025-07",
215 Self::V2025_10 => "2025-10",
216 Self::V2026_01 => "2026-01",
217 Self::Unstable => "unstable",
218 Self::Custom(s) => s,
219 };
220 f.write_str(version_str)
221 }
222}
223
224impl FromStr for ApiVersion {
225 type Err = ConfigError;
226
227 fn from_str(s: &str) -> Result<Self, Self::Err> {
228 let s = s.trim().to_lowercase();
229
230 match s.as_str() {
231 "2024-01" => Ok(Self::V2024_01),
232 "2024-04" => Ok(Self::V2024_04),
233 "2024-07" => Ok(Self::V2024_07),
234 "2024-10" => Ok(Self::V2024_10),
235 "2025-01" => Ok(Self::V2025_01),
236 "2025-04" => Ok(Self::V2025_04),
237 "2025-07" => Ok(Self::V2025_07),
238 "2025-10" => Ok(Self::V2025_10),
239 "2026-01" => Ok(Self::V2026_01),
240 "unstable" => Ok(Self::Unstable),
241 _ => {
242 if Self::is_valid_version_format(&s) {
244 Ok(Self::Custom(s))
245 } else {
246 Err(ConfigError::InvalidApiVersion { version: s })
247 }
248 }
249 }
250 }
251}
252
253impl ApiVersion {
254 fn is_valid_version_format(s: &str) -> bool {
255 if s.len() != 7 {
257 return false;
258 }
259
260 let parts: Vec<&str> = s.split('-').collect();
261 if parts.len() != 2 {
262 return false;
263 }
264
265 let year = parts[0];
266 let month = parts[1];
267
268 if year.len() != 4 || month.len() != 2 {
269 return false;
270 }
271
272 if !year.chars().all(|c| c.is_ascii_digit()) {
274 return false;
275 }
276
277 matches!(month, "01" | "04" | "07" | "10")
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn test_api_version_parses_known_versions() {
288 assert_eq!(
289 "2024-01".parse::<ApiVersion>().unwrap(),
290 ApiVersion::V2024_01
291 );
292 assert_eq!(
293 "2024-10".parse::<ApiVersion>().unwrap(),
294 ApiVersion::V2024_10
295 );
296 assert_eq!(
297 "2025-01".parse::<ApiVersion>().unwrap(),
298 ApiVersion::V2025_01
299 );
300 assert_eq!(
301 "unstable".parse::<ApiVersion>().unwrap(),
302 ApiVersion::Unstable
303 );
304 }
305
306 #[test]
307 fn test_api_version_display() {
308 assert_eq!(format!("{}", ApiVersion::V2024_01), "2024-01");
309 assert_eq!(format!("{}", ApiVersion::V2024_10), "2024-10");
310 assert_eq!(format!("{}", ApiVersion::V2026_01), "2026-01");
311 assert_eq!(format!("{}", ApiVersion::Unstable), "unstable");
312 assert_eq!(
313 format!("{}", ApiVersion::Custom("2026-04".to_string())),
314 "2026-04"
315 );
316 }
317
318 #[test]
319 fn test_api_version_is_stable() {
320 assert!(ApiVersion::V2024_01.is_stable());
321 assert!(ApiVersion::V2025_10.is_stable());
322 assert!(ApiVersion::V2026_01.is_stable());
323 assert!(!ApiVersion::Unstable.is_stable());
324 assert!(!ApiVersion::Custom("2026-04".to_string()).is_stable());
325 }
326
327 #[test]
328 fn test_api_version_latest() {
329 let latest = ApiVersion::latest();
330 assert!(latest.is_stable());
331 assert_eq!(latest, ApiVersion::V2026_01);
332 }
333
334 #[test]
335 fn test_api_version_parses_future_versions() {
336 let version: ApiVersion = "2026-04".parse().unwrap();
338 assert_eq!(version, ApiVersion::Custom("2026-04".to_string()));
339 assert!(!version.is_stable());
340 }
341
342 #[test]
343 fn test_api_version_rejects_invalid() {
344 assert!("invalid".parse::<ApiVersion>().is_err());
345 assert!("2024".parse::<ApiVersion>().is_err());
346 assert!("2024-1".parse::<ApiVersion>().is_err());
347 assert!("2024-02".parse::<ApiVersion>().is_err()); assert!("24-01".parse::<ApiVersion>().is_err());
349 }
350
351 #[test]
352 fn test_supported_versions_chronological() {
353 let versions = ApiVersion::supported_versions();
354
355 assert!(!versions.is_empty());
357
358 assert!(versions.contains(&ApiVersion::latest()));
360
361 for window in versions.windows(2) {
363 assert!(
364 window[0] < window[1],
365 "Versions should be in chronological order"
366 );
367 }
368
369 for version in &versions {
371 assert!(version.is_supported(), "{version} should be supported");
372 }
373 }
374
375 #[test]
376 fn test_minimum_supported() {
377 let minimum = ApiVersion::minimum_supported();
378
379 assert!(minimum.is_supported());
381
382 assert!(!minimum.is_deprecated());
384
385 assert!(ApiVersion::V2024_01.is_deprecated());
387 assert!(ApiVersion::V2024_04.is_deprecated());
388 assert!(ApiVersion::V2024_07.is_deprecated());
389 assert!(ApiVersion::V2024_10.is_deprecated());
390 assert!(ApiVersion::V2025_01.is_deprecated());
391 }
392
393 #[test]
394 fn test_is_deprecated_for_old_versions() {
395 assert!(ApiVersion::V2024_01.is_deprecated());
397 assert!(ApiVersion::V2024_04.is_deprecated());
398 assert!(ApiVersion::V2024_07.is_deprecated());
399 assert!(ApiVersion::V2024_10.is_deprecated());
400 assert!(ApiVersion::V2025_01.is_deprecated());
401
402 assert!(!ApiVersion::V2025_04.is_deprecated());
404 assert!(!ApiVersion::V2025_07.is_deprecated());
405 assert!(!ApiVersion::V2025_10.is_deprecated());
406 assert!(!ApiVersion::V2026_01.is_deprecated());
407
408 assert!(!ApiVersion::Unstable.is_deprecated());
410 assert!(!ApiVersion::Custom("2026-04".to_string()).is_deprecated());
411 }
412
413 #[test]
414 fn test_is_supported() {
415 assert!(ApiVersion::V2025_04.is_supported());
417 assert!(ApiVersion::V2025_07.is_supported());
418 assert!(ApiVersion::V2025_10.is_supported());
419 assert!(ApiVersion::V2026_01.is_supported());
420 assert!(ApiVersion::Unstable.is_supported());
421 assert!(ApiVersion::Custom("2026-04".to_string()).is_supported());
422
423 assert!(!ApiVersion::V2024_01.is_supported());
425 assert!(!ApiVersion::V2024_04.is_supported());
426 assert!(!ApiVersion::V2024_07.is_supported());
427 assert!(!ApiVersion::V2024_10.is_supported());
428 assert!(!ApiVersion::V2025_01.is_supported());
429 }
430
431 #[test]
432 fn test_version_ordering() {
433 assert!(ApiVersion::V2024_01 < ApiVersion::V2024_04);
435 assert!(ApiVersion::V2024_04 < ApiVersion::V2024_07);
436 assert!(ApiVersion::V2024_07 < ApiVersion::V2024_10);
437 assert!(ApiVersion::V2024_10 < ApiVersion::V2025_01);
438 assert!(ApiVersion::V2025_01 < ApiVersion::V2025_04);
439 assert!(ApiVersion::V2025_04 < ApiVersion::V2025_07);
440 assert!(ApiVersion::V2025_07 < ApiVersion::V2025_10);
441 assert!(ApiVersion::V2025_10 < ApiVersion::V2026_01);
442
443 assert!(ApiVersion::V2026_01 < ApiVersion::Unstable);
445
446 assert!(ApiVersion::Unstable < ApiVersion::Custom("2026-04".to_string()));
448
449 assert!(
451 ApiVersion::Custom("2026-04".to_string()) < ApiVersion::Custom("2026-07".to_string())
452 );
453 }
454
455 #[test]
456 fn test_version_equality() {
457 assert_eq!(ApiVersion::V2024_01, ApiVersion::V2024_01);
458 assert_ne!(ApiVersion::V2024_01, ApiVersion::V2024_04);
459 assert_eq!(ApiVersion::V2026_01, ApiVersion::V2026_01);
460 assert_eq!(
461 ApiVersion::Custom("2026-04".to_string()),
462 ApiVersion::Custom("2026-04".to_string())
463 );
464 }
465}