Skip to content

Commit

Permalink
Add Int validation annotation
Browse files Browse the repository at this point in the history
Closes #506
  • Loading branch information
sleberknight committed Feb 18, 2021
1 parent 620de63 commit 93f47e1
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 0 deletions.
38 changes: 38 additions & 0 deletions src/main/java/org/kiwiproject/validation/Int.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.kiwiproject.validation;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* The annotated element must have a value that can be converted to a Java int or {@link Integer}.
*/
@Documented
@Constraint(validatedBy = IntValidator.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Int {

String message() default "{org.kiwiproject.validation.Int.message}";

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

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

/**
* Whether to consider null as valid. The default is false.
*
* @return true to consider null as valid
*/
boolean allowNull() default false;
}
30 changes: 30 additions & 0 deletions src/main/java/org/kiwiproject/validation/IntValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.kiwiproject.validation;

import static java.util.Objects.isNull;

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

public class IntValidator implements ConstraintValidator<Int, CharSequence> {

private Int anInt;

@Override
public void initialize(Int constraintAnnotation) {
this.anInt = constraintAnnotation;
}

@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if (isNull(value)) {
return anInt.allowNull();
}

try {
Integer.parseInt(value.toString());
return true;
} catch (NumberFormatException e) {
return false;
}
}
}
1 change: 1 addition & 0 deletions src/main/resources/ValidationMessages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ org.kiwiproject.validation.FieldRange.maxOnly.message=must be below or equal to
org.kiwiproject.validation.FieldRange.minOnly.message=must be equal to or above ${minLabel.length() > 0 ? minLabel : min}
org.kiwiproject.validation.FilePath.message=is not a valid file path
org.kiwiproject.validation.InEnum.message=is not in the list
org.kiwiproject.validation.Int.message=must be convertible to an integer
org.kiwiproject.validation.Ipv4Address.message=is not a valid IPv4 address
org.kiwiproject.validation.Ipv4AndPort.message=is not a valid ipv4:port, e.g. 192.168.1.150:8888
org.kiwiproject.validation.Required.message=is required
Expand Down
91 changes: 91 additions & 0 deletions src/test/java/org/kiwiproject/validation/IntValidatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.kiwiproject.validation;

import static org.kiwiproject.validation.ValidationTestHelper.assertNoPropertyViolations;
import static org.kiwiproject.validation.ValidationTestHelper.assertOnePropertyViolation;
import static org.kiwiproject.validation.ValidationTestHelper.assertPropertyViolations;

import lombok.Value;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@DisplayName("IntValidator")
class IntValidatorTest {

private static final long ONE_BELOW_MAX_INTEGER = Integer.MIN_VALUE - 1L;
private static final long ONE_ABOVE_MAX_INTEGER = Integer.MAX_VALUE + 1L;

@Nested
class ShouldBeValid {

@Test
void whenAllowingNull_AndValueIsNull() {
var object = new SampleAllowNullObject(null);
assertValueIsValid(object);
}

@ParameterizedTest
@ValueSource(ints = {Integer.MIN_VALUE, -84, 0, 42, Integer.MAX_VALUE})
void whenValueIsAnInt(int value) {
var strValue = String.valueOf(value);
var object = new SampleObject(strValue);
assertValueIsValid(object);
}

@ParameterizedTest
@ValueSource(strings = {"042", "0042", "001", "+42", "-42", "+0042", "-0084"})
void whenValueContainingSignsAndLeadingZeroesIsAnInt(String value) {
var object = new SampleObject(value);
assertValueIsValid(object);
}

private void assertValueIsValid(Object object) {
assertNoPropertyViolations(object, "value");
}
}

@Nested
class ShouldNotBeValid {

@Test
void whenNotAllowingNull_AndValueIsNull() {
var object = new SampleObject(null);
assertValueIsNotValid(object);
assertPropertyViolations(object, "value", "must be convertible to an integer");
}

@ParameterizedTest
@ValueSource(strings = {"foo", "bar", "baz", "spam", "eggs", "ham", "0xCAFEBABE"})
void whenNotAnInt(String value) {
var object = new SampleObject(value);
assertValueIsNotValid(object);
}

@ParameterizedTest
@ValueSource(longs = {ONE_BELOW_MAX_INTEGER, ONE_ABOVE_MAX_INTEGER})
void whenOutsideIntRange(long value) {
var strValue = String.valueOf(value);
var object = new SampleObject(strValue);
assertValueIsNotValid(object);
}

private void assertValueIsNotValid(SampleObject object) {
assertOnePropertyViolation(object, "value");
}
}

@Value
private static class SampleObject {
@Int
String value;
}

@Value
private static class SampleAllowNullObject {
@Int(allowNull = true)
String value;
}

}
14 changes: 14 additions & 0 deletions src/test/java/org/kiwiproject/validation/ValidationTestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,18 @@ public static void assertViolations(Validator validator, Object object, String..
}
}

public static void assertOnePropertyViolation(Object object, String propertyName) {
assertOnePropertyViolation(DEFAULT_VALIDATOR, object, propertyName);
}

public static void assertOnePropertyViolation(Validator validator, Object object, String propertyName) {
assertPropertyViolations(validator, object, propertyName, 1);
}

public static void assertNoPropertyViolations(Object object, String propertyName) {
assertNoPropertyViolations(DEFAULT_VALIDATOR, object, propertyName);
}

public static void assertNoPropertyViolations(Validator validator, Object object, String propertyName) {
assertPropertyViolations(validator, object, propertyName, 0);
}
Expand All @@ -61,6 +69,12 @@ public static void assertPropertyViolations(Validator validator,
assertThat(violations).hasSize(numExpectedViolations);
}

public static void assertPropertyViolations(Object object,
String propertyName,
String... expectedMessages) {
assertPropertyViolations(DEFAULT_VALIDATOR, object, propertyName, expectedMessages);
}

public static void assertPropertyViolations(Validator validator,
Object object,
String propertyName,
Expand Down

0 comments on commit 93f47e1

Please sign in to comment.