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