Skip to content

Commit 4a58897

Browse files
committed
Merge branch 'SentryMan-validation'
2 parents c00b43c + 53df00f commit 4a58897

File tree

31 files changed

+182
-104
lines changed

31 files changed

+182
-104
lines changed

http-api/src/main/java/io/avaje/http/api/Valid.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,23 @@
99
import java.lang.annotation.Target;
1010

1111
/**
12-
* Add {@code @Valid} annotation on a controller/method/BeanParam that we want bean validation to
13-
* be included for. When we do this controller methods that take a request payload will then have
14-
* the request bean (populated by JSON payload or form parameters) validated before it is passed
15-
* to the controller method.
16-
* <p>
17-
* When trying to validate a {@code @BeanParam} bean, this will need to be placed on the BeanParam type.
18-
* <p>
19-
* When using this annotation we need to provide an implementation of {@link Validator} to use.
20-
* <p>
21-
* Alternatively we can use the Jakarta {@code @Valid} along with a Jakarta validator implementation.
12+
* Add {@code @Valid} annotation on a controller/method/BeanParam that we want bean validation to be
13+
* included for. When we do this controller methods that take a request payload will then have the
14+
* request bean (populated by JSON payload or form parameters) validated before it is passed to the
15+
* controller method.
16+
*
17+
* <p>When trying to validate a {@code @BeanParam} bean, this will need to be placed on the
18+
* BeanParam type.
19+
*
20+
* <p>When using this annotation we need to provide an implementation of {@link Validator} to use.
21+
*
22+
* <p>Alternatively we can use the Jakarta {@code @Valid} along with a Jakarta validator
23+
* implementation.
2224
*/
2325
@Retention(SOURCE)
2426
@Target({METHOD, TYPE, PARAMETER})
2527
public @interface Valid {
28+
29+
/** Validation groups to use */
30+
Class<?>[] groups() default {};
2631
}
Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
package io.avaje.http.api;
22

3-
/**
4-
* Validator for form beans or request beans.
5-
*/
3+
import java.util.Collection;
4+
import java.util.List;
5+
import java.util.Locale;
6+
import java.util.Locale.LanguageRange;
7+
8+
/** Validator for form beans or request beans. */
69
public interface Validator {
710

811
/**
912
* Validate the bean throwing an exception if the bean fails validation.
10-
* <p>
11-
* Typically the exception will be handled by a specific exception handler
12-
* returning a 422 or 400 status code and usually a map of field paths to error messages.
13+
*
14+
* <p>Typically the exception will be handled by a specific exception handler returning a 422 or
15+
* 400 status code and usually a map of field paths to error messages.
1316
*
1417
* @param bean The bean to validate
1518
*/
16-
void validate(Object bean);
19+
void validate(Object bean, String acceptLanguage, Class<?>... groups) throws ValidationException;
20+
21+
default Locale resolveLocale(String acceptLanguage, Collection<Locale> acceptLocales) {
22+
if (acceptLanguage == null) {
23+
return null;
24+
}
25+
final List<LanguageRange> list = LanguageRange.parse(acceptLanguage);
26+
return Locale.lookup(list, acceptLocales);
27+
}
1728
}

http-client/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
<dependency>
8181
<groupId>io.avaje</groupId>
8282
<artifactId>avaje-http-hibernate-validator</artifactId>
83-
<version>3.2</version>
83+
<version>3.3</version>
8484
<scope>test</scope>
8585
</dependency>
8686

http-client/src/test/java/org/example/webserver/HelloController$Route.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,16 @@ public void registerRoutes() {
6464

6565
ApiBuilder.post("/hello", ctx -> {
6666
ctx.status(201);
67-
HelloDto dto = ctx.bodyAsClass(HelloDto.class);
68-
validator.validate(dto);
67+
final HelloDto dto = ctx.bodyAsClass(HelloDto.class);
68+
validator.validate(dto, "en-us");
6969
ctx.json(controller.post(dto));
7070
});
7171

7272
ApiBuilder.post("/hello/savebean/{foo}", ctx -> {
7373
ctx.status(201);
74-
String foo = ctx.pathParam("foo");
75-
HelloDto dto = ctx.bodyAsClass(HelloDto.class);
76-
validator.validate(dto);
74+
final String foo = ctx.pathParam("foo");
75+
final HelloDto dto = ctx.bodyAsClass(HelloDto.class);
76+
validator.validate(dto, "en-us");
7777
controller.saveBean(foo, dto, ctx);
7878
});
7979

@@ -86,7 +86,7 @@ public void registerRoutes() {
8686
helloForm.url = ctx.formParam("url");
8787
helloForm.startDate = toLocalDate(ctx.formParam("startDate"));
8888

89-
validator.validate(helloForm);
89+
validator.validate(helloForm, "en-us");
9090
controller.saveForm(helloForm);
9191
});
9292

@@ -107,7 +107,7 @@ public void registerRoutes() {
107107
helloForm.url = ctx.formParam("url");
108108
helloForm.startDate = toLocalDate(ctx.formParam("startDate"));
109109

110-
validator.validate(helloForm);
110+
validator.validate(helloForm, "en-us");
111111
ctx.json(controller.saveForm3(helloForm));
112112
});
113113

http-generator-client/src/main/java/io/avaje/http/generator/client/ClientPlatformAdapter.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,20 @@ public boolean isBodyMethodParam() {
3434
public String indent() {
3535
return null;
3636
}
37-
37+
// these have no meaning for client adapters
3838
@Override
39-
public void controllerRoles(List<String> roles, ControllerReader controller) {
40-
41-
}
39+
public void controllerRoles(List<String> roles, ControllerReader controller) {}
4240

4341
@Override
44-
public void methodRoles(List<String> roles, ControllerReader controller) {
45-
46-
}
42+
public void methodRoles(List<String> roles, ControllerReader controller) {}
4743

4844
@Override
49-
public void writeReadParameter(Append writer, ParamType paramType, String paramName) {
50-
51-
}
45+
public void writeReadParameter(Append writer, ParamType paramType, String paramName) {}
5246

5347
@Override
54-
public void writeReadParameter(Append writer, ParamType paramType, String paramName, String paramDefault) {
48+
public void writeReadParameter(
49+
Append writer, ParamType paramType, String paramName, String paramDefault) {}
5550

56-
}
51+
@Override
52+
public void writeAcceptLanguage(Append writer) {}
5753
}

http-generator-core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<artifactId>avaje-http-generator-core</artifactId>
1111

1212
<properties>
13-
<avaje.prisms.version>1.9</avaje.prisms.version>
13+
<avaje.prisms.version>1.10</avaje.prisms.version>
1414
</properties>
1515
<dependencies>
1616
<dependency>

http-generator-core/src/main/java/io/avaje/http/generator/core/Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class Constants {
1919
static final String IMPORT_HTTP_API = "io.avaje.http.api.*";
2020
static final String VALIDATOR = "io.avaje.http.api.Validator";
2121
public static final String META_INF_COMPONENT = "META-INF/services/io.avaje.http.client.HttpClient$GeneratedComponent";
22+
public static final String ACCEPT_LANGUAGE = "Accept-Language";
2223

2324

2425
}

http-generator-core/src/main/java/io/avaje/http/generator/core/ControllerReader.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,7 @@ private boolean initDocHidden() {
180180

181181
private boolean initHasValid() {
182182

183-
return findAnnotation(JavaxValidPrism::getOptionalOn).isPresent()
184-
|| findAnnotation(JakartaValidPrism::getOptionalOn).isPresent()
185-
|| findAnnotation(ValidPrism::getOptionalOn).isPresent();
183+
return findAnnotation(ValidPrism::getOptionalOn).isPresent();
186184
}
187185

188186
String produces() {

http-generator-core/src/main/java/io/avaje/http/generator/core/ElementReader.java

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import static io.avaje.http.generator.core.ParamType.RESPONSE_HANDLER;
44
import static io.avaje.http.generator.core.ProcessingContext.platform;
55
import static io.avaje.http.generator.core.ProcessingContext.typeElement;
6+
import static java.util.function.Predicate.not;
67

8+
import java.util.ArrayList;
79
import java.util.HashSet;
810
import java.util.List;
911
import java.util.Optional;
1012
import java.util.Set;
1113

1214
import javax.lang.model.element.*;
15+
import javax.lang.model.type.TypeMirror;
1316

1417
import io.avaje.http.generator.core.openapi.MethodDocBuilder;
1518
import io.avaje.http.generator.core.openapi.MethodParamDocBuilder;
@@ -39,6 +42,8 @@ public class ElementReader {
3942
private boolean isParamMap;
4043
private final Set<String> imports = new HashSet<>();
4144

45+
private final List<String> validationGroups = new ArrayList<>();
46+
4247
ElementReader(Element element, ParamType defaultType, boolean formMarker) {
4348
this(element, null, Util.typeDef(element.asType()), defaultType, formMarker);
4449
}
@@ -70,6 +75,11 @@ public class ElementReader {
7075
if (!contextType) {
7176
readAnnotations(element, defaultType);
7277
useValidation = useValidation();
78+
HttpValidPrism.getOptionalOn(element).map(HttpValidPrism::groups).stream()
79+
.flatMap(List::stream)
80+
.map(TypeMirror::toString)
81+
.forEach(validationGroups::add);
82+
this.imports.addAll(validationGroups);
7383
} else {
7484
paramType = ParamType.CONTEXT;
7585
useValidation = false;
@@ -138,10 +148,7 @@ private boolean useValidation() {
138148
return false;
139149
}
140150
final var elementType = typeElement(rawType);
141-
return elementType != null
142-
&& (ValidPrism.isPresent(elementType)
143-
|| JavaxValidPrism.isPresent(elementType)
144-
|| JakartaValidPrism.isPresent(elementType));
151+
return elementType != null && ValidPrism.isPresent(elementType);
145152
}
146153

147154
private void readAnnotations(Element element, ParamType defaultType) {
@@ -276,7 +283,14 @@ void buildApiDocumentation(MethodDocBuilder methodDoc) {
276283
void writeValidate(Append writer) {
277284
if (!contextType && typeHandler == null) {
278285
if (useValidation) {
279-
writer.append("validator.validate(%s);", varName).eol();
286+
writer.append("validator.validate(%s, ", varName);
287+
platform().writeAcceptLanguage(writer);
288+
289+
if (!validationGroups.isEmpty()) {
290+
validationGroups.forEach(g -> writer.append(", %s", Util.shortName(g)));
291+
}
292+
293+
writer.append(");").eol();
280294
} else {
281295
writer.append("// no validation required on %s", varName).eol();
282296
}
@@ -290,7 +304,7 @@ void writeCtxGet(Append writer, PathSegments segments) {
290304
// body passed as method parameter (Helidon)
291305
return;
292306
}
293-
String shortType = handlerShortType();
307+
final String shortType = handlerShortType();
294308
writer.append("%s var %s = ", platform().indent(), varName);
295309
if (setValue(writer, segments, shortType)) {
296310
writer.append(";").eol();
@@ -320,12 +334,12 @@ private boolean setValue(Append writer, PathSegments segments, String shortType)
320334
return false;
321335
}
322336
if (impliedParamType) {
323-
var name = matrixParamName != null ? matrixParamName : varName;
324-
PathSegments.Segment segment = segments.segment(name);
337+
final var name = matrixParamName != null ? matrixParamName : varName;
338+
final PathSegments.Segment segment = segments.segment(name);
325339
if (segment != null) {
326340
// path or matrix parameter
327-
boolean requiredParam = segment.isRequired(varName);
328-
String asMethod =
341+
final boolean requiredParam = segment.isRequired(varName);
342+
final String asMethod =
329343
(typeHandler == null)
330344
? null
331345
: (requiredParam) ? typeHandler.asMethod() : typeHandler.toMethod();
@@ -341,7 +355,7 @@ private boolean setValue(Append writer, PathSegments segments, String shortType)
341355
}
342356
}
343357

344-
String asMethod = (typeHandler == null) ? null : typeHandler.toMethod();
358+
final String asMethod = (typeHandler == null) ? null : typeHandler.toMethod();
345359
if (asMethod != null) {
346360
writer.append(asMethod);
347361
}
@@ -380,8 +394,8 @@ private boolean setValue(Append writer, PathSegments segments, String shortType)
380394
}
381395

382396
private void writeForm(Append writer, String shortType, String varName, ParamType defaultParamType) {
383-
TypeElement formBeanType = typeElement(rawType);
384-
BeanParamReader form = new BeanParamReader(formBeanType, varName, shortType, defaultParamType);
397+
final TypeElement formBeanType = typeElement(rawType);
398+
final BeanParamReader form = new BeanParamReader(formBeanType, varName, shortType, defaultParamType);
385399
form.write(writer);
386400
}
387401

http-generator-core/src/main/java/io/avaje/http/generator/core/MethodReader.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,18 +126,12 @@ private Javadoc buildJavadoc(ExecutableElement element) {
126126
}
127127

128128
private boolean initValid() {
129-
return findAnnotation(ValidPrism::getOptionalOn).isPresent()
130-
|| findAnnotation(JavaxValidPrism::getOptionalOn).isPresent()
131-
|| findAnnotation(JakartaValidPrism::getOptionalOn).isPresent()
132-
|| superMethodHasValid();
129+
return findAnnotation(ValidPrism::getOptionalOn).isPresent() || superMethodHasValid();
133130
}
134131

135132
private boolean superMethodHasValid() {
136133
return superMethods.stream()
137-
.anyMatch(
138-
e ->
139-
findAnnotation(ValidPrism::getOptionalOn).isPresent()
140-
|| findAnnotation(JavaxValidPrism::getOptionalOn).isPresent());
134+
.anyMatch(e -> findAnnotation(ValidPrism::getOptionalOn).isPresent());
141135
}
142136

143137
@Override

0 commit comments

Comments
 (0)