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 { if let Ok(parsed) = OffsetDateTime::parse(value, &Rfc3339) { return Ok(parsed); } OffsetDateTime::deserialize(StrDeserializer::::new(value)) .map_err(|error| error.to_string()) } /// Deserialize a required timestamp, tolerating both shapes. pub fn deserialize_datetime<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(DateTimeVisitor) } /// Deserialize `Option`, tolerating both shapes plus `null`. pub fn deserialize_opt_datetime<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { deserializer.deserialize_option(OptDateTimeVisitor) } /// Deserialize a three-state `Option>` 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>, D::Error> where D: Deserializer<'de>, { crate::serde_patch::deserialize_opt_opt::(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>(deserializer: D) -> Result { 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(self, value: &str) -> Result { parse_offset_datetime(value).map_err(E::custom) } } struct OptDateTimeVisitor; impl<'de> Visitor<'de> for OptDateTimeVisitor { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("an RFC 3339 timestamp or null") } fn visit_none(self) -> Result { Ok(None) } fn visit_unit(self) -> Result { Ok(None) } fn visit_some>(self, deserializer: D) -> Result { deserialize_datetime(deserializer).map(Some) } }