1use serde::de::{self, Deserialize, Deserializer, Visitor};
4use serde::forward_to_deserialize_any;
5use std::fmt;
6
7#[derive(Debug)]
9enum Probe {
10 Fields(&'static [&'static str]),
12 NotAStruct,
14 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
36struct 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
65pub 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}