use std::collections::HashMap;
use std::io;
use std::iter::Peekable;
use std::num::ParseIntError;
use xml;
use xml::reader::{EventReader, Events, ParserConfig, XmlEvent};
use xml::writer::EventWriter;
use crate::{error::RusotoError, request::HttpResponse};
#[derive(Debug)]
pub struct XmlParseError(pub String);
impl XmlParseError {
pub fn new(msg: &str) -> XmlParseError {
XmlParseError(msg.to_string())
}
}
pub type XmlStack<'a> = Peekable<Events<&'a [u8]>>;
pub trait Peek {
fn peek(&mut self) -> Option<&Result<XmlEvent, xml::reader::Error>>;
}
pub trait Next {
fn next(&mut self) -> Option<Result<XmlEvent, xml::reader::Error>>;
}
pub struct XmlResponse<'b> {
xml_stack: Peekable<Events<&'b [u8]>>, }
impl<'b> XmlResponse<'b> {
pub fn new(stack: Peekable<Events<&'b [u8]>>) -> XmlResponse {
XmlResponse { xml_stack: stack }
}
}
impl<'b> Peek for XmlResponse<'b> {
fn peek(&mut self) -> Option<&Result<XmlEvent, xml::reader::Error>> {
while let Some(&Ok(XmlEvent::Whitespace(_))) = self.xml_stack.peek() {
self.xml_stack.next();
}
self.xml_stack.peek()
}
}
impl<'b> Next for XmlResponse<'b> {
fn next(&mut self) -> Option<Result<XmlEvent, xml::reader::Error>> {
let mut maybe_event;
loop {
maybe_event = self.xml_stack.next();
match maybe_event {
Some(Ok(XmlEvent::Whitespace(_))) => {}
_ => break,
}
}
maybe_event
}
}
impl From<ParseIntError> for XmlParseError {
fn from(_e: ParseIntError) -> XmlParseError {
XmlParseError::new("ParseIntError")
}
}
pub fn string_field<T: Peek + Next>(name: &str, stack: &mut T) -> Result<String, XmlParseError> {
start_element(name, stack)?;
let value = characters(stack)?;
end_element(name, stack)?;
Ok(value)
}
pub fn write_characters_element<W>(
writer: &mut EventWriter<W>,
name: &str,
value_str: &str,
) -> Result<(), xml::writer::Error>
where
W: io::Write,
{
writer.write(xml::writer::XmlEvent::start_element(name))?;
writer.write(xml::writer::XmlEvent::characters(value_str))?;
writer.write(xml::writer::XmlEvent::end_element())
}
pub fn deserialize_primitive<T: Peek + Next, U>(
tag_name: &str,
stack: &mut T,
deserialize: fn(String) -> Result<U, XmlParseError>,
) -> Result<U, XmlParseError> {
start_element(tag_name, stack)?;
let obj = deserialize(characters(stack)?)?;
end_element(tag_name, stack)?;
Ok(obj)
}
pub fn characters<T: Peek + Next>(stack: &mut T) -> Result<String, XmlParseError> {
{
let current = stack.peek();
if let Some(&Ok(XmlEvent::EndElement { .. })) = current {
return Ok("".to_string());
}
}
match stack.next() {
Some(Ok(XmlEvent::Characters(data))) |
Some(Ok(XmlEvent::CData(data))) => {
Ok(data)
},
_ => Err(XmlParseError::new("Expected characters")),
}
}
pub fn peek_at_name<T: Peek + Next>(stack: &mut T) -> Result<String, XmlParseError> {
let current = stack.peek();
if let Some(&Ok(XmlEvent::StartElement { ref name, .. })) = current {
Ok(name.local_name.to_string())
} else {
Ok("".to_string())
}
}
pub fn start_element<T: Peek + Next>(
element_name: &str,
stack: &mut T,
) -> Result<HashMap<String, String>, XmlParseError> {
let next = stack.next();
if let Some(Ok(XmlEvent::StartElement {
name, attributes, ..
})) = next
{
if name.local_name == element_name {
let mut attr_map = HashMap::new();
for attr in attributes {
attr_map.insert(attr.name.local_name, attr.value);
}
Ok(attr_map)
} else {
Err(XmlParseError::new(&format!(
"START Expected {} got {}",
element_name, name.local_name
)))
}
} else {
Err(XmlParseError::new(&format!(
"Expected StartElement {} got {:#?}",
element_name, next
)))
}
}
pub fn end_element<T: Peek + Next>(element_name: &str, stack: &mut T) -> Result<(), XmlParseError> {
let next = stack.next();
if let Some(Ok(XmlEvent::EndElement { name, .. })) = next {
if name.local_name == element_name {
Ok(())
} else {
Err(XmlParseError::new(&format!(
"END Expected {} got {}",
element_name, name.local_name
)))
}
} else {
Err(XmlParseError::new(&format!(
"Expected EndElement {} got {:?}",
element_name, next
)))
}
}
pub fn skip_tree<T: Peek + Next>(stack: &mut T) {
let mut deep: usize = 0;
loop {
match stack.next() {
None => break,
Some(Ok(XmlEvent::StartElement { .. })) => deep += 1,
Some(Ok(XmlEvent::EndElement { .. })) => {
if deep > 1 {
deep -= 1;
} else {
break;
}
}
_ => (),
}
}
}
pub fn find_start_element<T: Peek + Next>(stack: &mut T) {
loop {
match stack.peek() {
Some(&Ok(XmlEvent::StartElement { .. })) => break,
Some(&Ok(_)) => {
stack.next().unwrap().unwrap();
}
Some(&Err(_)) => break,
None => break,
}
}
}
pub fn deserialize_elements<T, S, F>(
tag_name: &str,
stack: &mut T,
mut handle_element: F,
) -> Result<S, XmlParseError>
where
T: Peek + Next,
S: Default,
F: FnMut(&str, &mut T, &mut S) -> Result<(), XmlParseError>,
{
let mut obj = S::default();
start_element(tag_name, stack)?;
loop {
match stack.peek() {
Some(&Ok(XmlEvent::EndElement { .. })) => break,
Some(&Ok(XmlEvent::StartElement { ref name, .. })) => {
let local_name = name.local_name.to_owned();
handle_element(&local_name, stack, &mut obj)?;
}
_ => {
stack.next();
}
}
}
end_element(tag_name, stack)?;
Ok(obj)
}
pub async fn parse_response<T, E>(
response: &mut HttpResponse,
deserialize: fn(&str, &mut XmlResponse<'_>) -> Result<T, XmlParseError>,
) -> Result<T, RusotoError<E>>
where
T: Default,
{
let xml_response = response.buffer().await.map_err(RusotoError::HttpDispatch)?;
if xml_response.body.is_empty() {
Ok(T::default())
} else {
let reader = EventReader::new_with_config(
xml_response.body.as_ref(),
ParserConfig::new().trim_whitespace(false),
);
let mut stack = XmlResponse::new(reader.into_iter().peekable());
let _start_document = stack.next();
let actual_tag_name = peek_at_name(&mut stack)?;
Ok(deserialize(&actual_tag_name, &mut stack)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Read;
#[test]
fn peek_at_name_happy_path() {
let mut file = File::open("test_resources/list_queues_with_queue.xml").unwrap();
let mut body = String::new();
let _size = file.read_to_string(&mut body);
let my_parser = EventReader::new(body.as_bytes());
let my_stack = my_parser.into_iter().peekable();
let mut reader = XmlResponse::new(my_stack);
loop {
reader.next();
match peek_at_name(&mut reader) {
Ok(data) => {
if data == "QueueUrl" {
return;
}
}
Err(_) => panic!("Couldn't peek at name"),
}
}
}
#[test]
fn start_element_happy_path() {
let mut file = File::open("test_resources/list_queues_with_queue.xml").unwrap();
let mut body = String::new();
let _size = file.read_to_string(&mut body);
let my_parser = EventReader::new(body.as_bytes());
let my_stack = my_parser.into_iter().peekable();
let mut reader = XmlResponse::new(my_stack);
reader.next();
reader.next();
match start_element("ListQueuesResult", &mut reader) {
Ok(_) => (),
Err(_) => panic!("Couldn't find start element"),
}
}
#[test]
fn string_field_happy_path() {
let mut file = File::open("test_resources/list_queues_with_queue.xml").unwrap();
let mut body = String::new();
let _size = file.read_to_string(&mut body);
let my_parser = EventReader::new(body.as_bytes());
let my_stack = my_parser.into_iter().peekable();
let mut reader = XmlResponse::new(my_stack);
reader.next();
reader.next();
reader.next();
let my_chars = string_field("QueueUrl", &mut reader).unwrap();
assert_eq!(
my_chars,
"https://sqs.us-east-1.amazonaws.com/347452556413/testqueue"
)
}
#[test]
fn end_element_happy_path() {
let mut file = File::open("test_resources/list_queues_with_queue.xml").unwrap();
let mut body = String::new();
let _size = file.read_to_string(&mut body);
let my_parser = EventReader::new(body.as_bytes());
let my_stack = my_parser.into_iter().peekable();
let mut reader = XmlResponse::new(my_stack);
reader.next();
reader.next();
reader.next();
reader.next();
reader.next();
reader.next();
match end_element("ListQueuesResult", &mut reader) {
Ok(_) => (),
Err(_) => panic!("Couldn't find end element"),
}
}
#[test]
fn test_find_start_element() {
let body = include_bytes!("../../../test_resources/list_queues_with_queue.xml");
let parser = EventReader::new(&body[..]);
let stack = parser.into_iter().peekable();
let mut reader = XmlResponse::new(stack);
find_start_element(&mut reader);
assert_eq!(peek_at_name(&mut reader).unwrap(), "ListQueuesResponse");
find_start_element(&mut reader);
assert_eq!(peek_at_name(&mut reader).unwrap(), "ListQueuesResponse");
}
}