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

ConstraintViolations context determines HTTP status #993

Merged
merged 1 commit into from May 1, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,19 +1,46 @@
package io.dropwizard.jersey.validation;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ElementKind;
import javax.validation.Path.Node;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import java.util.Set;

@Provider
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
private static final int UNPROCESSABLE_ENTITY = 422;

@Override
public Response toResponse(ConstraintViolationException exception) {
final ValidationErrorMessage message = new ValidationErrorMessage(exception.getConstraintViolations());
int status = 422;

// Detect where the constraint validation occurred so we can return an appropriate status
// code. If the constraint failed with a *Param annotation, return a bad request. If it
// failed validating the return value, return internal error. Else return unprocessable
// entity.
Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
if (violations.size() > 0) {
ConstraintViolation<?> violation = violations.iterator().next();
boolean isReturnValue = false;
boolean isArgument = false;

return Response.status(UNPROCESSABLE_ENTITY)
// A return value can only occur at the last path, but a parameter
// can occur anywhere, such as a @BeanParam that has validations.
for (Node node : violation.getPropertyPath()) {
isArgument |= node.getKind().equals(ElementKind.PARAMETER);
isReturnValue = node.getKind().equals(ElementKind.RETURN_VALUE);
}

if (isReturnValue) {
status = 500;
} else if (isArgument) {
status = 400;
}
}

final ValidationErrorMessage message = new ValidationErrorMessage(exception.getConstraintViolations());
return Response.status(status)
.entity(message)
.build();
}
Expand Down
@@ -0,0 +1,15 @@
package io.dropwizard.jersey.validation;

import org.hibernate.validator.constraints.NotEmpty;

import javax.ws.rs.QueryParam;

public class BeanParameter {
@QueryParam("name")
@NotEmpty
private String name;

public String getName() {
return name;
}
}
Expand Up @@ -28,12 +28,36 @@ protected Application configure() {
}

@Test
public void returnsAnErrorMessage() throws Exception {
public void postInvalidEntityIs422() throws Exception {
assumeThat(Locale.getDefault().getLanguage(), is("en"));

final Response response = target("/valid/").request(MediaType.APPLICATION_JSON)
final Response response = target("/valid/foo").request(MediaType.APPLICATION_JSON)
.post(Entity.entity("{}", MediaType.APPLICATION_JSON));
assertThat(response.getStatus()).isEqualTo(422);
assertThat(response.readEntity(String.class)).isEqualTo("{\"errors\":[\"name may not be empty (was null)\"]}");
}

@Test
public void getInvalidReturnIs500() throws Exception {
// return value is too long and so will fail validation
final Response response = target("/valid/bar")
.queryParam("name", "dropwizard").request().get();
assertThat(response.getStatus()).isEqualTo(500);
}

@Test
public void getInvalidQueryParamsIs400() throws Exception {
// query parameter is too short and so will fail validation
final Response response = target("/valid/bar")
.queryParam("name", "hi").request().get();
assertThat(response.getStatus()).isEqualTo(400);
}

@Test
public void getInvalidBeanParamsIs400() throws Exception {
// bean parameter is too short and so will fail validation
final Response response = target("/valid/zoo")
.request().get();
assertThat(response.getStatus()).isEqualTo(400);
}
}
@@ -1,11 +1,11 @@
package io.dropwizard.jersey.validation;

import io.dropwizard.validation.Validated;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.io.IOException;

Expand All @@ -14,7 +14,21 @@
@Consumes(MediaType.APPLICATION_JSON)
public class ValidatingResource {
@POST
@Path("foo")
public String blah(@Validated ValidRepresentation representation) throws IOException {
return representation.getName();
}

@GET
@Path("bar")
@Length(max = 3)
public String blaze(@QueryParam("name") @Length(min = 3) String name) {
return name;
}

@GET
@Path("zoo")
public String blazer(@Valid @BeanParam BeanParameter params) {
return params.getName();
}
}