Skip to content

Commit

Permalink
Scan Bean Validation annotations applied to request body parameter
Browse files Browse the repository at this point in the history
Fixes smallrye#1218

Signed-off-by: Michael Edgar <michael@xlate.io>
  • Loading branch information
MikeEdgar committed Aug 19, 2022
1 parent 401212f commit 22f837f
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import io.smallrye.openapi.runtime.scanner.AnnotationScannerExtension;
import io.smallrye.openapi.runtime.scanner.ResourceParameters;
import io.smallrye.openapi.runtime.scanner.dataobject.AugmentedIndexView;
import io.smallrye.openapi.runtime.scanner.dataobject.BeanValidationScanner;
import io.smallrye.openapi.runtime.scanner.processor.JavaSecurityProcessor;
import io.smallrye.openapi.runtime.util.JandexUtil;
import io.smallrye.openapi.runtime.util.ModelUtil;
Expand Down Expand Up @@ -915,6 +916,8 @@ default RequestBody processRequestBody(final AnnotationScannerContext context,
if (requestBody.getRequired() == null && TypeUtil.isOptional(requestBodyType)) {
requestBody.setRequired(Boolean.FALSE);
}

setRequestBodyConstraints(context, requestBody, method, requestBodyType);
}
}

Expand Down Expand Up @@ -960,6 +963,8 @@ && getConsumes(context) != null) {
if (requestBody.getRequired() == null && TypeUtil.isOptional(requestBodyType)) {
requestBody.setRequired(Boolean.FALSE);
}

setRequestBodyConstraints(context, requestBody, method, requestBodyType);
}
}
}
Expand Down Expand Up @@ -1004,6 +1009,31 @@ default Type getRequestBodyParameterClassType(final AnnotationScannerContext con
.orElse(null);
}

default void setRequestBodyConstraints(AnnotationScannerContext context, RequestBody requestBody, MethodInfo method,
Type requestBodyType) {
List<AnnotationInstance> paramAnnotations = JandexUtil.getMethodParameterAnnotations(method, requestBodyType);

if (!paramAnnotations.isEmpty() && context.getBeanValidationScanner().isPresent()) {
BeanValidationScanner constraintScanner = context.getBeanValidationScanner().get();
AnnotationTarget paramTarget = paramAnnotations.iterator().next().target();

Optional.ofNullable(requestBody.getContent())
.map(Content::getMediaTypes)
.map(Map::entrySet)
.orElseGet(Collections::emptySet)
.stream()
.map(Map.Entry::getValue)
.map(MediaType::getSchema)
.filter(Objects::nonNull)
.forEach(schema -> constraintScanner.applyConstraints(paramTarget, schema, null,
(target, name) -> {
if (requestBody.getRequired() == null) {
requestBody.setRequired(Boolean.TRUE);
}
}));
}
}

default boolean isPathParameter(final AnnotationScannerContext context, String name, final ResourceParameters params) {
if (context.getConfig().allowNakedPathParameter().orElse(Boolean.FALSE)) {
return params.getAllParameters()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,23 @@ public static AnnotationInstance getMethodParameterAnnotation(MethodInfo method,
return getMethodParameterAnnotation(method, parameterIndex, annotationName);
}

/**
* Finds all annotations on a particular parameter of a method based on the identity of the parameterType.
*
* @param method the method
* @param parameterType the parameter type
* @return the list of annotations, never null
*/
public static List<AnnotationInstance> getMethodParameterAnnotations(MethodInfo method, Type parameterType) {
// parameterType must be the same object as in the method's parameter type array
int parameterIndex = method.parameterTypes().indexOf(parameterType);
return method.annotations()
.stream()
.filter(annotation -> annotation.target().kind() == Kind.METHOD_PARAMETER)
.filter(annotation -> annotation.target().asMethodParameter().position() == parameterIndex)
.collect(Collectors.toList());
}

public static List<AnnotationValue> schemaDisplayValues(AnnotationInstance annotation) {
return annotation.values()
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.IOException;
import java.util.HashMap;

import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.jboss.jandex.Index;
import org.json.JSONException;
Expand Down Expand Up @@ -81,4 +82,24 @@ void testJakartaResteasyMultipartRelatedInput() throws IOException, JSONExceptio
test("params.resteasy-multipart-related-input.json",
test.io.smallrye.openapi.runtime.scanner.jakarta.ResteasyMultipartRelatedInputTestResource.class);
}

@Test
void testRequestBodyMethodParameterConstraints() throws Exception {
@jakarta.ws.rs.Path("/")
class Resource {
@jakarta.ws.rs.POST
@jakarta.ws.rs.Consumes("text/plain")
@jakarta.ws.rs.Path("foos")
public void addFoo(@jakarta.validation.constraints.NotEmpty String foo) {
}

@jakarta.ws.rs.POST
@jakarta.ws.rs.Consumes("text/plain")
@jakarta.ws.rs.Path("bars")
@RequestBody(required = false)
public void addBar(@jakarta.validation.constraints.NotEmpty String foo) {
}
}
test("params.request-body-constraints.json", Resource.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"openapi": "3.0.3",
"paths": {
"/bars": {
"post": {
"requestBody": {
"content": {
"text/plain": {
"schema": {
"minLength": 1,
"type": "string"
}
}
},
"required": false
},
"responses": {
"201": {
"description": "Created"
}
}
}
},
"/foos": {
"post": {
"requestBody": {
"content": {
"text/plain": {
"schema": {
"minLength": 1,
"type": "string"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "Created"
}
}
}
}
}
}

0 comments on commit 22f837f

Please sign in to comment.