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 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 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}