Skip to content

Commit

Permalink
Prefer parameter order from @Parameters annotation
Browse files Browse the repository at this point in the history
Closes smallrye#1165

Signed-off-by: Michael Edgar <michael@xlate.io>
  • Loading branch information
MikeEdgar committed Oct 14, 2022
1 parent dcca1fa commit 3772fc6
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,29 @@
*/
public class ResourceParameters {

public static final Comparator<Parameter> PARAMETER_COMPARATOR = Comparator
.comparing(Parameter::getRef, Comparator.nullsFirst(Comparator.naturalOrder()))
.thenComparing(Parameter::getIn, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Parameter::getName, Comparator.nullsLast(Comparator.naturalOrder()));
private static int position(List<Parameter> preferredOrder, Parameter p) {
int pos = preferredOrder.indexOf(p);
return pos >= 0 ? pos : Integer.MAX_VALUE;
}

public static Comparator<Parameter> parameterComparator(List<Parameter> preferredOrder) {
Comparator<Parameter> primaryComparator;

if (preferredOrder != null) {
Comparator<Parameter> preferredComparator = (p1, p2) -> Integer.compare(position(preferredOrder, p1),
position(preferredOrder, p2));

primaryComparator = preferredComparator
.thenComparing(Parameter::getRef, Comparator.nullsFirst(Comparator.naturalOrder()));
} else {
primaryComparator = Comparator
.comparing(Parameter::getRef, Comparator.nullsFirst(Comparator.naturalOrder()));
}

return primaryComparator
.thenComparing(Parameter::getIn, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Parameter::getName, Comparator.nullsLast(Comparator.naturalOrder()));
}

static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\{(\\w[\\w\\.-]*)\\}");

Expand Down Expand Up @@ -108,12 +127,14 @@ public void setFormBodyContent(Content formBodyContent) {
this.formBodyContent = formBodyContent;
}

public void sort() {
public void sort(List<Parameter> preferredOrder) {
Comparator<Parameter> comparator = parameterComparator(preferredOrder);

if (pathItemParameters != null) {
pathItemParameters.sort(PARAMETER_COMPARATOR);
pathItemParameters.sort(comparator);
}
if (operationParameters != null) {
operationParameters.sort(PARAMETER_COMPARATOR);
operationParameters.sort(comparator);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public abstract class AbstractParameterProcessor {
protected Map<String, Map<String, AnnotationInstance>> matrixParams = new LinkedHashMap<>();

private Set<String> processedMatrixSegments = new HashSet<>();
private List<Parameter> preferredOrder;

/**
* Used for collecting and merging any scanned {@link Parameter} annotations
Expand Down Expand Up @@ -276,7 +277,7 @@ protected void processOperationParameters(MethodInfo resourceMethod, ResourcePar
.forEach(this::readAnnotatedType);

/*
* Phase III - Read @Parameter(s) annotations directly on the recourd method
* Phase III - Read @Parameter(s) annotations directly on the record method
*
* Read the resource method and any method super classes/interfaces that it may override
*/
Expand Down Expand Up @@ -309,7 +310,7 @@ protected void processFinalize(ClassInfo resourceClass, MethodInfo resourceMetho
.forEach(parameters::addOperationParameter);

// Re-sort (names of matrix parameters may have changed)
parameters.sort();
parameters.sort(preferredOrder);

parameters.setFormBodyContent(getFormBodyContent());
}
Expand Down Expand Up @@ -424,7 +425,7 @@ protected List<Parameter> getParameters(MethodInfo resourceMethod) {
.stream()
.map(context -> this.mapParameter(resourceMethod, context))
.filter(Objects::nonNull)
.sorted(ResourceParameters.PARAMETER_COMPARATOR)
.sorted(ResourceParameters.parameterComparator(preferredOrder))
.collect(Collectors.toList());

return parameters.isEmpty() ? null : parameters;
Expand Down Expand Up @@ -936,11 +937,14 @@ protected void readParameterAnnotation(AnnotationInstance annotation) {
AnnotationValue annotationValue = annotation.value();

if (annotationValue != null) {
AnnotationInstance[] parameters = annotationValue.asNestedArray();
preferredOrder = new ArrayList<>(parameters.length);

/*
* Unwrap annotations wrapped by @Parameters and
* identify the target as the target of the @Parameters annotation
*/
for (AnnotationInstance nested : annotationValue.asNestedArray()) {
for (AnnotationInstance nested : parameters) {
readAnnotatedType(AnnotationInstance.create(nested.name(),
annotation.target(),
nested.values()),
Expand Down Expand Up @@ -1264,6 +1268,13 @@ protected boolean isReadableParameterAnnotation(DotName name) {
protected void readParameterAnnotation(AnnotationInstance annotation, boolean overriddenParametersOnly) {
Parameter oaiParam = readerFunction.apply(annotation);

if (oaiParam.getRef() != null) {
Parameter commonParam = ModelUtil.dereference(scannerContext.getOpenApi(), oaiParam);
oaiParam.setName(commonParam.getName());
oaiParam.setIn(commonParam.getIn());
oaiParam.setStyle(commonParam.getStyle());
}

readParameter(new ParameterContextKey(oaiParam),
oaiParam,
null,
Expand Down Expand Up @@ -1363,6 +1374,10 @@ protected void readParameter(ParameterContextKey key,
if (addParam) {
params.put(new ParameterContextKey(context), context);
}

if (preferredOrder != null) {
preferredOrder.add(context.oaiParam);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,4 +442,10 @@ void testJakartaGenericTypeVariableResource() throws IOException, JSONException
test.io.smallrye.openapi.runtime.scanner.jakarta.BaseGenericResource.GenericBean.class,
test.io.smallrye.openapi.runtime.scanner.jakarta.IntegerStringUUIDResource.class);
}

@Test
void testPreferredParameterOrderWithAnnotation() throws IOException, JSONException {
test("params.annotation-preferred-order.json",
test.io.smallrye.openapi.runtime.scanner.jakarta.ParameterOrderResource.CLASSES);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package test.io.smallrye.openapi.runtime.scanner.jakarta;

import java.util.List;

import jakarta.json.JsonObject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Application;

import org.eclipse.microprofile.openapi.annotations.Components;
import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition;
import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.info.Info;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameters;

public class ParameterOrderResource {

public static final Class<?>[] CLASSES = {
Application1.class,
Resource1.class
};

@OpenAPIDefinition(info = @Info(title = "Parameter Order", version = "1.0.0"), components = @Components(parameters = {
@Parameter(name = "namespace", schema = @Schema(type = SchemaType.STRING)),
@Parameter(name = "collection", schema = @Schema(type = SchemaType.STRING)),
@Parameter(name = "where", schema = @Schema(type = SchemaType.OBJECT)),
@Parameter(name = "fields", schema = @Schema(implementation = String[].class)),
@Parameter(name = "page-state", schema = @Schema(type = SchemaType.BOOLEAN)),
@Parameter(name = "profile", schema = @Schema(type = SchemaType.BOOLEAN)),
@Parameter(name = "raw", schema = @Schema(type = SchemaType.BOOLEAN))
}))
static class Application1 extends Application {
}

@Path("/1")
static class Resource1 {
@Parameters(value = {
@Parameter(ref = "namespace"),
@Parameter(name = "collection", ref = "collection"),
@Parameter(name = "where", ref = "where"),
@Parameter(ref = "fields"),
@Parameter(name = "page-size", in = ParameterIn.QUERY, description = "The max number of results to return.", schema = @Schema(implementation = Integer.class, defaultValue = "3", minimum = "1", maximum = "20")),
@Parameter(name = "page-state", ref = "page-state"),
@Parameter(name = "profile", ref = "profile"),
@Parameter(name = "raw", ref = "raw"),
})
@GET
public String get(
// Order purposefully different than parameters listed in `@Parameters` on method
@QueryParam("raw") boolean raw,
@QueryParam("collection") String collection,
@QueryParam("fields") List<String> fields,
@QueryParam("namespace") String namespace,
@QueryParam("page-size") int pageSize,
@QueryParam("page-state") boolean pageState,
@QueryParam("profile") boolean profile,
@QueryParam("where") JsonObject where) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"openapi": "3.0.3",
"info": {
"title": "Parameter Order",
"version": "1.0.0"
},
"paths": {
"/1": {
"get": {
"parameters": [
{
"$ref": "#/components/parameters/namespace"
},
{
"$ref": "#/components/parameters/collection"
},
{
"$ref": "#/components/parameters/where"
},
{
"$ref": "#/components/parameters/fields"
},
{
"name": "page-size",
"in": "query",
"description": "The max number of results to return.",
"schema": {
"format": "int32",
"default": 3,
"maximum": 20,
"minimum": 1,
"type": "integer"
}
},
{
"$ref": "#/components/parameters/page-state"
},
{
"$ref": "#/components/parameters/profile"
},
{
"$ref": "#/components/parameters/raw"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"*/*": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
},
"components": {
"parameters": {
"collection": {
"name": "collection",
"schema": {
"type": "string"
}
},
"fields": {
"name": "fields",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"namespace": {
"name": "namespace",
"schema": {
"type": "string"
}
},
"page-state": {
"name": "page-state",
"schema": {
"type": "boolean"
}
},
"profile": {
"name": "profile",
"schema": {
"type": "boolean"
}
},
"raw": {
"name": "raw",
"schema": {
"type": "boolean"
}
},
"where": {
"name": "where",
"schema": {
"type": "object"
}
}
}
}
}
Loading

0 comments on commit 3772fc6

Please sign in to comment.