junction_api/
shared.rs

1//! Shared configuration.
2
3use core::fmt;
4use serde::de::{self, Visitor};
5use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
6use std::str::FromStr;
7use std::time::Duration as StdDuration;
8
9#[cfg(feature = "typeinfo")]
10use junction_typeinfo::TypeInfo;
11
12/// A fraction, expressed as a numerator and a denominator.
13#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
14#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
15pub struct Fraction {
16    pub numerator: i32,
17
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub denominator: Option<i32>,
20}
21
22/// A regular expression.
23///
24/// `Regex` has same syntax and semantics as Rust's [`regex` crate](https://docs.rs/regex/latest/regex/).
25#[derive(Clone)]
26pub struct Regex(regex::Regex);
27
28impl PartialEq for Regex {
29    fn eq(&self, other: &Self) -> bool {
30        self.0.as_str() == other.0.as_str()
31    }
32}
33
34impl Eq for Regex {}
35
36impl std::fmt::Debug for Regex {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
38        f.write_str(self.0.as_str())
39    }
40}
41
42impl std::ops::Deref for Regex {
43    type Target = regex::Regex;
44
45    fn deref(&self) -> &Self::Target {
46        &self.0
47    }
48}
49
50impl AsRef<regex::Regex> for Regex {
51    fn as_ref(&self) -> &regex::Regex {
52        &self.0
53    }
54}
55
56impl FromStr for Regex {
57    type Err = String;
58
59    fn from_str(s: &str) -> Result<Self, Self::Err> {
60        match regex::Regex::try_from(s) {
61            Ok(e) => Ok(Self(e)),
62            Err(e) => Err(e.to_string()),
63        }
64    }
65}
66
67#[cfg(feature = "typeinfo")]
68impl TypeInfo for Regex {
69    fn kind() -> junction_typeinfo::Kind {
70        junction_typeinfo::Kind::String
71    }
72}
73
74impl serde::Serialize for Regex {
75    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76    where
77        S: serde::Serializer,
78    {
79        serializer.serialize_str(self.0.as_str())
80    }
81}
82
83impl<'de> Deserialize<'de> for Regex {
84    fn deserialize<D>(deserializer: D) -> Result<Regex, D::Error>
85    where
86        D: Deserializer<'de>,
87    {
88        struct RegexVisitor;
89
90        impl Visitor<'_> for RegexVisitor {
91            type Value = Regex;
92
93            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
94                formatter.write_str("a Regex")
95            }
96
97            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
98            where
99                E: de::Error,
100            {
101                match Regex::from_str(value) {
102                    Ok(s) => Ok(s),
103                    Err(e) => Err(E::custom(format!("could not parse {}: {}", value, e))),
104                }
105            }
106        }
107        deserializer.deserialize_string(RegexVisitor)
108    }
109}
110
111/// A wrapper around [std::time::Duration] that serializes to and from a f64
112/// number of seconds.
113#[derive(Copy, Clone, PartialEq, Eq)]
114pub struct Duration(StdDuration);
115
116impl Duration {
117    pub const fn new(secs: u64, nanos: u32) -> Duration {
118        Duration(StdDuration::new(secs, nanos))
119    }
120}
121
122impl AsRef<StdDuration> for Duration {
123    fn as_ref(&self) -> &StdDuration {
124        &self.0
125    }
126}
127
128impl std::ops::Deref for Duration {
129    type Target = StdDuration;
130
131    fn deref(&self) -> &Self::Target {
132        &self.0
133    }
134}
135
136impl std::fmt::Debug for Duration {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        self.0.fmt(f)
139    }
140}
141
142impl std::fmt::Display for Duration {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        write!(f, "{}", self.as_secs_f64())
145    }
146}
147
148#[cfg(feature = "typeinfo")]
149impl TypeInfo for Duration {
150    fn kind() -> junction_typeinfo::Kind {
151        junction_typeinfo::Kind::Duration
152    }
153}
154
155impl From<Duration> for StdDuration {
156    fn from(val: Duration) -> Self {
157        val.0
158    }
159}
160
161impl From<StdDuration> for Duration {
162    fn from(duration: StdDuration) -> Self {
163        Duration(duration)
164    }
165}
166
167macro_rules! duration_from {
168    ($($(#[$attr:meta])* $method:ident: $arg:ty),* $(,)*) => {
169        impl Duration {
170            $(
171            $(#[$attr])*
172            pub fn $method(val: $arg) -> Self {
173                Duration(StdDuration::$method(val))
174            }
175            )*
176        }
177    };
178}
179
180duration_from! {
181    /// Create a new `Duration` from a whole number of seconds. See
182    /// [Duration::from_secs][std::time::Duration::from_secs].
183    from_secs: u64,
184
185    /// Create a new `Duration` from a whole number of milliseconds. See
186    /// [Duration::from_millis][std::time::Duration::from_millis].
187    from_millis: u64,
188
189    /// Create a new `Duration` from a whole number of microseconds. See
190    /// [Duration::from_micros][std::time::Duration::from_micros].
191    from_micros: u64,
192
193    /// Create a new `Duration` from a floating point number of seconds. See
194    /// [Duration::from_secs_f32][std::time::Duration::from_secs_f32].
195    from_secs_f32: f32,
196
197
198    /// Create a new `Duration` from a floating point number of seconds. See
199    /// [Duration::from_secs_f64][std::time::Duration::from_secs_f64].
200    from_secs_f64: f64,
201}
202
203impl Serialize for Duration {
204    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
205    where
206        S: Serializer,
207    {
208        serializer.serialize_f64(self.as_secs_f64())
209    }
210}
211
212impl<'de> Deserialize<'de> for Duration {
213    fn deserialize<D>(deserializer: D) -> Result<Duration, D::Error>
214    where
215        D: Deserializer<'de>,
216    {
217        // deserialize as a number of seconds, may be any int or float
218        //
219        // https://serde.rs/string-or-struct.html
220        struct DurationVisitor;
221
222        impl Visitor<'_> for DurationVisitor {
223            type Value = Duration;
224
225            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
226                formatter.write_str("a Duration expressed as a number of seconds")
227            }
228
229            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
230            where
231                E: de::Error,
232            {
233                Ok(Duration::from(StdDuration::from_secs_f64(v)))
234            }
235
236            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
237            where
238                E: de::Error,
239            {
240                Ok(Duration::from(StdDuration::from_secs(v)))
241            }
242
243            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
244            where
245                E: de::Error,
246            {
247                let v: u64 = v
248                    .try_into()
249                    .map_err(|_| E::custom("Duration cannot be negative"))?;
250
251                Ok(Duration::from(StdDuration::from_secs(v)))
252            }
253        }
254
255        deserializer.deserialize_any(DurationVisitor)
256    }
257}
258
259#[cfg(test)]
260mod test_duration {
261    use super::*;
262
263    #[test]
264    /// Duration should deserialize from strings, an int number of seconds, or a
265    /// float number of seconds.
266    fn test_duration_deserialize() {
267        #[derive(Debug, Deserialize, PartialEq, Eq)]
268        struct SomeValue {
269            float_duration: Duration,
270            int_duration: Duration,
271        }
272
273        let value = serde_json::json!({
274            "string_duration": "1h23m4s",
275            "float_duration": 1.234,
276            "int_duration": 1234,
277        });
278
279        assert_eq!(
280            serde_json::from_value::<SomeValue>(value).unwrap(),
281            SomeValue {
282                float_duration: Duration::from_millis(1234),
283                int_duration: Duration::from_secs(1234),
284            }
285        );
286    }
287
288    #[test]
289    /// Duration should always serialize to the string representation.
290    fn test_duration_serialize() {
291        #[derive(Debug, Serialize, PartialEq, Eq)]
292        struct SomeValue {
293            duration: Duration,
294        }
295
296        assert_eq!(
297            serde_json::json!({
298                "duration": 123.456,
299            }),
300            serde_json::to_value(SomeValue {
301                duration: Duration::from_secs_f64(123.456),
302            })
303            .unwrap(),
304        );
305    }
306}