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
10// List of keywords that can appear in #[serde(...)]/#[schemars(...)] attributes which we want
11// serde_derive_internals to parse for us.
12pub(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    // Special case - `bound` is removed from serde attrs, so is only respected when present in
32    // schemars attr.
33    "bound",
34    // Special cases - `with`/`serialize_with` are passed to serde but not copied from schemars
35    // attrs to serde attrs. This is because we want to preserve any serde attribute's
36    // `serialize_with` value to determine whether the field's default value should be
37    // serialized. We also check the `with` value on schemars/serde attrs e.g. to support deriving
38    // JsonSchema on remote types, but we parse that ourselves rather than using
39    // serde_derive_internals.
40    "serialize_with",
41    "with",
42];
43
44pub(crate) static SCHEMARS_KEYWORDS_PARSED_BY_SERDE: &[&str] =
45    // exclude "serialize_with" and "with"
46    SERDE_KEYWORDS.split_at(SERDE_KEYWORDS.len() - 2).0;
47
48// If a struct/variant/field has any #[schemars] attributes, then create copies of them
49// as #[serde] attributes so that serde_derive_internals will parse them for us.
50pub 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    // Remove #[serde(...)] attributes (some may be re-added later)
77    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    // Copy appropriate #[schemars(...)] attributes to #[serde(...)] attributes
87    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    // Re-add #[serde(...)] attributes that weren't overridden by #[schemars(...)] attributes
125    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                /// blah blah blah
192                #[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}