use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use crate::domain::ir::LogLevel;
const OPEN: u8 = 0;
const CLOSED: u8 = u8::MAX;
#[derive(Debug, Clone)]
pub struct LogGate {
threshold: Arc<AtomicU8>,
}
impl LogGate {
#[must_use]
pub fn open() -> Self {
Self::stored(OPEN)
}
#[must_use]
pub fn closed() -> Self {
Self::stored(CLOSED)
}
#[must_use]
pub fn at(level: LogLevel) -> Self {
Self::stored(encode(level))
}
#[must_use]
pub fn for_request(level: Option<LogLevel>) -> Self {
level.map_or_else(Self::closed, Self::at)
}
pub fn set(&self, level: LogLevel) {
self.threshold.store(encode(level), Ordering::Relaxed);
}
#[must_use]
pub fn admits(&self, level: LogLevel) -> bool {
match self.threshold.load(Ordering::Relaxed) {
OPEN => true,
CLOSED => false,
threshold => encode(level) >= threshold,
}
}
fn stored(value: u8) -> Self {
Self {
threshold: Arc::new(AtomicU8::new(value)),
}
}
}
impl Default for LogGate {
fn default() -> Self {
Self::open()
}
}
fn encode(level: LogLevel) -> u8 {
let index = LogLevel::ALL
.iter()
.position(|candidate| *candidate == level)
.unwrap_or_default();
u8::try_from(index).map_or(1, |index| index.saturating_add(1))
}
#[cfg(test)]
mod tests {
use super::LogGate;
use crate::domain::ir::LogLevel;
#[test]
fn an_open_gate_admits_everything() {
assert!(LogGate::open().admits(LogLevel::Debug));
}
#[test]
fn a_closed_gate_admits_nothing() {
assert!(!LogGate::closed().admits(LogLevel::Emergency));
}
#[test]
fn a_threshold_admits_its_level_and_above() {
let gate = LogGate::at(LogLevel::Warning);
assert!(!gate.admits(LogLevel::Info));
assert!(gate.admits(LogLevel::Warning));
assert!(gate.admits(LogLevel::Error));
}
#[test]
fn a_request_without_a_level_is_closed() {
assert!(!LogGate::for_request(None).admits(LogLevel::Emergency));
assert!(LogGate::for_request(Some(LogLevel::Debug)).admits(LogLevel::Debug));
}
#[test]
fn set_reaches_every_clone() {
let gate = LogGate::open();
let clone = gate.clone();
gate.set(LogLevel::Error);
assert!(!clone.admits(LogLevel::Warning));
assert!(clone.admits(LogLevel::Error));
}
#[test]
fn the_stored_order_agrees_with_the_level_order() {
for (lower, higher) in LogLevel::ALL.iter().zip(LogLevel::ALL.iter().skip(1)) {
assert!(lower < higher, "{lower} sorts below {higher}");
assert!(super::encode(*lower) < super::encode(*higher));
}
}
}