Skip to main content

typespec/
xml.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4//! XML serialization functions.
5use crate::error::{ErrorKind, Result, ResultExt};
6use bytes::Bytes;
7pub use quick_xml::serde_helpers::text_content as content;
8use quick_xml::{
9    de::from_reader,
10    se::{to_string, to_string_with_root},
11};
12use serde::de::DeserializeOwned;
13
14/// The UTF8 [byte order marker](https://en.wikipedia.org/wiki/Byte_order_mark).
15const UTF8_BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];
16
17/// The XML declaration used when serializing.
18const DECLARATION: &[u8; 38] = br#"<?xml version="1.0" encoding="utf-8"?>"#;
19
20/// Reads XML from bytes.
21pub fn from_xml<S, T>(body: S) -> Result<T>
22where
23    S: AsRef<[u8]>,
24    T: DeserializeOwned,
25{
26    let body = body.as_ref();
27    from_reader(slice_bom(body)).with_context_fn(ErrorKind::DataConversion, || {
28        let t = core::any::type_name::<T>();
29        let xml = std::str::from_utf8(body).unwrap_or("(XML is not UTF8-encoded)");
30        format!("failed to deserialize the following xml into a {t}\n{xml}")
31    })
32}
33
34/// Serializes a type to bytes.
35///
36/// Automatically includes the XML declaration.
37pub fn to_xml<T>(value: &T) -> Result<Bytes>
38where
39    T: serde::Serialize,
40{
41    let value = to_string(value).with_context_fn(ErrorKind::DataConversion, || {
42        let t = core::any::type_name::<T>();
43        format!("failed to serialize {t} into xml")
44    })?;
45    let mut buf = bytes::BytesMut::with_capacity(DECLARATION.len() + value.len());
46    buf.extend_from_slice(DECLARATION);
47    buf.extend_from_slice(value.as_bytes());
48    Ok(buf.into())
49}
50
51/// Serializes a type to bytes with a specified root tag.
52///
53/// Automatically includes the XML declaration.
54pub fn to_xml_with_root<T>(root_tag: &str, value: &T) -> Result<Bytes>
55where
56    T: serde::Serialize,
57{
58    let value =
59        to_string_with_root(root_tag, value).with_context_fn(ErrorKind::DataConversion, || {
60            let t = core::any::type_name::<T>();
61            format!("failed to serialize {t} into xml")
62        })?;
63    let mut buf = bytes::BytesMut::with_capacity(DECLARATION.len() + value.len());
64    buf.extend_from_slice(DECLARATION);
65    buf.extend_from_slice(value.as_bytes());
66    Ok(buf.into())
67}
68
69/// Returns bytes without the UTF-8 BOM.
70fn slice_bom(bytes: &[u8]) -> &[u8] {
71    if bytes.len() > 3 && bytes[0..3] == UTF8_BOM {
72        &bytes[3..]
73    } else {
74        bytes
75    }
76}
77
78#[cfg(test)]
79mod test {
80    use super::*;
81    use serde::{Deserialize, Serialize};
82
83    #[test]
84    fn test_slice_bom() {
85        let bytes = &[0xEF, 0xBB, 0xBF, 7];
86        assert_eq!(&[7], slice_bom(bytes));
87
88        let bytes = &[8];
89        assert_eq!(&[8], slice_bom(bytes));
90    }
91
92    #[derive(Deserialize, Serialize, PartialEq, Debug)]
93    #[serde(rename = "Foo")]
94    struct Test {
95        x: String,
96    }
97
98    #[test]
99    fn reading_xml() -> Result<()> {
100        let test = Test {
101            x: "Hello, world!".into(),
102        };
103        let xml = br#"<?xml version="1.0" encoding="utf-8"?><Foo><x>Hello, world!</x></Foo>"#;
104        assert_eq!(test, from_xml(xml)?);
105
106        let error = from_xml::<_, Test>(&xml[..xml.len() - 2]).unwrap_err();
107        assert!(format!("{error}").contains("typespec::xml::test::Test"));
108        Ok(())
109    }
110
111    #[test]
112    fn writing_xml() -> Result<()> {
113        assert_eq!(
114            br#"<?xml version="1.0" encoding="utf-8"?><Foo><x>Hello, world!</x></Foo>"#,
115            to_xml(&Test {
116                x: "Hello, world!".to_string()
117            })?
118            .to_vec()
119            .as_slice()
120        );
121
122        assert_eq!(
123            br#"<?xml version="1.0" encoding="utf-8"?><Bob><x>Hello, world!</x></Bob>"#,
124            to_xml_with_root(
125                "Bob",
126                &Test {
127                    x: "Hello, world!".to_string()
128                }
129            )?
130            .to_vec()
131            .as_slice()
132        );
133        Ok(())
134    }
135}