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