1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
56//! XML Encoding module that uses Rust lifetimes to make
7//! generating malformed XML a compile error
89use crate::escape::escape;
10use std::error::Error as StdError;
11use std::fmt::{self, Display, Formatter, Write};
1213// currently there's actually no way that encoding can fail but give it time :-)
14#[non_exhaustive]
15#[derive(Debug)]
16pub struct XmlEncodeError {}
1718impl Display for XmlEncodeError {
19fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
20write!(f, "error encoding XML")
21 }
22}
2324impl StdError for XmlEncodeError {}
2526/// 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}
5657impl<'a> XmlWriter<'a> {
58pub fn new(doc: &'a mut String) -> Self {
59Self { doc }
60 }
61}
6263impl<'a> XmlWriter<'a> {
64pub fn start_el<'b, 'c>(&'c mut self, tag: &'b str) -> ElWriter<'c, 'b> {
65write!(self.doc, "<{}", tag).unwrap();
66 ElWriter::new(self.doc, tag)
67 }
68}
6970pub struct ElWriter<'a, 'b> {
71 start: &'b str,
72 doc: Option<&'a mut String>,
73}
7475impl<'a, 'b> ElWriter<'a, 'b> {
76fn new(doc: &'a mut String, start: &'b str) -> ElWriter<'a, 'b> {
77 ElWriter {
78 start,
79 doc: Some(doc),
80 }
81 }
8283pub fn write_attribute(&mut self, key: &str, value: &str) -> &mut Self {
84write!(self.doc(), " {}=\"{}\"", key, escape(value)).unwrap();
85self
86}
8788pub fn write_ns(mut self, namespace: &str, prefix: Option<&str>) -> Self {
89match prefix {
90Some(prefix) => {
91write!(self.doc(), " xmlns:{}=\"{}\"", prefix, escape(namespace)).unwrap()
92 }
93None => write!(self.doc(), " xmlns=\"{}\"", escape(namespace)).unwrap(),
94 }
95self
96}
9798fn write_end(doc: &mut String) {
99write!(doc, ">").unwrap();
100 }
101102fn doc<'c>(&'c mut self) -> &'c mut String
103where
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.
116self.doc.as_mut().unwrap()
117 }
118119pub fn finish(mut self) -> ScopeWriter<'a, 'b> {
120let doc = self.doc.take().unwrap();
121Self::write_end(doc);
122 ScopeWriter {
123 doc,
124 start: self.start,
125 }
126 }
127}
128129impl Drop for ElWriter<'_, '_> {
130fn drop(&mut self) {
131if 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.
135Self::write_end(doc);
136 }
137 }
138}
139140/// Wrap the construction of a tag pair `<a></a>`
141pub struct ScopeWriter<'a, 'b> {
142 doc: &'a mut String,
143 start: &'b str,
144}
145146impl Drop for ScopeWriter<'_, '_> {
147fn drop(&mut self) {
148write!(self.doc, "</{}>", self.start).unwrap();
149 }
150}
151152impl ScopeWriter<'_, '_> {
153pub fn data(&mut self, data: &str) {
154self.doc.write_str(escape(data).as_ref()).unwrap();
155 }
156157pub fn finish(self) {
158// drop will be called which writes the closer to the document
159}
160161pub fn start_el<'b, 'c>(&'c mut self, tag: &'b str) -> ElWriter<'c, 'b> {
162write!(self.doc, "<{}", tag).unwrap();
163 ElWriter::new(self.doc, tag)
164 }
165}
166167#[cfg(test)]
168mod test {
169use crate::encode::XmlWriter;
170use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType};
171172#[test]
173fn forgot_finish() {
174let mut out = String::new();
175176fn writer(out: &mut String) {
177let 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);
183184 assert_ok(validate_body(out, r#"<Hello></Hello>"#, MediaType::Xml));
185 }
186187#[test]
188fn forgot_finish_with_attribute() {
189let mut out = String::new();
190191fn writer(out: &mut String) {
192let 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);
198199 assert_ok(validate_body(
200 out,
201r#"<Hello key="foo"></Hello>"#,
202 MediaType::Xml,
203 ));
204 }
205206#[test]
207fn basic_document_encoding() {
208let mut out = String::new();
209let mut doc_writer = XmlWriter::new(&mut out);
210let mut start_el = doc_writer
211 .start_el("Hello")
212 .write_ns("http://example.com", None);
213 start_el.write_attribute("key", "foo");
214let mut tag = start_el.finish();
215let mut inner = tag.start_el("inner").finish();
216 inner.data("hello world!");
217 inner.finish();
218let more_inner = tag.start_el("inner").finish();
219 more_inner.finish();
220 tag.finish();
221222 assert_ok(validate_body(
223 out,
224r#"<Hello key="foo" xmlns="http://example.com">
225 <inner>hello world!</inner>
226 <inner></inner>
227 </Hello>"#,
228 MediaType::Xml,
229 ));
230 }
231232#[test]
233fn escape_data() {
234let mut s = String::new();
235 {
236let mut doc_writer = XmlWriter::new(&mut s);
237let mut start_el = doc_writer.start_el("Hello");
238 start_el.write_attribute("key", "<key=\"value\">");
239let mut tag = start_el.finish();
240 tag.data("\n\r&");
241 }
242assert_eq!(
243 s,
244r#"<Hello key="<key="value">">

&</Hello>"#
245)
246 }
247}