use crate::escape::escape;
use std::error::Error as StdError;
use std::fmt::{self, Display, Formatter, Write};
#[non_exhaustive]
#[derive(Debug)]
pub struct XmlEncodeError {}
impl Display for XmlEncodeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "error encoding XML")
}
}
impl StdError for XmlEncodeError {}
pub struct XmlWriter<'a> {
doc: &'a mut String,
}
impl<'a> XmlWriter<'a> {
pub fn new(doc: &'a mut String) -> Self {
Self { doc }
}
}
impl XmlWriter<'_> {
pub fn start_el<'b, 'c>(&'c mut self, tag: &'b str) -> ElWriter<'c, 'b> {
write!(self.doc, "<{tag}").unwrap();
ElWriter::new(self.doc, tag)
}
}
pub struct ElWriter<'a, 'b> {
start: &'b str,
doc: Option<&'a mut String>,
}
impl<'a, 'b> ElWriter<'a, 'b> {
fn new(doc: &'a mut String, start: &'b str) -> ElWriter<'a, 'b> {
ElWriter {
start,
doc: Some(doc),
}
}
pub fn write_attribute(&mut self, key: &str, value: &str) -> &mut Self {
write!(self.doc(), " {}=\"{}\"", key, escape(value)).unwrap();
self
}
pub fn write_ns(mut self, namespace: &str, prefix: Option<&str>) -> Self {
match prefix {
Some(prefix) => {
write!(self.doc(), " xmlns:{}=\"{}\"", prefix, escape(namespace)).unwrap()
}
None => write!(self.doc(), " xmlns=\"{}\"", escape(namespace)).unwrap(),
}
self
}
fn write_end(doc: &mut String) {
write!(doc, ">").unwrap();
}
fn doc<'c>(&'c mut self) -> &'c mut String
where
'a: 'c,
{
self.doc.as_mut().unwrap()
}
pub fn finish(mut self) -> ScopeWriter<'a, 'b> {
let doc = self.doc.take().unwrap();
Self::write_end(doc);
ScopeWriter {
doc,
start: self.start,
}
}
}
impl Drop for ElWriter<'_, '_> {
fn drop(&mut self) {
if let Some(doc) = self.doc.take() {
Self::write_end(doc);
}
}
}
pub struct ScopeWriter<'a, 'b> {
doc: &'a mut String,
start: &'b str,
}
impl Drop for ScopeWriter<'_, '_> {
fn drop(&mut self) {
write!(self.doc, "</{}>", self.start).unwrap();
}
}
impl ScopeWriter<'_, '_> {
pub fn data(&mut self, data: &str) {
self.doc.write_str(escape(data).as_ref()).unwrap();
}
pub fn finish(self) {
}
pub fn start_el<'b, 'c>(&'c mut self, tag: &'b str) -> ElWriter<'c, 'b> {
write!(self.doc, "<{tag}").unwrap();
ElWriter::new(self.doc, tag)
}
}
#[cfg(test)]
mod test {
use crate::encode::XmlWriter;
use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType};
#[test]
fn forgot_finish() {
let mut out = String::new();
fn writer(out: &mut String) {
let mut doc_writer = XmlWriter::new(out);
doc_writer.start_el("Hello");
}
writer(&mut out);
assert_ok(validate_body(out, r#"<Hello></Hello>"#, MediaType::Xml));
}
#[test]
fn forgot_finish_with_attribute() {
let mut out = String::new();
fn writer(out: &mut String) {
let mut doc_writer = XmlWriter::new(out);
doc_writer.start_el("Hello").write_attribute("key", "foo");
}
writer(&mut out);
assert_ok(validate_body(
out,
r#"<Hello key="foo"></Hello>"#,
MediaType::Xml,
));
}
#[test]
fn basic_document_encoding() {
let mut out = String::new();
let mut doc_writer = XmlWriter::new(&mut out);
let mut start_el = doc_writer
.start_el("Hello")
.write_ns("http://example.com", None);
start_el.write_attribute("key", "foo");
let mut tag = start_el.finish();
let mut inner = tag.start_el("inner").finish();
inner.data("hello world!");
inner.finish();
let more_inner = tag.start_el("inner").finish();
more_inner.finish();
tag.finish();
assert_ok(validate_body(
out,
r#"<Hello key="foo" xmlns="http://example.com">
<inner>hello world!</inner>
<inner></inner>
</Hello>"#,
MediaType::Xml,
));
}
#[test]
fn escape_data() {
let mut s = String::new();
{
let mut doc_writer = XmlWriter::new(&mut s);
let mut start_el = doc_writer.start_el("Hello");
start_el.write_attribute("key", "<key=\"value\">");
let mut tag = start_el.finish();
tag.data("\n\r&");
}
assert_eq!(
s,
r#"<Hello key="<key="value">">

&</Hello>"#
)
}
}