aboutsummaryrefslogtreecommitdiff
path: root/src/error.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/error.rs')
-rw-r--r--src/error.rs184
1 files changed, 184 insertions, 0 deletions
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>();
+ }
+}