Spring Boot 3 - Buenas prácticas y protección: Aula 2
This commit is contained in:
parent
bb4bfe3772
commit
e9fbc012c2
@ -2,8 +2,8 @@ package med.voll.api.controller;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
import med.voll.api.medico.*;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.medico.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@ -50,7 +50,7 @@ public class MedicoController {
|
||||
@PutMapping
|
||||
@Transactional
|
||||
public ResponseEntity<DatosRespuestaMedico> actualizarMedico(
|
||||
@RequestBody @Valid DatosActualizarMedico datosActualizarMedico) {
|
||||
@RequestBody @Valid DatosActualizarMedico datosActualizarMedico) {
|
||||
Medico medico = medicoRepository.getReferenceById(datosActualizarMedico.id());
|
||||
medico.actualizarDatos(datosActualizarMedico);
|
||||
return ResponseEntity.ok(new DatosRespuestaMedico(
|
||||
|
@ -2,8 +2,8 @@ package med.voll.api.controller;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
import med.voll.api.paciente.*;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.paciente.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@ -25,8 +25,8 @@ public class PacienteController {
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<DatosRespuestaPaciente> registrarPaciente(
|
||||
@RequestBody @Valid DatosRegistroPaciente datosRegistroPaciente,
|
||||
UriComponentsBuilder uriComponentsBuilder) {
|
||||
@RequestBody @Valid DatosRegistroPaciente datosRegistroPaciente,
|
||||
UriComponentsBuilder uriComponentsBuilder) {
|
||||
Paciente paciente = pacienteRepository.save(new Paciente(datosRegistroPaciente));
|
||||
DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
|
||||
paciente.getId(), paciente.getNombre(), paciente.getEmail(), paciente.getTelefono(), paciente.getDocumento(),
|
||||
|
@ -1,4 +1,4 @@
|
||||
package med.voll.api.direccion;
|
||||
package med.voll.api.domain.direccion;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package med.voll.api.direccion;
|
||||
package med.voll.api.domain.direccion;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
import lombok.AllArgsConstructor;
|
@ -1,7 +1,7 @@
|
||||
package med.voll.api.medico;
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosActualizarMedico(@NotNull Long id, String nombre, String documento, DatosDireccion direccion) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package med.voll.api.medico;
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
public record DatosListadoMedicos(
|
||||
Long id,
|
@ -1,11 +1,11 @@
|
||||
package med.voll.api.medico;
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRegistroMedico(
|
||||
@NotBlank String nombre,
|
@ -1,7 +1,7 @@
|
||||
package med.voll.api.medico;
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRespuestaMedico(@NotNull Long id, String nombre,
|
||||
String email, String telefono, String documento,
|
@ -1,4 +1,4 @@
|
||||
package med.voll.api.medico;
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
public enum Especialidad {
|
||||
ORTOPEDIA,
|
@ -1,11 +1,11 @@
|
||||
package med.voll.api.medico;
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import med.voll.api.direccion.Direccion;
|
||||
import med.voll.api.domain.direccion.Direccion;
|
||||
|
||||
@Table(name="medicos")
|
||||
@Entity(name="Medico")
|
@ -1,4 +1,4 @@
|
||||
package med.voll.api.medico;
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
@ -1,7 +1,7 @@
|
||||
package med.voll.api.paciente;
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosActualizarPaciente(@NotNull Long id, String nombre, String documento, DatosDireccion direccion) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package med.voll.api.paciente;
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
public record DatosListadoPacientes(Long id, String nombre, String documento, String email) {
|
||||
|
@ -1,11 +1,11 @@
|
||||
package med.voll.api.paciente;
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRegistroPaciente(
|
||||
@NotBlank String nombre,
|
@ -1,7 +1,7 @@
|
||||
package med.voll.api.paciente;
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRespuestaPaciente(@NotNull Long id, String nombre,
|
||||
String email, String telefono, String documento,
|
@ -1,12 +1,11 @@
|
||||
package med.voll.api.paciente;
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import med.voll.api.direccion.Direccion;
|
||||
import med.voll.api.medico.DatosActualizarMedico;
|
||||
import med.voll.api.domain.direccion.Direccion;
|
||||
|
||||
@Table(name="pacientes")
|
||||
@Entity(name="Paciente")
|
@ -1,4 +1,4 @@
|
||||
package med.voll.api.paciente;
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
@ -0,0 +1,29 @@
|
||||
package med.voll.api.infra;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ManejadorDeErrores {
|
||||
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
public ResponseEntity manejarError404(){
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity manejarError400(MethodArgumentNotValidException e){
|
||||
var errores = e.getFieldErrors().stream().map(DatosErrorValidacion::new).toList();
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
|
||||
private record DatosErrorValidacion(String campo, String error) {
|
||||
public DatosErrorValidacion(FieldError error) {
|
||||
this(error.getField(), error.getDefaultMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -5,3 +5,5 @@ spring.datasource.password=alura
|
||||
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
server.error.include-stacktrace=never
|
@ -165,3 +165,142 @@ ejm consulta por código `405 Method Not Allowed`
|
||||
https://http.cat/405
|
||||
```
|
||||
|
||||
## Manejando errores
|
||||
|
||||
- Spring boot common
|
||||
[properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html)
|
||||
|
||||
- Spring boot server
|
||||
[properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server)
|
||||
|
||||
#### Ocultando el stacktrace
|
||||
|
||||
[application.properties](./api_rest/api2/src/main/resources/application.properties)
|
||||
|
||||
```conf
|
||||
server.error.include-stacktrace=never
|
||||
```
|
||||
|
||||
Nuevo package [infra](./api_rest/api2/src/main/java/med/voll/api/infra) con nueva
|
||||
clase
|
||||
[ManejadorDeErrores](./api_rest/api2/src/main/java/med/voll/api/infra/ManejadorDeErrores.java)
|
||||
|
||||
```java
|
||||
...
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ManejadorDeErrores {
|
||||
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
public ResponseEntity manejarError404(){
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity manejarError400(MethodArgumentNotValidException e){
|
||||
var errores = e.getFieldErrors().stream().map(DatosErrorValidacion::new).toList();
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
|
||||
private record DatosErrorValidacion(String campo, String error) {
|
||||
public DatosErrorValidacion(FieldError error) {
|
||||
this(error.getField(), error.getDefaultMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Por defecto, **Bean Validation** devuelve mensajes de error en inglés, sin
|
||||
embargo, hay una traducción de estos mensajes al español ya implementada en
|
||||
esta especificación.
|
||||
|
||||
En el protocolo HTTP hay un encabezado llamado `Accept-Language`, que sirve para
|
||||
indicar al servidor el idioma preferido del cliente que activa la solicitud.
|
||||
Podemos utilizar esta cabecera para indicarle a Spring el idioma deseado, para
|
||||
que en la integración con Bean Validation pueda buscar mensajes según el idioma
|
||||
indicado.
|
||||
|
||||
En Insomnia, y también en otras herramientas similares, existe una opción
|
||||
llamada Header en la que podemos incluir cabeceras a enviar en la petición.
|
||||
Si agregamos el encabezado `Accept-Language` con el valor `es`, los mensajes de
|
||||
error de **Bean Validation** se devolverán automáticamente en español.
|
||||
|
||||
> Nota: Bean Validation solo traduce los mensajes de error a unos pocos idiomas.
|
||||
|
||||
### Personalización de mensajes de error
|
||||
|
||||
**Bean Validation** tiene un mensaje de error para cada una de sus anotaciones.
|
||||
P.e. cuando la validación falla en algún atributo anotado con `@NotBlank`, el
|
||||
mensaje de error será: `must not be blank`.
|
||||
|
||||
Estos mensajes de error no se definieron en la aplicación, ya que son mensajes
|
||||
de error estándar de Bean Validation. Sin embargo, si lo desea, puede
|
||||
personalizar dichos mensajes.
|
||||
|
||||
Una de las formas de personalizar los mensajes de error es agregar el atributo
|
||||
del mensaje a las anotaciones de validación:
|
||||
|
||||
```java
|
||||
public record DatosCadastroMedico(
|
||||
@NotBlank(message = "Nombre es obligatorio")
|
||||
String nombre,
|
||||
|
||||
@NotBlank(message = "Email es obligatorio")
|
||||
@Email(message = "Formato de email es inválido")
|
||||
String email,
|
||||
|
||||
@NotBlank(message = "Teléfono es obligatorio")
|
||||
String telefono,
|
||||
|
||||
@NotBlank(message = "CRM es obligatorio")
|
||||
@Pattern(regexp = "\\d{4,6}", message = "Formato do CRM es inválido")
|
||||
String crm,
|
||||
|
||||
@NotNull(message = "Especialidad es obligatorio")
|
||||
Especialidad especialidad,
|
||||
|
||||
@NotNull(message = "Datos de dirección son obligatorios")
|
||||
@Valid DatosDireccion direccion) {}
|
||||
```
|
||||
|
||||
Otra forma es aislar los mensajes en un archivo de propiedades, que debe tener
|
||||
el nombre `ValidationMessages.properties` y estar creado en el directorio
|
||||
`src/main/resources`:
|
||||
|
||||
```config
|
||||
nombre.obligatorio=El nombre es obligatorio
|
||||
email.obligatorio=Correo electrónico requerido
|
||||
email.invalido=El formato del correo electrónico no es válido
|
||||
phone.obligatorio=Teléfono requerido
|
||||
crm.obligatorio=CRM es obligatorio
|
||||
crm.invalido=El formato CRM no es válido
|
||||
especialidad.obligatorio=La especialidad es obligatoria
|
||||
address.obligatorio=Los datos de dirección son obligatorios
|
||||
```
|
||||
|
||||
Y, en las anotaciones, indicar la clave de las propiedades por el propio atributo
|
||||
message, delimitando con los caracteres `{` y `}`:
|
||||
|
||||
```java
|
||||
public record DatosRegistroMedico(
|
||||
@NotBlank(message = "{nombre.obligatorio}")
|
||||
String nombre,
|
||||
|
||||
@NotBlank(message = "{email.obligatorio}")
|
||||
@Email(message = "{email.invalido}")
|
||||
String email,
|
||||
|
||||
@NotBlank(message = "{telefono.obligatorio}")
|
||||
String telefono,
|
||||
|
||||
@NotBlank(message = "{crm.obligatorio}")
|
||||
@Pattern(regexp = "\\d{4,6}", message = "{crm.invalido}")
|
||||
String crm,
|
||||
|
||||
@NotNull(message = "{especialidad.obligatorio}")
|
||||
Especialidad especialidad,
|
||||
|
||||
@NotNull(message = "{direccion.obligatorio}")
|
||||
@Valid DatosDireccion direccion) {}
|
||||
```
|
||||
|
||||
|
@ -51,3 +51,4 @@ primoridiales en programación con Javascript
|
||||
- [Persistencia con JPA - Hibernate](./010_spring_boot/jpa_persistencia_hibernate.md)
|
||||
- [JPA consultas avanzadas, rendimiento y modelos complejos](./010_spring_boot/jpa_avanzado.md)
|
||||
- [Desarrollo API Rest](./010_spring_boot/spring_boot_1.md)
|
||||
- [Buenas prácticas y protección de API Rest](./010_spring_boot/spring_boot_2.md)
|
||||
|
Loading…
Reference in New Issue
Block a user