aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml7
-rw-r--r--src/error.rs184
-rw-r--r--src/lib.rs256
-rw-r--r--src/utils.rs99
-rw-r--r--src/value.rs562
5 files changed, 1106 insertions, 2 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 342598f..f540c3c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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>();
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 31e1bb2..02c79a5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)
+ }
+}