config/
field_names.rs

1//! Reports the field names a `Deserialize` impl accepts, without
2//! deserializing any real data.
3use serde::de::{self, Deserialize, Deserializer, Visitor};
4use serde::forward_to_deserialize_any;
5use std::fmt;
6
7/// The outcome of probing a type's `Deserialize` impl.
8#[derive(Debug)]
9enum Probe {
10    /// The field names the impl declared to `deserialize_struct`.
11    Fields(&'static [&'static str]),
12    /// The impl asked for something other than a struct.
13    NotAStruct,
14    /// The impl raised its own error before asking for anything.
15    Custom(String),
16}
17
18impl fmt::Display for Probe {
19    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
20        match self {
21            Self::Fields(fields) => write!(fmt, "struct with fields {fields:?}"),
22            Self::NotAStruct => write!(fmt, "not a struct"),
23            Self::Custom(msg) => write!(fmt, "{msg}"),
24        }
25    }
26}
27
28impl std::error::Error for Probe {}
29
30impl de::Error for Probe {
31    fn custom<T: fmt::Display>(msg: T) -> Self {
32        Self::Custom(msg.to_string())
33    }
34}
35
36/// A `Deserializer` that drives a type's `derive(Deserialize)` impl just far
37/// enough to learn its field names, without ever producing a value.
38/// `deserialize_struct` returns the field list it receives, as an error.
39/// Every other entry point returns `NotAStruct`.
40struct FieldNameProbe;
41
42impl<'de> Deserializer<'de> for FieldNameProbe {
43    type Error = Probe;
44
45    fn deserialize_struct<V: Visitor<'de>>(
46        self,
47        _name: &'static str,
48        fields: &'static [&'static str],
49        _visitor: V,
50    ) -> Result<V::Value, Probe> {
51        Err(Probe::Fields(fields))
52    }
53
54    fn deserialize_any<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Probe> {
55        Err(Probe::NotAStruct)
56    }
57
58    forward_to_deserialize_any! {
59        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
60        bytes byte_buf option unit unit_struct newtype_struct seq tuple
61        tuple_struct map enum identifier ignored_any
62    }
63}
64
65/// Returns the field names of `T` as they appear in input data, after `rename`
66/// and `rename_all`, for a plain `derive(Deserialize)` struct. Returns `None`
67/// for a struct with `#[serde(flatten)]`, an enum, a newtype, or a hand-written
68/// impl.
69///
70/// A field with `#[serde(alias)]` appears only under its primary name. Serde's
71/// derive does not include aliases in that name list. Callers that classify
72/// input keys against this list will treat an alias as unknown.
73pub fn struct_field_names<T: for<'de> Deserialize<'de>>() -> Option<&'static [&'static str]> {
74    match T::deserialize(FieldNameProbe) {
75        Err(Probe::Fields(fields)) => Some(fields),
76        Ok(_) | Err(Probe::NotAStruct) | Err(Probe::Custom(_)) => None,
77    }
78}
79
80#[cfg(test)]
81mod test {
82    use super::*;
83    use serde::Deserialize;
84
85    #[derive(Deserialize)]
86    #[serde(deny_unknown_fields)]
87    #[allow(dead_code)]
88    struct Plain {
89        alpha: u32,
90        #[serde(rename = "beta_renamed")]
91        beta: String,
92        #[serde(default)]
93        gamma: Option<bool>,
94    }
95
96    #[derive(Deserialize)]
97    #[serde(rename_all = "kebab-case")]
98    #[allow(dead_code)]
99    struct Kebab {
100        first_field: u32,
101        second_field: u32,
102    }
103
104    #[derive(Deserialize)]
105    #[allow(dead_code)]
106    struct Inner {
107        x: u32,
108    }
109
110    #[derive(Deserialize)]
111    #[allow(dead_code)]
112    struct Flattened {
113        a: u32,
114        #[serde(flatten)]
115        inner: Inner,
116    }
117
118    #[derive(Deserialize)]
119    #[allow(dead_code)]
120    enum Kind {
121        One,
122        Two,
123    }
124
125    #[derive(Deserialize)]
126    #[allow(dead_code)]
127    struct Newtype(u32);
128
129    #[test]
130    fn plain_struct_reports_renamed_fields() {
131        k9::assert_equal!(
132            struct_field_names::<Plain>(),
133            Some(&["alpha", "beta_renamed", "gamma"][..])
134        );
135    }
136
137    #[test]
138    fn rename_all_is_applied() {
139        k9::assert_equal!(
140            struct_field_names::<Kebab>(),
141            Some(&["first-field", "second-field"][..])
142        );
143    }
144
145    #[test]
146    fn flattened_struct_has_no_field_list() {
147        k9::assert_equal!(struct_field_names::<Flattened>(), None);
148    }
149
150    #[test]
151    fn non_structs_have_no_field_list() {
152        k9::assert_equal!(struct_field_names::<Kind>(), None);
153        k9::assert_equal!(struct_field_names::<Newtype>(), None);
154        k9::assert_equal!(struct_field_names::<u32>(), None);
155    }
156}