xmpp-rs/xso/src/from_xml_doc.md

352 lines
12 KiB
Markdown
Raw Normal View History

# Make a struct or enum parseable from XML
This derives the [`FromXml`] trait on a struct or enum. It is the counterpart
2024-07-09 17:01:42 +02:00
to [`macro@AsXml`].
## Example
```rust
# use xso::FromXml;
#[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.
- 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:
| Key | Value type | Description |
| --- | --- | --- |
| `namespace` | *string literal* or *path* | The XML element namespace to match. If it is a *path*, it must point at a `&'static str`. |
| `name` | *string literal* or *path* | The XML element name to match. If it is a *path*, it must point at a `&'static NcNameStr`. |
| `builder` | optional *ident* | The name to use for the generated builder type. |
| `iterator` | optional *ident* | The 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:
```compile_fail
# use xso::FromXml;
#[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`.
2024-06-30 09:06:10 +02:00
### Enum meta
The following keys are defined on enums:
| Key | Value type | Description |
| --- | --- | --- |
| `namespace` | *string literal* or *path* | The XML element namespace to match for this enum. If it is a *path*, it must point at a `&'static str`. |
| `builder` | optional *ident* | The name to use for the generated builder type. |
| `iterator` | optional *ident* | The name to use for the generated iterator type. |
| `exhaustive` | *flag* | If present, the enum considers itself authoritative for its namespace; unknown elements within the namespace are rejected instead of treated as mismatch. |
2024-06-30 09:06:10 +02:00
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]`.
2024-06-30 09:06:10 +02:00
For details on `builder` and `iterator`, see the [Struct meta](#struct-meta)
documentation above.
#### Enum variant meta
| Key | Value type | Description |
| --- | --- | --- |
| `name` | *string literal* or *path* | The 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
```rust
# use xso::FromXml;
#[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:
| Type | Description |
| --- | --- |
| [`attribute`](#attribute-meta) | Map the field to an XML attribute on the struct's element |
| [`child`](#child-meta) | Map the field to a child element |
| [`text`](#text-meta) | Map 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
2024-07-09 17:01:42 +02:00
for `AsXml`, the field's type must implement [`AsOptionalXmlText`].
The following keys can be used inside the `#[xml(attribute(..))]` meta:
| Key | Value type | Description |
| --- | --- | --- |
| `namespace` | *string literal* or *path* | The 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. |
| `name` | *string literal* or *path* | The name of the XML attribute to match. If it is a *path*, it must point at a `&'static NcNameStr`. |
| `default` | flag | If 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
2024-07-09 17:01:42 +02:00
influence on `AsXml`.
##### Example
```rust
# use xso::FromXml;
#[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:
| Key | Value type | Description |
| --- | --- | --- |
| `default` | flag | If present, an absent child will substitute the default value instead of raising an error. |
| `n` | `1` 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>`][`std::iter::IntoIterator`], 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>`][`std::iter::Extend`] 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
```rust
# use xso::FromXml;
#[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() },
],
});
```
#### `text` meta
The `text` meta causes the field to be mapped to the text content of the
element.
| Key | Value type | Description |
| --- | --- | --- |
| `codec` | *expression* | Optional [`TextCodec`] implementation which is used to encode or decode the field. |
If `codec` is given, the given `codec` value must implement
[`TextCodec<T>`][`TextCodec`] where `T` is the type of the field.
If `codec` is *not* given, the field's type must implement [`FromXmlText`] for
2024-07-09 17:01:42 +02:00
`FromXml` and for `AsXml`, the field's type must implement [`AsXmlText`].
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,
2024-07-09 17:01:42 +02:00
to avoid parsing ambiguities. This is also true if only `AsXml` is derived on
a field, for consistency.
##### Example without codec
```rust
# use xso::FromXml;
#[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
```rust
# use xso::FromXml;
#[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,
});
```