Skip to content

Commit

Permalink
Adds validator for java util date (#6481)
Browse files Browse the repository at this point in the history
* Adds validator for java util date

Signed-off-by: ashishku <ashishku@thoughtworks.com>

* Fixes import statements

Signed-off-by: ashishku <ashishku@thoughtworks.com>

* Adds tests for pastOrPresent and FutureOrPresent
  • Loading branch information
ashishkujoy authored Nov 12, 2021
1 parent 79214e2 commit 6179c8c
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Date;
import java.util.concurrent.atomic.DoubleAccumulator;
import java.util.concurrent.atomic.DoubleAdder;

Expand Down Expand Up @@ -292,6 +293,16 @@ public class DefaultConstraintValidators implements ConstraintValidatorRegistry
return comparable.compareTo(value) > 0;
};

private final ConstraintValidator<Past, Date> pastDateConstraintValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
// null is valid according to spec
return true;
}
Comparable comparable = Date.from(context.getClockProvider().getClock().instant());
return comparable.compareTo(value) > 0;
};

private final ConstraintValidator<PastOrPresent, TemporalAccessor> pastOrPresentTemporalAccessorConstraintValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
Expand All @@ -302,6 +313,16 @@ public class DefaultConstraintValidators implements ConstraintValidatorRegistry
return comparable.compareTo(value) >= 0;
};

private final ConstraintValidator<PastOrPresent, Date> pastOrPresentDateConstraintValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
// null is valid according to spec
return true;
}
Comparable comparable = Date.from(context.getClockProvider().getClock().instant());
return comparable.compareTo(value) >= 0;
};

private final ConstraintValidator<Future, TemporalAccessor> futureTemporalAccessorConstraintValidator = (value, annotationMetadata, context) -> {
if (value == null) {
// null is valid according to spec
Expand All @@ -311,6 +332,15 @@ public class DefaultConstraintValidators implements ConstraintValidatorRegistry
return comparable.compareTo(value) < 0;
};

private final ConstraintValidator<Future, Date> futureDateConstraintValidator = (value, annotationMetadata, context) -> {
if (value == null) {
// null is valid according to spec
return true;
}
Comparable comparable = Date.from(context.getClockProvider().getClock().instant());
return comparable.compareTo(value) < 0;
};

private final ConstraintValidator<FutureOrPresent, TemporalAccessor> futureOrPresentTemporalAccessorConstraintValidator = (value, annotationMetadata, context) -> {
if (value == null) {
// null is valid according to spec
Expand All @@ -320,6 +350,15 @@ public class DefaultConstraintValidators implements ConstraintValidatorRegistry
return comparable.compareTo(value) <= 0;
};

private final ConstraintValidator<FutureOrPresent, Date> futureOrPresentDateConstraintValidator = (value, annotationMetadata, context) -> {
if (value == null) {
// null is valid according to spec
return true;
}
Comparable comparable = Date.from(context.getClockProvider().getClock().instant());
return comparable.compareTo(value) <= 0;
};

private final @Nullable BeanContext beanContext;
private final Map<ValidatorKey, ConstraintValidator> localValidators;

Expand Down Expand Up @@ -819,6 +858,15 @@ public ConstraintValidator<Past, TemporalAccessor> getPastTemporalAccessorConstr
return pastTemporalAccessorConstraintValidator;
}

/**
* The {@link Past} validator for Date accessor.
*
* @return The validator
*/
public ConstraintValidator<Past, Date> getPastDateConstraintValidator() {
return pastDateConstraintValidator;
}

/**
* The {@link PastOrPresent} validator for temporal accessor.
*
Expand All @@ -828,6 +876,15 @@ public ConstraintValidator<PastOrPresent, TemporalAccessor> getPastOrPresentTemp
return pastOrPresentTemporalAccessorConstraintValidator;
}

/**
* The {@link PastOrPresent} validator for Date accessor.
*
* @return The validator
*/
public ConstraintValidator<PastOrPresent, Date> getPastOrPresentDateConstraintValidator() {
return pastOrPresentDateConstraintValidator;
}

/**
* The {@link Future} validator for temporal accessor.
*
Expand All @@ -837,6 +894,15 @@ public ConstraintValidator<Future, TemporalAccessor> getFutureTemporalAccessorCo
return futureTemporalAccessorConstraintValidator;
}

/**
* The {@link Future} validator for Date accessor.
*
* @return The validator
*/
public ConstraintValidator<Future, Date> getFutureDateConstraintValidator() {
return futureDateConstraintValidator;
}

/**
* The {@link FutureOrPresent} validator for temporal accessor.
*
Expand All @@ -846,6 +912,15 @@ public ConstraintValidator<FutureOrPresent, TemporalAccessor> getFutureOrPresent
return futureOrPresentTemporalAccessorConstraintValidator;
}

/**
* The {@link FutureOrPresent} validator for Date accessor.
*
* @return The validator
*/
public ConstraintValidator<FutureOrPresent, Date> getFutureOrPresentDateConstraintValidator() {
return futureOrPresentDateConstraintValidator;
}

/**
* Last chance resolve for constraint validator.
* @param constraintType The constraint type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Unroll

import javax.validation.ClockProvider
import javax.validation.constraints.*
import java.time.Clock
import java.time.Instant
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import java.time.ZoneId
import java.time.ZoneOffset

import static java.math.BigInteger.ONE

Expand Down Expand Up @@ -233,26 +236,77 @@ class ConstraintsSpec extends AbstractTypeElementSpec {
Digits | 10 | true | constraintMetadata(constraint, /@Digits(integer=2, fraction=2)/)
Digits | 110 | false | constraintMetadata(constraint, /@Digits(integer=2, fraction=2)/)

// Past
Past | null | true | constraintMetadata(constraint, /@Past/)
Past | Instant.now().minus(1, ChronoUnit.DAYS) | true | constraintMetadata(constraint, /@Past/)
Past | Instant.now().plus(1, ChronoUnit.DAYS) | false | constraintMetadata(constraint, /@Past/)
Past | LocalDateTime.now().minus(1, ChronoUnit.DAYS) | true | constraintMetadata(constraint, /@Past/)
Past | LocalDateTime.now().plus(1, ChronoUnit.DAYS) | false | constraintMetadata(constraint, /@Past/)

// Future
Future | null | true | constraintMetadata(constraint, /@Past/)
Future | Instant.now().minus(1, ChronoUnit.DAYS) | false | constraintMetadata(constraint, /@Past/)
Future | Instant.now().plus(1, ChronoUnit.DAYS) | true | constraintMetadata(constraint, /@Past/)
Future | LocalDateTime.now().minus(1, ChronoUnit.DAYS) | false | constraintMetadata(constraint, /@Past/)
Future | LocalDateTime.now().plus(1, ChronoUnit.DAYS) | true | constraintMetadata(constraint, /@Past/)

// Email
Email | null | true | constraintMetadata(constraint, /@Email/)
Email | "junk" | false | constraintMetadata(constraint, /@Email/)
Email | "junk@junk.com" | true | constraintMetadata(constraint, /@Email/)
}

@Unroll
void "test #constraint constraint for value dates [#value]"() {
given:
def context = Mock(ConstraintValidatorContext)
def fixedInstant = LocalDateTime.parse("2020-01-01T00:00:00").toInstant(ZoneOffset.UTC)
context.getClockProvider() >> new TestClockProvider(fixedInstant)

def validator = reg.getConstraintValidator(constraint, value?.getClass() ?: Object)

expect:
validator.isValid(value, metadata, context) == isValid

where:
constraint | value | isValid | metadata

// Past
Past | Date.from(LocalDateTime.parse("2019-12-30T00:00:00").toInstant(ZoneOffset.UTC)) | true | constraintMetadata(constraint, /@Past/)
Past | Date.from(LocalDateTime.parse("2020-02-01T00:00:00").toInstant(ZoneOffset.UTC)) | false | constraintMetadata(constraint, /@Past/)
Past | LocalDateTime.parse("2019-12-30T00:00:00").toInstant(ZoneOffset.UTC) | true | constraintMetadata(constraint, /@Past/)
Past | LocalDateTime.parse("2020-02-01T00:00:00").toInstant(ZoneOffset.UTC) | false | constraintMetadata(constraint, /@Past/)
Past | LocalDateTime.parse("2019-12-30T00:00:00") | true | constraintMetadata(constraint, /@Past/)
Past | LocalDateTime.parse("2020-02-01T00:00:00") | false | constraintMetadata(constraint, /@Past/)
Past | null | true | constraintMetadata(constraint, /@Past/)

// Future
Future | Date.from(LocalDateTime.parse("2019-12-30T00:00:00").toInstant(ZoneOffset.UTC)) | false | constraintMetadata(constraint, /@Future/)
Future | Date.from(LocalDateTime.parse("2020-02-01T00:00:00").toInstant(ZoneOffset.UTC)) | true | constraintMetadata(constraint, /@Future/)
Future | LocalDateTime.parse("2019-12-30T00:00:00").toInstant(ZoneOffset.UTC) | false | constraintMetadata(constraint, /@Future/)
Future | LocalDateTime.parse("2020-02-01T00:00:00").toInstant(ZoneOffset.UTC) | true | constraintMetadata(constraint, /@Future/)
Future | LocalDateTime.parse("2019-12-30T00:00:00") | false | constraintMetadata(constraint, /@Future/)
Future | LocalDateTime.parse("2020-02-01T00:00:00") | true | constraintMetadata(constraint, /@Future/)
Future | null | true | constraintMetadata(constraint, /@Future/)

// FutureOrPresent
FutureOrPresent | Date.from(LocalDateTime.parse("2019-12-30T00:00:00").toInstant(ZoneOffset.UTC)) | false| constraintMetadata(constraint, /@FutureOrPresent/)
FutureOrPresent | Date.from(LocalDateTime.parse("2020-01-01T00:00:00").toInstant(ZoneOffset.UTC)) | true | constraintMetadata(constraint, /@FutureOrPresent/)
FutureOrPresent | Date.from(LocalDateTime.parse("2020-01-02T00:00:00").toInstant(ZoneOffset.UTC)) | true | constraintMetadata(constraint, /@FutureOrPresent/)
FutureOrPresent | LocalDateTime.parse("2019-12-30T00:00:00").toInstant(ZoneOffset.UTC) | false| constraintMetadata(constraint, /@FutureOrPresent/)
FutureOrPresent | LocalDateTime.parse("2020-01-01T00:00:00").toInstant(ZoneOffset.UTC) | true | constraintMetadata(constraint, /@FutureOrPresent/)
FutureOrPresent | LocalDateTime.parse("2020-01-02T00:00:00").toInstant(ZoneOffset.UTC) | true | constraintMetadata(constraint, /@FutureOrPresent/)
FutureOrPresent | null | true | constraintMetadata(constraint, /@FutureOrPresent/)

// PastOrPresent
PastOrPresent | Date.from(LocalDateTime.parse("2019-12-30T00:00:00").toInstant(ZoneOffset.UTC)) | true | constraintMetadata(constraint, /@PastOrPresent/)
PastOrPresent | Date.from(LocalDateTime.parse("2020-01-01T00:00:00").toInstant(ZoneOffset.UTC)) | true | constraintMetadata(constraint, /@PastOrPresent/)
PastOrPresent | Date.from(LocalDateTime.parse("2020-01-02T00:00:00").toInstant(ZoneOffset.UTC)) | false| constraintMetadata(constraint, /@PastOrPresent/)
PastOrPresent | LocalDateTime.parse("2019-12-30T00:00:00").toInstant(ZoneOffset.UTC) | true | constraintMetadata(constraint, /@PastOrPresent/)
PastOrPresent | LocalDateTime.parse("2020-01-01T00:00:00").toInstant(ZoneOffset.UTC) | true | constraintMetadata(constraint, /@PastOrPresent/)
PastOrPresent | LocalDateTime.parse("2020-01-02T00:00:00").toInstant(ZoneOffset.UTC) | false| constraintMetadata(constraint, /@PastOrPresent/)
PastOrPresent | null | true | constraintMetadata(constraint, /@PastOrPresent/)
}

private class TestClockProvider implements ClockProvider {
final Instant instant

TestClockProvider(Instant instant) {
this.instant = instant
}

@Override
Clock getClock() {
return Clock.fixed(instant, ZoneId.of("UTC"))
}
}

private AnnotationValue constraintMetadata(Class annotation, String ann) {
buildAnnotationMetadata(ann, "javax.validation.constraints").getAnnotation(annotation)
}
Expand Down

0 comments on commit 6179c8c

Please sign in to comment.