use std::collections::HashMap;
use axum::{
http::{HeaderName, HeaderValue, StatusCode},
response::IntoResponse,
Json,
};
use chrono::{SecondsFormat, Utc};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
#[allow(clippy::result_large_err)]
#[derive(Debug)]
pub struct HttpResponse {
data: Box<Value>,
error: Box<Value>,
errors: Box<Value>,
code: StatusCode,
message: Box<str>,
headers: Option<HashMap<HeaderName, HeaderValue>>,
}
impl HttpResponse {
pub fn builder(code: StatusCode) -> Self {
Self {
code,
data: Box::new(Value::Null),
error: Box::new(Value::Null),
errors: Box::new(Value::Null),
message: code.canonical_reason().unwrap_or("No Message").into(),
headers: None,
}
}
pub fn message(mut self, message: impl Into<String>) -> Self {
self.message = message.into().into_boxed_str();
self
}
pub fn add_header(mut self, key: &str, value: &str) -> Self {
if let (Ok(header_name), Ok(header_value)) =
(HeaderName::try_from(key), HeaderValue::try_from(value))
{
(*self.headers.get_or_insert_with(HashMap::new)).insert(header_name, header_value);
}
self
}
pub fn data<T: Serialize>(mut self, data: T) -> Self {
let data = serde_json::to_value(data).unwrap_or_else(|err| {
eprintln!("Warning: Failed to serialize response data field: {err}");
Value::String("Serialization failed".into())
});
self.data = Box::new(data);
self
}
pub fn error<T: Serialize>(mut self, error: T) -> Self {
let error = serde_json::to_value(error).unwrap_or_else(|err| {
eprintln!("Warning: Failed to serialize response error field: {err}");
Value::String("Serialization failed".into())
});
self.error = Box::new(error);
self
}
pub fn errors<T: Serialize>(mut self, errors: T) -> Self {
let errors = serde_json::to_value(errors).unwrap_or_else(|err| {
eprintln!("Warning: Failed to serialize response errors field: {err}");
Value::String("Serialization failed".into())
});
self.errors = Box::new(errors);
self
}
pub fn build(self) -> impl IntoResponse {
self.into_response()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseBody {
pub code: u16,
pub success: bool,
pub message: Box<str>,
pub timestamp: String,
pub data: Option<Value>,
pub error: Option<Value>,
pub errors: Option<Value>,
}
impl IntoResponse for HttpResponse {
fn into_response(self) -> axum::response::Response {
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
let mut body = json!({
"code": self.code.as_u16(),
"success": self.code.is_success(),
"message": self.message,
"timestamp": timestamp,
});
if *self.data != Value::Null {
body.as_object_mut()
.unwrap()
.insert("data".into(), *self.data);
}
if *self.error != Value::Null {
body.as_object_mut()
.unwrap()
.insert("error".into(), *self.error);
}
if *self.errors != Value::Null {
body.as_object_mut()
.unwrap()
.insert("errors".into(), *self.errors);
}
let mut response = (self.code, Json(body)).into_response();
if let Some(headers) = self.headers {
for (key, value) in headers.iter() {
response.headers_mut().insert(key, value.clone());
}
}
response
}
}
impl HttpResponse {
pub fn Continue() -> Self {
Self::builder(StatusCode::CONTINUE)
}
pub fn SwitchingProtocols() -> Self {
Self::builder(StatusCode::SWITCHING_PROTOCOLS)
}
pub fn Processing() -> Self {
Self::builder(StatusCode::PROCESSING)
}
pub fn Ok() -> Self {
Self::builder(StatusCode::OK)
}
pub fn Created() -> Self {
Self::builder(StatusCode::CREATED)
}
pub fn Accepted() -> Self {
Self::builder(StatusCode::ACCEPTED)
}
pub fn NonAuthoritativeInformation() -> Self {
Self::builder(StatusCode::NON_AUTHORITATIVE_INFORMATION)
}
pub fn NoContent() -> Self {
Self::builder(StatusCode::NO_CONTENT)
}
pub fn ResetContent() -> Self {
Self::builder(StatusCode::RESET_CONTENT)
}
pub fn PartialContent() -> Self {
Self::builder(StatusCode::PARTIAL_CONTENT)
}
pub fn MultiStatus() -> Self {
Self::builder(StatusCode::MULTI_STATUS)
}
pub fn AlreadyReported() -> Self {
Self::builder(StatusCode::ALREADY_REPORTED)
}
pub fn ImUsed() -> Self {
Self::builder(StatusCode::IM_USED)
}
pub fn MultipleChoices() -> Self {
Self::builder(StatusCode::MULTIPLE_CHOICES)
}
pub fn MovedPermanently() -> Self {
Self::builder(StatusCode::MOVED_PERMANENTLY)
}
pub fn Found() -> Self {
Self::builder(StatusCode::FOUND)
}
pub fn SeeOther() -> Self {
Self::builder(StatusCode::SEE_OTHER)
}
pub fn NotModified() -> Self {
Self::builder(StatusCode::NOT_MODIFIED)
}
pub fn UseProxy() -> Self {
Self::builder(StatusCode::USE_PROXY)
}
pub fn TemporaryRedirect() -> Self {
Self::builder(StatusCode::TEMPORARY_REDIRECT)
}
pub fn PermanentRedirect() -> Self {
Self::builder(StatusCode::PERMANENT_REDIRECT)
}
pub fn BadRequest() -> Self {
Self::builder(StatusCode::BAD_REQUEST)
}
pub fn Unauthorized() -> Self {
Self::builder(StatusCode::UNAUTHORIZED)
}
pub fn PaymentRequired() -> Self {
Self::builder(StatusCode::PAYMENT_REQUIRED)
}
pub fn Forbidden() -> Self {
Self::builder(StatusCode::FORBIDDEN)
}
pub fn NotFound() -> Self {
Self::builder(StatusCode::NOT_FOUND)
}
pub fn MethodNotAllowed() -> Self {
Self::builder(StatusCode::METHOD_NOT_ALLOWED)
}
pub fn NotAcceptable() -> Self {
Self::builder(StatusCode::NOT_ACCEPTABLE)
}
pub fn ProxyAuthenticationRequired() -> Self {
Self::builder(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
}
pub fn RequestTimeout() -> Self {
Self::builder(StatusCode::REQUEST_TIMEOUT)
}
pub fn Conflict() -> Self {
Self::builder(StatusCode::CONFLICT)
}
pub fn Gone() -> Self {
Self::builder(StatusCode::GONE)
}
pub fn LengthRequired() -> Self {
Self::builder(StatusCode::LENGTH_REQUIRED)
}
pub fn PreconditionFailed() -> Self {
Self::builder(StatusCode::PRECONDITION_FAILED)
}
pub fn PayloadTooLarge() -> Self {
Self::builder(StatusCode::PAYLOAD_TOO_LARGE)
}
pub fn UriTooLong() -> Self {
Self::builder(StatusCode::URI_TOO_LONG)
}
pub fn UnsupportedMediaType() -> Self {
Self::builder(StatusCode::UNSUPPORTED_MEDIA_TYPE)
}
pub fn RangeNotSatisfiable() -> Self {
Self::builder(StatusCode::RANGE_NOT_SATISFIABLE)
}
pub fn ExpectationFailed() -> Self {
Self::builder(StatusCode::EXPECTATION_FAILED)
}
pub fn ImATeapot() -> Self {
Self::builder(StatusCode::IM_A_TEAPOT)
}
pub fn MisdirectedRequest() -> Self {
Self::builder(StatusCode::MISDIRECTED_REQUEST)
}
pub fn UnprocessableEntity() -> Self {
Self::builder(StatusCode::UNPROCESSABLE_ENTITY)
}
pub fn Locked() -> Self {
Self::builder(StatusCode::LOCKED)
}
pub fn FailedDependency() -> Self {
Self::builder(StatusCode::FAILED_DEPENDENCY)
}
pub fn TooEarly() -> Self {
Self::builder(StatusCode::TOO_EARLY)
}
pub fn UpgradeRequired() -> Self {
Self::builder(StatusCode::UPGRADE_REQUIRED)
}
pub fn PreconditionRequired() -> Self {
Self::builder(StatusCode::PRECONDITION_REQUIRED)
}
pub fn TooManyRequests() -> Self {
Self::builder(StatusCode::TOO_MANY_REQUESTS)
}
pub fn RequestHeaderFieldsTooLarge() -> Self {
Self::builder(StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE)
}
pub fn UnavailableForLegalReasons() -> Self {
Self::builder(StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS)
}
pub fn InternalServerError() -> Self {
Self::builder(StatusCode::INTERNAL_SERVER_ERROR)
}
pub fn NotImplemented() -> Self {
Self::builder(StatusCode::NOT_IMPLEMENTED)
}
pub fn BadGateway() -> Self {
Self::builder(StatusCode::BAD_GATEWAY)
}
pub fn ServiceUnavailable() -> Self {
Self::builder(StatusCode::SERVICE_UNAVAILABLE)
}
pub fn GatewayTimeout() -> Self {
Self::builder(StatusCode::GATEWAY_TIMEOUT)
}
pub fn HttpVersionNotSupported() -> Self {
Self::builder(StatusCode::HTTP_VERSION_NOT_SUPPORTED)
}
pub fn VariantAlsoNegotiates() -> Self {
Self::builder(StatusCode::VARIANT_ALSO_NEGOTIATES)
}
pub fn InsufficientStorage() -> Self {
Self::builder(StatusCode::INSUFFICIENT_STORAGE)
}
pub fn LoopDetected() -> Self {
Self::builder(StatusCode::LOOP_DETECTED)
}
pub fn NotExtended() -> Self {
Self::builder(StatusCode::NOT_EXTENDED)
}
pub fn NetworkAuthenticationRequired() -> Self {
Self::builder(StatusCode::NETWORK_AUTHENTICATION_REQUIRED)
}
}