Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rest Data Panache: Correct Open API integration #28811

Merged
merged 1 commit into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import static org.hamcrest.Matchers.is;

import java.util.List;

import org.hamcrest.Matchers;
Expand All @@ -25,6 +27,7 @@
class OpenApiIntegrationTest {

private static final String OPEN_API_PATH = "/q/openapi";
private static final String COLLECTIONS_SCHEMA_REF = "#/components/schemas/Collection";

@RegisterExtension
static final QuarkusProdModeTest TEST = new QuarkusProdModeTest()
Expand All @@ -36,7 +39,7 @@ class OpenApiIntegrationTest {
.addAsResource("application.properties")
.addAsResource("import.sql"))
.setForcedDependencies(List.of(
Dependency.of("io.quarkus", "quarkus-smallrye-openapi", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-smallrye-openapi-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-jdbc-h2-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-resteasy-jsonb-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-security-deployment", Version.getVersion())))
Expand All @@ -51,14 +54,28 @@ public void testOpenApiForGeneratedResources() {
.body("info.title", Matchers.equalTo("quarkus-hibernate-orm-rest-data-panache-deployment API"))
.body("paths.'/collections'", Matchers.hasKey("get"))
.body("paths.'/collections'.get.tags", Matchers.hasItem("CollectionsResource"))
.body("paths.'/collections'.get.responses.'200'.content.'application/json'.schema.type", is("array"))
.body("paths.'/collections'.get.responses.'200'.content.'application/json'.schema.items.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections'", Matchers.hasKey("post"))
.body("paths.'/collections'.post.tags", Matchers.hasItem("CollectionsResource"))
.body("paths.'/collections'.post.requestBody.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections'.post.responses.'201'.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections'.post.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("get"))
.body("paths.'/collections/{id}'.get.responses.'200'.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections/{id}'.get.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("put"))
.body("paths.'/collections/{id}'.put.requestBody.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections/{id}'.put.responses.'201'.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections/{id}'.put.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("delete"))
.body("paths.'/collections/{id}'.delete.responses", Matchers.hasKey("204"))
.body("paths.'/collections/{id}'.delete.security[0].SecurityScheme", Matchers.hasItem("admin"))
.body("paths.'/empty-list-items'", Matchers.hasKey("get"))
.body("paths.'/empty-list-items'.get.tags", Matchers.hasItem("EmptyListItemsResource"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.hibernate.reactive.rest.data.panache.deployment.openapi;

import static org.hamcrest.Matchers.is;

import java.util.List;

import org.hamcrest.Matchers;
Expand All @@ -25,6 +27,7 @@
class OpenApiIntegrationTest {

private static final String OPEN_API_PATH = "/q/openapi";
private static final String COLLECTIONS_SCHEMA_REF = "#/components/schemas/Collection";

@RegisterExtension
static final QuarkusProdModeTest TEST = new QuarkusProdModeTest()
Expand All @@ -36,7 +39,7 @@ class OpenApiIntegrationTest {
.addAsResource("application.properties")
.addAsResource("import.sql"))
.setForcedDependencies(List.of(
Dependency.of("io.quarkus", "quarkus-smallrye-openapi", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-smallrye-openapi-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-reactive-pg-client-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-resteasy-reactive-jsonb-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-security-deployment", Version.getVersion())))
Expand All @@ -51,14 +54,28 @@ public void testOpenApiForGeneratedResources() {
.body("info.title", Matchers.equalTo("quarkus-hibernate-reactive-rest-data-panache-deployment API"))
.body("paths.'/collections'", Matchers.hasKey("get"))
.body("paths.'/collections'.get.tags", Matchers.hasItem("CollectionsResource"))
.body("paths.'/collections'.get.responses.'200'.content.'application/json'.schema.type", is("array"))
.body("paths.'/collections'.get.responses.'200'.content.'application/json'.schema.items.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections'", Matchers.hasKey("post"))
.body("paths.'/collections'.post.tags", Matchers.hasItem("CollectionsResource"))
.body("paths.'/collections'.post.requestBody.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections'.post.responses.'201'.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections'.post.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("get"))
.body("paths.'/collections/{id}'.get.responses.'200'.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections/{id}'.get.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("put"))
.body("paths.'/collections/{id}'.put.requestBody.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections/{id}'.put.responses.'201'.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections/{id}'.put.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("delete"))
.body("paths.'/collections/{id}'.delete.responses", Matchers.hasKey("204"))
.body("paths.'/collections/{id}'.delete.security[0].SecurityScheme", Matchers.hasItem("admin"))
.body("paths.'/empty-list-items'", Matchers.hasKey("get"))
.body("paths.'/empty-list-items'.get.tags", Matchers.hasItem("EmptyListItemsResource"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addConsumesAnnotation(methodCreator, APPLICATION_JSON);
addProducesJsonAnnotation(methodCreator, resourceProperties);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addOpenApiResponseAnnotation(methodCreator, Response.Status.CREATED, resourceMetadata.getEntityType());
addSecurityAnnotations(methodCreator, resourceProperties);
// Add parameter annotations
if (hasValidatorCapability()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addGetAnnotation(methodCreator);
addProducesAnnotation(methodCreator, APPLICATION_JSON);
addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), RESOURCE_METHOD_NAME));
addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, Long.class, false);
addSecurityAnnotations(methodCreator, resourceProperties);
if (!isResteasyClassic()) {
// We only add the Links annotation in Resteasy Reactive because Resteasy Classic ignores the REL parameter:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addDeleteAnnotation(methodCreator);
addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addOpenApiResponseAnnotation(methodCreator, Response.Status.NO_CONTENT);
addSecurityAnnotations(methodCreator, resourceProperties);

ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), "{id}"));
addGetAnnotation(methodCreator);
addProducesJsonAnnotation(methodCreator, resourceProperties);
addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType());
addSecurityAnnotations(methodCreator, resourceProperties);

addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ private void implementPaged(ClassCreator classCreator, ResourceMetadata resource
addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
addProducesAnnotation(methodCreator, APPLICATION_JSON);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType(), true);
addSecurityAnnotations(methodCreator, resourceProperties);
addSortQueryParamValidatorAnnotation(methodCreator);
addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort");
Expand Down Expand Up @@ -209,6 +210,7 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou
addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
addProducesAnnotation(methodCreator, APPLICATION_JSON);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType(), true);
addSecurityAnnotations(methodCreator, resourceProperties);
addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import org.jboss.logging.Logger;

Expand All @@ -33,7 +34,11 @@
* A standard JAX-RS method implementor.
*/
public abstract class StandardMethodImplementor implements MethodImplementor {

private static final String OPENAPI_PACKAGE = "org.eclipse.microprofile.openapi.annotations";
private static final String OPENAPI_RESPONSE_ANNOTATION = OPENAPI_PACKAGE + ".responses.APIResponse";
private static final String OPENAPI_CONTENT_ANNOTATION = OPENAPI_PACKAGE + ".media.Content";
private static final String OPENAPI_SCHEMA_ANNOTATION = OPENAPI_PACKAGE + ".media.Schema";
private static final String SCHEMA_TYPE_ARRAY = "ARRAY";
private static final String ROLES_ALLOWED_ANNOTATION = "javax.annotation.security.RolesAllowed";
private static final Logger LOGGER = Logger.getLogger(StandardMethodImplementor.class);

Expand Down Expand Up @@ -156,6 +161,43 @@ protected void addSecurityAnnotations(AnnotatedElement element, ResourceProperti
}
}

protected void addOpenApiResponseAnnotation(AnnotatedElement element, Response.Status status) {
if (capabilities.isPresent(Capability.SMALLRYE_OPENAPI)) {
element.addAnnotation(OPENAPI_RESPONSE_ANNOTATION)
.add("responseCode", String.valueOf(status.getStatusCode()));
}
}

protected void addOpenApiResponseAnnotation(AnnotatedElement element, Response.Status status, String entityType) {
addOpenApiResponseAnnotation(element, status, entityType, false);
}

protected void addOpenApiResponseAnnotation(AnnotatedElement element, Response.Status status, String entityType,
boolean isList) {
if (capabilities.isPresent(Capability.SMALLRYE_OPENAPI)) {
addOpenApiResponseAnnotation(element, status, toClass(entityType), isList);
}
}

protected void addOpenApiResponseAnnotation(AnnotatedElement element, Response.Status status, Class<?> clazz,
boolean isList) {
if (capabilities.isPresent(Capability.SMALLRYE_OPENAPI)) {
AnnotationCreator schemaAnnotation = AnnotationCreator.of(OPENAPI_SCHEMA_ANNOTATION)
.add("implementation", clazz);

if (isList) {
schemaAnnotation.add("type", SCHEMA_TYPE_ARRAY);
}

element.addAnnotation(OPENAPI_RESPONSE_ANNOTATION)
.add("responseCode", String.valueOf(status.getStatusCode()))
.add("content", new Object[] { AnnotationCreator.of(OPENAPI_CONTENT_ANNOTATION)
.add("mediaType", APPLICATION_JSON)
.add("schema", schemaAnnotation)
});
}
}

protected String appendToPath(String path, String suffix) {
if (path.endsWith("/")) {
path = path.substring(0, path.lastIndexOf("/"));
Expand All @@ -181,4 +223,13 @@ protected boolean isResteasyClassic() {
protected boolean isNotReactivePanache() {
return !capabilities.isPresent(Capability.HIBERNATE_REACTIVE);
}

private static Class<?> toClass(String className) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("The class (" + className + ") cannot be found during deployment.", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addConsumesAnnotation(methodCreator, APPLICATION_JSON);
addProducesJsonAnnotation(methodCreator, resourceProperties);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addOpenApiResponseAnnotation(methodCreator, Response.Status.CREATED, resourceMetadata.getEntityType());
addSecurityAnnotations(methodCreator, resourceProperties);
// Add parameter annotations
if (hasValidatorCapability()) {
Expand Down