Crate facet_xml

Crate facet_xml 

Source
Expand description

§facet-xml

XML serialization and deserialization for Rust using the facet reflection framework.

The XML serializer and deserializer assumes every node is lowerCamelCase, unless explicitly renamed.

#[derive(Facet, Debug)]
struct Banana {
    taste: String,
}

Use rename to override:

#[derive(Facet, Debug)]
#[facet(rename = "Banana")]
struct Banana {
    taste: String,
}

§Child Elements

By default, fields are matched against child elements with the same name (in lowerCamelCase).

<person>
    <name>Ella</name>
    <age>42</age>
</person>
#[derive(Facet, Debug)]
struct Person {
    name: String, // captures "Ella"
    age: u32,     // captures 42
}

§Attributes

Use xml::attribute to capture XML attributes:

<link href="/home">Home</link>
#[derive(Facet, Debug)]
struct Link {
    #[facet(xml::attribute)]
    href: String,      // captures "/home"
    
    #[facet(xml::text)]
    text: String,      // captures "Home"
}

§Text

Use xml::text to capture text content:

<name>Ella</name>
#[derive(Facet, Debug)]
struct Name {
    #[facet(xml::text)]
    value: String, // captures "Ella"
}

§Lists

For list types (Vec, etc.), facet-xml collects items. By default, items are child elements with the singularized field name (via facet-singularize).

§Default: child elements with singularized name

<playlist>
    <track>Song A</track>
    <track>Song B</track>
</playlist>
#[derive(Facet, Debug)]
struct Playlist {
    tracks: Vec<String>, // "tracks" → expects <track> elements
}

§Override element name with rename

#[derive(Facet, Debug)]
struct Playlist {
    #[facet(rename = "song")]
    tracks: Vec<String>, // expects <song> instead of <track>
}

§Explicit xml::elements (same as default)

#[derive(Facet, Debug)]
struct Playlist {
    #[facet(xml::elements)]
    tracks: Vec<String>,
}

§Lists of structs with rename

When collecting struct items, use rename to specify the element name. The rename overrides the default singularized field name:

#[derive(Facet, Debug, PartialEq)]
struct Person {
    #[facet(xml::attribute)]
    name: String,
}

#[derive(Facet, Debug)]
struct Team {
    // "individual" instead of default "member" (singularized from "members")
    #[facet(xml::elements, rename = "individual")]
    members: Vec<Person>,
}

§Collect text nodes with xml::text

<message>Hello <b>world</b>!</message>
#[derive(Facet, Debug)]
struct Message {
    #[facet(xml::text)]
    parts: Vec<String>, // collects text nodes: ["Hello ", "!"]
}

§Collect attributes with xml::attribute

<element foo="1" bar="2" baz="3"/>
#[derive(Facet, Debug)]
#[facet(rename = "element")]
struct Element {
    #[facet(xml::attribute)]
    values: Vec<String>, // collects all attribute values
}

§Flattened Lists (Heterogeneous Children)

When you have a Vec<SomeEnum> and want each enum variant to appear directly as a child element (without a wrapper), use #[facet(flatten)]:

<canvas>
    <circle r="5"/>
    <rect width="10" height="20"/>
    <path d="M0 0 L10 10"/>
</canvas>
#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Shape {
    Circle {
        #[facet(xml::attribute)]
        r: f64
    },
    Rect {
        #[facet(xml::attribute)]
        width: f64,
        #[facet(xml::attribute)]
        height: f64
    },
    Path {
        #[facet(xml::attribute)]
        d: String
    },
}

#[derive(Facet, Debug)]
struct Canvas {
    #[facet(flatten)]
    children: Vec<Shape>, // collects <circle>, <rect>, <path> directly
}

Without #[facet(flatten)], the field would expect wrapper elements:

<!-- Without flatten: expects <child> wrappers -->
<canvas>
    <child><circle r="5"/></child>
    <child><rect width="10" height="20"/></child>
</canvas>

This pattern is essential for XML formats like SVG, HTML, or any schema where parent elements contain heterogeneous children identified by their element names.

§Tuples

Tuples are treated like lists: each element becomes a child element with the field’s name (or singularized name for plural field names). Elements are matched by position.

<record>
    <value>42</value>
    <value>hello</value>
    <value>true</value>
</record>
#[derive(Facet, Debug, PartialEq)]
#[facet(rename = "record")]
struct Record {
    #[facet(rename = "value")]
    data: (i32, String, bool),
}

Without rename, the field name is used directly (no singularization for tuples since tuple fields typically have singular names):

#[derive(Facet, Debug, PartialEq)]
#[facet(rename = "record")]
struct Record {
    data: (i32, String, bool),
}

§Enums

In XML, enums are always treated as externally tagged - the element name is the variant discriminator. This is natural for XML because the element structure already provides tagging.

Any #[facet(tag = "...")] or #[facet(content = "...")] attributes are ignored for XML serialization. These attributes are useful for JSON (which needs explicit tag fields), but XML doesn’t need them since element names serve this purpose.

§Unit variants

Unit variants become empty elements:

#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Status {
    Active,
    Inactive,
}
// "Active" becomes <active> (lowerCamelCase)

§Newtype variants

Newtype variants (single unnamed field) wrap their content:

#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Value {
    Text(String),
    Number(i32),
}
// <text>hello</text> deserializes to Value::Text("hello")

§Struct variants

Struct variants have child elements for their fields:

#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}
// <circle><radius>5.0</radius></circle>

§Variant renaming

Use #[facet(rename = "...")] on variants to override the element name:

#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Status {
    #[facet(rename = "on")]
    Active,
    #[facet(rename = "off")]
    Inactive,
}

§Internally/adjacently tagged enums

Attributes like #[facet(tag = "type")] or #[facet(tag = "t", content = "c")] are ignored for XML. They exist for JSON compatibility but don’t affect XML serialization:

#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
#[facet(tag = "type")] // ignored for XML!
enum Shape {
    Circle { radius: f64 },
}
// Still uses element name as discriminator

§Untagged enums

Untagged enums (#[facet(untagged)]) use the enum’s own name as the element, not a variant name. The content determines which variant is selected:

#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
#[facet(untagged, rename = "point")]
enum Point {
    Coords { x: i32, y: i32 },
}

Macros§

trace
Emit a trace-level log message (no-op version).

Structs§

RawMarkup
A string containing raw markup captured verbatim from the source.
SerializeOptions
Options for XML serialization.
XmlParser
Streaming XML parser implementing DomParser.
XmlSerializeError
XmlSerializer
XML serializer with configurable output options.

Enums§

Attr
XML attribute types for field and container configuration.
DeserializeError
Error type for DOM deserialization.
SerializeError
Error produced by the DOM serializer.
XmlError
XML parsing error.

Functions§

from_slice
Deserialize a value from XML bytes into an owned type.
from_slice_borrowed
Deserialize a value from XML bytes, allowing borrowing from the input.
from_str
Deserialize a value from an XML string into an owned type.
from_str_borrowed
Deserialize a value from an XML string, allowing borrowing from the input.
to_string
Serialize a value to an XML string with default options.
to_string_pretty
Serialize a value to a pretty-printed XML string with default indentation.
to_string_with_options
Serialize a value to an XML string with custom options.
to_vec
Serialize a value to XML bytes with default options.
to_vec_with_options
Serialize a value to XML bytes with custom options.

Type Aliases§

FloatFormatter
A function that formats a floating-point number to a writer.