xso_proc/field/mod.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//! Compound (struct or enum variant) field types
8
9use proc_macro2::{Span, TokenStream};
10use syn::{spanned::Spanned, *};
11
12use rxml_validation::NcName;
13
14use crate::compound::Compound;
15use crate::error_message::ParentRef;
16use crate::meta::{AmountConstraint, Flag, NameRef, NamespaceRef, QNameRef, XmlFieldMeta};
17use crate::scope::{AsItemsScope, FromEventsScope};
18
19mod attribute;
20mod child;
21#[cfg(feature = "minidom")]
22mod element;
23mod flag;
24mod text;
25
26use self::attribute::AttributeField;
27use self::child::{ChildField, ExtractDef};
28#[cfg(feature = "minidom")]
29use self::element::ElementField;
30use self::flag::FlagField;
31use self::text::TextField;
32
33/// Code slices necessary for declaring and initializing a temporary variable
34/// for parsing purposes.
35pub(crate) struct FieldTempInit {
36 /// The type of the temporary variable.
37 pub(crate) ty: Type,
38
39 /// The initializer for the temporary variable.
40 pub(crate) init: TokenStream,
41}
42
43/// Configure how a nested field builder selects child elements.
44pub(crate) enum NestedMatcher {
45 /// Matches a specific child element fallabily.
46 Selective(
47 /// Expression which evaluates to `Result<T, FromEventsError>`,
48 /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`.
49 ///
50 /// If the `name` and `attrs` allow starting to parse the child
51 /// element as a value of this field, `Ok(_)` must be returned. If
52 /// the `name` and `attrs` are those of an element which *could*
53 /// be a value of this field, but they have invalid contents,
54 /// `Err(FromEventsError::Invalid(_))` must be returned. Depending
55 /// on the field kind, it may also be acceptable to return the
56 /// `Invalid` variant if the data is valid, but no further child
57 /// element can be accepted into the value.
58 ///
59 /// Otherwise, the `name` and `attrs` must be returned *unchanged* in
60 /// a `FromEventsError::Mismatch { .. }` variant. In that case, the
61 /// implementation in `Compound` will let the next field attempt to
62 /// parse the child element.
63 ///
64 /// `T` must be the type specified in the
65 /// [`FieldBuilderPart::Nested::builder`] field.
66 TokenStream,
67 ),
68
69 #[cfg_attr(not(feature = "minidom"), allow(dead_code))]
70 /// Matches any child element not matched by another matcher.
71 ///
72 /// Only a single field may use this variant, otherwise an error is
73 /// raised during execution of the proc macro.
74 Fallback(
75 /// Expression which evaluates to `T` (or `return`s an error),
76 /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`.
77 ///
78 /// Unlike the [`Selective`][`Self::Selective`] variant, this
79 /// expression must always evaluate to an instance of `T`. If that is
80 /// not possible, the expression must diverge, most commonly using
81 /// `return` with a `Err::<_, xso::error::Error>(_)`.
82 ///
83 /// `T` must be the type specified in the
84 /// [`FieldBuilderPart::Nested::builder`] field.
85 TokenStream,
86 ),
87}
88
89/// Describe how a struct or enum variant's member is parsed from XML data.
90///
91/// This struct is returned from [`FieldDef::make_builder_part`] and
92/// contains code snippets and instructions for
93/// [`Compound::make_from_events_statemachine`][`crate::compound::Compound::make_from_events_statemachine`]
94/// to parse the field's data from XML.
95pub(crate) enum FieldBuilderPart {
96 /// Parse a field from the item's element's start event.
97 Init {
98 /// Expression and type which extracts the field's data from the
99 /// element's start event.
100 value: FieldTempInit,
101 },
102
103 /// Parse a field from text events.
104 Text {
105 /// Expression and type which initializes a buffer to use during
106 /// parsing.
107 value: FieldTempInit,
108
109 /// Statement which takes text and accumulates it into the temporary
110 /// value declared via `value`.
111 collect: TokenStream,
112
113 /// Expression which evaluates to the field's type, consuming the
114 /// temporary value.
115 finalize: TokenStream,
116 },
117
118 /// Parse a field from child element events.
119 Nested {
120 /// Additional definition items which need to be inserted at module
121 /// level for the rest of the implementation to work.
122 extra_defs: TokenStream,
123
124 /// Expression and type which initializes a buffer to use during
125 /// parsing.
126 value: FieldTempInit,
127
128 /// Configure child matching behaviour for this field. See
129 /// [`NestedMatcher`] for options.
130 matcher: NestedMatcher,
131
132 /// Type implementing `xso::FromEventsBuilder` which parses the child
133 /// element.
134 ///
135 /// This type is returned by the expressions in
136 /// [`matcher`][`Self::Nested::matcher`].
137 builder: Type,
138
139 /// Expression which consumes the value stored in the identifier
140 /// [`crate::common::FromEventsScope::substate_result`][`FromEventsScope::substate_result`]
141 /// and somehow collects it into the field declared with
142 /// [`value`][`Self::Nested::value`].
143 collect: TokenStream,
144
145 /// Expression which consumes the data from the field declared with
146 /// [`value`][`Self::Nested::value`] and converts it into the field's
147 /// type.
148 finalize: TokenStream,
149 },
150}
151
152/// Describe how a struct or enum variant's member is converted to XML data.
153///
154/// This struct is returned from [`FieldDef::make_iterator_part`] and
155/// contains code snippets and instructions for
156/// [`Compound::make_into_events_statemachine`][`crate::compound::Compound::make_into_events_statemachine`]
157/// to convert the field's data into XML.
158pub(crate) enum FieldIteratorPart {
159 /// The field is emitted as part of StartElement.
160 Header {
161 /// An expression which consumes the field's value and returns a
162 /// `Item`.
163 generator: TokenStream,
164 },
165
166 /// The field is emitted as text item.
167 Text {
168 /// An expression which consumes the field's value and returns a
169 /// String, which is then emitted as text data.
170 generator: TokenStream,
171 },
172
173 /// The field is emitted as series of items which form a child element.
174 Content {
175 /// Additional definition items which need to be inserted at module
176 /// level for the rest of the implementation to work.
177 extra_defs: TokenStream,
178
179 /// Expression and type which initializes the nested iterator.
180 ///
181 /// Note that this is evaluated at construction time of the iterator.
182 /// Fields of this variant do not get access to their original data,
183 /// unless they carry it in the contents of this `value`.
184 value: FieldTempInit,
185
186 /// An expression which uses the value (mutably) and evaluates to
187 /// a Result<Option<Item>, Error>. Once the state returns None, the
188 /// processing will advance to the next state.
189 generator: TokenStream,
190 },
191}
192
193trait Field {
194 /// Construct the builder pieces for this field.
195 ///
196 /// `container_name` must be a reference to the compound's type, so that
197 /// it can be used for error messages.
198 ///
199 /// `member` and `ty` refer to the field itself.
200 fn make_builder_part(
201 &self,
202 scope: &FromEventsScope,
203 container_name: &ParentRef,
204 member: &Member,
205 ty: &Type,
206 ) -> Result<FieldBuilderPart>;
207
208 /// Construct the iterator pieces for this field.
209 ///
210 /// `bound_name` must be the name to which the field's value is bound in
211 /// the iterator code.
212 ///
213 /// `member` and `ty` refer to the field itself.
214 ///
215 /// `bound_name` is the name under which the field's value is accessible
216 /// in the various parts of the code.
217 fn make_iterator_part(
218 &self,
219 scope: &AsItemsScope,
220 container_name: &ParentRef,
221 bound_name: &Ident,
222 member: &Member,
223 ty: &Type,
224 ) -> Result<FieldIteratorPart>;
225
226 /// Return true if and only if this field captures text content.
227 fn captures_text(&self) -> bool {
228 false
229 }
230}
231
232fn default_name(span: Span, name: Option<NameRef>, field_ident: Option<&Ident>) -> Result<NameRef> {
233 match name {
234 Some(v) => Ok(v),
235 None => match field_ident {
236 None => Err(Error::new(
237 span,
238 "name must be explicitly specified with the `name` key on unnamed fields",
239 )),
240 Some(field_ident) => match NcName::try_from(field_ident.to_string()) {
241 Ok(value) => Ok(NameRef::Literal {
242 span: field_ident.span(),
243 value,
244 }),
245 Err(e) => Err(Error::new(
246 field_ident.span(),
247 format!("invalid XML name: {}", e),
248 )),
249 },
250 },
251 }
252}
253
254/// Construct a new field implementation from the meta attributes.
255///
256/// `field_ident` is, for some field types, used to infer an XML name if
257/// it is not specified explicitly.
258///
259/// `field_ty` is needed for type inference on extracted fields.
260///
261/// `container_namespace` is used in some cases to insert a default
262/// namespace.
263fn new_field(
264 meta: XmlFieldMeta,
265 field_ident: Option<&Ident>,
266 field_ty: &Type,
267 container_namespace: &NamespaceRef,
268) -> Result<Box<dyn Field>> {
269 match meta {
270 XmlFieldMeta::Attribute {
271 span,
272 qname: QNameRef { namespace, name },
273 default_,
274 type_,
275 codec,
276 } => {
277 let xml_name = default_name(span, name, field_ident)?;
278
279 // This would've been taken via `XmlFieldMeta::take_type` if
280 // this field was within an extract where a `type_` is legal
281 // to have.
282 if let Some(type_) = type_ {
283 return Err(Error::new_spanned(
284 type_,
285 "specifying `type_` on fields inside structs and enum variants is redundant and not allowed."
286 ));
287 }
288
289 Ok(Box::new(AttributeField {
290 xml_name,
291 xml_namespace: namespace,
292 default_,
293 codec,
294 }))
295 }
296
297 XmlFieldMeta::Text {
298 span: _,
299 codec,
300 type_,
301 } => {
302 // This would've been taken via `XmlFieldMeta::take_type` if
303 // this field was within an extract where a `type_` is legal
304 // to have.
305 if let Some(type_) = type_ {
306 return Err(Error::new_spanned(
307 type_,
308 "specifying `type_` on fields inside structs and enum variants is redundant and not allowed."
309 ));
310 }
311
312 Ok(Box::new(TextField { codec }))
313 }
314
315 XmlFieldMeta::Child {
316 span: _,
317 default_,
318 amount,
319 } => {
320 if let Some(AmountConstraint::Any(ref amount_span)) = amount {
321 if let Flag::Present(ref flag_span) = default_ {
322 let mut err =
323 Error::new(*flag_span, "`default` has no meaning for child collections");
324 err.combine(Error::new(
325 *amount_span,
326 "the field is treated as a collection because of this `n` value",
327 ));
328 return Err(err);
329 }
330 }
331
332 Ok(Box::new(ChildField {
333 default_,
334 amount: amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())),
335 extract: None,
336 }))
337 }
338
339 XmlFieldMeta::Extract {
340 span,
341 default_,
342 qname: QNameRef { namespace, name },
343 amount,
344 fields,
345 on_unknown_attribute,
346 on_unknown_child,
347 } => {
348 let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone());
349 let xml_name = default_name(span, name, field_ident)?;
350
351 let amount = amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site()));
352 match amount {
353 AmountConstraint::Any(ref amount) => {
354 if let Flag::Present(default_) = default_ {
355 let mut err = Error::new(
356 default_,
357 "default cannot be set when collecting into a collection",
358 );
359 err.combine(Error::new(
360 *amount,
361 "`n` was set to a non-1 value here, which enables collection logic",
362 ));
363 return Err(err);
364 }
365 }
366 AmountConstraint::FixedSingle(_) => (),
367 }
368
369 let mut field_defs = Vec::new();
370 let allow_inference =
371 matches!(amount, AmountConstraint::FixedSingle(_)) && fields.len() == 1;
372 for (i, mut field) in fields.into_iter().enumerate() {
373 let field_ty = match field.take_type() {
374 Some(v) => v,
375 None => {
376 if allow_inference {
377 field_ty.clone()
378 } else {
379 return Err(Error::new(
380 field.span(),
381 "extracted field must specify a type explicitly when extracting into a collection or when extracting more than one field."
382 ));
383 }
384 }
385 };
386
387 field_defs.push(FieldDef::from_extract(
388 field,
389 i as u32,
390 &field_ty,
391 &xml_namespace,
392 ));
393 }
394 let parts =
395 Compound::from_field_defs(field_defs, on_unknown_attribute, on_unknown_child)?;
396
397 Ok(Box::new(ChildField {
398 default_,
399 amount,
400 extract: Some(ExtractDef {
401 xml_namespace,
402 xml_name,
403 parts,
404 }),
405 }))
406 }
407
408 #[cfg(feature = "minidom")]
409 XmlFieldMeta::Element {
410 span,
411 default_,
412 amount,
413 } => Ok(Box::new(ElementField {
414 default_,
415 amount: amount.unwrap_or(AmountConstraint::FixedSingle(span)),
416 })),
417
418 #[cfg(not(feature = "minidom"))]
419 XmlFieldMeta::Element { span, amount } => {
420 let _ = amount;
421 Err(Error::new(
422 span,
423 "#[xml(element)] requires xso to be built with the \"minidom\" feature.",
424 ))
425 }
426
427 XmlFieldMeta::Flag {
428 span,
429 qname: QNameRef { namespace, name },
430 } => {
431 let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone());
432 let xml_name = default_name(span, name, field_ident)?;
433 Ok(Box::new(FlagField {
434 xml_namespace,
435 xml_name,
436 }))
437 }
438 }
439}
440
441/// Definition of a single field in a compound.
442///
443/// See [`Compound`][`crate::compound::Compound`] for more information on
444/// compounds in general.
445pub(crate) struct FieldDef {
446 /// A span which refers to the field's definition.
447 span: Span,
448
449 /// The member identifying the field.
450 member: Member,
451
452 /// The type of the field.
453 ty: Type,
454
455 /// The way the field is mapped to XML.
456 inner: Box<dyn Field>,
457}
458
459impl FieldDef {
460 /// Create a new field definition from its declaration.
461 ///
462 /// The `index` must be the zero-based index of the field even for named
463 /// fields.
464 pub(crate) fn from_field(
465 field: &syn::Field,
466 index: u32,
467 container_namespace: &NamespaceRef,
468 ) -> Result<Self> {
469 let (member, ident) = match field.ident.as_ref() {
470 Some(v) => (Member::Named(v.clone()), Some(v)),
471 None => (
472 Member::Unnamed(Index {
473 index,
474 // We use the type's span here, because `field.span()`
475 // will visually point at the `#[xml(..)]` meta, which is
476 // not helpful when glancing at error messages referring
477 // to the field itself.
478 span: field.ty.span(),
479 }),
480 None,
481 ),
482 };
483 // This will either be the field's identifier's span (for named
484 // fields) or the field's type (for unnamed fields), which should give
485 // the user a good visual feedback about which field an error message
486 // is.
487 let field_span = member.span();
488 let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?;
489 let ty = field.ty.clone();
490
491 Ok(Self {
492 span: field_span,
493 inner: new_field(meta, ident, &ty, container_namespace)?,
494 member,
495 ty,
496 })
497 }
498
499 /// Create a new field definition from its declaration.
500 ///
501 /// The `index` must be the zero-based index of the field even for named
502 /// fields.
503 pub(crate) fn from_extract(
504 meta: XmlFieldMeta,
505 index: u32,
506 ty: &Type,
507 container_namespace: &NamespaceRef,
508 ) -> Result<Self> {
509 let span = meta.span();
510 Ok(Self {
511 span,
512 member: Member::Unnamed(Index { index, span }),
513 ty: ty.clone(),
514 inner: new_field(meta, None, ty, container_namespace)?,
515 })
516 }
517
518 /// Access the [`syn::Member`] identifying this field in the original
519 /// type.
520 pub(crate) fn member(&self) -> &Member {
521 &self.member
522 }
523
524 /// Access the field's type.
525 pub(crate) fn ty(&self) -> &Type {
526 &self.ty
527 }
528
529 /// Construct the builder pieces for this field.
530 ///
531 /// `container_name` must be a reference to the compound's type, so that
532 /// it can be used for error messages.
533 pub(crate) fn make_builder_part(
534 &self,
535 scope: &FromEventsScope,
536 container_name: &ParentRef,
537 ) -> Result<FieldBuilderPart> {
538 self.inner
539 .make_builder_part(scope, container_name, &self.member, &self.ty)
540 }
541
542 /// Construct the iterator pieces for this field.
543 ///
544 /// `bound_name` must be the name to which the field's value is bound in
545 /// the iterator code.
546 pub(crate) fn make_iterator_part(
547 &self,
548 scope: &AsItemsScope,
549 container_name: &ParentRef,
550 bound_name: &Ident,
551 ) -> Result<FieldIteratorPart> {
552 self.inner
553 .make_iterator_part(scope, container_name, bound_name, &self.member, &self.ty)
554 }
555
556 /// Return true if this field's parsing consumes text data.
557 pub(crate) fn is_text_field(&self) -> bool {
558 self.inner.captures_text()
559 }
560
561 /// Return a span which points at the field's definition.'
562 pub(crate) fn span(&self) -> Span {
563 self.span
564 }
565}