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