diff options
author | Kaleb Elwert <belak@coded.io> | 2020-01-28 02:35:49 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-28 02:35:49 -0800 |
commit | 0a1f18b51f29dda73755ff12be98b84342ce72ca (patch) | |
tree | adbb8be12133294d8f6faa087e8e3d3b7aa84e6f | |
parent | d5f46b11ef642a8f1675f7cb3a1d8cda450bdb13 (diff) |
Initial code commit (#2)
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | src/error.rs | 184 | ||||
-rw-r--r-- | src/lib.rs | 256 | ||||
-rw-r--r-- | src/utils.rs | 99 | ||||
-rw-r--r-- | src/value.rs | 562 |
5 files changed, 1106 insertions, 2 deletions
@@ -1,5 +1,7 @@ [package] name = "simple-xmlrpc" +description = "A simple XML-RPC implementation for Rust" +license = "MIT" version = "0.1.0" authors = ["Kaleb Elwert <belak@coded.io>"] edition = "2018" @@ -7,3 +9,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0" +base64 = "0.11" +iso8601 = "0.3" +quick-xml = "0.17" +thiserror = "1.0" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..ae30aec --- /dev/null +++ b/src/error.rs @@ -0,0 +1,184 @@ +use std::collections::BTreeMap; +use std::convert::TryFrom; +use std::num::{ParseFloatError, ParseIntError}; +use std::result; +use std::string::FromUtf8Error; + +use base64::DecodeError; +use quick_xml::Error as XmlError; +use thiserror::Error as ThisError; + +use crate::Value; + +/// Errors that can occur when trying to perform an XML-RPC request. +/// +/// This can be a lower-level error (for example, the HTTP request failed), a problem with the +/// server (maybe it's not implementing XML-RPC correctly), or just a failure to execute the +/// operation. +#[derive(ThisError, Debug)] +pub enum Error { + /// The response could not be parsed. This can happen when the server doesn't correctly + /// implement the XML-RPC spec. + #[error("parse error: {0}")] + ParseError(#[from] ParseError), + + /// The response could not be encoded. + #[error("encoding error: {0}")] + EncodingError(#[from] EncodingError), + + /// The server returned a `<fault>` response, indicating that the execution of the call + /// encountered a problem (for example, an invalid (number of) arguments was passed). + #[error("server fault: {0}")] + Fault(#[from] Fault), +} + +/// Error while parsing XML. +#[derive(ThisError, Debug)] +pub enum ParseError { + #[error("malformed XML: {0}")] + XmlError(#[from] XmlError), + + #[error("malformed XML: {0}")] + ParseIntError(#[from] ParseIntError), + + #[error("malformed XML: {0}")] + ParseFloatError(#[from] ParseFloatError), + + #[error("malformed XML: {0}")] + Base64DecodeError(#[from] DecodeError), + + #[error("malformed XML: {0}")] + DateTimeDecodeError(String), + + #[error("malformed XML: invalid boolean value: {0}")] + BooleanDecodeError(String), + + #[error("malformed UTF-8: {0}")] + Utf8Error(#[from] FromUtf8Error), + + #[error("unexpected tag: found {0}, expected {1}")] + UnexpectedTag(String, String), + + #[error("unexpected error: {0}, expected tag {1}")] + UnexpectedError(anyhow::Error, String), + + #[error("unexpected event: expected tag {0}")] + UnexpectedEvent(String), + + #[error("unexpected EOF: expected tag {0}")] + UnexpectedEOF(String), + + #[error("tag not found: {0}")] + TagNotFound(String), + + #[error("fault: {0}")] + ParseFaultError(String), +} + +/// Error while encoding XML. +#[derive(ThisError, Debug)] +pub enum EncodingError { + #[error("malformed UTF-8: {0}")] + Utf8Error(#[from] FromUtf8Error), + + #[error("XML error: {0}")] + XmlError(#[from] XmlError), +} + +pub type Result<T> = result::Result<T, Error>; + +/// A `<fault>` response, indicating that a request failed. +/// +/// The XML-RPC specification requires that a `<faultCode>` and `<faultString>` is returned in the +/// `<fault>` case, further describing the error. +#[derive(ThisError, Debug, PartialEq, Eq)] +#[error("{fault_string} ({fault_code})")] +pub struct Fault { + /// An application-specific error code. + pub fault_code: i32, + /// Human-readable error description. + pub fault_string: String, +} + +/// Creates a `Fault` from a `Value`. +/// +/// The `Value` must be a `Value::Struct` with a `faultCode` and `faultString` field (and no +/// other fields). +impl TryFrom<Value> for Fault { + type Error = ParseError; + + fn try_from(value: Value) -> std::result::Result<Self, Self::Error> { + match value { + Value::Struct(ref map) => { + match (map.get("faultCode"), map.get("faultString")) { + (Some(&Value::Int(fault_code)), Some(&Value::String(ref fault_string))) => { + if map.len() != 2 { + // incorrect field count + Err(ParseError::ParseFaultError( + "extra fields returned in fault".into(), + )) + } else { + Ok(Fault { + fault_code, + fault_string: fault_string.to_string(), + }) + } + } + _ => Err(ParseError::ParseFaultError( + "missing either faultCode or faultString".into(), + )), + } + } + _ => Err(ParseError::ParseFaultError("expected struct".into())), + } + } +} + +/// Turns this `Fault` into an equivalent `Value`. +/// +/// The returned value can be parsed back into a `Fault` using `Fault::try_from` +/// or returned as a `<fault>` error response by serializing it into a +/// `<fault></fault>` tag. +impl From<&Fault> for Value { + fn from(other: &Fault) -> Self { + let mut map = BTreeMap::new(); + map.insert("faultCode".to_string(), Value::from(other.fault_code)); + map.insert( + "faultString".to_string(), + Value::from(other.fault_string.clone()), + ); + + Value::Struct(map) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::error; + + #[test] + fn fault_roundtrip() { + let input = Fault { + fault_code: -123456, + fault_string: "The Bald Lazy House Jumps Over The Hyperactive Kitten".to_string(), + }; + + assert_eq!(Fault::try_from(Value::from(&input)).unwrap(), input); + } + + #[test] + fn error_impls_error() { + fn assert_error<T: error::Error>() {} + + assert_error::<Error>(); + } + + #[test] + fn error_is_send_sync() { + fn assert_send_sync<T: Send + Sync>() {} + + assert_send_sync::<Error>(); + } +} @@ -1,7 +1,259 @@ +use quick_xml::{Reader, Writer}; + +mod error; +mod utils; +mod value; + +use utils::{ReaderExt, WriterExt}; + +pub use crate::error::{Error, Result}; +pub use crate::value::Value; + +pub fn parse_response(data: &str) -> Result<Value> { + let mut reader = Reader::from_str(data); + reader.expand_empty_elements(true); + reader.trim_text(true); + + let mut buf = Vec::new(); + + // We expect a value tag first, followed by a value. Note that the inner + // read will properly handle ensuring we get a closing value tag. + reader.expect_tag(b"methodResponse", &mut buf)?; + + Value::read_response_from_reader(&mut reader, &mut buf) +} + +pub fn parse_value(data: &str) -> Result<Value> { + let mut reader = Reader::from_str(data); + reader.expand_empty_elements(true); + reader.trim_text(true); + + let mut buf = Vec::new(); + + // We expect a value tag first, followed by a value. Note that the inner + // read will properly handle ensuring we get a closing value tag. + reader.expect_tag(b"value", &mut buf)?; + + Value::read_value_from_reader(&mut reader, &mut buf) +} + +pub fn stringify_request(name: &str, args: &[Value]) -> Result<String> { + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + + writer + .write(br#"<?xml version="1.0" encoding="utf-8"?>"#) + .map_err(error::EncodingError::from)?; + + writer.write_start_tag(b"methodCall")?; + writer.write_tag(b"methodName", name)?; + + writer.write_start_tag(b"params")?; + for value in args { + writer.write_start_tag(b"param")?; + + writer + .write(value.stringify()?.as_ref()) + .map_err(error::EncodingError::from)?; + + writer.write_end_tag(b"param")?; + } + writer.write_end_tag(b"params")?; + writer.write_end_tag(b"methodCall")?; + + Ok(String::from_utf8(buf).map_err(error::EncodingError::from)?) +} + #[cfg(test)] mod tests { + use super::*; + + #[test] + fn test_stringify_request() { + assert_eq!( + stringify_request("hello world", &[]).unwrap(), + r#"<?xml version="1.0" encoding="utf-8"?><methodCall><methodName>hello world</methodName><params></params></methodCall>"#.to_owned() + ) + } + + /// A 32-bit signed integer (`<i4>` or `<int>`). + #[test] + fn parse_int_values() { + assert_eq!( + parse_value("<value><i4>42</i4></value>").unwrap().as_i32(), + Some(42) + ); + + assert_eq!( + parse_value("<value><int>-42</int></value>") + .unwrap() + .as_i32(), + Some(-42) + ); + + assert_eq!( + parse_value("<value><int>2147483647</int></value>") + .unwrap() + .as_i32(), + Some(2147483647) + ); + } + + /// A 64-bit signed integer (`<i8>`). + #[test] + fn parse_long_values() { + assert_eq!( + parse_value("<value><i8>42</i8></value>").unwrap().as_i64(), + Some(42) + ); + + assert_eq!( + parse_value("<value><i8>9223372036854775807</i8></value>") + .unwrap() + .as_i64(), + Some(9223372036854775807) + ); + } + + /// A boolean value (`<boolean>`, 0 == `false`, 1 == `true`). + #[test] + fn parse_boolean_values() { + assert_eq!( + parse_value("<value><boolean>1</boolean></value>") + .unwrap() + .as_bool(), + Some(true) + ); + assert_eq!( + parse_value("<value><boolean>0</boolean></value>") + .unwrap() + .as_bool(), + Some(false) + ); + } + + /// A string (`<string>`). Note that these can also appear as a raw + /// value tag as well. + #[test] + fn parse_string_values() { + assert_eq!( + parse_value("<value><string>hello</string></value>") + .unwrap() + .as_str(), + Some("hello") + ); + + assert_eq!( + parse_value("<value>world</value>").unwrap().as_str(), + Some("world") + ); + + assert_eq!(parse_value("<value />").unwrap().as_str(), Some("")); + } + + /// A double-precision IEEE 754 floating point number (`<double>`). + #[test] + fn parse_double_values() { + assert_eq!( + parse_value("<value><double>1</double></value>") + .unwrap() + .as_f64(), + Some(1.0) + ); + assert_eq!( + parse_value("<value><double>0</double></value>") + .unwrap() + .as_f64(), + Some(0.0) + ); + assert_eq!( + parse_value("<value><double>42</double></value>") + .unwrap() + .as_f64(), + Some(42.0) + ); + assert_eq!( + parse_value("<value><double>3.14</double></value>") + .unwrap() + .as_f64(), + Some(3.14) + ); + assert_eq!( + parse_value("<value><double>-3.14</double></value>") + .unwrap() + .as_f64(), + Some(-3.14) + ); + } + + /// An ISO 8601 formatted date/time value (`<dateTime.iso8601>`). + + /// Base64-encoded binary data (`<base64>`). + #[test] + fn parse_base64_values() { + assert_eq!( + parse_value("<value><base64>aGVsbG8gd29ybGQ=</base64></value>") + .unwrap() + .as_bytes(), + Some(&b"hello world"[..]) + ); + } + + /// A mapping of named values (`<struct>`). + + /// A list of arbitrary (heterogeneous) values (`<array>`). + #[test] + fn parse_array_values() { + assert_eq!( + parse_value( + "<value><array><data><value></value><value><nil /></value></data></array></value>" + ) + .unwrap() + .as_array(), + Some(&[Value::String("".to_owned()), Value::Nil][..]) + ); + } + + /// The empty (Unit) value (`<nil/>`). + #[test] + fn parse_nil_values() { + assert_eq!(parse_value("<value><nil /></value>").unwrap(), Value::Nil); + } + #[test] - fn it_works() { - assert_eq!(2 + 2, 4); + fn parse_fault() { + let err = parse_response( + r#"<?xml version="1.0" encoding="utf-8"?> + <methodResponse> + <fault> + <value> + <struct> + <member> + <name>faultCode</name> + <value><int>4</int></value> + </member> + <member> + <name>faultString</name> + <value><string>Too many parameters.</string></value> + </member> + </struct> + </value> + </fault> + </methodResponse>"#, + ) + .unwrap_err(); + + match err { + error::Error::Fault(f) => assert_eq!( + f, + error::Fault { + fault_code: 4, + fault_string: "Too many parameters.".into(), + } + ), + _ => { + assert!(false); + } + } } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..41d3729 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,99 @@ +use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; +use quick_xml::{Reader, Writer}; + +use crate::error::{EncodingError, ParseError, Result}; + +pub(crate) trait ReaderExt { + fn expect_tag(&mut self, end: &[u8], buf: &mut Vec<u8>) -> Result<()>; +} + +impl<B> ReaderExt for Reader<B> +where + B: std::io::BufRead, +{ + fn expect_tag(&mut self, end: &[u8], buf: &mut Vec<u8>) -> Result<()> { + loop { + match self.read_event(buf) { + // TODO: this isn't exactly right, but it's good enough for now. + Ok(Event::Decl(ref _d)) => continue, + Ok(Event::Start(ref e)) => { + if e.name() != end { + return Err(ParseError::UnexpectedTag( + String::from_utf8_lossy(e.name()).into(), + String::from_utf8_lossy(end).into(), + ) + .into()); + } + + break; + } + Ok(_e) => { + return Err(ParseError::UnexpectedEvent( + //e, + String::from_utf8_lossy(end).into(), + ) + .into()); + } + Err(e) => { + return Err(ParseError::UnexpectedError( + e.into(), + String::from_utf8_lossy(end).into(), + ) + .into()) + } + }; + } + + Ok(()) + } +} + +pub(crate) trait WriterExt { + // High level functions + fn write_tag(&mut self, tag: &[u8], text: &str) -> Result<()> { + self.write_start_tag(tag)?; + self.write_text(text)?; + self.write_end_tag(tag)?; + Ok(()) + } + + fn write_safe_tag(&mut self, tag: &[u8], text: &str) -> Result<()> { + self.write_start_tag(tag)?; + self.write_safe_text(text)?; + self.write_end_tag(tag)?; + Ok(()) + } + + // Building blocks + fn write_start_tag(&mut self, tag: &[u8]) -> Result<()>; + fn write_end_tag(&mut self, tag: &[u8]) -> Result<()>; + fn write_text(&mut self, text: &str) -> Result<()>; + fn write_safe_text(&mut self, text: &str) -> Result<()>; +} + +impl<W> WriterExt for Writer<W> +where + W: std::io::Write, +{ + fn write_start_tag(&mut self, tag: &[u8]) -> Result<()> { + self.write_event(Event::Start(BytesStart::borrowed_name(tag))) + .map_err(EncodingError::from)?; + Ok(()) + } + + fn write_end_tag(&mut self, tag: &[u8]) -> Result<()> { + self.write_event(Event::End(BytesEnd::borrowed(tag))) + .map_err(EncodingError::from)?; + Ok(()) + } + fn write_text(&mut self, text: &str) -> Result<()> { + self.write_event(Event::Text(BytesText::from_plain_str(text))) + .map_err(EncodingError::from)?; + Ok(()) + } + fn write_safe_text(&mut self, text: &str) -> Result<()> { + self.write_event(Event::Text(BytesText::from_escaped_str(text))) + .map_err(EncodingError::from)?; + Ok(()) + } +} diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..7defbbc --- /dev/null +++ b/src/value.rs @@ -0,0 +1,562 @@ +use std::collections::BTreeMap; +use std::convert::TryFrom; + +use base64::{decode as decode_base64, encode as encode_base64}; +use iso8601::{datetime as parse_datetime, DateTime}; +use quick_xml::events::Event; +use quick_xml::{Reader, Writer}; + +use crate::error::{EncodingError, Error, Fault, ParseError, Result}; +use crate::utils::{ReaderExt, WriterExt}; + +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + /// A 32-bit signed integer (`<i4>` or `<int>`). + Int(i32), + /// A 64-bit signed integer (`<i8>`). + Int64(i64), + /// A boolean value (`<boolean>`, 0 == `false`, 1 == `true`). + Bool(bool), + /// A string (`<string>`). + String(String), + /// A double-precision IEEE 754 floating point number (`<double>`). + Double(f64), + /// An ISO 8601 formatted date/time value (`<dateTime.iso8601>`). + DateTime(DateTime), + /// Base64-encoded binary data (`<base64>`). + Base64(Vec<u8>), + /// A mapping of named values (`<struct>`). + Struct(BTreeMap<String, Value>), + /// A list of arbitrary (heterogeneous) values (`<array>`). + Array(Vec<Value>), + /// The empty (Unit) value (`<nil/>`). + Nil, +} + +// Public API definitions +impl Value { + pub fn stringify(&self) -> Result<String> { + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + + writer.write_start_tag(b"value")?; + + match *self { + Value::Int(i) => { + writer.write_safe_tag(b"i4", &i.to_string()[..])?; + } + Value::Int64(i) => { + writer.write_safe_tag(b"i8", &i.to_string()[..])?; + } + Value::Bool(b) => { + writer.write_safe_tag(b"boolean", if b { "1" } else { "0" })?; + } + Value::String(ref s) => { + writer.write_tag(b"string", &s[..])?; + } + Value::Double(d) => { + writer.write_safe_tag(b"double", &d.to_string()[..])?; + } + Value::DateTime(date_time) => { + writer.write_safe_tag(b"dateTime.iso8601", &format!("{}", date_time)[..])?; + } + Value::Base64(ref data) => { + writer.write_safe_tag(b"base64", &encode_base64(data)[..])?; + } + Value::Struct(ref map) => { + writer.write_start_tag(b"struct")?; + for (ref name, ref value) in map { + writer.write_start_tag(b"member")?; + writer.write_tag(b"name", &name[..])?; + writer + .write(&value.stringify()?.as_ref()) + .map_err(EncodingError::from)?; + writer.write_end_tag(b"member")?; + } + writer.write_end_tag(b"struct")?; + } + Value::Array(ref array) => { + writer.write_start_tag(b"array")?; + writer.write_start_tag(b"data")?; + for value in array { + // Raw write the value to the buffer because it's encoded xml. + writer + .write(&value.stringify()?.as_ref()) + .map_err(EncodingError::from)?; + } + writer.write_end_tag(b"data")?; + writer.write_end_tag(b"array")?; + } + Value::Nil => { + writer.write(b"<nil />").map_err(EncodingError::from)?; + } + } + writer.write_end_tag(b"value")?; + + Ok(String::from_utf8(buf).map_err(EncodingError::from)?) + } + + /// Returns an inner struct or array value indexed by `index`. + /// + /// Returns `None` if the member doesn't exist or `self` is neither a struct nor an array. + /// + /// You can also use Rust's square-bracket indexing syntax to perform this operation if you want + /// a default value instead of an `Option`. Refer to the top-level [examples](#examples) for + /// details. + /* + pub fn get<I: Index>(&self, index: I) -> Option<&Value> { + index.get(self) + } + */ + + /// If the `Value` is a normal integer (`Value::Int`), returns associated value. Returns `None` + /// otherwise. + /// + /// In particular, `None` is also returned if `self` is a `Value::Int64`. Use [`as_i64`] to + /// handle this case. + /// + /// [`as_i64`]: #method.as_i64 + pub fn as_i32(&self) -> Option<i32> { + match *self { + Value::Int(i) => Some(i), + _ => None, + } + } + + /// If the `Value` is an integer, returns associated value. Returns `None` otherwise. + /// + /// This works with both `Value::Int` and `Value::Int64`. + pub fn as_i64(&self) -> Option<i64> { + match *self { + Value::Int(i) => Some(i64::from(i)), + Value::Int64(i) => Some(i), + _ => None, + } + } + + /// If the `Value` is a boolean, returns associated value. Returns `None` otherwise. + pub fn as_bool(&self) -> Option<bool> { + match *self { + Value::Bool(b) => Some(b), + _ => None, + } + } + + /// If the `Value` is a string, returns associated value. Returns `None` otherwise. + pub fn as_str(&self) -> Option<&str> { + match *self { + Value::String(ref s) => Some(s), + _ => None, + } + } + + /// If the `Value` is a floating point number, returns associated value. Returns `None` + /// otherwise. + pub fn as_f64(&self) -> Option<f64> { + match *self { + Value::Double(d) => Some(d), + _ => None, + } + } + + /// If the `Value` is a date/time, returns associated value. Returns `None` otherwise. + pub fn as_datetime(&self) -> Option<DateTime> { + match *self { + Value::DateTime(dt) => Some(dt), + _ => None, + } + } + + /// If the `Value` is base64 binary data, returns associated value. Returns `None` otherwise. + pub fn as_bytes(&self) -> Option<&[u8]> { + match *self { + Value::Base64(ref data) => Some(data), + _ => None, + } + } + + /// If the `Value` is a struct, returns associated map. Returns `None` otherwise. + pub fn as_struct(&self) -> Option<&BTreeMap<String, Value>> { + match *self { + Value::Struct(ref map) => Some(map), + _ => None, + } + } + + /// If the `Value` is an array, returns associated slice. Returns `None` otherwise. + pub fn as_array(&self) -> Option<&[Value]> { + match *self { + Value::Array(ref array) => Some(array), + _ => None, + } + } +} + +// Crate local definitions +impl Value { + pub(crate) fn read_response_from_reader( + mut reader: &mut Reader<&[u8]>, + mut buf: &mut Vec<u8>, + ) -> Result<Self> { + let ret = match reader.read_event(&mut buf) { + // If we got a start tag, we need to handle each of the value types. + Ok(Event::Start(ref e)) => match e.name() { + b"fault" => { + reader.expect_tag(b"value", &mut buf)?; + let val = Self::read_value_from_reader(&mut reader, &mut buf)?; + reader + .read_to_end(b"fault", &mut buf) + .map_err(ParseError::from)?; + + let f = Fault::try_from(val)?; + let e = Error::from(f); + Err(e) + } + b"params" => { + reader.expect_tag(b"param", &mut buf)?; + reader.expect_tag(b"value", &mut buf)?; + let val = Self::read_value_from_reader(&mut reader, &mut buf)?; + reader + .read_to_end(b"param", &mut buf) + .map_err(ParseError::from)?; + reader + .read_to_end(b"params", &mut buf) + .map_err(ParseError::from)?; + Ok(val) + } + _ => { + return Err(ParseError::UnexpectedTag( + String::from_utf8_lossy(e.name()).into(), + "one of fault|params".into(), + ) + .into()) + } + }, + + // Possible error states + Ok(Event::Eof) => { + return Err(ParseError::UnexpectedEOF("one of fault|params".into()).into()) + } + + Err(e) => return Err(ParseError::from(e).into()), + + _ => return Err(ParseError::UnexpectedEvent("one of fault|params".into()).into()), + }; + + reader + .read_to_end(b"methodResponse", &mut buf) + .map_err(ParseError::from)?; + + ret + } + + pub(crate) fn read_value_from_reader( + mut reader: &mut Reader<&[u8]>, + mut buf: &mut Vec<u8>, + ) -> Result<Self> { + let mut txt = Vec::new(); + + // Read the next event. If it's text or the value closing tag, we know + // we've got a string. If it's a start tag, we've got more work to do. + let ret: Self = match reader.read_event(&mut buf) { + // If we got text, this is a String value. + Ok(Event::Text(e)) => e + .unescape_and_decode(reader) + .map(Value::from) + .map_err(ParseError::from)?, + + // Alternatively, if we got the matching end tag, this is an empty + // string value. Note that we need to return early here so the end + // doesn't try to read the closing tag. + Ok(Event::End(ref e)) if e.name() == b"value" => return Ok("".to_string().into()), + + // If we got a start tag, we need to handle each of the value types. + Ok(Event::Start(ref e)) => match e.name() { + b"i4" | b"int" => Self::read_int_from_reader(e.name(), &mut reader, &mut txt)?, + b"i8" => Self::read_long_from_reader(b"i8", &mut reader, &mut txt)?, + b"boolean" => Self::read_boolean_from_reader(b"boolean", &mut reader, &mut txt)?, + b"string" => Self::read_string_from_reader(b"string", &mut reader, &mut txt)?, + b"double" => Self::read_double_from_reader(b"double", &mut reader, &mut txt)?, + b"dateTime.iso8601" => { + Self::read_datetime_from_reader(b"dateTime.iso8601", &mut reader, &mut txt)? + } + b"base64" => Self::read_base64_from_reader(b"base64", &mut reader, &mut txt)?, + b"struct" => Self::read_struct_from_reader(b"struct", &mut reader, &mut txt)?, + b"array" => Self::read_array_from_reader(b"array", &mut reader, &mut txt)?, + b"nil" => Self::read_nil_from_reader(b"nil", &mut reader, &mut txt)?, + + _ => { + return Err(ParseError::UnexpectedTag( + String::from_utf8_lossy(e.name()).into(), + "one of i4|int|i8|boolean|string|double|dateTime.iso8601|base64|struct|array|nil".into(), + ).into()); + } + }, + + // Possible error states + Ok(Event::Eof) => return Err(ParseError::UnexpectedEOF( + "one of i4|int|i8|boolean|string|double|dateTime.iso8601|base64|struct|array|nil" + .into(), + ) + .into()), + + Err(e) => return Err(ParseError::from(e).into()), + + _ => return Err(ParseError::UnexpectedEvent( + "one of i4|int|i8|boolean|string|double|dateTime.iso8601|base64|struct|array|nil" + .into(), + ) + .into()), + }; + + // Make sure we consume the closing value tag. + reader + .read_to_end("value", &mut buf) + .map_err(ParseError::from)?; + + Ok(ret) + } + + fn read_int_from_reader<K: AsRef<[u8]>>( + end: K, + reader: &mut Reader<&[u8]>, + buf: &mut Vec<u8>, + ) -> Result<Self> { + let text = reader.read_text(end, buf).map_err(ParseError::from)?; + Ok(text.parse::<i32>().map_err(ParseError::from)?.into()) + } + + fn read_long_from_reader<K: AsRef<[u8]>>( + end: K, + reader: &mut Reader<&[u8]>, + buf: &mut Vec<u8>, + ) -> Result<Self> { + let text = reader.read_text(end, buf).map_err(ParseError::from)?; + Ok(text.parse::<i64>().map_err(ParseError::from)?.into()) + } + + fn read_boolean_from_reader<K: AsRef<[u8]>>( + end: K, + reader: &mut Reader<&[u8]>, + buf: &mut Vec<u8>, + ) -> Result<Self> { + let val = reader.read_text(end, buf).map_err(ParseError::from)?; + let val = val.as_ref(); + match val { + "1" => Ok(Value::Bool(true)), + "0" => Ok(Value::Bool(false)), + _ => Err(ParseError::BooleanDecodeError(val.into()).into()), + } + } + + fn read_string_from_reader<K: AsRef<[u8]>>( + end: K, + reader: &mut Reader<&[u8]>, + buf: &mut Vec<u8>, + ) -> Result<Self> { + Ok(reader + .read_text(end, buf) + .map(Value::from) + .map_err(ParseError::from)?) + } + + fn read_double_from_reader<K: AsRef<[u8]>>( + end: K, + reader: &mut Reader<&[u8]>, + buf: &mut Vec<u8>, + ) -> Result<Self> { + let text = reader.read_text(end, buf).map_err(ParseError::from)?; + Ok(text.parse::<f64>().map_err(ParseError::from)?.into()) + } + + fn read_datetime_from_reader<K: AsRef<[u8]>>( + end: K, + reader: &mut Reader<&[u8]>, + buf: &mut Vec<u8>, + ) -> Result<Self> { + let text = reader.read_text(end, buf).map_err(ParseError::from)?; + Ok(parse_datetime(text.as_ref()) + .map(Self::from) + .map_err(ParseError::DateTimeDecodeError)?) + } + + fn read_struct_from_reader<K: AsRef<[u8]>>( + end: K, + mut reader: &mut Reader<&[u8]>, + mut buf: &mut Vec<u8>, + ) -> Result<Self> { + let mut ret: BTreeMap<String, Self> = BTreeMap::new(); + + // Read the next event. If it's text or the value closing tag, we know + // we've got a string. If it's a start tag, we've got more work to do. + loop { + match reader.read_event(&mut buf) { + // If we got the matching end struct tag, we need to exit the + // loop so we can handle cleanup. + Ok(Event::End(ref e)) if e.name() == end.as_ref() => { + break; + } + + // If we got a start tag, we need to handle each of the value types. + Ok(Event::Start(ref e)) => match e.name() { + b"member" => { + reader.expect_tag(b"name", &mut buf)?; + let name = reader + .read_text(b"name", &mut buf) + .map_err(ParseError::from)?; + reader.expect_tag(b"value", &mut buf)?; + let val = Self::read_value_from_reader(&mut reader, &mut buf)?; + reader + .read_to_end(b"member", &mut buf) + .map_err(ParseError::from)?; + ret.insert(name, val); + } + _ => { + return Err(ParseError::UnexpectedTag( + String::from_utf8_lossy(e.name()).into(), + "member".into(), + ) + .into()); + } + }, + + // Possible error states + Ok(Event::Eof) => return Err(ParseError::UnexpectedEOF("member".into()).into()), + + Err(e) => return Err(ParseError::from(e).into()), + + _ => return Err(ParseError::UnexpectedEvent("member".into()).into()), + } + } + + Ok(ret.into()) + } + + fn read_base64_from_reader<K: AsRef<[u8]>>( + end: K, + reader: &mut Reader<&[u8]>, + buf: &mut Vec<u8>, + ) -> Result<Self> { + let text = reader.read_text(end, buf).map_err(ParseError::from)?; + Ok(decode_base64(&text).map_err(ParseError::from)?.into()) + } + + fn read_array_from_reader<K: AsRef<[u8]>>( + end: K, + mut reader: &mut Reader<&[u8]>, + mut buf: &mut Vec<u8>, + ) -> Result<Self> { + let mut ret: Vec<Self> = Vec::new(); + + // The inner tag of an array should be a data tag. + reader.expect_tag(b"data", buf)?; + + // Read the next event. If it's text or the value closing tag, we know + // we've got a string. If it's a start tag, we've got more work to do. + loop { + match reader.read_event(&mut buf) { + // If we got the matching end data tag, we need to exit the loop + // so we can handle cleanup. + Ok(Event::End(ref e)) if e.name() == b"data" => { + break; + } + + // If we got a start tag, we need to handle each of the value types. + Ok(Event::Start(ref e)) => match e.name() { + b"value" => ret.push(Value::read_value_from_reader(&mut reader, &mut buf)?), + _ => { + return Err(ParseError::UnexpectedTag( + String::from_utf8_lossy(e.name()).into(), + "value".into(), + ) + .into()); + } + }, + + // Possible error states + Ok(Event::Eof) => return Err(ParseError::UnexpectedEOF("value".into()).into()), + + Err(e) => return Err(ParseError::from(e).into()), + + _ => return Err(ParseError::UnexpectedEvent("value".into()).into()), + } + } + + reader + .read_to_end(end, &mut buf) + .map_err(ParseError::from)?; + + Ok(ret.into()) + } + + fn read_nil_from_reader<K: AsRef<[u8]>>( + end: K, + reader: &mut Reader<&[u8]>, + buf: &mut Vec<u8>, + ) -> Result<Self> { + reader.read_to_end(end, buf).map_err(ParseError::from)?; + Ok(Self::Nil) + } +} + +// Conversions into Value + +impl From<i32> for Value { + fn from(other: i32) -> Self { + Value::Int(other) + } +} + +impl From<i64> for Value { + fn from(other: i64) -> Self { + Value::Int64(other) + } +} + +impl From<bool> for Value { + fn from(other: bool) -> Self { + Value::Bool(other) + } +} + +impl From<String> for Value { + fn from(other: String) -> Self { + Value::String(other) + } +} + +impl From<&str> for Value { + fn from(other: &str) -> Self { + Value::String(other.to_string()) + } +} + +impl From<f64> for Value { + fn from(other: f64) -> Self { + Value::Double(other) + } +} + +impl From<DateTime> for Value { + fn from(other: DateTime) -> Self { + Value::DateTime(other) + } +} + +impl From<Vec<Value>> for Value { + fn from(other: Vec<Value>) -> Value { + Value::Array(other) + } +} + +impl From<BTreeMap<String, Value>> for Value { + fn from(other: BTreeMap<String, Value>) -> Value { + Value::Struct(other) + } +} + +impl From<Vec<u8>> for Value { + fn from(other: Vec<u8>) -> Self { + Value::Base64(other) + } +} |