pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Resource not found: {resource}")]
NotFound { resource: String },
#[error("Unauthorized: {provider}")]
Unauthorized { provider: String },
#[error("Forbidden: {resource}")]
Forbidden { resource: String },
#[error("Conflict: {message}")]
Conflict { message: String },
#[error("Invalid request: {message}")]
InvalidRequest { message: String },
#[error("Server error {status}: {message}")]
ServerError { status: u16, message: String },
#[error("Network error: {source}")]
NetworkError {
#[from]
source: reqwest::Error,
},
#[cfg(not(target_family = "wasm"))]
#[error("Invalid ARN: {arn}")]
InvalidArn { arn: String },
#[error("Invalid configuration: {message}")]
InvalidConfig { message: String },
#[error("JSON parse error: {source}")]
JsonError {
#[from]
source: serde_json::Error,
},
#[error("Unexpected error: {message}")]
Unexpected { message: String },
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("I/O error: {0}")]
IoError(String),
#[error("Concurrent modification detected: {message}")]
ConcurrentModification { message: String },
}
impl Error {
pub fn not_found(resource: impl Into<String>) -> Self {
Self::NotFound {
resource: resource.into(),
}
}
pub fn unauthorized(provider: impl Into<String>) -> Self {
Self::Unauthorized {
provider: provider.into(),
}
}
pub fn forbidden(resource: impl Into<String>) -> Self {
Self::Forbidden {
resource: resource.into(),
}
}
pub fn conflict(message: impl Into<String>) -> Self {
Self::Conflict {
message: message.into(),
}
}
pub fn invalid_request(message: impl Into<String>) -> Self {
Self::InvalidRequest {
message: message.into(),
}
}
pub fn server_error(status: u16, message: impl Into<String>) -> Self {
Self::ServerError {
status,
message: message.into(),
}
}
#[cfg(not(target_family = "wasm"))]
pub fn invalid_arn(arn: impl Into<String>) -> Self {
Self::InvalidArn { arn: arn.into() }
}
pub fn invalid_config(message: impl Into<String>) -> Self {
Self::InvalidConfig {
message: message.into(),
}
}
pub fn unexpected(message: impl Into<String>) -> Self {
Self::Unexpected {
message: message.into(),
}
}
pub fn concurrent_modification(message: impl Into<String>) -> Self {
Self::ConcurrentModification {
message: message.into(),
}
}
pub fn invalid_input(message: impl Into<String>) -> Self {
Self::InvalidInput(message.into())
}
pub fn io_error(message: impl Into<String>) -> Self {
Self::IoError(message.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_constructors() {
let err = Error::not_found("table1");
assert!(matches!(err, Error::NotFound { .. }));
assert_eq!(err.to_string(), "Resource not found: table1");
let err = Error::unauthorized("AWS");
assert!(matches!(err, Error::Unauthorized { .. }));
let err = Error::forbidden("namespace1");
assert!(matches!(err, Error::Forbidden { .. }));
#[cfg(not(target_family = "wasm"))]
{
let err = Error::invalid_arn("bad-arn");
assert!(matches!(err, Error::InvalidArn { .. }));
}
}
#[test]
fn test_error_display() {
let err = Error::server_error(500, "Internal error");
assert_eq!(err.to_string(), "Server error 500: Internal error");
let err = Error::conflict("Resource already exists");
assert_eq!(err.to_string(), "Conflict: Resource already exists");
}
#[test]
fn test_concurrent_modification_error() {
let err = Error::concurrent_modification("expected v2, found v3");
assert!(matches!(err, Error::ConcurrentModification { .. }));
assert_eq!(
err.to_string(),
"Concurrent modification detected: expected v2, found v3"
);
}
}