Detailed changes
@@ -183,3 +183,48 @@ fn namespace_lit_roundtrip() {
};
roundtrip_full::<NamespaceLit>("<baz xmlns='urn:example:ns2'/>");
}
+
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = "attr")]
+struct RequiredAttribute {
+ #[xml(attribute)]
+ foo: String,
+}
+
+#[test]
+fn required_attribute_roundtrip() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::<RequiredAttribute>("<attr xmlns='urn:example:ns1' foo='bar'/>");
+}
+
+#[test]
+fn required_attribute_positive() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ let data = parse_str::<RequiredAttribute>("<attr xmlns='urn:example:ns1' foo='bar'/>").unwrap();
+ assert_eq!(data.foo, "bar");
+}
+
+#[test]
+fn required_attribute_missing() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::<RequiredAttribute>("<attr xmlns='urn:example:ns1'/>") {
+ Err(::xso::error::FromElementError::Invalid(::xso::error::Error::Other(e)))
+ if e.contains("Required attribute field") && e.contains("missing") =>
+ {
+ ()
+ }
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
@@ -7,29 +7,41 @@
//! Handling of the insides of compound structures (structs and enum variants)
use proc_macro2::{Span, TokenStream};
-use quote::{quote, ToTokens};
+use quote::quote;
use syn::*;
+use crate::error_message::ParentRef;
+use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit};
+use crate::scope::{mangle_member, FromEventsScope, IntoEventsScope};
use crate::state::{FromEventsSubmachine, IntoEventsSubmachine, State};
use crate::types::qname_ty;
/// A struct or enum variant's contents.
-pub(crate) struct Compound;
+pub(crate) struct Compound {
+ /// The fields of this compound.
+ fields: Vec<FieldDef>,
+}
impl Compound {
/// Construct a compound from fields.
pub(crate) fn from_fields(compound_fields: &Fields) -> Result<Self> {
- match compound_fields {
- Fields::Unit => (),
- other => {
- return Err(Error::new_spanned(
- other,
- "cannot derive on non-unit struct (yet!)",
- ))
- }
+ let mut fields = Vec::with_capacity(compound_fields.len());
+ for (i, field) in compound_fields.iter().enumerate() {
+ let index = match i.try_into() {
+ Ok(v) => v,
+ // we are converting to u32, are you crazy?!
+ // (u32, because syn::Member::Index needs that.)
+ Err(_) => {
+ return Err(Error::new_spanned(
+ field,
+ "okay, mate, that are way too many fields. get your life together.",
+ ))
+ }
+ };
+ fields.push(FieldDef::from_field(field, index)?);
}
- Ok(Self)
+ Ok(Self { fields })
}
/// Make and return a set of states which is used to construct the target
@@ -40,9 +52,12 @@ impl Compound {
pub(crate) fn make_from_events_statemachine(
&self,
state_ty_ident: &Ident,
- output_cons: &Path,
+ output_name: &ParentRef,
state_prefix: &str,
) -> Result<FromEventsSubmachine> {
+ let scope = FromEventsScope::new();
+ let FromEventsScope { ref attrs, .. } = scope;
+
let default_state_ident = quote::format_ident!("{}Default", state_prefix);
let builder_data_ident = quote::format_ident!("__data");
let builder_data_ty: Type = TypePath {
@@ -52,9 +67,44 @@ impl Compound {
.into();
let mut states = Vec::new();
- let readable_name = output_cons.to_token_stream().to_string();
- let unknown_attr_err = format!("Unknown attribute in {} element.", readable_name);
- let unknown_child_err = format!("Unknown child in {} element.", readable_name);
+ let mut builder_data_def = TokenStream::default();
+ let mut builder_data_init = TokenStream::default();
+ let mut output_cons = TokenStream::default();
+
+ for field in self.fields.iter() {
+ let member = field.member();
+ let builder_field_name = mangle_member(member);
+ let part = field.make_builder_part(&scope, &output_name)?;
+
+ match part {
+ FieldBuilderPart::Init {
+ value: FieldTempInit { ty, init },
+ } => {
+ builder_data_def.extend(quote! {
+ #builder_field_name: #ty,
+ });
+
+ builder_data_init.extend(quote! {
+ #builder_field_name: #init,
+ });
+
+ output_cons.extend(quote! {
+ #member: #builder_data_ident.#builder_field_name,
+ });
+ }
+ }
+ }
+
+ let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
+ let unknown_child_err = format!("Unknown child in {}.", output_name);
+
+ let output_cons = match output_name {
+ ParentRef::Named(ref path) => {
+ quote! {
+ #path { #output_cons }
+ }
+ }
+ };
states.push(State::new_with_builder(
default_state_ident.clone(),
@@ -86,18 +136,21 @@ impl Compound {
Ok(FromEventsSubmachine {
defs: quote! {
- struct #builder_data_ty;
+ struct #builder_data_ty {
+ #builder_data_def
+ }
},
states,
init: quote! {
- if attrs.len() > 0 {
+ let #builder_data_ident = #builder_data_ty {
+ #builder_data_init
+ };
+ if #attrs.len() > 0 {
return ::core::result::Result::Err(::xso::error::Error::Other(
#unknown_attr_err,
).into());
}
- ::core::result::Result::Ok(#state_ty_ident::#default_state_ident {
- #builder_data_ident: #builder_data_ty,
- })
+ ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
},
})
}
@@ -116,23 +169,54 @@ impl Compound {
input_name: &Path,
state_prefix: &str,
) -> Result<IntoEventsSubmachine> {
+ let scope = IntoEventsScope::new();
+ let IntoEventsScope { ref attrs, .. } = scope;
+
let start_element_state_ident = quote::format_ident!("{}StartElement", state_prefix);
let end_element_state_ident = quote::format_ident!("{}EndElement", state_prefix);
let name_ident = quote::format_ident!("name");
let mut states = Vec::new();
+ let mut init_body = TokenStream::default();
+ let mut destructure = TokenStream::default();
+ let mut start_init = TokenStream::default();
+
states.push(
State::new(start_element_state_ident.clone())
- .with_field(&name_ident, &qname_ty(Span::call_site()))
- .with_impl(quote! {
- ::core::option::Option::Some(::xso::exports::rxml::Event::StartElement(
- ::xso::exports::rxml::parser::EventMetrics::zero(),
- #name_ident,
- ::xso::exports::rxml::AttrMap::new(),
- ))
- }),
+ .with_field(&name_ident, &qname_ty(Span::call_site())),
);
+ for field in self.fields.iter() {
+ let member = field.member();
+ let bound_name = mangle_member(member);
+ let part = field.make_iterator_part(&scope, &bound_name)?;
+
+ match part {
+ FieldIteratorPart::Header { setter } => {
+ destructure.extend(quote! {
+ #member: #bound_name,
+ });
+ init_body.extend(setter);
+ start_init.extend(quote! {
+ #bound_name,
+ });
+ states[0].add_field(&bound_name, field.ty());
+ }
+ }
+ }
+
+ states[0].set_impl(quote! {
+ {
+ let mut #attrs = ::xso::exports::rxml::AttrMap::new();
+ #init_body
+ ::core::option::Option::Some(::xso::exports::rxml::Event::StartElement(
+ ::xso::exports::rxml::parser::EventMetrics::zero(),
+ #name_ident,
+ #attrs,
+ ))
+ }
+ });
+
states.push(
State::new(end_element_state_ident.clone()).with_impl(quote! {
::core::option::Option::Some(::xso::exports::rxml::Event::EndElement(
@@ -145,10 +229,10 @@ impl Compound {
defs: TokenStream::default(),
states,
destructure: quote! {
- #input_name
+ #input_name { #destructure }
},
init: quote! {
- Self::#start_element_state_ident { #name_ident }
+ Self::#start_element_state_ident { #name_ident, #start_init }
},
})
}
@@ -0,0 +1,81 @@
+// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+//! Infrastructure for contextual error messages
+
+use std::fmt;
+
+use syn::*;
+
+/// Reference to a compound field's parent
+///
+/// This reference can be converted to a hopefully-useful human-readable
+/// string via [`std::fmt::Display`].
+#[derive(Clone, Debug)]
+pub(super) enum ParentRef {
+ /// The parent is addressable by a path, e.g. a struct type or enum
+ /// variant.
+ Named(Path),
+}
+
+impl From<Path> for ParentRef {
+ fn from(other: Path) -> Self {
+ Self::Named(other)
+ }
+}
+
+impl From<&Path> for ParentRef {
+ fn from(other: &Path) -> Self {
+ Self::Named(other.clone())
+ }
+}
+
+impl fmt::Display for ParentRef {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::Named(name) => {
+ let mut first = true;
+ for segment in name.segments.iter() {
+ if !first || name.leading_colon.is_some() {
+ write!(f, "::")?;
+ }
+ first = false;
+ write!(f, "{}", segment.ident)?;
+ }
+ write!(f, " element")
+ }
+ }
+ }
+}
+
+/// Ephemeral struct to create a nice human-readable representation of
+/// [`syn::Member`].
+///
+/// It implements [`std::fmt::Display`] for that purpose and is otherwise of
+/// little use.
+#[repr(transparent)]
+struct FieldName<'x>(&'x Member);
+
+impl fmt::Display for FieldName<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ Member::Named(v) => write!(f, "field '{}'", v),
+ Member::Unnamed(v) => write!(f, "unnamed field {}", v.index),
+ }
+ }
+}
+
+/// Create a string error message for a missing attribute.
+///
+/// `parent_name` should point at the compound which is being parsed and
+/// `field` should be the field to which the attribute belongs.
+pub(super) fn on_missing_attribute(parent_name: &ParentRef, field: &Member) -> String {
+ format!(
+ "Required attribute {} on {} missing.",
+ FieldName(&field),
+ parent_name
+ )
+}
@@ -0,0 +1,215 @@
+// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+//! Compound (struct or enum variant) field types
+
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{spanned::Spanned, *};
+
+use rxml_validation::NcName;
+
+use crate::error_message::{self, ParentRef};
+use crate::meta::{NameRef, XmlFieldMeta};
+use crate::scope::{FromEventsScope, IntoEventsScope};
+
+/// Code slices necessary for declaring and initializing a temporary variable
+/// for parsing purposes.
+pub(crate) struct FieldTempInit {
+ /// The type of the temporary variable.
+ pub(crate) ty: Type,
+
+ /// The initializer for the temporary variable.
+ pub(crate) init: TokenStream,
+}
+
+/// Describe how a struct or enum variant's member is parsed from XML data.
+///
+/// This struct is returned from [`FieldDef::make_builder_part`] and
+/// contains code snippets and instructions for
+/// [`Compound::make_from_events_statemachine`][`crate::compound::Compound::make_from_events_statemachine`]
+/// to parse the field's data from XML.
+pub(crate) enum FieldBuilderPart {
+ /// Parse a field from the item's element's start event.
+ Init {
+ /// Expression and type which extracts the field's data from the
+ /// element's start event.
+ value: FieldTempInit,
+ },
+}
+
+/// Describe how a struct or enum variant's member is converted to XML data.
+///
+/// This struct is returned from [`FieldDef::make_iterator_part`] and
+/// contains code snippets and instructions for
+/// [`Compound::make_into_events_statemachine`][`crate::compound::Compound::make_into_events_statemachine`]
+/// to convert the field's data into XML.
+pub(crate) enum FieldIteratorPart {
+ /// The field is emitted as part of StartElement.
+ Header {
+ /// A sequence of statements which updates the temporary variables
+ /// during the StartElement event's construction, consuming the
+ /// field's value.
+ setter: TokenStream,
+ },
+}
+
+/// Specify how the field is mapped to XML.
+enum FieldKind {
+ /// The field maps to an attribute.
+ Attribute {
+ /// The XML name of the attribute.
+ xml_name: NameRef,
+ },
+}
+
+impl FieldKind {
+ /// Construct a new field implementation from the meta attributes.
+ ///
+ /// `field_ident` is, for some field types, used to infer an XML name if
+ /// it is not specified explicitly.
+ fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> {
+ match meta {
+ XmlFieldMeta::Attribute { span } => {
+ let Some(field_ident) = field_ident else {
+ return Err(Error::new(
+ span,
+ "attribute extraction not supported on unnamed fields",
+ ));
+ };
+
+ let xml_name = match NcName::try_from(field_ident.to_string()) {
+ Ok(v) => v,
+ Err(e) => {
+ return Err(Error::new(
+ field_ident.span(),
+ format!("invalid XML attribute name: {}", e),
+ ))
+ }
+ };
+
+ Ok(Self::Attribute {
+ xml_name: NameRef::Literal {
+ span: field_ident.span(),
+ value: xml_name,
+ },
+ })
+ }
+ }
+ }
+}
+
+/// Definition of a single field in a compound.
+///
+/// See [`Compound`][`crate::compound::Compound`] for more information on
+/// compounds in general.
+pub(crate) struct FieldDef {
+ /// The member identifying the field.
+ member: Member,
+
+ /// The type of the field.
+ ty: Type,
+
+ /// The way the field is mapped to XML.
+ kind: FieldKind,
+}
+
+impl FieldDef {
+ /// Create a new field definition from its declaration.
+ ///
+ /// The `index` must be the zero-based index of the field even for named
+ /// fields.
+ pub(crate) fn from_field(field: &syn::Field, index: u32) -> Result<Self> {
+ let field_span = field.span();
+ let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?;
+
+ let (member, ident) = match field.ident.as_ref() {
+ Some(v) => (Member::Named(v.clone()), Some(v)),
+ None => (
+ Member::Unnamed(Index {
+ index,
+ span: field_span,
+ }),
+ None,
+ ),
+ };
+
+ let ty = field.ty.clone();
+
+ Ok(Self {
+ member,
+ ty,
+ kind: FieldKind::from_meta(meta, ident)?,
+ })
+ }
+
+ /// Access the [`syn::Member`] identifying this field in the original
+ /// type.
+ pub(crate) fn member(&self) -> &Member {
+ &self.member
+ }
+
+ /// Access the field's type.
+ pub(crate) fn ty(&self) -> &Type {
+ &self.ty
+ }
+
+ /// Construct the builder pieces for this field.
+ ///
+ /// `container_name` must be a reference to the compound's type, so that
+ /// it can be used for error messages.
+ pub(crate) fn make_builder_part(
+ &self,
+ scope: &FromEventsScope,
+ container_name: &ParentRef,
+ ) -> Result<FieldBuilderPart> {
+ match self.kind {
+ FieldKind::Attribute { ref xml_name } => {
+ let FromEventsScope { ref attrs, .. } = scope;
+
+ let missing_msg = error_message::on_missing_attribute(container_name, &self.member);
+
+ return Ok(FieldBuilderPart::Init {
+ value: FieldTempInit {
+ ty: self.ty.clone(),
+ init: quote! {
+ match #attrs.remove(::xso::exports::rxml::Namespace::none(), #xml_name) {
+ ::core::option::Option::Some(v) => v,
+ ::core::option::Option::None => return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()),
+ }
+ },
+ },
+ });
+ }
+ }
+ }
+
+ /// Construct the iterator pieces for this field.
+ ///
+ /// `bound_name` must be the name to which the field's value is bound in
+ /// the iterator code.
+ pub(crate) fn make_iterator_part(
+ &self,
+ scope: &IntoEventsScope,
+ bound_name: &Ident,
+ ) -> Result<FieldIteratorPart> {
+ match self.kind {
+ FieldKind::Attribute { ref xml_name } => {
+ let IntoEventsScope { ref attrs, .. } = scope;
+
+ return Ok(FieldIteratorPart::Header {
+ setter: quote! {
+ #attrs.insert(
+ ::xso::exports::rxml::Namespace::NONE,
+ #xml_name.to_owned(),
+ #bound_name,
+ );
+ },
+ });
+ }
+ }
+ }
+}
@@ -26,7 +26,10 @@ use quote::quote;
use syn::*;
mod compound;
+mod error_message;
+mod field;
mod meta;
+mod scope;
mod state;
mod structs;
mod types;
@@ -11,7 +11,7 @@
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
-use syn::{spanned::Spanned, *};
+use syn::{meta::ParseNestedMeta, spanned::Spanned, *};
use rxml_validation::NcName;
@@ -194,3 +194,101 @@ impl XmlCompoundMeta {
}
}
}
+
+/// Contents of an `#[xml(..)]` attribute on a struct or enum variant member.
+#[derive(Debug)]
+pub(crate) enum XmlFieldMeta {
+ Attribute {
+ /// The span of the `#[xml(attribute)]` meta from which this was parsed.
+ ///
+ /// This is useful for error messages.
+ span: Span,
+ },
+}
+
+impl XmlFieldMeta {
+ /// Parse a `#[xml(attribute(..))]` meta.
+ fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
+ Ok(Self::Attribute {
+ span: meta.path.span(),
+ })
+ }
+
+ /// Parse [`Self`] from a nestd meta, switching on the identifier
+ /// of that nested meta.
+ fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
+ if meta.path.is_ident("attribute") {
+ Self::attribute_from_meta(meta)
+ } else {
+ Err(Error::new_spanned(meta.path, "unsupported field meta"))
+ }
+ }
+
+ /// Parse an `#[xml(..)]` meta on a field.
+ ///
+ /// This switches based on the first identifier within the `#[xml(..)]`
+ /// meta and generates an enum variant accordingly.
+ ///
+ /// Only a single nested meta is allowed; more than one will be
+ /// rejected with an appropriate compile-time error.
+ ///
+ /// If no meta is contained at all, a compile-time error is generated.
+ ///
+ /// Undefined options or options with incompatible values are rejected
+ /// with an appropriate compile-time error.
+ pub(crate) fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
+ let mut result: Option<Self> = None;
+
+ attr.parse_nested_meta(|meta| {
+ if result.is_some() {
+ return Err(Error::new_spanned(
+ meta.path,
+ "multiple field type specifiers are not supported",
+ ));
+ }
+
+ result = Some(Self::parse_from_meta(meta)?);
+ Ok(())
+ })?;
+
+ if let Some(result) = result {
+ Ok(result)
+ } else {
+ Err(Error::new_spanned(
+ attr,
+ "missing field type specifier within `#[xml(..)]`",
+ ))
+ }
+ }
+
+ /// Find and parse a `#[xml(..)]` meta on a field.
+ ///
+ /// This invokes [`Self::parse_from_attribute`] internally on the first
+ /// encountered `#[xml(..)]` meta.
+ ///
+ /// If not exactly one `#[xml(..)]` meta is encountered, an error is
+ /// returned. The error is spanned to `err_span`.
+ pub(crate) fn parse_from_attributes(attrs: &[Attribute], err_span: &Span) -> Result<Self> {
+ let mut result: Option<Self> = None;
+ for attr in attrs {
+ if !attr.path().is_ident("xml") {
+ continue;
+ }
+
+ if result.is_some() {
+ return Err(Error::new_spanned(
+ attr,
+ "only one #[xml(..)] attribute per field allowed.",
+ ));
+ }
+
+ result = Some(Self::parse_from_attribute(attr)?);
+ }
+
+ if let Some(result) = result {
+ Ok(result)
+ } else {
+ Err(Error::new(*err_span, "missing #[xml(..)] meta on field"))
+ }
+ }
+}
@@ -0,0 +1,73 @@
+// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+//! Identifiers used within generated code.
+
+use proc_macro2::Span;
+use syn::*;
+
+/// Container struct for various identifiers used throughout the parser code.
+///
+/// This struct is passed around from the [`crate::compound::Compound`]
+/// downward to the code generators in order to ensure that everyone is on the
+/// same page about which identifiers are used for what.
+///
+/// The recommended usage is to bind the names which are needed into the local
+/// scope like this:
+///
+/// ```text
+/// # let scope = FromEventsScope::new();
+/// let FromEventsScope {
+/// ref attrs,
+/// ..
+/// } = scope;
+/// ```
+pub(crate) struct FromEventsScope {
+ /// Accesses the `AttrMap` from code in
+ /// [`crate::field::FieldBuilderPart::Init`].
+ pub(crate) attrs: Ident,
+}
+
+impl FromEventsScope {
+ /// Create a fresh scope with all necessary identifiers.
+ pub(crate) fn new() -> Self {
+ // Sadly, `Ident::new` is not `const`, so we have to create even the
+ // well-known identifiers from scratch all the time.
+ Self {
+ attrs: Ident::new("attrs", Span::call_site()),
+ }
+ }
+}
+
+/// Container struct for various identifiers used throughout the generator
+/// code.
+///
+/// This struct is passed around from the [`crate::compound::Compound`]
+/// downward to the code generators in order to ensure that everyone is on the
+/// same page about which identifiers are used for what.
+///
+/// See [`FromEventsScope`] for recommendations on the usage.
+pub(crate) struct IntoEventsScope {
+ /// Accesses the `AttrMap` from code in
+ /// [`crate::field::FieldIteratorPart::Header`].
+ pub(crate) attrs: Ident,
+}
+
+impl IntoEventsScope {
+ /// Create a fresh scope with all necessary identifiers.
+ pub(crate) fn new() -> Self {
+ Self {
+ attrs: Ident::new("attrs", Span::call_site()),
+ }
+ }
+}
+
+pub(crate) fn mangle_member(member: &Member) -> Ident {
+ match member {
+ Member::Named(member) => quote::format_ident!("f{}", member),
+ Member::Unnamed(member) => quote::format_ident!("f_u{}", member.index),
+ }
+}
@@ -88,6 +88,13 @@ impl State {
self.advance_body = body;
self
}
+
+ /// Override the current `advance` implementation of this state.
+ ///
+ /// This is an in-place version of [`Self::with_impl`].
+ pub(crate) fn set_impl(&mut self, body: TokenStream) {
+ self.advance_body = body;
+ }
}
/// A partial [`FromEventsStateMachine`] which only covers the builder for a
@@ -96,7 +96,7 @@ impl StructDef {
.inner
.make_from_events_statemachine(
&state_ty_ident,
- &target_ty_ident.clone().into(),
+ &Path::from(target_ty_ident.clone()).into(),
"Struct",
)?
.with_augmented_init(|init| {
@@ -17,15 +17,20 @@ assert_eq!(foo, Foo);
## Attributes
-The derive macros need to know which XML namespace and name the elements it
-is supposed have. This must be specified via key-value pairs on the type the
-derive macro is invoked on. These are specified as Rust attributes. In order
-to disambiguate between XML attributes and Rust attributes, we are going to
-refer to Rust attributes using the term *meta* instead, which is consistent
-with the Rust language reference calling that syntax construct *meta*.
+The derive macros need additional information, such as XML namespaces and
+names to match. This must be specified via key-value pairs on the type or
+fields the derive macro is invoked on. These key-value pairs are specified as
+Rust attributes. In order to disambiguate between XML attributes and Rust
+attributes, we are going to refer to Rust attributes using the term *meta*
+instead, which is consistent with the Rust language reference calling that
+syntax construct *meta*.
All key-value pairs interpreted by these derive macros must be wrapped in a
-`#[xml( ... )]` *meta*. The following keys are defined on structs:
+`#[xml( ... )]` *meta*.
+
+### Struct meta
+
+The following keys are defined on structs:
| Key | Value type | Description |
| --- | --- | --- |
@@ -43,16 +48,20 @@ and cannot be overridden. The following will thus not compile:
struct Foo;
```
-## Limitations
+### Field meta
-Supports only empty structs currently. For example, the following will not
-work:
+For fields, the *meta* consists of a nested meta inside the `#[xml(..)]` meta,
+the identifier of which controls *how* the field is mapped to XML, while the
+contents control the parameters of that mapping.
-```compile_fail
-# use xso::FromXml;
-#[derive(FromXml, Debug, PartialEq)]
-#[xml(namespace = "urn:example", name = "foo")]
-struct Foo {
- some_field: String,
-}
-```
+The following mapping types are defined:
+
+| Type | Description |
+| --- | --- |
+| [`attribute`](#attribute-meta) | Map the field to an XML attribute on the struct's element |
+
+#### `attribute` meta
+
+The `attribute` meta does not support additional parameters. The field it is
+used on is mapped to an XML attribute of the same name and must be of type
+[`String`].