mysqldump_quick_xml_derive/
lib.rs

1/*!
2Crate `mysqldump-quick-xml-derive` provides a derive macro to convert from mysqldump in xml format to struct using quick-xml.
3
4# Installation
5
6Add following dependency to your `Cargo.toml`:
7
8```toml,ignore
9[dependencies]
10mysqldump-quick-xml = "0.1"
11```
12
13*/
14#![recursion_limit = "256"]
15extern crate proc_macro;
16
17use proc_macro2::TokenStream;
18use quote::quote;
19use syn::{parse_macro_input, Data, DeriveInput, Fields};
20
21#[proc_macro_derive(MysqlDumpQuickXml)]
22pub fn mysqldump_quick_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
23    let input = parse_macro_input!(input as DeriveInput);
24
25    let name = &input.ident;
26
27    let (fields_declare, fields_set, fields_match_and_set_current, fields_set_none) =
28        mysqldump_quick_xml_fields(&input.data);
29
30    let expanded = quote! {
31
32        impl ::mysqldump_quick_xml::MysqlDumpQuickXml for #name {
33            fn from_str(xml: &str) -> Vec<Self> {
34                use ::mysqldump_quick_xml::quick_xml::Reader;
35                use ::mysqldump_quick_xml::quick_xml::events::Event;
36
37                let mut reader = Reader::from_str(xml);
38                reader.trim_text(true);
39
40                let mut buf = Vec::new();
41
42                let mut rows = vec![];
43
44                #fields_declare
45
46                let mut current_field = None;
47
48                loop {
49                    match reader.read_event(&mut buf) {
50                        Ok(Event::Start(ref e)) => match e.name() {
51                            b"field" => {
52                                let field = e
53                                    .attributes()
54                                    .map(|x| x.unwrap())
55                                    .filter(|x| x.key == b"name")
56                                    .next()
57                                    .map(|x| x.value)
58                                    .unwrap();
59                                match field.as_ref() {
60                                    #fields_match_and_set_current
61                                    _ => (),
62                                }
63                            }
64                            _ => (),
65                        },
66                        Ok(Event::End(ref e)) => match e.name() {
67                            b"row" => {
68                                current_field = None;
69                                rows.push(Self {
70                                    #fields_set
71                                });
72                                #fields_set_none
73                            }
74                            b"field" => {
75                                current_field = None;
76                            }
77                            _ => (),
78                        },
79                        Ok(Event::Text(e)) => {
80                            if let Some(field) = &mut current_field {
81                                field.replace(e.unescape_and_decode(&reader).unwrap());
82                            }
83                        }
84                        Ok(Event::Eof) => break,
85                        Err(e) => panic!("panic at {}: {:?}", reader.buffer_position(), e),
86                        _ => (),
87                    }
88
89                    buf.clear();
90                }
91
92                rows
93            }
94        }
95
96    };
97
98    expanded.into()
99}
100
101fn mysqldump_quick_xml_fields(data: &Data) -> (TokenStream, TokenStream, TokenStream, TokenStream) {
102    match *data {
103        Data::Struct(ref data) => match data.fields {
104            Fields::Named(ref fields) => {
105                let fields_declare = fields.named.iter().map(|f| {
106                    let name = &f.ident;
107                    quote! {
108                        let mut #name: Option<String> = None;
109                    }
110                });
111                let fields_set = fields.named.iter().map(|f| {
112                    let name = &f.ident;
113                    quote! {
114                        #name: #name.clone().unwrap_or_default().into(),
115                    }
116                });
117                let fields_match_and_set_current = fields.named.iter().map(|f| {
118                    let name = f.ident.as_ref().unwrap();
119                    let concatenated = format!("{}", name);
120                    let varname = syn::LitByteStr::new(concatenated.as_bytes(), name.span());
121
122                    quote! {
123                        #varname => current_field = Some(&mut #name),
124                    }
125                });
126                let fields_set_null = fields.named.iter().map(|f| {
127                    let name = &f.ident;
128                    quote! {
129                        #name = None;
130                    }
131                });
132
133                (
134                    quote! {
135                        #(#fields_declare)*
136                    },
137                    quote! {
138                        #(#fields_set)*
139                    },
140                    quote! {
141                        #(#fields_match_and_set_current)*
142                    },
143                    quote! {
144                        #(#fields_set_null)*
145                    },
146                )
147            }
148            Fields::Unnamed(_) => unimplemented!(),
149            Fields::Unit => unimplemented!(),
150        },
151        Data::Enum(_) | Data::Union(_) => unimplemented!(),
152    }
153}