use crate::xml;
use std::fmt;
pub const SOAP_NS: &str = "http://schemas.xmlsoap.org/soap/envelope/";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fault {
pub code: String,
pub reason: String,
pub status: u16,
}
impl Fault {
#[must_use]
pub fn new(code: impl Into<String>, reason: impl Into<String>, status: u16) -> Fault {
Fault {
code: code.into(),
reason: reason.into(),
status,
}
}
#[must_use]
pub fn client(reason: impl Into<String>) -> Fault {
Fault::new("Client", reason, 400)
}
#[must_use]
pub fn validation(reason: impl Into<String>) -> Fault {
Fault::new("Client.Validation", reason, 400)
}
#[must_use]
pub fn authorization(reason: impl Into<String>) -> Fault {
Fault::new("Client.Authorization", reason, 403)
}
#[must_use]
pub fn server(reason: impl Into<String>) -> Fault {
Fault::new("Server", reason, 500)
}
#[must_use]
pub fn configuration(reason: impl Into<String>) -> Fault {
Fault::new("Server.Configuration", reason, 500)
}
#[must_use]
pub fn is_retryable(&self) -> bool {
self.status >= 500
}
#[must_use]
pub fn to_envelope(&self) -> String {
format!(
concat!(
r#"<soapenv:Envelope xmlns:soapenv="{}">"#,
"<soapenv:Body>",
"<soapenv:Fault>",
"<faultcode>{}</faultcode>",
"<faultstring>{}</faultstring>",
"</soapenv:Fault>",
"</soapenv:Body>",
"</soapenv:Envelope>",
),
SOAP_NS,
xml::escape(&self.code),
xml::escape(&self.reason),
)
}
}
impl fmt::Display for Fault {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({}): {}", self.code, self.status, self.reason)
}
}
impl std::error::Error for Fault {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_kind_carries_the_status_that_matches_it() {
assert_eq!(Fault::client("x").status, 400);
assert_eq!(Fault::validation("x").status, 400);
assert_eq!(Fault::authorization("x").status, 403);
assert_eq!(Fault::server("x").status, 500);
assert_eq!(Fault::configuration("x").status, 500);
}
#[test]
fn only_a_server_fault_is_worth_retrying() {
assert!(!Fault::client("x").is_retryable());
assert!(!Fault::authorization("x").is_retryable());
assert!(Fault::server("x").is_retryable());
}
#[test]
fn a_reason_cannot_break_out_of_the_envelope() {
let fault = Fault::client(r#"bad <tag> & "quote""#);
let envelope = fault.to_envelope();
assert!(envelope.contains("bad <tag> & "quote""));
let root = xml::parse(&envelope).unwrap();
assert_eq!(root.local_name(), "Envelope");
}
#[test]
fn reads_back_as_the_fault_it_describes() {
let envelope = Fault::validation("no good").to_envelope();
let root = xml::parse(&envelope).unwrap();
let fault = root.find("Fault").unwrap();
assert_eq!(fault.child("faultcode").unwrap().text, "Client.Validation");
assert_eq!(fault.child("faultstring").unwrap().text, "no good");
}
}