use std::fmt;
macro_rules! parse_version_num {
($s:expr) => {{
const BYTES: &[u8] = $s.as_bytes();
let mut n: u32 = 0;
let mut i: usize = 0;
while i < BYTES.len() && BYTES[i] >= b'0' && BYTES[i] <= b'9' {
n = n * 10 + (BYTES[i] - b'0') as u32;
i += 1;
}
n
}};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct ApiVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl ApiVersion {
#[must_use]
pub const fn current() -> Self {
Self {
major: parse_version_num!(env!("CARGO_PKG_VERSION_MAJOR")),
minor: parse_version_num!(env!("CARGO_PKG_VERSION_MINOR")),
patch: parse_version_num!(env!("CARGO_PKG_VERSION_PATCH")),
}
}
#[must_use]
pub fn compatible_with(&self, engine_version: &ApiVersion) -> Result<(), VersionMismatch> {
if self.major != engine_version.major {
return Err(VersionMismatch::Incompatible {
plugin: *self,
engine: *engine_version,
});
}
if self.minor != engine_version.minor {
}
Ok(())
}
}
impl fmt::Display for ApiVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VersionMismatch {
Incompatible {
plugin: ApiVersion,
engine: ApiVersion,
},
}
impl fmt::Display for VersionMismatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VersionMismatch::Incompatible { plugin, engine } => {
write!(
f,
"plugin API v{plugin} is incompatible with engine API v{engine} \
(major version mismatch; expected {major}, got {got})",
major = engine.major,
got = plugin.major,
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_current_version_parsed() {
let v = ApiVersion::current();
assert_eq!(v.major, 0);
assert!(v.minor < 100);
assert!(v.patch < 100);
}
#[test]
fn test_display() {
let v = ApiVersion {
major: 0,
minor: 2,
patch: 2,
};
assert_eq!(v.to_string(), "0.2.2");
}
#[test]
fn test_compatible_same_version() {
let v = ApiVersion { major: 1, minor: 0, patch: 0 };
assert!(v.compatible_with(&v).is_ok());
}
#[test]
fn test_compatible_minor_mismatch_still_ok() {
let plugin = ApiVersion { major: 1, minor: 1, patch: 0 };
let engine = ApiVersion { major: 1, minor: 2, patch: 0 };
assert!(plugin.compatible_with(&engine).is_ok());
}
#[test]
fn test_compatible_major_mismatch_error() {
let plugin = ApiVersion { major: 2, minor: 0, patch: 0 };
let engine = ApiVersion { major: 1, minor: 0, patch: 0 };
let err = plugin.compatible_with(&engine).unwrap_err();
assert!(matches!(err, VersionMismatch::Incompatible { .. }));
}
#[test]
fn test_version_mismatch_display() {
let mismatch = VersionMismatch::Incompatible {
plugin: ApiVersion { major: 2, minor: 0, patch: 0 },
engine: ApiVersion { major: 1, minor: 0, patch: 0 },
};
let msg = mismatch.to_string();
assert!(msg.contains("2.0.0"));
assert!(msg.contains("1.0.0"));
}
}