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//! Handling of structs
8
9use proc_macro2::TokenStream;
10use quote::quote;
11use syn::*;
12
13use crate::compound::Compound;
14use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
15
16/// Parts necessary to construct a `::xso::FromXml` implementation.
17pub(crate) struct FromXmlParts {
18 /// Additional items necessary for the implementation.
19 pub(crate) defs: TokenStream,
20
21 /// The body of the `::xso::FromXml::from_xml` function.
22 pub(crate) from_events_body: TokenStream,
23
24 /// The name of the type which is the `::xso::FromXml::Builder`.
25 pub(crate) builder_ty_ident: Ident,
26}
27
28/// Parts necessary to construct a `::xso::IntoXml` implementation.
29pub(crate) struct IntoXmlParts {
30 /// Additional items necessary for the implementation.
31 pub(crate) defs: TokenStream,
32
33 /// The body of the `::xso::IntoXml::into_event_iter` function.
34 pub(crate) into_event_iter_body: TokenStream,
35
36 /// The name of the type which is the `::xso::IntoXml::EventIter`.
37 pub(crate) event_iter_ty_ident: Ident,
38}
39
40/// Definition of a struct and how to parse it.
41pub(crate) struct StructDef {
42 /// The XML namespace of the element to map the struct to.
43 namespace: NamespaceRef,
44
45 /// The XML name of the element to map the struct to.
46 name: NameRef,
47
48 /// The field(s) of this struct.
49 inner: Compound,
50
51 /// Name of the target type.
52 target_ty_ident: Ident,
53
54 /// Name of the builder type.
55 builder_ty_ident: Ident,
56
57 /// Name of the iterator type.
58 event_iter_ty_ident: Ident,
59}
60
61impl StructDef {
62 /// Create a new struct from its name, meta, and fields.
63 pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
64 let Some(namespace) = meta.namespace else {
65 return Err(Error::new(meta.span, "`namespace` is required on structs"));
66 };
67
68 let Some(name) = meta.name else {
69 return Err(Error::new(meta.span, "`name` is required on structs"));
70 };
71
72 Ok(Self {
73 namespace,
74 name,
75 inner: Compound::from_fields(fields)?,
76 target_ty_ident: ident.clone(),
77 builder_ty_ident: quote::format_ident!("{}FromXmlBuilder", ident),
78 event_iter_ty_ident: quote::format_ident!("{}IntoXmlIterator", ident),
79 })
80 }
81
82 pub(crate) fn make_from_events_builder(
83 &self,
84 vis: &Visibility,
85 name_ident: &Ident,
86 attrs_ident: &Ident,
87 ) -> Result<FromXmlParts> {
88 let xml_namespace = &self.namespace;
89 let xml_name = &self.name;
90
91 let target_ty_ident = &self.target_ty_ident;
92 let builder_ty_ident = &self.builder_ty_ident;
93 let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
94
95 let defs = self
96 .inner
97 .make_from_events_statemachine(
98 &state_ty_ident,
99 &Path::from(target_ty_ident.clone()).into(),
100 "Struct",
101 )?
102 .with_augmented_init(|init| {
103 quote! {
104 if name.0 != #xml_namespace || name.1 != #xml_name {
105 ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
106 name,
107 attrs,
108 })
109 } else {
110 #init
111 }
112 }
113 })
114 .compile()
115 .render(
116 vis,
117 &builder_ty_ident,
118 &state_ty_ident,
119 &TypePath {
120 qself: None,
121 path: target_ty_ident.clone().into(),
122 }
123 .into(),
124 )?;
125
126 Ok(FromXmlParts {
127 defs,
128 from_events_body: quote! {
129 #builder_ty_ident::new(#name_ident, #attrs_ident)
130 },
131 builder_ty_ident: builder_ty_ident.clone(),
132 })
133 }
134
135 pub(crate) fn make_into_event_iter(&self, vis: &Visibility) -> Result<IntoXmlParts> {
136 let xml_namespace = &self.namespace;
137 let xml_name = &self.name;
138
139 let target_ty_ident = &self.target_ty_ident;
140 let event_iter_ty_ident = &self.event_iter_ty_ident;
141 let state_ty_ident = quote::format_ident!("{}State", event_iter_ty_ident);
142
143 let defs = self
144 .inner
145 .make_into_event_iter_statemachine(&target_ty_ident.clone().into(), "Struct")?
146 .with_augmented_init(|init| {
147 quote! {
148 let name = (
149 ::xso::exports::rxml::Namespace::from(#xml_namespace),
150 #xml_name.into(),
151 );
152 #init
153 }
154 })
155 .compile()
156 .render(
157 vis,
158 &TypePath {
159 qself: None,
160 path: target_ty_ident.clone().into(),
161 }
162 .into(),
163 &state_ty_ident,
164 &event_iter_ty_ident,
165 )?;
166
167 Ok(IntoXmlParts {
168 defs,
169 into_event_iter_body: quote! {
170 #event_iter_ty_ident::new(self)
171 },
172 event_iter_ty_ident: event_iter_ty_ident.clone(),
173 })
174 }
175}