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 Gauge(f64);
8
9impl Gauge {
10 #[must_use]
11 pub fn new(value: f64) -> Self {
12 Self(value)
13 }
14
15 #[must_use]
16 pub fn value(&self) -> f64 {
17 self.0
18 }
19
20 #[must_use]
21 pub fn primitive(&self) -> f64 {
22 self.value()
23 }
24
25 pub fn set(&mut self, value: f64) {
26 self.0 = value;
27 }
28
29 pub fn increment(&mut self, value: f64) {
30 self.0 += value;
31 }
32
33 pub fn decrement(&mut self, value: f64) {
34 self.0 -= value;
35 }
36}
37
38impl From<f32> for Gauge {
39 fn from(value: f32) -> Self {
40 Self(f64::from(value))
41 }
42}
43
44impl From<f64> for Gauge {
45 fn from(value: f64) -> Self {
46 Self(value)
47 }
48}
49
50impl From<Gauge> for f64 {
51 fn from(counter: Gauge) -> Self {
52 counter.value()
53 }
54}
55
56impl PrometheusSerializable for Gauge {
57 fn to_prometheus(&self) -> String {
58 format!("{}", self.value())
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use approx::assert_relative_eq;
65
66 use super::*;
67
68 #[test]
69 fn it_should_be_created_from_integer_values() {
70 let gauge = Gauge::new(0.0);
71 assert_relative_eq!(gauge.value(), 0.0);
72 }
73
74 #[test]
75 fn it_could_be_converted_from_u64() {
76 let gauge: Gauge = 42.0.into();
77 assert_relative_eq!(gauge.value(), 42.0);
78 }
79
80 #[test]
81 fn it_could_be_converted_into_i64() {
82 let gauge = Gauge::new(42.0);
83 let value: f64 = gauge.into();
84 assert_relative_eq!(value, 42.0);
85 }
86
87 #[test]
88 fn it_could_be_set() {
89 let mut gauge = Gauge::new(0.0);
90 gauge.set(1.0);
91 assert_relative_eq!(gauge.value(), 1.0);
92 }
93
94 #[test]
95 fn it_could_be_incremented() {
96 let mut gauge = Gauge::new(0.0);
97 gauge.increment(1.0);
98 assert_relative_eq!(gauge.value(), 1.0);
99 }
100
101 #[test]
102 fn it_could_be_decremented() {
103 let mut gauge = Gauge::new(1.0);
104 gauge.decrement(1.0);
105 assert_relative_eq!(gauge.value(), 0.0);
106 }
107
108 #[test]
109 fn it_serializes_to_prometheus() {
110 let counter = Gauge::new(42.0);
111 assert_eq!(counter.to_prometheus(), "42");
112
113 let counter = Gauge::new(42.1);
114 assert_eq!(counter.to_prometheus(), "42.1");
115 }
116
117 #[test]
118 fn it_could_be_converted_from_f32() {
119 let gauge: Gauge = 42.5f32.into();
120 assert_relative_eq!(gauge.value(), 42.5);
121 }
122
123 #[test]
124 fn it_should_return_primitive_value() {
125 let gauge = Gauge::new(123.456);
126 assert_relative_eq!(gauge.primitive(), 123.456);
127 }
128
129 #[test]
130 fn it_should_handle_zero_value() {
131 let gauge = Gauge::new(0.0);
132 assert_relative_eq!(gauge.value(), 0.0);
133 assert_relative_eq!(gauge.primitive(), 0.0);
134 }
135
136 #[test]
137 fn it_should_handle_negative_values() {
138 let gauge = Gauge::new(-42.5);
139 assert_relative_eq!(gauge.value(), -42.5);
140 }
141
142 #[test]
143 fn it_should_handle_large_values() {
144 let gauge = Gauge::new(f64::MAX);
145 assert_relative_eq!(gauge.value(), f64::MAX);
146 }
147
148 #[test]
149 fn it_should_handle_infinity() {
150 let gauge = Gauge::new(f64::INFINITY);
151 assert_relative_eq!(gauge.value(), f64::INFINITY);
152 }
153
154 #[test]
155 fn it_should_handle_nan() {
156 let gauge = Gauge::new(f64::NAN);
157 assert!(gauge.value().is_nan());
158 }
159
160 #[test]
161 fn it_should_be_displayable() {
162 let gauge = Gauge::new(42.5);
163 assert_eq!(gauge.to_string(), "42.5");
164
165 let gauge = Gauge::new(0.0);
166 assert_eq!(gauge.to_string(), "0");
167 }
168
169 #[test]
170 fn it_should_be_debuggable() {
171 let gauge = Gauge::new(42.5);
172 let debug_string = format!("{gauge:?}");
173 assert_eq!(debug_string, "Gauge(42.5)");
174 }
175
176 #[test]
177 fn it_should_be_cloneable() {
178 let gauge = Gauge::new(42.5);
179 let cloned_gauge = gauge.clone();
180 assert_eq!(gauge, cloned_gauge);
181 assert_relative_eq!(gauge.value(), cloned_gauge.value());
182 }
183
184 #[test]
185 fn it_should_support_equality_comparison() {
186 let gauge1 = Gauge::new(42.5);
187 let gauge2 = Gauge::new(42.5);
188 let gauge3 = Gauge::new(43.0);
189
190 assert_eq!(gauge1, gauge2);
191 assert_ne!(gauge1, gauge3);
192 }
193
194 #[test]
195 fn it_should_have_default_value() {
196 let gauge = Gauge::default();
197 assert_relative_eq!(gauge.value(), 0.0);
198 }
199
200 #[test]
201 fn it_should_handle_conversion_roundtrip() {
202 let original_value = 12345.678;
203 let gauge = Gauge::from(original_value);
204 let converted_back: f64 = gauge.into();
205 assert_relative_eq!(original_value, converted_back);
206 }
207
208 #[test]
209 fn it_should_handle_f32_conversion_roundtrip() {
210 let original_value = 12345.5f32;
211 let gauge = Gauge::from(original_value);
212 assert_relative_eq!(gauge.value(), f64::from(original_value));
213 }
214
215 #[test]
216 fn it_should_handle_multiple_operations() {
217 let mut gauge = Gauge::new(100.0);
218
219 gauge.increment(50.0);
220 assert_relative_eq!(gauge.value(), 150.0);
221
222 gauge.decrement(25.0);
223 assert_relative_eq!(gauge.value(), 125.0);
224
225 gauge.set(200.0);
226 assert_relative_eq!(gauge.value(), 200.0);
227 }
228
229 #[test]
230 fn it_should_serialize_special_values_to_prometheus() {
231 let gauge = Gauge::new(f64::INFINITY);
232 assert_eq!(gauge.to_prometheus(), "inf");
233
234 let gauge = Gauge::new(f64::NEG_INFINITY);
235 assert_eq!(gauge.to_prometheus(), "-inf");
236
237 let gauge = Gauge::new(f64::NAN);
238 assert_eq!(gauge.to_prometheus(), "NaN");
239 }
240}