use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Error)]
pub struct OpClientError {
pub description: String,
pub validation_errors: Option<Vec<String>>,
pub status: Option<String>,
pub code: Option<u16>,
pub details: Option<HashMap<String, serde_json::Value>>,
}
impl OpClientError {
pub fn http(description: impl Into<String>, status: Option<String>, code: Option<u16>) -> Self {
Self {
description: description.into(),
validation_errors: None,
status,
code,
details: None,
}
}
pub fn validation(description: impl Into<String>, validation_errors: Vec<String>) -> Self {
Self {
description: description.into(),
validation_errors: Some(validation_errors),
status: None,
code: None,
details: None,
}
}
pub fn other(description: impl Into<String>) -> Self {
Self {
description: description.into(),
validation_errors: None,
status: None,
code: None,
details: None,
}
}
pub fn with_details(mut self, details: HashMap<String, serde_json::Value>) -> Self {
self.details = Some(details);
self
}
pub fn with_detail(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
if self.details.is_none() {
self.details = Some(HashMap::new());
}
if let Some(ref mut details) = self.details {
details.insert(key.into(), value);
}
self
}
}
impl std::fmt::Display for OpClientError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.description)?;
if let Some(status) = &self.status {
write!(f, " (Status: {status})")?;
}
if let Some(code) = &self.code {
write!(f, " (Code: {code})")?;
}
if let Some(validation_errors) = &self.validation_errors {
write!(f, " [Validation errors: {}]", validation_errors.join(", "))?;
}
Ok(())
}
}
impl From<reqwest::Error> for OpClientError {
fn from(err: reqwest::Error) -> Self {
let status = err
.status()
.map(|s| s.canonical_reason().unwrap_or("Unknown").to_string());
let code = err.status().map(|s| s.as_u16());
let description = format!("HTTP error: {err}");
Self {
description,
validation_errors: None,
status,
code,
details: None,
}
}
}
impl From<serde_json::Error> for OpClientError {
fn from(err: serde_json::Error) -> Self {
Self::other(format!("JSON serialization/deserialization error: {err}"))
}
}
impl From<std::io::Error> for OpClientError {
fn from(err: std::io::Error) -> Self {
Self::other(format!("I/O error: {err}"))
}
}
impl From<base64::DecodeError> for OpClientError {
fn from(err: base64::DecodeError) -> Self {
Self::other(format!("Base64 decoding error: {err}"))
}
}
impl From<url::ParseError> for OpClientError {
fn from(err: url::ParseError) -> Self {
Self::other(format!("URL parsing error: {err}"))
}
}
impl OpClientError {
pub fn header_parse(description: impl Into<String>) -> Self {
Self::other(format!("Header parse error: {}", description.into()))
}
pub fn pem(description: impl Into<String>) -> Self {
Self::other(format!("Invalid PEM: {}", description.into()))
}
pub fn pkcs8(description: impl Into<String>) -> Self {
Self::other(format!("PKCS8 error: {}", description.into()))
}
pub fn signature(description: impl Into<String>) -> Self {
Self::other(format!("Signature error: {}", description.into()))
}
}
impl From<url::ParseError> for Box<OpClientError> {
fn from(err: url::ParseError) -> Self {
Box::new(OpClientError::from(err))
}
}
impl From<reqwest::Error> for Box<OpClientError> {
fn from(err: reqwest::Error) -> Self {
Box::new(OpClientError::from(err))
}
}
impl From<serde_json::Error> for Box<OpClientError> {
fn from(err: serde_json::Error) -> Self {
Box::new(OpClientError::from(err))
}
}
impl From<std::io::Error> for Box<OpClientError> {
fn from(err: std::io::Error) -> Self {
Box::new(OpClientError::from(err))
}
}
impl From<base64::DecodeError> for Box<OpClientError> {
fn from(err: base64::DecodeError) -> Self {
Box::new(OpClientError::from(err))
}
}
pub type Result<T> = std::result::Result<T, Box<OpClientError>>;