serde_xml/
lib.rs

1//! # serde_xml
2//!
3//! A fast, 100% Serde-compatible XML serialization and deserialization library.
4//!
5//! ## Features
6//!
7//! - Full Serde compatibility for serialization and deserialization
8//! - Zero-copy parsing where possible
9//! - Fast XML tokenization using SIMD-accelerated string searching
10//! - Support for attributes, namespaces, CDATA, comments, and processing instructions
11//! - Comprehensive error reporting with line/column positions
12//! - No unsafe code in the public API
13//!
14//! ## Quick Start
15//!
16//! ```rust
17//! use serde::{Deserialize, Serialize};
18//! use serde_xml::{from_str, to_string};
19//!
20//! #[derive(Debug, Serialize, Deserialize, PartialEq)]
21//! struct Person {
22//!     name: String,
23//!     age: u32,
24//! }
25//!
26//! // Serialize to XML
27//! let person = Person {
28//!     name: "Alice".to_string(),
29//!     age: 30,
30//! };
31//! let xml = to_string(&person).unwrap();
32//!
33//! // Deserialize from XML
34//! let xml = "<Person><name>Alice</name><age>30</age></Person>";
35//! let person: Person = from_str(xml).unwrap();
36//! assert_eq!(person.name, "Alice");
37//! assert_eq!(person.age, 30);
38//! ```
39//!
40//! ## Nested Structures
41//!
42//! ```rust
43//! use serde::{Deserialize, Serialize};
44//! use serde_xml::from_str;
45//!
46//! #[derive(Debug, Deserialize)]
47//! struct Address {
48//!     city: String,
49//!     country: String,
50//! }
51//!
52//! #[derive(Debug, Deserialize)]
53//! struct Person {
54//!     name: String,
55//!     address: Address,
56//! }
57//!
58//! let xml = r#"
59//!     <Person>
60//!         <name>Bob</name>
61//!         <address>
62//!             <city>New York</city>
63//!             <country>USA</country>
64//!         </address>
65//!     </Person>
66//! "#;
67//!
68//! let person: Person = from_str(xml).unwrap();
69//! assert_eq!(person.address.city, "New York");
70//! ```
71//!
72//! ## Collections
73//!
74//! ```rust
75//! use serde::{Deserialize, Serialize};
76//! use serde_xml::{from_str, to_string};
77//!
78//! #[derive(Debug, Serialize, Deserialize)]
79//! struct Library {
80//!     books: Vec<String>,
81//! }
82//!
83//! let library = Library {
84//!     books: vec![
85//!         "The Rust Programming Language".to_string(),
86//!         "Programming Rust".to_string(),
87//!     ],
88//! };
89//!
90//! let xml = to_string(&library).unwrap();
91//! ```
92//!
93//! ## Optional Fields
94//!
95//! ```rust
96//! use serde::{Deserialize, Serialize};
97//! use serde_xml::from_str;
98//!
99//! #[derive(Debug, Deserialize)]
100//! struct Config {
101//!     name: String,
102//!     description: Option<String>,
103//! }
104//!
105//! let xml = "<Config><name>test</name></Config>";
106//! let config: Config = from_str(xml).unwrap();
107//! assert_eq!(config.description, None);
108//! ```
109
110#![warn(missing_docs)]
111#![warn(rust_2018_idioms)]
112#![deny(unsafe_op_in_unsafe_fn)]
113
114pub mod de;
115pub mod error;
116pub mod escape;
117pub mod reader;
118pub mod ser;
119pub mod writer;
120
121// Re-export main types and functions
122pub use de::{from_bytes, from_str, Deserializer};
123pub use error::{Error, ErrorKind, Position, Result};
124pub use escape::{escape, unescape};
125pub use reader::{Attribute, XmlEvent, XmlReader};
126pub use ser::{to_string, to_string_with_root, to_vec, to_writer, Serializer};
127pub use writer::{IndentConfig, XmlWriter};
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use serde::{Deserialize, Serialize};
133
134    #[test]
135    fn test_roundtrip_simple() {
136        #[derive(Debug, Serialize, Deserialize, PartialEq)]
137        struct Person {
138            name: String,
139            age: u32,
140        }
141
142        let original = Person {
143            name: "Alice".to_string(),
144            age: 30,
145        };
146
147        let xml = to_string(&original).unwrap();
148        let parsed: Person = from_str(&xml).unwrap();
149        assert_eq!(original, parsed);
150    }
151
152    #[test]
153    fn test_roundtrip_nested() {
154        #[derive(Debug, Serialize, Deserialize, PartialEq)]
155        struct Address {
156            city: String,
157            country: String,
158        }
159
160        #[derive(Debug, Serialize, Deserialize, PartialEq)]
161        struct Person {
162            name: String,
163            address: Address,
164        }
165
166        let original = Person {
167            name: "Bob".to_string(),
168            address: Address {
169                city: "New York".to_string(),
170                country: "USA".to_string(),
171            },
172        };
173
174        let xml = to_string(&original).unwrap();
175        let parsed: Person = from_str(&xml).unwrap();
176        assert_eq!(original, parsed);
177    }
178
179    #[test]
180    fn test_roundtrip_vector() {
181        #[derive(Debug, Serialize, Deserialize, PartialEq)]
182        struct Items {
183            item: Vec<String>,
184        }
185
186        let original = Items {
187            item: vec!["one".to_string(), "two".to_string(), "three".to_string()],
188        };
189
190        let xml = to_string(&original).unwrap();
191        let parsed: Items = from_str(&xml).unwrap();
192        assert_eq!(original, parsed);
193    }
194
195    #[test]
196    fn test_roundtrip_optional() {
197        #[derive(Debug, Serialize, Deserialize, PartialEq)]
198        struct Config {
199            name: String,
200            value: Option<String>,
201        }
202
203        let with_value = Config {
204            name: "test".to_string(),
205            value: Some("val".to_string()),
206        };
207
208        let xml = to_string(&with_value).unwrap();
209        let parsed: Config = from_str(&xml).unwrap();
210        assert_eq!(with_value, parsed);
211    }
212
213    #[test]
214    fn test_roundtrip_escaped() {
215        #[derive(Debug, Serialize, Deserialize, PartialEq)]
216        struct Data {
217            content: String,
218        }
219
220        let original = Data {
221            content: "<hello> & \"world\"".to_string(),
222        };
223
224        let xml = to_string(&original).unwrap();
225        let parsed: Data = from_str(&xml).unwrap();
226        assert_eq!(original, parsed);
227    }
228
229    #[test]
230    fn test_xml_reader_basic() {
231        let mut reader = XmlReader::from_str("<root><child>text</child></root>");
232
233        match reader.next_event().unwrap() {
234            XmlEvent::StartElement { name, .. } => assert_eq!(name, "root"),
235            _ => panic!("expected StartElement"),
236        }
237
238        match reader.next_event().unwrap() {
239            XmlEvent::StartElement { name, .. } => assert_eq!(name, "child"),
240            _ => panic!("expected StartElement"),
241        }
242
243        match reader.next_event().unwrap() {
244            XmlEvent::Text(text) => assert_eq!(text, "text"),
245            _ => panic!("expected Text"),
246        }
247    }
248
249    #[test]
250    fn test_xml_writer_basic() {
251
252        let mut buffer = Vec::new();
253        {
254            let mut writer = XmlWriter::new(&mut buffer);
255            writer.start_element("root").unwrap();
256            writer.start_element("child").unwrap();
257            writer.write_text("text").unwrap();
258            writer.end_element().unwrap();
259            writer.end_element().unwrap();
260        }
261
262        let xml = String::from_utf8(buffer).unwrap();
263        assert!(xml.contains("<root>"));
264        assert!(xml.contains("<child>text</child>"));
265    }
266
267    #[test]
268    fn test_escape_unescape() {
269        let original = "<hello> & \"world\"";
270        let escaped = escape(original);
271        let unescaped = unescape(&escaped).unwrap();
272        assert_eq!(unescaped, original);
273    }
274
275    #[test]
276    fn test_error_reporting() {
277        // Test mismatched tags error
278        #[allow(dead_code)]
279        #[derive(Debug, Deserialize)]
280        struct Item {
281            name: String,
282        }
283
284        let result: Result<Item> = from_str("<Item><name>test</wrong></Item>");
285        assert!(result.is_err());
286        let err = result.unwrap_err();
287        assert!(err.to_string().contains("mismatched") || err.to_string().contains("wrong"));
288    }
289
290    #[test]
291    fn test_complex_xml() {
292        #[derive(Debug, Serialize, Deserialize, PartialEq)]
293        struct Book {
294            title: String,
295            author: String,
296            year: u32,
297        }
298
299        #[derive(Debug, Serialize, Deserialize, PartialEq)]
300        struct Library {
301            name: String,
302            book: Vec<Book>,
303        }
304
305        let original = Library {
306            name: "My Library".to_string(),
307            book: vec![
308                Book {
309                    title: "The Rust Programming Language".to_string(),
310                    author: "Steve Klabnik".to_string(),
311                    year: 2018,
312                },
313                Book {
314                    title: "Programming Rust".to_string(),
315                    author: "Jim Blandy".to_string(),
316                    year: 2021,
317                },
318            ],
319        };
320
321        let xml = to_string(&original).unwrap();
322        let parsed: Library = from_str(&xml).unwrap();
323        assert_eq!(original, parsed);
324    }
325}