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