pub const MAJOR: u32 = 0;
pub const MINOR: u32 = 1;
pub const PATCH: u32 = 0;
use std::sync::OnceLock;
static VERSION: OnceLock<String> = OnceLock::new();
fn get_version() -> &'static String {
VERSION.get_or_init(|| format!("{}.{}.{}", MAJOR, MINOR, PATCH))
}
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 {
get_version()
}
pub fn version_info() -> String {
format!(
"oxibase {} (commit: {}, built: {})",
get_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_constants() {
assert_eq!(MAJOR, 0);
assert_eq!(MINOR, 1);
assert_eq!(PATCH, 0);
}
#[test]
fn test_version_string() {
assert_eq!(version(), "0.1.0");
}
#[test]
fn test_version_info() {
let info = version_info();
assert!(info.contains("oxibase"));
assert!(info.contains("0.1.0"));
}
#[test]
#[allow(clippy::const_is_empty)]
fn test_git_commit_default() {
assert!(!GIT_COMMIT.is_empty());
}
#[test]
#[allow(clippy::const_is_empty)]
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 = "0.1.0".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));
}
}