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}