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