Skip to content

Commit

Permalink
Merge pull request dropwizard#471 from mveitas/validation-collections
Browse files Browse the repository at this point in the history
Add support for performing validation of individual elements of a collection
  • Loading branch information
nicktelford committed Feb 28, 2014
2 parents 8298a75 + 69011e4 commit f499ad2
Show file tree
Hide file tree
Showing 2 changed files with 299 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Set;
import java.util.*;

/**
* A Jersey provider which enables using Jackson to parse request entities into objects and generate
Expand Down Expand Up @@ -75,16 +74,41 @@ private Object validate(Annotation[] annotations, Object value) {
final Class<?>[] classes = findValidationGroups(annotations);

if (classes != null) {
final Set<ConstraintViolation<Object>> violations = validator.validate(value, classes);
if (!violations.isEmpty()) {
Set<ConstraintViolation<Object>> violations = null;

if(value instanceof Map) {
violations = validate(((Map)value).values(), classes);
} else if(value instanceof Iterable) {
violations = validate((Iterable)value, classes);
} else if(value.getClass().isArray()) {
violations = new HashSet<>();

Object[] values = (Object[]) value;
for(Object item : values) {
violations.addAll(validator.validate(item, classes));
}
} else {
violations = validator.validate(value, classes);
}

if (violations != null && !violations.isEmpty()) {
throw new ConstraintViolationException("The request entity had the following errors:",
ConstraintViolations.copyOf(violations));
ConstraintViolations.copyOf(violations));
}
}

return value;
}

private Set<ConstraintViolation<Object>> validate(Iterable values, Class<?>[] classes) {
Set<ConstraintViolation<Object>> violations = new HashSet<>();
for(Object value : values) {
violations.addAll(validator.validate(value, classes));
}

return violations;
}

private Class<?>[] findValidationGroups(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Valid.class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Objects;
import com.google.common.reflect.TypeToken;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.sun.jersey.core.util.StringKeyObjectValueIgnoreCaseMultivaluedMap;
import io.dropwizard.jackson.Jackson;
import io.dropwizard.validation.ConstraintViolations;
import io.dropwizard.validation.Validated;
import org.hibernate.validator.constraints.NotEmpty;
import org.junit.Before;
import org.junit.Test;

Expand All @@ -21,8 +24,10 @@
import javax.ws.rs.core.MediaType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Locale;
import java.lang.reflect.Type;
import java.util.*;

import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.failBecauseExceptionWasNotThrown;
Expand All @@ -43,6 +48,23 @@ public static class Example {
@Min(0)
@JsonProperty
int id;

@Override
public int hashCode() {
return id;
}

@Override
public boolean equals(Object obj) {
return Objects.equal(this.id, obj);
}
}

public static class ListExample {
@NotEmpty
@Valid
@JsonProperty
List<Example> examples;
}

public interface Partial1{}
Expand Down Expand Up @@ -304,4 +326,251 @@ public void throwsAConstraintViolationExceptionForEmptyRequestEntities() throws
new MultivaluedMapImpl(),
null);
}

@Test
public void returnsValidatedArrayRequestEntities() throws Exception {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity = new ByteArrayInputStream("[{\"id\":1}, {\"id\":2}]".getBytes());
final Class<?> klass = Example[].class;

final Object obj = provider.readFrom((Class<Object>) klass,
Example[].class,
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);

assertThat(obj)
.isInstanceOf(Example[].class);

assertThat(((Example[]) obj)[0].id)
.isEqualTo(1);
assertThat(((Example[]) obj)[1].id)
.isEqualTo(2);
}

@Test
public void returnsValidatedCollectionRequestEntities() throws Exception {
testValidatedCollectionType(Collection.class,
new TypeToken<Collection<Example>>() {}.getType());
}

@Test
public void returnsValidatedSetRequestEntities() throws Exception {
testValidatedCollectionType(Set.class,
new TypeToken<Set<Example>>() {}.getType());
}

@Test
public void returnsValidatedListRequestEntities() throws Exception {
testValidatedCollectionType(List.class,
new TypeToken<List<Example>>() {}.getType());
}

@Test
public void returnsValidatedMapRequestEntities() throws Exception {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity = new ByteArrayInputStream("{\"one\": {\"id\":1}, \"two\": {\"id\":2}}".getBytes());
final Class<?> klass = Map.class;

final Object obj = provider.readFrom((Class<Object>) klass,
new TypeToken<Map<Object, Example>>() {}.getType(),
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);

assertThat(obj)
.isInstanceOf(Map.class);

Map<Object, Example> map = (Map<Object, Example>) obj;
assertThat(map.get("one").id).isEqualTo(1);
assertThat(map.get("two").id).isEqualTo(2);
}

private void testValidatedCollectionType(Class<?> klass, Type type) throws IOException {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity = new ByteArrayInputStream("[{\"id\":1}, {\"id\":2}]".getBytes());

final Object obj = provider.readFrom((Class<Object>) klass,
type,
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);

assertThat(obj)
.isInstanceOf(klass);

Iterator<Example> iterator = ((Iterable<Example>)obj).iterator();
assertThat(iterator.next().id).isEqualTo(1);
assertThat(iterator.next().id).isEqualTo(2);
}

@Test
public void throwsAnInvalidEntityExceptionForInvalidCollectionRequestEntities() throws Exception {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity = new ByteArrayInputStream("[{\"id\":-1}, {\"id\":-2}]".getBytes());

try {
final Class<?> klass = Example.class;
provider.readFrom((Class<Object>) klass,
new TypeToken<Collection<Example>>() {}.getType(),
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);
failBecauseExceptionWasNotThrown(ConstraintViolationException.class);
} catch (ConstraintViolationException e) {
assertThat(ConstraintViolations.formatUntyped(e.getConstraintViolations()))
.contains("id must be greater than or equal to 0 (was -1)",
"id must be greater than or equal to 0 (was -2)");
}
}

@Test
public void throwsASingleInvalidEntityExceptionForInvalidCollectionRequestEntities() throws Exception {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity = new ByteArrayInputStream("[{\"id\":1}, {\"id\":-2}]".getBytes());

try {
final Class<?> klass = Example.class;
provider.readFrom((Class<Object>) klass,
new TypeToken<Collection<Example>>() {}.getType(),
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);
failBecauseExceptionWasNotThrown(ConstraintViolationException.class);
} catch (ConstraintViolationException e) {
assertThat(ConstraintViolations.formatUntyped(e.getConstraintViolations()))
.contains("id must be greater than or equal to 0 (was -2)");
}
}

@Test
public void throwsAnInvalidEntityExceptionForInvalidSetRequestEntities() throws Exception {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity = new ByteArrayInputStream("[{\"id\":-1}, {\"id\":-2}]".getBytes());

try {
final Class<?> klass = Example.class;
provider.readFrom((Class<Object>) klass,
new TypeToken<Set<Example>>() {}.getType(),
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);
failBecauseExceptionWasNotThrown(ConstraintViolationException.class);
} catch (ConstraintViolationException e) {
assertThat(ConstraintViolations.formatUntyped(e.getConstraintViolations()))
.contains("id must be greater than or equal to 0 (was -1)",
"id must be greater than or equal to 0 (was -2)");
}
}

@Test
public void throwsAnInvalidEntityExceptionForInvalidListRequestEntities() throws Exception {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity = new ByteArrayInputStream("[{\"id\":-1}, {\"id\":-2}]".getBytes());

try {
final Class<?> klass = Example.class;
provider.readFrom((Class<Object>) klass,
new TypeToken<List<Example>>() {}.getType(),
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);
failBecauseExceptionWasNotThrown(ConstraintViolationException.class);
} catch (ConstraintViolationException e) {
assertThat(ConstraintViolations.formatUntyped(e.getConstraintViolations()))
.containsOnly("id must be greater than or equal to 0 (was -1)",
"id must be greater than or equal to 0 (was -2)");
}
}

@Test
public void throwsAnInvalidEntityExceptionForInvalidMapRequestEntities() throws Exception {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity = new ByteArrayInputStream("{\"one\": {\"id\":-1}, \"two\": {\"id\":-2}}".getBytes());

try {
final Class<?> klass = Example.class;
provider.readFrom((Class<Object>) klass,
new TypeToken<Map<Object, Example>>() {}.getType(),
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);
failBecauseExceptionWasNotThrown(ConstraintViolationException.class);
} catch (ConstraintViolationException e) {
assertThat(ConstraintViolations.formatUntyped(e.getConstraintViolations()))
.contains("id must be greater than or equal to 0 (was -1)",
"id must be greater than or equal to 0 (was -2)");
}
}

@Test
public void returnsValidatedEmbeddedListRequestEntities() throws IOException {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity =
new ByteArrayInputStream("[ {\"examples\": [ {\"id\":1 } ] } ]".getBytes());
Class<?> klass = List.class;

final Object obj = provider.readFrom((Class<Object>) klass,
new TypeToken<List<ListExample>>() {}.getType(),
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);

assertThat(obj)
.isInstanceOf(klass);

Iterator<ListExample> iterator = ((Iterable<ListExample>)obj).iterator();
assertThat(iterator.next().examples.get(0).id).isEqualTo(1);
}

@Test
public void throwsAnInvalidEntityExceptionForInvalidEmbeddedListRequestEntities() throws Exception {
final Annotation valid = mock(Annotation.class);
doReturn(Valid.class).when(valid).annotationType();

final ByteArrayInputStream entity =
new ByteArrayInputStream("[ {\"examples\": [ {\"id\":1 } ] }, { } ]".getBytes());

try {
final Class<?> klass = List.class;
provider.readFrom((Class<Object>) klass,
new TypeToken<List<ListExample>>() {}.getType(),
new Annotation[]{ valid },
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedMapImpl(),
entity);
failBecauseExceptionWasNotThrown(ConstraintViolationException.class);
} catch (ConstraintViolationException e) {
assertThat(ConstraintViolations.formatUntyped(e.getConstraintViolations()))
.containsOnly("examples may not be empty (was null)");
}
}

}

0 comments on commit f499ad2

Please sign in to comment.