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

Implements the use of ProblemDetail for error responses. #174

Closed
Closed
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
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.springframework.samples.petclinic.mapper;

import org.mapstruct.Mapper;
import org.springframework.samples.petclinic.rest.dto.OwnerDto;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.springframework.samples.petclinic.model.Owner;
import org.springframework.samples.petclinic.rest.dto.OwnerDto;
import org.springframework.samples.petclinic.rest.dto.OwnerFieldsDto;

import java.util.Collection;
Expand All @@ -18,6 +20,7 @@ public interface OwnerMapper {

Owner toOwner(OwnerDto ownerDto);

@Mappings(value = {@Mapping(target = "id", ignore = true), @Mapping(target = "pets", ignore = true)})
Owner toOwner(OwnerFieldsDto ownerDto);

List<OwnerDto> toOwnerDtoCollection(Collection<Owner> ownerCollection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.PetType;
import org.springframework.samples.petclinic.rest.dto.PetDto;
import org.springframework.samples.petclinic.rest.dto.PetFieldsDto;
import org.springframework.samples.petclinic.rest.dto.PetTypeDto;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.PetType;

import java.util.Collection;

/**
* Map Pet & PetDto using mapstruct
*/
@Mapper
@Mapper(uses = VisitMapper.class)
public interface PetMapper {

@Mapping(source = "owner.id", target = "ownerId")
Expand All @@ -26,6 +27,7 @@ public interface PetMapper {
@Mapping(source = "ownerId", target = "owner.id")
Pet toPet(PetDto petDto);

@Mappings(value = {@Mapping(target = "id", ignore = true), @Mapping(target = "owner", ignore = true), @Mapping(target = "visits", ignore = true)})
Pet toPet(PetFieldsDto petFieldsDto);

PetTypeDto toPetTypeDto(PetType petType);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.springframework.samples.petclinic.mapper;

import org.mapstruct.Mapper;
import org.springframework.samples.petclinic.rest.dto.PetTypeDto;
import org.mapstruct.Mapping;
import org.springframework.samples.petclinic.model.PetType;
import org.springframework.samples.petclinic.rest.dto.PetTypeDto;
import org.springframework.samples.petclinic.rest.dto.PetTypeFieldsDto;

import java.util.Collection;
Expand All @@ -16,6 +17,7 @@ public interface PetTypeMapper {

PetType toPetType(PetTypeDto petTypeDto);

@Mapping(target = "id", ignore = true)
PetType toPetType(PetTypeFieldsDto petTypeFieldsDto);

PetTypeDto toPetTypeDto(PetType petType);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.springframework.samples.petclinic.mapper;

import org.mapstruct.Mapper;
import org.springframework.samples.petclinic.rest.dto.RoleDto;
import org.springframework.samples.petclinic.rest.dto.UserDto;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.springframework.samples.petclinic.model.Role;
import org.springframework.samples.petclinic.model.User;
import org.springframework.samples.petclinic.rest.dto.RoleDto;
import org.springframework.samples.petclinic.rest.dto.UserDto;

import java.util.Collection;

Expand All @@ -13,6 +15,8 @@
*/
@Mapper
public interface UserMapper {

@Mappings(value = {@Mapping(target = "id", ignore = true), @Mapping(target = "user", ignore = true)})
Role toRole(RoleDto roleDto);

RoleDto toRoleDto(Role role);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.springframework.samples.petclinic.mapper;

import org.mapstruct.Mapper;
import org.springframework.samples.petclinic.rest.dto.VetDto;
import org.mapstruct.Mapping;
import org.springframework.samples.petclinic.model.Vet;
import org.springframework.samples.petclinic.rest.dto.VetDto;
import org.springframework.samples.petclinic.rest.dto.VetFieldsDto;

import java.util.Collection;
Expand All @@ -14,6 +15,7 @@
public interface VetMapper {
Vet toVet(VetDto vetDto);

@Mapping(target = "id", ignore = true)
Vet toVet(VetFieldsDto vetFieldsDto);

VetDto toVetDto(Vet vet);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.springframework.samples.petclinic.rest.dto.VisitDto;
import org.mapstruct.Mappings;
import org.springframework.samples.petclinic.model.Visit;
import org.springframework.samples.petclinic.rest.dto.VisitDto;
import org.springframework.samples.petclinic.rest.dto.VisitFieldsDto;

import java.util.Collection;
Expand All @@ -16,6 +17,7 @@ public interface VisitMapper {
@Mapping(source = "petId", target = "pet.id")
Visit toVisit(VisitDto visitDto);

@Mappings(value = {@Mapping(target = "id", ignore = true), @Mapping(target = "pet", ignore = true)})
Visit toVisit(VisitFieldsDto visitFieldsDto);

@Mapping(source = "pet.id", target = "petId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,16 @@

package org.springframework.samples.petclinic.rest.advice;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.samples.petclinic.rest.controller.BindingErrorsResponse;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;

import static org.springframework.http.HttpStatus.BAD_REQUEST;

/**
* Global Exception handler for REST controllers.
Expand All @@ -46,29 +40,30 @@
public class ExceptionControllerAdvice {

/**
* Record for storing error information.
* <p>
* This record encapsulates the class name and message of the exception.
* Private method for constructing the {@link ProblemDetail} object passing the name and details of the exception class.
*
* @param className The name of the exception class
* @param exMessage The message of the exception
* @param ex Object referring to the thrown exception.
* @param status HTTP response status.
*/
private record ErrorInfo(String className, String exMessage) {
public ErrorInfo(Exception ex) {
this(ex.getClass().getName(), ex.getLocalizedMessage());
}
private ProblemDetail detailBuild(Exception ex, HttpStatus status) {
ProblemDetail detail = ProblemDetail.forStatus(status);
detail.setTitle(ex.getClass().getName());
detail.setDetail(ex.getLocalizedMessage());
return detail;
}

/**
* Handles all general exceptions by returning a 500 Internal Server Error status with error details.
*
* @param e The exception to be handled
* @param e The {@link Exception} to be handled
* @return A {@link ResponseEntity} containing the error information and a 500 Internal Server Error status
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorInfo> handleGeneralException(Exception e) {
ErrorInfo info = new ErrorInfo(e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(info);
@ResponseBody
public ResponseEntity<ProblemDetail> handleGeneralException(Exception e) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
ProblemDetail detail = this.detailBuild(e, status);
return ResponseEntity.status(status).body(detail);
}

/**
Expand All @@ -79,31 +74,32 @@ public ResponseEntity<ErrorInfo> handleGeneralException(Exception e) {
* @return A {@link ResponseEntity} containing the error information and a 404 Not Found status
*/
@ExceptionHandler(DataIntegrityViolationException.class)
@ResponseStatus(code = HttpStatus.NOT_FOUND)
@ResponseBody
public ResponseEntity<ErrorInfo> handleDataIntegrityViolationException(DataIntegrityViolationException ex) {
ErrorInfo errorInfo = new ErrorInfo(ex);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorInfo);
public ResponseEntity<ProblemDetail> handleDataIntegrityViolationException(DataIntegrityViolationException ex) {
HttpStatus status = HttpStatus.NOT_FOUND;
ProblemDetail detail = this.detailBuild(ex, status);
return ResponseEntity.status(status).body(detail);
}

/**
* Handles exception thrown by Bean Validation on controller methods parameters
*
* @param ex The thrown exception
* @param ex The {@link MethodArgumentNotValidException} to be handled
*
* @return an empty response entity
* @return A {@link ResponseEntity} containing the error information and a 400 Bad Request status.
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
public ResponseEntity<ErrorInfo> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
public ResponseEntity<ProblemDetail> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
HttpStatus status = HttpStatus.BAD_REQUEST;
BindingErrorsResponse errors = new BindingErrorsResponse();
BindingResult bindingResult = ex.getBindingResult();
if (bindingResult.hasErrors()) {
errors.addAllErrors(bindingResult);
return ResponseEntity.badRequest().body(new ErrorInfo("MethodArgumentNotValidException", "Validation failed"));
ProblemDetail detail = this.detailBuild(ex, status);
return ResponseEntity.status(status).body(detail);
}
return ResponseEntity.badRequest().build();
return ResponseEntity.status(status).build();
}

}