diff --git a/Cargo.lock b/Cargo.lock index e08737f..25f84d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1224,6 +1224,7 @@ dependencies = [ "actix-web", "axum", "config", + "http 1.3.1", "redis", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 5ff468c..0dc9786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ tracing = "0.1" serde = { version = "1", features = ["derive"] } serde_json = { version = "1", optional = true } + http = "1" # опциональные интеграции diff --git a/src/response.rs b/src/response.rs index dc90e5f..7f82b6c 100644 --- a/src/response.rs +++ b/src/response.rs @@ -173,6 +173,24 @@ impl ErrorResponse { self.www_authenticate = Some(value.into()); self } + + /// Convert numeric [`status`](ErrorResponse::status) into [`StatusCode`]. + /// + /// Invalid codes default to `StatusCode::INTERNAL_SERVER_ERROR`. + /// + /// # Examples + /// + /// ``` + /// use http::StatusCode; + /// use masterror::{AppCode, ErrorResponse}; + /// + /// let resp = ErrorResponse::new(404, AppCode::NotFound, "missing").expect("status"); + /// assert_eq!(resp.status_code(), StatusCode::NOT_FOUND); + /// ``` + #[must_use] + pub fn status_code(&self) -> StatusCode { + StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) + } } /// Legacy constructor retained for migration purposes. @@ -240,7 +258,7 @@ mod axum_impl { use axum::{ Json, http::{ - HeaderValue, StatusCode, + HeaderValue, header::{RETRY_AFTER, WWW_AUTHENTICATE} }, response::{IntoResponse, Response} @@ -251,8 +269,7 @@ mod axum_impl { impl IntoResponse for ErrorResponse { fn into_response(self) -> Response { - let status = - StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); + let status = self.status_code(); // Serialize JSON body first (borrow self for payload). let mut response = (status, Json(&self)).into_response(); @@ -299,7 +316,7 @@ mod actix_impl { HttpRequest, HttpResponse, Responder, body::BoxBody, http::{ - StatusCode, + StatusCode as ActixStatus, header::{RETRY_AFTER, WWW_AUTHENTICATE} } }; @@ -310,8 +327,9 @@ mod actix_impl { type Body = BoxBody; fn respond_to(self, _req: &HttpRequest) -> HttpResponse { - let status = - StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); + let status = self.status_code(); + let status = ActixStatus::from_u16(status.as_u16()) + .unwrap_or(ActixStatus::INTERNAL_SERVER_ERROR); let mut builder = HttpResponse::build(status); if let Some(retry) = self.retry { @@ -360,6 +378,24 @@ mod tests { assert_eq!(e.www_authenticate.as_deref(), Some(r#"Bearer realm="api""#)); } + #[test] + fn status_code_maps_invalid_to_internal_server_error() { + use http::StatusCode; + + let valid = ErrorResponse::new(404, AppCode::NotFound, "missing").expect("status"); + assert_eq!(valid.status_code(), StatusCode::NOT_FOUND); + + let invalid = ErrorResponse { + status: 1000, + code: AppCode::Internal, + message: "oops".into(), + details: None, + retry: None, + www_authenticate: None + }; + assert_eq!(invalid.status_code(), StatusCode::INTERNAL_SERVER_ERROR); + } + // --- Details: JSON vs text ---------------------------------------------- #[cfg(feature = "serde_json")]