951a522
use serde::Deserialize;
use serde::de::value::StrDeserializer;
use serde::de::{self, Deserializer, Visitor};
use std::fmt;
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
/// Parse a timestamp in either shape a client may send.
///
/// `time`'s `serde-human-readable` feature — enabled workspace-wide — parses and
/// emits `YYYY-MM-DD HH:MM:SS.ffffff +00:00:00` and nothing else. Browser
/// clients send RFC 3339 (`new Date(...).toISOString()`), and the OpenAPI schema
/// advertises `format: date-time` (RFC 3339) for these fields, so the documented
/// contract used to fail with a 422 on every write.
///
/// RFC 3339 is tried first, then `time`'s own default parser (the legacy
/// space-separated shape), so payloads written against either convention work.
pub fn parse_offset_datetime(value: &str) -> Result<OffsetDateTime, String> {
if let Ok(parsed) = OffsetDateTime::parse(value, &Rfc3339) {
return Ok(parsed);
}
OffsetDateTime::deserialize(StrDeserializer::<de::value::Error>::new(value))
.map_err(|error| error.to_string())
}
/// Deserialize a required timestamp, tolerating both shapes.
pub fn deserialize_datetime<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(DateTimeVisitor)
}
/// Deserialize `Option<OffsetDateTime>`, tolerating both shapes plus `null`.
pub fn deserialize_opt_datetime<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_option(OptDateTimeVisitor)
}
/// Deserialize a three-state `Option<Option<OffsetDateTime>>` update field:
/// absent leaves the column alone, `null` clears it, a value sets it.
///
/// Composes the shared three-state reader with this module's parser, so a
/// clearable timestamp accepts both `null` and RFC 3339.
pub fn deserialize_opt_opt_datetime<'de, D>(
deserializer: D,
) -> Result<Option<Option<OffsetDateTime>>, D::Error>
where
D: Deserializer<'de>,
{
crate::serde_patch::deserialize_opt_opt::<D, DateTimeWire>(deserializer)
.map(|outer| outer.map(|inner| inner.map(|wrapped| wrapped.0)))
}
/// Newtype so the shared three-state reader can drive this module's parser.
struct DateTimeWire(OffsetDateTime);
impl<'de> Deserialize<'de> for DateTimeWire {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserialize_datetime(deserializer).map(DateTimeWire)
}
}
struct DateTimeVisitor;
impl<'de> Visitor<'de> for DateTimeVisitor {
type Value = OffsetDateTime;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an RFC 3339 timestamp")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
parse_offset_datetime(value).map_err(E::custom)
}
}
struct OptDateTimeVisitor;
impl<'de> Visitor<'de> for OptDateTimeVisitor {
type Value = Option<OffsetDateTime>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an RFC 3339 timestamp or null")
}
fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
deserialize_datetime(deserializer).map(Some)
}
}