Skip to main content

tiberius/tds/
xml.rs

1//! The XML containers
2use super::codec::Encode;
3use bytes::{BufMut, BytesMut};
4use std::borrow::BorrowMut;
5use std::sync::Arc;
6
7/// Provides information of the location for the schema.
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct XmlSchema {
11    db_name: String,
12    owner: String,
13    collection: String,
14}
15
16impl XmlSchema {
17    pub(crate) fn new(
18        db_name: impl ToString,
19        owner: impl ToString,
20        collection: impl ToString,
21    ) -> Self {
22        Self {
23            db_name: db_name.to_string(),
24            owner: owner.to_string(),
25            collection: collection.to_string(),
26        }
27    }
28
29    /// Specifies the name of the database where the schema collection is defined.
30    pub fn db_name(&self) -> &str {
31        &self.db_name
32    }
33
34    /// Specifies the name of the relational schema containing the schema collection.
35    pub fn owner(&self) -> &str {
36        &self.owner
37    }
38
39    /// Specifies the name of the XML schema collection to which the type is
40    /// bound.
41    pub fn collection(&self) -> &str {
42        &self.collection
43    }
44}
45
46/// A representation of XML data in TDS. Holds the data as a UTF-8 string and
47/// and optional information about the schema.
48#[derive(Debug, Clone, PartialEq, Eq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50pub struct XmlData {
51    data: String,
52    schema: Option<Arc<XmlSchema>>,
53}
54
55impl XmlData {
56    /// Create a new XmlData with the given string. Validation of the XML data
57    /// happens in the database.
58    pub fn new(data: impl ToString) -> Self {
59        Self {
60            data: data.to_string(),
61            schema: None,
62        }
63    }
64
65    pub(crate) fn set_schema(&mut self, schema: Arc<XmlSchema>) {
66        self.schema = Some(schema);
67    }
68
69    /// Returns information about the schema of the XML file, if existing.
70    #[allow(clippy::option_as_ref_deref)]
71    pub fn schema(&self) -> Option<&XmlSchema> {
72        self.schema.as_ref().map(|s| &**s)
73    }
74
75    /// Takes the XML string out from the struct.
76    pub fn into_string(self) -> String {
77        self.data
78    }
79}
80
81impl std::fmt::Display for XmlData {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(f, "{}", self.data)
84    }
85}
86
87impl AsRef<str> for XmlData {
88    fn as_ref(&self) -> &str {
89        self.data.as_ref()
90    }
91}
92
93impl Encode<BytesMut> for XmlData {
94    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
95        // unknown size
96        dst.put_u64_le(0xfffffffffffffffe_u64);
97
98        // first blob
99        let mut length = 0u32;
100        let len_pos = dst.len();
101
102        // writing the length later
103        dst.put_u32_le(length);
104
105        for chr in self.data.encode_utf16() {
106            length += 1;
107            dst.put_u16_le(chr);
108        }
109
110        // PLP_TERMINATOR, no next blobs
111        dst.put_u32_le(0);
112
113        let dst: &mut [u8] = dst.borrow_mut();
114        let mut dst = &mut dst[len_pos..];
115        dst.put_u32_le(length * 2);
116
117        Ok(())
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn xml_schema_accessors() {
127        let schema = XmlSchema::new("db", "owner", "collection");
128        assert_eq!(schema.db_name(), "db");
129        assert_eq!(schema.owner(), "owner");
130        assert_eq!(schema.collection(), "collection");
131    }
132
133    #[test]
134    fn xml_schema_eq_and_clone() {
135        let a = XmlSchema::new("db", "owner", "collection");
136        let b = a.clone();
137        assert_eq!(a, b);
138    }
139
140    #[test]
141    fn xml_data_without_schema() {
142        let data = XmlData::new("<root/>");
143        assert!(data.schema().is_none());
144        assert_eq!(data.as_ref(), "<root/>");
145        assert_eq!(format!("{}", data), "<root/>");
146        assert_eq!(data.into_string(), "<root/>");
147    }
148
149    #[test]
150    fn xml_data_with_schema() {
151        let schema = Arc::new(XmlSchema::new("db", "owner", "collection"));
152        let mut data = XmlData::new("<a>1</a>");
153        data.set_schema(schema.clone());
154
155        let stored = data.schema().expect("schema present");
156        assert_eq!(stored.db_name(), "db");
157        assert_eq!(stored.owner(), "owner");
158        assert_eq!(stored.collection(), "collection");
159    }
160
161    #[test]
162    fn encode_writes_plp_header_and_backpatches_length() {
163        let mut buf = BytesMut::new();
164        XmlData::new("ab")
165            .encode(&mut buf)
166            .expect("encode succeeds");
167
168        // 8 (unknown-size marker) + 4 (length) + 2*2 (utf16 chars) + 4 (terminator)
169        assert_eq!(buf.len(), 8 + 4 + 4 + 4);
170
171        // unknown size marker
172        assert_eq!(&buf[0..8], &0xfffffffffffffffe_u64.to_le_bytes());
173        // backpatched length is number of chars * 2 bytes
174        assert_eq!(&buf[8..12], &(4u32).to_le_bytes());
175        // 'a' then 'b' as UTF-16LE
176        assert_eq!(&buf[12..16], &[b'a', 0, b'b', 0]);
177        // PLP terminator
178        assert_eq!(&buf[16..20], &(0u32).to_le_bytes());
179    }
180}