Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(responder): introduce "response code" mechanism, user can provide it dynamically #109

Merged
merged 4 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,313 changes: 471 additions & 842 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "medullah-web"
version = "0.26.0"
version = "0.27.0"
edition = "2021"
license = "MIT"
description = "Micro-Framework Based on Ntex"
Expand Down
3 changes: 3 additions & 0 deletions src/contracts/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod response_code_contract;

pub use response_code_contract::ResponseCodeContract;
16 changes: 16 additions & 0 deletions src/contracts/response_code_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use ntex::http::StatusCode;

pub trait ResponseCodeContract: Clone {
fn code(&self) -> &str;

fn status(&self) -> StatusCode;

fn success(&self) -> bool {
let code = self.status().as_u16();
(200..300).contains(&code)
}

fn from_code(code: &str) -> Self;

fn from_status(status: StatusCode) -> Self;
}
55 changes: 33 additions & 22 deletions src/enums/app_message.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use crate::contracts::ResponseCodeContract;
use crate::enums::ResponseCode;
#[cfg(feature = "reqwest")]
use crate::helpers::reqwest::ReqwestResponseError;
use crate::helpers::responder::Responder;
use log::error;
#[cfg(feature = "multipart")]
use medullah_multipart::{ErrorMessage as MultipartErrorMessage, MultipartError};
use ntex::http::error::BlockingError;
use ntex::http::StatusCode;
use ntex::web::{HttpRequest, WebResponseError};
Expand Down Expand Up @@ -222,7 +226,7 @@ fn send_response(message: &AppMessage) -> ntex::web::HttpResponse {
#[cfg(feature = "jwt")]
AppMessage::JwtError(message) => {
log::error!("Jwt Error: {}", message);
Responder::message("invalid jwt token", StatusCode::UNAUTHORIZED)
Responder::message("invalid jwt token", ResponseCode::Unauthorized)
}
#[cfg(feature = "crypto")]
AppMessage::ArgonError(message) => {
Expand Down Expand Up @@ -279,7 +283,7 @@ fn send_response(message: &AppMessage) -> ntex::web::HttpResponse {
}
AppMessage::SerdeError(message) => {
log::error!("Serde Error: {:?}", message);
Responder::message(&message.to_string(), StatusCode::BAD_REQUEST)
Responder::message(&message.to_string(), ResponseCode::BadRequest)
}
AppMessage::SerdeError500(message) => {
log::error!("Serde Error: {}", message);
Expand Down Expand Up @@ -313,38 +317,38 @@ fn send_response(message: &AppMessage) -> ntex::web::HttpResponse {
}
AppMessage::SuccessMessage(message) => Responder::ok_message(message),
AppMessage::SuccessMessageString(message) => Responder::ok_message(message),
AppMessage::ErrorMessage(message, status) => Responder::message(message, *status),
AppMessage::ErrorMessage(message, status) => {
Responder::message(message, ResponseCode::from_status(*status))
}
AppMessage::UnAuthorized => {
Responder::message(&message.message(), StatusCode::UNAUTHORIZED)
Responder::message(&message.message(), ResponseCode::Unauthorized)
}
AppMessage::UnAuthorizedMessage(message) => {
Responder::message(message, StatusCode::UNAUTHORIZED)
Responder::message(message, ResponseCode::Unauthorized)
}
AppMessage::UnAuthorizedMessageString(message) => {
Responder::message(message, StatusCode::UNAUTHORIZED)
Responder::message(message, ResponseCode::Unauthorized)
}
AppMessage::Forbidden => Responder::message(&message.message(), ResponseCode::Forbidden),
AppMessage::ForbiddenMessage(message) => {
Responder::message(message, ResponseCode::Forbidden)
}
AppMessage::Forbidden => Responder::message(&message.message(), StatusCode::FORBIDDEN),
AppMessage::ForbiddenMessage(message) => Responder::message(message, StatusCode::FORBIDDEN),
AppMessage::ForbiddenMessageString(message) => {
Responder::message(message, StatusCode::FORBIDDEN)
Responder::message(message, ResponseCode::Forbidden)
}
AppMessage::ChronoParseError(error) => {
let message = error.to_string();
log::error!("Failed To Parse DateTime: {}", message);
Responder::message(&message, StatusCode::BAD_REQUEST)
Responder::message(&message, ResponseCode::BadRequest)
}
#[cfg(feature = "validator")]
AppMessage::FormValidationError(e) => Responder::failure(
e,
Some(String::from("Validation Error")),
StatusCode::BAD_REQUEST,
),
AppMessage::FormValidationError(e) => {
Responder::send_msg(e, ResponseCode::BadRequest, "Validation Error")
}
#[cfg(feature = "multipart")]
AppMessage::MultipartError(e) => Responder::failure(
e.to_string(),
Some(String::from("Multipart Error")),
StatusCode::BAD_REQUEST,
),
AppMessage::MultipartError(e) => {
Responder::send_msg(e.to_string(), ResponseCode::BadRequest, "File Upload Error")
}
#[cfg(feature = "database")]
AppMessage::DatabaseError(err) => match err {
diesel::result::Error::NotFound => {
Expand All @@ -354,7 +358,7 @@ fn send_response(message: &AppMessage) -> ntex::web::HttpResponse {
error!("database error: {:?}", err);
match err {
diesel::result::DatabaseErrorKind::UniqueViolation => {
Responder::message(&message.message(), StatusCode::CONFLICT)
Responder::message(&message.message(), ResponseCode::BadRequest)
}
_ => Responder::internal_server_error(),
}
Expand Down Expand Up @@ -386,7 +390,14 @@ fn get_status_code(status: &AppMessage) -> StatusCode {
| AppMessage::ChronoParseError(_) => StatusCode::BAD_REQUEST,
AppMessage::EntityNotFound(_msg) => StatusCode::NOT_FOUND,
#[cfg(feature = "multipart")]
AppMessage::MultipartError(_) => StatusCode::BAD_REQUEST,
AppMessage::MultipartError(err) => match err {
MultipartError::ValidationError(err) => match err.error {
MultipartErrorMessage::InvalidFileExtension(_)
| MultipartErrorMessage::InvalidContentType(_) => StatusCode::UNSUPPORTED_MEDIA_TYPE,
_ => StatusCode::BAD_REQUEST,
},
_ => StatusCode::BAD_REQUEST,
},
#[cfg(feature = "database")]
AppMessage::DatabaseError(DieselError::NotFound) => StatusCode::NOT_FOUND,
#[cfg(feature = "database")]
Expand Down
3 changes: 3 additions & 0 deletions src/enums/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
pub mod app_message;
mod response_code;

pub use response_code::ResponseCode;
95 changes: 95 additions & 0 deletions src/enums/response_code.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::contracts::ResponseCodeContract;
use ntex::http::StatusCode;

#[derive(Clone)]
pub enum ResponseCode {
Ok,
Created,
Accepted,
NoContent,
BadRequest,
Unauthorized,
PaymentRequired,
Forbidden,
NotFound,
Conflict,
InternalServerError,
ServiceUnavailable,
NotImplemented,
}

impl ResponseCodeContract for ResponseCode {
fn code(&self) -> &str {
match self {
ResponseCode::Ok => "000",
ResponseCode::Created => "001",
ResponseCode::Accepted => "002",
ResponseCode::NoContent => "003",
ResponseCode::BadRequest => "004",
ResponseCode::Unauthorized => "005",
ResponseCode::PaymentRequired => "006",
ResponseCode::Forbidden => "007",
ResponseCode::NotFound => "008",
ResponseCode::Conflict => "009",
ResponseCode::InternalServerError => "010",
ResponseCode::ServiceUnavailable => "011",
ResponseCode::NotImplemented => "012",
}
}

fn status(&self) -> StatusCode {
match self {
ResponseCode::Ok => StatusCode::OK,
ResponseCode::Created => StatusCode::CREATED,
ResponseCode::Accepted => StatusCode::ACCEPTED,
ResponseCode::NoContent => StatusCode::NO_CONTENT,
ResponseCode::BadRequest => StatusCode::BAD_REQUEST,
ResponseCode::Unauthorized => StatusCode::UNAUTHORIZED,
ResponseCode::PaymentRequired => StatusCode::PAYMENT_REQUIRED,
ResponseCode::Forbidden => StatusCode::FORBIDDEN,
ResponseCode::NotFound => StatusCode::NOT_FOUND,
ResponseCode::Conflict => StatusCode::CONFLICT,
ResponseCode::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
ResponseCode::ServiceUnavailable => StatusCode::SERVICE_UNAVAILABLE,
ResponseCode::NotImplemented => StatusCode::NOT_IMPLEMENTED,
}
}

fn from_code(code: &str) -> Self {
match code {
"000" => ResponseCode::Ok,
"001" => ResponseCode::Created,
"002" => ResponseCode::Accepted,
"003" => ResponseCode::NoContent,
"004" => ResponseCode::BadRequest,
"005" => ResponseCode::Unauthorized,
"006" => ResponseCode::PaymentRequired,
"007" => ResponseCode::Forbidden,
"008" => ResponseCode::NotFound,
"009" => ResponseCode::Conflict,
"010" => ResponseCode::InternalServerError,
"011" => ResponseCode::ServiceUnavailable,
"012" => ResponseCode::NotImplemented,
_ => panic!("Invalid response code"),
}
}

fn from_status(status: StatusCode) -> Self {
match status {
StatusCode::OK => ResponseCode::Ok,
StatusCode::CREATED => ResponseCode::Created,
StatusCode::ACCEPTED => ResponseCode::Accepted,
StatusCode::NO_CONTENT => ResponseCode::NoContent,
StatusCode::BAD_REQUEST => ResponseCode::BadRequest,
StatusCode::UNAUTHORIZED => ResponseCode::Unauthorized,
StatusCode::PAYMENT_REQUIRED => ResponseCode::PaymentRequired,
StatusCode::FORBIDDEN => ResponseCode::Forbidden,
StatusCode::NOT_FOUND => ResponseCode::NotFound,
StatusCode::CONFLICT => ResponseCode::Conflict,
StatusCode::INTERNAL_SERVER_ERROR => ResponseCode::InternalServerError,
StatusCode::SERVICE_UNAVAILABLE => ResponseCode::ServiceUnavailable,
StatusCode::NOT_IMPLEMENTED => ResponseCode::NotImplemented,
_ => panic!("Invalid status code"),
}
}
}
68 changes: 17 additions & 51 deletions src/helpers/json_message.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use ntex::http::StatusCode;
use serde::Serialize;

use crate::helpers::responder::JsonResponse;
Expand All @@ -7,75 +6,39 @@ use crate::helpers::time::current_timestamp;
pub struct JsonMessage;

impl JsonMessage {
pub fn ok<T: Serialize>(data: T, message: Option<String>) -> JsonResponse<T> {
Self::base(data, StatusCode::OK, true, message)
}

pub fn success<T: Serialize>(
data: T,
message: Option<String>,
status: StatusCode,
) -> JsonResponse<T> {
Self::base(data, status, true, message)
}

pub fn failure<T: Serialize>(
pub fn make<T: Serialize>(
data: T,
message: Option<String>,
status: StatusCode,
) -> JsonResponse<T> {
Self::base(data, status, false, message)
}

fn base<T: Serialize>(
data: T,
status: StatusCode,
code: &str,
success: bool,
message: Option<String>,
) -> JsonResponse<T> {
JsonResponse {
success,
code: status.as_u16(),
status: status.to_string(),
timestamp: current_timestamp(),
data,
success,
message,
code: code.to_string(),
timestamp: current_timestamp(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use ntex::http::StatusCode;
use crate::contracts::ResponseCodeContract;
use crate::enums::ResponseCode;
use serde_json::json;

#[test]
fn test_ok() {
let data = json!({"key": "value"});
let message = Some("Operation successful".to_string());

let response = JsonMessage::ok(data.clone(), message.clone());

assert!(response.success);
assert_eq!(response.code, StatusCode::OK.as_u16());
assert_eq!(response.status, StatusCode::OK.to_string());
assert!(response.timestamp > 0); // Check if timestamp is a positive value
assert_eq!(response.data, data);
assert_eq!(response.message, message);
}

#[test]
fn test_success() {
let data = json!({"key": "value"});
let message = Some("Operation successful".to_string());
let status = StatusCode::CREATED;

let response = JsonMessage::success(data.clone(), message.clone(), status);
let response =
JsonMessage::make(data.clone(), ResponseCode::Ok.code(), true, message.clone());

assert!(response.success);
assert_eq!(response.code, status.as_u16());
assert_eq!(response.status, status.to_string());
assert_eq!(response.code, ResponseCode::Ok.code());
assert!(response.timestamp > 0); // Check if timestamp is a positive value
assert_eq!(response.data, data);
assert_eq!(response.message, message);
Expand All @@ -85,13 +48,16 @@ mod tests {
fn test_failure() {
let data = json!({"error": "something went wrong"});
let message = Some("Operation failed".to_string());
let status = StatusCode::INTERNAL_SERVER_ERROR;

let response = JsonMessage::failure(data.clone(), message.clone(), status);
let response = JsonMessage::make(
data.clone(),
ResponseCode::InternalServerError.code(),
false,
message.clone(),
);

assert!(!response.success);
assert_eq!(response.code, status.as_u16());
assert_eq!(response.status, status.to_string());
assert_eq!(response.code, ResponseCode::InternalServerError.code());
assert!(response.timestamp > 0); // Check if timestamp is a positive value
assert_eq!(response.data, data);
assert_eq!(response.message, message);
Expand Down
Loading
Loading