poem_openapi/types/external/
chrono.rs

1use std::borrow::Cow;
2
3use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc};
4use poem::web::Field;
5use serde_json::Value;
6
7use crate::{
8    registry::{MetaSchema, MetaSchemaRef},
9    types::{
10        ParseError, ParseFromJSON, ParseFromMultipartField, ParseFromParameter, ParseResult,
11        ToJSON, Type,
12    },
13};
14
15macro_rules! impl_datetime_types {
16    ($ty:ty, $type_name:literal, $format:literal) => {
17        impl Type for $ty {
18            const IS_REQUIRED: bool = true;
19
20            type RawValueType = Self;
21
22            type RawElementValueType = Self;
23
24            fn name() -> Cow<'static, str> {
25                concat!($type_name, "_", $format).into()
26            }
27
28            fn schema_ref() -> MetaSchemaRef {
29                MetaSchemaRef::Inline(Box::new(MetaSchema::new_with_format($type_name, $format)))
30            }
31
32            fn as_raw_value(&self) -> Option<&Self::RawValueType> {
33                Some(self)
34            }
35
36            fn raw_element_iter<'a>(
37                &'a self,
38            ) -> Box<dyn Iterator<Item = &'a Self::RawElementValueType> + 'a> {
39                Box::new(self.as_raw_value().into_iter())
40            }
41        }
42
43        impl ParseFromJSON for $ty {
44            fn parse_from_json(value: Option<Value>) -> ParseResult<Self> {
45                let value = value.unwrap_or_default();
46                if let Value::String(value) = value {
47                    Ok(value.parse()?)
48                } else {
49                    Err(ParseError::expected_type(value))
50                }
51            }
52        }
53
54        impl ParseFromParameter for $ty {
55            fn parse_from_parameter(value: &str) -> ParseResult<Self> {
56                Ok(value.parse()?)
57            }
58        }
59
60        impl ParseFromMultipartField for $ty {
61            async fn parse_from_multipart(field: Option<Field>) -> ParseResult<Self> {
62                match field {
63                    Some(field) => Ok(field.text().await?.parse()?),
64                    None => Err(ParseError::expected_input()),
65                }
66            }
67        }
68
69        impl ToJSON for $ty {
70            fn to_json(&self) -> Option<Value> {
71                Some(Value::String(self.to_rfc3339()))
72            }
73        }
74    };
75}
76
77impl_datetime_types!(DateTime<Utc>, "string", "date-time");
78impl_datetime_types!(DateTime<Local>, "string", "date-time");
79impl_datetime_types!(DateTime<FixedOffset>, "string", "date-time");
80
81macro_rules! impl_naive_datetime_types {
82    ($ty:ty, $type_name:literal, $format:literal, $display:literal) => {
83        impl Type for $ty {
84            const IS_REQUIRED: bool = true;
85
86            type RawValueType = Self;
87
88            type RawElementValueType = Self;
89
90            fn name() -> Cow<'static, str> {
91                concat!($type_name, "_", $format).into()
92            }
93
94            fn schema_ref() -> MetaSchemaRef {
95                MetaSchemaRef::Inline(Box::new(MetaSchema::new_with_format($type_name, $format)))
96            }
97
98            fn as_raw_value(&self) -> Option<&Self::RawValueType> {
99                Some(self)
100            }
101
102            fn raw_element_iter<'a>(
103                &'a self,
104            ) -> Box<dyn Iterator<Item = &'a Self::RawElementValueType> + 'a> {
105                Box::new(self.as_raw_value().into_iter())
106            }
107        }
108
109        impl ParseFromJSON for $ty {
110            fn parse_from_json(value: Option<Value>) -> ParseResult<Self> {
111                let value = value.unwrap_or_default();
112                if let Value::String(value) = value {
113                    Ok(value.parse()?)
114                } else {
115                    Err(ParseError::expected_type(value))
116                }
117            }
118        }
119
120        impl ParseFromParameter for $ty {
121            fn parse_from_parameter(value: &str) -> ParseResult<Self> {
122                Ok(value.parse()?)
123            }
124        }
125
126        impl ParseFromMultipartField for $ty {
127            async fn parse_from_multipart(field: Option<Field>) -> ParseResult<Self> {
128                match field {
129                    Some(field) => Ok(field.text().await?.parse()?),
130                    None => Err(ParseError::expected_input()),
131                }
132            }
133        }
134
135        impl ToJSON for $ty {
136            fn to_json(&self) -> Option<Value> {
137                Some(Value::String(format!($display, &self)))
138            }
139        }
140    };
141}
142
143impl_naive_datetime_types!(NaiveDateTime, "string", "naive-date-time", "{:?}");
144impl_naive_datetime_types!(NaiveDate, "string", "naive-date", "{}");
145impl_naive_datetime_types!(NaiveTime, "string", "naive-time", "{}");
146
147#[cfg(test)]
148mod tests {
149    use chrono::TimeZone;
150
151    use super::*;
152
153    #[test]
154    fn date_time() {
155        let dt = Utc.from_utc_datetime(
156            &NaiveDate::from_ymd_opt(2015, 9, 18)
157                .unwrap()
158                .and_hms_opt(23, 56, 4)
159                .unwrap(),
160        );
161        let value = dt.to_json();
162        assert_eq!(
163            value,
164            Some(Value::String("2015-09-18T23:56:04+00:00".to_string()))
165        );
166        assert_eq!(
167            DateTime::<Utc>::parse_from_json(Some(Value::String(
168                "2015-09-18T23:56:04+00:00".to_string()
169            )))
170            .unwrap(),
171            Utc.from_utc_datetime(
172                &NaiveDate::from_ymd_opt(2015, 9, 18)
173                    .unwrap()
174                    .and_hms_opt(23, 56, 4)
175                    .unwrap()
176            )
177        );
178    }
179
180    #[test]
181    fn naive_date_time() {
182        let dt = NaiveDate::from_ymd_opt(2015, 9, 18)
183            .unwrap()
184            .and_hms_opt(23, 56, 4)
185            .unwrap();
186        let value = dt.to_json();
187        assert_eq!(
188            value,
189            Some(Value::String("2015-09-18T23:56:04".to_string()))
190        );
191        assert_eq!(
192            NaiveDateTime::parse_from_json(Some(Value::String("2015-09-18T23:56:04".to_string())))
193                .unwrap(),
194            NaiveDate::from_ymd_opt(2015, 9, 18)
195                .unwrap()
196                .and_hms_opt(23, 56, 4)
197                .unwrap()
198        );
199    }
200
201    #[test]
202    fn naive_date() {
203        let dt = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap();
204        let value = dt.to_json();
205        assert_eq!(value, Some(Value::String("2015-09-18".to_string())));
206        assert_eq!(
207            NaiveDate::parse_from_json(Some(Value::String("2015-09-18".to_string()))).unwrap(),
208            NaiveDate::from_ymd_opt(2015, 9, 18).unwrap()
209        );
210    }
211
212    #[test]
213    fn naive_time() {
214        let dt = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
215        let value = dt.to_json();
216        assert_eq!(value, Some(Value::String("23:56:04".to_string())));
217        assert_eq!(
218            NaiveTime::parse_from_json(Some(Value::String("23:56:04".to_string()))).unwrap(),
219            NaiveTime::from_hms_opt(23, 56, 4).unwrap()
220        );
221    }
222}