Add support for custom violation exceptions

This commit is contained in:
René Pfeuffer
2018-11-20 11:04:30 +01:00
parent 51c9a4dbb2
commit b9458f47e9
6 changed files with 166 additions and 6 deletions

View File

@@ -0,0 +1,76 @@
package sonia.scm;
import java.util.ArrayList;
import java.util.Collection;
import static java.util.Collections.unmodifiableCollection;
public class ScmConstraintViolationException extends RuntimeException {
private final Collection<ScmConstraintViolation> violations;
private final String furtherInformations;
private ScmConstraintViolationException(Collection<ScmConstraintViolation> violations, String furtherInformations) {
this.violations = violations;
this.furtherInformations = furtherInformations;
}
public Collection<ScmConstraintViolation> getViolations() {
return unmodifiableCollection(violations);
}
public String getUrl() {
return furtherInformations;
}
public static class Builder {
private final Collection<ScmConstraintViolation> violations = new ArrayList<>();
private String furtherInformations;
public static Builder doThrow() {
Builder builder = new Builder();
return builder;
}
public Builder andThrow() {
this.violations.clear();
this.furtherInformations = null;
return this;
}
public Builder violation(String message, String... pathElements) {
this.violations.add(new ScmConstraintViolation(message, pathElements));
return this;
}
public Builder withFurtherInformations(String furtherInformations) {
this.furtherInformations = furtherInformations;
return this;
}
public void when(boolean condition) {
if (condition && !this.violations.isEmpty()) {
throw new ScmConstraintViolationException(violations, furtherInformations);
}
}
}
public static class ScmConstraintViolation {
private final String message;
private final String path;
private ScmConstraintViolation(String message, String... pathElements) {
this.message = message;
this.path = String.join(".", pathElements);
}
public String getMessage() {
return message;
}
public String getPropertyPath() {
return path;
}
}
}

View File

@@ -1,7 +1,7 @@
package sonia.scm.api.v2;
import org.jboss.resteasy.api.validation.ResteasyViolationException;
import sonia.scm.api.v2.resources.ViolationExceptionToErrorDtoMapper;
import sonia.scm.api.v2.resources.ResteasyViolationExceptionToErrorDtoMapper;
import javax.inject.Inject;
import javax.ws.rs.core.MediaType;
@@ -10,12 +10,12 @@ import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ResteasyViolationException> {
public class ResteasyValidationExceptionMapper implements ExceptionMapper<ResteasyViolationException> {
private final ViolationExceptionToErrorDtoMapper mapper;
private final ResteasyViolationExceptionToErrorDtoMapper mapper;
@Inject
public ValidationExceptionMapper(ViolationExceptionToErrorDtoMapper mapper) {
public ResteasyValidationExceptionMapper(ResteasyViolationExceptionToErrorDtoMapper mapper) {
this.mapper = mapper;
}

View File

@@ -0,0 +1,30 @@
package sonia.scm.api.v2;
import sonia.scm.ScmConstraintViolationException;
import sonia.scm.api.v2.resources.ScmViolationExceptionToErrorDtoMapper;
import javax.inject.Inject;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class ScmConstraintValidationExceptionMapper implements ExceptionMapper<ScmConstraintViolationException> {
private final ScmViolationExceptionToErrorDtoMapper mapper;
@Inject
public ScmConstraintValidationExceptionMapper(ScmViolationExceptionToErrorDtoMapper mapper) {
this.mapper = mapper;
}
@Override
public Response toResponse(ScmConstraintViolationException exception) {
return Response
.status(Response.Status.BAD_REQUEST)
.type(MediaType.APPLICATION_JSON_TYPE)
.entity(mapper.map(exception))
.build();
}
}

View File

@@ -39,7 +39,8 @@ public class MapperModule extends AbstractModule {
bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapper(ReducedObjectModelToDtoMapper.class).getClass());
bind(ViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ViolationExceptionToErrorDtoMapper.class).getClass());
bind(ResteasyViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ResteasyViolationExceptionToErrorDtoMapper.class).getClass());
bind(ScmViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ScmViolationExceptionToErrorDtoMapper.class).getClass());
bind(ExceptionWithContextToErrorDtoMapper.class).to(Mappers.getMapper(ExceptionWithContextToErrorDtoMapper.class).getClass());
// no mapstruct required

View File

@@ -12,7 +12,7 @@ import java.util.List;
import java.util.stream.Collectors;
@Mapper
public abstract class ViolationExceptionToErrorDtoMapper {
public abstract class ResteasyViolationExceptionToErrorDtoMapper {
@Mapping(target = "errorCode", ignore = true)
@Mapping(target = "transactionId", ignore = true)

View File

@@ -0,0 +1,53 @@
package sonia.scm.api.v2.resources;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.slf4j.MDC;
import sonia.scm.ScmConstraintViolationException;
import sonia.scm.ScmConstraintViolationException.ScmConstraintViolation;
import java.util.List;
import java.util.stream.Collectors;
@Mapper
public abstract class ScmViolationExceptionToErrorDtoMapper {
@Mapping(target = "errorCode", ignore = true)
@Mapping(target = "transactionId", ignore = true)
@Mapping(target = "context", ignore = true)
public abstract ErrorDto map(ScmConstraintViolationException exception);
@AfterMapping
void setTransactionId(@MappingTarget ErrorDto dto) {
dto.setTransactionId(MDC.get("transaction_id"));
}
@AfterMapping
void mapViolations(ScmConstraintViolationException exception, @MappingTarget ErrorDto dto) {
List<ErrorDto.ConstraintViolationDto> violations =
exception.getViolations()
.stream()
.map(this::createViolationDto)
.collect(Collectors.toList());
dto.setViolations(violations);
}
private ErrorDto.ConstraintViolationDto createViolationDto(ScmConstraintViolation violation) {
ErrorDto.ConstraintViolationDto constraintViolationDto = new ErrorDto.ConstraintViolationDto();
constraintViolationDto.setMessage(violation.getMessage());
constraintViolationDto.setPath(violation.getPropertyPath());
return constraintViolationDto;
}
@AfterMapping
void setErrorCode(@MappingTarget ErrorDto dto) {
dto.setErrorCode("3zR9vPNIE1");
}
@AfterMapping
void setMessage(@MappingTarget ErrorDto dto) {
dto.setMessage("input violates conditions (see violation list)");
}
}