Skip to content

Commit 0152375

Browse files
committed
Merge remote-tracking branch 'upstream/main' into class-constraints
2 parents eb026c1 + 36a9621 commit 0152375

File tree

9 files changed

+201
-46
lines changed

9 files changed

+201
-46
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package example.avaje.cascade;
2+
3+
import io.avaje.validation.constraints.NotBlank;
4+
import io.avaje.validation.constraints.Valid;
5+
6+
@Valid
7+
public record ACrew (@NotBlank(max = 4) String name, String role) {
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package example.avaje.cascade;
2+
3+
import io.avaje.validation.constraints.NotBlank;
4+
import io.avaje.validation.constraints.Valid;
5+
6+
import java.util.List;
7+
8+
@Valid
9+
public record AShip(
10+
@NotBlank String name,
11+
@Valid List<ACrew> crew // cascade validation
12+
) { }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package example.avaje.cascade;
2+
3+
import io.avaje.validation.constraints.NotBlank;
4+
import io.avaje.validation.constraints.NotEmpty;
5+
import io.avaje.validation.constraints.Valid;
6+
7+
import java.util.List;
8+
9+
@Valid
10+
public record AShip2(
11+
@NotBlank String name,
12+
@Valid @NotEmpty List<ACrew> crew // not empty + cascade
13+
) { }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package example.avaje.cascade;
2+
3+
import io.avaje.validation.constraints.NotBlank;
4+
import io.avaje.validation.constraints.NotEmpty;
5+
import io.avaje.validation.constraints.Valid;
6+
7+
import java.util.List;
8+
9+
@Valid
10+
public record AShip3(
11+
@NotBlank String name,
12+
@NotEmpty List<ACrew> crew // not empty but no cascade validation
13+
) { }
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package example.avaje.cascade;
2+
3+
import io.avaje.validation.ConstraintViolation;
4+
import io.avaje.validation.ConstraintViolationException;
5+
import io.avaje.validation.Validator;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.Locale;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
import static org.assertj.core.api.Assertions.fail;
14+
15+
class AShipTest {
16+
17+
Validator validator = Validator.builder().build();
18+
19+
@Test
20+
void valid() {
21+
var ship = new AShip("lollyPop", List.of(new ACrew("ok", null)));
22+
validator.validate(ship);
23+
}
24+
25+
@Test
26+
void validAllowEmptyCollection() {
27+
var ship = new AShip("lollyPop", List.of());
28+
validator.validate(ship);
29+
}
30+
31+
@Test
32+
void validAllowNullCollection() {
33+
var ship = new AShip("lollyPop", null);
34+
validator.validate(ship);
35+
}
36+
37+
@Test
38+
void valid2() {
39+
var ship = new AShip2("lollyPop", List.of(new ACrew("ok", null)));
40+
validator.validate(ship);
41+
}
42+
43+
@Test
44+
void valid3_expect_noCascadeValidationToCrew() {
45+
var ship = new AShip3("lollyPop", List.of(new ACrew("NotValid", null)));
46+
validator.validate(ship);
47+
}
48+
49+
@Test
50+
void invalid() {
51+
var ship = new AShip("", List.of(new ACrew("NotValid", null)));
52+
List<ConstraintViolation> violations = violations(ship);
53+
54+
assertThat(violations).hasSize(2);
55+
assertThat(violations.get(0).path()).isEqualTo("name");
56+
assertThat(violations.get(0).message()).isEqualTo("must not be blank");
57+
assertThat(violations.get(1).path()).isEqualTo("crew[0].name");
58+
assertThat(violations.get(1).message()).isEqualTo("maximum length 4 exceeded");
59+
}
60+
61+
@Test
62+
void invalidShip2_expect_InvalidEmptyCollection() {
63+
var ship = new AShip2("", List.of());
64+
List<ConstraintViolation> violations = violations(ship);
65+
66+
assertThat(violations).hasSize(2);
67+
assertThat(violations.get(0).path()).isEqualTo("name");
68+
assertThat(violations.get(0).message()).isEqualTo("must not be blank");
69+
assertThat(violations.get(1).path()).isEqualTo("crew");
70+
assertThat(violations.get(1).message()).isEqualTo("must not be empty");
71+
}
72+
73+
@Test
74+
void invalidShip2_when_nullCollection_expect_InvalidEmptyCollection() {
75+
var ship = new AShip2("", null);
76+
List<ConstraintViolation> violations = violations(ship);
77+
78+
assertThat(violations).hasSize(2);
79+
assertThat(violations.get(0).path()).isEqualTo("name");
80+
assertThat(violations.get(0).message()).isEqualTo("must not be blank");
81+
assertThat(violations.get(1).path()).isEqualTo("crew");
82+
assertThat(violations.get(1).message()).isEqualTo("must not be empty");
83+
}
84+
85+
List<ConstraintViolation> violations(Object any) {
86+
return violations(any, Locale.ENGLISH);
87+
}
88+
89+
List<ConstraintViolation> violations(Object any, Locale locale) {
90+
try {
91+
validator.validate(any, locale);
92+
fail("not expected");
93+
return null;
94+
} catch (ConstraintViolationException e) {
95+
return new ArrayList<>(e.violations());
96+
}
97+
}
98+
}

validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,95 @@
11
package io.avaje.validation.generator;
22

33
import java.util.Map;
4+
45
import static io.avaje.validation.generator.ProcessingContext.isAssignable2Interface;
56

6-
public class AdapterHelper {
7+
class AdapterHelper {
78

8-
static void writeAdapterWithValues(
9-
Append writer, ElementAnnotationContainer elementAnnotations, String indent, String type) {
10-
boolean first = true;
9+
private final Append writer;
10+
private final ElementAnnotationContainer elementAnnotations;
11+
private final String indent;
12+
private final String type;
13+
private final GenericType topType;
14+
private final GenericType genericType;
15+
16+
AdapterHelper(Append writer, ElementAnnotationContainer elementAnnotations, String indent) {
17+
this(writer, elementAnnotations, indent,"Object", null);
18+
}
19+
20+
AdapterHelper(Append writer, ElementAnnotationContainer elementAnnotations, String indent, String type, GenericType topType) {
21+
this.writer = writer;
22+
this.elementAnnotations = elementAnnotations;
23+
this.indent = indent;
24+
this.type = type;
25+
this.topType = topType;
26+
this.genericType = elementAnnotations.genericType();
27+
}
28+
29+
void write() {
1130
final var annotations = elementAnnotations.annotations();
12-
final var genericType = elementAnnotations.genericType();
1331
final var typeUse1 = elementAnnotations.typeUse1();
1432
final var typeUse2 = elementAnnotations.typeUse2();
1533
final var hasValid = elementAnnotations.hasValid();
34+
boolean first = true;
1635
for (final var a : annotations.entrySet()) {
1736
if (first) {
18-
writer.append(
19-
"%sctx.<%s>adapter(%s.class, %s)", indent, type, a.getKey().shortName(), a.getValue());
37+
writer.append("%sctx.<%s>adapter(%s.class, %s)", indent, type, a.getKey().shortName(), a.getValue());
2038
first = false;
2139
continue;
2240
}
23-
writer
24-
.eol()
25-
.append(
26-
"%s .andThen(ctx.adapter(%s.class,%s))",
27-
indent, a.getKey().shortName(), a.getValue());
41+
writer.eol().append("%s .andThen(ctx.adapter(%s.class,%s))", indent, a.getKey().shortName(), a.getValue());
2842
}
2943

3044
if (annotations.isEmpty()) {
3145
writer.append("%sctx.<%s>noop()", indent, type);
3246
}
3347

34-
if (!typeUse1.isEmpty()
35-
&& (isAssignable2Interface(genericType.topType(), "java.lang.Iterable"))) {
48+
if (!typeUse1.isEmpty() && (isAssignable2Interface(genericType.topType(), "java.lang.Iterable"))) {
3649
writer.eol().append("%s .list()", indent);
37-
final var t = genericType.firstParamType();
38-
writeTypeUse(writer, indent, t, typeUse1, genericType);
50+
writeTypeUse(genericType.firstParamType(), typeUse1);
51+
52+
} else if (isTopTypeIterable()) {
53+
writer.eol().append("%s .list()", indent);
54+
if (hasValid) {
55+
// cascade validate
56+
writer.eol().append("%s .andThenMulti(ctx.adapter(%s.class))", indent, Util.shortName(topType.firstParamType()));
57+
}
3958

4059
} else if ((!typeUse1.isEmpty() || !typeUse2.isEmpty())
4160
&& "java.util.Map".equals(genericType.topType())) {
4261

4362
writer.eol().append("%s .mapKeys()", indent);
44-
writeTypeUse(writer, indent, genericType.firstParamType(), typeUse1, genericType);
63+
writeTypeUse(genericType.firstParamType(), typeUse1);
4564

4665
writer.eol().append("%s .mapValues()", indent);
47-
writeTypeUse(writer, indent, genericType.secondParamType(), typeUse2, false, genericType);
66+
writeTypeUse(genericType.secondParamType(), typeUse2, false);
4867

4968
} else if (genericType.topType().contains("[]") && hasValid) {
50-
5169
writer.eol().append("%s .array()", indent);
52-
writeTypeUse(writer, indent, genericType.firstParamType(), typeUse1, genericType);
70+
writeTypeUse(genericType.firstParamType(), typeUse1);
71+
5372
} else if (hasValid) {
54-
writer
55-
.eol()
56-
.append(
57-
"%s .andThen(ctx.adapter(%s.class))",
58-
indent, Util.shortName(genericType.topType()));
73+
writer.eol().append("%s .andThen(ctx.adapter(%s.class))", indent, Util.shortName(genericType.topType()));
74+
5975
} else if (genericType.topType().contains("java.util.Optional")) {
6076
writer.eol().append("%s .optional()", indent);
6177
}
6278
}
6379

64-
private static void writeTypeUse(
65-
Append writer,
66-
String indent,
67-
String firstParamType,
68-
Map<GenericType, String> typeUse12,
69-
GenericType genericType) {
70-
writeTypeUse(writer, indent, firstParamType, typeUse12, true, genericType);
80+
private boolean isTopTypeIterable() {
81+
if (topType != null && isAssignable2Interface(topType.topType(), "java.lang.Iterable")) {
82+
return true;
83+
}
84+
return false;
7185
}
7286

73-
private static void writeTypeUse(
74-
Append writer,
75-
String indent,
76-
String paramType,
77-
Map<GenericType, String> typeUseMap,
78-
boolean keys,
79-
GenericType genericType) {
87+
private void writeTypeUse(String firstParamType, Map<GenericType, String> typeUse12) {
88+
writeTypeUse(firstParamType, typeUse12, true);
89+
}
8090

91+
private void writeTypeUse(String paramType, Map<GenericType, String> typeUseMap, boolean keys) {
8192
for (final var a : typeUseMap.entrySet()) {
82-
8393
if (Constants.VALID_ANNOTATIONS.contains(a.getKey().topType())) {
8494
continue;
8595
}

validator-generator/src/main/java/io/avaje/validation/generator/FieldReader.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,9 @@ public GenericType type() {
167167
public void writeConstructor(Append writer) {
168168
writer.append(" this.%s = ", adapterFieldName).eol();
169169

170-
AdapterHelper.writeAdapterWithValues(
171-
writer, elementAnnotations, " ", PrimitiveUtil.wrap(genericType.shortType()));
170+
new AdapterHelper(
171+
writer, elementAnnotations, " ", PrimitiveUtil.wrap(genericType.shortType()), genericType)
172+
.write();
172173
writer.append(";").eol().eol();
173174
}
174175

validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public List<ValidationAdapter<Object>> paramAdapters(ValidationContext ctx) {
9292
writer.append(" return List.of(");
9393
final var size = paramAnnotations.size();
9494
for (int i = 0; i < paramAnnotations.size(); i++) {
95-
AdapterHelper.writeAdapterWithValues(writer, paramAnnotations.get(i), "\n ", "Object");
95+
new AdapterHelper(writer, paramAnnotations.get(i), "\n ").write();
9696
if (i + 1 != size) {
9797
writer.append(",");
9898
}
@@ -107,7 +107,7 @@ public List<ValidationAdapter<Object>> paramAdapters(ValidationContext ctx) {
107107
public ValidationAdapter<Object> returnAdapter(ValidationContext ctx) {
108108
""");
109109
writer.append(" return ");
110-
AdapterHelper.writeAdapterWithValues(writer, returnElementAnnotation, "", "Object");
110+
new AdapterHelper(writer, returnElementAnnotation, "").write();
111111

112112
writer.append(";").eol();
113113
writer.append(" }").eol();

validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
public abstract class AbstractContainerAdapter<T> implements ValidationAdapter<T> {
88

99
/**
10-
* Adapter placed on the the container type
10+
* Adapter placed on the container type
1111
*/
1212
protected final ValidationAdapter<T> initalAdapter;
1313

@@ -33,7 +33,7 @@ public AbstractContainerAdapter<T> andThenMulti(ValidationAdapter<?> adapter) {
3333

3434
/** Execute validations for all items in the given iterable */
3535
protected boolean validateAll(Iterable<Object> value, ValidationRequest req, String propertyName) {
36-
if (value == null) {
36+
if (value == null || multiAdapter == null) {
3737
return true;
3838
}
3939
if (propertyName != null) {

0 commit comments

Comments
 (0)