1#[cfg(test)]
17pub(crate) mod test;
18
19#[cfg(test)]
20mod test_datetime_from_json;
21
22use std::fmt;
23
24use chrono::{DateTime, NaiveDateTime, TimeZone as _, Utc};
25
26use crate::{
27 into_caveat, into_caveat_all, json,
28 warning::{self, GatherWarnings as _},
29 IntoCaveat, Verdict,
30};
31
32#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
34pub enum Warning {
35 ContainsEscapeCodes,
37
38 Decode(json::decode::Warning),
40
41 Invalid(String),
58
59 InvalidType { type_found: json::ValueKind },
61}
62
63impl Warning {
64 fn invalid_type(elem: &json::Element<'_>) -> Self {
65 Self::InvalidType {
66 type_found: elem.value().kind(),
67 }
68 }
69}
70
71impl fmt::Display for Warning {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 Self::ContainsEscapeCodes => {
75 f.write_str("The value contains escape codes but it does not need them.")
76 }
77 Self::Decode(warning) => fmt::Display::fmt(warning, f),
78 Self::Invalid(err) => write!(f, "The value is not valid: {err}"),
79 Self::InvalidType { type_found } => {
80 write!(f, "The value should be a string but found `{type_found}`.")
81 }
82 }
83 }
84}
85
86impl crate::Warning for Warning {
87 fn id(&self) -> warning::Id {
88 match self {
89 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
90 Self::Decode(kind) => kind.id(),
91 Self::Invalid(_) => warning::Id::from_static("invalid"),
92 Self::InvalidType { .. } => warning::Id::from_static("invalid_type"),
93 }
94 }
95}
96
97impl From<json::decode::Warning> for Warning {
98 fn from(warn_kind: json::decode::Warning) -> Self {
99 Self::Decode(warn_kind)
100 }
101}
102
103into_caveat!(DateTime<Utc>);
104into_caveat_all!(chrono::NaiveTime, chrono::NaiveDate);
105
106impl json::FromJson<'_, '_> for DateTime<Utc> {
107 type Warning = Warning;
108
109 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
110 let mut warnings = warning::Set::new();
111 let Some(s) = elem.as_raw_str() else {
112 return warnings.bail(Warning::invalid_type(elem), elem);
113 };
114
115 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
116
117 let s = match pending_str {
118 json::decode::PendingStr::NoEscapes(s) => s,
119 json::decode::PendingStr::HasEscapes(_) => {
120 return warnings.bail(Warning::ContainsEscapeCodes, elem);
121 }
122 };
123
124 let err = match s.parse::<DateTime<Utc>>() {
126 Ok(date) => return Ok(date.into_caveat(warnings)),
127 Err(err) => err,
128 };
129
130 let Ok(date) = s.parse::<NaiveDateTime>() else {
131 return warnings.bail(Warning::Invalid(err.to_string()), elem);
132 };
133
134 let datetime = Utc.from_utc_datetime(&date);
135 Ok(datetime.into_caveat(warnings))
136 }
137}
138
139impl json::FromJson<'_, '_> for chrono::NaiveDate {
140 type Warning = Warning;
141
142 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
143 let mut warnings = warning::Set::new();
144 let Some(s) = elem.as_raw_str() else {
145 return warnings.bail(Warning::invalid_type(elem), elem);
146 };
147
148 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
149
150 let s = match pending_str {
151 json::decode::PendingStr::NoEscapes(s) => s,
152 json::decode::PendingStr::HasEscapes(_) => {
153 return warnings.bail(Warning::ContainsEscapeCodes, elem);
154 }
155 };
156
157 let date = match s.parse::<chrono::NaiveDate>() {
158 Ok(v) => v,
159 Err(err) => {
160 return warnings.bail(Warning::Invalid(err.to_string()), elem);
161 }
162 };
163
164 Ok(date.into_caveat(warnings))
165 }
166}
167
168impl json::FromJson<'_, '_> for chrono::NaiveTime {
169 type Warning = Warning;
170
171 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
172 let mut warnings = warning::Set::new();
173 let value = elem.as_value();
174
175 let Some(s) = value.as_raw_str() else {
176 return warnings.bail(Warning::invalid_type(elem), elem);
177 };
178
179 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
180
181 let s = match pending_str {
182 json::decode::PendingStr::NoEscapes(s) => s,
183 json::decode::PendingStr::HasEscapes(_) => {
184 return warnings.bail(Warning::ContainsEscapeCodes, elem);
185 }
186 };
187
188 let date = match chrono::NaiveTime::parse_from_str(s, "%H:%M") {
189 Ok(v) => v,
190 Err(err) => {
191 return warnings.bail(Warning::Invalid(err.to_string()), elem);
192 }
193 };
194
195 Ok(date.into_caveat(warnings))
196 }
197}