mockforge_foundation/
rate_counters.rs1use std::sync::atomic::{AtomicU64, Ordering};
22
23pub static SUCCESSFUL_RESPONSES_TOTAL: AtomicU64 = AtomicU64::new(0);
25
26pub static OK_RESPONSES_TOTAL: AtomicU64 = AtomicU64::new(0);
28
29pub static HTTP_ACCEPTS_TOTAL: AtomicU64 = AtomicU64::new(0);
31
32#[inline]
34pub fn record_response(status_code: u16) {
35 if (200..=399).contains(&status_code) {
36 SUCCESSFUL_RESPONSES_TOTAL.fetch_add(1, Ordering::Relaxed);
37 }
38 if status_code == 200 {
39 OK_RESPONSES_TOTAL.fetch_add(1, Ordering::Relaxed);
40 }
41}
42
43#[inline]
45pub fn record_accept() {
46 HTTP_ACCEPTS_TOTAL.fetch_add(1, Ordering::Relaxed);
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub struct CounterSnapshot {
52 pub successful: u64,
53 pub ok: u64,
54 pub accepts: u64,
55}
56
57pub fn snapshot() -> CounterSnapshot {
60 CounterSnapshot {
61 successful: SUCCESSFUL_RESPONSES_TOTAL.load(Ordering::Relaxed),
62 ok: OK_RESPONSES_TOTAL.load(Ordering::Relaxed),
63 accepts: HTTP_ACCEPTS_TOTAL.load(Ordering::Relaxed),
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use std::sync::Mutex;
71
72 static TEST_LOCK: Mutex<()> = Mutex::new(());
74
75 fn reset_counters() {
76 SUCCESSFUL_RESPONSES_TOTAL.store(0, Ordering::Relaxed);
77 OK_RESPONSES_TOTAL.store(0, Ordering::Relaxed);
78 HTTP_ACCEPTS_TOTAL.store(0, Ordering::Relaxed);
79 }
80
81 #[test]
82 fn record_response_classifies_2xx_as_successful() {
83 let _g = TEST_LOCK.lock().unwrap();
84 reset_counters();
85 record_response(200);
86 record_response(204);
87 record_response(301);
88 let s = snapshot();
89 assert_eq!(s.successful, 3, "200, 204, 301 are all successful");
90 assert_eq!(s.ok, 1, "only one 200");
91 }
92
93 #[test]
94 fn record_response_excludes_4xx_5xx_from_successful() {
95 let _g = TEST_LOCK.lock().unwrap();
96 reset_counters();
97 record_response(404);
98 record_response(429);
99 record_response(500);
100 record_response(503);
101 let s = snapshot();
102 assert_eq!(s.successful, 0);
103 assert_eq!(s.ok, 0);
104 }
105
106 #[test]
107 fn record_accept_increments() {
108 let _g = TEST_LOCK.lock().unwrap();
109 reset_counters();
110 record_accept();
111 record_accept();
112 record_accept();
113 let s = snapshot();
114 assert_eq!(s.accepts, 3);
115 }
116
117 #[test]
118 fn snapshot_returns_current_values() {
119 let _g = TEST_LOCK.lock().unwrap();
120 reset_counters();
121 record_response(200);
122 record_response(200);
123 record_accept();
124 let s = snapshot();
125 assert_eq!(s.successful, 2);
126 assert_eq!(s.ok, 2);
127 assert_eq!(s.accepts, 1);
128 }
129
130 #[test]
131 fn ok_counter_only_for_status_200() {
132 let _g = TEST_LOCK.lock().unwrap();
133 reset_counters();
134 record_response(200);
135 record_response(201);
136 record_response(204);
137 let s = snapshot();
138 assert_eq!(s.ok, 1, "only 200 increments ok counter");
139 assert_eq!(s.successful, 3, "all three are successful");
140 }
141}