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 = Compound::from_field_defs(
395 field_defs,
396 on_unknown_attribute,
397 on_unknown_child,
398 vec![],
399 )?;
400
401 Ok(Box::new(ChildField {
402 default_,
403 amount,
404 extract: Some(ExtractDef {
405 xml_namespace,
406 xml_name,
407 parts,
408 }),
409 }))
410 }
411
412 #[cfg(feature = "minidom")]
413 XmlFieldMeta::Element {
414 span,
415 default_,
416 amount,
417 } => Ok(Box::new(ElementField {
418 default_,
419 amount: amount.unwrap_or(AmountConstraint::FixedSingle(span)),
420 })),
421
422 #[cfg(not(feature = "minidom"))]
423 XmlFieldMeta::Element { span, amount } => {
424 let _ = amount;
425 Err(Error::new(
426 span,
427 "#[xml(element)] requires xso to be built with the \"minidom\" feature.",
428 ))
429 }
430
431 XmlFieldMeta::Flag {
432 span,
433 qname: QNameRef { namespace, name },
434 } => {
435 let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone());
436 let xml_name = default_name(span, name, field_ident)?;
437 Ok(Box::new(FlagField {
438 xml_namespace,
439 xml_name,
440 }))
441 }
442 }
443}
444
445/// Definition of a single field in a compound.
446///
447/// See [`Compound`][`crate::compound::Compound`] for more information on
448/// compounds in general.
449pub(crate) struct FieldDef {
450 /// A span which refers to the field's definition.
451 span: Span,
452
453 /// The member identifying the field.
454 member: Member,
455
456 /// The type of the field.
457 ty: Type,
458
459 /// The way the field is mapped to XML.
460 inner: Box<dyn Field>,
461}
462
463impl FieldDef {
464 /// Create a new field definition from its declaration.
465 ///
466 /// The `index` must be the zero-based index of the field even for named
467 /// fields.
468 pub(crate) fn from_field(
469 field: &syn::Field,
470 index: u32,
471 container_namespace: &NamespaceRef,
472 ) -> Result<Self> {
473 let (member, ident) = match field.ident.as_ref() {
474 Some(v) => (Member::Named(v.clone()), Some(v)),
475 None => (
476 Member::Unnamed(Index {
477 index,
478 // We use the type's span here, because `field.span()`
479 // will visually point at the `#[xml(..)]` meta, which is
480 // not helpful when glancing at error messages referring
481 // to the field itself.
482 span: field.ty.span(),
483 }),
484 None,
485 ),
486 };
487 // This will either be the field's identifier's span (for named
488 // fields) or the field's type (for unnamed fields), which should give
489 // the user a good visual feedback about which field an error message
490 // is.
491 let field_span = member.span();
492 let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?;
493 let ty = field.ty.clone();
494
495 Ok(Self {
496 span: field_span,
497 inner: new_field(meta, ident, &ty, container_namespace)?,
498 member,
499 ty,
500 })
501 }
502
503 /// Create a new field definition from its declaration.
504 ///
505 /// The `index` must be the zero-based index of the field even for named
506 /// fields.
507 pub(crate) fn from_extract(
508 meta: XmlFieldMeta,
509 index: u32,
510 ty: &Type,
511 container_namespace: &NamespaceRef,
512 ) -> Result<Self> {
513 let span = meta.span();
514 Ok(Self {
515 span,
516 member: Member::Unnamed(Index { index, span }),
517 ty: ty.clone(),
518 inner: new_field(meta, None, ty, container_namespace)?,
519 })
520 }
521
522 /// Access the [`syn::Member`] identifying this field in the original
523 /// type.
524 pub(crate) fn member(&self) -> &Member {
525 &self.member
526 }
527
528 /// Access the field's type.
529 pub(crate) fn ty(&self) -> &Type {
530 &self.ty
531 }
532
533 /// Construct the builder pieces for this field.
534 ///
535 /// `container_name` must be a reference to the compound's type, so that
536 /// it can be used for error messages.
537 pub(crate) fn make_builder_part(
538 &self,
539 scope: &FromEventsScope,
540 container_name: &ParentRef,
541 ) -> Result<FieldBuilderPart> {
542 self.inner
543 .make_builder_part(scope, container_name, &self.member, &self.ty)
544 }
545
546 /// Construct the iterator pieces for this field.
547 ///
548 /// `bound_name` must be the name to which the field's value is bound in
549 /// the iterator code.
550 pub(crate) fn make_iterator_part(
551 &self,
552 scope: &AsItemsScope,
553 container_name: &ParentRef,
554 bound_name: &Ident,
555 ) -> Result<FieldIteratorPart> {
556 self.inner
557 .make_iterator_part(scope, container_name, bound_name, &self.member, &self.ty)
558 }
559
560 /// Return true if this field's parsing consumes text data.
561 pub(crate) fn is_text_field(&self) -> bool {
562 self.inner.captures_text()
563 }
564
565 /// Return a span which points at the field's definition.'
566 pub(crate) fn span(&self) -> Span {
567 self.span
568 }
569}