ocre/fields.rs
1//! Serde helpers for optional (`NULL`-able) fields in forms and JSON bodies.
2//!
3//! HTML forms send every field as a string, and an empty input means "no
4//! value". JSON bodies send `null` or a typed value. These helpers accept both.
5
6use std::{fmt::Display, str::FromStr};
7
8use serde::{Deserialize, Deserializer};
9
10#[derive(Deserialize)]
11#[serde(untagged)]
12enum Raw<T> {
13 Text(String),
14 Value(T),
15}
16
17fn parse<T, E>(raw: Option<Raw<T>>) -> Result<Option<T>, E>
18where
19 T: FromStr,
20 T::Err: Display,
21 E: serde::de::Error,
22{
23 match raw {
24 None => Ok(None),
25 Some(Raw::Value(value)) => Ok(Some(value)),
26 Some(Raw::Text(text)) if text.trim().is_empty() => Ok(None),
27 Some(Raw::Text(text)) => text.parse().map(Some).map_err(E::custom),
28 }
29}
30
31/// Deserializes an optional field where `null`, a missing value or an empty string is `None`.
32///
33/// Use on `Option<T>` fields with
34/// `#[serde(default, deserialize_with = "ocre::optional")]` (`default` makes a
35/// missing key `None`). A typed value (`42`) is taken as is; a blank string
36/// (only whitespace) is `None`; any other string (`"42"`, as HTML forms send)
37/// is parsed with `T::from_str`.
38///
39/// # Errors
40///
41/// Fails with the deserializer's error, carrying `T::Err`'s message, when a
42/// non-empty string does not parse. Behind [`Json`](crate::Json) that is a JSON 400.
43///
44/// # Examples
45///
46/// ```
47/// use serde::Deserialize;
48///
49/// #[derive(Deserialize)]
50/// struct Book {
51/// #[serde(default, deserialize_with = "ocre::optional")]
52/// pages: Option<i64>,
53/// }
54///
55/// let parse = |json| serde_json::from_str::<Book>(json).map(|book| book.pages);
56/// assert_eq!(parse(r#"{"pages": 320}"#).unwrap(), Some(320));
57/// assert_eq!(parse(r#"{"pages": "320"}"#).unwrap(), Some(320));
58/// assert_eq!(parse(r#"{"pages": ""}"#).unwrap(), None);
59/// assert_eq!(parse(r#"{"pages": null}"#).unwrap(), None);
60/// assert_eq!(parse("{}").unwrap(), None);
61/// assert!(parse(r#"{"pages": "many"}"#).is_err());
62/// ```
63pub fn optional<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
64where
65 D: Deserializer<'de>,
66 T: Deserialize<'de> + FromStr,
67 T::Err: Display,
68{
69 parse(Option::<Raw<T>>::deserialize(deserializer)?)
70}
71
72/// Deserializes a field of a partial update (PATCH) into keep / clear / set.
73///
74/// Use on `Option<Option<T>>` fields with
75/// `#[serde(default, deserialize_with = "ocre::patch")]`: a missing field is
76/// `None` (keep the value, thanks to `default`), `null` or an empty string is
77/// `Some(None)` (clear it), a value is `Some(Some(value))`. Strings are parsed
78/// with `T::from_str`, as in [`optional`].
79///
80/// # Errors
81///
82/// Fails with the deserializer's error, carrying `T::Err`'s message, when a
83/// non-empty string does not parse.
84///
85/// # Examples
86///
87/// ```
88/// use serde::Deserialize;
89///
90/// #[derive(Deserialize)]
91/// struct BookChanges {
92/// #[serde(default, deserialize_with = "ocre::patch")]
93/// pages: Option<Option<i64>>,
94/// }
95///
96/// let parse = |json| serde_json::from_str::<BookChanges>(json).unwrap().pages;
97/// assert_eq!(parse("{}"), None); // keep
98/// assert_eq!(parse(r#"{"pages": null}"#), Some(None)); // clear
99/// assert_eq!(parse(r#"{"pages": ""}"#), Some(None)); // clear (HTML form)
100/// assert_eq!(parse(r#"{"pages": 12}"#), Some(Some(12))); // set
101/// ```
102pub fn patch<'de, D, T>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
103where
104 D: Deserializer<'de>,
105 T: Deserialize<'de> + FromStr,
106 T::Err: Display,
107{
108 parse::<T, D::Error>(Option::<Raw<T>>::deserialize(deserializer)?).map(Some)
109}
110
111/// Deserializes an optional JSON field of a partial update into keep / clear / set.
112///
113/// [`patch`] for `Option<Option<serde_json::Value>>` fields: a missing field
114/// is `None`, `null` is `Some(None)`, any other JSON value (strings included,
115/// taken as JSON strings, not parsed) is `Some(Some(value))`. Use with
116/// `#[serde(default, deserialize_with = "ocre::patch_json")]`.
117///
118/// # Errors
119///
120/// Only the deserializer's own errors (malformed input); every JSON value is accepted.
121///
122/// # Examples
123///
124/// ```
125/// use serde::Deserialize;
126/// use serde_json::json;
127///
128/// #[derive(Deserialize)]
129/// struct PostChanges {
130/// #[serde(default, deserialize_with = "ocre::patch_json")]
131/// metadata: Option<Option<serde_json::Value>>,
132/// }
133///
134/// let parse = |json| serde_json::from_str::<PostChanges>(json).unwrap().metadata;
135/// assert_eq!(parse("{}"), None);
136/// assert_eq!(parse(r#"{"metadata": null}"#), Some(None));
137/// assert_eq!(parse(r#"{"metadata": {"a": 1}}"#), Some(Some(json!({"a": 1}))));
138/// assert_eq!(parse(r#"{"metadata": "{}"}"#), Some(Some(json!("{}"))));
139/// ```
140pub fn patch_json<'de, D>(deserializer: D) -> Result<Option<Option<serde_json::Value>>, D::Error>
141where
142 D: Deserializer<'de>,
143{
144 Option::<serde_json::Value>::deserialize(deserializer).map(Some)
145}
146
147#[cfg(test)]
148#[path = "../tests/fields.rs"]
149mod tests;