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