bloqade_lanes_bytecode_core/
version.rs1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Version {
11 pub major: u16,
12 pub minor: u16,
13}
14
15impl Version {
16 pub fn new(major: u16, minor: u16) -> Self {
17 Self { major, minor }
18 }
19
20 pub fn is_compatible(&self, other: &Version) -> bool {
22 self.major == other.major
23 }
24}
25
26impl fmt::Display for Version {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "{}.{}", self.major, self.minor)
29 }
30}
31
32impl From<u32> for Version {
33 fn from(v: u32) -> Self {
34 Self {
35 major: (v >> 16) as u16,
36 minor: v as u16,
37 }
38 }
39}
40
41impl From<Version> for u32 {
42 fn from(v: Version) -> u32 {
43 ((v.major as u32) << 16) | (v.minor as u32)
44 }
45}
46
47impl Serialize for Version {
49 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
50 serializer.collect_str(self)
51 }
52}
53
54impl<'de> Deserialize<'de> for Version {
56 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
57 let s = String::deserialize(deserializer)?;
58 let (major, minor) = s.split_once('.').ok_or_else(|| {
59 serde::de::Error::custom(format!(
60 "invalid version string '{}': expected 'major.minor'",
61 s
62 ))
63 })?;
64 let major: u16 = major.parse().map_err(serde::de::Error::custom)?;
65 let minor: u16 = minor.parse().map_err(serde::de::Error::custom)?;
66 Ok(Version::new(major, minor))
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_version_display() {
76 assert_eq!(Version::new(1, 0).to_string(), "1.0");
77 assert_eq!(Version::new(2, 3).to_string(), "2.3");
78 }
79
80 #[test]
81 fn test_version_u32_round_trip() {
82 let v = Version::new(1, 0);
83 let packed: u32 = v.into();
84 assert_eq!(packed, 0x00010000);
85 assert_eq!(Version::from(packed), v);
86
87 let v2 = Version::new(2, 5);
88 let packed2: u32 = v2.into();
89 assert_eq!(packed2, 0x00020005);
90 assert_eq!(Version::from(packed2), v2);
91 }
92
93 #[test]
94 fn test_version_from_u32_packed() {
95 let v = Version::from(1u32);
96 assert_eq!(v, Version::new(0, 1));
97 }
98
99 #[test]
100 fn test_version_is_compatible() {
101 let v1 = Version::new(1, 0);
102 let v1_1 = Version::new(1, 1);
103 let v2 = Version::new(2, 0);
104
105 assert!(v1.is_compatible(&v1_1));
106 assert!(v1_1.is_compatible(&v1));
107 assert!(!v1.is_compatible(&v2));
108 }
109
110 #[test]
111 fn test_version_serde_round_trip() {
112 let v = Version::new(1, 0);
113 let json = serde_json::to_string(&v).unwrap();
114 assert_eq!(json, r#""1.0""#);
115 let deserialized: Version = serde_json::from_str(&json).unwrap();
116 assert_eq!(v, deserialized);
117 }
118
119 #[test]
120 fn test_version_serde_round_trip_with_minor() {
121 let v = Version::new(1, 2);
122 let json = serde_json::to_string(&v).unwrap();
123 assert_eq!(json, r#""1.2""#);
124 let deserialized: Version = serde_json::from_str(&json).unwrap();
125 assert_eq!(v, deserialized);
126 }
127
128 #[test]
129 fn test_version_serde_zero_zero() {
130 let v = Version::new(0, 0);
131 let json = serde_json::to_string(&v).unwrap();
132 assert_eq!(json, r#""0.0""#);
133 let deserialized: Version = serde_json::from_str(&json).unwrap();
134 assert_eq!(v, deserialized);
135 }
136
137 #[test]
138 fn test_version_serde_invalid_no_dot() {
139 let err = serde_json::from_str::<Version>(r#""bad""#).unwrap_err();
140 assert!(err.to_string().contains("expected 'major.minor'"));
141 }
142
143 #[test]
144 fn test_version_serde_invalid_major() {
145 let err = serde_json::from_str::<Version>(r#""abc.1""#).unwrap_err();
146 assert!(err.to_string().contains("invalid digit"));
147 }
148
149 #[test]
150 fn test_version_serde_invalid_minor() {
151 let err = serde_json::from_str::<Version>(r#""1.abc""#).unwrap_err();
152 assert!(err.to_string().contains("invalid digit"));
153 }
154
155 #[test]
156 fn test_version_serde_rejects_integer() {
157 let err = serde_json::from_str::<Version>("1").unwrap_err();
158 assert!(err.to_string().contains("invalid type: integer"));
159 }
160
161 #[test]
162 fn test_version_serde_rejects_boolean() {
163 let err = serde_json::from_str::<Version>("true").unwrap_err();
164 assert!(err.to_string().contains("invalid type: boolean"));
165 }
166}