protocol_droid/application/server/
log_gate.rs1use std::sync::Arc;
19use std::sync::atomic::{AtomicU8, Ordering};
20
21use crate::domain::ir::LogLevel;
22
23const OPEN: u8 = 0;
25const CLOSED: u8 = u8::MAX;
27
28#[derive(Debug, Clone)]
33pub struct LogGate {
34 threshold: Arc<AtomicU8>,
35}
36
37impl LogGate {
38 #[must_use]
40 pub fn open() -> Self {
41 Self::stored(OPEN)
42 }
43
44 #[must_use]
46 pub fn closed() -> Self {
47 Self::stored(CLOSED)
48 }
49
50 #[must_use]
52 pub fn at(level: LogLevel) -> Self {
53 Self::stored(encode(level))
54 }
55
56 #[must_use]
58 pub fn for_request(level: Option<LogLevel>) -> Self {
59 level.map_or_else(Self::closed, Self::at)
60 }
61
62 pub fn set(&self, level: LogLevel) {
64 self.threshold.store(encode(level), Ordering::Relaxed);
65 }
66
67 #[must_use]
69 pub fn admits(&self, level: LogLevel) -> bool {
70 match self.threshold.load(Ordering::Relaxed) {
71 OPEN => true,
72 CLOSED => false,
73 threshold => encode(level) >= threshold,
74 }
75 }
76
77 fn stored(value: u8) -> Self {
78 Self {
79 threshold: Arc::new(AtomicU8::new(value)),
80 }
81 }
82}
83
84impl Default for LogGate {
85 fn default() -> Self {
86 Self::open()
87 }
88}
89
90fn encode(level: LogLevel) -> u8 {
92 let index = LogLevel::ALL
93 .iter()
94 .position(|candidate| *candidate == level)
95 .unwrap_or_default();
96 u8::try_from(index).map_or(1, |index| index.saturating_add(1))
97}
98
99#[cfg(test)]
100mod tests {
101 use super::LogGate;
102 use crate::domain::ir::LogLevel;
103
104 #[test]
105 fn an_open_gate_admits_everything() {
106 assert!(LogGate::open().admits(LogLevel::Debug));
107 }
108
109 #[test]
110 fn a_closed_gate_admits_nothing() {
111 assert!(!LogGate::closed().admits(LogLevel::Emergency));
112 }
113
114 #[test]
115 fn a_threshold_admits_its_level_and_above() {
116 let gate = LogGate::at(LogLevel::Warning);
117 assert!(!gate.admits(LogLevel::Info));
118 assert!(gate.admits(LogLevel::Warning));
119 assert!(gate.admits(LogLevel::Error));
120 }
121
122 #[test]
123 fn a_request_without_a_level_is_closed() {
124 assert!(!LogGate::for_request(None).admits(LogLevel::Emergency));
125 assert!(LogGate::for_request(Some(LogLevel::Debug)).admits(LogLevel::Debug));
126 }
127
128 #[test]
129 fn set_reaches_every_clone() {
130 let gate = LogGate::open();
131 let clone = gate.clone();
132 gate.set(LogLevel::Error);
133 assert!(!clone.admits(LogLevel::Warning));
134 assert!(clone.admits(LogLevel::Error));
135 }
136
137 #[test]
138 fn the_stored_order_agrees_with_the_level_order() {
139 for (lower, higher) in LogLevel::ALL.iter().zip(LogLevel::ALL.iter().skip(1)) {
140 assert!(lower < higher, "{lower} sorts below {higher}");
141 assert!(super::encode(*lower) < super::encode(*higher));
142 }
143 }
144}