use core::fmt;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
InvalidBaseUrl(String),
InvalidPath(String),
InvalidHeader(String),
InvalidTlsConfig(String),
InvalidTimeout(&'static str),
InvalidParameter(String),
Internal(&'static str),
Http(reqwest::Error),
Decode(String),
Api {
status: reqwest::StatusCode,
errors: Vec<String>,
},
MissingField(&'static str),
MissingToken,
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidBaseUrl(message) => {
write!(formatter, "invalid OpenBao base URL: {message}")
}
Self::InvalidPath(message) => write!(formatter, "invalid OpenBao path: {message}"),
Self::InvalidHeader(message) => write!(formatter, "invalid OpenBao header: {message}"),
Self::InvalidTlsConfig(message) => {
write!(formatter, "invalid OpenBao TLS configuration: {message}")
}
Self::InvalidTimeout(message) => {
write!(
formatter,
"invalid OpenBao timeout configuration: {message}"
)
}
Self::InvalidParameter(message) => {
write!(formatter, "invalid OpenBao request parameter: {message}")
}
Self::Internal(message) => write!(formatter, "internal OpenBao SDK error: {message}"),
Self::Http(error) => write!(formatter, "OpenBao HTTP error: {error}"),
Self::Decode(error) => write!(formatter, "OpenBao decode error: {error}"),
Self::Api { status, errors } if errors.is_empty() => {
write!(formatter, "OpenBao API returned {status}")
}
Self::Api { status, errors } => {
write!(formatter, "OpenBao API returned {status}: ")?;
for (index, error) in errors.iter().enumerate() {
if index > 0 {
write!(formatter, "; ")?;
}
write!(formatter, "{}", sanitize_api_error(error))?;
}
Ok(())
}
Self::MissingField(field) => {
write!(formatter, "OpenBao response missing field `{field}`")
}
Self::MissingToken => write!(
formatter,
"OpenBao client is missing an authentication token"
),
}
}
}
fn sanitize_api_error(error: &str) -> String {
error
.chars()
.filter(|character| !character.is_control())
.take(512)
.collect()
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Http(error) => Some(error),
_ => None,
}
}
}
impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self {
Self::Http(error)
}
}
#[cfg(test)]
mod tests {
use reqwest::StatusCode;
use super::Error;
#[test]
fn display_sanitizes_api_errors() {
let error = Error::Api {
status: StatusCode::BAD_REQUEST,
errors: vec![format!("bad\nmessage\r{}", "x".repeat(600))],
};
let message = error.to_string();
assert!(!message.contains('\n'));
assert!(!message.contains('\r'));
assert!(message.len() < 600);
}
}