async_graphql/types/external/
jiff.rs

1use jiff::{
2    Span, Timestamp, Zoned,
3    civil::{Date, Time},
4};
5
6use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
7
8/// The `printf`-style format string for serializing/deserializing [`Date`].
9const DATE_FORMAT: &'static str = "%Y-%m-%d";
10
11/// The `printf`-style format string for serializing/deserializing [`Time`].
12const TIME_FORMAT: &'static str = "%H:%M:%S%.f";
13
14#[Scalar(internal, name = "Date")]
15impl ScalarType for Date {
16    fn parse(value: Value) -> InputValueResult<Self> {
17        match value {
18            Value::String(s) => Ok(Date::strptime(DATE_FORMAT, s)?),
19            _ => Err(InputValueError::expected_type(value)),
20        }
21    }
22
23    fn to_value(&self) -> Value {
24        Value::String(self.strftime(DATE_FORMAT).to_string())
25    }
26}
27
28#[Scalar(internal, name = "Time")]
29impl ScalarType for Time {
30    fn parse(value: Value) -> InputValueResult<Self> {
31        match value {
32            Value::String(s) => Ok(Time::strptime(TIME_FORMAT, s)?),
33            _ => Err(InputValueError::expected_type(value)),
34        }
35    }
36
37    fn to_value(&self) -> Value {
38        Value::String(self.strftime(TIME_FORMAT).to_string())
39    }
40}
41
42#[Scalar(
43    internal,
44    name = "DateTime",
45    specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339"
46)]
47impl ScalarType for Timestamp {
48    fn parse(value: Value) -> InputValueResult<Self> {
49        match value {
50            Value::String(s) => Ok(s.parse()?),
51            _ => Err(InputValueError::expected_type(value)),
52        }
53    }
54
55    fn to_value(&self) -> Value {
56        Value::String(self.to_string())
57    }
58}
59
60#[Scalar(
61    internal,
62    name = "ZonedDateTime",
63    specified_by_url = "https://datatracker.ietf.org/doc/html/rfc8536"
64)]
65impl ScalarType for Zoned {
66    fn parse(value: Value) -> InputValueResult<Self> {
67        match value {
68            Value::String(s) => Ok(s.parse()?),
69            _ => Err(InputValueError::expected_type(value)),
70        }
71    }
72
73    fn to_value(&self) -> Value {
74        Value::String(self.to_string())
75    }
76}
77
78#[Scalar(
79    internal,
80    name = "Duration",
81    specified_by_url = "https://en.wikipedia.org/wiki/ISO_8601#Durations"
82)]
83impl ScalarType for Span {
84    fn parse(value: Value) -> InputValueResult<Self> {
85        match &value {
86            Value::String(s) => Ok(s.parse()?),
87            _ => Err(InputValueError::expected_type(value)),
88        }
89    }
90
91    fn to_value(&self) -> Value {
92        Value::String(self.to_string())
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use jiff::{
99        Span, Timestamp, ToSpan, Zoned,
100        civil::{Date, Time},
101    };
102
103    use crate::{ScalarType, Value};
104
105    #[test]
106    fn test_span_to_value() {
107        let cases = [
108            (40.days(), "P40D"),
109            (1.year().days(1), "P1Y1D"),
110            (3.days().hours(4).minutes(59), "P3DT4H59M"),
111            (2.hours().minutes(30), "PT2H30M"),
112            (1.month(), "P1M"),
113            (1.week(), "P1W"),
114            (1.week().days(4), "P1W4D"),
115            (1.minute(), "PT1M"),
116            (2.milliseconds().microseconds(100), "PT0.0021S"),
117            (0.seconds(), "PT0S"),
118            (
119                1.year()
120                    .months(1)
121                    .days(1)
122                    .hours(1)
123                    .minutes(1)
124                    .seconds(1)
125                    .milliseconds(100),
126                "P1Y1M1DT1H1M1.1S",
127            ),
128        ];
129
130        for (value, expected) in cases {
131            let value = value.to_value();
132
133            if let Value::String(s) = value {
134                assert_eq!(s, expected);
135            } else {
136                panic!("Unexpected Value type when formatting Span: {:?}", value);
137            }
138        }
139    }
140
141    #[test]
142    fn test_span_parse() {
143        let cases = [
144            ("P40D", 40.days()),
145            ("P1y1d", 1.year().days(1)),
146            ("P3dT4h59m", 3.days().hours(4).minutes(59)),
147            ("PT2H30M", 2.hours().minutes(30)),
148            ("P1m", 1.month()),
149            ("P1w", 1.week()),
150            ("P1w4d", 1.week().days(4)),
151            ("PT1m", 1.minute()),
152            ("PT0.0021s", 2.milliseconds().microseconds(100)),
153            ("PT0s", 0.seconds()),
154            (
155                "P1y1m1dT1h1m1.1s",
156                1.year()
157                    .months(1)
158                    .days(1)
159                    .hours(1)
160                    .minutes(1)
161                    .seconds(1)
162                    .milliseconds(100),
163            ),
164        ];
165
166        for (value, expected) in cases {
167            let value = Value::String(value.to_string());
168            let parsed = <Span as ScalarType>::parse(value).unwrap();
169            assert_eq!(parsed.fieldwise(), expected.fieldwise());
170        }
171    }
172
173    #[test]
174    fn test_zoned_to_value() {
175        let cases = [
176            (
177                "2022-01-12T04:00:19.12345+00:00[UTC]"
178                    .parse::<Zoned>()
179                    .unwrap(),
180                "2022-01-12T04:00:19.12345+00:00[UTC]",
181            ),
182            (
183                "2022-01-12T04:00:19.12345-05:00[America/New_York]"
184                    .parse::<Zoned>()
185                    .unwrap(),
186                "2022-01-12T04:00:19.12345-05:00[America/New_York]",
187            ),
188        ];
189
190        for (value, expected) in cases {
191            let value = value.to_value();
192
193            if let Value::String(s) = value {
194                assert_eq!(s, expected);
195            } else {
196                panic!("Unexpected Value type when formatting Zoned: {:?}", value);
197            }
198        }
199    }
200
201    #[test]
202    fn test_zoned_parse() {
203        let cases = [
204            (
205                "2022-01-12T04:00:19.12345+00:00[UTC]",
206                "2022-01-12T04:00:19.12345+00:00[UTC]"
207                    .parse::<Zoned>()
208                    .unwrap(),
209            ),
210            (
211                "2022-01-12T04:00:19.12345-05:00[America/New_York]",
212                "2022-01-12T04:00:19.12345-05:00[America/New_York]"
213                    .parse::<Zoned>()
214                    .unwrap(),
215            ),
216        ];
217
218        for (value, expected) in cases {
219            let value = Value::String(value.to_string());
220            let parsed = <Zoned as ScalarType>::parse(value).unwrap();
221            assert_eq!(parsed, expected);
222        }
223    }
224
225    #[test]
226    fn test_timestamp_to_value() {
227        let cases = [
228            (
229                "2022-01-12T04:00:19.12345Z".parse::<Timestamp>().unwrap(),
230                "2022-01-12T04:00:19.12345Z",
231            ),
232            (
233                "2022-01-12T07:30:19Z".parse::<Timestamp>().unwrap(),
234                "2022-01-12T07:30:19Z",
235            ),
236        ];
237        for (value, expected) in cases {
238            let value = value.to_value();
239
240            if let Value::String(s) = value {
241                assert_eq!(s, expected);
242            } else {
243                panic!(
244                    "Unexpected Value type when formatting Timestamp: {:?}",
245                    value
246                );
247            }
248        }
249    }
250
251    #[test]
252    fn test_timestamp_parse() {
253        let cases = [
254            (
255                "2022-01-12T04:00:19.12345Z",
256                "2022-01-12T04:00:19.12345Z".parse::<Timestamp>().unwrap(),
257            ),
258            (
259                "2022-01-12T07:30:19Z",
260                "2022-01-12T07:30:19Z".parse::<Timestamp>().unwrap(),
261            ),
262        ];
263        for (value, expected) in cases {
264            let value = Value::String(value.to_string());
265            let parsed = <Timestamp as ScalarType>::parse(value).unwrap();
266            assert_eq!(parsed, expected);
267        }
268    }
269
270    #[test]
271    fn test_date_to_value() {
272        let cases = [
273            ("2022-01-12".parse::<Date>().unwrap(), "2022-01-12"),
274            ("2023-12-31".parse::<Date>().unwrap(), "2023-12-31"),
275        ];
276        for (value, expected) in cases {
277            let value = value.to_value();
278
279            if let Value::String(s) = value {
280                assert_eq!(s, expected);
281            } else {
282                panic!("Unexpected Value type when formatting Date: {:?}", value);
283            }
284        }
285    }
286
287    #[test]
288    fn test_time_to_value() {
289        let cases = [
290            ("04:00:19.12345".parse::<Time>().unwrap(), "04:00:19.12345"),
291            ("07:30:19".parse::<Time>().unwrap(), "07:30:19"),
292        ];
293        for (value, expected) in cases {
294            let value = value.to_value();
295
296            if let Value::String(s) = value {
297                assert_eq!(s, expected);
298            } else {
299                panic!("Unexpected Value type when formatting Time: {:?}", value);
300            }
301        }
302    }
303
304    #[test]
305    fn test_time_parse() {
306        let cases = [
307            ("04:00:19.12345", "04:00:19.12345".parse::<Time>().unwrap()),
308            ("07:30:19", "07:30:19".parse::<Time>().unwrap()),
309        ];
310        for (value, expected) in cases {
311            let value = Value::String(value.to_string());
312            let parsed = <Time as ScalarType>::parse(value).unwrap();
313            assert_eq!(parsed, expected);
314        }
315    }
316}