Derive Macro xso::FromXml

source ·
#[derive(FromXml)]
{
    // Attributes available to this derive:
    #[xml]
}
Available on crate feature macros only.
Expand description

§Make a struct or enum parseable from XML

This derives the FromXml trait on a struct or enum. It is the counterpart to AsXml.

§Example

#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "foo")]
struct Foo;

let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example'/>").unwrap();
assert_eq!(foo, Foo);

§Attributes

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 values associated with the keys may be of different types, defined as such:

  • path: A Rust path, like some_crate::foo::Bar. Note that foo on its own is also a path.
  • string literal: A string literal, like "hello world!".
  • type: A Rust type.
  • expression: A Rust expression.
  • ident: A Rust identifier.
  • nested: The meta is followed by parentheses, inside of which meta-specific additional keys are present.
  • flag: Has no value. The key’s mere presence has relevance and it must not be followed by a = sign.

§Struct meta

The following keys are defined on structs:

KeyValue typeDescription
namespacestring literal or pathThe XML element namespace to match. If it is a path, it must point at a &'static str.
namestring literal or pathThe XML element name to match. If it is a path, it must point at a &'static NcNameStr.
builderoptional identThe name to use for the generated builder type.
iteratoroptional identThe name to use for the generated iterator type.

Note that the name value must be a valid XML element name, without colons. The namespace prefix, if any, is assigned automatically at serialisation time and cannot be overridden. The following will thus not compile:

#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "fnord:foo")]  // colon not allowed
struct Foo;

If builder or iterator are given, the respective generated types will use the given names instead of names chosen by the derive macro implementation. Helper types will use these names as prefix. The exact names of helper types are implementation defined, which is why any type name starting with the identifiers passed to either of these keys is considered reserved.

By default, the builder type uses the type’s name suffixed with FromXmlBuilder and the iterator type uses the type’s name suffixed with AsXmlIterator.

§Enum meta

The following keys are defined on enums:

KeyValue typeDescription
namespacestring literal or pathThe XML element namespace to match for this enum. If it is a path, it must point at a &'static str.
builderoptional identThe name to use for the generated builder type.
iteratoroptional identThe name to use for the generated iterator type.
exhaustiveflagIf present, the enum considers itself authoritative for its namespace; unknown elements within the namespace are rejected instead of treated as mismatch.

All variants of an enum live within the same namespace and are distinguished exclusively by their XML name within that namespace. The contents of the XML element (including attributes) is not inspected before selecting the variant when parsing XML.

If exhaustive is set and an element is encountered which matches the namespace of the enum, but matches none of its variants, parsing will fail with an error. If exhaustive is not set, in such a situation, parsing would attempt to continue with other siblings of the enum, attempting to find a handler for that element.

Note that the exhaustive flag is orthogonal to the Rust attribute #[non_exhaustive].

For details on builder and iterator, see the Struct meta documentation above.

§Enum variant meta
KeyValue typeDescription
namestring literal or pathThe XML element name to match for this variant. If it is a path, it must point at a &'static NcNameStr.

Note that the name value must be a valid XML element name, without colons. The namespace prefix, if any, is assigned automatically at serialisation time and cannot be overridden.

§Example
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example")]
enum Foo {
    #[xml(name = "a")]
    Variant1 {
        #[xml(attribute)]
        foo: String,
    },
    #[xml(name = "b")]
    Variant2 {
        #[xml(attribute)]
        bar: String,
    },
}

let foo: Foo = xso::from_bytes(b"<a xmlns='urn:example' foo='hello'/>").unwrap();
assert_eq!(foo, Foo::Variant1 { foo: "hello".to_string() });

let foo: Foo = xso::from_bytes(b"<b xmlns='urn:example' bar='hello'/>").unwrap();
assert_eq!(foo, Foo::Variant2 { bar: "hello".to_string() });

§Field meta

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.

The following mapping types are defined:

TypeDescription
attributeMap the field to an XML attribute on the struct’s element
childMap the field to a child element
extractMap the field to contents of a child element of specified structure
textMap the field to the text content of the struct’s element
§attribute meta

The attribute meta causes the field to be mapped to an XML attribute of the same name. For FromXml, the field’s type must implement FromXmlText and for AsXml, the field’s type must implement AsOptionalXmlText.

The following keys can be used inside the #[xml(attribute(..))] meta:

KeyValue typeDescription
namespacestring literal or pathThe optional namespace of the XML attribute to match. If it is a path, it must point at a &'static str. Note that attributes, unlike elements, are unnamespaced by default.
namestring literal or pathThe name of the XML attribute to match. If it is a path, it must point at a &'static NcNameStr.
defaultflagIf present, an absent attribute will substitute the default value instead of raising an error.

If the name key contains a namespace prefix, it must be one of the prefixes defined as built-in in the XML specifications. That prefix will then be expanded to the corresponding namespace URI and the value for the namespace key is implied. Mixing a prefixed name with an explicit namespace key is not allowed.

The attribute meta also supports a shorthand syntax, #[xml(attribute = ..)], where the value is treated as the value for the name key (with optional prefix as described above, and unnamespaced otherwise).

If default is specified and the attribute is absent in the source, the value is generated using std::default::Default, requiring the field type to implement the Default trait for a FromXml derivation. default has no influence on AsXml.

§Example
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "foo")]
struct Foo {
    #[xml(attribute)]
    a: String,
    #[xml(attribute = "bar")]
    b: String,
    #[xml(attribute(name = "baz"))]
    c: String,
    #[xml(attribute(namespace = "urn:example", name = "fnord"))]
    d: String,
    #[xml(attribute = "xml:lang")]
    e: String,
};

let foo: Foo = xso::from_bytes(b"<foo
    xmlns='urn:example'
    a='1' bar='2' baz='3'
    xmlns:tns0='urn:example' tns0:fnord='4'
    xml:lang='5'
/>").unwrap();
assert_eq!(foo, Foo {
    a: "1".to_string(),
    b: "2".to_string(),
    c: "3".to_string(),
    d: "4".to_string(),
    e: "5".to_string(),
});
§child meta

The child meta causes the field to be mapped to a child element of the element.

The following keys can be used inside the #[xml(child(..))] meta:

KeyValue typeDescription
defaultflagIf present, an absent child will substitute the default value instead of raising an error.
n1 or ..If 1, a single element is parsed. If .., a collection is parsed. Defaults to 1.

When parsing a single child element (i.e. n = 1 or no n value set at all), the field’s type must implement FromXml in order to derive FromXml and AsXml in order to derive AsXml.

When parsing a collection (with n = ..), the field’s type must implement IntoIterator<Item = T>, where T must implement FromXml in order to derive FromXml and AsXml in order to derive AsXml. In addition, the field’s type must implement Extend<T> to derive FromXml and the field’s reference type must implement IntoIterator<Item = &'_ T> to derive AsXml.

If default is specified and the child is absent in the source, the value is generated using std::default::Default, requiring the field type to implement the Default trait for a FromXml derivation. default has no influence on AsXml. Combining default and n where n is not set to 1 is not supported and will cause a compile-time error.

§Example
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "child")]
struct Child {
    #[xml(attribute = "some-attr")]
    some_attr: String,
}

#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "other-child")]
struct OtherChild {
    #[xml(attribute = "some-attr")]
    some_attr: String,
}

#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "parent")]
struct Parent {
    #[xml(attribute)]
    foo: String,

    #[xml(child)]
    bar: Child,

    #[xml(child(n = ..))]
    baz: Vec<OtherChild>,
}

let parent: Parent = xso::from_bytes(b"<parent
    xmlns='urn:example'
    foo='hello world!'
><child
    some-attr='within'
/><other-child
    some-attr='c1'
/><other-child
    some-attr='c2'
/></parent>").unwrap();
assert_eq!(parent, Parent {
    foo: "hello world!".to_owned(),
    bar: Child { some_attr: "within".to_owned() },
    baz: vec! [
        OtherChild { some_attr: "c1".to_owned() },
        OtherChild { some_attr: "c2".to_owned() },
    ],
});
§extract meta

The extract meta causes the field to be mapped to the contents of a child element.

The following keys can be used inside the #[xml(extract(..))] meta:

KeyValue typeDescription
namespacestring literal or pathThe XML namespace of the child element.
namestring literal or pathThe XML name of the child element. If it is a path, it must point at a &'static NcNameStr.
n1 or ..If 1, a single element is parsed. If .., a collection is parsed. Defaults to 1.
fieldsnestedA list of field meta which describe the contents of the child element.

If the name key contains a namespace prefix, it must be one of the prefixes defined as built-in in the XML specifications. That prefix will then be expanded to the corresponding namespace URI and the value for the namespace key is implied. Mixing a prefixed name with an explicit namespace key is not allowed.

Both namespace and name may be omitted. If namespace is omitted, it defaults to the namespace of the surrounding container. If name is omitted and the extract meta is being used on a named field, that field’s name is used. If name is omitted and extract is not used on a named field, an error is emitted.

When parsing a single child element (i.e. n = 1 or no n value set at all), the extracted field’s type is set to be the same type as the field on which the extract is declared, unless overridden in the extracted field’s meta.

When parsing a collection (with n = ..), the extracted fields within fields() must all have type specifications. Not all fields kinds support that.

The sequence of field meta inside fields can be thought of as a nameless tuple-style struct. The macro generates serialisation/deserialisation code for that nameless tuple-style struct and uses it to serialise/deserialise the field.

Note: Currently, only a single field can be extracted. This restriction will be lifted in the future. Collections are not supported yet, either.

Using extract instead of child combined with a specific struct declaration comes with trade-offs. On the one hand, using extract gives you flexibility in regard of the specific serialisation of a field: it is possible to exchange a nested child element for an attribute without changing the Rust interface of the struct.

On the other hand, extract meta declarations can quickly become unwieldly and they may not support all configuration options which may in the future be added on structs (such as configuring handling of undeclared attributes) and they cannot be used for enumerations.

§Example
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "foo")]
struct Foo {
    #[xml(extract(namespace = "urn:example", name = "bar", fields(attribute = "a")))]
    a: String,
}

let foo: Foo = xso::from_bytes(b"<foo
    xmlns='urn:example'><bar a='xyz'/></foo>").unwrap();
assert_eq!(foo, Foo {
    a: "xyz".to_string(),
});
§text meta

The text meta causes the field to be mapped to the text content of the element.

KeyValue typeDescription
codecexpressionOptional TextCodec implementation which is used to encode or decode the field.
type_typeOptional explicit type specification. Only allowed within #[xml(extract(fields(..)))].

If codec is given, the given codec value must implement TextCodec<T> where T is the type of the field.

If codec is not given, the field’s type must implement FromXmlText for FromXml and for AsXml, the field’s type must implement AsXmlText.

If type_ is specified and the text meta is used within an #[xml(extract(fields(..)))] meta, the specified type is used instead of the field type on which the extract is declared.

The text meta also supports a shorthand syntax, #[xml(text = ..)], where the value is treated as the value for the codec key (with optional prefix as described above, and unnamespaced otherwise).

Only a single field per struct may be annotated with #[xml(text)] at a time, to avoid parsing ambiguities. This is also true if only AsXml is derived on a field, for consistency.

§Example without codec
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "foo")]
struct Foo {
    #[xml(text)]
    a: String,
};

let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example'>hello</foo>").unwrap();
assert_eq!(foo, Foo {
    a: "hello".to_string(),
});
§Example with codec
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "foo")]
struct Foo {
    #[xml(text = xso::text::EmptyAsNone)]
    a: Option<String>,
};

let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example'/>").unwrap();
assert_eq!(foo, Foo {
    a: None,
});

Macro to derive a xso::FromXml implementation on a type.

The user-facing documentation for this macro lives in the xso crate.