Skip to content

Commit a414eed

Browse files
SentryManrbygrave
andauthored
add groups to valid (#274)
* add groups to valid - add a valid adapter - will request a valid adapter only if the valid annotation has groups * Format and extract helper method only --------- Co-authored-by: Rob Bygrave <robin.bygrave@gmail.com>
1 parent 981315f commit a414eed

File tree

8 files changed

+94
-5
lines changed

8 files changed

+94
-5
lines changed

blackbox-test/src/main/java/example/avaje/cascade/ACrew.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
import io.avaje.validation.constraints.Valid;
55

66
@Valid
7-
public record ACrew (@NotBlank(max = 4) String name) {
7+
public record ACrew(@NotBlank(max = 4) String name) {
88
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package example.avaje.cascade;
2+
3+
import io.avaje.validation.constraints.NotBlank;
4+
import io.avaje.validation.constraints.NotNull;
5+
import io.avaje.validation.constraints.Valid;
6+
7+
@Valid
8+
public record CascadeGroup(@Valid(groups = {CascadeGroup.class}) @NotNull Cascaded name) {
9+
10+
public record Cascaded(@NotBlank(groups = {CascadeGroup.class}) String val) {}
11+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package example.avaje.cascade;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import example.avaje.cascade.CascadeGroup.Cascaded;
8+
import io.avaje.validation.Validator;
9+
10+
class CascadeGroupTest {
11+
12+
Validator validator =
13+
Validator.builder()
14+
.add(CascadeGroup.class, CascadeGroupValidationAdapter::new)
15+
.add(CascadeGroup.Cascaded.class, CascadeGroup$CascadedValidationAdapter::new)
16+
.build();
17+
18+
@Test
19+
void valid() {
20+
var value = new CascadeGroup(new Cascaded(""));
21+
assertThat(validator.check(value)).isEmpty();
22+
}
23+
24+
@Test
25+
void validGroup() {
26+
var value = new CascadeGroup(new Cascaded(""));
27+
assertThat(validator.check(value, CascadeGroup.class).iterator().next())
28+
.matches(c -> "must not be blank".equals(c.message()));
29+
}
30+
}

validator-constraints/src/main/java/io/avaje/validation/constraints/Valid.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@
1919
*/
2020
@Retention(CLASS)
2121
@Target({TYPE, TYPE_USE, FIELD})
22-
public @interface Valid {}
22+
public @interface Valid {
23+
24+
/** Validation groups to use */
25+
Class<?>[] groups() default {};
26+
27+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private void writeFirst(List<Entry<UType, String>> annotations) {
122122
first = false;
123123
continue;
124124
}
125-
writer.eol().append("%s .andThen(ctx.adapter(%s.class,%s))", indent, a.getKey().shortWithoutAnnotations(), a.getValue());
125+
writer.eol().append("%s .andThen(ctx.adapter(%s.class, %s))", indent, a.getKey().shortWithoutAnnotations(), a.getValue());
126126
}
127127
if (annotations.isEmpty()) {
128128
writer.append("%sctx.<%s>noop()", indent, type);
@@ -150,7 +150,7 @@ private void writeTypeUse(UType uType, List<Entry<UType, String>> typeUse1, bool
150150
}
151151
final var k = a.getKey().shortType();
152152
final var v = a.getValue();
153-
writer.eol().append("%s .andThenMulti(ctx.adapter(%s.class,%s))", indent, k, v);
153+
writer.eol().append("%s .andThenMulti(ctx.adapter(%s.class, %s))", indent, k, v);
154154
}
155155

156156
if (!Util.isBasicType(uType.fullWithoutAnnotations())

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static java.util.stream.Collectors.toList;
66

77
import java.util.ArrayList;
8+
import java.util.Comparator;
89
import java.util.List;
910
import java.util.Map;
1011
import java.util.Map.Entry;
@@ -16,6 +17,7 @@
1617
import javax.lang.model.element.AnnotationMirror;
1718
import javax.lang.model.element.Element;
1819
import javax.lang.model.element.ExecutableElement;
20+
import javax.lang.model.element.TypeElement;
1921

2022
record ElementAnnotationContainer(
2123
UType genericType,
@@ -53,7 +55,7 @@ static ElementAnnotationContainer create(Element element) {
5355

5456
private static List<Entry<UType, String>> annotations(Element element, UType uType, List<Entry<UType, String>> crossParam) {
5557
return Stream.concat(element.getAnnotationMirrors().stream(), uType.annotations().stream())
56-
.filter(m -> !ValidPrism.isInstance(m))
58+
.filter(a -> excludePlainValid(a, element))
5759
.filter(ElementAnnotationContainer::hasMetaConstraintAnnotation)
5860
.map(a -> {
5961
if (CrossParamConstraintPrism.isPresent(a.getAnnotationType().asElement())) {
@@ -71,9 +73,18 @@ private static List<Entry<UType, String>> annotations(Element element, UType uTy
7173
UType.parse(a.getAnnotationType()),
7274
AnnotationUtil.annotationAttributeMap(a, element)))
7375
.distinct()
76+
// valid annotation goes last
77+
.sorted(Comparator.comparing(
78+
e -> e.getKey().shortType(),
79+
Comparator.comparing("Valid"::equals)))
7480
.collect(toList());
7581
}
7682

83+
/** Only include Valid with groups defined */
84+
private static boolean excludePlainValid(AnnotationMirror a, Element element) {
85+
return !ValidPrism.isInstance(a) || !ValidPrism.instance(a).groups().isEmpty() && !(element instanceof TypeElement);
86+
}
87+
7788
private static List<Entry<UType, String>> typeUseFor(UType uType, Element element) {
7889
return Optional.ofNullable(uType).map(UType::annotations).stream()
7990
.flatMap(List::stream)

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package io.avaje.validation.generator;
22

3+
import java.util.List;
4+
import java.util.Optional;
5+
36
import javax.lang.model.element.AnnotationMirror;
47
import javax.lang.model.element.Element;
8+
import javax.lang.model.type.TypeMirror;
59

610
import io.avaje.prism.GeneratePrism;
711

@@ -36,4 +40,17 @@ static boolean isInstance(AnnotationMirror e) {
3640
|| JavaxValidPrism.getInstance(e) != null
3741
|| HttpValidPrism.getInstance(e) != null;
3842
}
43+
44+
static ValidPrism instance(AnnotationMirror e) {
45+
return Optional.<ValidPrism>empty()
46+
.or(() -> AvajeValidPrism.getOptional(e))
47+
.or(() -> JakartaValidPrism.getOptional(e))
48+
.or(() -> JavaxValidPrism.getOptional(e))
49+
.or(() -> HttpValidPrism.getOptional(e))
50+
.orElse(null);
51+
}
52+
53+
default List<TypeMirror> groups() {
54+
return List.of();
55+
}
3956
}

validator/src/main/java/io/avaje/validation/core/adapters/BasicAdapters.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ private BasicAdapters() {}
3636
case "NotEmpty" -> new NotEmptyAdapter(request);
3737
case "Pattern" -> new PatternAdapter(request);
3838
case "Size", "Length" -> new SizeAdapter(request);
39+
case "Valid" -> new ValidAdapter(request);
3940
default -> null;
4041
};
4142

@@ -273,6 +274,20 @@ public boolean validate(Object value, ValidationRequest req, String propertyName
273274
}
274275
}
275276

277+
private static final class ValidAdapter implements ValidationAdapter<Object> {
278+
279+
private final Set<Class<?>> groups;
280+
281+
ValidAdapter(AdapterCreateRequest request) {
282+
this.groups = request.groups();
283+
}
284+
285+
@Override
286+
public boolean validate(Object value, ValidationRequest req, String propertyName) {
287+
return checkGroups(groups, req);
288+
}
289+
}
290+
276291
private static int arrayLength(Object array) {
277292
if (array instanceof final int[] intArr) {
278293
return intArr.length;

0 commit comments

Comments
 (0)