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

OpenAPI enhancement #25800

Merged
merged 2 commits into from
May 30, 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
Expand Up @@ -71,6 +71,13 @@ public final class SmallRyeOpenApiConfig {
@ConfigItem(defaultValue = "true")
public boolean autoAddTags;

/**
* Setting it to `true` will automatically add a default server to the schema if none is provided,
* using the current running server host and port.
*/
@ConfigItem
public Optional<Boolean> autoAddServer;

/**
* This will automatically add security based on the security extension included (if any).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@
import io.quarkus.security.Authenticated;
import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig;
import io.quarkus.smallrye.openapi.deployment.filter.AutoRolesAllowedFilter;
import io.quarkus.smallrye.openapi.deployment.filter.AutoServerFilter;
import io.quarkus.smallrye.openapi.deployment.filter.AutoTagFilter;
import io.quarkus.smallrye.openapi.deployment.filter.SecurityConfigFilter;
import io.quarkus.smallrye.openapi.deployment.spi.AddToOpenAPIDefinitionBuildItem;
import io.quarkus.smallrye.openapi.deployment.spi.IgnoreStaticDocumentBuildItem;
import io.quarkus.smallrye.openapi.deployment.spi.OpenApiDocumentBuildItem;
import io.quarkus.smallrye.openapi.runtime.OpenApiConstants;
import io.quarkus.smallrye.openapi.runtime.OpenApiDocumentService;
import io.quarkus.smallrye.openapi.runtime.OpenApiRecorder;
Expand Down Expand Up @@ -289,7 +291,7 @@ OpenApiFilteredIndexViewBuildItem smallryeOpenApiIndex(CombinedIndexBuildItem co
}

@BuildStep
void addSecurityFilter(BuildProducer<AddToOpenAPIDefinitionBuildItem> addToOpenAPIDefinitionProducer,
void addAutoFilters(BuildProducer<AddToOpenAPIDefinitionBuildItem> addToOpenAPIDefinitionProducer,
OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem,
SmallRyeOpenApiConfig config) {

Expand Down Expand Up @@ -342,6 +344,11 @@ void addSecurityFilter(BuildProducer<AddToOpenAPIDefinitionBuildItem> addToOpenA
addToOpenAPIDefinitionProducer.produce(new AddToOpenAPIDefinitionBuildItem(autoTagFilter));
}

// Add Auto Server based on the current server details
OASFilter autoServerFilter = getAutoServerFilter(config, false);
if (autoServerFilter != null) {
addToOpenAPIDefinitionProducer.produce(new AddToOpenAPIDefinitionBuildItem(autoServerFilter));
}
}

private OASFilter getAutoSecurityFilter(List<SecurityInformationBuildItem> securityInformationBuildItems,
Expand Down Expand Up @@ -439,6 +446,27 @@ private OASFilter getAutoTagFilter(OpenApiFilteredIndexViewBuildItem apiFiltered
return null;
}

private OASFilter getAutoServerFilter(SmallRyeOpenApiConfig config, boolean defaultFlag) {
if (config.autoAddServer.orElse(defaultFlag)) {
Config c = ConfigProvider.getConfig();

String scheme = "http";
String host = c.getOptionalValue("quarkus.http.host", String.class).orElse("0.0.0.0");
int port;

String insecure = c.getOptionalValue("quarkus.http.insecure-requests", String.class).orElse("enabled");
if (insecure.equalsIgnoreCase("enabled")) {
port = c.getOptionalValue("quarkus.http.port", Integer.class).orElse(8080);
} else {
scheme = "https";
port = c.getOptionalValue("quarkus.http.ssl-port", Integer.class).orElse(8443);
}

return new AutoServerFilter(scheme, host, port);
}
return null;
}

private Map<String, List<String>> getRolesAllowedMethodReferences(
OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem) {
List<AnnotationInstance> rolesAllowedAnnotations = new ArrayList<>();
Expand Down Expand Up @@ -650,6 +678,7 @@ private void registerReflectionForApiResponseSchemaSerialization(BuildProducer<R
public void build(BuildProducer<FeatureBuildItem> feature,
BuildProducer<GeneratedResourceBuildItem> resourceBuildItemBuildProducer,
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
BuildProducer<OpenApiDocumentBuildItem> openApiDocumentProducer,
OpenApiFilteredIndexViewBuildItem openApiFilteredIndexViewBuildItem,
Capabilities capabilities,
List<AddToOpenAPIDefinitionBuildItem> openAPIBuildItems,
Expand Down Expand Up @@ -686,11 +715,9 @@ public void build(BuildProducer<FeatureBuildItem> feature,
nativeImageResources.produce(new NativeImageResourceBuildItem(name));
}

// Store the document if needed
boolean shouldStore = openApiConfig.storeSchemaDirectory.isPresent();
if (shouldStore) {
storeDocument(out, openApiConfig, staticModel, annotationModel, openAPIBuildItems);
}
OpenApiDocument finalStoredOpenApiDocument = storeDocument(out, openApiConfig, staticModel, annotationModel,
openAPIBuildItems);
openApiDocumentProducer.produce(new OpenApiDocumentBuildItem(finalStoredOpenApiDocument));
}

@BuildStep
Expand Down Expand Up @@ -964,7 +991,7 @@ private OpenApiDocument loadDocument(OpenAPI staticModel, OpenAPI annotationMode
return document;
}

private void storeDocument(OutputTargetBuildItem out,
private OpenApiDocument storeDocument(OutputTargetBuildItem out,
SmallRyeOpenApiConfig smallRyeOpenApiConfig,
OpenAPI staticModel,
OpenAPI annotationModel,
Expand All @@ -976,14 +1003,24 @@ private void storeDocument(OutputTargetBuildItem out,
OpenApiDocument document = prepareOpenApiDocument(staticModel, annotationModel, openAPIBuildItems);

document.filter(filter(openApiConfig)); // This usually happens at runtime, so when storing we want to filter here too.
// By default also add the auto generated server
OASFilter autoServerFilter = getAutoServerFilter(smallRyeOpenApiConfig, true);
if (autoServerFilter != null) {
document.filter(autoServerFilter);
}
document.initialize();

for (Format format : Format.values()) {
String name = OpenApiConstants.BASE_NAME + format;
byte[] schemaDocument = OpenApiSerializer.serialize(document.get(), format).getBytes(StandardCharsets.UTF_8);
storeGeneratedSchema(smallRyeOpenApiConfig, out, schemaDocument, format);
// Store the document if needed
boolean shouldStore = smallRyeOpenApiConfig.storeSchemaDirectory.isPresent();
if (shouldStore) {
for (Format format : Format.values()) {
String name = OpenApiConstants.BASE_NAME + format;
byte[] schemaDocument = OpenApiSerializer.serialize(document.get(), format).getBytes(StandardCharsets.UTF_8);
storeGeneratedSchema(smallRyeOpenApiConfig, out, schemaDocument, format);
}
}

return document;
}

private OpenApiDocument prepareOpenApiDocument(OpenAPI staticModel,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.quarkus.smallrye.openapi.deployment.filter;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.eclipse.microprofile.openapi.models.servers.Server;

import io.smallrye.openapi.api.models.servers.ServerImpl;

/**
* Automatically add default server if none is provided
*/
public class AutoServerFilter implements OASFilter {

private static final String DESCRIPTION = "Auto generated value";
private static final String HTTP = "http";
private static final String ZEROS = "0.0.0.0";
private static final String LOCALHOST = "localhost";
private static final String URL_PATTERN = "%s://%s:%d";

private final String defaultScheme;
private final String defaultHost;
private final int defaultPort;

public AutoServerFilter(String defaultScheme, String defaultHost, int defaultPort) {
if (defaultScheme == null) {
defaultScheme = HTTP;
}
if (defaultHost == null) {
defaultHost = ZEROS;
}
this.defaultScheme = defaultScheme;
this.defaultHost = defaultHost;
this.defaultPort = defaultPort;
}

@Override
public void filterOpenAPI(OpenAPI openAPI) {

List<Server> servers = openAPI.getServers();
if (servers == null || servers.isEmpty()) {
servers = new ArrayList<>();

// In case of 0.0.0.0, also add localhost
if (this.defaultHost.equals(ZEROS)) {
ServerImpl localhost = new ServerImpl();
localhost.setUrl(getUrl(this.defaultScheme, LOCALHOST, this.defaultPort));
localhost.setDescription(DESCRIPTION);
servers.add(localhost);
}

ServerImpl serverImpl = new ServerImpl();
serverImpl.setUrl(getUrl(this.defaultScheme, this.defaultHost, this.defaultPort));
serverImpl.setDescription(DESCRIPTION);
servers.add(serverImpl);

openAPI.setServers(servers);
}
}

private String getUrl(String scheme, String host, int port) {
return String.format(URL_PATTERN, scheme, host, port);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.smallrye.openapi.deployment.spi;

import io.quarkus.builder.item.SimpleBuildItem;
import io.smallrye.openapi.api.OpenApiDocument;

/**
* The final OpenAPI Document as generated by the Extension.
*/
public final class OpenApiDocumentBuildItem extends SimpleBuildItem {

private final OpenApiDocument openApiDocument;

public OpenApiDocumentBuildItem(OpenApiDocument openApiDocument) {
this.openApiDocument = openApiDocument;
}

public OpenApiDocument getOpenApiDocument() {
return openApiDocument;
}
}