use crate::context::RequestContext; use axum::http::StatusCode; use axum::http::{HeaderName, HeaderValue}; use axum::response::{IntoResponse, Response}; use axum::Json; use serde::Serialize; #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct ErrorBody { pub ok: bool, pub code: &'static str, pub message: String, pub request_id: Option, pub trace_id: Option, } #[derive(Debug, Clone)] pub struct WebError { status: StatusCode, code: &'static str, message: String, request_context: Option, headers: Vec<(&'static str, String)>, } impl WebError { pub fn new(status: StatusCode, code: &'static str, message: impl Into) -> Self { Self { status, code, message: message.into(), request_context: None, headers: Vec::new(), } } pub fn bad_request(message: impl Into) -> Self { Self::new(StatusCode::BAD_REQUEST, "bad_request", message) } pub fn bad_request_code(code: &'static str, message: impl Into) -> Self { Self::new(StatusCode::BAD_REQUEST, code, message) } pub fn internal(message: impl Into) -> Self { Self::new(StatusCode::INTERNAL_SERVER_ERROR, "internal_error", message) } pub fn bad_gateway_code(code: &'static str, message: impl Into) -> Self { Self::new(StatusCode::BAD_GATEWAY, code, message) } pub fn service_unavailable_code(code: &'static str, message: impl Into) -> Self { Self::new(StatusCode::SERVICE_UNAVAILABLE, code, message) } pub fn gateway_timeout_code(code: &'static str, message: impl Into) -> Self { Self::new(StatusCode::GATEWAY_TIMEOUT, code, message) } pub fn with_context(mut self, request_context: &RequestContext) -> Self { self.request_context = Some(request_context.clone()); self } pub fn with_header(mut self, name: &'static str, value: impl Into) -> Self { self.headers.push((name, value.into())); self } pub fn message(&self) -> &str { &self.message } } impl IntoResponse for WebError { fn into_response(self) -> Response { let body = ErrorBody { ok: false, code: self.code, message: self.message, request_id: self .request_context .as_ref() .map(|context| context.trace.request_id.clone()), trace_id: self .request_context .as_ref() .map(|context| context.trace.trace_id.clone()), }; let mut response = (self.status, Json(body)).into_response(); if let Ok(name) = HeaderName::from_lowercase(b"x-error-code") { if let Ok(value) = HeaderValue::from_str(self.code) { response.headers_mut().insert(name, value); } } for (name, value) in self.headers { let Ok(header_name) = HeaderName::from_lowercase(name.as_bytes()) else { continue; }; let Ok(header_value) = HeaderValue::from_str(&value) else { continue; }; response.headers_mut().insert(header_name, header_value); } response } }