Skip to main content

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