pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const MAJOR: u32 = parse_version_component(env!("CARGO_PKG_VERSION_MAJOR"));
pub const MINOR: u32 = parse_version_component(env!("CARGO_PKG_VERSION_MINOR"));
pub const PATCH: u32 = parse_version_component(env!("CARGO_PKG_VERSION_PATCH"));
const fn parse_version_component(s: &str) -> u32 {
let bytes = s.as_bytes();
let mut result: u32 = 0;
let mut i = 0;
while i < bytes.len() {
let digit = bytes[i] - b'0';
result = result * 10 + digit as u32;
i += 1;
}
result
}
pub const GIT_COMMIT: &str = match option_env!("STOOLAP_GIT_COMMIT") {
Some(commit) => commit,
None => "unknown",
};
pub const BUILD_TIME: &str = match option_env!("STOOLAP_BUILD_TIME") {
Some(time) => time,
None => "unknown",
};
pub fn version() -> &'static str {
VERSION
}
pub fn version_info() -> String {
format!(
"stoolap {} (commit: {}, built: {})",
VERSION, GIT_COMMIT, BUILD_TIME
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SemVer {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl SemVer {
pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
pub const fn current() -> Self {
Self::new(MAJOR, MINOR, PATCH)
}
pub fn is_compatible_with(&self, other: &SemVer) -> bool {
self.major == other.major && self.minor >= other.minor
}
}
impl std::fmt::Display for SemVer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl std::str::FromStr for SemVer {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 3 {
return Err(format!("invalid version format: {}", s));
}
let major = parts[0]
.parse()
.map_err(|_| format!("invalid major version: {}", parts[0]))?;
let minor = parts[1]
.parse()
.map_err(|_| format!("invalid minor version: {}", parts[1]))?;
let patch = parts[2]
.parse()
.map_err(|_| format!("invalid patch version: {}", parts[2]))?;
Ok(SemVer::new(major, minor, patch))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_string() {
let expected = format!("{}.{}.{}", MAJOR, MINOR, PATCH);
assert_eq!(version(), expected);
}
#[test]
fn test_version_info() {
let info = version_info();
assert!(info.contains("stoolap"));
assert!(info.contains(version()));
}
#[test]
fn test_git_commit_default() {
assert!(!GIT_COMMIT.is_empty());
}
#[test]
fn test_build_time_default() {
assert!(!BUILD_TIME.is_empty());
}
#[test]
fn test_semver_new() {
let v = SemVer::new(1, 2, 3);
assert_eq!(v.major, 1);
assert_eq!(v.minor, 2);
assert_eq!(v.patch, 3);
}
#[test]
fn test_semver_current() {
let v = SemVer::current();
assert_eq!(v.major, MAJOR);
assert_eq!(v.minor, MINOR);
assert_eq!(v.patch, PATCH);
}
#[test]
fn test_semver_display() {
let v = SemVer::new(1, 2, 3);
assert_eq!(v.to_string(), "1.2.3");
}
#[test]
fn test_semver_from_str() {
let v: SemVer = "1.2.3".parse().unwrap();
assert_eq!(v, SemVer::new(1, 2, 3));
let v: SemVer = version().parse().unwrap();
assert_eq!(v, SemVer::current());
}
#[test]
fn test_semver_from_str_invalid() {
assert!("1.2".parse::<SemVer>().is_err());
assert!("1.2.3.4".parse::<SemVer>().is_err());
assert!("a.b.c".parse::<SemVer>().is_err());
assert!("".parse::<SemVer>().is_err());
}
#[test]
fn test_semver_compatibility() {
let v1 = SemVer::new(1, 2, 0);
let v2 = SemVer::new(1, 1, 0);
let v3 = SemVer::new(1, 3, 0);
let v4 = SemVer::new(2, 0, 0);
assert!(v1.is_compatible_with(&v2));
assert!(!v1.is_compatible_with(&v3));
assert!(!v1.is_compatible_with(&v4));
assert!(v1.is_compatible_with(&v1));
}
}