Skip to main content

icinga2_api/
serde.rs

1//! Custom deserializers for various parts of the Icinga API results
2
3use serde::Deserialize as _;
4
5/// deserializes a unix timestamp with sub second accuracy
6/// (usually 6 digits after the decimal point for icinga)
7///
8/// # Errors
9///
10/// returns an error if the value can not be parsed as an f64
11/// or if it can not be converted from a unix timestamp to a
12/// [time::OffsetDateTime]
13#[expect(
14    clippy::cast_possible_truncation,
15    clippy::as_conversions,
16    reason = "intentional conversion from f64 unix timestamp seconds to i128 nanoseconds for the Icinga API"
17)]
18pub fn deserialize_icinga_timestamp<'de, D>(
19    deserializer: D,
20) -> Result<time::OffsetDateTime, D::Error>
21where
22    D: serde::Deserializer<'de>,
23{
24    let f: f64 = f64::deserialize(deserializer)?;
25
26    let i = (f * 1_000_000_000f64) as i128;
27
28    time::OffsetDateTime::from_unix_timestamp_nanos(i).map_err(serde::de::Error::custom)
29}
30
31/// serialize a unix timestamp with sub second accuracy
32///
33/// # Errors
34///
35/// this should not return any errors
36#[expect(
37    clippy::cast_precision_loss,
38    clippy::as_conversions,
39    reason = "intentional conversion from i128 nanoseconds to f64 unix timestamp seconds for the Icinga API"
40)]
41pub fn serialize_icinga_timestamp<S>(
42    v: &time::OffsetDateTime,
43    serializer: S,
44) -> Result<S::Ok, S::Error>
45where
46    S: serde::Serializer,
47{
48    let ts = (v.unix_timestamp_nanos() as f64) / 1_000_000_000_f64;
49    <f64 as serde::Serialize>::serialize(&ts, serializer)
50}
51
52/// deserializes an optional unix timestamp with sub second accuracy
53/// (usually 6 digits after the decimal point for icinga)
54/// if the value is 0 return None
55///
56/// # Errors
57///
58/// returns an error if the value can not be parsed as an f64
59/// or if it can not be converted from a unix timestamp to a
60/// [time::OffsetDateTime]
61#[expect(
62    clippy::cast_possible_truncation,
63    clippy::as_conversions,
64    reason = "intentional conversion from f64 unix timestamp seconds to i128 nanoseconds for the Icinga API"
65)]
66pub fn deserialize_optional_icinga_timestamp<'de, D>(
67    deserializer: D,
68) -> Result<Option<time::OffsetDateTime>, D::Error>
69where
70    D: serde::Deserializer<'de>,
71{
72    let f: f64 = f64::deserialize(deserializer)?;
73
74    if f == 0.0f64 {
75        Ok(None)
76    } else {
77        let i = (f * 1_000_000_000f64) as i128;
78
79        Ok(Some(
80            time::OffsetDateTime::from_unix_timestamp_nanos(i).map_err(serde::de::Error::custom)?,
81        ))
82    }
83}
84
85/// serialize a unix timestamp with sub second accuracy
86/// if the value is None serialize 0
87///
88/// # Errors
89///
90/// this should not return any errors
91#[expect(
92    clippy::cast_precision_loss,
93    clippy::as_conversions,
94    reason = "intentional conversion from i128 nanoseconds to f64 unix timestamp seconds for the Icinga API"
95)]
96pub fn serialize_optional_icinga_timestamp<S>(
97    v: &Option<time::OffsetDateTime>,
98    serializer: S,
99) -> Result<S::Ok, S::Error>
100where
101    S: serde::Serializer,
102{
103    let mut ts = 0f64;
104    if let Some(v) = v {
105        ts = (v.unix_timestamp_nanos() as f64) / 1_000_000_000_f64;
106    }
107    <f64 as serde::Serialize>::serialize(&ts, serializer)
108}
109
110/// deserialize an optional String where None is represented as
111/// an empty string
112///
113/// # Errors
114///
115/// returns an error if the value can not be interpreted as null or a String
116pub fn deserialize_empty_string_or_string<'de, D>(
117    deserializer: D,
118) -> Result<Option<String>, D::Error>
119where
120    D: serde::Deserializer<'de>,
121{
122    let s: Option<String> = Option::deserialize(deserializer)?;
123
124    if let Some(s) = s {
125        if s.is_empty() { Ok(None) } else { Ok(Some(s)) }
126    } else {
127        Ok(None)
128    }
129}
130
131/// serialize an `Option<String>` as an empty string in the None case and normally
132/// otherwise
133///
134/// # Errors
135///
136/// this should not return any errors
137pub fn serialize_none_as_empty_string<S>(
138    v: &Option<String>,
139    serializer: S,
140) -> Result<S::Ok, S::Error>
141where
142    S: serde::Serializer,
143{
144    let mut s = &"".to_string();
145    if let Some(v) = v {
146        s = v;
147    }
148    <String as serde::Serialize>::serialize(s, serializer)
149}
150
151/// deserialize an optional value with a FromStr implementation where None is represented as
152/// an empty string
153///
154/// # Errors
155///
156/// returns an error if the value can not be interpreted as null or a String
157pub fn deserialize_empty_string_or_parse<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
158where
159    D: serde::Deserializer<'de>,
160    T: std::str::FromStr,
161    T::Err: std::fmt::Display,
162{
163    let s: Option<String> = Option::deserialize(deserializer)?;
164
165    if let Some(s) = s {
166        if s.is_empty() {
167            Ok(None)
168        } else {
169            Ok(Some(s.parse().map_err(serde::de::Error::custom)?))
170        }
171    } else {
172        Ok(None)
173    }
174}
175
176/// serialize an option value with a ToString implementation where None is represented as
177/// an empty string
178///
179/// # Errors
180///
181/// this should not return any errors
182pub fn serialize_none_as_empty_string_or_to_string<T, S>(
183    v: &Option<T>,
184    serializer: S,
185) -> Result<S::Ok, S::Error>
186where
187    S: serde::Serializer,
188    T: std::string::ToString,
189{
190    let mut s = "".to_string();
191    if let Some(v) = v {
192        s = v.to_string();
193    }
194    <String as serde::Serialize>::serialize(&s, serializer)
195}
196
197/// deserialize an integer as a time::Duration where the integer represents seconds
198///
199/// # Errors
200///
201/// returns an error if the value can not be parsed as an integer
202pub fn deserialize_seconds_as_duration<'de, D>(deserializer: D) -> Result<time::Duration, D::Error>
203where
204    D: serde::Deserializer<'de>,
205{
206    let i: i64 = i64::deserialize(deserializer)?;
207    Ok(time::Duration::seconds(i))
208}
209
210/// serialize a [time::Duration] as seconds
211///
212/// # Errors
213///
214/// this should not return any errors
215pub fn serialize_duration_as_seconds<S>(
216    v: &time::Duration,
217    serializer: S,
218) -> Result<S::Ok, S::Error>
219where
220    S: serde::Serializer,
221{
222    let d = v.whole_seconds();
223    <i64 as serde::Serialize>::serialize(&d, serializer)
224}
225
226/// deserialize an integer as a time::Duration where the integer represents seconds
227///
228/// # Errors
229///
230/// returns an error if the value can not be interpreted as null or an integer
231pub fn deserialize_optional_seconds_as_duration<'de, D>(
232    deserializer: D,
233) -> Result<Option<time::Duration>, D::Error>
234where
235    D: serde::Deserializer<'de>,
236{
237    let i: Option<i64> = Option::deserialize(deserializer)?;
238    if let Some(i) = i {
239        Ok(Some(time::Duration::seconds(i)))
240    } else {
241        Ok(None)
242    }
243}
244
245/// serialize an optional [time::Duration] as seconds
246///
247/// # Errors
248///
249/// this should not return any errors
250pub fn serialize_optional_duration_as_seconds<S>(
251    v: &Option<time::Duration>,
252    serializer: S,
253) -> Result<S::Ok, S::Error>
254where
255    S: serde::Serializer,
256{
257    let mut d: Option<i64> = None;
258    if let Some(v) = v {
259        d = Some(v.whole_seconds());
260    }
261    <Option<i64> as serde::Serialize>::serialize(&d, serializer)
262}