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