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, 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>)> {
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            Ok((item.vis, item.ident, Box::new(def)))
53        }
54        Item::Enum(item) => {
55            let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
56            let def = enums::EnumDef::new(&item.ident, meta, &item.variants)?;
57            Ok((item.vis, item.ident, Box::new(def)))
58        }
59        other => Err(Error::new_spanned(other, "cannot derive on this item")),
60    }
61}
62
63/// Generate a `xso::FromXml` implementation for the given item, or fail with
64/// a proper compiler error.
65fn from_xml_impl(input: Item) -> Result<TokenStream> {
66    let (vis, ident, def) = parse_struct(input)?;
67
68    let name_ident = Ident::new("name", Span::call_site());
69    let attrs_ident = Ident::new("attrs", Span::call_site());
70
71    let FromXmlParts {
72        defs,
73        from_events_body,
74        builder_ty_ident,
75        name_matcher,
76    } = def.make_from_events_builder(&vis, &name_ident, &attrs_ident)?;
77
78    #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
79    let mut result = quote! {
80        #defs
81
82        impl ::xso::FromXml for #ident {
83            type Builder = #builder_ty_ident;
84
85            fn from_events(
86                name: ::xso::exports::rxml::QName,
87                attrs: ::xso::exports::rxml::AttrMap,
88                ctx: &::xso::Context<'_>,
89            ) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
90                #from_events_body
91            }
92
93            fn xml_name_matcher() -> ::xso::fromxml::XmlNameMatcher<'static> {
94                #name_matcher
95            }
96        }
97    };
98
99    #[cfg(feature = "minidom")]
100    result.extend(quote! {
101        impl ::core::convert::TryFrom<::xso::exports::minidom::Element> for #ident {
102            type Error = ::xso::error::FromElementError;
103
104            fn try_from(other: ::xso::exports::minidom::Element) -> ::core::result::Result<Self, ::xso::error::FromElementError> {
105                ::xso::try_from_element(other)
106            }
107        }
108    });
109
110    if def.debug() {
111        println!("{}", result);
112    }
113
114    Ok(result)
115}
116
117/// Macro to derive a `xso::FromXml` implementation on a type.
118///
119/// The user-facing documentation for this macro lives in the `xso` crate.
120#[proc_macro_derive(FromXml, attributes(xml))]
121pub fn from_xml(input: RawTokenStream) -> RawTokenStream {
122    // Shim wrapper around `from_xml_impl` which converts any errors into
123    // actual compiler errors within the resulting token stream.
124    let item = syn::parse_macro_input!(input as Item);
125    match from_xml_impl(item) {
126        Ok(v) => v.into(),
127        Err(e) => e.into_compile_error().into(),
128    }
129}
130
131/// Generate a `xso::AsXml` implementation for the given item, or fail with
132/// a proper compiler error.
133fn as_xml_impl(input: Item) -> Result<TokenStream> {
134    let (vis, ident, def) = parse_struct(input)?;
135
136    let AsXmlParts {
137        defs,
138        as_xml_iter_body,
139        item_iter_ty_lifetime,
140        item_iter_ty,
141    } = def.make_as_xml_iter(&vis)?;
142
143    #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
144    let mut result = quote! {
145        #defs
146
147        impl ::xso::AsXml for #ident {
148            type ItemIter<#item_iter_ty_lifetime> = #item_iter_ty;
149
150            fn as_xml_iter(&self) -> ::core::result::Result<Self::ItemIter<'_>, ::xso::error::Error> {
151                #as_xml_iter_body
152            }
153        }
154    };
155
156    #[cfg(all(feature = "minidom", feature = "panicking-into-impl"))]
157    result.extend(quote! {
158        impl ::core::convert::From<#ident> for ::xso::exports::minidom::Element {
159            fn from(other: #ident) -> Self {
160                ::xso::transform(&other).expect("seamless conversion into minidom::Element")
161            }
162        }
163
164        impl ::core::convert::From<&#ident> for ::xso::exports::minidom::Element {
165            fn from(other: &#ident) -> Self {
166                ::xso::transform(other).expect("seamless conversion into minidom::Element")
167            }
168        }
169    });
170
171    #[cfg(all(feature = "minidom", not(feature = "panicking-into-impl")))]
172    result.extend(quote! {
173        impl ::core::convert::TryFrom<#ident> for ::xso::exports::minidom::Element {
174            type Error = ::xso::error::Error;
175
176            fn try_from(other: #ident) -> ::core::result::Result<Self, Self::Error> {
177                ::xso::transform(&other)
178            }
179        }
180        impl ::core::convert::TryFrom<&#ident> for ::xso::exports::minidom::Element {
181            type Error = ::xso::error::Error;
182
183            fn try_from(other: &#ident) -> ::core::result::Result<Self, Self::Error> {
184                ::xso::transform(other)
185            }
186        }
187    });
188
189    if def.debug() {
190        println!("{}", result);
191    }
192
193    Ok(result)
194}
195
196/// Macro to derive a `xso::AsXml` implementation on a type.
197///
198/// The user-facing documentation for this macro lives in the `xso` crate.
199#[proc_macro_derive(AsXml, attributes(xml))]
200pub fn as_xml(input: RawTokenStream) -> RawTokenStream {
201    // Shim wrapper around `as_xml_impl` which converts any errors into
202    // actual compiler errors within the resulting token stream.
203    let item = syn::parse_macro_input!(input as Item);
204    match as_xml_impl(item) {
205        Ok(v) => v.into(),
206        Err(e) => e.into_compile_error().into(),
207    }
208}