torrust_metrics/
counter.rs1use derive_more::Display;
2use serde::{Deserialize, Serialize};
3
4use super::prometheus::PrometheusSerializable;
5
6#[derive(Debug, Display, Clone, Default, PartialEq, Serialize, Deserialize)]
7pub struct Counter(u64);
8
9impl Counter {
10 #[must_use]
11 pub fn new(value: u64) -> Self {
12 Self(value)
13 }
14
15 #[must_use]
16 pub fn value(&self) -> u64 {
17 self.0
18 }
19
20 #[must_use]
21 pub fn primitive(&self) -> u64 {
22 self.value()
23 }
24
25 pub fn increment(&mut self, value: u64) {
26 self.0 += value;
27 }
28
29 pub fn absolute(&mut self, value: u64) {
30 self.0 = value;
31 }
32}
33
34impl From<u32> for Counter {
35 fn from(value: u32) -> Self {
36 Self(u64::from(value))
37 }
38}
39
40impl From<u64> for Counter {
41 fn from(value: u64) -> Self {
42 Self(value)
43 }
44}
45
46impl From<i32> for Counter {
47 fn from(value: i32) -> Self {
48 #[allow(clippy::cast_sign_loss)]
49 Self(value as u64)
50 }
51}
52
53impl From<Counter> for u64 {
54 fn from(counter: Counter) -> Self {
55 counter.value()
56 }
57}
58
59impl PrometheusSerializable for Counter {
60 fn to_prometheus(&self) -> String {
61 format!("{}", self.value())
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn it_should_be_created_from_integer_values() {
71 let counter = Counter::new(0);
72 assert_eq!(counter.value(), 0);
73 }
74
75 #[test]
76 fn it_could_be_converted_from_u64() {
77 let counter: Counter = 42.into();
78 assert_eq!(counter.value(), 42);
79 }
80
81 #[test]
82 fn it_could_be_converted_into_u64() {
83 let counter = Counter::new(42);
84 let value: u64 = counter.into();
85 assert_eq!(value, 42);
86 }
87
88 #[test]
89 fn it_could_be_incremented() {
90 let mut counter = Counter::new(0);
91 counter.increment(1);
92 assert_eq!(counter.value(), 1);
93
94 counter.increment(2);
95 assert_eq!(counter.value(), 3);
96 }
97
98 #[test]
99 fn it_could_set_to_an_absolute_value() {
100 let mut counter = Counter::new(0);
101 counter.absolute(1);
102 assert_eq!(counter.value(), 1);
103 }
104
105 #[test]
106 fn it_serializes_to_prometheus() {
107 let counter = Counter::new(42);
108 assert_eq!(counter.to_prometheus(), "42");
109 }
110
111 #[test]
112 fn it_could_be_converted_from_u32() {
113 let counter: Counter = 42u32.into();
114 assert_eq!(counter.value(), 42);
115 }
116
117 #[test]
118 fn it_could_be_converted_from_i32() {
119 let counter: Counter = 42i32.into();
120 assert_eq!(counter.value(), 42);
121 }
122
123 #[test]
124 fn it_should_return_primitive_value() {
125 let counter = Counter::new(123);
126 assert_eq!(counter.primitive(), 123);
127 }
128
129 #[test]
130 fn it_should_handle_zero_value() {
131 let counter = Counter::new(0);
132 assert_eq!(counter.value(), 0);
133 assert_eq!(counter.primitive(), 0);
134 }
135
136 #[test]
137 fn it_should_handle_large_values() {
138 let counter = Counter::new(u64::MAX);
139 assert_eq!(counter.value(), u64::MAX);
140 }
141
142 #[test]
143 fn it_should_handle_u32_max_conversion() {
144 let counter: Counter = u32::MAX.into();
145 assert_eq!(counter.value(), u64::from(u32::MAX));
146 }
147
148 #[test]
149 fn it_should_handle_i32_max_conversion() {
150 let counter: Counter = i32::MAX.into();
151 assert_eq!(counter.value(), i32::MAX as u64);
152 }
153
154 #[test]
155 fn it_should_handle_negative_i32_conversion() {
156 let counter: Counter = (-42i32).into();
157 #[allow(clippy::cast_sign_loss)]
158 let expected = (-42i32) as u64;
159 assert_eq!(counter.value(), expected);
160 }
161
162 #[test]
163 fn it_should_handle_i32_min_conversion() {
164 let counter: Counter = i32::MIN.into();
165 #[allow(clippy::cast_sign_loss)]
166 let expected = i32::MIN as u64;
167 assert_eq!(counter.value(), expected);
168 }
169
170 #[test]
171 fn it_should_handle_large_increments() {
172 let mut counter = Counter::new(100);
173 counter.increment(1000);
174 assert_eq!(counter.value(), 1100);
175
176 counter.increment(u64::MAX - 1100);
177 assert_eq!(counter.value(), u64::MAX);
178 }
179
180 #[test]
181 fn it_should_support_multiple_absolute_operations() {
182 let mut counter = Counter::new(0);
183
184 counter.absolute(100);
185 assert_eq!(counter.value(), 100);
186
187 counter.absolute(50);
188 assert_eq!(counter.value(), 50);
189
190 counter.absolute(0);
191 assert_eq!(counter.value(), 0);
192 }
193
194 #[test]
195 fn it_should_be_displayable() {
196 let counter = Counter::new(42);
197 assert_eq!(counter.to_string(), "42");
198
199 let counter = Counter::new(0);
200 assert_eq!(counter.to_string(), "0");
201 }
202
203 #[test]
204 fn it_should_be_debuggable() {
205 let counter = Counter::new(42);
206 let debug_string = format!("{counter:?}");
207 assert_eq!(debug_string, "Counter(42)");
208 }
209
210 #[test]
211 fn it_should_be_cloneable() {
212 let counter = Counter::new(42);
213 let cloned_counter = counter.clone();
214 assert_eq!(counter, cloned_counter);
215 assert_eq!(counter.value(), cloned_counter.value());
216 }
217
218 #[test]
219 fn it_should_support_equality_comparison() {
220 let counter1 = Counter::new(42);
221 let counter2 = Counter::new(42);
222 let counter3 = Counter::new(43);
223
224 assert_eq!(counter1, counter2);
225 assert_ne!(counter1, counter3);
226 }
227
228 #[test]
229 fn it_should_have_default_value() {
230 let counter = Counter::default();
231 assert_eq!(counter.value(), 0);
232 }
233
234 #[test]
235 fn it_should_handle_conversion_roundtrip() {
236 let original_value = 12345u64;
237 let counter = Counter::from(original_value);
238 let converted_back: u64 = counter.into();
239 assert_eq!(original_value, converted_back);
240 }
241
242 #[test]
243 fn it_should_handle_u32_conversion_roundtrip() {
244 let original_value = 12345u32;
245 let counter = Counter::from(original_value);
246 assert_eq!(counter.value(), u64::from(original_value));
247 }
248
249 #[test]
250 fn it_should_handle_i32_conversion_roundtrip() {
251 let original_value = 12345i32;
252 let counter = Counter::from(original_value);
253 #[allow(clippy::cast_sign_loss)]
254 let expected = original_value as u64;
255 assert_eq!(counter.value(), expected);
256 }
257
258 #[test]
259 fn it_should_serialize_large_values_to_prometheus() {
260 let counter = Counter::new(u64::MAX);
261 assert_eq!(counter.to_prometheus(), u64::MAX.to_string());
262
263 let counter = Counter::new(0);
264 assert_eq!(counter.to_prometheus(), "0");
265 }
266}