use handled::Handle;
#[derive(Debug, Clone)]
pub struct UserError {
pub message: String,
pub usage_hint: Option<String>,
}
impl std::fmt::Display for UserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl Handle<UserError> for UserError {
fn handle(&self) -> Option<UserError> {
Some(self.clone())
}
}
#[derive(Debug)]
pub struct EntityParseError {
pub input: String,
pub reason: String,
}
impl std::fmt::Display for EntityParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid entity ID '{}': {}", self.input, self.reason)
}
}
impl std::error::Error for EntityParseError {}
impl Handle<UserError> for EntityParseError {
fn handle(&self) -> Option<UserError> {
Some(UserError {
message: format!("Invalid entity ID '{}': {}", self.input, self.reason),
usage_hint: Some(
"Entity IDs should be in format 'entity:BASE64_STRING' or just 'BASE64_STRING'"
.to_string(),
),
})
}
}
#[derive(Debug)]
pub struct HttpOperationError {
pub operation: String,
pub status: Option<u16>,
pub details: String,
}
impl std::fmt::Display for HttpOperationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(status) = self.status {
write!(
f,
"{} failed (HTTP {}): {}",
self.operation, status, self.details
)
} else {
write!(f, "{} failed: {}", self.operation, self.details)
}
}
}
impl std::error::Error for HttpOperationError {}
impl Handle<UserError> for HttpOperationError {
fn handle(&self) -> Option<UserError> {
let message = if let Some(status) = self.status {
format!(
"{} failed (HTTP {}): {}",
self.operation, status, self.details
)
} else {
format!("{} failed: {}", self.operation, self.details)
};
let usage_hint = match self.status {
Some(404) => Some(
"The requested resource was not found. Check the ID and try again.".to_string(),
),
Some(400) => Some("Invalid request. Check your input data and try again.".to_string()),
Some(401) => Some("Authentication required. Check your credentials.".to_string()),
Some(403) => Some(
"Access forbidden. You may not have permission for this operation.".to_string(),
),
Some(429) => Some("Too many requests. Wait a moment and try again.".to_string()),
Some(500..=599) => {
Some("Server error. The service may be temporarily unavailable.".to_string())
}
_ => None,
};
Some(UserError {
message,
usage_hint,
})
}
}
impl HttpOperationError {
pub async fn from_response(response: reqwest::Response, operation: &str) -> Self {
let status = response.status().as_u16();
let details = response
.text()
.await
.unwrap_or_else(|_| "No error details".to_string());
Self {
operation: operation.to_string(),
status: Some(status),
details: if details.is_empty() {
"No error details".to_string()
} else {
details
},
}
}
pub fn new(operation: &str, details: &str) -> Self {
Self {
operation: operation.to_string(),
status: None,
details: details.to_string(),
}
}
}
#[derive(Debug)]
pub struct ValidationError {
pub field: String,
pub value: String,
pub reason: String,
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Invalid {}: '{}' - {}",
self.field, self.value, self.reason
)
}
}
impl std::error::Error for ValidationError {}
impl Handle<UserError> for ValidationError {
fn handle(&self) -> Option<UserError> {
Some(UserError {
message: format!("Invalid {}: '{}' - {}", self.field, self.value, self.reason),
usage_hint: Some("Check the documentation for valid input formats".to_string()),
})
}
}
pub fn extract_user_error<E>(error: &E) -> Option<UserError>
where
E: Handle<UserError>,
{
error.handle()
}
pub fn format_cli_error<E>(error: &E) -> String
where
E: Handle<UserError> + std::fmt::Display,
{
if let Some(user_error) = error.handle() {
let mut output = format!("Error: {}", user_error.message);
if let Some(hint) = user_error.usage_hint {
output.push_str(&format!("\nHint: {}", hint));
}
output
} else {
format!("Error: {}", error)
}
}