use std::fmt;
use url::Url;
pub fn redact_url(url: &Url) -> String {
let mut u = url.clone();
u.set_query(None);
u.set_fragment(None);
u.to_string()
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub const RESPONSE_BODY_CAP: usize = 4096;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("{message}")]
Argument {
message: String,
},
#[error("{method} {url}: {status} {status_text}")]
Response {
method: String,
url: String,
status: u16,
status_text: String,
body: Vec<u8>,
},
#[error("invalid {kind} length: got {got}, expected {expected:?}")]
LengthMismatch {
kind: &'static str,
expected: &'static [usize],
got: usize,
},
#[error("invalid hex: {0}")]
Hex(#[from] hex::FromHexError),
#[error("crypto: {0}")]
Crypto(String),
#[error("json: {0}")]
Json(#[from] serde_json::Error),
#[error("transport: {0}")]
Transport(#[from] reqwest::Error),
#[error("{0}")]
Other(String),
}
impl Error {
pub fn argument<M: Into<String>>(msg: M) -> Self {
Error::Argument {
message: msg.into(),
}
}
pub fn crypto<M: fmt::Display>(msg: M) -> Self {
Error::Crypto(msg.to_string())
}
pub fn is_response(&self) -> bool {
matches!(self, Error::Response { .. })
}
pub fn status(&self) -> Option<u16> {
match self {
Error::Response { status, .. } => Some(*status),
_ => None,
}
}
}
pub async fn response_error_from(resp: reqwest::Response) -> Error {
let method = resp.url().as_str().to_owned();
let url = method.clone();
let status = resp.status();
let status_text = status.canonical_reason().unwrap_or("").to_string();
let body = resp
.bytes()
.await
.map(|b| {
let n = b.len().min(RESPONSE_BODY_CAP);
b.slice(..n).to_vec()
})
.unwrap_or_default();
Error::Response {
method: String::new(),
url,
status: status.as_u16(),
status_text: format!("{} {}", status.as_u16(), status_text),
body,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn length_mismatch_displays() {
let e = Error::LengthMismatch {
kind: "Reference",
expected: &[32, 64],
got: 16,
};
assert_eq!(
format!("{e}"),
"invalid Reference length: got 16, expected [32, 64]"
);
}
#[test]
fn argument_helper() {
let e = Error::argument("bad input");
assert!(matches!(e, Error::Argument { .. }));
assert_eq!(format!("{e}"), "bad input");
}
}