Skip to content

Commit a3d71e7

Browse files
authored
Adding more tests and renamed custom validation error handler fn (#10)
1 parent 1ffea4e commit a3d71e7

File tree

4 files changed

+268
-40
lines changed

4 files changed

+268
-40
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ garde = { version = "0.20", optional = true }
1717

1818
[dev-dependencies]
1919
serde = { version = "1", features = ["derive"]}
20+
serde_json = "1"
2021
validator = { version = "0.18", features = ["derive"] }
2122
garde = { version = "0.20", features = ["derive"] }
2223

src/custom.rs

Lines changed: 91 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl<T> std::ops::DerefMut for Validated<T> {
8080
pub struct ValidatedFut<T: FromRequest> {
8181
req: actix_web::HttpRequest,
8282
fut: <T as FromRequest>::Future,
83-
error_handler: Option<ValidatorErrHandler>,
83+
error_handler: Option<ValidationErrHandler>,
8484
}
8585
impl<T> Future for ValidatedFut<T>
8686
where
@@ -132,7 +132,7 @@ where
132132
payload: &mut actix_web::dev::Payload,
133133
) -> Self::Future {
134134
let error_handler = req
135-
.app_data::<ValidatorErrorHandler>()
135+
.app_data::<ValidationErrorHandler>()
136136
.map(|h| h.handler.clone());
137137

138138
let fut = T::from_request(req, payload);
@@ -184,29 +184,30 @@ impl ResponseError for Error {
184184
}
185185
}
186186

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

190-
struct ValidatorErrorHandler {
191-
handler: ValidatorErrHandler,
190+
struct ValidationErrorHandler {
191+
handler: ValidationErrHandler,
192192
}
193193

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

198-
impl<T> ValidatorErrorHandlerExt for App<T>
198+
impl<T> ValidationErrorHandlerExt for App<T>
199199
where
200200
T: ServiceFactory<ServiceRequest, Config = (), Error = actix_web::Error, InitError = ()>,
201201
{
202-
fn validator_error_handler(self, handler: ValidatorErrHandler) -> Self {
203-
self.app_data(ValidatorErrorHandler { handler })
202+
fn validation_error_handler(self, handler: ValidationErrHandler) -> Self {
203+
self.app_data(ValidationErrorHandler { handler })
204204
}
205205
}
206206

207207
#[cfg(test)]
208208
mod test {
209209
use super::*;
210+
use actix_web::web::Bytes;
210211
use actix_web::{http::header::ContentType, post, test, web::Json, App, Responder};
211212
use serde::{Deserialize, Serialize};
212213

@@ -227,14 +228,14 @@ mod test {
227228
}
228229
}
229230

231+
#[post("/")]
232+
async fn endpoint(v: Validated<Json<ExamplePayload>>) -> impl Responder {
233+
assert!(v.name.len() > 4);
234+
HttpResponse::Ok().body(())
235+
}
236+
230237
#[actix_web::test]
231238
async fn should_validate_simple() {
232-
#[post("/")]
233-
async fn endpoint(v: Validated<Json<ExamplePayload>>) -> impl Responder {
234-
assert!(v.name.len() > 4);
235-
HttpResponse::Ok().body(())
236-
}
237-
238239
let app = test::init_service(App::new().service(endpoint)).await;
239240

240241
// Valid request
@@ -259,4 +260,78 @@ mod test {
259260
let resp = test::call_service(&app, req).await;
260261
assert_eq!(resp.status().as_u16(), 400);
261262
}
263+
264+
#[actix_web::test]
265+
async fn should_respond_with_errors_correctly() {
266+
let app = test::init_service(App::new().service(endpoint)).await;
267+
268+
// Invalid request
269+
let req = test::TestRequest::post()
270+
.uri("/")
271+
.insert_header(ContentType::plaintext())
272+
.set_json(ExamplePayload {
273+
name: "1234".to_string(),
274+
})
275+
.to_request();
276+
let result = test::call_and_read_body(&app, req).await;
277+
assert_eq!(
278+
result,
279+
Bytes::from_static(b"Validation errors in fields:\n\tname not long enough")
280+
);
281+
}
282+
283+
#[derive(Debug, Serialize, Error)]
284+
struct CustomErrorResponse {
285+
custom_message: String,
286+
errors: Vec<String>,
287+
}
288+
289+
impl Display for CustomErrorResponse {
290+
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291+
unimplemented!()
292+
}
293+
}
294+
295+
impl ResponseError for CustomErrorResponse {
296+
fn status_code(&self) -> actix_web::http::StatusCode {
297+
actix_web::http::StatusCode::BAD_REQUEST
298+
}
299+
300+
fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
301+
HttpResponse::build(self.status_code()).body(serde_json::to_string(self).unwrap())
302+
}
303+
}
304+
305+
fn error_handler(errors: Vec<ValidationError>, _: &HttpRequest) -> actix_web::Error {
306+
CustomErrorResponse {
307+
custom_message: "My custom message".to_string(),
308+
errors: errors.iter().map(|err| err.message.clone()).collect(),
309+
}
310+
.into()
311+
}
312+
313+
#[actix_web::test]
314+
async fn should_use_allow_custom_error_responses() {
315+
let app = test::init_service(
316+
App::new()
317+
.service(endpoint)
318+
.validation_error_handler(Arc::new(error_handler)),
319+
)
320+
.await;
321+
322+
let req = test::TestRequest::post()
323+
.uri("/")
324+
.insert_header(ContentType::plaintext())
325+
.set_json(ExamplePayload {
326+
name: "1234".to_string(),
327+
})
328+
.to_request();
329+
let result = test::call_and_read_body(&app, req).await;
330+
assert_eq!(
331+
result,
332+
Bytes::from_static(
333+
b"{\"custom_message\":\"My custom message\",\"errors\":[\"name not long enough\"]}"
334+
)
335+
);
336+
}
262337
}

src/garde.rs

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -181,24 +181,25 @@ where
181181
#[cfg(test)]
182182
mod test {
183183
use super::*;
184+
use actix_web::web::Bytes;
184185
use actix_web::{http::header::ContentType, post, test, web::Json, App, Responder};
185186
use garde::Validate;
186187
use serde::{Deserialize, Serialize};
187188

188-
#[actix_web::test]
189-
async fn should_validate_simple() {
190-
#[derive(Debug, Deserialize, Serialize, Validate)]
191-
struct ExamplePayload {
192-
#[garde(length(min = 5))]
193-
name: String,
194-
}
189+
#[derive(Debug, Deserialize, Serialize, Validate)]
190+
struct ExamplePayload {
191+
#[garde(length(min = 5))]
192+
name: String,
193+
}
195194

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

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

204205
// Valid request
@@ -223,4 +224,76 @@ mod test {
223224
let resp = test::call_service(&app, req).await;
224225
assert_eq!(resp.status().as_u16(), 400);
225226
}
227+
228+
#[actix_web::test]
229+
async fn should_respond_with_errors_correctly() {
230+
let app = test::init_service(App::new().service(endpoint)).await;
231+
232+
// Invalid request
233+
let req = test::TestRequest::post()
234+
.uri("/")
235+
.insert_header(ContentType::plaintext())
236+
.set_json(ExamplePayload {
237+
name: "1234".to_string(),
238+
})
239+
.to_request();
240+
let result = test::call_and_read_body(&app, req).await;
241+
assert_eq!(
242+
result,
243+
Bytes::from_static(b"Validation errors in fields:\nname: length is lower than 5")
244+
);
245+
}
246+
247+
#[derive(Debug, Serialize, Error)]
248+
struct CustomErrorResponse {
249+
custom_message: String,
250+
errors: Vec<String>,
251+
}
252+
253+
impl Display for CustomErrorResponse {
254+
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255+
unimplemented!()
256+
}
257+
}
258+
259+
impl ResponseError for CustomErrorResponse {
260+
fn status_code(&self) -> actix_web::http::StatusCode {
261+
actix_web::http::StatusCode::BAD_REQUEST
262+
}
263+
264+
fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
265+
HttpResponse::build(self.status_code()).body(serde_json::to_string(self).unwrap())
266+
}
267+
}
268+
269+
fn error_handler(errors: ::garde::Report, _: &HttpRequest) -> actix_web::Error {
270+
CustomErrorResponse {
271+
custom_message: "My custom message".to_string(),
272+
errors: errors.iter().map(|(_, err)| err.to_string()).collect(),
273+
}
274+
.into()
275+
}
276+
277+
#[actix_web::test]
278+
async fn should_use_allow_custom_error_responses() {
279+
let app = test::init_service(
280+
App::new()
281+
.service(endpoint)
282+
.garde_error_handler(Arc::new(error_handler)),
283+
)
284+
.await;
285+
286+
let req = test::TestRequest::post()
287+
.uri("/")
288+
.insert_header(ContentType::plaintext())
289+
.set_json(ExamplePayload {
290+
name: "1234".to_string(),
291+
})
292+
.to_request();
293+
let result = test::call_and_read_body(&app, req).await;
294+
assert_eq!(
295+
result,
296+
Bytes::from_static(b"{\"custom_message\":\"My custom message\",\"errors\":[\"length is lower than 5\"]}")
297+
);
298+
}
226299
}

0 commit comments

Comments
 (0)