Skip to content

Commit

Permalink
Merge pull request #2 from Mercateo/add-ConstrainViolationExceptionMa…
Browse files Browse the repository at this point in the history
…pper

added ConstrainViolationExceptionMapper
  • Loading branch information
ibrahim-alzant authored Jan 19, 2018
2 parents 05952bb + 393fa58 commit 847e7af
Show file tree
Hide file tree
Showing 15 changed files with 670 additions and 2 deletions.
26 changes: 26 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,26 @@
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20170516</version>
</dependency>
<dependency>
<artifactId>jersey-test-framework-provider-inmemory</artifactId>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
Expand Down Expand Up @@ -231,6 +251,12 @@
<artifactId>common.rest.schemagen</artifactId>
<version>0.18.9</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.mercateo.rest.jersey.utils.exception;

import lombok.extern.slf4j.Slf4j;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static javax.ws.rs.core.Response.Status.BAD_REQUEST;

@Slf4j
@Provider
public class ConstrainViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException>{

@Override
public Response toResponse(ConstraintViolationException violationException) {
List<ValidationError> errors = toValidationErrors(violationException);

log.error("Sending error response to client {}", errors
.stream()
.map(ValidationError::toString)
.collect(Collectors.joining(",")));

return Response
.status(BAD_REQUEST)
.entity(new ValidationExceptionJson("Invalid",
BAD_REQUEST.getStatusCode(),
"The request body is syntactically correct, but is not accepted, because of its data.",
errors))
.type("application/problem+json")
.build();
}

private List<ValidationError> toValidationErrors(ConstraintViolationException violationException){
return violationException
.getConstraintViolations()
.stream() //
.map(ValidationError::of)
.collect(Collectors.toList());

}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.mercateo.rest.jersey.utils.exception;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.Value;

@Value
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class SimpleExceptionJson {
@NonNull
String title;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.mercateo.rest.jersey.utils.exception;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.mercateo.rest.jersey.utils.validation.EnumValue;
import com.mercateo.rest.jersey.utils.validation.NullOrNotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import javax.validation.ConstraintViolation;
import javax.validation.ElementKind;
import javax.validation.Path;
import javax.validation.constraints.*;
import java.lang.annotation.Annotation;
import java.util.Collection;

@Getter
@AllArgsConstructor
@RequiredArgsConstructor
public class ValidationError {

@NonNull
ValidationErrorCode code;

@NonNull
String path;

@JsonInclude(JsonInclude.Include.NON_NULL)
Integer limit;

public static ValidationError of(ConstraintViolation<?> constraintViolation){
Annotation annotation = constraintViolation.getConstraintDescriptor().getAnnotation();
String path = constructJsonPath(constraintViolation.getPropertyPath());

ValidationError error = new ValidationError(ValidationErrorCode.UNKNOWN, path);

if(annotation instanceof NotNull || annotation instanceof NotBlank || annotation instanceof AssertTrue){
return new ValidationError(ValidationErrorCode.REQUIRED, path);
} else if(annotation instanceof Size){
final Size sizeAnnotation = (Size) annotation;
final Object value = constraintViolation.getInvalidValue();

if (value instanceof String && value.toString().length() < sizeAnnotation.min()) {
error = new ValidationError(ValidationErrorCode.MINLENGTH, path, sizeAnnotation.min());
} else if (value instanceof String && value.toString().length() > sizeAnnotation.max()) {
error = new ValidationError(ValidationErrorCode.MAXLENGTH, path, sizeAnnotation.max());
} else if (value instanceof Collection && ((Collection<?>) value).size() < sizeAnnotation.min()) {
error = new ValidationError(ValidationErrorCode.MINITEMS, path, sizeAnnotation.min());
} else if (value instanceof Collection && ((Collection<?>) value).size() > sizeAnnotation.max()) {
error = new ValidationError(ValidationErrorCode.MAXITEMS, path, sizeAnnotation.max());
}
} else if(annotation instanceof EnumValue){
error = new ValidationError(ValidationErrorCode.ENUM, path);
} else if(annotation instanceof NullOrNotBlank){
error = new ValidationError(ValidationErrorCode.INVALID, path);
} else if (annotation instanceof Min) {
final Min min = (Min) annotation;
error = new ValidationError(ValidationErrorCode.MINLENGTH, path, ((int) min.value()));
} else if (annotation instanceof Max) {
Max max = (Max) annotation;
error = new ValidationError(ValidationErrorCode.MAXLENGTH, path, ((int) max.value()));
} else if (annotation instanceof Email) {
error = new ValidationError(ValidationErrorCode.INVALID_EMAIL, path);
}
return error;
}

private static String constructJsonPath(Path path) {
StringBuilder jsonPath = new StringBuilder("#");
path.forEach(pathComponent -> {
if (pathComponent.getKind() == ElementKind.PROPERTY) {
jsonPath.append("/").append(pathComponent.getName());
}
});
return jsonPath.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mercateo.rest.jersey.utils.exception;

public enum ValidationErrorCode {

REQUIRED,
PATTERN,
TYPE,
MINLENGTH,
MAXLENGTH,
MINIMUM,
MAXIMUM,
INVALID,
MINITEMS,
MAXITEMS,
ENUM,
INVALID_EMAIL,
UNKNOWN

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.mercateo.rest.jersey.utils.exception;

import lombok.Getter;

import java.util.List;

@Getter
public class ValidationExceptionJson extends SimpleExceptionJson {

List<ValidationError> errors;

public ValidationExceptionJson(String title, int status, String detail,List<ValidationError> errors) {
super(title, status, detail);
this.errors = errors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.mercateo.rest.jersey.utils.validation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValueValidator.class)
public @interface EnumValue {

Class<?> targetEnum();

Class<?>[] groups() default {};

String message() default "INVALID_VALUE";

Class<? extends Payload>[] payload() default {};


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.mercateo.rest.jersey.utils.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class EnumValueValidator implements ConstraintValidator<EnumValue, CharSequence> {

private Class targetEnum;

@Override
public void initialize(EnumValue enumValue) {
this.targetEnum = enumValue.targetEnum();
}

@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext constraintValidatorContext) {
List<Object> values = Arrays.stream(targetEnum.getEnumConstants())
.map(Object::toString)
.collect(Collectors.toList());

return values.contains(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.mercateo.rest.jersey.utils.validation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target( {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = NullOrNotBlankValidator.class)
public @interface NullOrNotBlank {
String message() default "INVALID_VALUE";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mercateo.rest.jersey.utils.validation;

import lombok.extern.slf4j.Slf4j;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

@Slf4j
public class NullOrNotBlankValidator implements ConstraintValidator<NullOrNotBlank, String> {

public void initialize(NullOrNotBlank parameters) { }

public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return (value == null) ? true : (hasValidLength(value)) ? true : false;
}

private boolean hasValidLength(String value){
return (value.trim().length() > 0);
}
}
Loading

0 comments on commit 847e7af

Please sign in to comment.