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    } = def.make_from_events_builder(&vis, &name_ident, &attrs_ident)?;
 75
 76    #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
 77    let mut result = quote! {
 78        #defs
 79
 80        impl ::xso::FromXml for #ident {
 81            type Builder = #builder_ty_ident;
 82
 83            fn from_events(
 84                name: ::xso::exports::rxml::QName,
 85                attrs: ::xso::exports::rxml::AttrMap,
 86            ) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
 87                #from_events_body
 88            }
 89        }
 90    };
 91
 92    #[cfg(feature = "minidom")]
 93    result.extend(quote! {
 94        impl ::core::convert::TryFrom<::xso::exports::minidom::Element> for #ident {
 95            type Error = ::xso::error::FromElementError;
 96
 97            fn try_from(other: ::xso::exports::minidom::Element) -> ::core::result::Result<Self, Self::Error> {
 98                ::xso::try_from_element(other)
 99            }
100        }
101    });
102
103    if def.debug() {
104        println!("{}", result);
105    }
106
107    Ok(result)
108}
109
110/// Macro to derive a `xso::FromXml` implementation on a type.
111///
112/// The user-facing documentation for this macro lives in the `xso` crate.
113#[proc_macro_derive(FromXml, attributes(xml))]
114pub fn from_xml(input: RawTokenStream) -> RawTokenStream {
115    // Shim wrapper around `from_xml_impl` which converts any errors into
116    // actual compiler errors within the resulting token stream.
117    let item = syn::parse_macro_input!(input as Item);
118    match from_xml_impl(item) {
119        Ok(v) => v.into(),
120        Err(e) => e.into_compile_error().into(),
121    }
122}
123
124/// Generate a `xso::AsXml` implementation for the given item, or fail with
125/// a proper compiler error.
126fn as_xml_impl(input: Item) -> Result<TokenStream> {
127    let (vis, ident, def) = parse_struct(input)?;
128
129    let AsXmlParts {
130        defs,
131        as_xml_iter_body,
132        item_iter_ty_lifetime,
133        item_iter_ty,
134    } = def.make_as_xml_iter(&vis)?;
135
136    #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
137    let mut result = quote! {
138        #defs
139
140        impl ::xso::AsXml for #ident {
141            type ItemIter<#item_iter_ty_lifetime> = #item_iter_ty;
142
143            fn as_xml_iter(&self) -> ::core::result::Result<Self::ItemIter<'_>, ::xso::error::Error> {
144                #as_xml_iter_body
145            }
146        }
147    };
148
149    #[cfg(all(feature = "minidom", feature = "panicking-into-impl"))]
150    result.extend(quote! {
151        impl ::core::convert::From<#ident> for ::xso::exports::minidom::Element {
152            fn from(other: #ident) -> Self {
153                ::xso::transform(other).expect("seamless conversion into minidom::Element")
154            }
155        }
156    });
157
158    #[cfg(all(feature = "minidom", not(feature = "panicking-into-impl")))]
159    result.extend(quote! {
160        impl ::core::convert::TryFrom<#ident> for ::xso::exports::minidom::Element {
161            type Error = ::xso::error::Error;
162
163            fn try_from(other: #ident) -> ::core::result::Result<Self, Self::Error> {
164                ::xso::transform(other)
165            }
166        }
167    });
168
169    if def.debug() {
170        println!("{}", result);
171    }
172
173    Ok(result)
174}
175
176/// Macro to derive a `xso::AsXml` implementation on a type.
177///
178/// The user-facing documentation for this macro lives in the `xso` crate.
179#[proc_macro_derive(AsXml, attributes(xml))]
180pub fn as_xml(input: RawTokenStream) -> RawTokenStream {
181    // Shim wrapper around `as_xml_impl` which converts any errors into
182    // actual compiler errors within the resulting token stream.
183    let item = syn::parse_macro_input!(input as Item);
184    match as_xml_impl(item) {
185        Ok(v) => v.into(),
186        Err(e) => e.into_compile_error().into(),
187    }
188}