use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde::Serialize; use crate::error::{AppError, AppResult}; /// Helper trait to convert error-centric results into the application's error type. pub trait IntoAppResult { fn into_app_result(self) -> AppResult; } impl IntoAppResult for Result where AppError: From, { fn into_app_result(self) -> AppResult { self.map_err(AppError::from) } } /// Extension helpers for optional values to map them into `AppResult`. pub trait OptionAppResultExt { fn or_not_found(self) -> AppResult; fn or_bad_request(self, message: impl Into) -> AppResult; } impl OptionAppResultExt for Option { fn or_not_found(self) -> AppResult { self.ok_or_else(AppError::not_found) } fn or_bad_request(self, message: impl Into) -> AppResult { self.ok_or_else(|| AppError::bad_request(message)) } } /// Provides helpers for statements returning number of affected rows. pub trait RowsAffectedExt: Sized { fn or_error(self, error: AppError) -> AppResult; fn or_not_found(self) -> AppResult { self.or_error(AppError::not_found()) } } impl RowsAffectedExt for usize { fn or_error(self, error: AppError) -> AppResult { if self == 0 { Err(error) } else { Ok(self) } } } /// Wrapper providing a consistent JSON response with a status code. pub struct JsonResponse { status: StatusCode, payload: T, } impl JsonResponse { pub fn new(status: StatusCode, payload: T) -> Self { Self { status, payload } } pub fn ok(payload: T) -> Self { Self::new(StatusCode::OK, payload) } pub fn created(payload: T) -> Self { Self::new(StatusCode::CREATED, payload) } pub fn accepted(payload: T) -> Self { Self::new(StatusCode::ACCEPTED, payload) } pub fn into_inner(self) -> T { self.payload } pub fn as_inner(&self) -> &T { &self.payload } } impl From for JsonResponse { fn from(value: T) -> Self { Self::ok(value) } } impl IntoResponse for JsonResponse where T: Serialize, { fn into_response(self) -> Response { (self.status, Json(self.payload)).into_response() } } /// Helper for returning empty responses with a status code. pub fn empty(status: StatusCode) -> AppResult { Ok(status) } /// Helper for returning `204 No Content`. pub fn no_content() -> AppResult { empty(StatusCode::NO_CONTENT) } /// Helper for returning JSON payloads with `200 OK`. pub fn ok_json(value: T) -> AppResult> where T: Serialize, { Ok(JsonResponse::ok(value)) } /// Helper for returning JSON payloads with `201 Created`. pub fn created_json(value: T) -> AppResult> where T: Serialize, { Ok(JsonResponse::created(value)) } /// Helper for returning JSON payloads with `202 Accepted`. pub fn accepted_json(value: T) -> AppResult> where T: Serialize, { Ok(JsonResponse::accepted(value)) } /// Standard wrapper for paginated responses. #[derive(Serialize)] pub struct PaginatedResponse where T: Serialize, M: Serialize, { pub data: T, pub meta: M, } pub fn paginated_json(data: T, meta: M) -> AppResult>> where T: Serialize, M: Serialize, { let payload = PaginatedResponse { data, meta }; Ok(JsonResponse::ok(payload)) }