openstranded_common_wasmcontract/
version.rs1use std::fmt;
2
3macro_rules! parse_version_num {
5 ($s:expr) => {{
6 const BYTES: &[u8] = $s.as_bytes();
7 let mut n: u32 = 0;
8 let mut i: usize = 0;
9 while i < BYTES.len() && BYTES[i] >= b'0' && BYTES[i] <= b'9' {
10 n = n * 10 + (BYTES[i] - b'0') as u32;
11 i += 1;
12 }
13 n
14 }};
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29#[repr(C)]
30pub struct ApiVersion {
31 pub major: u32,
33 pub minor: u32,
35 pub patch: u32,
37}
38
39impl ApiVersion {
40 #[must_use]
53 pub const fn current() -> Self {
54 Self {
55 major: parse_version_num!(env!("CARGO_PKG_VERSION_MAJOR")),
56 minor: parse_version_num!(env!("CARGO_PKG_VERSION_MINOR")),
57 patch: parse_version_num!(env!("CARGO_PKG_VERSION_PATCH")),
58 }
59 }
60
61 #[must_use]
73 pub fn compatible_with(&self, engine_version: &ApiVersion) -> Result<(), VersionMismatch> {
74 if self.major != engine_version.major {
75 return Err(VersionMismatch::Incompatible {
76 plugin: *self,
77 engine: *engine_version,
78 });
79 }
80 if self.minor != engine_version.minor {
81 }
83 Ok(())
84 }
85}
86
87impl fmt::Display for ApiVersion {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
90 }
91}
92
93#[derive(Clone, Debug, PartialEq, Eq)]
95pub enum VersionMismatch {
96 Incompatible {
98 plugin: ApiVersion,
99 engine: ApiVersion,
100 },
101}
102
103impl fmt::Display for VersionMismatch {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 match self {
106 VersionMismatch::Incompatible { plugin, engine } => {
107 write!(
108 f,
109 "plugin API v{plugin} is incompatible with engine API v{engine} \
110 (major version mismatch; expected {major}, got {got})",
111 major = engine.major,
112 got = plugin.major,
113 )
114 }
115 }
116 }
117}
118
119#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_current_version_parsed() {
127 let v = ApiVersion::current();
128 assert_eq!(v.major, 0);
131 assert!(v.minor < 100);
132 assert!(v.patch < 100);
133 }
134
135 #[test]
136 fn test_display() {
137 let v = ApiVersion {
138 major: 0,
139 minor: 2,
140 patch: 2,
141 };
142 assert_eq!(v.to_string(), "0.2.2");
143 }
144
145 #[test]
146 fn test_compatible_same_version() {
147 let v = ApiVersion { major: 1, minor: 0, patch: 0 };
148 assert!(v.compatible_with(&v).is_ok());
149 }
150
151 #[test]
152 fn test_compatible_minor_mismatch_still_ok() {
153 let plugin = ApiVersion { major: 1, minor: 1, patch: 0 };
154 let engine = ApiVersion { major: 1, minor: 2, patch: 0 };
155 assert!(plugin.compatible_with(&engine).is_ok());
156 }
157
158 #[test]
159 fn test_compatible_major_mismatch_error() {
160 let plugin = ApiVersion { major: 2, minor: 0, patch: 0 };
161 let engine = ApiVersion { major: 1, minor: 0, patch: 0 };
162 let err = plugin.compatible_with(&engine).unwrap_err();
163 assert!(matches!(err, VersionMismatch::Incompatible { .. }));
164 }
165
166 #[test]
167 fn test_version_mismatch_display() {
168 let mismatch = VersionMismatch::Incompatible {
169 plugin: ApiVersion { major: 2, minor: 0, patch: 0 },
170 engine: ApiVersion { major: 1, minor: 0, patch: 0 },
171 };
172 let msg = mismatch.to_string();
173 assert!(msg.contains("2.0.0"));
174 assert!(msg.contains("1.0.0"));
175 }
176}