pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const GIT_COMMIT: &str = env!("NODEDB_GIT_COMMIT");
pub const BUILD_DATE: &str = env!("NODEDB_BUILD_DATE");
pub const BUILD_PROFILE: &str = env!("NODEDB_BUILD_PROFILE");
pub const RUST_VERSION: &str = env!("NODEDB_RUST_VERSION");
pub use nodedb_types::wire_version::{MIN_WIRE_FORMAT_VERSION, WIRE_FORMAT_VERSION};
pub fn features_str() -> &'static str {
"promql,otel,grafana,kafka"
}
pub fn hostname() -> String {
let mut buf = vec![0u8; 256];
let result = unsafe { libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len()) };
if result != 0 {
return "unknown".to_owned();
}
let nul_pos = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8(buf[..nul_pos].to_vec()).unwrap_or_else(|_| "unknown".to_owned())
}
pub fn format_banner(
pgwire_port: u16,
http_port: u16,
native_port: u16,
cluster_mode: &str,
data_dir: &str,
auth_mode: &str,
) -> String {
let pid = std::process::id();
let host = hostname();
let mut out = String::with_capacity(512);
out.push('\n');
out.push_str(&format!(" NodeDB v{VERSION}\n"));
out.push_str(" ─────────────────────────────────────\n");
out.push_str(&format!(" git_commit : {GIT_COMMIT}\n"));
out.push_str(&format!(" build_date : {BUILD_DATE}\n"));
out.push_str(&format!(" build_profile : {BUILD_PROFILE}\n"));
out.push_str(&format!(" rust_version : {RUST_VERSION}\n"));
out.push_str(&format!(" wire_format_version : {WIRE_FORMAT_VERSION}\n"));
out.push_str(" ─────────────────────────────────────\n");
out.push_str(&format!(" hostname : {host}\n"));
out.push_str(&format!(" pid : {pid}\n"));
out.push_str(&format!(" pgwire_port : {pgwire_port}\n"));
out.push_str(&format!(" http_port : {http_port}\n"));
out.push_str(&format!(" native_port : {native_port}\n"));
out.push_str(&format!(" cluster_mode : {cluster_mode}\n"));
out.push_str(&format!(" data_dir : {data_dir}\n"));
out.push_str(&format!(" auth_mode : {auth_mode}\n"));
out.push_str("\n Press Ctrl+C to stop.\n");
out
}
pub fn check_wire_compatibility(remote_version: u16) -> crate::Result<()> {
if remote_version > WIRE_FORMAT_VERSION {
return Err(crate::Error::VersionCompat {
detail: format!(
"remote wire version {remote_version} is newer than local {WIRE_FORMAT_VERSION}; upgrade this node"
),
});
}
if remote_version < MIN_WIRE_FORMAT_VERSION {
return Err(crate::Error::VersionCompat {
detail: format!(
"remote wire version {remote_version} is too old (minimum {MIN_WIRE_FORMAT_VERSION}); upgrade the remote node"
),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_version_compatible() {
assert!(check_wire_compatibility(WIRE_FORMAT_VERSION).is_ok());
}
#[test]
fn newer_version_rejected() {
assert!(check_wire_compatibility(WIRE_FORMAT_VERSION + 1).is_err());
}
#[test]
fn version_string_valid() {
assert!(!VERSION.is_empty());
}
#[test]
fn git_commit_env_present() {
let commit = env!("NODEDB_GIT_COMMIT");
assert!(!commit.is_empty(), "NODEDB_GIT_COMMIT must not be empty");
}
#[test]
fn build_date_env_format() {
let date = env!("NODEDB_BUILD_DATE");
assert_eq!(
date.len(),
10,
"NODEDB_BUILD_DATE must be YYYY-MM-DD (10 chars)"
);
let bytes = date.as_bytes();
assert_eq!(bytes[4], b'-', "position 4 must be '-'");
assert_eq!(bytes[7], b'-', "position 7 must be '-'");
for (i, &b) in bytes.iter().enumerate() {
if i == 4 || i == 7 {
continue;
}
assert!(
b.is_ascii_digit(),
"position {i} must be a digit, got '{}'",
b as char
);
}
}
#[test]
fn features_str_contains_observability_capabilities() {
let s = features_str();
assert!(s.contains("promql"), "features_str must include promql");
assert!(s.contains("otel"), "features_str must include otel");
assert!(s.contains("kafka"), "features_str must include kafka");
}
#[test]
fn hostname_is_non_empty() {
let h = hostname();
assert!(
!h.is_empty(),
"hostname() must never return an empty string"
);
}
#[test]
fn build_profile_is_debug_or_release() {
let p = BUILD_PROFILE;
assert!(
p == "debug" || p == "release",
"BUILD_PROFILE must be 'debug' or 'release', got '{p}'"
);
}
#[test]
fn rust_version_format() {
let v = RUST_VERSION;
assert!(
v.starts_with("rustc "),
"RUST_VERSION must start with 'rustc ', got '{v}'"
);
}
#[test]
fn format_banner_contains_all_required_fields() {
let banner = format_banner(5432, 8080, 7700, "single-node", "/data/nodedb", "password");
assert!(banner.contains(VERSION), "missing version");
assert!(banner.contains(GIT_COMMIT), "missing git_commit");
assert!(banner.contains(BUILD_DATE), "missing build_date");
assert!(banner.contains(BUILD_PROFILE), "missing build_profile");
assert!(banner.contains("rustc"), "missing rust_version");
assert!(
banner.contains(&WIRE_FORMAT_VERSION.to_string()),
"missing wire_format_version"
);
assert!(banner.contains("5432"), "missing pgwire_port");
assert!(banner.contains("8080"), "missing http_port");
assert!(banner.contains("7700"), "missing native_port");
assert!(banner.contains("single-node"), "missing cluster_mode");
assert!(
banner.contains(&std::process::id().to_string()),
"missing pid"
);
assert!(!hostname().is_empty(), "hostname must not be empty");
assert!(banner.contains(&hostname()), "missing hostname");
assert!(banner.contains("/data/nodedb"), "missing data_dir");
assert!(banner.contains("password"), "missing auth_mode");
}
#[test]
fn tracing_span_u64_field_captured() {
let span = tracing::info_span!("test_service", node_id = 0u64);
let _guard = span.enter();
span.record("node_id", 42u64);
}
}