xso_proc: introduce Field trait and object

This simplifies the code of FieldDef and makes it more extensible for
future ideas.
This commit is contained in:
Jonas Schäfer 2024-04-02 17:11:33 +02:00
parent 56e78a8197
commit 06732ca721
14 changed files with 574 additions and 485 deletions

View File

@ -1175,3 +1175,32 @@ fn element_codec_roundtrip() {
other => panic!("unexpected result: {:?}", other),
}
}
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
#[xml(namespace = self::TEST_NS1, name = "child-switched", child = self::TEST_NS2)]
pub enum ChildSwitchedEnum {
#[xml(name = "variant-1")]
Variant1 {
#[xml(text)]
data: String,
},
#[xml(name = "variant-2")]
Variant2 {
#[xml(attribute)]
data: String,
},
}
#[test]
fn child_switched_enum_roundtrip_1() {
crate::util::test::roundtrip_full::<ChildSwitchedEnum>(
"<child-switched xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><variant-1 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>some data</variant-1></child-switched>",
);
}
#[test]
fn child_switched_enum_roundtrip_2() {
crate::util::test::roundtrip_full::<ChildSwitchedEnum>(
"<child-switched xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><variant-2 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' data='other data'/></child-switched>",
);
}

View File

@ -17,9 +17,5 @@ proc-macro = true
[dependencies]
quote = "^1"
syn = { version = "^2", features = ["full"] }
syn = { version = "^2", features = ["full", "extra-traits"] }
proc-macro2 = "^1"
[features]
debug = ["syn/extra-traits"]
default = ["debug"]

View File

@ -70,7 +70,7 @@ fn default_on_unknown_attribute(p: Option<Ident>) -> Expr {
/// This struct is used to generate the parsing/serialisation loop, but most
/// notably it is *not* responsible for matching the incoming element; that
/// is the responsibility of the caller of the corresponding functions.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct Compound {
/// The fields, in declaration order.
fields: Vec<FieldDef>,
@ -122,7 +122,7 @@ impl Compound {
text_field = Some(field.ident.span());
}
if field.kind.is_collect_wildcard() {
if field.kind.is_child_wildcard() {
if collect_wildcard_field.is_some() {
return Err(Error::new_spanned(
field.ident,
@ -195,15 +195,15 @@ impl Compound {
/// can be used to ignore attributes which have been used in element
/// matching (e.g. enum discriminators).
pub(crate) fn build_try_from_element(
self,
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
residual: &Ident,
forgive_attributes: &[&str],
) -> Result<TokenStream> {
let readable_name = container_name.to_string();
let on_unknown_child = self.on_unknown_child;
let on_unknown_attribute = self.on_unknown_attribute;
let on_unknown_child = &self.on_unknown_child;
let on_unknown_attribute = &self.on_unknown_attribute;
let mut init = quote! {};
let mut tupinit = quote! {};
@ -221,7 +221,7 @@ impl Compound {
};
let mut had_fallback: bool = false;
for field in self.fields {
for field in self.fields.iter() {
let ident = field.ident.clone();
let FieldParsePart {
tempinit: field_tempinit,
@ -311,14 +311,14 @@ impl Compound {
/// Note that the field referenced by [`Self::namespace_field`] is not
/// accessed by this function.
pub(crate) fn build_into_element(
self,
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
builder: &Ident,
mut access_field: impl FnMut(Member) -> Expr,
) -> Result<TokenStream> {
let mut build = quote! {};
for field in self.fields {
for field in self.fields.iter() {
let field_build = field.build_into_element(
container_name,
container_namespace_expr,

View File

@ -57,7 +57,7 @@ fn build_ident_mapping<I: Iterator<Item = Member>>(
///
/// The caller of the respective methods decides what string the variant
/// matches against and how that match is carried out.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
struct StrMatchedVariant {
/// The string to match against.
value: LitStr,
@ -193,7 +193,7 @@ impl StrMatchedVariant {
}
/// Variant of an enum where each variant has completely independent matching.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
struct DynamicVariant {
/// The identifier of the enum variant.
ident: Ident,
@ -248,7 +248,7 @@ impl DynamicVariant {
}
/// An enum which switches on the XML element's name, with a fixed namespace.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
struct XmlNameSwitched {
/// The exhaustive flag, if set on the enum.
///
@ -450,7 +450,7 @@ impl XmlNameSwitched {
}
/// An enum which switches on the value of an attribute of the XML element.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
struct XmlAttributeSwitched {
/// The namespace the enum's XML element resides in.
namespace: StaticNamespace,
@ -678,7 +678,7 @@ impl XmlAttributeSwitched {
}
/// An enum where each variant has completely independent matching.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
struct Dynamic {
/// Variants of the enum.
variants: Vec<DynamicVariant>,
@ -785,7 +785,7 @@ impl Dynamic {
/// Inner part of [`EnumDef`], supporting the different styles of
/// enumerations.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
enum EnumInner {
/// Enum item where the variants switch on the XML element's name.
XmlNameSwitched(XmlNameSwitched),
@ -840,7 +840,7 @@ impl EnumInner {
}
/// Represent an enum.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
struct EnumDef {
/// The `validate` value, if set on the enum.
///

View File

@ -9,8 +9,7 @@ use syn::{spanned::Spanned, Member, Path};
///
/// This reference can be converted to a hopefully-useful human-readable
/// string via [`std::fmt::Display`].
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone, Debug)]
pub(super) enum ParentRef {
/// The parent is addressable by a path, e.g. a struct type or enum
/// variant.

View File

@ -7,12 +7,12 @@ use syn::{spanned::Spanned, *};
use crate::error_message::{self, ParentRef};
use crate::meta::{FlagOr, Name, NameRef};
use super::FieldParsePart;
use super::{Field, FieldParsePart};
/// A field parsed from an XML attribute.
///
/// Maps to `#[xml(attribute)]`.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct AttributeField {
/// The XML name of the attribute.
///
@ -71,21 +71,19 @@ impl AttributeField {
codec,
})
}
}
/// Construct the code necessary to parse the attribute from a
/// `minidom::Element` into a field.
///
/// `ty` must be the field's type. The other arguments are currently
/// ignored.
pub(super) fn build_try_from_element(
self,
name: &ParentRef,
impl Field for AttributeField {
fn build_try_from_element(
&self,
container_name: &ParentRef,
_container_namespace_expr: &Expr,
_tempname: Ident,
ident: Member,
ty: Type,
member: &Member,
ty: &Type,
) -> Result<FieldParsePart> {
let missing_msg = error_message::on_missing_attribute(name, &ident);
let name = self.name;
let missing_msg = error_message::on_missing_attribute(container_name, &member);
let name = &self.name;
let on_missing = match self.default_ {
FlagOr::Absent => {
quote! {
@ -101,14 +99,14 @@ impl AttributeField {
#ty_default()
}
}
FlagOr::Value { value, .. } => {
FlagOr::Value { ref value, .. } => {
quote! {
#value()
}
}
};
let decode = match self.codec {
Some(codec_ty) => {
Some(ref codec_ty) => {
let codec_ty_decode = quote_spanned! {codec_ty.span()=> <#codec_ty as ::xso::TextCodec::<#ty>>::decode};
quote! {
residual.attr(#name).map(|value| {
@ -140,29 +138,29 @@ impl AttributeField {
})
}
/// Construct an expression which consumes the ident `builder`, which must
/// be a minidom `Builder`, and returns it, modified in such a way that it
/// contains the field's data.
///
/// - `ident` must be an expression to consume the field's data. It is
/// evaluated exactly once.
/// - `ty` must be the field's type.
pub(super) fn build_into_element(self, ident: Expr, ty: Type) -> Result<TokenStream> {
fn build_into_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
_member: &Member,
ty: &Type,
access: Expr,
) -> Result<TokenStream> {
let encode = match self.codec {
Some(codec_ty) => {
Some(ref codec_ty) => {
let codec_ty_encode = quote_spanned! {codec_ty.span()=> <#codec_ty as ::xso::TextCodec::<#ty>>::encode};
quote! {
#codec_ty_encode(#ident)
#codec_ty_encode(#access)
}
}
None => {
let ty_into_optional_xml_text = quote_spanned! {ty.span()=> <#ty as ::xso::IntoOptionalXmlText>::into_optional_xml_text};
quote! {
#ty_into_optional_xml_text(#ident)
#ty_into_optional_xml_text(#access)
}
}
};
let name = self.name;
let name = &self.name;
Ok(quote! {
match #encode {
Some(v) => builder.attr(#name, v),
@ -170,4 +168,13 @@ impl AttributeField {
}
})
}
fn build_set_namespace(
&self,
_input: &Ident,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
Ok(TokenStream::default())
}
}

View File

@ -9,13 +9,13 @@ use crate::compound::Compound;
use crate::error_message::{self, ParentRef};
use crate::meta::{Flag, FlagOr, NameRef, NamespaceRef, XmlFieldMeta};
use super::{ChildMode, FieldDef, FieldNamespace, FieldParsePart};
use super::{ChildMode, Field, FieldDef, FieldNamespace, FieldParsePart};
/// Definition of a child data extraction.
///
/// This is used to implement fields annotated with
/// `#[xml(child(.., extract(..))]` or `#[xml(children(.., extract(..)))]`.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(super) struct ExtractDef {
namespace: FieldNamespace,
@ -100,13 +100,13 @@ impl ExtractDef {
/// latched into a local variable (in the generated code) and that is
/// passed on to the inner call to [`Compound::build_into_element`].
fn build_extract(
self,
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
residual: &Ident,
) -> Result<TokenStream> {
let namespace_expr = self.expand_namespace(container_namespace_expr);
let xml_name = self.name;
let xml_name = &self.name;
let nfields = self.parts.field_count();
@ -121,7 +121,7 @@ impl ExtractDef {
.build_try_from_element(container_name, &namespace_expr, residual, &[])?;
let test_expr = match self.namespace {
FieldNamespace::Static(xml_namespace) => quote! {
FieldNamespace::Static(ref xml_namespace) => quote! {
#residual.is(#xml_name, #xml_namespace)
},
FieldNamespace::Super(_) => quote! {
@ -153,13 +153,13 @@ impl ExtractDef {
/// latched into a local variable (in the generated code) and that is
/// passed on to the inner call to [`Compound::build_into_element`].
fn build_assemble(
self,
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
field: &Expr,
) -> Result<TokenStream> {
let xml_namespace = self.expand_namespace(container_namespace_expr);
let xml_name = self.name;
let xml_name = &self.name;
let nfields = self.parts.field_count();
@ -174,7 +174,7 @@ impl ExtractDef {
let builder = Ident::new("builder", Span::call_site());
let builder_init = match self.namespace {
FieldNamespace::Static(xml_namespace) => quote! {
FieldNamespace::Static(ref xml_namespace) => quote! {
::xso::exports::minidom::Element::builder(
#xml_name,
#xml_namespace
@ -226,7 +226,7 @@ impl ExtractDef {
/// A field parsed from an XML child, destructured into a Rust data structure.
///
/// Maps to `#[xml(child)]` and `#[xml(children)]`.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct ChildField {
/// Determines whether one or more matching child elements are expected.
///
@ -373,41 +373,23 @@ impl ChildField {
})
}
}
}
/// Construct the code necessary to parse the child from a
/// `minidom::Element` into a field.
///
/// - `name` should be the identifier or path of the containing compound
/// and is used to generate runtime error messages.
///
/// - `tempname` must be an identifier which the implementation can freely
/// use for a temporary variable related to parsing.
///
/// - `ident` must be the target struct member identifier or index, used
/// for error messages.
///
/// - `ty` must be the field's type. If this field is not an extract, the
/// type's `FromXml` implementation is used to destructure matching XML
/// element(s).
///
/// - `parent_namespace_expr` may be an expression under which the
/// parent compound's `::xso::DynNamespaceEnum` value is
/// obtainable, if any. This is needed for fields and compounds
/// using `#[xml(namespace = super)]`.
pub(super) fn build_try_from_element(
self,
name: &ParentRef,
tempname: Ident,
ident: Member,
ty: Type,
impl Field for ChildField {
fn build_try_from_element(
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
tempname: Ident,
member: &Member,
ty: &Type,
) -> Result<FieldParsePart> {
let ty_span = ty.span();
let ty_default = quote_spanned! {ty_span=> <#ty as std::default::Default>::default};
match self.mode {
ChildMode::Single => {
let missingerr = error_message::on_missing_child(name, &ident);
let duperr = error_message::on_duplicate_child(name, &ident);
let missingerr = error_message::on_missing_child(container_name, &member);
let duperr = error_message::on_duplicate_child(container_name, &member);
let on_missing = match self.default_ {
FlagOr::Absent => {
quote! {
@ -419,16 +401,16 @@ impl ChildField {
#ty_default()
}
}
FlagOr::Value { value, .. } => {
FlagOr::Value { ref value, .. } => {
quote! {
#value()
}
}
};
match self.extract {
Some(extract) => {
Some(ref extract) => {
let extract = extract.build_extract(
&name.child(ident),
&container_name.child(member.clone()),
container_namespace_expr,
&Ident::new("residual", Span::call_site()),
)?;
@ -467,9 +449,9 @@ impl ChildField {
},
};
let codec_ty = match self.codec {
Some(ty) => Type::Path(TypePath {
Some(ref ty) => Type::Path(TypePath {
qself: None,
path: ty,
path: ty.clone(),
}),
None => ty.clone(),
};
@ -523,9 +505,9 @@ impl ChildField {
let ty_extend =
quote_spanned! {ty_span=> <#ty as ::std::iter::Extend<#item_ty>>::extend};
match self.extract {
Some(extract) => {
Some(ref extract) => {
let extract = extract.build_extract(
&name.child(ident),
&container_name.child(member.clone()),
container_namespace_expr,
&Ident::new("residual", Span::call_site()),
)?;
@ -554,9 +536,9 @@ impl ChildField {
));
}
let codec_ty = match self.codec {
Some(ty) => Type::Path(TypePath {
Some(ref ty) => Type::Path(TypePath {
qself: None,
path: ty,
path: ty.clone(),
}),
None => item_ty.clone(),
};
@ -587,24 +569,19 @@ impl ChildField {
}
}
/// Construct the code necessary to update the namespace on the field.
pub(super) fn build_set_namespace(
&self,
input: &Ident,
member: &Expr,
member_ty: &Type,
) -> Result<TokenStream> {
fn build_set_namespace(&self, input: &Ident, ty: &Type, access: Expr) -> Result<TokenStream> {
match self.mode {
ChildMode::Single => match self.extract {
Some(_) => Ok(quote! {}),
None => match self.super_namespace {
Flag::Absent => Ok(quote! {}),
Flag::Present(_) => {
let member_ty_span = member_ty.span();
let ty_span = ty.span();
// using quote_spanned in this way here causes the "the trait `DynNamespace` is not implemented for `..`" error message appear on member_ty instead of on the derive macro invocation.
let method = quote_spanned! {member_ty_span=> <#member_ty as ::xso::DynNamespace>::set_namespace};
let method =
quote_spanned! {ty_span=> <#ty as ::xso::DynNamespace>::set_namespace};
Ok(quote! {
#method(&mut #member, #input.clone());
#method(&mut #access, #input.clone());
})
}
},
@ -613,28 +590,17 @@ impl ChildField {
}
}
/// Construct an expression which consumes the ident `builder`, which must
/// be a minidom `Builder`, and returns it, modified in such a way that it
/// contains the field's data.
///
/// - `ident` must be an expression to consume the field's data. It is
/// evaluated exactly once.
/// - `ty` must be the field's type.
/// - `parent_namespace_expr` may be an expression under which the
/// parent compound's `::xso::DynNamespaceEnum` value is
/// obtainable, if any. This is needed for fields and compounds
/// using `#[xml(namespace = super)]`.
pub(super) fn build_into_element(
self,
name: &ParentRef,
member: Member,
ident: Expr,
ty: Type,
fn build_into_element(
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
member: &Member,
ty: &Type,
access: Expr,
) -> Result<TokenStream> {
let temp_ident = Ident::new("__data", Span::call_site());
let skip_map = match self.skip_if {
Some(callable) => quote! {
Some(ref callable) => quote! {
match #callable(&#temp_ident) {
false => Some(#temp_ident),
true => None,
@ -644,7 +610,7 @@ impl ChildField {
};
match self.mode {
ChildMode::Single => match self.extract {
Some(extract) => {
Some(ref extract) => {
let temp_expr = Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
@ -655,12 +621,12 @@ impl ChildField {
.expect("child extract can only have one field!")
.clone();
let assemble = extract.build_assemble(
&name.child(member),
&container_name.child(member.clone()),
container_namespace_expr,
&temp_expr,
)?;
Ok(quote! {
match Option::<#inner_ty>::from(#ident).and_then(|#temp_ident| #skip_map) {
match Option::<#inner_ty>::from(#access).and_then(|#temp_ident| #skip_map) {
Some(#temp_ident) => builder.append(::xso::exports::minidom::Node::Element(#assemble)),
None => builder,
}
@ -668,9 +634,9 @@ impl ChildField {
}
None => {
let codec_ty = match self.codec {
Some(ty) => Type::Path(TypePath {
Some(ref ty) => Type::Path(TypePath {
qself: None,
path: ty,
path: ty.clone(),
}),
None => ty.clone(),
};
@ -681,7 +647,7 @@ impl ChildField {
let codec_ty_encode = quote_spanned! {codec_ty_span=> <#codec_ty as ::xso::ElementCodec::<#field_ty>>::encode};
Ok(quote! {
{
let #temp_ident = #ident;
let #temp_ident = #access;
match #skip_map.map(#codec_ty_encode).and_then(#codec_ty_into_tree) {
Some(#temp_ident) => builder.append(::xso::exports::minidom::Node::Element(#temp_ident)),
None => builder,
@ -691,9 +657,9 @@ impl ChildField {
}
},
ChildMode::Collection => match self.extract {
Some(extract) => {
Some(ref extract) => {
let assemble = extract.build_assemble(
&name.child(member),
&container_name.child(member.clone()),
container_namespace_expr,
&Expr::Path(ExprPath {
attrs: Vec::new(),
@ -703,7 +669,7 @@ impl ChildField {
)?;
Ok(quote! {
builder.append_all(
#ident.into_iter().filter_map(|#temp_ident| {
#access.into_iter().filter_map(|#temp_ident| {
match #skip_map {
Some(#temp_ident) => Some(#assemble),
None => None,
@ -719,9 +685,9 @@ impl ChildField {
})
.expect("failed to construct item type");
let codec_ty = match self.codec {
Some(ty) => Type::Path(TypePath {
Some(ref ty) => Type::Path(TypePath {
qself: None,
path: ty,
path: ty.clone(),
}),
None => item_ty.clone(),
};
@ -730,7 +696,7 @@ impl ChildField {
quote_spanned! {codec_ty_span=> <#codec_ty as ::xso::IntoXml>::into_tree};
let codec_ty_encode = quote_spanned! {codec_ty_span=> <#codec_ty as ::xso::ElementCodec::<#item_ty>>::encode};
Ok(quote! {
builder.append_all(#ident.into_iter().filter_map(|#temp_ident| {
builder.append_all(#access.into_iter().filter_map(|#temp_ident| {
#skip_map.map(#codec_ty_encode).and_then(#codec_ty_into_tree).map(|el| ::xso::exports::minidom::Node::Element(el))
}))
})

View File

@ -9,13 +9,13 @@ use crate::error_message::{self, ParentRef};
use crate::meta::{FlagOr, Name, NameRef, NamespaceRef, StaticNamespace};
use crate::structs::ElementSelector;
use super::FieldParsePart;
use super::{Field, FieldParsePart};
/// A field parsed from an XML child, without destructuring it into Rust
/// data structures.
///
/// Maps to `#[xml(element)]`.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct ElementField {
/// Logic to select matching child elements.
selector: ElementSelector,
@ -71,34 +71,22 @@ impl ElementField {
};
Ok(Self { selector, default_ })
}
}
/// Construct the code necessary to parse the child element from a
/// `minidom::Element` into a field.
///
/// - `name` should be the identifier or path of the containing compound
/// and is used to generate runtime error messages.
///
/// - `tempname` must be an identifier which the implementation can freely
/// use for a temporary variable related to parsing.
///
/// - `ident` must be the target struct member identifier or index, used
/// for error messages.
///
/// - `ty` must be the field's type. This type must implement
/// `From<Element>`. If [`Self::default_on_missing`] is set, it also
/// needs to implement `Default`.
pub(super) fn build_try_from_element(
self,
name: &ParentRef,
impl Field for ElementField {
fn build_try_from_element(
&self,
container_name: &ParentRef,
_container_namespace_expr: &Expr,
tempname: Ident,
ident: Member,
ty: Type,
member: &Member,
ty: &Type,
) -> Result<FieldParsePart> {
let test = self
.selector
.build_test(&Ident::new("residual", Span::call_site()));
let missingerr = error_message::on_missing_child(name, &ident);
let duperr = error_message::on_duplicate_child(name, &ident);
let missingerr = error_message::on_missing_child(container_name, member);
let duperr = error_message::on_duplicate_child(container_name, member);
let ty_span = ty.span();
let ty_default = quote_spanned! {ty_span=> <#ty as std::default::Default>::default};
let ty_from_element =
@ -114,7 +102,7 @@ impl ElementField {
#ty_default()
}
}
FlagOr::Value { value, .. } => {
FlagOr::Value { ref value, .. } => {
quote! {
#value()
}
@ -146,29 +134,37 @@ impl ElementField {
})
}
/// Construct an expression which consumes the ident `builder`, which must
/// be a minidom `Builder`, and returns it, modified in such a way that it
/// contains the field's data.
///
/// - `ident` must be an expression to consume the field's data. It is
/// evaluated exactly once.
/// - `ty` must be the field's type. This type must implement
/// `Into<Option<Element>>`.
pub(super) fn build_into_element(self, ident: Expr, ty: Type) -> Result<TokenStream> {
fn build_into_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
_member: &Member,
ty: &Type,
access: Expr,
) -> Result<TokenStream> {
Ok(quote! {
match <#ty as Into<Option<::xso::exports::minidom::Element>>>::into(#ident) {
match <#ty as Into<Option<::xso::exports::minidom::Element>>>::into(#access) {
Some(v) => builder.append(::xso::exports::minidom::Node::Element(v)),
None => builder
}
})
}
fn build_set_namespace(
&self,
_input: &Ident,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
Ok(TokenStream::default())
}
}
/// A field parsed from a XML children, without destructuring them into Rust
/// data structures.
///
/// Maps to `#[xml(elements)]`.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct ElementsField {
/// Selector to choose the child elements to collect.
///
@ -225,30 +221,26 @@ impl ElementsField {
selector: namespace.map(|x| (x, name.map(|x| x.into()))),
})
}
}
/// Construct the code necessary to parse the child elements from a
/// `minidom::Element` into a field.
///
/// - `name` should be the identifier or path of the containing compound
/// and is used to generate runtime error messages.
///
/// - `tempname` must be an identifier which the implementation can freely
/// use for a temporary variable related to parsing.
///
/// - `ident` must be the target struct member identifier or index, used
/// for error messages.
///
/// - `ty` must be the field's type. This must be `Vec<minidom::Element>`,
/// otherwise compile-time errors will follow.
pub(super) fn build_try_from_element(
self,
_name: &ParentRef,
impl Field for ElementsField {
fn is_child_wildcard(&self) -> bool {
match self.selector {
None => true,
_ => false,
}
}
fn build_try_from_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
tempname: Ident,
_ident: Member,
_ty: Type,
_member: &Member,
_ty: &Type,
) -> Result<FieldParsePart> {
match self.selector {
Some((field_namespace, field_name)) => {
Some((ref field_namespace, ref field_name)) => {
let childiter = match field_name {
// namespace match only
None => quote! {
@ -290,16 +282,25 @@ impl ElementsField {
}
}
/// Construct an expression which consumes the ident `builder`, which must
/// be a minidom `Builder`, and returns it, modified in such a way that it
/// contains the field's data.
///
/// - `ident` must be an expression to consume the field's data. It is
/// evaluated exactly once.
/// - `ty` must be the field's type.
pub(super) fn build_into_element(self, ident: Expr, _ty: Type) -> Result<TokenStream> {
fn build_into_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
_member: &Member,
_ty: &Type,
access: Expr,
) -> Result<TokenStream> {
Ok(quote! {
builder.append_all(#ident.into_iter().map(|elem| ::xso::exports::minidom::Node::Element(elem)))
builder.append_all(#access.into_iter().map(|elem| ::xso::exports::minidom::Node::Element(elem)))
})
}
fn build_set_namespace(
&self,
_input: &Ident,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
Ok(TokenStream::default())
}
}

View File

@ -7,12 +7,12 @@ use syn::*;
use crate::error_message::{self, ParentRef};
use crate::meta::{Name, NameRef, NamespaceRef, StaticNamespace};
use super::FieldParsePart;
use super::{Field, FieldParsePart};
/// A field parsed from the presence of an empty XML child.
///
/// Maps to `#[xml(flag)]`.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct FlagField {
/// The XML namespace of the child element to look for.
namespace: StaticNamespace,
@ -70,30 +70,20 @@ impl FlagField {
name: name.into(),
})
}
}
/// Construct the code necessary to parse the child element from a
/// `minidom::Element` into a field.
///
/// - `name` should be the identifier or path of the containing compound
/// and is used to generate runtime error messages.
///
/// - `tempname` must be an identifier which the implementation can freely
/// use for a temporary variable related to parsing.
///
/// - `ident` must be the target struct member identifier or index, used
/// for error messages.
///
/// - `ty` must be the field's type. This must be `bool`.
pub(super) fn build_try_from_element(
self,
name: &ParentRef,
impl Field for FlagField {
fn build_try_from_element(
&self,
container_name: &ParentRef,
_container_namespace_expr: &Expr,
tempname: Ident,
ident: Member,
_ty: Type,
member: &Member,
_ty: &Type,
) -> Result<FieldParsePart> {
let field_name = self.name;
let field_namespace = self.namespace;
let duperr = error_message::on_duplicate_child(name, &ident);
let field_name = &self.name;
let field_namespace = &self.namespace;
let duperr = error_message::on_duplicate_child(container_name, member);
Ok(FieldParsePart {
tempinit: quote! {
let mut #tempname = false;
@ -115,23 +105,31 @@ impl FlagField {
})
}
/// Construct an expression which consumes the ident `builder`, which must
/// be a minidom `Builder`, and returns it, modified in such a way that it
/// contains the field's data.
///
/// - `ident` must be an expression to consume the field's data. It is
/// evaluated exactly once.
/// - `ty` must be the field's type. This type must implement
/// `Into<Option<Element>>`.
pub(super) fn build_into_element(self, ident: Expr, _ty: Type) -> Result<TokenStream> {
let child_name = self.name;
let child_namespace = self.namespace;
fn build_into_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
_member: &Member,
_ty: &Type,
access: Expr,
) -> Result<TokenStream> {
let child_name = &self.name;
let child_namespace = &self.namespace;
Ok(quote! {
if #ident {
if #access {
builder.append(::xso::exports::minidom::Node::Element(::xso::exports::minidom::Element::builder(#child_name, #child_namespace).build()))
} else {
builder
}
})
}
fn build_set_namespace(
&self,
_input: &Ident,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
Ok(TokenStream::default())
}
}

View File

@ -8,11 +8,34 @@ The main file of the module contains the outward, generic types covering all
use cases. The actual implementations for all but the trivial field kinds are
sorted into submodules.
*/
#[cfg(doc)]
pub(crate) mod attribute;
#[cfg(not(doc))]
mod attribute;
#[cfg(doc)]
pub(crate) mod child;
#[cfg(not(doc))]
mod child;
#[cfg(doc)]
pub(crate) mod element;
#[cfg(not(doc))]
mod element;
#[cfg(doc)]
pub(crate) mod flag;
#[cfg(not(doc))]
mod flag;
#[cfg(doc)]
pub(crate) mod namespace;
#[cfg(not(doc))]
mod namespace;
#[cfg(doc)]
pub(crate) mod text;
#[cfg(not(doc))]
mod text;
use proc_macro2::{Span, TokenStream};
@ -30,6 +53,92 @@ use self::flag::FlagField;
use self::namespace::NamespaceField;
use self::text::TextField;
pub(crate) trait Field: std::fmt::Debug {
/// Return true if and only if this field is a field collecting all XML
/// text of the element.
fn is_text(&self) -> bool {
false
}
/// Return true if and only if this field is a field collecting *all* XML
/// children of the element.
fn is_child_wildcard(&self) -> bool {
false
}
/// Return true if and only if this field is a field storing the namespace
/// of the XML element.
fn is_namespace(&self) -> bool {
false
}
/// Construct the code necessary to parse data from a `minidom::Element`
/// into the field.
///
/// - `container_name` should be the identifier or path of the containing
/// compound and is used to generate runtime error messages.
///
/// - `container_namespace_expr` may be an expression under which the
/// parent compound's `::xso::DynNamespaceEnum` value is
/// obtainable, if any. This is needed for fields and compounds
/// using `#[xml(namespace = super)]`.
///
/// - `tempname` must be an identifier which the implementation can freely
/// use for a temporary variable related to parsing.
///
/// - `member` must be the target struct member identifier or index, used
/// for error messages.
///
/// - `ty` must be the field's type.
fn build_try_from_element(
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
tempname: Ident,
member: &Member,
ty: &Type,
) -> Result<FieldParsePart>;
/// Construct an expression which consumes the identifier `builder`, which
/// must be a minidom `Builder`, and returns it, modified in such a way
/// that it contains the field's data.
///
/// - `container_name` should be the identifier or path of the containing
/// compound and is used to generate runtime error messages.
///
/// - `container_namespace_expr` may be an expression under which the
/// parent compound's `::xso::DynNamespaceEnum` value is
/// obtainable, if any. This is needed for fields and compounds
/// using `#[xml(namespace = super)]`.
///
/// - `member` must be the target struct member identifier or index, used
/// for error messages.
///
/// - `ty` must be the field's type.
///
/// - `access` must be an expression to consume the field's data. It is
/// evaluated exactly once.
fn build_into_element(
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
member: &Member,
ty: &Type,
access: Expr,
) -> Result<TokenStream>;
/// Construct the code necessary to update the namespace on the field.
///
/// - `input` must be the identifier of the variable holding the new
/// namespace to set.
///
/// - `ty` must be the field's type.
///
/// - `access` must be an expression which can be written to, which allows
/// updating the field's value.
fn build_set_namespace(&self, input: &Ident, ty: &Type, access: Expr) -> Result<TokenStream>;
}
/// Code slices necessary for parsing a single field.
#[derive(Default)]
pub(crate) struct FieldParsePart {
@ -68,8 +177,7 @@ pub(crate) struct FieldParsePart {
}
/// A XML namespace as declared on a field.
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone, Debug)]
pub(crate) enum FieldNamespace {
/// The namespace is a static string.
Static(
@ -97,49 +205,114 @@ impl From<FieldNamespace> for NamespaceRef {
}
}
/// Enumeration of possible XML data ↔ Rust field mappings.
///
/// This matches the processed `#[xml(..)]` metas on the corresponding enum
/// variant or struct fields.
///
/// This enum only covers the parsing logic; the surrounding metadata, such as
/// the field identifier and type, are contained in [`FieldDef`].
#[cfg_attr(feature = "debug", derive(Debug))]
pub(crate) enum FieldKind {
/// This field is parsed from an XML attribute (`#[xml(attribute)]`).
Attribute(AttributeField),
#[derive(Debug)]
pub(crate) struct Ignore;
/// This field is parsed from XML text content (`#[xml(text)]`).
Text(TextField),
impl Field for Ignore {
fn build_try_from_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
_tempname: Ident,
_member: &Member,
ty: &Type,
) -> Result<FieldParsePart> {
let ty_default = quote_spanned! {ty.span()=> <#ty as std::default::Default>::default};
Ok(FieldParsePart {
value: quote! { #ty_default() },
..FieldParsePart::default()
})
}
/// This field represents the parent compound's namespace
/// (`#[xml(namespace)]`).
///
/// See also
/// [`StructNamespace::Dyn`][`crate::structs::StructNamespace::Dyn`].
Namespace(NamespaceField),
fn build_into_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
_member: &Member,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
Ok(quote! { builder })
}
/// This field is parsed from an XML child using the `FromXml` /
/// `IntoXml` traits (`#[xml(child)]`, `#[xml(children)]`).
Child(ChildField),
/// This field is parsed from an XML child as raw `minidom::Element`
/// (`#[xml(element)]`).
Element(ElementField),
/// This field is parsed from multiple XML children as raw
/// `Vec<minidom::Element>` (`#[xml(elements)]`).
Elements(ElementsField),
/// This field represents the presence/absence of an empty XML child
/// (`#[xml(flag)]`).
Flag(FlagField),
/// This field is not parsed to/from XML.
Ignore,
fn build_set_namespace(
&self,
_input: &Ident,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
Ok(TokenStream::default())
}
}
impl FieldKind {
/// Construct a `Field` implementation by processing the options in the meta.
///
/// *Note*: Explicit type specifications are rejected by this function.
/// Those must have been taken out of the `meta` before calling this
/// function. The compile-time error message emitted by this function is
/// not very useful in this case, so if you do not want to support
/// explicit types, you should take them out at the call site and emit
/// a more useful error there.
fn new_field(
span: &Span,
field_ident: Option<&Ident>,
field_ty: &Type,
meta: XmlFieldMeta,
) -> Result<Box<dyn Field>> {
match meta {
XmlFieldMeta::Attribute {
name,
default_,
codec,
ty,
} => {
if let Some(ty) = ty {
return Err(Error::new_spanned(ty, "cannot set attribute type here"));
};
Ok(Box::new(AttributeField::new(
span,
field_ident,
name,
default_,
codec,
)?))
}
XmlFieldMeta::Child {
mode,
namespace,
name,
extract,
default_,
skip_if,
codec,
} => Ok(Box::new(ChildField::new(
span, mode, namespace, name, extract, default_, skip_if, codec, field_ty,
)?)),
XmlFieldMeta::Text { codec, ty } => {
if let Some(ty) = ty {
return Err(Error::new_spanned(ty, "cannot set text type here"));
};
Ok(Box::new(TextField::new(span, codec)?))
}
XmlFieldMeta::Namespace => Ok(Box::new(NamespaceField::new())),
XmlFieldMeta::Element {
namespace,
name,
default_,
} => Ok(Box::new(ElementField::new(
span, namespace, name, default_,
)?)),
XmlFieldMeta::Elements { namespace, name } => {
Ok(Box::new(ElementsField::new(span, namespace, name)?))
}
XmlFieldMeta::Flag { namespace, name } => {
Ok(Box::new(FlagField::new(span, namespace, name)?))
}
XmlFieldMeta::Ignore => Ok(Box::new(Ignore)),
}
}
/* impl FieldKind {
/// Return true if the field kind is equal to [`Self::Text`].
pub(crate) fn is_text(&self) -> bool {
match self {
@ -156,78 +329,11 @@ impl FieldKind {
_ => false,
}
}
/// Construct a `FieldKind` by processing the options in the meta.
///
/// *Note*: Explicit type specifications are rejected by this function.
/// Those must have been taken out of the `meta` before calling this
/// function. The compile-time error message emitted by this function is
/// not very useful in this case, so if you do not want to support
/// explicit types, you should take them out at the call site and emit
/// a more useful error there.
fn new(
span: &Span,
field_ident: Option<&Ident>,
field_ty: &Type,
meta: XmlFieldMeta,
) -> Result<Self> {
match meta {
XmlFieldMeta::Attribute {
name,
default_,
codec,
ty,
} => {
if let Some(ty) = ty {
return Err(Error::new_spanned(ty, "cannot set attribute type here"));
};
Ok(FieldKind::Attribute(AttributeField::new(
span,
field_ident,
name,
default_,
codec,
)?))
}
XmlFieldMeta::Child {
mode,
namespace,
name,
extract,
default_,
skip_if,
codec,
} => Ok(FieldKind::Child(ChildField::new(
span, mode, namespace, name, extract, default_, skip_if, codec, field_ty,
)?)),
XmlFieldMeta::Text { codec, ty } => {
if let Some(ty) = ty {
return Err(Error::new_spanned(ty, "cannot set text type here"));
};
Ok(FieldKind::Text(TextField::new(span, codec)?))
}
XmlFieldMeta::Namespace => Ok(FieldKind::Namespace(NamespaceField::new())),
XmlFieldMeta::Element {
namespace,
name,
default_,
} => Ok(FieldKind::Element(ElementField::new(
span, namespace, name, default_,
)?)),
XmlFieldMeta::Elements { namespace, name } => Ok(FieldKind::Elements(
ElementsField::new(span, namespace, name)?,
)),
XmlFieldMeta::Flag { namespace, name } => {
Ok(FieldKind::Flag(FlagField::new(span, namespace, name)?))
}
XmlFieldMeta::Ignore => Ok(FieldKind::Ignore),
}
}
}
} */
/// All data necessary to generate code to convert a Rust field to or from
/// XML.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct FieldDef {
/// The span of the `#[xml]` meta defining this field.
pub(crate) span: Span,
@ -240,7 +346,7 @@ pub(crate) struct FieldDef {
pub(crate) ty: Type,
/// The way the field is mapped to XML.
pub(crate) kind: FieldKind,
pub(crate) kind: Box<dyn Field>,
}
fn try_unwrap_option_type(ty: Type) -> Type {
@ -307,7 +413,7 @@ impl FieldDef {
let ty = ty
.or(single_extract_type.map(try_unwrap_option_type))
.unwrap_or(string_ty);
let kind = FieldKind::new(
let kind = new_field(
&span, None, // field_ident is always none here, we use unnamed fields.
&ty, extract,
)?;
@ -323,21 +429,22 @@ impl FieldDef {
}
/// Return a reference to the field's type, if and only if it is a
/// [`FieldKind::Namespace`].
/// [`NamespaceField`][`crate::field::namespace::NamespaceField`].
pub(crate) fn namespace_field_type(&self) -> Option<&Type> {
match self.kind {
FieldKind::Namespace(..) => Some(&self.ty),
_ => None,
if self.kind.is_namespace() {
Some(&self.ty)
} else {
None
}
}
/// Generate a [`FieldDef`] from a [`Field`].
/// Generate a [`FieldDef`] from a [`syn::Field`].
///
/// `index` must be the number of the field within the compound, starting
/// at zero. It is used only for unnamed fields.
///
/// This parses the attributes using [`XmlFieldMeta`].
pub(crate) fn from_field(field: &Field, index: u32) -> Result<Self> {
pub(crate) fn from_field(field: &syn::Field, index: u32) -> Result<Self> {
let mut meta: Option<(XmlFieldMeta, Span)> = None;
for attr in field.attrs.iter() {
if !attr.path().is_ident("xml") {
@ -375,7 +482,7 @@ impl FieldDef {
));
}
let kind = FieldKind::new(&span, field.ident.as_ref(), &field.ty, meta)?;
let kind = new_field(&span, field.ident.as_ref(), &field.ty, meta)?;
Ok(Self {
span,
@ -388,52 +495,20 @@ impl FieldDef {
/// Construct a [`FieldParsePart`] which creates the field's value from
/// XML.
pub(crate) fn build_try_from_element(
self,
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
) -> Result<FieldParsePart> {
let ident = self.ident;
let ty = self.ty;
let ident = &self.ident;
let ty = &self.ty;
let tempname = quote::format_ident!("__field_init_{}", ident);
match self.kind {
FieldKind::Attribute(field) => {
field.build_try_from_element(container_name, tempname, ident, ty)
}
FieldKind::Child(field) => field.build_try_from_element(
container_name,
tempname,
ident,
ty,
container_namespace_expr,
),
FieldKind::Text(field) => {
field.build_try_from_element(container_name, tempname, ident, ty)
}
FieldKind::Element(field) => {
field.build_try_from_element(container_name, tempname, ident, ty)
}
FieldKind::Elements(field) => {
field.build_try_from_element(container_name, tempname, ident, ty)
}
FieldKind::Flag(field) => {
field.build_try_from_element(container_name, tempname, ident, ty)
}
FieldKind::Namespace(field) => field.build_try_from_element(
container_name,
container_namespace_expr,
tempname,
ident,
ty,
),
FieldKind::Ignore => {
let ty_default =
quote_spanned! {ty.span()=> <#ty as std::default::Default>::default};
Ok(FieldParsePart {
value: quote! { #ty_default() },
..FieldParsePart::default()
})
}
}
self.kind.build_try_from_element(
container_name,
container_namespace_expr,
tempname,
ident,
ty,
)
}
/// Construct a [`TokenStream`] which propagates the parent's namespace
@ -446,11 +521,7 @@ impl FieldDef {
) -> Result<TokenStream> {
let member = access_field(self.ident.clone());
let ty = &self.ty;
match self.kind {
FieldKind::Child(ref field) => field.build_set_namespace(input, &member, &ty),
FieldKind::Namespace(ref field) => field.build_set_namespace(input, &member, &ty),
_ => Ok(quote! {}),
}
self.kind.build_set_namespace(input, ty, member)
}
/// Construct an expression which consumes the ident `builder` and returns
@ -463,29 +534,15 @@ impl FieldDef {
/// `TokenStream` is used so that the expressions returned by
/// `access_field` and the `builder` ident are accessible.
pub(crate) fn build_into_element(
self,
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
mut access_field: impl FnMut(Member) -> Expr,
) -> Result<TokenStream> {
let member = self.ident.clone();
let ident = access_field(self.ident);
let ty = self.ty;
match self.kind {
FieldKind::Attribute(field) => field.build_into_element(ident, ty),
FieldKind::Text(field) => field.build_into_element(ident, ty),
FieldKind::Child(field) => field.build_into_element(
container_name,
member,
ident,
ty,
container_namespace_expr,
),
FieldKind::Element(field) => field.build_into_element(ident, ty),
FieldKind::Elements(field) => field.build_into_element(ident, ty),
FieldKind::Flag(field) => field.build_into_element(ident, ty),
FieldKind::Namespace(field) => field.build_into_element(ident, ty),
FieldKind::Ignore => Ok(quote! { builder }),
}
let member = &self.ident;
let ident = access_field(member.clone());
let ty = &self.ty;
self.kind
.build_into_element(container_name, container_namespace_expr, member, ty, ident)
}
}

View File

@ -6,13 +6,16 @@ use syn::*;
use crate::error_message::ParentRef;
use super::FieldParsePart;
use super::{Field, FieldParsePart};
/// A field holding the dynamic namespace of a compound with
/// `#[xml(namespace = dyn)]`.
///
/// See also
/// [`StructNamespace::Dyn`][`crate::structs::StructNamespace::Dyn`].
///
/// Maps to `#[xml(namespace)]`.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct NamespaceField;
impl NamespaceField {
@ -20,6 +23,12 @@ impl NamespaceField {
pub(super) fn new() -> Self {
Self
}
}
impl Field for NamespaceField {
fn is_namespace(&self) -> bool {
true
}
/// Construct code which copies the value from the `namespace_tempname`
/// into the value of the field.
@ -27,13 +36,13 @@ impl NamespaceField {
/// The actual parsing is special, because it has to happen during type
/// matching, and happens in
/// [`Compound::build_try_from_element`][`crate::compound::Compound::build_try_from_element`].
pub(super) fn build_try_from_element(
self,
fn build_try_from_element(
&self,
_container_name: &ParentRef,
container_namespace_expr: &Expr,
_tempname: Ident,
_ident: Member,
_ty: Type,
_member: &Member,
_ty: &Type,
) -> Result<FieldParsePart> {
Ok(FieldParsePart {
value: quote! { #container_namespace_expr },
@ -41,15 +50,13 @@ impl NamespaceField {
})
}
/// Construct the code necessary to update the namespace on the field.
///
/// Actually, this is a no-op, because the actual implementation is in
/// This is a no-op, because the actual implementation is in
/// [`crate::compound::DynCompound::build_set_namespace`].
pub(super) fn build_set_namespace(
fn build_set_namespace(
&self,
_input: &Ident,
_member: &Expr,
_member_ty: &Type,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
// NOTE: does nothing because this is handled specially by
// Compound::build_set_namespace
@ -61,7 +68,14 @@ impl NamespaceField {
/// The actual implementation of the specialities of dynamic namespace
/// fields resides in
/// [`Compound::build_into_element`][`crate::compound::Compound::build_into_element`].
pub(super) fn build_into_element(self, _ident: Expr, _ty: Type) -> Result<TokenStream> {
fn build_into_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
_member: &Member,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
Ok(quote! {builder})
}
}

View File

@ -6,12 +6,12 @@ use syn::{spanned::Spanned, *};
use crate::error_message::ParentRef;
use super::FieldParsePart;
use super::{Field, FieldParsePart};
/// A field parsed from a XML text.
///
/// Maps to `#[xml(text)]`.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct TextField {
/// The codec implementation to use.
///
@ -32,18 +32,23 @@ impl TextField {
pub(super) fn new(_attr_span: &Span, codec: Option<Type>) -> Result<Self> {
Ok(Self { codec })
}
}
/// Construct the code necessary to parse the text from a
/// `minidom::Element` into a field.
pub(super) fn build_try_from_element(
self,
_name: &ParentRef,
impl Field for TextField {
fn is_text(&self) -> bool {
true
}
fn build_try_from_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
tempname: Ident,
_ident: Member,
ty: Type,
_member: &Member,
ty: &Type,
) -> Result<FieldParsePart> {
let decode = match self.codec {
Some(codec_ty) => {
Some(ref codec_ty) => {
let codec_ty_decode = quote_spanned! {codec_ty.span()=> <#codec_ty as ::xso::TextCodec::<#ty>>::decode};
quote! {
#codec_ty_decode(&residual.text())?
@ -67,26 +72,26 @@ impl TextField {
})
}
/// Construct an expression which consumes the ident `builder`, which must
/// be a minidom `Builder`, and returns it, modified in such a way that it
/// contains the field's data.
///
/// - `ident` must be an expression to consume the field's data. It is
/// evaluated exactly once.
/// - `ty` must be the field's type.
pub(super) fn build_into_element(self, ident: Expr, ty: Type) -> Result<TokenStream> {
fn build_into_element(
&self,
_container_name: &ParentRef,
_container_namespace_expr: &Expr,
_member: &Member,
ty: &Type,
access: Expr,
) -> Result<TokenStream> {
let encode = match self.codec {
Some(codec_ty) => {
Some(ref codec_ty) => {
let codec_ty_encode = quote_spanned! {codec_ty.span()=> <#codec_ty as ::xso::TextCodec::<#ty>>::encode};
quote! {
#codec_ty_encode(#ident).unwrap_or_else(String::new)
#codec_ty_encode(#access).unwrap_or_else(String::new)
}
}
None => {
let ty_into_xml_text =
quote_spanned! {ty.span()=> <#ty as ::xso::IntoXmlText>::into_xml_text};
quote! {
#ty_into_xml_text(#ident)
#ty_into_xml_text(#access)
}
}
};
@ -102,4 +107,13 @@ impl TextField {
}
})
}
fn build_set_namespace(
&self,
_input: &Ident,
_ty: &Type,
_access: Expr,
) -> Result<TokenStream> {
Ok(TokenStream::default())
}
}

View File

@ -186,8 +186,7 @@ impl<'a> MetaParse for ParseExtracts<'a> {
}
/// Represents a boolean flag from a `#[xml(..)]` attribute meta.
#[derive(Clone, Copy)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone, Copy, Debug)]
pub(crate) enum Flag {
/// The flag is not set.
Absent,
@ -226,8 +225,7 @@ impl<T: Spanned> From<T> for Flag {
}
/// A flag with an optional value.
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone, Debug)]
pub(crate) enum FlagOr<T> {
/// The flag is not set.
Absent,
@ -290,7 +288,7 @@ pub(crate) type StaticNamespace = Path;
///
/// XML namespaces can be configured in different ways, which are
/// distinguished in this enum.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) enum NamespaceRef {
/// A dynamic namespace, expressed as `#[xml(namespace = dyn)]`.
///
@ -355,7 +353,7 @@ pub(crate) type NameRef = LitStr;
///
/// This enum, unlike when passing around the XML name as a string, preserves
/// the original tokens, which is useful for better error messages.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) enum Name {
/// Represented as a string literal.
Lit(LitStr),
@ -417,8 +415,7 @@ impl ToTokens for Name {
}
/// Struct containing namespace and name matchers.
#[derive(Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Default, Debug)]
pub(crate) struct NodeFilterMeta {
/// The value of the `namespace` option.
pub(crate) namespace: Option<NamespaceRef>,
@ -460,7 +457,7 @@ impl NodeFilterMeta {
/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
///
/// This is the counterpart to [`XmlFieldMeta`].
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct XmlCompoundMeta {
/// The span of the `#[xml(..)]` meta from which this was parsed.
///
@ -569,19 +566,12 @@ impl XmlCompoundMeta {
ParseValue("normalize_with", &mut normalize_with),
ParseValue("normalise_with", &mut normalize_with),
ParseNodeFilter("element", &mut element),
ParseNodeFilter("wrapped", &mut wrapped),
ParseValue("on_unknown_attribute", &mut on_unknown_attribute),
ParseValue("on_unknown_child", &mut on_unknown_child),
)
})?;
#[cfg(not(feature = "debug"))]
if let Flag::Present(debug) = debug {
return Err(Error::new(
debug,
"`debug` is only allowed if the macros were built with --feature debug",
));
}
Ok(Self {
span: attr.span(),
namespace,
@ -651,7 +641,7 @@ impl XmlCompoundMeta {
/// `default` flag, `type` and `codec` option.
///
/// These three are used in several places.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
struct AttributeMeta {
/// The value assigned to `namespace` inside a potentially nested
/// `#[xml(..)]`, if any.
@ -762,7 +752,7 @@ impl AttributeMeta {
}
/// Mode for destructured child collection.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) enum ChildMode {
/// The field represents a single XML child element.
Single,
@ -774,15 +764,15 @@ pub(crate) enum ChildMode {
/// Contents of an `#[xml(..)]` attribute on a field.
///
/// This is the counterpart to [`XmlCompoundMeta`].
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) enum XmlFieldMeta {
/// Maps the field to an XML attribute.
///
/// Maps to the `#[xml(attribute)]`, `#[xml(attribute = ..)]` and
/// `#[xml(attribute(..))]` Rust syntaxes.
///
/// Gets transformed into [`crate::field::FieldKind::Attribute`] in later
/// processing stages.
/// Gets transformed into [`crate::field::attribute::AttributeField`] in
/// later processing stages.
Attribute {
/// Contents of the `name = ..` option or value assigned to
/// `attribute` in the shorthand syntax.
@ -803,7 +793,7 @@ pub(crate) enum XmlFieldMeta {
/// Maps to the `#[xml(child)]`, `#[xml(child(..))]`, `#[xml(children)]`,
/// and `#[xml(children(..)]` Rust syntaxes.
///
/// Gets transformed into [`crate::field::FieldKind::Child`] in later
/// Gets transformed into [`crate::field::child::ChildField`] in later
/// processing stages.
Child {
/// Distinguishes between `#[xml(child)]` and `#[xml(children)]`
@ -834,11 +824,17 @@ pub(crate) enum XmlFieldMeta {
/// See also [`crate::structs::StructNamespace::Dyn`].
///
/// Maps to the `#[xml(namespace)]` Rust syntax.
///
/// Gets transformed into [`crate::field::namespace::NamespaceField`] in
/// later processing stages.
Namespace,
/// Maps the field to the text contents of the XML element.
///
/// Maps to the `#[xml(text)]` Rust syntax.
///
/// Gets transformed into [`crate::field::text::TextField`] in later
/// processing stages.
Text {
/// Contents of the `codec = ..` option.
codec: Option<Type>,
@ -852,6 +848,9 @@ pub(crate) enum XmlFieldMeta {
///
/// Maps to the `#[xml(elements)]` and `#[xml(elements(..))]` Rust
/// syntaxes.
///
/// Gets transformed into [`crate::field::element::ElementsField`] in
/// later processing stages.
Elements {
/// Contents of the `namespace = ..` option.
namespace: Option<NamespaceRef>,
@ -864,6 +863,9 @@ pub(crate) enum XmlFieldMeta {
/// it into a Rust struct.
///
/// Maps to the `#[xml(element(..))]` Rust syntax.
///
/// Gets transformed into [`crate::field::element::ElementField`] in
/// later processing stages.
Element {
/// Contents of the `namespace = ..` option.
namespace: Option<NamespaceRef>,
@ -878,6 +880,9 @@ pub(crate) enum XmlFieldMeta {
/// Maps the field to the presence of an empty XML child element.
///
/// Maps to the `#[xml(flag(..))]` Rust syntax.
///
/// Gets transformed into [`crate::field::flag::FlagField`] in later
/// processing stages.
Flag {
/// Contents of the `namespace = ..` option.
namespace: Option<NamespaceRef>,
@ -889,6 +894,9 @@ pub(crate) enum XmlFieldMeta {
/// Ignores the field for the purpose of XML processing.
///
/// Maps to the `#[xml(ignore)]` Rust syntax.
///
/// Gets transformed into [`crate::field::Ignore`] in later processing
/// stages.
Ignore,
}

View File

@ -19,7 +19,7 @@ use crate::meta::{
};
/// A XML namespace as declared on a struct.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) enum StructNamespace {
/// The namespace is a static string.
Static(
@ -28,8 +28,8 @@ pub(crate) enum StructNamespace {
),
/// Instead of a fixed namespace, the namespace is dynamic. The allowed
/// values are determined by a field with
/// [`FieldKind::Namespace`][`crate::field::FieldKind::Namespace`] kind
/// values are determined by a
/// [`NamespaceField`][`crate::field::namespace::NamespaceField`]
/// (declared using `#[xml(namespace)]`).
Dyn {
/// The `dyn` token from the `#[xml(namespace = dyn)]` meta.
@ -47,7 +47,7 @@ pub(crate) enum StructNamespace {
/// Represent a selector for element-transparent structs.
///
/// See also [`StructInner::Element`].
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) enum ElementSelector {
/// Any element will be accepted.
///
@ -109,7 +109,7 @@ impl ElementSelector {
/// If the `minidom::Element` in `residual` matches the selector, the
/// token stream will evaluate to true. Otherwise, it will evaluate to
/// false.
pub(crate) fn build_test(self, residual: &Ident) -> TokenStream {
pub(crate) fn build_test(&self, residual: &Ident) -> TokenStream {
match self {
Self::Any => quote! { true },
Self::ByName(name) => quote! {
@ -130,7 +130,7 @@ impl ElementSelector {
/// This contains all data necessary for the matching logic, but does not
/// include validation/preparation of the data. The latter is handled by
/// [`StructDef`].
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) enum StructInner {
/// Single-field tuple-like struct declared with `#[xml(transparent)]`.
///
@ -660,7 +660,7 @@ fn make_accessor(struct_path: Path) -> impl FnMut(Member) -> Expr {
}
/// Represent a struct.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Debug)]
pub(crate) struct StructDef {
/// The `validate` value, if set on the struct.
///