use axum::http::StatusCode;
use serde::Serialize;
use serde_json::{Map, Value};
#[derive(Debug, Clone, Serialize)]
pub struct AwsError {
#[serde(skip)]
pub status: StatusCode,
pub code: String,
pub message: String,
pub error_type: ErrorType,
#[serde(skip_serializing_if = "Option::is_none")]
pub extras: Option<Box<Map<String, Value>>>,
}
#[derive(Debug, Clone, Serialize)]
pub enum ErrorType {
Sender,
Receiver,
}
impl AwsError {
pub fn not_found(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
status: StatusCode::NOT_FOUND,
code: code.into(),
message: message.into(),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn service_not_found(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
status: StatusCode::BAD_REQUEST,
code: code.into(),
message: message.into(),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn bad_request(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
status: StatusCode::BAD_REQUEST,
code: code.into(),
message: message.into(),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn range_not_satisfiable(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
status: StatusCode::RANGE_NOT_SATISFIABLE,
code: code.into(),
message: message.into(),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn precondition_failed(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
status: StatusCode::PRECONDITION_FAILED,
code: code.into(),
message: message.into(),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn conflict(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
status: StatusCode::CONFLICT,
code: code.into(),
message: message.into(),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn internal(message: impl Into<String>) -> Self {
Self {
status: StatusCode::INTERNAL_SERVER_ERROR,
code: "InternalServiceError".to_string(),
message: message.into(),
error_type: ErrorType::Receiver,
extras: None,
}
}
pub fn not_implemented(operation: &str) -> Self {
Self {
status: StatusCode::NOT_IMPLEMENTED,
code: "NotImplemented".to_string(),
message: format!("Operation '{operation}' is not yet implemented in AWSim"),
error_type: ErrorType::Receiver,
extras: None,
}
}
pub fn unknown_operation(operation: &str) -> Self {
Self {
status: StatusCode::BAD_REQUEST,
code: "UnknownOperationException".to_string(),
message: format!("Unknown operation: {operation}"),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn access_denied(message: impl Into<String>) -> Self {
Self {
status: StatusCode::FORBIDDEN,
code: "AccessDeniedException".to_string(),
message: message.into(),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn access_denied_for(action: &str, principal_arn: &str, resource: &str) -> Self {
Self {
status: StatusCode::FORBIDDEN,
code: "AccessDenied".to_string(),
message: format!(
"User: {principal_arn} is not authorized to perform: {action} on resource: {resource}"
),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn validation(message: impl Into<String>) -> Self {
Self {
status: StatusCode::BAD_REQUEST,
code: "ValidationException".to_string(),
message: message.into(),
error_type: ErrorType::Sender,
extras: None,
}
}
pub fn with_extras(mut self, extras: Map<String, Value>) -> Self {
self.extras = Some(Box::new(extras));
self
}
pub fn with_extra(mut self, key: impl Into<String>, value: Value) -> Self {
self.extras
.get_or_insert_with(|| Box::new(Map::new()))
.insert(key.into(), value);
self
}
}
impl std::fmt::Display for AwsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.code, self.message)
}
}
impl std::error::Error for AwsError {}