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}