aws_smithy_xml/
encode.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! XML Encoding module that uses Rust lifetimes to make
7//! generating malformed XML a compile error
8
9use crate::escape::escape;
10use std::error::Error as StdError;
11use std::fmt::{self, Display, Formatter, Write};
12
13// currently there's actually no way that encoding can fail but give it time :-)
14#[non_exhaustive]
15#[derive(Debug)]
16pub struct XmlEncodeError {}
17
18impl Display for XmlEncodeError {
19    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
20        write!(f, "error encoding XML")
21    }
22}
23
24impl StdError for XmlEncodeError {}
25
26/// XmlWriter Abstraction
27///
28/// XmlWriter (and friends) make generating an invalid XML document a type error. Nested branches
29/// of the Xml document mutable borrow from the root. You cannot continue writing to the root
30/// until the nested branch is dropped and dropping the nested branch writes the terminator (e.g.
31/// closing element).
32///
33/// The one exception to this rule is names—it is possible to construct an invalid Xml Name. However,
34/// names are always known ahead of time and always static, so this would be obvious from the code.
35///
36/// Furthermore, once `const panic` stabilizes, we'll be able to make an invalid XmlName a compiler
37/// error.
38///
39/// # Examples
40/// ```rust
41/// use aws_smithy_xml::encode::XmlWriter;
42/// let mut s = String::new();
43/// let mut doc = XmlWriter::new(&mut s);
44/// let mut start_el = doc.start_el("Root")
45///     .write_ns("http://example.com", None);
46/// let mut start_tag = start_el.finish();
47/// start_tag.data("hello");
48/// start_tag.finish();
49/// assert_eq!(s, "<Root xmlns=\"http://example.com\">hello</Root>");
50/// ```
51///
52/// See `tests/handwritten_serializers.rs` for more usage examples.
53pub struct XmlWriter<'a> {
54    doc: &'a mut String,
55}
56
57impl<'a> XmlWriter<'a> {
58    pub fn new(doc: &'a mut String) -> Self {
59        Self { doc }
60    }
61}
62
63impl<'a> XmlWriter<'a> {
64    pub fn start_el<'b, 'c>(&'c mut self, tag: &'b str) -> ElWriter<'c, 'b> {
65        write!(self.doc, "<{}", tag).unwrap();
66        ElWriter::new(self.doc, tag)
67    }
68}
69
70pub struct ElWriter<'a, 'b> {
71    start: &'b str,
72    doc: Option<&'a mut String>,
73}
74
75impl<'a, 'b> ElWriter<'a, 'b> {
76    fn new(doc: &'a mut String, start: &'b str) -> ElWriter<'a, 'b> {
77        ElWriter {
78            start,
79            doc: Some(doc),
80        }
81    }
82
83    pub fn write_attribute(&mut self, key: &str, value: &str) -> &mut Self {
84        write!(self.doc(), " {}=\"{}\"", key, escape(value)).unwrap();
85        self
86    }
87
88    pub fn write_ns(mut self, namespace: &str, prefix: Option<&str>) -> Self {
89        match prefix {
90            Some(prefix) => {
91                write!(self.doc(), " xmlns:{}=\"{}\"", prefix, escape(namespace)).unwrap()
92            }
93            None => write!(self.doc(), " xmlns=\"{}\"", escape(namespace)).unwrap(),
94        }
95        self
96    }
97
98    fn write_end(doc: &mut String) {
99        write!(doc, ">").unwrap();
100    }
101
102    fn doc<'c>(&'c mut self) -> &'c mut String
103    where
104        'a: 'c,
105    {
106        // The self.doc is an Option in order to signal whether the closing '>' has been emitted
107        // already (None) or not (Some). It ensures the following invariants:
108        // - If finish() has been called, then self.doc is None and therefore no more writes
109        //   to the &mut String are possible.
110        // - When drop() is called, if self.doc is Some, then finish() has not (and will not)
111        //   be called, and therefore drop() should close the tag represented by this struct.
112        //
113        // Since this function calls unwrap(), it must not be called from finish() or drop().
114        // As finish() consumes self, calls to this method from any other method will not encounter
115        // a None value in self.doc.
116        self.doc.as_mut().unwrap()
117    }
118
119    pub fn finish(mut self) -> ScopeWriter<'a, 'b> {
120        let doc = self.doc.take().unwrap();
121        Self::write_end(doc);
122        ScopeWriter {
123            doc,
124            start: self.start,
125        }
126    }
127}
128
129impl Drop for ElWriter<'_, '_> {
130    fn drop(&mut self) {
131        if let Some(doc) = self.doc.take() {
132            // Calls to write_end() are always preceded by self.doc.take(). The value in self.doc
133            // is set to Some initially, and is never reset to Some after being taken. Since this
134            // transition to None happens only once, we will never double-close the XML element.
135            Self::write_end(doc);
136        }
137    }
138}
139
140/// Wrap the construction of a tag pair `<a></a>`
141pub struct ScopeWriter<'a, 'b> {
142    doc: &'a mut String,
143    start: &'b str,
144}
145
146impl Drop for ScopeWriter<'_, '_> {
147    fn drop(&mut self) {
148        write!(self.doc, "</{}>", self.start).unwrap();
149    }
150}
151
152impl ScopeWriter<'_, '_> {
153    pub fn data(&mut self, data: &str) {
154        self.doc.write_str(escape(data).as_ref()).unwrap();
155    }
156
157    pub fn finish(self) {
158        // drop will be called which writes the closer to the document
159    }
160
161    pub fn start_el<'b, 'c>(&'c mut self, tag: &'b str) -> ElWriter<'c, 'b> {
162        write!(self.doc, "<{}", tag).unwrap();
163        ElWriter::new(self.doc, tag)
164    }
165}
166
167#[cfg(test)]
168mod test {
169    use crate::encode::XmlWriter;
170    use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType};
171
172    #[test]
173    fn forgot_finish() {
174        let mut out = String::new();
175
176        fn writer(out: &mut String) {
177            let mut doc_writer = XmlWriter::new(out);
178            doc_writer.start_el("Hello");
179            // We intentionally "forget" to call finish() on the ElWriter:
180            // when the XML structs get dropped, the element must get closed automatically.
181        }
182        writer(&mut out);
183
184        assert_ok(validate_body(out, r#"<Hello></Hello>"#, MediaType::Xml));
185    }
186
187    #[test]
188    fn forgot_finish_with_attribute() {
189        let mut out = String::new();
190
191        fn writer(out: &mut String) {
192            let mut doc_writer = XmlWriter::new(out);
193            doc_writer.start_el("Hello").write_attribute("key", "foo");
194            // We intentionally "forget" to call finish() on the ElWriter:
195            // when the XML structs get dropped, the element must get closed automatically.
196        }
197        writer(&mut out);
198
199        assert_ok(validate_body(
200            out,
201            r#"<Hello key="foo"></Hello>"#,
202            MediaType::Xml,
203        ));
204    }
205
206    #[test]
207    fn basic_document_encoding() {
208        let mut out = String::new();
209        let mut doc_writer = XmlWriter::new(&mut out);
210        let mut start_el = doc_writer
211            .start_el("Hello")
212            .write_ns("http://example.com", None);
213        start_el.write_attribute("key", "foo");
214        let mut tag = start_el.finish();
215        let mut inner = tag.start_el("inner").finish();
216        inner.data("hello world!");
217        inner.finish();
218        let more_inner = tag.start_el("inner").finish();
219        more_inner.finish();
220        tag.finish();
221
222        assert_ok(validate_body(
223            out,
224            r#"<Hello key="foo" xmlns="http://example.com">
225                    <inner>hello world!</inner>
226                    <inner></inner>
227                </Hello>"#,
228            MediaType::Xml,
229        ));
230    }
231
232    #[test]
233    fn escape_data() {
234        let mut s = String::new();
235        {
236            let mut doc_writer = XmlWriter::new(&mut s);
237            let mut start_el = doc_writer.start_el("Hello");
238            start_el.write_attribute("key", "<key=\"value\">");
239            let mut tag = start_el.finish();
240            tag.data("\n\r&");
241        }
242        assert_eq!(
243            s,
244            r#"<Hello key="&lt;key=&quot;value&quot;&gt;">&#xA;&#xD;&amp;</Hello>"#
245        )
246    }
247}