Skip to main content

torrust_metrics/
counter.rs

1use 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}