1#[cfg(test)]
17pub(crate) mod test;
18
19#[cfg(test)]
20mod test_datetime_from_json;
21
22#[cfg(test)]
23mod test_from_schema;
24
25use std::fmt;
26
27use chrono::{DateTime, NaiveDateTime, TimeZone as _, Utc};
28
29use crate::{
30 json, schema,
31 warning::{self, GatherWarnings as _},
32 FromSchema, IntoCaveat as _, Verdict,
33};
34
35#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
37pub enum Warning {
38 ContainsEscapeCodes,
40
41 Decode(json::decode::Warning),
43
44 Invalid(String),
61
62 InvalidType { type_found: json::ValueKind },
64}
65
66impl Warning {
67 fn invalid_type(elem: &json::Element<'_>) -> Self {
68 Self::InvalidType {
69 type_found: elem.value().kind(),
70 }
71 }
72}
73
74impl fmt::Display for Warning {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::ContainsEscapeCodes => {
78 f.write_str("The value contains escape codes but it does not need them.")
79 }
80 Self::Decode(warning) => fmt::Display::fmt(warning, f),
81 Self::Invalid(err) => write!(f, "The value is not valid: {err}"),
82 Self::InvalidType { type_found } => {
83 write!(f, "The value should be a string but found `{type_found}`.")
84 }
85 }
86 }
87}
88
89impl crate::Warning for Warning {
90 fn id(&self) -> warning::Id {
91 match self {
92 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
93 Self::Decode(kind) => kind.id(),
94 Self::Invalid(_) => warning::Id::from_static("invalid"),
95 Self::InvalidType { type_found } => {
96 warning::Id::from_string(format!("invalid_type({type_found})"))
97 }
98 }
99 }
100}
101
102impl From<json::decode::Warning> for Warning {
103 fn from(warn_kind: json::decode::Warning) -> Self {
104 Self::Decode(warn_kind)
105 }
106}
107
108impl json::FromJson<'_> for DateTime<Utc> {
109 type Warning = Warning;
110
111 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
112 let mut warnings = warning::Set::new();
113 let Some(s) = elem.to_raw_str() else {
114 return warnings.bail(elem, Warning::invalid_type(elem));
115 };
116
117 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
118
119 let s = match pending_str {
120 json::PendingStr::NoEscapes(s) => s,
121 json::PendingStr::HasEscapes(_) => {
122 return warnings.bail(elem, Warning::ContainsEscapeCodes);
123 }
124 };
125
126 let err = match s.parse::<DateTime<Utc>>() {
128 Ok(date) => return Ok(date.into_caveat(warnings)),
129 Err(err) => err,
130 };
131
132 let Ok(date) = s.parse::<NaiveDateTime>() else {
133 return warnings.bail(elem, Warning::Invalid(err.to_string()));
134 };
135
136 let datetime = Utc.from_utc_datetime(&date);
137 Ok(datetime.into_caveat(warnings))
138 }
139}
140
141impl<'buf> FromSchema<'buf, schema::Str<'buf>> for DateTime<Utc> {
142 type Warning = Warning;
143
144 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
145 let mut warnings = warning::Set::new();
146 let elem = source.element();
147
148 let pending_str = source
149 .value()
150 .has_escapes(elem)
151 .gather_warnings_into(&mut warnings);
152
153 let s = match pending_str {
154 json::PendingStr::NoEscapes(s) => s,
155 json::PendingStr::HasEscapes(_) => {
156 return warnings.bail(elem, Warning::ContainsEscapeCodes);
157 }
158 };
159
160 let err = match s.parse::<DateTime<Utc>>() {
162 Ok(date) => return Ok(date.into_caveat(warnings)),
163 Err(err) => err,
164 };
165
166 let Ok(date) = s.parse::<NaiveDateTime>() else {
167 return warnings.bail(elem, Warning::Invalid(err.to_string()));
168 };
169
170 let datetime = Utc.from_utc_datetime(&date);
171 Ok(datetime.into_caveat(warnings))
172 }
173}
174
175impl json::FromJson<'_> for chrono::NaiveDate {
176 type Warning = Warning;
177
178 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
179 let mut warnings = warning::Set::new();
180 let Some(s) = elem.to_raw_str() else {
181 return warnings.bail(elem, Warning::invalid_type(elem));
182 };
183
184 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
185
186 let s = match pending_str {
187 json::PendingStr::NoEscapes(s) => s,
188 json::PendingStr::HasEscapes(_) => {
189 return warnings.bail(elem, Warning::ContainsEscapeCodes);
190 }
191 };
192
193 let date = match s.parse::<chrono::NaiveDate>() {
194 Ok(v) => v,
195 Err(err) => {
196 return warnings.bail(elem, Warning::Invalid(err.to_string()));
197 }
198 };
199
200 Ok(date.into_caveat(warnings))
201 }
202}
203
204impl<'buf> FromSchema<'buf, schema::Str<'buf>> for chrono::NaiveDate {
205 type Warning = Warning;
206
207 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
208 let mut warnings = warning::Set::new();
209 let elem = source.element();
210
211 let pending_str = source
214 .value()
215 .has_escapes(elem)
216 .gather_warnings_into(&mut warnings);
217
218 let s = match pending_str {
219 json::PendingStr::NoEscapes(s) => s,
220 json::PendingStr::HasEscapes(_) => {
221 return warnings.bail(elem, Warning::ContainsEscapeCodes);
222 }
223 };
224
225 let date = match s.parse::<chrono::NaiveDate>() {
226 Ok(v) => v,
227 Err(err) => {
228 return warnings.bail(elem, Warning::Invalid(err.to_string()));
229 }
230 };
231
232 Ok(date.into_caveat(warnings))
233 }
234}
235
236impl json::FromJson<'_> for chrono::NaiveTime {
237 type Warning = Warning;
238
239 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
240 let mut warnings = warning::Set::new();
241 let value = elem.as_value();
242
243 let Some(s) = value.to_raw_str() else {
244 return warnings.bail(elem, Warning::invalid_type(elem));
245 };
246
247 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
248
249 let s = match pending_str {
250 json::PendingStr::NoEscapes(s) => s,
251 json::PendingStr::HasEscapes(_) => {
252 return warnings.bail(elem, Warning::ContainsEscapeCodes);
253 }
254 };
255
256 let date = match chrono::NaiveTime::parse_from_str(s, "%H:%M") {
257 Ok(v) => v,
258 Err(err) => {
259 return warnings.bail(elem, Warning::Invalid(err.to_string()));
260 }
261 };
262
263 Ok(date.into_caveat(warnings))
264 }
265}
266
267impl<'buf> FromSchema<'buf, schema::Str<'buf>> for chrono::NaiveTime {
268 type Warning = Warning;
269
270 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
271 let mut warnings = warning::Set::new();
272 let elem = source.element();
273
274 let pending_str = source
277 .value()
278 .has_escapes(elem)
279 .gather_warnings_into(&mut warnings);
280
281 let s = match pending_str {
282 json::PendingStr::NoEscapes(s) => s,
283 json::PendingStr::HasEscapes(_) => {
284 return warnings.bail(elem, Warning::ContainsEscapeCodes);
285 }
286 };
287
288 let date = match chrono::NaiveTime::parse_from_str(s, "%H:%M") {
289 Ok(v) => v,
290 Err(err) => {
291 return warnings.bail(elem, Warning::Invalid(err.to_string()));
292 }
293 };
294
295 Ok(date.into_caveat(warnings))
296 }
297}