Skip to content

Commit

Permalink
Merge pull request #137 from avaje/feature/primitive-validation
Browse files Browse the repository at this point in the history
Support primitive validation - limited to int,long with @range initially
  • Loading branch information
rob-bygrave authored Aug 9, 2023
2 parents 088c9bc + 1e10582 commit 562bcb8
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package example.avaje.range;

import io.avaje.validation.constraints.Range;
import jakarta.validation.Valid;

@Valid
public record APrimitiveLongRange(
@Range(min = 1, max = 3)
long value
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package example.avaje.range;

import io.avaje.validation.Validator;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;

import static org.assertj.core.api.Assertions.assertThat;

class APrimitiveTest {

Validator validator = Validator.builder().build();

@Test
void rangeLongValid() {
validator.validate(new APrimitiveLongRange(1));
validator.validate(new APrimitiveLongRange(2));
validator.validate(new APrimitiveLongRange(3));
}

@Test
void rangeLongBelowMin() {
var violations = new ArrayList<>(validator.check(new APrimitiveLongRange(0)));
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("must be between 1 and 3");
}

@Test
void rangeLongAboveMax() {
var violations = new ArrayList<>(validator.check(new APrimitiveLongRange(4)));
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("must be between 1 and 3");
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package example.avaje.range;

import io.avaje.validation.constraints.Length;
import io.avaje.validation.constraints.Range;
import jakarta.validation.Valid;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ final class AdapterHelper {
private final GenericType genericType;
private final boolean classLevel;
private final boolean crossParam;
private boolean usePrimitiveValidation;

AdapterHelper(Append writer, ElementAnnotationContainer elementAnnotations, String indent) {
this(writer, elementAnnotations, indent,"Object", null, false, false);
Expand Down Expand Up @@ -47,6 +48,11 @@ final class AdapterHelper {
this.crossParam = crossParam;
}

AdapterHelper usePrimitiveValidation(boolean usePrimitiveValidation) {
this.usePrimitiveValidation = usePrimitiveValidation;
return this;
}

void write() {
final var typeUse1 = elementAnnotations.typeUse1();
final var typeUse2 = elementAnnotations.typeUse2();
Expand All @@ -55,6 +61,10 @@ void write() {
if (crossParam) {
return;
}
if (usePrimitiveValidation) {
writer.eol().append("%s .primitive()", indent);
return;
}

if (!typeUse1.isEmpty() && (isAssignable2Interface(genericType.topType(), "java.lang.Iterable"))) {
writer.eol().append("%s .list()", indent);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.avaje.validation.generator;

import static io.avaje.validation.generator.PrimitiveUtil.isPrimitiveValidationAnnotations;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.toMap;

Expand Down Expand Up @@ -80,7 +81,6 @@ static boolean hasMetaConstraintAnnotation(AnnotationMirror m) {
}

static boolean hasMetaConstraintAnnotation(Element element) {

return ConstraintPrism.isPresent(element);
}

Expand All @@ -89,7 +89,6 @@ static boolean hasMetaConstraintAnnotation(Element element) {

static ElementAnnotationContainer create(VariableElement varElement) {
final var asString = varElement.asType().toString();

final var noGeneric = AnnotationUtil.splitString(asString, "<")[0];

final var annotations =
Expand Down Expand Up @@ -125,4 +124,14 @@ public void addImports(Set<String> importTypes) {
boolean isEmpty() {
return annotations.isEmpty() && typeUse1.isEmpty() && typeUse2.isEmpty();
}

boolean supportsPrimitiveValidation() {
for (GenericType validationAnnotation : annotations.keySet()) {
if (!isPrimitiveValidationAnnotations(validationAnnotation.shortName())) {
return false;
}
}
return true;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.avaje.validation.generator;

import static io.avaje.validation.generator.PrimitiveUtil.isPrimitiveValidationType;
import static io.avaje.validation.generator.ProcessingContext.logError;

import java.util.List;
Expand All @@ -10,8 +11,6 @@

final class FieldReader {

static final Set<String> BASIC_TYPES = Set.of("java.lang.String", "java.math.BigDecimal");

private final List<String> genericTypeParams;
private final boolean publicField;
private final GenericType genericType;
Expand All @@ -25,6 +24,7 @@ final class FieldReader {
private final Element element;
private final ElementAnnotationContainer elementAnnotations;
private final boolean classLevel;
private final boolean usePrimitiveValidation;

FieldReader(Element element, List<String> genericTypeParams) {
this(element, genericTypeParams, false);
Expand All @@ -38,13 +38,17 @@ final class FieldReader {
this.elementAnnotations = ElementAnnotationContainer.create(element, classLevel);
this.genericType = elementAnnotations.genericType();
final String shortType = genericType.shortType();
usePrimitiveValidation = isPrimitiveValidationType(shortType) && elementAnnotations.supportsPrimitiveValidation();
adapterShortType = initAdapterShortType(shortType);
adapterFieldName = initShortName();
this.optionalValidation = Util.isNullable(element);
this.classLevel = classLevel;
}

private String initAdapterShortType(String shortType) {
if (usePrimitiveValidation) {
return "ValidationAdapter.Primitive";
}
String typeWrapped = "ValidationAdapter<" + PrimitiveUtil.wrap(shortType) + ">";
for (final String typeParam : genericTypeParams) {
if (typeWrapped.contains("<" + typeParam + ">")) {
Expand Down Expand Up @@ -186,6 +190,7 @@ public void writeConstructor(Append writer) {
PrimitiveUtil.wrap(genericType.shortType()),
genericType,
classLevel)
.usePrimitiveValidation(usePrimitiveValidation)
.write();
writer.append(";").eol().eol();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

final class PrimitiveUtil {

static Map<String, String> wrapperMap = new HashMap<>();
private static final Set<String> primitiveValidationTypes = Set.of("int", "long");
private static final Set<String> primitiveValidationAnnotations =
Set.of("Range", "Min", "Max", "Positive", "PositiveOrZero", "Negative", "NegativeOrZero");
private static final Map<String, String> wrapperMap = new HashMap<>();

static {
wrapperMap.put("char", "Character");
Expand All @@ -27,6 +31,14 @@ static boolean isPrimitive(String typeShortName) {
return wrapperMap.containsKey(typeShortName);
}

static boolean isPrimitiveValidationType(String typeShortName) {
return primitiveValidationTypes.contains(typeShortName);
}

static boolean isPrimitiveValidationAnnotations(String annotationShortName) {
return primitiveValidationAnnotations.contains(annotationShortName);
}

static String defaultValue(String shortType) {
return "boolean".equals(shortType) ? "false" : "0";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ default boolean validate(T value, ValidationRequest req) {
return validate(value, req, null);
}

/**
* Return a primitive adapter. Supports int, long with Range, Min, Max, Positive.
*/
default Primitive primitive() {
throw new UnsupportedOperationException();
}

/**
* Create an adapter for validating a list of values.
*
Expand Down Expand Up @@ -113,4 +120,20 @@ default boolean checkGroups(Set<Class<?>> adapterGroups, ValidationRequest reque
}
return false;
}

/**
* Validation adapter that supports and uses the primitive type.
*/
interface Primitive {

/**
* Validate using primitive int.
*/
boolean validate(int value, ValidationRequest req, String propertyName);

/**
* Validate using primitive long.
*/
boolean validate(long value, ValidationRequest req, String propertyName);
}
}
Loading

0 comments on commit 562bcb8

Please sign in to comment.