facet_xml/
lib.rs

1#![doc = include_str!("../README.md.in")]
2#![deny(unsafe_code)]
3
4#[macro_use]
5mod tracing_macros;
6
7mod dom_parser;
8mod escaping;
9mod serializer;
10
11#[cfg(feature = "axum")]
12mod axum;
13
14pub use dom_parser::{XmlError, XmlParser};
15
16#[cfg(feature = "axum")]
17pub use axum::{Xml, XmlRejection};
18
19pub use serializer::{
20    FloatFormatter, SerializeOptions, XmlSerializeError, XmlSerializer, to_string,
21    to_string_pretty, to_string_with_options, to_vec, to_vec_with_options,
22};
23
24// Re-export error types for convenience
25pub use facet_dom::DomDeserializeError as DeserializeError;
26pub use facet_dom::DomSerializeError as SerializeError;
27pub use facet_dom::RawMarkup;
28
29/// Deserialize a value from an XML string into an owned type.
30///
31/// This is the recommended default for most use cases. The input does not need
32/// to outlive the result, making it suitable for deserializing from temporary
33/// buffers (e.g., HTTP request bodies).
34///
35/// # Example
36///
37/// ```
38/// use facet::Facet;
39/// use facet_xml::from_str;
40///
41/// #[derive(Facet, Debug, PartialEq)]
42/// struct Person {
43///     name: String,
44///     age: u32,
45/// }
46///
47/// // "Person" becomes <person> (lowerCamelCase convention)
48/// let xml = r#"<person><name>Alice</name><age>30</age></person>"#;
49/// let person: Person = from_str(xml).unwrap();
50/// assert_eq!(person.name, "Alice");
51/// assert_eq!(person.age, 30);
52/// ```
53pub fn from_str<T>(input: &str) -> Result<T, DeserializeError<XmlError>>
54where
55    T: facet_core::Facet<'static>,
56{
57    from_slice(input.as_bytes())
58}
59
60/// Deserialize a value from XML bytes into an owned type.
61///
62/// This is the recommended default for most use cases. The input does not need
63/// to outlive the result, making it suitable for deserializing from temporary
64/// buffers (e.g., HTTP request bodies).
65///
66/// # Example
67///
68/// ```
69/// use facet::Facet;
70/// use facet_xml::from_slice;
71///
72/// #[derive(Facet, Debug, PartialEq)]
73/// struct Person {
74///     name: String,
75///     age: u32,
76/// }
77///
78/// // "Person" becomes <person> (lowerCamelCase convention)
79/// let xml = b"<person><name>Alice</name><age>30</age></person>";
80/// let person: Person = from_slice(xml).unwrap();
81/// assert_eq!(person.name, "Alice");
82/// assert_eq!(person.age, 30);
83/// ```
84pub fn from_slice<T>(input: &[u8]) -> Result<T, DeserializeError<XmlError>>
85where
86    T: facet_core::Facet<'static>,
87{
88    let parser = XmlParser::new(input);
89    let mut de = facet_dom::DomDeserializer::new_owned(parser);
90    de.deserialize()
91}
92
93/// Deserialize a value from an XML string, allowing borrowing from the input.
94///
95/// Use this when the deserialized type can borrow from the input string
96/// (e.g., contains `&'a str` fields). The input must outlive the result.
97///
98/// For most use cases, prefer [`from_str`] which produces owned types.
99pub fn from_str_borrowed<'input, T>(input: &'input str) -> Result<T, DeserializeError<XmlError>>
100where
101    T: facet_core::Facet<'input>,
102{
103    from_slice_borrowed(input.as_bytes())
104}
105
106/// Deserialize a value from XML bytes, allowing borrowing from the input.
107///
108/// Use this when the deserialized type can borrow from the input bytes
109/// (e.g., contains `&'a str` fields). The input must outlive the result.
110///
111/// For most use cases, prefer [`from_slice`] which produces owned types.
112pub fn from_slice_borrowed<'input, T>(input: &'input [u8]) -> Result<T, DeserializeError<XmlError>>
113where
114    T: facet_core::Facet<'input>,
115{
116    let parser = XmlParser::new(input);
117    let mut de = facet_dom::DomDeserializer::new(parser);
118    de.deserialize()
119}
120
121// XML extension attributes for use with #[facet(xml::attr)] syntax.
122//
123// After importing `use facet_xml as xml;`, users can write:
124//   #[facet(xml::element)]
125//   #[facet(xml::elements)]
126//   #[facet(xml::attribute)]
127//   #[facet(xml::text)]
128//   #[facet(xml::tag)]
129
130// Generate XML attribute grammar using the grammar DSL.
131// This generates:
132// - `Attr` enum with all XML attribute variants
133// - `__attr!` macro that dispatches to attribute handlers and returns ExtensionAttr
134// - `__parse_attr!` macro for parsing (internal use)
135facet::define_attr_grammar! {
136    ns "xml";
137    crate_path ::facet_xml;
138
139    /// XML attribute types for field and container configuration.
140    pub enum Attr {
141        /// Marks a field as a single XML child element
142        Element,
143        /// Marks a field as collecting multiple XML child elements
144        Elements,
145        /// Marks a field as an XML attribute (on the element tag)
146        Attribute,
147        /// Marks a field as the text content of the element
148        Text,
149        /// Marks a field as storing the XML element tag name dynamically.
150        ///
151        /// Used on a `String` field to capture the tag name of an element
152        /// during deserialization. When serializing, this value becomes the element's tag.
153        Tag,
154        /// Specifies the XML namespace URI for this field.
155        ///
156        /// Usage: `#[facet(xml::ns = "http://example.com/ns")]`
157        ///
158        /// When deserializing, the field will only match elements/attributes
159        /// in the specified namespace. When serializing, the element/attribute
160        /// will be emitted with the appropriate namespace prefix.
161        Ns(&'static str),
162        /// Specifies the default XML namespace URI for all fields in this container.
163        ///
164        /// Usage: `#[facet(xml::ns_all = "http://example.com/ns")]`
165        ///
166        /// This sets the default namespace for all fields that don't have their own
167        /// `xml::ns` attribute. Individual fields can override this with `xml::ns`.
168        NsAll(&'static str),
169        /// Marks an enum variant as a catch-all for unknown XML elements.
170        ///
171        /// Usage: `#[facet(xml::custom_element)]`
172        ///
173        /// When deserializing, if no variant name matches the element tag,
174        /// this variant is selected. Use with `xml::tag` to capture the tag name.
175        CustomElement,
176        /// Marks a field as capturing the DOCTYPE declaration from the XML document.
177        ///
178        /// Usage: `#[facet(xml::doctype)]`
179        ///
180        /// When deserializing, this field captures the DOCTYPE declaration if present.
181        /// When serializing, the field's value is emitted as the DOCTYPE declaration.
182        /// This attribute is ignored on non-root structs to allow struct reuse.
183        ///
184        /// The field type should be `Option<String>` to handle documents without DOCTYPE.
185        Doctype,
186    }
187}