#![allow(
clippy::pub_with_shorthand,
clippy::pub_without_shorthand,
reason = "restriction lints have contradictory pub visibility rules"
)]
use std::borrow::Cow;
use thiserror::Error;
use crate::types::{ConnectionError, HttpError};
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ErrorSource {
#[error("Connection error: {0}")]
Connection(#[from] ConnectionError),
#[error("HTTP error: {0}")]
Http(#[from] HttpError),
#[error("URL parse error: {0}")]
UrlParse(#[from] url::ParseError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum WaitForError {
#[error("Invalid target format '{0}': expected host:port or http(s)://host:port/path")]
InvalidTarget(Cow<'static, str>),
#[error("Invalid port: {0} (must be 1-65535)")]
InvalidPort(u16),
#[error("Invalid hostname: {0}")]
InvalidHostname(Cow<'static, str>),
#[error("Invalid timeout format '{0}': {1}")]
InvalidTimeout(Cow<'static, str>, Cow<'static, str>),
#[error("Invalid interval format '{0}': {1}")]
InvalidInterval(Cow<'static, str>, Cow<'static, str>),
#[error("Connection error: {0}")]
Connection(#[from] ConnectionError),
#[error("HTTP error: {0}")]
Http(#[from] HttpError),
#[error("Timeout waiting for {targets}")]
Timeout {
targets: Cow<'static, str>,
},
#[error("URL parse error: {0}")]
UrlParse(#[from] url::ParseError),
#[error("Retry limit exceeded: {limit} attempts")]
RetryLimitExceeded {
limit: u32,
},
#[error("{message}: {source}")]
WithContext {
message: Cow<'static, str>,
#[source]
source: ErrorSource,
},
#[error("Operation was cancelled")]
Cancelled,
}
pub type Result<T> = std::result::Result<T, WaitForError>;
impl From<&'static str> for WaitForError {
fn from(msg: &'static str) -> Self {
Self::InvalidTarget(Cow::Borrowed(msg))
}
}
impl From<String> for WaitForError {
fn from(msg: String) -> Self {
Self::InvalidTarget(Cow::Owned(msg))
}
}
pub trait ResultExt<T> {
fn with_context<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> String;
fn context(self, msg: &'static str) -> Result<T>;
}
impl<T, E> ResultExt<T> for std::result::Result<T, E>
where
E: Into<ErrorSource>,
{
fn with_context<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> String,
{
self.map_err(|e| WaitForError::WithContext {
message: Cow::Owned(f()),
source: e.into(),
})
}
fn context(self, msg: &'static str) -> Result<T> {
self.map_err(|e| WaitForError::WithContext {
message: Cow::Borrowed(msg),
source: e.into(),
})
}
}
impl<T> ResultExt<T> for std::result::Result<T, WaitForError> {
fn with_context<F>(self, f: F) -> Self
where
F: FnOnce() -> String,
{
self.map_err(|e| {
match e {
WaitForError::Connection(conn_err) => WaitForError::WithContext {
message: Cow::Owned(f()),
source: ErrorSource::Connection(conn_err),
},
WaitForError::Http(http_err) => WaitForError::WithContext {
message: Cow::Owned(f()),
source: ErrorSource::Http(http_err),
},
WaitForError::UrlParse(url_err) => WaitForError::WithContext {
message: Cow::Owned(f()),
source: ErrorSource::UrlParse(url_err),
},
other => {
let context_msg = f();
match other {
WaitForError::InvalidTarget(msg) => {
WaitForError::InvalidTarget(Cow::Owned(format!("{context_msg}: {msg}")))
}
WaitForError::InvalidHostname(msg) => WaitForError::InvalidHostname(
Cow::Owned(format!("{context_msg}: {msg}")),
),
_ => other, }
}
}
})
}
fn context(self, msg: &'static str) -> Self {
self.map_err(|e| {
match e {
WaitForError::Connection(conn_err) => WaitForError::WithContext {
message: Cow::Borrowed(msg),
source: ErrorSource::Connection(conn_err),
},
WaitForError::Http(http_err) => WaitForError::WithContext {
message: Cow::Borrowed(msg),
source: ErrorSource::Http(http_err),
},
WaitForError::UrlParse(url_err) => WaitForError::WithContext {
message: Cow::Borrowed(msg),
source: ErrorSource::UrlParse(url_err),
},
other => {
match other {
WaitForError::InvalidTarget(orig_msg) => {
WaitForError::InvalidTarget(Cow::Owned(format!("{msg}: {orig_msg}")))
}
WaitForError::InvalidHostname(orig_msg) => {
WaitForError::InvalidHostname(Cow::Owned(format!("{msg}: {orig_msg}")))
}
_ => other, }
}
}
})
}
}
pub(crate) mod error_messages {
pub const EMPTY_HOSTNAME: &str = "Hostname cannot be empty";
pub const HOSTNAME_TOO_LONG: &str = "Hostname too long (max 253 characters)";
pub const HOSTNAME_INVALID_HYPHEN: &str = "Hostname cannot start or end with hyphen";
pub const HOSTNAME_EMPTY_LABEL: &str = "Hostname labels cannot be empty";
pub const HOSTNAME_LABEL_TOO_LONG: &str = "Hostname labels cannot exceed 63 characters";
pub const HOSTNAME_LABEL_INVALID_HYPHEN: &str =
"Hostname labels cannot start or end with hyphen";
pub const HOSTNAME_INVALID_CHARS: &str = "Hostname contains invalid characters";
pub const INVALID_IPV4_FORMAT: &str = "Invalid IPv4 format";
pub const INVALID_IPV4_OCTET: &str = "Invalid IPv4 octet";
}