use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum RanjPrecision {
Microseconds = 0b00,
Nanoseconds = 0b01,
Picoseconds = 0b10,
Femtoseconds = 0b11,
}
impl RanjPrecision {
pub fn from_micros_multiplier(self) -> u128 {
match self {
Self::Microseconds => 1,
Self::Nanoseconds => 1_000,
Self::Picoseconds => 1_000_000,
Self::Femtoseconds => 1_000_000_000,
}
}
pub fn to_micros_divisor(self) -> u128 {
self.from_micros_multiplier()
}
pub fn from_millis_multiplier(self) -> u128 {
self.from_micros_multiplier() * 1_000
}
pub fn from_bits(bits: u8) -> Option<Self> {
match bits & 0b11 {
0b00 => Some(Self::Microseconds),
0b01 => Some(Self::Nanoseconds),
0b10 => Some(Self::Picoseconds),
0b11 => Some(Self::Femtoseconds),
_ => None,
}
}
pub fn to_bits(self) -> u8 {
self as u8
}
pub fn label(self) -> &'static str {
match self {
Self::Microseconds => "us",
Self::Nanoseconds => "ns",
Self::Picoseconds => "ps",
Self::Femtoseconds => "fs",
}
}
}
static GENERATION_PRECISION: OnceLock<RanjPrecision> = OnceLock::new();
pub fn try_generation_precision() -> Result<RanjPrecision, String> {
match std::env::var("RANJID_PRECISION") {
Err(std::env::VarError::NotPresent) => Ok(RanjPrecision::Nanoseconds),
Err(std::env::VarError::NotUnicode(_)) => {
Err("RANJID_PRECISION is set but not valid UTF-8".to_string())
}
Ok(val) => match val.as_str() {
"us" => Ok(RanjPrecision::Microseconds),
"ns" => Ok(RanjPrecision::Nanoseconds),
"ps" => Ok(RanjPrecision::Picoseconds),
"fs" => Ok(RanjPrecision::Femtoseconds),
invalid => Err(format!(
"RANJID_PRECISION must be one of: us, ns, ps, fs (got '{invalid}')"
)),
},
}
}
pub fn generation_precision() -> RanjPrecision {
*GENERATION_PRECISION
.get_or_init(|| try_generation_precision().expect("invalid RANJID_PRECISION"))
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[test]
#[serial]
fn try_valid_us() {
unsafe { std::env::set_var("RANJID_PRECISION", "us") };
assert_eq!(try_generation_precision(), Ok(RanjPrecision::Microseconds));
unsafe { std::env::remove_var("RANJID_PRECISION") };
}
#[test]
#[serial]
fn try_valid_ns() {
unsafe { std::env::set_var("RANJID_PRECISION", "ns") };
assert_eq!(try_generation_precision(), Ok(RanjPrecision::Nanoseconds));
unsafe { std::env::remove_var("RANJID_PRECISION") };
}
#[test]
#[serial]
fn try_valid_ps() {
unsafe { std::env::set_var("RANJID_PRECISION", "ps") };
assert_eq!(try_generation_precision(), Ok(RanjPrecision::Picoseconds));
unsafe { std::env::remove_var("RANJID_PRECISION") };
}
#[test]
#[serial]
fn try_valid_fs() {
unsafe { std::env::set_var("RANJID_PRECISION", "fs") };
assert_eq!(try_generation_precision(), Ok(RanjPrecision::Femtoseconds));
unsafe { std::env::remove_var("RANJID_PRECISION") };
}
#[test]
#[serial]
fn try_unset_defaults_to_nanoseconds() {
unsafe { std::env::remove_var("RANJID_PRECISION") };
assert_eq!(try_generation_precision(), Ok(RanjPrecision::Nanoseconds));
}
#[test]
#[serial]
fn try_invalid_returns_err() {
unsafe { std::env::set_var("RANJID_PRECISION", "nanoseconds") };
let result = try_generation_precision();
unsafe { std::env::remove_var("RANJID_PRECISION") };
assert!(result.is_err());
let msg = result.unwrap_err();
assert!(
msg.contains("nanoseconds"),
"error message should contain the invalid value, got: {msg}"
);
assert!(
msg.contains("us") && msg.contains("ns") && msg.contains("ps") && msg.contains("fs"),
"error message should list valid options, got: {msg}"
);
}
#[test]
#[serial]
fn try_non_unicode_returns_err() {
use std::ffi::OsStr;
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
let bad = OsStr::from_bytes(&[0xFF]);
unsafe { std::env::set_var("RANJID_PRECISION", bad) };
let result = try_generation_precision();
unsafe { std::env::remove_var("RANJID_PRECISION") };
assert!(result.is_err(), "non-UTF-8 value should return Err");
let msg = result.unwrap_err();
assert!(
msg.contains("UTF-8") || msg.contains("not valid"),
"error should mention UTF-8, got: {msg}"
);
}
#[cfg(not(unix))]
{
let _ = OsStr::new(""); }
}
}