instant_xml_macros/
lib.rs

1extern crate proc_macro;
2
3use std::collections::BTreeSet;
4use std::mem;
5
6use proc_macro2::{Literal, Span, TokenStream};
7use quote::{quote, ToTokens};
8use syn::spanned::Spanned;
9use syn::{parse_macro_input, DeriveInput, Generics};
10
11mod case;
12use case::RenameRule;
13mod de;
14mod meta;
15use meta::{meta_items, MetaItem, Namespace, NamespaceMeta};
16mod ser;
17
18#[proc_macro_derive(ToXml, attributes(xml))]
19pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
20    let ast = parse_macro_input!(input as syn::DeriveInput);
21    ser::to_xml(&ast).into()
22}
23
24#[proc_macro_derive(FromXml, attributes(xml))]
25pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
26    let ast = parse_macro_input!(input as syn::DeriveInput);
27    proc_macro::TokenStream::from(de::from_xml(&ast))
28}
29
30struct ContainerMeta<'input> {
31    input: &'input DeriveInput,
32    ns: NamespaceMeta,
33    rename: Option<Literal>,
34    rename_all: RenameRule,
35    mode: Option<Mode>,
36}
37
38impl<'input> ContainerMeta<'input> {
39    fn from_derive(input: &'input syn::DeriveInput) -> Result<Self, syn::Error> {
40        let mut ns = NamespaceMeta::default();
41        let mut rename = Default::default();
42        let mut rename_all = Default::default();
43        let mut mode = None;
44
45        for (item, span) in meta_items(&input.attrs) {
46            match item {
47                MetaItem::Ns(namespace) => ns = namespace,
48                MetaItem::Rename(lit) => rename = Some(lit),
49                MetaItem::RenameAll(lit) => {
50                    rename_all = match RenameRule::from_str(&lit.to_string()) {
51                        Ok(rule) => rule,
52                        Err(err) => return Err(syn::Error::new(span, err)),
53                    };
54                }
55                MetaItem::Mode(new) => match mode {
56                    None => mode = Some(new),
57                    Some(_) => return Err(syn::Error::new(span, "cannot have two modes")),
58                },
59                _ => {
60                    return Err(syn::Error::new(
61                        span,
62                        "invalid field in container xml attribute",
63                    ))
64                }
65            }
66        }
67
68        Ok(Self {
69            input,
70            ns,
71            rename,
72            rename_all,
73            mode,
74        })
75    }
76
77    fn xml_generics(&self, borrowed: BTreeSet<syn::Lifetime>) -> Generics {
78        let mut xml_generics = self.input.generics.clone();
79        let mut xml = syn::LifetimeParam::new(syn::Lifetime::new("'xml", Span::call_site()));
80        xml.bounds.extend(borrowed);
81        xml_generics.params.push(xml.into());
82
83        for param in xml_generics.type_params_mut() {
84            param
85                .bounds
86                .push(syn::parse_str("::instant_xml::FromXml<'xml>").unwrap());
87        }
88
89        xml_generics
90    }
91
92    fn tag(&self) -> TokenStream {
93        match &self.rename {
94            Some(name) => quote!(#name),
95            None => self.input.ident.to_string().into_token_stream(),
96        }
97    }
98
99    fn default_namespace(&self) -> TokenStream {
100        match &self.ns.uri {
101            Some(ns) => quote!(#ns),
102            None => quote!(""),
103        }
104    }
105}
106
107#[derive(Debug, Default)]
108struct FieldMeta {
109    attribute: bool,
110    borrow: bool,
111    direct: bool,
112    ns: NamespaceMeta,
113    tag: TokenStream,
114    serialize_with: Option<Literal>,
115    deserialize_with: Option<Literal>,
116}
117
118impl FieldMeta {
119    fn from_field(input: &syn::Field, container: &ContainerMeta) -> Result<FieldMeta, syn::Error> {
120        let field_name = input.ident.as_ref().unwrap();
121        let mut meta = FieldMeta {
122            tag: container
123                .rename_all
124                .apply_to_field(field_name)
125                .into_token_stream(),
126            ..Default::default()
127        };
128
129        for (item, span) in meta_items(&input.attrs) {
130            match item {
131                MetaItem::Attribute => meta.attribute = true,
132                MetaItem::Borrow => meta.borrow = true,
133                MetaItem::Direct => meta.direct = true,
134                MetaItem::Ns(ns) => meta.ns = ns,
135                MetaItem::Rename(lit) => meta.tag = quote!(#lit),
136                MetaItem::SerializeWith(lit) => meta.serialize_with = Some(lit),
137                MetaItem::DeserializeWith(lit) => meta.deserialize_with = Some(lit),
138                MetaItem::RenameAll(_) => {
139                    return Err(syn::Error::new(
140                        span,
141                        "attribute 'rename_all' invalid in field xml attribute",
142                    ))
143                }
144                MetaItem::Mode(_) => {
145                    return Err(syn::Error::new(span, "invalid attribute for struct field"));
146                }
147            }
148        }
149
150        Ok(meta)
151    }
152}
153
154#[derive(Debug, Default)]
155struct VariantMeta {
156    serialize_as: TokenStream,
157}
158
159impl VariantMeta {
160    fn from_variant(
161        input: &syn::Variant,
162        container: &ContainerMeta,
163    ) -> Result<VariantMeta, syn::Error> {
164        if !input.fields.is_empty() {
165            return Err(syn::Error::new(
166                input.fields.span(),
167                "only unit enum variants are permitted!",
168            ));
169        }
170
171        let mut rename = None;
172        for (item, span) in meta_items(&input.attrs) {
173            match item {
174                MetaItem::Rename(lit) => rename = Some(lit.to_token_stream()),
175                _ => {
176                    return Err(syn::Error::new(
177                        span,
178                        "only 'rename' attribute is permitted on enum variants",
179                    ))
180                }
181            }
182        }
183
184        let discriminant = match input.discriminant {
185            Some((
186                _,
187                syn::Expr::Lit(syn::ExprLit {
188                    lit: syn::Lit::Str(ref lit),
189                    ..
190                }),
191            )) => Some(lit.to_token_stream()),
192            Some((
193                _,
194                syn::Expr::Lit(syn::ExprLit {
195                    lit: syn::Lit::Int(ref lit),
196                    ..
197                }),
198            )) => Some(lit.base10_digits().to_token_stream()),
199            Some((_, ref value)) => {
200                return Err(syn::Error::new(
201                    value.span(),
202                    "invalid field discriminant value!",
203                ))
204            }
205            None => None,
206        };
207
208        if discriminant.is_some() && rename.is_some() {
209            return Err(syn::Error::new(
210                input.span(),
211                "conflicting `rename` attribute and variant discriminant!",
212            ));
213        }
214
215        let serialize_as = match rename.or(discriminant) {
216            Some(lit) => lit.into_token_stream(),
217            None => container
218                .rename_all
219                .apply_to_variant(&input.ident)
220                .to_token_stream(),
221        };
222
223        Ok(VariantMeta { serialize_as })
224    }
225}
226
227fn discard_lifetimes(
228    ty: &mut syn::Type,
229    borrowed: &mut BTreeSet<syn::Lifetime>,
230    borrow: bool,
231    top: bool,
232) {
233    match ty {
234        syn::Type::Path(ty) => discard_path_lifetimes(ty, borrowed, borrow),
235        syn::Type::Reference(ty) => {
236            if top {
237                // If at the top level, we'll want to borrow from `&'a str` and `&'a [u8]`.
238                match &*ty.elem {
239                    syn::Type::Path(inner) if top && inner.path.is_ident("str") => {
240                        if let Some(lt) = ty.lifetime.take() {
241                            borrowed.insert(lt);
242                        }
243                    }
244                    syn::Type::Slice(inner) if top => match &*inner.elem {
245                        syn::Type::Path(inner) if inner.path.is_ident("u8") => {
246                            borrowed.extend(ty.lifetime.take());
247                        }
248                        _ => {}
249                    },
250                    _ => {}
251                }
252            } else if borrow {
253                // Otherwise, only borrow if the user has requested it.
254                borrowed.extend(ty.lifetime.take());
255            } else {
256                ty.lifetime = None;
257            }
258
259            discard_lifetimes(&mut ty.elem, borrowed, borrow, false);
260        }
261        _ => {}
262    }
263}
264
265fn discard_path_lifetimes(
266    path: &mut syn::TypePath,
267    borrowed: &mut BTreeSet<syn::Lifetime>,
268    borrow: bool,
269) {
270    if let Some(q) = &mut path.qself {
271        discard_lifetimes(&mut q.ty, borrowed, borrow, false);
272    }
273
274    for segment in &mut path.path.segments {
275        match &mut segment.arguments {
276            syn::PathArguments::None => {}
277            syn::PathArguments::AngleBracketed(args) => {
278                args.args.iter_mut().for_each(|arg| match arg {
279                    syn::GenericArgument::Lifetime(lt) => {
280                        let lt = mem::replace(lt, syn::Lifetime::new("'_", Span::call_site()));
281                        if borrow {
282                            borrowed.insert(lt);
283                        }
284                    }
285                    syn::GenericArgument::Type(ty) => {
286                        discard_lifetimes(ty, borrowed, borrow, false)
287                    }
288                    _ => {}
289                })
290            }
291            syn::PathArguments::Parenthesized(args) => args
292                .inputs
293                .iter_mut()
294                .for_each(|ty| discard_lifetimes(ty, borrowed, borrow, false)),
295        }
296    }
297}
298
299#[derive(Clone, Copy, Debug, Eq, PartialEq)]
300enum Mode {
301    Forward,
302    Scalar,
303    Transparent,
304}
305
306#[cfg(test)]
307mod tests {
308    use syn::parse_quote;
309
310    #[test]
311    fn non_unit_enum_variant_unsupported() {
312        dbg!(super::ser::to_xml(&parse_quote! {
313            #[xml(scalar)]
314            pub enum TestEnum {
315                Foo(String),
316                Bar,
317                Baz
318            }
319        })
320        .to_string())
321        .find("compile_error ! { \"only unit enum variants are permitted!\" }")
322        .unwrap();
323    }
324
325    #[test]
326    fn non_scalar_enums_unsupported() {
327        dbg!(super::ser::to_xml(&parse_quote! {
328            #[xml()]
329            pub enum TestEnum {
330                Foo,
331                Bar,
332                Baz
333            }
334        })
335        .to_string())
336        .find("compile_error ! { \"missing mode\" }")
337        .unwrap();
338    }
339
340    #[test]
341    fn scalar_variant_attribute_not_permitted() {
342        dbg!(super::ser::to_xml(&parse_quote! {
343            #[xml(scalar)]
344            pub enum TestEnum {
345                Foo,
346                Bar,
347                #[xml(scalar)]
348                Baz
349            }
350        })
351        .to_string())
352        .find("compile_error ! { \"only 'rename' attribute is permitted on enum variants\" }")
353        .unwrap();
354    }
355
356    #[test]
357    fn scalar_discrimintant_must_be_literal() {
358        assert_eq!(
359            None,
360            dbg!(super::ser::to_xml(&parse_quote! {
361                #[xml(scalar)]
362                pub enum TestEnum {
363                    Foo = 1,
364                    Bar,
365                    Baz
366                }
367            })
368            .to_string())
369            .find("compile_error ! { \"invalid field discriminant value!\" }")
370        );
371
372        dbg!(super::ser::to_xml(&parse_quote! {
373            #[xml(scalar)]
374            pub enum TestEnum {
375                Foo = 1+1,
376                Bar,
377                Baz
378            }
379        })
380        .to_string())
381        .find("compile_error ! { \"invalid field discriminant value!\" }")
382        .unwrap();
383    }
384
385    #[test]
386    fn rename_all_attribute_not_permitted() {
387        dbg!(super::ser::to_xml(&parse_quote! {
388            pub struct TestStruct {
389                #[xml(rename_all = "UPPERCASE")]
390                field_1: String,
391                field_2: u8,
392            }
393        })
394        .to_string())
395        .find("compile_error ! { \"attribute 'rename_all' invalid in field xml attribute\" }")
396        .unwrap();
397
398        dbg!(super::ser::to_xml(&parse_quote! {
399            #[xml(scalar)]
400            pub enum TestEnum {
401                Foo = 1,
402                Bar,
403                #[xml(rename_all = "UPPERCASE")]
404                Baz
405            }
406        })
407        .to_string())
408        .find("compile_error ! { \"only 'rename' attribute is permitted on enum variants\" }")
409        .unwrap();
410    }
411
412    #[test]
413    fn bogus_rename_all_not_permitted() {
414        dbg!(super::ser::to_xml(&parse_quote! {
415            #[xml(rename_all = "forgetaboutit")]
416            pub struct TestStruct {
417                field_1: String,
418                field_2: u8,
419            }
420        })
421        .to_string())
422        .find("compile_error ! {")
423        .unwrap();
424    }
425}