async_graphql/types/external/
jiff.rs1use jiff::{
2 Span, Timestamp, Zoned,
3 civil::{Date, Time},
4};
5
6use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
7
8const DATE_FORMAT: &'static str = "%Y-%m-%d";
10
11const 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}