schemars_derive/attr/
schemars_to_serde.rs
1use quote::ToTokens;
2use serde_derive_internals::Ctxt;
3use std::collections::btree_map::Entry;
4use std::collections::{BTreeMap, BTreeSet};
5use syn::parse::Parser;
6use syn::{Attribute, Data, Field, Variant};
7
8use super::{get_meta_items, CustomMeta};
9
10pub(crate) static SERDE_KEYWORDS: &[&str] = &[
13 "rename",
14 "rename_all",
15 "rename_all_fields",
16 "deny_unknown_fields",
17 "tag",
18 "content",
19 "untagged",
20 "default",
21 "skip",
22 "skip_serializing",
23 "skip_serializing_if",
24 "skip_deserializing",
25 "flatten",
26 "remote",
27 "transparent",
28 "into",
29 "from",
30 "try_from",
31 "bound",
34 "serialize_with",
41 "with",
42];
43
44pub(crate) static SCHEMARS_KEYWORDS_PARSED_BY_SERDE: &[&str] =
45 SERDE_KEYWORDS.split_at(SERDE_KEYWORDS.len() - 2).0;
47
48pub fn process_serde_attrs(input: &mut syn::DeriveInput) -> syn::Result<()> {
51 let ctxt = Ctxt::new();
52 process_attrs(&ctxt, &mut input.attrs);
53 match &mut input.data {
54 Data::Struct(s) => process_serde_field_attrs(&ctxt, s.fields.iter_mut()),
55 Data::Enum(e) => process_serde_variant_attrs(&ctxt, e.variants.iter_mut()),
56 Data::Union(u) => process_serde_field_attrs(&ctxt, u.fields.named.iter_mut()),
57 }
58
59 ctxt.check()
60}
61
62fn process_serde_variant_attrs<'a>(ctxt: &Ctxt, variants: impl Iterator<Item = &'a mut Variant>) {
63 for v in variants {
64 process_attrs(ctxt, &mut v.attrs);
65 process_serde_field_attrs(ctxt, v.fields.iter_mut());
66 }
67}
68
69fn process_serde_field_attrs<'a>(ctxt: &Ctxt, fields: impl Iterator<Item = &'a mut Field>) {
70 for f in fields {
71 process_attrs(ctxt, &mut f.attrs);
72 }
73}
74
75fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec<Attribute>) {
76 let (serde_attrs, other_attrs): (Vec<_>, Vec<_>) =
78 attrs.drain(..).partition(|at| at.path().is_ident("serde"));
79 *attrs = other_attrs;
80
81 let mut effective_serde_meta = Vec::new();
82 let mut unset_meta = BTreeMap::new();
83 let mut serde_meta_names = BTreeSet::new();
84 let mut schemars_meta_names = BTreeSet::new();
85
86 for meta in get_meta_items(attrs, "schemars", ctxt) {
88 let Some(keyword) = get_meta_ident(&meta) else {
89 continue;
90 };
91
92 if matches!(meta, CustomMeta::Not(..)) {
93 match unset_meta.entry(keyword) {
94 Entry::Occupied(o) => {
95 ctxt.error_spanned_by(
96 meta,
97 format_args!("duplicate schemars attribute item `!{}`", o.key()),
98 );
99 }
100 Entry::Vacant(v) => {
101 v.insert(meta);
102 }
103 }
104 } else if SCHEMARS_KEYWORDS_PARSED_BY_SERDE.contains(&keyword.as_ref()) {
105 schemars_meta_names.insert(keyword);
106 effective_serde_meta.push(meta);
107 }
108 }
109
110 for (keyword, meta) in &unset_meta {
111 if schemars_meta_names.contains(keyword) {
112 ctxt.error_spanned_by(
113 meta,
114 format_args!("schemars attribute cannot contain both `{keyword}` and `!{keyword}`"),
115 );
116 }
117 }
118
119 if schemars_meta_names.contains("skip") {
120 schemars_meta_names.insert("skip_serializing".to_string());
121 schemars_meta_names.insert("skip_deserializing".to_string());
122 }
123
124 for meta in get_meta_items(&serde_attrs, "serde", ctxt) {
126 let Some(keyword) = get_meta_ident(&meta) else {
127 continue;
128 };
129
130 if !schemars_meta_names.contains(&keyword)
131 && !unset_meta.contains_key(&keyword)
132 && SERDE_KEYWORDS.contains(&keyword.as_ref())
133 && keyword != "bound"
134 {
135 effective_serde_meta.push(meta);
136 }
137
138 serde_meta_names.insert(keyword);
139 }
140
141 for (keyword, meta) in &unset_meta {
142 if !serde_meta_names.contains(keyword) {
143 ctxt.error_spanned_by(
144 meta,
145 format_args!(
146 "useless `!{keyword}` - no serde attribute containing `{keyword}` is present"
147 ),
148 );
149 }
150 }
151
152 if !effective_serde_meta.is_empty() {
153 let new_serde_attr = quote! {
154 #[serde(#(#effective_serde_meta),*)]
155 };
156
157 let parser = Attribute::parse_outer;
158 match parser.parse2(new_serde_attr) {
159 Ok(ref mut parsed) => attrs.append(parsed),
160 Err(e) => ctxt.error_spanned_by(to_tokens(attrs), e),
161 }
162 }
163}
164
165fn to_tokens(attrs: &[Attribute]) -> impl ToTokens {
166 let mut tokens = proc_macro2::TokenStream::new();
167 for attr in attrs {
168 attr.to_tokens(&mut tokens);
169 }
170 tokens
171}
172
173fn get_meta_ident(meta: &CustomMeta) -> Option<String> {
174 meta.path().get_ident().map(std::string::ToString::to_string)
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use pretty_assertions::assert_eq;
181 use syn::DeriveInput;
182
183 #[test]
184 fn test_process_serde_attrs() {
185 let mut input: DeriveInput = parse_quote! {
186 #[serde(rename(serialize = "ser_name"), rename_all = "camelCase", from = "T")]
187 #[serde(default, unknown_word)]
188 #[schemars(rename = "overriden", another_unknown_word, !from)]
189 #[misc]
190 struct MyStruct {
191 #[serde(skip_serializing_if = "some_fn", bound = "removed")]
193 field1: i32,
194 #[serde(serialize_with = "se", deserialize_with = "de")]
195 #[schemars(with = "with", bound = "bound")]
196 field2: i32,
197 #[schemars(skip)]
198 #[serde(skip_serializing)]
199 field3: i32,
200 }
201 };
202 let expected: DeriveInput = parse_quote! {
203 #[schemars(rename = "overriden", another_unknown_word, !from)]
204 #[misc]
205 #[serde(rename = "overriden", rename_all = "camelCase", default)]
206 struct MyStruct {
207 #[doc = r" blah blah blah"]
208 #[serde(skip_serializing_if = "some_fn")]
209 field1: i32,
210 #[schemars(with = "with", bound = "bound")]
211 #[serde(bound = "bound", serialize_with = "se")]
212 field2: i32,
213 #[schemars(skip)]
214 #[serde(skip)]
215 field3: i32,
216 }
217 };
218
219 if let Err(e) = process_serde_attrs(&mut input) {
220 panic!("process_serde_attrs returned error: {e}")
221 }
222
223 assert_eq!(input, expected);
224 }
225}