xso_proc/field/
attribute.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 attributes.
8//!
9//! In particular, it provides the `#[xml(attribute)]` implementation.
10
11use quote::{quote, ToTokens};
12use syn::*;
13
14use crate::error_message::{self, ParentRef};
15use crate::meta::{Flag, NameRef, NamespaceRef};
16use crate::scope::{AsItemsScope, FromEventsScope};
17use crate::types::{
18    as_optional_xml_text_fn, default_fn, from_xml_text_fn, text_codec_decode_fn,
19    text_codec_encode_fn,
20};
21
22use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit};
23
24/// The field maps to an attribute.
25pub(super) struct AttributeField {
26    /// The optional XML namespace of the attribute.
27    pub(super) xml_namespace: Option<NamespaceRef>,
28
29    /// The XML name of the attribute.
30    pub(super) xml_name: NameRef,
31
32    /// Flag indicating whether the value should be defaulted if the
33    /// attribute is absent.
34    pub(super) default_: Flag,
35
36    /// Optional codec to use.
37    pub(super) codec: Option<Expr>,
38}
39
40impl Field for AttributeField {
41    fn make_builder_part(
42        &self,
43        scope: &FromEventsScope,
44        container_name: &ParentRef,
45        member: &Member,
46        ty: &Type,
47    ) -> Result<FieldBuilderPart> {
48        let FromEventsScope { ref attrs, .. } = scope;
49        let ty = ty.clone();
50        let xml_namespace = &self.xml_namespace;
51        let xml_name = &self.xml_name;
52
53        let missing_msg = error_message::on_missing_attribute(container_name, member);
54
55        let xml_namespace = match xml_namespace {
56            Some(v) => v.to_token_stream(),
57            None => quote! {
58                ::xso::exports::rxml::Namespace::none()
59            },
60        };
61
62        let finalize = match self.codec {
63            Some(ref codec) => {
64                let decode = text_codec_decode_fn(ty.clone());
65                quote! {
66                    |value| #decode(&#codec, value)
67                }
68            }
69            None => {
70                let from_xml_text = from_xml_text_fn(ty.clone());
71                quote! { #from_xml_text }
72            }
73        };
74
75        let on_absent = match self.default_ {
76            Flag::Absent => quote! {
77                return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
78            },
79            Flag::Present(_) => {
80                let default_ = default_fn(ty.clone());
81                quote! {
82                    #default_()
83                }
84            }
85        };
86
87        Ok(FieldBuilderPart::Init {
88            value: FieldTempInit {
89                init: quote! {
90                    match #attrs.remove(#xml_namespace, #xml_name).map(#finalize).transpose()? {
91                        ::core::option::Option::Some(v) => v,
92                        ::core::option::Option::None => #on_absent,
93                    }
94                },
95                ty: ty.clone(),
96            },
97        })
98    }
99
100    fn make_iterator_part(
101        &self,
102        _scope: &AsItemsScope,
103        _container_name: &ParentRef,
104        bound_name: &Ident,
105        _member: &Member,
106        ty: &Type,
107    ) -> Result<FieldIteratorPart> {
108        let xml_namespace = match self.xml_namespace {
109            Some(ref v) => quote! { ::xso::exports::rxml::Namespace::from(#v) },
110            None => quote! {
111                ::xso::exports::rxml::Namespace::NONE
112            },
113        };
114        let xml_name = &self.xml_name;
115
116        let generator = match self.codec {
117            Some(ref codec) => {
118                let encode = text_codec_encode_fn(ty.clone());
119                quote! { #encode(&#codec, #bound_name)? }
120            }
121            None => {
122                let as_optional_xml_text = as_optional_xml_text_fn(ty.clone());
123                quote! { #as_optional_xml_text(#bound_name)? }
124            }
125        };
126
127        Ok(FieldIteratorPart::Header {
128            generator: quote! {
129                #generator.map(|#bound_name| ::xso::Item::Attribute(
130                    #xml_namespace,
131                    ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
132                    #bound_name,
133                ));
134            },
135        })
136    }
137}