use rabbitmq_http_client::blocking_api::EndpointValidationError;
use rabbitmq_http_client::error::{ConversionError, Error as ApiClientError, ErrorDetails};
use rabbitmq_http_client::{blocking_api::HttpClientError, responses::HealthCheckFailureDetails};
use reqwest::StatusCode;
use reqwest::header::{HeaderMap, InvalidHeaderValue};
use std::io;
use url::Url;
#[derive(Debug)]
#[allow(dead_code)]
pub struct HttpErrorInfo {
pub status_code: StatusCode,
pub url: Option<Url>,
pub body: Option<String>,
pub error_details: Option<ErrorDetails>,
pub headers: Option<HeaderMap>,
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct HealthCheckInfo {
pub health_check_path: String,
pub details: HealthCheckFailureDetails,
pub status_code: StatusCode,
}
#[derive(thiserror::Error, Debug)]
pub enum CommandRunError {
#[error("Asked to run an unknown command '{command} {subcommand}'")]
UnknownCommandTarget { command: String, subcommand: String },
#[error("Missing required argument: {name}")]
MissingRequiredArgument { name: String },
#[error("Invalid value for argument '{name}': {message}")]
InvalidArgumentValue { name: String, message: String },
#[error(
"Local TLS certificate file at {local_path} could not be parsed as a PEM file: {cause}"
)]
CertificateFileCouldNotBeLoaded1 {
local_path: String,
cause: reqwest::Error,
},
#[error("Local TLS certificate file at {local_path} could not be read: {cause}")]
CertificateFileCouldNotBeLoaded2 {
local_path: String,
cause: rustls::pki_types::pem::Error,
},
#[error("Ran into an I/O error when loading a file: {0}")]
IoError(io::Error),
#[error("TLS certificate file at {local_path} does not exist or is not readable")]
CertificateFileNotFound { local_path: String },
#[error(
"TLS certificate file at {local_path} could not be parsed, is empty or contains no valid certificates"
)]
CertificateFileEmpty { local_path: String },
#[error("TLS certificate file at {local_path} contains invalid PEM data: {details}")]
CertificateFileInvalidPem { local_path: String, details: String },
#[error("TLS private key file at {local_path} contains an unsupported key type or format")]
PrivateKeyFileUnsupported { local_path: String },
#[error("TLS certificate {cert_path} and private key {key_path} do not match")]
CertificateKeyMismatch { cert_path: String, key_path: String },
#[error("{}", format_client_error(&.0.status_code, &.0.error_details))]
ClientError(Box<HttpErrorInfo>),
#[error("{}", format_server_error(&.0.status_code, &.0.error_details))]
ServerError(Box<HttpErrorInfo>),
#[error("Health check failed")]
HealthCheckFailed(Box<HealthCheckInfo>),
#[error("API responded with a 404 Not Found")]
NotFound,
#[error("{message}")]
ConflictingOptions { message: String },
#[error("{message}")]
MissingOptions { message: String },
#[error("Missing argument value for property (field) {property}")]
MissingArgumentValue { property: String },
#[error("Unsupported argument value for property (field) {property}")]
UnsupportedArgumentValue { property: String },
#[error("This request produces an invalid HTTP header value: {error}")]
InvalidHeaderValue { error: InvalidHeaderValue },
#[error("Response is incompatible with the target data type: {error}")]
IncompatibleBody { error: ConversionError },
#[error("Encountered an error when performing an HTTP request: {error}")]
RequestError { error: reqwest::Error },
#[error("Failed to build HTTP client: {0}")]
HttpClientBuildError(reqwest::Error),
#[error("Failed to parse JSON argument: {message}")]
JsonParseError { message: String },
#[error("Invalid base URI '{uri}': {message}")]
InvalidBaseUri { uri: String, message: String },
#[error("Command execution failed: {message}")]
FailureDuringExecution { message: String },
#[error("An unspecified error")]
Other,
}
impl From<io::Error> for CommandRunError {
fn from(value: io::Error) -> Self {
CommandRunError::IoError(value)
}
}
impl From<HttpClientError> for CommandRunError {
fn from(value: HttpClientError) -> Self {
match value {
ApiClientError::UnsupportedArgumentValue { property } => Self::UnsupportedArgumentValue { property },
ApiClientError::ClientErrorResponse { status_code, url, body, error_details, headers, .. } => {
Self::ClientError(Box::new(HttpErrorInfo { status_code, url, body, error_details, headers }))
}
ApiClientError::ServerErrorResponse { status_code, url, body, error_details, headers, .. } => {
Self::ServerError(Box::new(HttpErrorInfo { status_code, url, body, error_details, headers }))
}
ApiClientError::HealthCheckFailed { path, details, status_code } => {
Self::HealthCheckFailed(Box::new(HealthCheckInfo { health_check_path: path, details, status_code }))
}
ApiClientError::NotFound => Self::NotFound,
ApiClientError::MultipleMatchingBindings => Self::ConflictingOptions {
message: "multiple bindings match, cannot determine which binding to delete without explicitly provided binding properties".to_owned()
},
ApiClientError::InvalidHeaderValue { error } => Self::InvalidHeaderValue { error },
ApiClientError::RequestError { error, .. } => Self::RequestError { error },
ApiClientError::Other => Self::Other,
ApiClientError::MissingProperty { argument } => Self::MissingArgumentValue { property: argument },
ApiClientError::IncompatibleBody { error, .. } => Self::IncompatibleBody { error },
ApiClientError::ParsingError { message } => Self::FailureDuringExecution { message },
}
}
}
impl From<EndpointValidationError> for CommandRunError {
fn from(value: EndpointValidationError) -> Self {
match value {
EndpointValidationError::UnsupportedScheme { endpoint } => {
CommandRunError::InvalidBaseUri {
uri: endpoint,
message: "unsupported URI scheme".to_owned(),
}
}
EndpointValidationError::ClientBuildError { source } => {
CommandRunError::HttpClientBuildError(source)
}
}
}
}
fn format_client_error(status_code: &StatusCode, error_details: &Option<ErrorDetails>) -> String {
if let Some(details) = error_details
&& let Some(reason) = details.reason()
{
return reason.to_string();
}
format!(
"API responded with a client error: status code of {}",
status_code
)
}
fn format_server_error(status_code: &StatusCode, error_details: &Option<ErrorDetails>) -> String {
if let Some(details) = error_details
&& let Some(reason) = details.reason()
{
return format!(
"API responded with a server error: status code of {}\n\n{}",
status_code, reason
);
}
format!(
"API responded with a server error: status code of {}",
status_code
)
}