diff --git a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java index e2ac9f9500581..a2e2df3b3d981 100644 --- a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java +++ b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java @@ -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 autoAddServer; + /** * This will automatically add security based on the security extension included (if any). */ diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index c87ed39582d9f..e0e9d402a62d1 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -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; @@ -289,7 +291,7 @@ OpenApiFilteredIndexViewBuildItem smallryeOpenApiIndex(CombinedIndexBuildItem co } @BuildStep - void addSecurityFilter(BuildProducer addToOpenAPIDefinitionProducer, + void addAutoFilters(BuildProducer addToOpenAPIDefinitionProducer, OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem, SmallRyeOpenApiConfig config) { @@ -342,6 +344,11 @@ void addSecurityFilter(BuildProducer 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 securityInformationBuildItems, @@ -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> getRolesAllowedMethodReferences( OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem) { List rolesAllowedAnnotations = new ArrayList<>(); @@ -650,6 +678,7 @@ private void registerReflectionForApiResponseSchemaSerialization(BuildProducer feature, BuildProducer resourceBuildItemBuildProducer, BuildProducer nativeImageResources, + BuildProducer openApiDocumentProducer, OpenApiFilteredIndexViewBuildItem openApiFilteredIndexViewBuildItem, Capabilities capabilities, List openAPIBuildItems, @@ -686,11 +715,9 @@ public void build(BuildProducer 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 @@ -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, @@ -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, diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/AutoServerFilter.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/AutoServerFilter.java new file mode 100644 index 0000000000000..e7068f72a6e26 --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/AutoServerFilter.java @@ -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 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); + } +} diff --git a/extensions/smallrye-openapi/spi/src/main/java/io/quarkus/smallrye/openapi/deployment/spi/OpenApiDocumentBuildItem.java b/extensions/smallrye-openapi/spi/src/main/java/io/quarkus/smallrye/openapi/deployment/spi/OpenApiDocumentBuildItem.java new file mode 100644 index 0000000000000..9fd7fb0f235db --- /dev/null +++ b/extensions/smallrye-openapi/spi/src/main/java/io/quarkus/smallrye/openapi/deployment/spi/OpenApiDocumentBuildItem.java @@ -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; + } +}