Skip to content

Commit

Permalink
Adding more tests and renamed custom validation error handler fn (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
ranger-ross authored Jul 7, 2024
1 parent 1ffea4e commit a3d71e7
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 40 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ garde = { version = "0.20", optional = true }

[dev-dependencies]
serde = { version = "1", features = ["derive"]}
serde_json = "1"
validator = { version = "0.18", features = ["derive"] }
garde = { version = "0.20", features = ["derive"] }

Expand Down
107 changes: 91 additions & 16 deletions src/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl<T> std::ops::DerefMut for Validated<T> {
pub struct ValidatedFut<T: FromRequest> {
req: actix_web::HttpRequest,
fut: <T as FromRequest>::Future,
error_handler: Option<ValidatorErrHandler>,
error_handler: Option<ValidationErrHandler>,
}
impl<T> Future for ValidatedFut<T>
where
Expand Down Expand Up @@ -132,7 +132,7 @@ where
payload: &mut actix_web::dev::Payload,
) -> Self::Future {
let error_handler = req
.app_data::<ValidatorErrorHandler>()
.app_data::<ValidationErrorHandler>()
.map(|h| h.handler.clone());

let fut = T::from_request(req, payload);
Expand Down Expand Up @@ -184,29 +184,30 @@ impl ResponseError for Error {
}
}

pub type ValidatorErrHandler =
pub type ValidationErrHandler =
Arc<dyn Fn(Vec<ValidationError>, &HttpRequest) -> actix_web::Error + Send + Sync>;

struct ValidatorErrorHandler {
handler: ValidatorErrHandler,
struct ValidationErrorHandler {
handler: ValidationErrHandler,
}

pub trait ValidatorErrorHandlerExt {
fn validator_error_handler(self, handler: ValidatorErrHandler) -> Self;
pub trait ValidationErrorHandlerExt {
fn validation_error_handler(self, handler: ValidationErrHandler) -> Self;
}

impl<T> ValidatorErrorHandlerExt for App<T>
impl<T> ValidationErrorHandlerExt for App<T>
where
T: ServiceFactory<ServiceRequest, Config = (), Error = actix_web::Error, InitError = ()>,
{
fn validator_error_handler(self, handler: ValidatorErrHandler) -> Self {
self.app_data(ValidatorErrorHandler { handler })
fn validation_error_handler(self, handler: ValidationErrHandler) -> Self {
self.app_data(ValidationErrorHandler { handler })
}
}

#[cfg(test)]
mod test {
use super::*;
use actix_web::web::Bytes;
use actix_web::{http::header::ContentType, post, test, web::Json, App, Responder};
use serde::{Deserialize, Serialize};

Expand All @@ -227,14 +228,14 @@ mod test {
}
}

#[post("/")]
async fn endpoint(v: Validated<Json<ExamplePayload>>) -> impl Responder {
assert!(v.name.len() > 4);
HttpResponse::Ok().body(())
}

#[actix_web::test]
async fn should_validate_simple() {
#[post("/")]
async fn endpoint(v: Validated<Json<ExamplePayload>>) -> impl Responder {
assert!(v.name.len() > 4);
HttpResponse::Ok().body(())
}

let app = test::init_service(App::new().service(endpoint)).await;

// Valid request
Expand All @@ -259,4 +260,78 @@ mod test {
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status().as_u16(), 400);
}

#[actix_web::test]
async fn should_respond_with_errors_correctly() {
let app = test::init_service(App::new().service(endpoint)).await;

// Invalid request
let req = test::TestRequest::post()
.uri("/")
.insert_header(ContentType::plaintext())
.set_json(ExamplePayload {
name: "1234".to_string(),
})
.to_request();
let result = test::call_and_read_body(&app, req).await;
assert_eq!(
result,
Bytes::from_static(b"Validation errors in fields:\n\tname not long enough")
);
}

#[derive(Debug, Serialize, Error)]
struct CustomErrorResponse {
custom_message: String,
errors: Vec<String>,
}

impl Display for CustomErrorResponse {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unimplemented!()
}
}

impl ResponseError for CustomErrorResponse {
fn status_code(&self) -> actix_web::http::StatusCode {
actix_web::http::StatusCode::BAD_REQUEST
}

fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
HttpResponse::build(self.status_code()).body(serde_json::to_string(self).unwrap())
}
}

fn error_handler(errors: Vec<ValidationError>, _: &HttpRequest) -> actix_web::Error {
CustomErrorResponse {
custom_message: "My custom message".to_string(),
errors: errors.iter().map(|err| err.message.clone()).collect(),
}
.into()
}

#[actix_web::test]
async fn should_use_allow_custom_error_responses() {
let app = test::init_service(
App::new()
.service(endpoint)
.validation_error_handler(Arc::new(error_handler)),
)
.await;

let req = test::TestRequest::post()
.uri("/")
.insert_header(ContentType::plaintext())
.set_json(ExamplePayload {
name: "1234".to_string(),
})
.to_request();
let result = test::call_and_read_body(&app, req).await;
assert_eq!(
result,
Bytes::from_static(
b"{\"custom_message\":\"My custom message\",\"errors\":[\"name not long enough\"]}"
)
);
}
}
97 changes: 85 additions & 12 deletions src/garde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,24 +181,25 @@ where
#[cfg(test)]
mod test {
use super::*;
use actix_web::web::Bytes;
use actix_web::{http::header::ContentType, post, test, web::Json, App, Responder};
use garde::Validate;
use serde::{Deserialize, Serialize};

#[actix_web::test]
async fn should_validate_simple() {
#[derive(Debug, Deserialize, Serialize, Validate)]
struct ExamplePayload {
#[garde(length(min = 5))]
name: String,
}
#[derive(Debug, Deserialize, Serialize, Validate)]
struct ExamplePayload {
#[garde(length(min = 5))]
name: String,
}

#[post("/")]
async fn endpoint(v: Validated<Json<ExamplePayload>>) -> impl Responder {
assert!(v.name.len() > 4);
HttpResponse::Ok().body(())
}
#[post("/")]
async fn endpoint(v: Validated<Json<ExamplePayload>>) -> impl Responder {
assert!(v.name.len() > 4);
HttpResponse::Ok().body(())
}

#[actix_web::test]
async fn should_validate_simple() {
let app = test::init_service(App::new().service(endpoint)).await;

// Valid request
Expand All @@ -223,4 +224,76 @@ mod test {
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status().as_u16(), 400);
}

#[actix_web::test]
async fn should_respond_with_errors_correctly() {
let app = test::init_service(App::new().service(endpoint)).await;

// Invalid request
let req = test::TestRequest::post()
.uri("/")
.insert_header(ContentType::plaintext())
.set_json(ExamplePayload {
name: "1234".to_string(),
})
.to_request();
let result = test::call_and_read_body(&app, req).await;
assert_eq!(
result,
Bytes::from_static(b"Validation errors in fields:\nname: length is lower than 5")
);
}

#[derive(Debug, Serialize, Error)]
struct CustomErrorResponse {
custom_message: String,
errors: Vec<String>,
}

impl Display for CustomErrorResponse {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unimplemented!()
}
}

impl ResponseError for CustomErrorResponse {
fn status_code(&self) -> actix_web::http::StatusCode {
actix_web::http::StatusCode::BAD_REQUEST
}

fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
HttpResponse::build(self.status_code()).body(serde_json::to_string(self).unwrap())
}
}

fn error_handler(errors: ::garde::Report, _: &HttpRequest) -> actix_web::Error {
CustomErrorResponse {
custom_message: "My custom message".to_string(),
errors: errors.iter().map(|(_, err)| err.to_string()).collect(),
}
.into()
}

#[actix_web::test]
async fn should_use_allow_custom_error_responses() {
let app = test::init_service(
App::new()
.service(endpoint)
.garde_error_handler(Arc::new(error_handler)),
)
.await;

let req = test::TestRequest::post()
.uri("/")
.insert_header(ContentType::plaintext())
.set_json(ExamplePayload {
name: "1234".to_string(),
})
.to_request();
let result = test::call_and_read_body(&app, req).await;
assert_eq!(
result,
Bytes::from_static(b"{\"custom_message\":\"My custom message\",\"errors\":[\"length is lower than 5\"]}")
);
}
}
Loading

0 comments on commit a3d71e7

Please sign in to comment.