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