poem_openapi/types/external/
chrono.rs1use 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}