Skip to main content

xso_proc/
lib.rs

1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#![forbid(unsafe_code)]
8#![warn(missing_docs)]
9#![allow(rustdoc::private_intra_doc_links)]
10#![cfg_attr(docsrs, feature(doc_cfg))]
11#![cfg_attr(docsrs, doc(auto_cfg))]
12/*!
13# Macros for parsing XML into Rust structs, and vice versa
14
15**If you are a user of `xso_proc` or `xso`, please
16return to `xso` for more information**. The documentation of
17`xso_proc` is geared toward developers of `…_macros` and `…_core`.
18
19**You have been warned.**
20*/
21
22// Wondering about RawTokenStream vs. TokenStream?
23// syn mostly works with proc_macro2, while the proc macros themselves use
24// proc_macro.
25use proc_macro::TokenStream as RawTokenStream;
26use proc_macro2::{Span, TokenStream};
27use quote::quote;
28use syn::*;
29
30mod common;
31mod compound;
32mod enums;
33mod error_message;
34mod field;
35mod meta;
36mod scope;
37mod state;
38mod structs;
39mod types;
40
41use common::{AsXmlParts, FromXmlParts, GenericsInfo, ItemDef};
42
43/// Convert an [`syn::Item`] into the parts relevant for us.
44///
45/// If the item is of an unsupported variant, an appropriate error is
46/// returned.
47fn parse_struct(item: Item) -> Result<(Visibility, Ident, Box<dyn ItemDef>, GenericsInfo)> {
48    match item {
49        Item::Struct(item) => {
50            let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
51            let def = structs::StructDef::new(&item.ident, meta, &item.fields)?;
52            let generics = GenericsInfo::bake(item.generics);
53            Ok((item.vis, item.ident, Box::new(def), generics))
54        }
55        Item::Enum(item) => {
56            let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
57            let def = enums::EnumDef::new(&item.ident, meta, &item.variants)?;
58            let generics = GenericsInfo::bake(item.generics);
59            Ok((item.vis, item.ident, Box::new(def), generics))
60        }
61        other => Err(Error::new_spanned(other, "cannot derive on this item")),
62    }
63}
64
65/// Generate a `xso::FromXml` implementation for the given item, or fail with
66/// a proper compiler error.
67fn from_xml_impl(input: Item) -> Result<TokenStream> {
68    let (vis, ident, def, mut generics) = parse_struct(input)?;
69
70    let name_ident = Ident::new("name", Span::call_site());
71    let attrs_ident = Ident::new("attrs", Span::call_site());
72
73    let FromXmlParts {
74        defs,
75        from_events_body,
76        builder_ty,
77        name_matcher,
78    } = def.make_from_events_builder(&vis, &mut generics, &name_ident, &attrs_ident)?;
79
80    let GenericsInfo {
81        decl: generics_decl,
82        ref_: generics_ref,
83        where_clause,
84        ..
85    } = generics;
86
87    #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
88    let mut result = quote! {
89        #defs
90
91        impl #generics_decl ::xso::FromXml for #ident #generics_ref #where_clause {
92            type Builder = #builder_ty;
93
94            fn from_events(
95                name: ::xso::exports::rxml::QName,
96                attrs: ::xso::exports::rxml::AttrMap,
97                ctx: &::xso::Context<'_>,
98            ) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
99                #from_events_body
100            }
101
102            fn xml_name_matcher() -> ::xso::fromxml::XmlNameMatcher<'static> {
103                #name_matcher
104            }
105        }
106    };
107
108    #[cfg(feature = "minidom")]
109    result.extend(quote! {
110        impl #generics_decl ::core::convert::TryFrom<::xso::exports::minidom::Element> for #ident #generics_ref #where_clause {
111            type Error = ::xso::error::FromElementError;
112
113            fn try_from(other: ::xso::exports::minidom::Element) -> ::core::result::Result<Self, ::xso::error::FromElementError> {
114                ::xso::try_from_element(other)
115            }
116        }
117    });
118
119    if def.debug() {
120        println!("{}", result);
121    }
122
123    Ok(result)
124}
125
126/// Macro to derive a `xso::FromXml` implementation on a type.
127///
128/// The user-facing documentation for this macro lives in the `xso` crate.
129#[proc_macro_derive(FromXml, attributes(xml))]
130pub fn from_xml(input: RawTokenStream) -> RawTokenStream {
131    // Shim wrapper around `from_xml_impl` which converts any errors into
132    // actual compiler errors within the resulting token stream.
133    let item = syn::parse_macro_input!(input as Item);
134    match from_xml_impl(item) {
135        Ok(v) => v.into(),
136        Err(e) => e.into_compile_error().into(),
137    }
138}
139
140/// Generate a `xso::AsXml` implementation for the given item, or fail with
141/// a proper compiler error.
142fn as_xml_impl(input: Item) -> Result<TokenStream> {
143    let (vis, ident, def, mut generics) = parse_struct(input)?;
144
145    let AsXmlParts {
146        defs,
147        as_xml_iter_body,
148        item_iter_ty_lifetime,
149        item_iter_ty,
150    } = def.make_as_xml_iter(&vis, &mut generics)?;
151
152    let GenericsInfo {
153        decl: generics_decl,
154        ref_: generics_ref,
155        where_clause,
156        ..
157    } = generics;
158
159    #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
160    let mut result = quote! {
161        #defs
162
163        impl #generics_decl ::xso::AsXml for #ident #generics_ref #where_clause {
164            type ItemIter<#item_iter_ty_lifetime> = #item_iter_ty
165                where Self: #item_iter_ty_lifetime;
166
167            fn as_xml_iter(&self) -> ::core::result::Result<Self::ItemIter<'_>, ::xso::error::Error> {
168                #as_xml_iter_body
169            }
170        }
171    };
172
173    #[cfg(all(feature = "minidom", feature = "panicking-into-impl"))]
174    result.extend(quote! {
175        impl #generics_decl ::core::convert::From<#ident #generics_ref> for ::xso::exports::minidom::Element #where_clause {
176            fn from(other: #ident #generics_ref) -> Self {
177                ::xso::transform(&other).expect("seamless conversion into minidom::Element")
178            }
179        }
180
181        impl #generics_decl ::core::convert::From<&#ident #generics_ref> for ::xso::exports::minidom::Element #where_clause {
182            fn from(other: &#ident #generics_ref) -> Self {
183                ::xso::transform(other).expect("seamless conversion into minidom::Element")
184            }
185        }
186    });
187
188    #[cfg(all(feature = "minidom", not(feature = "panicking-into-impl")))]
189    result.extend(quote! {
190        impl #generics_decl ::core::convert::TryFrom<#ident #generics_ref> for ::xso::exports::minidom::Element #where_clause {
191            type Error = ::xso::error::Error;
192
193            fn try_from(other: #ident #generics_ref) -> ::core::result::Result<Self, Self::Error> {
194                ::xso::transform(&other)
195            }
196        }
197        impl #generics_decl ::core::convert::TryFrom<&#ident #generics_ref> for ::xso::exports::minidom::Element #where_clause {
198            type Error = ::xso::error::Error;
199
200            fn try_from(other: &#ident #generics_ref) -> ::core::result::Result<Self, Self::Error> {
201                ::xso::transform(other)
202            }
203        }
204    });
205
206    if def.debug() {
207        println!("{}", result);
208    }
209
210    Ok(result)
211}
212
213/// Macro to derive a `xso::AsXml` implementation on a type.
214///
215/// The user-facing documentation for this macro lives in the `xso` crate.
216#[proc_macro_derive(AsXml, attributes(xml))]
217pub fn as_xml(input: RawTokenStream) -> RawTokenStream {
218    // Shim wrapper around `as_xml_impl` which converts any errors into
219    // actual compiler errors within the resulting token stream.
220    let item = syn::parse_macro_input!(input as Item);
221    match as_xml_impl(item) {
222        Ok(v) => v.into(),
223        Err(e) => e.into_compile_error().into(),
224    }
225}