#[cfg(test)]
pub(crate) mod test;
#[cfg(test)]
mod test_datetime_from_json;
use std::fmt;
use chrono::{DateTime, NaiveDateTime, TimeZone as _, Utc};
use crate::{
into_caveat, into_caveat_all, json,
warning::{self, GatherWarnings as _},
IntoCaveat, Verdict,
};
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Warning {
ContainsEscapeCodes,
Decode(json::decode::Warning),
Invalid(String),
InvalidType { type_found: json::ValueKind },
}
impl Warning {
fn invalid_type(elem: &json::Element<'_>) -> Self {
Self::InvalidType {
type_found: elem.value().kind(),
}
}
}
impl fmt::Display for Warning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ContainsEscapeCodes => {
f.write_str("The value contains escape codes but it does not need them.")
}
Self::Decode(warning) => fmt::Display::fmt(warning, f),
Self::Invalid(err) => write!(f, "The value is not valid: {err}"),
Self::InvalidType { type_found } => {
write!(f, "The value should be a string but found `{type_found}`.")
}
}
}
}
impl crate::Warning for Warning {
fn id(&self) -> warning::Id {
match self {
Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
Self::Decode(kind) => kind.id(),
Self::Invalid(_) => warning::Id::from_static("invalid"),
Self::InvalidType { .. } => warning::Id::from_static("invalid_type"),
}
}
}
impl From<json::decode::Warning> for Warning {
fn from(warn_kind: json::decode::Warning) -> Self {
Self::Decode(warn_kind)
}
}
into_caveat!(DateTime<Utc>);
into_caveat_all!(chrono::NaiveTime, chrono::NaiveDate);
impl json::FromJson<'_, '_> for DateTime<Utc> {
type Warning = Warning;
fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
let Some(s) = elem.as_raw_str() else {
return warnings.bail(Warning::invalid_type(elem), elem);
};
let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
let s = match pending_str {
json::decode::PendingStr::NoEscapes(s) => s,
json::decode::PendingStr::HasEscapes(_) => {
return warnings.bail(Warning::ContainsEscapeCodes, elem);
}
};
let err = match s.parse::<DateTime<Utc>>() {
Ok(date) => return Ok(date.into_caveat(warnings)),
Err(err) => err,
};
let Ok(date) = s.parse::<NaiveDateTime>() else {
return warnings.bail(Warning::Invalid(err.to_string()), elem);
};
let datetime = Utc.from_utc_datetime(&date);
Ok(datetime.into_caveat(warnings))
}
}
impl json::FromJson<'_, '_> for chrono::NaiveDate {
type Warning = Warning;
fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
let Some(s) = elem.as_raw_str() else {
return warnings.bail(Warning::invalid_type(elem), elem);
};
let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
let s = match pending_str {
json::decode::PendingStr::NoEscapes(s) => s,
json::decode::PendingStr::HasEscapes(_) => {
return warnings.bail(Warning::ContainsEscapeCodes, elem);
}
};
let date = match s.parse::<chrono::NaiveDate>() {
Ok(v) => v,
Err(err) => {
return warnings.bail(Warning::Invalid(err.to_string()), elem);
}
};
Ok(date.into_caveat(warnings))
}
}
impl json::FromJson<'_, '_> for chrono::NaiveTime {
type Warning = Warning;
fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
let value = elem.as_value();
let Some(s) = value.as_raw_str() else {
return warnings.bail(Warning::invalid_type(elem), elem);
};
let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
let s = match pending_str {
json::decode::PendingStr::NoEscapes(s) => s,
json::decode::PendingStr::HasEscapes(_) => {
return warnings.bail(Warning::ContainsEscapeCodes, elem);
}
};
let date = match chrono::NaiveTime::parse_from_str(s, "%H:%M") {
Ok(v) => v,
Err(err) => {
return warnings.bail(Warning::Invalid(err.to_string()), elem);
}
};
Ok(date.into_caveat(warnings))
}
}