Filtro CPL Polarizado ventajas

How to validate Request Headers [String, Integer and Boolean] @RequestBody @RequestHeader Spring Boot - Full example

Bienvenidos a esta nueva entrada del blog.

Veremos como validar un String, Integer y un Boolean en el Request o el Header que se declaran en un @RequestHeader o @RequestBody de un Microservicio REST con Spring Boot.

Para nuestro ejemplo manejaremos una identidad  o model en nuestro @RequestBody que contendrá lo siguiente:

 

package com.spring.remote.model;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class RequestBodyModel {
@NotEmpty
private String name;
@NotNull
@Min(1)
private Integer age;
@NotNull
private Boolean active;
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Nuestro código del controller esta así

 

package com.spring.remote.controllers;
import javax.validation.Valid;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.spring.remote.model.RequestBodyModel;
@RestController
@RequestMapping("/data")
public class ValidController {
@GetMapping("/person")
public ResponseEntity<Object> getData(
@RequestHeader(required = true) Integer idType,
@RequestHeader(required = true) String identify,
@Valid@RequestBody(required = true) RequestBodyModel request
){
return new ResponseEntity<Object>("Everything is OK", new HttpHeaders(), HttpStatus.OK);
}
}
 

Si todo sale bien nos regresará un mensaje diciendo que todo esta bien. 

Al armar un request desde Postman quedaría de la siguiente manera

 


Pero si le ingresamos tipos de datos que no son los correspondientes con los esperados por el @RequestHeader o el modelo del @RequestBody ??

 Si le cambio el tipo de dato en el request de Postman al atributo age y envío la petición pasa lo siguiente

 

 Nos salta un error que no se puede parsear al tipo de dato correspondiente.

En nuestro caso ya tenemos preparado una respuesta genérica para informar a quien nos consume, sobre el problema que esta sucediendo.  

Nuestra clase que arma el response para informar del error es la siguiente

 

 

package com.spring.remote.exceptions;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ElementKind;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.tags.form.ErrorsTag;
import com.spring.remote.model.ApiError;
@ControllerAdvice
public class RestException extends ExceptionHandlerExceptionResolver{
//cuando el tipo HTTP no es correcto
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public ResponseEntity<ApiError> methodNotSupportedException(HttpRequestMethodNotSupportedException ex){
ApiError error = new ApiError();
error.setMensaje("HTTP " + ex.getMethod() + " NO soportado. Debe ser " +ex.getSupportedMethods()[0] + " para el endpoint solicitado");
error.setDetalles(Arrays.asList(""+ex));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
//cuando el tipo de datos enviado no es aceptado
@ExceptionHandler({ HttpMediaTypeNotSupportedException.class })
public ResponseEntity<ApiError> notAcceptableMediaTypeHandler(HttpMediaTypeNotSupportedException ex) {
ApiError error = new ApiError();
error.setMensaje(ex.getMessage());
error.setDetalles(Arrays.asList(""+ex));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
//cuando no existe el header requerido
@ExceptionHandler({ MissingRequestHeaderException.class })
public ResponseEntity<ApiError> MissingRequestHeaderException(MissingRequestHeaderException ex) {
ApiError error = new ApiError();
error.setMensaje("No existe " + ex.getParameter().getParameterName() + " en el header");
error.setDetalles(Arrays.asList(""+ex));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
//cuando el body no es correcto
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ApiError> handleAllOtherErrors(HttpMessageNotReadableException ex) {
ApiError error = new ApiError();
error.setMensaje(ex.getMessage());
error.setDetalles(Arrays.asList(""+ex));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
//cuando @Valid tiene errores en validacion del body
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> MethodArgumentNotValidException(MethodArgumentNotValidException ex) {
ApiError error = new ApiError();
error.setMensaje(ex.getMessage());
error.setDetalles(Arrays.asList(""+ex));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
//cuando valores del body estan vacios o no existen
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiError> ConstraintViolationException(ConstraintViolationException ex) {
ApiError error = new ApiError();
error.setMensaje("Body no valido. Validar datos");
ex.getConstraintViolations().stream().forEach(v -> {
v.getPropertyPath().forEach(e ->{
if(e.getKind() == ElementKind.PROPERTY)
error.addDetalle(e.getName()+ " debe existir y no estar vacio");
});
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
//cuando el tipo de dato del header no coincide
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ApiError> MethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
ApiError error = new ApiError();
error.setMensaje("El header " + ex.getName() + " debe der de tipo " + ex.getRequiredType());
error.setDetalles(Arrays.asList(""+ex));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
//cuando el path no existe
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ApiError> noHandlerFoundException(NoHandlerFoundException ex) {
ApiError error = new ApiError();
error.setMensaje("Ruta " + ex.getRequestURL() + " no existe");
error.setDetalles(Arrays.asList(""+ex));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
//cuando hay un error SQL
@ExceptionHandler(SQLException.class)
public ResponseEntity<ApiError> exception(SQLException ex){
ApiError error = new ApiError();
error.setMensaje(ex.getMessage());
error.setDetalles(Arrays.asList(""+ex));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
}
El error provocado por el tipo de datos ya lo hace el API por nosotros, pero hay algunos que pasan desapercibido. Por ejemplo en este caso nos informo del error. 

 Pero que pasa si yo en el campo del Boolean active le pongo un numero (7)?, o en el caso del Integer age le pongo un decimal (34.6).

Vamos a ver como se comporta nuestro microservicio al cambiarle esos datos en el request del Postman.

Para el caso de la propiedad age para transparente

 

 Para el caso del Boolean le ponemos un número

 

 Están pasando transparente a nuestra validación.

Yo no puedo tener una edad decimal, o un boolean con un valor 3. 

Entonces vamos a validar esos casos. Lo haremos desde el modelo, que es donde parte el API para parsear a los tipos correspondientes.

Validamos el atributo age, que no reciba decimales

El metodo setAge quedaría así

 

 

 Ya no se nos esta escapando.

Ahora validaremos el Boolean active usando la misma estrategia del Integer

Nuestro setActive quedaría así

 

 Y ahora si, nos valida 

 

 Si queremos validar también el String  name, que no le pasen una cadena "true" o "false", podemos validarlo en el model.

 Esto va a depender de las políticas de la empresa.

Para el caso de los headers lo podemos hacer usando la misma estrategia como lo hicimos con el body.

Esto sería todo de mi parte. Espero les sea de utilidad.

Cualquier duda pueden dejar su comentario.

Les dejo el link de descarga del proyecto completo.


Descargar


Comentarios

  1. Best Casinos in San Francisco, CA - Mapyro
    The cheapest way to get from 영천 출장샵 San Francisco to Alton City Casino 포항 출장샵 & Hotel costs 의정부 출장마사지 only $3, 전라남도 출장안마 and the quickest way takes 안동 출장마사지 just 4 mins. Find the travel option that

    ResponderBorrar

Publicar un comentario