Skip to content

Commit

Permalink
Improve @Tag support for JAX-RS sub-resources
Browse files Browse the repository at this point in the history
@tag order of priority:

1. Directly on resource method
2. On nearest subresource producer method (when operation is in a
subresource)
3. On nearest subresource class (when operation is in a subresource)
4. On root resource class

Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar committed Oct 24, 2023
1 parent 6d4eecf commit 8965661
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ private void processResourceClasses(final AnnotationScannerContext context, Open
for (ClassInfo resourceClass : resourceClasses) {
TypeResolver resolver = TypeResolver.forClass(context, resourceClass, null);
context.getResolverStack().push(resolver);
processResourceClass(context, openApi, resourceClass, null);
// Process tags (both declarations and references).
Set<String> tags = processTags(context, resourceClass, openApi, false);
processResourceClass(context, openApi, resourceClass, null, tags);
context.getResolverStack().pop();
}
}
Expand All @@ -242,7 +244,8 @@ private void processResourceClasses(final AnnotationScannerContext context, Open
private void processResourceClass(final AnnotationScannerContext context,
OpenAPI openApi,
ClassInfo resourceClass,
List<Parameter> locatorPathParameters) {
List<Parameter> locatorPathParameters,
Set<String> tagRefs) {
JaxRsLogging.log.processingClass(resourceClass.simpleName());

// Process @SecurityScheme annotations.
Expand All @@ -252,7 +255,7 @@ private void processResourceClass(final AnnotationScannerContext context,
processJavaSecurity(context, resourceClass, openApi);

// Now find and process the operation methods
processResourceMethods(context, resourceClass, openApi, locatorPathParameters);
processResourceMethods(context, resourceClass, openApi, locatorPathParameters, tagRefs);
}

/**
Expand All @@ -266,10 +269,8 @@ private void processResourceClass(final AnnotationScannerContext context,
private void processResourceMethods(final AnnotationScannerContext context,
final ClassInfo resourceClass,
OpenAPI openApi,
List<Parameter> locatorPathParameters) {

// Process tags (both declarations and references).
Set<String> tagRefs = processTags(context, resourceClass, openApi, false);
List<Parameter> locatorPathParameters,
Set<String> tagRefs) {

// Process exception mapper to auto generate api response based on method exceptions
Map<DotName, List<AnnotationInstance>> exceptionAnnotationMap = processExceptionMappers(context);
Expand All @@ -291,7 +292,7 @@ private void processResourceMethods(final AnnotationScannerContext context,
});

if (resourceCount.get() == 0 && Annotations.hasAnnotation(methodInfo, JaxRsConstants.PATH)) {
processSubResource(context, resourceClass, methodInfo, openApi, locatorPathParameters);
processSubResource(context, resourceClass, methodInfo, openApi, locatorPathParameters, tagRefs);
}
}
}
Expand Down Expand Up @@ -367,7 +368,8 @@ private void processSubResource(final AnnotationScannerContext context,
final ClassInfo resourceClass,
final MethodInfo method,
OpenAPI openApi,
List<Parameter> locatorPathParameters) {
List<Parameter> locatorPathParameters,
Set<String> tagRefs) {
final Type methodReturnType = context.getResourceTypeResolver().resolve(method.returnType());

if (Type.Kind.VOID.equals(methodReturnType.kind())) {
Expand Down Expand Up @@ -401,6 +403,11 @@ private void processSubResource(final AnnotationScannerContext context,
TypeResolver resolver = TypeResolver.forClass(context, subResourceClass, methodReturnType);
context.getResolverStack().push(resolver);

// Check for @Tags from the method or the subresource class, default to the parent's tags
Set<String> subresourceTags = Optional.ofNullable(processTags(context, method, openApi, true))
.orElseGet(() -> Optional.ofNullable(processTags(context, subResourceClass, openApi, true))
.orElse(tagRefs));

/*
* Combine parameters passed previously with all of those from the current resource class and
* method that apply to this Path. The full list will be used as PATH-LEVEL parameters for
Expand All @@ -409,7 +416,8 @@ private void processSubResource(final AnnotationScannerContext context,
processResourceClass(context, openApi, subResourceClass,
ListUtil.mergeNullableLists(locatorPathParameters,
params.getPathItemParameters(),
params.getOperationParameters()));
params.getOperationParameters()),
subresourceTags);

context.getResolverStack().pop();
this.subResourceStack.pop();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
import java.io.IOException;
import java.util.HashMap;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.eclipse.microprofile.openapi.annotations.tags.Tags;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.jboss.jandex.Index;
import org.json.JSONException;
Expand Down Expand Up @@ -35,4 +42,62 @@ void testJakartaResteasyMultipartInput() throws IOException, JSONException {
test.io.smallrye.openapi.runtime.scanner.jakarta.Sub2TestResource.class,
test.io.smallrye.openapi.runtime.scanner.jakarta.RecursiveLocatorResource.class);
}

@Test
void testTagPlacementPriority() throws IOException, JSONException {
@Tag(name = "subresource-a-tag")
@Produces(MediaType.TEXT_PLAIN)
class SubresourceA {
@GET
@Path("/op1")
@Tag(name = "subresource-a-op1-tag")
public String op1() {
return null;
}

@GET
@Path("/op2")
public String op2() {
return null;
}
}

@Produces(MediaType.TEXT_PLAIN)
class SubresourceB {
@GET
@Path("/op1")
public String op1() {
return null;
}

@GET
@Path("/op2")
@Tags()
public String op2() {
return null;
}
}

@Path("/root")
@Tag(name = "root-tag")
class RootResource {
@Path("a1")
public SubresourceA getA1() {
return null;
}

@Path("a2")
@Tag(name = "root-a2-tag")
public SubresourceA getA2() {
return null;
}

@Path("b1")
public SubresourceB getB1() {
return null;
}
}

test("resource.subresource-tag-placement-priority.json", RootResource.class, SubresourceA.class, SubresourceB.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"openapi" : "3.0.3",
"tags" : [ {
"name" : "root-a2-tag"
}, {
"name" : "root-tag"
}, {
"name" : "subresource-a-op1-tag"
}, {
"name" : "subresource-a-tag"
} ],
"paths" : {
"/root/a1/op1" : {
"get" : {
"tags" : [ "subresource-a-op1-tag" ],
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"text/plain" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
},
"/root/a1/op2" : {
"get" : {
"tags" : [ "subresource-a-tag" ],
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"text/plain" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
},
"/root/a2/op1" : {
"get" : {
"tags" : [ "subresource-a-op1-tag" ],
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"text/plain" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
},
"/root/a2/op2" : {
"get" : {
"tags" : [ "root-a2-tag" ],
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"text/plain" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
},
"/root/b1/op1" : {
"get" : {
"tags" : [ "root-tag" ],
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"text/plain" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
},
"/root/b1/op2" : {
"get" : {
"responses" : {
"200" : {
"description" : "OK",
"content" : {
"text/plain" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
}
}
}

0 comments on commit 8965661

Please sign in to comment.