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

Range Adapter #96

Merged
merged 5 commits into from
Jul 30, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.avaje.validation.constraints;

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 static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import io.avaje.validation.constraints.Range.List;

/**
* The annotated element has to be in the appropriate range. Apply on numeric values or string
* representation of the numeric value.
*
* @author Hardy Ferentschik
*/
@Documented
@Retention(RUNTIME)
@Repeatable(List.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
public @interface Range {
long min() default 0;

long max() default Long.MAX_VALUE;

String message() default "{avaje.Range.message}";

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

/** Defines several {@code @Range} annotations on the same element. */
@Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
Range[] value();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,31 @@ public final class BasicAdapters {
private BasicAdapters() {}

public static final ValidationContext.AnnotationFactory FACTORY =
(request) ->
request ->
switch (request.annotationType().getSimpleName()) {
case "Email" -> new EmailAdapter(request);
case "Null" -> new NullableAdapter(request, true);
case "NotNull", "NonNull" -> new NullableAdapter(
request, false);
case "AssertTrue" -> new AssertBooleanAdapter(
request, Boolean.TRUE);
case "AssertFalse" -> new AssertBooleanAdapter(
request, Boolean.FALSE);
case "NotNull", "NonNull" -> new NullableAdapter(request, false);
case "AssertTrue" -> new AssertBooleanAdapter(request, Boolean.TRUE);
case "AssertFalse" -> new AssertBooleanAdapter(request, Boolean.FALSE);
case "NotBlank" -> new NotBlankAdapter(request);
case "NotEmpty" -> new NotEmptyAdapter(request);
case "Pattern" -> new PatternAdapter(request);
case "Size", "Length" -> new SizeAdapter(request);
default -> null;
};

private static final class PatternAdapter extends AbstractConstraintAdapter<CharSequence> {
static sealed class PatternAdapter extends AbstractConstraintAdapter<CharSequence>
permits EmailAdapter {

private final Predicate<String> pattern;
protected final Predicate<String> pattern;

@SuppressWarnings("unchecked")
PatternAdapter(AdapterCreateRequest request) {
this(request, (String) request.attributes().get("regexp"));
}

@SuppressWarnings("unchecked")
PatternAdapter(AdapterCreateRequest request, String regex) {
super(request);
int flags = 0;

Expand All @@ -51,8 +53,7 @@ private static final class PatternAdapter extends AbstractConstraintAdapter<Char
flags |= flag.getValue();
}
}
this.pattern =
Pattern.compile((String) attributes.get("regexp"), flags).asMatchPredicate().negate();
this.pattern = Pattern.compile(regex, flags).asMatchPredicate().negate();
}

@Override
Expand Down Expand Up @@ -125,7 +126,7 @@ public boolean isValid(CharSequence cs) {
return (cs != null) && !isBlank(cs);
}

private static boolean isBlank(final CharSequence cs) {
static boolean isBlank(final CharSequence cs) {
final int strLen = cs.length();
if (strLen == 0) {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
package io.avaje.validation.core.adapters;

import io.avaje.validation.adapter.AbstractConstraintAdapter;
import io.avaje.validation.adapter.RegexFlag;
import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest;
import static java.util.regex.Pattern.CASE_INSENSITIVE;

import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.CASE_INSENSITIVE;
import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest;
import io.avaje.validation.core.adapters.BasicAdapters.PatternAdapter;

/* most of this was written by
* @author Emmanuel Bernard
* @author Hardy Ferentschik
* @author Guillaume Smet
*/
final class EmailAdapter extends AbstractConstraintAdapter<CharSequence> {
final class EmailAdapter extends PatternAdapter {

private static final int MAX_LOCAL_PART_LENGTH = 64;

Expand All @@ -39,25 +36,8 @@ final class EmailAdapter extends AbstractConstraintAdapter<CharSequence> {
+ ")*",
CASE_INSENSITIVE);

private final Predicate<String> pattern;

@SuppressWarnings("unchecked")
EmailAdapter(AdapterCreateRequest request) {
super(request);
final var attributes = request.attributes();
int flags = 0;
var regex = (String) attributes.get("regexp");
if (regex == null) {
regex = ".*";
}

final List<RegexFlag> flags1 = (List<RegexFlag>) attributes.get("flags");
if (flags1 != null) {
for (final var flag : flags1) {
flags |= flag.getValue();
}
}
this.pattern = Pattern.compile(regex, flags).asMatchPredicate().negate();
super(request, (String) request.attributes().getOrDefault("regexp", ".*"));
}

@Override
Expand All @@ -79,13 +59,9 @@ public boolean isValid(CharSequence value) {
final String localPart = stringValue.substring(0, splitPosition);
final String domainPart = stringValue.substring(splitPosition + 1);

if (!isValidEmailLocalPart(localPart)
|| !DomainNameUtil.isValidEmailDomainAddress(domainPart)
|| pattern.test(value.toString())) {
return false;
}

return true;
return isValidEmailLocalPart(localPart)
&& DomainNameUtil.isValidEmailDomainAddress(domainPart)
&& !pattern.test(value.toString());
}

private boolean isValidEmailLocalPart(String localPart) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ public final class NumberAdapters {
private NumberAdapters() {}

public static final ValidationContext.AnnotationFactory FACTORY =
(request) ->
switch (request.annotationType().getSimpleName()) {
case "Digits" -> new DigitsAdapter(request);
case "Positive" -> new PositiveAdapter(request, false);
case "PositiveOrZero" -> new PositiveAdapter(request, true);
case "Negative" -> new NegativeAdapter(request, false);
case "NegativeOrZero" -> new NegativeAdapter(request, true);
case "Max" -> new MaxAdapter(request);
case "Min" -> new MinAdapter(request);
case "DecimalMax" -> new DecimalMaxAdapter(request);
case "DecimalMin" -> new DecimalMinAdapter(request);
default -> null;
};
request->switch (request.annotationType().getSimpleName()) {
case "Digits" -> new DigitsAdapter(request);
case "Positive" -> new PositiveAdapter(request, false);
case "PositiveOrZero" -> new PositiveAdapter(request, true);
case "Negative" -> new NegativeAdapter(request, false);
case "NegativeOrZero" -> new NegativeAdapter(request, true);
case "Max" -> new MaxAdapter(request);
case "Min" -> new MinAdapter(request);
case "DecimalMax" -> new DecimalMaxAdapter(request);
case "DecimalMin" -> new DecimalMinAdapter(request);
case "Range" -> new RangeAdapter(request);
default -> null;
};

private static final class DecimalMaxAdapter extends AbstractConstraintAdapter<Number> {

Expand Down Expand Up @@ -73,10 +73,8 @@ public boolean isValid(Number number) {
}

final int comparisonResult = NumberComparatorHelper.compareDecimal(number, value, LESS_THAN);
if (inclusive ? comparisonResult < 0 : comparisonResult <= 0) {
return false;
}
return true;

return !(inclusive ? comparisonResult < 0 : comparisonResult <= 0);
}
}

Expand All @@ -90,18 +88,16 @@ private static final class MaxAdapter extends AbstractConstraintAdapter<Number>
this.value = (long) attributes.get("value");
}

MaxAdapter(AdapterCreateRequest request, long value) {
super(request);
this.value = value;
}

@Override
public boolean isValid(Number number) {
// null values are valid
if (number == null) {
return true;
}

if (NumberComparatorHelper.compare(number, value, GREATER_THAN) > 0) {

return false;
}
return true;
return number == null || NumberComparatorHelper.compare(number, value, GREATER_THAN) <= 0;
}
}

Expand All @@ -115,18 +111,16 @@ private static final class MinAdapter extends AbstractConstraintAdapter<Number>
this.value = (long) attributes.get("value");
}

MinAdapter(AdapterCreateRequest request, long value) {
super(request);
this.value = value;
}

@Override
public boolean isValid(Number number) {
// null values are valid
if (number == null) {
return true;
}

if (NumberComparatorHelper.compare(number, value, LESS_THAN) < 0) {

return false;
}
return true;
return number == null || NumberComparatorHelper.compare(number, value, LESS_THAN) >= 0;
}
}

Expand Down Expand Up @@ -158,12 +152,8 @@ public boolean isValid(Object value) {

final int integerPartLength = bigNum.precision() - bigNum.scale();
final int fractionPartLength = Math.max(bigNum.scale(), 0);
if (integer < integerPartLength || fraction < fractionPartLength) {

return false;
}

return true;
return (integer >= integerPartLength) && (fraction >= fractionPartLength);
}
}

Expand All @@ -184,12 +174,8 @@ public boolean isValid(Object value) {
}

final int sign = NumberSignHelper.signum(value, LESS_THAN);
if (inclusive ? sign < 0 : sign <= 0) {

return false;
}

return true;
return !(inclusive ? sign < 0 : sign <= 0);
}
}

Expand All @@ -210,12 +196,33 @@ public boolean isValid(Object value) {
}

final int sign = NumberSignHelper.signum(value, GREATER_THAN);
if (inclusive ? sign > 0 : sign >= 0) {

return false;
}
return !(inclusive ? sign > 0 : sign >= 0);
}
}

private static final class RangeAdapter extends AbstractConstraintAdapter<Object> {

private final MaxAdapter maxAdapter;
private final MinAdapter minAdapter;

return true;
RangeAdapter(AdapterCreateRequest request) {
super(request);
final var attributes = request.attributes();
final var min = (int) attributes.get("min");
final var max = (int) attributes.get("max");
this.maxAdapter = new MaxAdapter(request, max);
this.minAdapter = new MinAdapter(request, min);
}

@Override
public boolean isValid(Object value) {

if (value instanceof final String s) {
value = Long.parseLong(s);
}
final var num = (Number) value;
return minAdapter.isValid(num) && maxAdapter.isValid(num);
}
}
}