xmlity_derive/
lib.rs

1use quote::quote;
2use syn::{parse_macro_input, DeriveInput};
3
4mod de;
5mod options;
6use options::{
7    XmlityFieldAttributeDeriveOpts, XmlityFieldElementDeriveOpts, XmlityFieldGroupDeriveOpts,
8    XmlityRootAttributeDeriveOpts, XmlityRootElementDeriveOpts, XmlityRootGroupDeriveOpts,
9    XmlityRootValueDeriveOpts,
10};
11mod ser;
12mod utils;
13
14/// Derives the [`xmlity::Serialize`] trait for a type.
15#[proc_macro_derive(Serialize, attributes(xelement, xattribute, xgroup, xvalue))]
16pub fn derive_serialize_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
17    let ast = parse_macro_input!(item as DeriveInput);
18    let element_opts = XmlityRootElementDeriveOpts::parse(&ast).expect("Wrong options");
19    let value_opts = XmlityRootValueDeriveOpts::parse(&ast).expect("Wrong options");
20
21    ser::derive_element_serialize_fn(ast, element_opts.as_ref(), value_opts.as_ref())
22        .expect("Wrong options")
23        .into()
24}
25
26/// Derives the [`xmlity::SerializeAttribute`] trait for a type.
27#[proc_macro_derive(SerializeAttribute, attributes(xattribute))]
28pub fn derive_serialize_attribute_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
29    let ast = parse_macro_input!(item as DeriveInput);
30    let opts = XmlityRootAttributeDeriveOpts::parse(&ast)
31        .expect("Wrong options")
32        .unwrap_or_default();
33
34    ser::derive_attribute_serialize_fn(ast, opts).into()
35}
36
37/// Derives the [`xmlity::Deserialize`] trait for a type.
38#[proc_macro_derive(Deserialize, attributes(xelement, xattribute, xgroup, xvalue))]
39pub fn derive_deserialize_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
40    let ast = parse_macro_input!(item as DeriveInput);
41    let element_opts = XmlityRootElementDeriveOpts::parse(&ast).expect("Wrong options");
42    let attribute_opts = XmlityRootAttributeDeriveOpts::parse(&ast).expect("Wrong options");
43    let value_opts = XmlityRootValueDeriveOpts::parse(&ast).expect("Wrong options");
44
45    de::derive_deserialize_fn(ast, element_opts, attribute_opts, value_opts)
46        .expect("Wrong options")
47        .into()
48}
49
50/// Derives the [`xmlity::SerializationGroup`] trait for a type.
51#[proc_macro_derive(SerializationGroup, attributes(xelement, xattribute, xgroup))]
52pub fn derive_serialization_group_attribute_fn(
53    item: proc_macro::TokenStream,
54) -> proc_macro::TokenStream {
55    let ast = parse_macro_input!(item as DeriveInput);
56    let opts = XmlityRootGroupDeriveOpts::parse(&ast)
57        .expect("Wrong options")
58        .unwrap_or_default();
59
60    ser::derive_group_serialize_fn(ast, opts)
61        .expect("Wrong options")
62        .into()
63}
64
65/// Derives the [`xmlity::DeserializationGroup`] trait for a type.
66#[proc_macro_derive(DeserializationGroup, attributes(xelement, xattribute, xgroup))]
67pub fn derive_deserialization_group_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
68    let ast = parse_macro_input!(item as DeriveInput);
69    let opts = XmlityRootGroupDeriveOpts::parse(&ast)
70        .expect("Wrong options")
71        .unwrap_or_default();
72
73    de::derive_deserialization_group_fn(ast, opts)
74        .expect("Wrong options")
75        .into()
76}
77
78fn simple_compile_error(text: &str) -> proc_macro2::TokenStream {
79    quote! {
80        compile_error!(#text);
81    }
82}
83
84#[derive(Clone)]
85enum XmlityFieldDeriveOpts {
86    Element(XmlityFieldElementDeriveOpts),
87    Attribute(XmlityFieldAttributeDeriveOpts),
88    Group(XmlityFieldGroupDeriveOpts),
89}
90
91#[derive(Clone)]
92enum XmlityFieldAttributeGroupDeriveOpts {
93    Attribute(XmlityFieldAttributeDeriveOpts),
94    Group(XmlityFieldGroupDeriveOpts),
95}
96
97#[derive(Clone)]
98enum XmlityFieldElementGroupDeriveOpts {
99    Element(XmlityFieldElementDeriveOpts),
100    Group(XmlityFieldGroupDeriveOpts),
101}
102
103impl XmlityFieldDeriveOpts {
104    fn from_field(field: &syn::Field) -> Result<Self, darling::Error> {
105        let element = XmlityFieldElementDeriveOpts::from_field(field)?;
106        let attribute = XmlityFieldAttributeDeriveOpts::from_field(field)?;
107        let group = XmlityFieldGroupDeriveOpts::from_field(field)?;
108        Ok(match (element, attribute, group) {
109            (Some(element), None, None) => Self::Element(element),
110            (None, Some(attribute), None) => Self::Attribute(attribute),
111            (None, None, Some(group)) => Self::Group(group),
112            (None, None, None) => Self::Element(XmlityFieldElementDeriveOpts::default()),
113            _ => {
114                return Err(darling::Error::custom(
115                    "Cannot have multiple xmlity field attributes on the same field",
116                ))
117            }
118        })
119    }
120}
121
122#[derive(Clone)]
123enum FieldIdent {
124    Named(syn::Ident),
125    Indexed(syn::Index),
126}
127
128impl quote::ToTokens for FieldIdent {
129    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
130        match self {
131            FieldIdent::Named(ident) => ident.to_tokens(tokens),
132            FieldIdent::Indexed(index) => index.to_tokens(tokens),
133        }
134    }
135}
136
137#[derive(Clone)]
138struct DeserializeBuilderField<BuilderFieldIdent, OptionType> {
139    builder_field_ident: BuilderFieldIdent,
140    // If the field is indexed, this is none.
141    field_ident: FieldIdent,
142    field_type: syn::Type,
143    options: OptionType,
144}
145
146impl<A, T> DeserializeBuilderField<A, T> {
147    pub fn map_options<U, F: FnOnce(T) -> U>(self, f: F) -> DeserializeBuilderField<A, U> {
148        DeserializeBuilderField {
149            builder_field_ident: self.builder_field_ident,
150            field_ident: self.field_ident,
151            field_type: self.field_type,
152            options: f(self.options),
153        }
154    }
155
156    pub fn map_options_opt<U, F: FnOnce(T) -> Option<U>>(
157        self,
158        f: F,
159    ) -> Option<DeserializeBuilderField<A, U>> {
160        f(self.options).map(|options| DeserializeBuilderField {
161            builder_field_ident: self.builder_field_ident,
162            field_ident: self.field_ident,
163            field_type: self.field_type,
164            options,
165        })
166    }
167}
168
169#[derive(Clone)]
170struct SerializeField<OptionType> {
171    // If the field is indexed, this is none.
172    field_ident: FieldIdent,
173    field_type: syn::Type,
174    options: OptionType,
175}
176
177#[allow(dead_code)]
178impl<T> SerializeField<T> {
179    pub fn map_options<U, F: FnOnce(T) -> U>(self, f: F) -> SerializeField<U> {
180        SerializeField {
181            field_ident: self.field_ident,
182            field_type: self.field_type,
183            options: f(self.options),
184        }
185    }
186
187    pub fn map_options_opt<U, F: FnOnce(T) -> Option<U>>(self, f: F) -> Option<SerializeField<U>> {
188        f(self.options).map(|options| SerializeField {
189            field_ident: self.field_ident,
190            field_type: self.field_type,
191            options,
192        })
193    }
194}
195
196struct ExpandedName<'a> {
197    name: &'a str,
198    namespace: Option<&'a str>,
199}
200
201impl<'a> ExpandedName<'a> {
202    fn new(name: &'a str, namespace: Option<&'a str>) -> Self {
203        Self { name, namespace }
204    }
205
206    fn to_expression(Self { name, namespace }: &Self) -> proc_macro2::TokenStream {
207        let xml_namespace = match namespace {
208            Some(xml_namespace) => {
209                quote! { ::core::option::Option::Some(<::xmlity::XmlNamespace as ::core::str::FromStr>::from_str(#xml_namespace).expect("XML namespace in derive macro is invalid. This is a bug in xmlity. Please report it.")) }
210            }
211            None => quote! { ::core::option::Option::None },
212        };
213
214        quote! {
215            ::xmlity::ExpandedName::new(<::xmlity::LocalName as ::core::str::FromStr>::from_str(#name).expect("XML name in derive macro is invalid. This is a bug in xmlity. Please report it."), #xml_namespace)
216        }
217    }
218}
219
220impl quote::ToTokens for ExpandedName<'_> {
221    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
222        tokens.extend(Self::to_expression(self))
223    }
224}