xso_proc/field/
element.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//! This module concerns the processing of untyped `minidom::Element`
8//! children.
9//!
10//! In particular, it provides the `#[xml(element)]` implementation.
11
12use proc_macro2::{Span, TokenStream};
13use quote::quote;
14use syn::*;
15
16use crate::error_message::{self, ParentRef};
17use crate::meta::{AmountConstraint, Flag};
18use crate::scope::{AsItemsScope, FromEventsScope};
19use crate::types::{
20    as_xml_iter_fn, default_fn, element_ty, from_events_fn, from_xml_builder_ty,
21    into_iterator_into_iter_fn, into_iterator_item_ty, into_iterator_iter_ty, item_iter_ty,
22    option_ty, ref_ty,
23};
24
25use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher};
26
27pub(super) struct ElementField {
28    /// Flag indicating whether the value should be defaulted if the
29    /// child is absent.
30    pub(super) default_: Flag,
31
32    /// Number of child elements allowed.
33    pub(super) amount: AmountConstraint,
34}
35
36impl Field for ElementField {
37    fn make_builder_part(
38        &self,
39        scope: &FromEventsScope,
40        container_name: &ParentRef,
41        member: &Member,
42        ty: &Type,
43    ) -> Result<FieldBuilderPart> {
44        let element_ty = match self.amount {
45            AmountConstraint::FixedSingle(_) => ty.clone(),
46            AmountConstraint::Any(_) => into_iterator_item_ty(ty.clone()),
47        };
48
49        let FromEventsScope {
50            ref substate_result,
51            ..
52        } = scope;
53
54        let from_events = from_events_fn(element_ty.clone());
55
56        let extra_defs = TokenStream::default();
57        let field_access = scope.access_field(member);
58
59        let default_fn = default_fn(ty.clone());
60        let builder = from_xml_builder_ty(element_ty.clone());
61
62        match self.amount {
63            AmountConstraint::FixedSingle(_) => {
64                let missing_msg = error_message::on_missing_child(container_name, member);
65                let on_absent = match self.default_ {
66                    Flag::Absent => quote! {
67                        return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
68                    },
69                    Flag::Present(_) => {
70                        quote! { #default_fn() }
71                    }
72                };
73                Ok(FieldBuilderPart::Nested {
74                    extra_defs,
75                    value: FieldTempInit {
76                        init: quote! { ::core::option::Option::None },
77                        ty: option_ty(ty.clone()),
78                    },
79                    matcher: NestedMatcher::Selective(quote! {
80                        if #field_access.is_some() {
81                            ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
82                        } else {
83                            #from_events(name, attrs)
84                        }
85                    }),
86                    builder,
87                    collect: quote! {
88                        #field_access = ::core::option::Option::Some(#substate_result);
89                    },
90                    finalize: quote! {
91                        match #field_access {
92                            ::core::option::Option::Some(value) => value,
93                            ::core::option::Option::None => #on_absent,
94                        }
95                    },
96                })
97            }
98            AmountConstraint::Any(_) => Ok(FieldBuilderPart::Nested {
99                extra_defs,
100                value: FieldTempInit {
101                    init: quote! { #default_fn() },
102                    ty: ty.clone(),
103                },
104                matcher: NestedMatcher::Fallback(quote! {
105                    #builder::new(name, attrs)
106                }),
107                builder,
108                collect: quote! {
109                    <#ty as ::core::iter::Extend::<#element_ty>>::extend(&mut #field_access, [#substate_result]);
110                },
111                finalize: quote! {
112                    #field_access
113                },
114            }),
115        }
116    }
117
118    fn make_iterator_part(
119        &self,
120        scope: &AsItemsScope,
121        _container_name: &ParentRef,
122        bound_name: &Ident,
123        _member: &Member,
124        ty: &Type,
125    ) -> Result<FieldIteratorPart> {
126        let AsItemsScope { ref lifetime, .. } = scope;
127
128        let item_ty = match self.amount {
129            AmountConstraint::FixedSingle(_) => ty.clone(),
130            AmountConstraint::Any(_) => {
131                // This should give us the type of element stored in the
132                // collection.
133                into_iterator_item_ty(ty.clone())
134            }
135        };
136
137        let element_ty = element_ty(Span::call_site());
138        let iter_ty = item_iter_ty(element_ty.clone(), lifetime.clone());
139        let element_iter = into_iterator_iter_ty(ref_ty(ty.clone(), lifetime.clone()));
140        let into_iter = into_iterator_into_iter_fn(ref_ty(ty.clone(), lifetime.clone()));
141
142        let state_ty = Type::Tuple(TypeTuple {
143            paren_token: token::Paren::default(),
144            elems: [element_iter, option_ty(iter_ty.clone())]
145                .into_iter()
146                .collect(),
147        });
148
149        let extra_defs = TokenStream::default();
150        let as_xml_iter = as_xml_iter_fn(item_ty.clone());
151        let init = quote! { #as_xml_iter(#bound_name)? };
152        let iter_ty = item_iter_ty(item_ty.clone(), lifetime.clone());
153
154        match self.amount {
155            AmountConstraint::FixedSingle(_) => Ok(FieldIteratorPart::Content {
156                extra_defs,
157                value: FieldTempInit { init, ty: iter_ty },
158                generator: quote! {
159                    #bound_name.next().transpose()
160                },
161            }),
162            AmountConstraint::Any(_) => Ok(FieldIteratorPart::Content {
163                extra_defs,
164                value: FieldTempInit {
165                    init: quote! {
166                        (#into_iter(#bound_name), ::core::option::Option::None)
167                    },
168                    ty: state_ty,
169                },
170                generator: quote! {
171                    loop {
172                        if let ::core::option::Option::Some(current) = #bound_name.1.as_mut() {
173                            if let ::core::option::Option::Some(item) = current.next() {
174                                break ::core::option::Option::Some(item).transpose();
175                            }
176                        }
177                        if let ::core::option::Option::Some(item) = #bound_name.0.next() {
178                            #bound_name.1 = ::core::option::Option::Some(
179                                <#element_ty as ::xso::AsXml>::as_xml_iter(item)?
180                            );
181                        } else {
182                            break ::core::result::Result::Ok(::core::option::Option::None)
183                        }
184                    }
185                },
186            }),
187        }
188    }
189}