facet_xml/
lib.rs

1#![deny(unsafe_code)]
2// Note: streaming.rs uses limited unsafe for lifetime extension in YieldingReader
3
4//! XML parser that implements `FormatParser` for the codex prototype.
5//!
6//! This uses quick-xml for the underlying XML parsing and translates its
7//! events into the format-agnostic ParseEvent stream.
8
9mod parser;
10mod serializer;
11
12#[cfg(feature = "streaming")]
13mod streaming;
14
15#[cfg(feature = "axum")]
16mod axum;
17
18#[cfg(feature = "diff")]
19mod diff_serialize;
20
21pub use parser::{XmlError, XmlParser};
22
23#[cfg(feature = "axum")]
24pub use axum::{Xml, XmlRejection};
25
26#[cfg(feature = "diff")]
27pub use diff_serialize::{
28    DiffSerializeOptions, DiffSymbols, DiffTheme, diff_to_string, diff_to_string_with_options,
29    diff_to_writer, diff_to_writer_with_options,
30};
31pub use serializer::{
32    FloatFormatter, SerializeOptions, XmlSerializeError, XmlSerializer, to_string,
33    to_string_pretty, to_string_with_options, to_vec, to_vec_with_options,
34};
35
36// Re-export DeserializeError for convenience
37pub use facet_format::DeserializeError;
38
39#[cfg(all(feature = "streaming", feature = "std"))]
40pub use streaming::from_reader;
41
42#[cfg(feature = "tokio")]
43pub use streaming::from_async_reader_tokio;
44
45/// Deserialize a value from an XML string into an owned type.
46///
47/// This is the recommended default for most use cases. The input does not need
48/// to outlive the result, making it suitable for deserializing from temporary
49/// buffers (e.g., HTTP request bodies).
50///
51/// # Example
52///
53/// ```
54/// use facet::Facet;
55/// use facet_xml::from_str;
56///
57/// #[derive(Facet, Debug, PartialEq)]
58/// struct Person {
59///     name: String,
60///     age: u32,
61/// }
62///
63/// let xml = r#"<Person><name>Alice</name><age>30</age></Person>"#;
64/// let person: Person = from_str(xml).unwrap();
65/// assert_eq!(person.name, "Alice");
66/// assert_eq!(person.age, 30);
67/// ```
68pub fn from_str<T>(input: &str) -> Result<T, DeserializeError<XmlError>>
69where
70    T: facet_core::Facet<'static>,
71{
72    from_slice(input.as_bytes())
73}
74
75/// Deserialize a value from XML bytes into an owned type.
76///
77/// This is the recommended default for most use cases. The input does not need
78/// to outlive the result, making it suitable for deserializing from temporary
79/// buffers (e.g., HTTP request bodies).
80///
81/// # Example
82///
83/// ```
84/// use facet::Facet;
85/// use facet_xml::from_slice;
86///
87/// #[derive(Facet, Debug, PartialEq)]
88/// struct Person {
89///     name: String,
90///     age: u32,
91/// }
92///
93/// let xml = b"<Person><name>Alice</name><age>30</age></Person>";
94/// let person: Person = from_slice(xml).unwrap();
95/// assert_eq!(person.name, "Alice");
96/// assert_eq!(person.age, 30);
97/// ```
98pub fn from_slice<T>(input: &[u8]) -> Result<T, DeserializeError<XmlError>>
99where
100    T: facet_core::Facet<'static>,
101{
102    use facet_format::FormatDeserializer;
103    let parser = XmlParser::new(input);
104    let mut de = FormatDeserializer::new_owned(parser);
105    de.deserialize()
106}
107
108/// Deserialize a value from an XML string, allowing zero-copy borrowing.
109///
110/// This variant requires the input to outlive the result (`'input: 'facet`),
111/// enabling zero-copy deserialization of string fields as `&str` or `Cow<str>`.
112///
113/// Use this when you need maximum performance and can guarantee the input
114/// buffer outlives the deserialized value. For most use cases, prefer
115/// [`from_str`] which doesn't have lifetime requirements.
116///
117/// # Example
118///
119/// ```
120/// use facet::Facet;
121/// use facet_xml::from_str_borrowed;
122///
123/// #[derive(Facet, Debug, PartialEq)]
124/// struct Person {
125///     name: String,
126///     age: u32,
127/// }
128///
129/// let xml = r#"<Person><name>Alice</name><age>30</age></Person>"#;
130/// let person: Person = from_str_borrowed(xml).unwrap();
131/// assert_eq!(person.name, "Alice");
132/// assert_eq!(person.age, 30);
133/// ```
134pub fn from_str_borrowed<'input, 'facet, T>(
135    input: &'input str,
136) -> Result<T, DeserializeError<XmlError>>
137where
138    T: facet_core::Facet<'facet>,
139    'input: 'facet,
140{
141    from_slice_borrowed(input.as_bytes())
142}
143
144/// Deserialize a value from XML bytes, allowing zero-copy borrowing.
145///
146/// This variant requires the input to outlive the result (`'input: 'facet`),
147/// enabling zero-copy deserialization of string fields as `&str` or `Cow<str>`.
148///
149/// Use this when you need maximum performance and can guarantee the input
150/// buffer outlives the deserialized value. For most use cases, prefer
151/// [`from_slice`] which doesn't have lifetime requirements.
152///
153/// # Example
154///
155/// ```
156/// use facet::Facet;
157/// use facet_xml::from_slice_borrowed;
158///
159/// #[derive(Facet, Debug, PartialEq)]
160/// struct Person {
161///     name: String,
162///     age: u32,
163/// }
164///
165/// let xml = b"<Person><name>Alice</name><age>30</age></Person>";
166/// let person: Person = from_slice_borrowed(xml).unwrap();
167/// assert_eq!(person.name, "Alice");
168/// assert_eq!(person.age, 30);
169/// ```
170pub fn from_slice_borrowed<'input, 'facet, T>(
171    input: &'input [u8],
172) -> Result<T, DeserializeError<XmlError>>
173where
174    T: facet_core::Facet<'facet>,
175    'input: 'facet,
176{
177    use facet_format::FormatDeserializer;
178    let parser = XmlParser::new(input);
179    let mut de = FormatDeserializer::new(parser);
180    de.deserialize()
181}
182
183// XML extension attributes for use with #[facet(xml::attr)] syntax.
184//
185// After importing `use facet_xml as xml;`, users can write:
186//   #[facet(xml::element)]
187//   #[facet(xml::elements)]
188//   #[facet(xml::attribute)]
189//   #[facet(xml::text)]
190//   #[facet(xml::element_name)]
191
192// Generate XML attribute grammar using the grammar DSL.
193// This generates:
194// - `Attr` enum with all XML attribute variants
195// - `__attr!` macro that dispatches to attribute handlers and returns ExtensionAttr
196// - `__parse_attr!` macro for parsing (internal use)
197facet::define_attr_grammar! {
198    ns "xml";
199    crate_path ::facet_xml;
200
201    /// XML attribute types for field and container configuration.
202    pub enum Attr {
203        /// Marks a field as a single XML child element
204        Element,
205        /// Marks a field as collecting multiple XML child elements
206        Elements,
207        /// Marks a field as an XML attribute (on the element tag)
208        Attribute,
209        /// Marks a field as the text content of the element
210        Text,
211        /// Marks a field as storing the XML element name dynamically
212        ElementName,
213        /// Specifies the XML namespace URI for this field.
214        ///
215        /// Usage: `#[facet(xml::ns = "http://example.com/ns")]`
216        ///
217        /// When deserializing, the field will only match elements/attributes
218        /// in the specified namespace. When serializing, the element/attribute
219        /// will be emitted with the appropriate namespace prefix.
220        Ns(&'static str),
221        /// Specifies the default XML namespace URI for all fields in this container.
222        ///
223        /// Usage: `#[facet(xml::ns_all = "http://example.com/ns")]`
224        ///
225        /// This sets the default namespace for all fields that don't have their own
226        /// `xml::ns` attribute. Individual fields can override this with `xml::ns`.
227        NsAll(&'static str),
228    }
229}