1use std::fmt;
9use std::str::FromStr;
10
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct SchemaVersion {
15 pub major: u16,
16 pub minor: u16,
17}
18
19pub const ENVELOPE_SCHEMA_VERSION: SchemaVersion = SchemaVersion::new(1, 0);
21
22pub const BUNDLE_FORMAT_VERSION: SchemaVersion = SchemaVersion::new(1, 0);
24
25impl SchemaVersion {
26 pub const fn new(major: u16, minor: u16) -> Self {
27 Self { major, minor }
28 }
29
30 pub fn can_read(&self, written: &SchemaVersion) -> bool {
32 self.major == written.major && written.minor <= self.minor
33 }
34}
35
36impl fmt::Display for SchemaVersion {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(f, "{}.{}", self.major, self.minor)
39 }
40}
41
42impl FromStr for SchemaVersion {
43 type Err = String;
44 fn from_str(s: &str) -> Result<Self, Self::Err> {
45 let (major, minor) = s.split_once('.').ok_or_else(|| format!("invalid schema version {s:?}"))?;
46 let parse = |p: &str| {
47 if p.is_empty() || (p.len() > 1 && p.starts_with('0')) {
48 return Err(format!("invalid schema version {s:?}"));
49 }
50 p.parse::<u16>().map_err(|_| format!("invalid schema version {s:?}"))
51 };
52 Ok(Self { major: parse(major)?, minor: parse(minor)? })
53 }
54}
55
56impl Serialize for SchemaVersion {
57 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
58 s.collect_str(self)
59 }
60}
61
62impl<'de> Deserialize<'de> for SchemaVersion {
63 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
64 let s = String::deserialize(d)?;
65 s.parse().map_err(serde::de::Error::custom)
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn compatibility_policy() {
75 let reader = SchemaVersion::new(1, 2);
76 assert!(reader.can_read(&SchemaVersion::new(1, 0)));
77 assert!(reader.can_read(&SchemaVersion::new(1, 2)));
78 assert!(!reader.can_read(&SchemaVersion::new(1, 3)));
79 assert!(!reader.can_read(&SchemaVersion::new(2, 0)));
80 assert!(!reader.can_read(&SchemaVersion::new(0, 9)));
81 }
82
83 #[test]
84 fn parses() {
85 assert_eq!("1.0".parse::<SchemaVersion>().unwrap(), SchemaVersion::new(1, 0));
86 assert!("1".parse::<SchemaVersion>().is_err());
87 assert!("01.0".parse::<SchemaVersion>().is_err());
88 assert!("1.x".parse::<SchemaVersion>().is_err());
89 }
90}