facet_xml/
lib.rs

1//! XML serialization and deserialization for Facet types.
2//!
3//! This crate provides XML support for Facet types using an event-driven
4//! architecture powered by [`quick-xml`](https://crates.io/crates/quick-xml).
5//!
6//! # Quick start
7//!
8//! ```rust
9//! use facet::Facet;
10//! use facet_xml as xml;
11//!
12//! #[derive(Facet, Debug, PartialEq)]
13//! struct Person {
14//!     #[facet(xml::attribute)]
15//!     id: u32,
16//!     #[facet(xml::element)]
17//!     name: String,
18//!     #[facet(xml::element)]
19//!     age: Option<u32>,
20//! }
21//!
22//! fn main() -> Result<(), facet_xml::XmlError> {
23//!     let xml_str = r#"<Person id="42"><name>Alice</name></Person>"#;
24//!     let person: Person = facet_xml::from_str(xml_str)?;
25//!     assert_eq!(person.name, "Alice");
26//!     assert_eq!(person.id, 42);
27//!     assert_eq!(person.age, None);
28//!
29//!     let output = facet_xml::to_string(&person)?;
30//!     // Output: <Person id="42"><name>Alice</name></Person>
31//!     Ok(())
32//! }
33//! ```
34//!
35//! > **Important:** Every struct field must declare how it maps to XML. Add
36//! > `#[facet(xml::attribute)]`, `#[facet(xml::element)]`,
37//! > `#[facet(xml::elements)]`, `#[facet(xml::text)]`,
38//! > `#[facet(xml::element_name)]`, or `#[facet(child)]` to every field that
39//! > should appear in XML. Fields without an annotation now trigger an error
40//! > instead of being silently skipped.
41//!
42//! # Attribute Guide
43//!
44//! ## `#[facet(xml::element)]`
45//!
46//! Maps a field to a child XML element:
47//!
48//! ```rust
49//! # use facet::Facet;
50//! # use facet_xml as xml;
51//! #[derive(Facet)]
52//! struct Book {
53//!     #[facet(xml::element)]
54//!     title: String,
55//!     #[facet(xml::element)]
56//!     author: String,
57//! }
58//! // Deserializes: <Book><title>1984</title><author>Orwell</author></Book>
59//! ```
60//!
61//! ## `#[facet(xml::elements)]`
62//!
63//! Maps a field to multiple child elements (for Vec, HashSet, etc.):
64//!
65//! ```rust
66//! # use facet::Facet;
67//! # use facet_xml as xml;
68//! #[derive(Facet)]
69//! struct Library {
70//!     #[facet(xml::elements)]
71//!     books: Vec<Book>,
72//! }
73//!
74//! #[derive(Facet)]
75//! struct Book {
76//!     #[facet(xml::attribute)]
77//!     isbn: String,
78//! }
79//! // Deserializes: <Library><Book isbn="123"/><Book isbn="456"/></Library>
80//! ```
81//!
82//! ## `#[facet(xml::attribute)]`
83//!
84//! Maps a field to an XML attribute:
85//!
86//! ```rust
87//! # use facet::Facet;
88//! # use facet_xml as xml;
89//! #[derive(Facet)]
90//! struct Item {
91//!     #[facet(xml::attribute)]
92//!     id: u32,
93//!     #[facet(xml::attribute)]
94//!     name: String,
95//! }
96//! // Deserializes: <Item id="1" name="widget"/>
97//! ```
98//!
99//! ## `#[facet(xml::text)]`
100//!
101//! Maps a field to the text content of the element:
102//!
103//! ```rust
104//! # use facet::Facet;
105//! # use facet_xml as xml;
106//! #[derive(Facet)]
107//! struct Message {
108//!     #[facet(xml::attribute)]
109//!     from: String,
110//!     #[facet(xml::text)]
111//!     content: String,
112//! }
113//! // Deserializes: <Message from="alice">Hello, world!</Message>
114//! ```
115//!
116//! # Error Reporting
117//!
118//! Errors use `miette` spans where possible, so diagnostics can point back to
119//! the offending XML source.
120
121#![warn(missing_docs)]
122#![allow(clippy::result_large_err)]
123
124mod annotation;
125mod deserialize;
126mod error;
127mod serialize;
128
129// Re-export span types from facet-reflect
130pub use facet_reflect::{Span, Spanned};
131
132// Re-export error types
133pub use error::{XmlError, XmlErrorKind};
134
135// Re-export deserialization
136pub use deserialize::{
137    DeserializeOptions, from_slice, from_slice_owned, from_slice_with_options, from_str,
138    from_str_with_options,
139};
140
141// Re-export serialization
142pub use serialize::{
143    FloatFormatter, SerializeOptions, to_string, to_string_pretty, to_string_with_options,
144    to_writer, to_writer_pretty, to_writer_with_options,
145};
146
147mod xml;
148pub use xml::Xml;
149
150#[cfg(feature = "axum")]
151mod axum;
152#[cfg(feature = "axum")]
153pub use self::axum::XmlRejection;
154
155#[cfg(feature = "diff")]
156mod diff_serialize;
157#[cfg(feature = "diff")]
158pub use diff_serialize::*;
159
160// XML extension attributes for use with #[facet(xml::attr)] syntax.
161//
162// After importing `use facet_xml as xml;`, users can write:
163//   #[facet(xml::element)]
164//   #[facet(xml::elements)]
165//   #[facet(xml::attribute)]
166//   #[facet(xml::text)]
167//   #[facet(xml::element_name)]
168
169// Generate XML attribute grammar using the grammar DSL.
170// This generates:
171// - `Attr` enum with all XML attribute variants
172// - `__attr!` macro that dispatches to attribute handlers and returns ExtensionAttr
173// - `__parse_attr!` macro for parsing (internal use)
174facet::define_attr_grammar! {
175    ns "xml";
176    crate_path ::facet_xml;
177
178    /// XML attribute types for field and container configuration.
179    pub enum Attr {
180        /// Marks a field as a single XML child element
181        Element,
182        /// Marks a field as collecting multiple XML child elements
183        Elements,
184        /// Marks a field as an XML attribute (on the element tag)
185        Attribute,
186        /// Marks a field as the text content of the element
187        Text,
188        /// Marks a field as storing the XML element name dynamically
189        ElementName,
190        /// Specifies the XML namespace URI for this field.
191        ///
192        /// Usage: `#[facet(xml::ns = "http://example.com/ns")]`
193        ///
194        /// When deserializing, the field will only match elements/attributes
195        /// in the specified namespace. When serializing, the element/attribute
196        /// will be emitted with the appropriate namespace prefix.
197        Ns(&'static str),
198        /// Specifies the default XML namespace URI for all fields in this container.
199        ///
200        /// Usage: `#[facet(xml::ns_all = "http://example.com/ns")]`
201        ///
202        /// This sets the default namespace for all fields that don't have their own
203        /// `xml::ns` attribute. Individual fields can override this with `xml::ns`.
204        NsAll(&'static str),
205    }
206}