use serde::Deserialize; use serde::de::{self, Deserializer, Visitor}; use std::fmt; use std::marker::PhantomData; /// Deserialize the three-state update field `Option>`. /// /// The documented PATCH contract (`lib/webapi/src/openapi.rs`) is: /// field absent → unchanged, field `null` → cleared, field value → set. /// /// serde does not unwrap the outer `Option` by itself, so a derived /// `Option>` collapses "absent" and "null" into `None` and every /// clear request silently no-ops. This maps `null` to `Some(None)` and a value /// to `Some(Some(value))`, which is what the service layer branches on. pub fn deserialize_opt_opt<'de, D, T>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, T: Deserialize<'de>, { deserializer.deserialize_option(OptOptVisitor(PhantomData)) } struct OptOptVisitor(PhantomData); impl<'de, T> Visitor<'de> for OptOptVisitor where T: Deserialize<'de>, { type Value = Option>; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a value, null, or nothing") } /// JSON `null`: the caller asked for the field to be cleared. fn visit_none(self) -> Result { Ok(Some(None)) } fn visit_unit(self) -> Result { Ok(Some(None)) } fn visit_some>(self, deserializer: D) -> Result { T::deserialize(deserializer).map(|value| Some(Some(value))) } }