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}