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 61e0dcd882917..5879bd62994f9 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 @@ -1,5 +1,8 @@ package io.quarkus.smallrye.openapi.common.deployment; +import java.nio.file.Path; +import java.util.Optional; + import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; @@ -10,4 +13,11 @@ public final class SmallRyeOpenApiConfig { */ @ConfigItem(defaultValue = "/openapi") public String path; + + /** + * If set, the generated OpenAPI schema documents will be stored here on build. + * Both openapi.json and openapi.yaml will be stored here if this is set. + */ + @ConfigItem + public Optional storeSchemaDirectory; } 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 5c799dbe44dcf..cde038a1c9933 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 @@ -5,6 +5,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -29,6 +31,7 @@ import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.jandex.Type; +import org.jboss.logging.Logger; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; @@ -81,6 +84,8 @@ */ public class SmallRyeOpenApiProcessor { + private static final Logger log = Logger.getLogger("io.quarkus.smallrye.openapi"); + private static final String META_INF_OPENAPI_YAML = "META-INF/openapi.yaml"; private static final String WEB_INF_CLASSES_META_INF_OPENAPI_YAML = "WEB-INF/classes/META-INF/openapi.yaml"; private static final String META_INF_OPENAPI_YML = "META-INF/openapi.yml"; @@ -103,7 +108,7 @@ public class SmallRyeOpenApiProcessor { private static final String SPRING = "Spring"; private static final String VERT_X = "Vert.x"; - SmallRyeOpenApiConfig openapi; + SmallRyeOpenApiConfig openApiConfig; @BuildStep void contributeClassesToIndex(BuildProducer additionalIndexedClasses) { @@ -143,9 +148,9 @@ RouteBuildItem handler(LaunchModeBuildItem launch, */ if (launch.getLaunchMode() == LaunchMode.DEVELOPMENT) { recorder.setupClDevMode(shutdownContext); - displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(openapi.path)); + displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(openApiConfig.path)); } - return new RouteBuildItem(openapi.path, new OpenApiHandler(), HandlerType.BLOCKING); + return new RouteBuildItem(openApiConfig.path, new OpenApiHandler(), HandlerType.BLOCKING); } @BuildStep @@ -317,11 +322,16 @@ public void build(ApplicationArchivesBuildItem archivesBuildItem, annotationModel = null; } OpenApiDocument finalDocument = loadDocument(staticModel, annotationModel); + boolean shouldStore = openApiConfig.storeSchemaDirectory.isPresent(); for (Format format : Format.values()) { String name = OpenApiHandler.BASE_NAME + format; - resourceBuildItemBuildProducer.produce(new GeneratedResourceBuildItem(name, - OpenApiSerializer.serialize(finalDocument.get(), format).getBytes(StandardCharsets.UTF_8))); + byte[] schemaDocument = OpenApiSerializer.serialize(finalDocument.get(), format).getBytes(StandardCharsets.UTF_8); + resourceBuildItemBuildProducer.produce(new GeneratedResourceBuildItem(name, schemaDocument)); nativeImageResources.produce(new NativeImageResourceBuildItem(name)); + + if (shouldStore) { + storeGeneratedSchema(schemaDocument, format); + } } } @@ -331,6 +341,21 @@ LogCleanupFilterBuildItem logCleanup() { "OpenAPI document initialized:"); } + private void storeGeneratedSchema(byte[] schemaDocument, Format format) throws IOException { + Path directory = openApiConfig.storeSchemaDirectory.get(); + if (!Files.exists(directory)) { + Files.createDirectories(directory); + } + + Path file = Paths.get(directory.toString(), "openapi." + format.toString().toLowerCase()); + if (!Files.exists(file)) { + Files.createFile(file); + } + Files.write(file, schemaDocument, StandardOpenOption.WRITE); + + log.info("OpenAPI " + format.toString() + " saved: " + file.toString()); + } + private boolean shouldScanAnnotations(Capabilities capabilities, IndexView index) { // Disabled via config Config config = ConfigProvider.getConfig(); diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiStoreSchemaTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiStoreSchemaTestCase.java new file mode 100644 index 0000000000000..2c66b6bab3e80 --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiStoreSchemaTestCase.java @@ -0,0 +1,36 @@ +package io.quarkus.smallrye.openapi.test.jaxrs; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.openapi.runtime.io.Format; + +public class OpenApiStoreSchemaTestCase { + + private static String directory = "target/generated/jax-rs/"; + private static final String OPEN_API_DOT = "openapi."; + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(OpenApiResource.class, ResourceBean.class) + .addAsResource(new StringAsset("quarkus.smallrye-openapi.store-schema-directory=" + directory), + "application.properties")); + + @Test + public void testOpenApiPathAccessResource() { + Path json = Paths.get(directory, OPEN_API_DOT + Format.JSON.toString().toLowerCase()); + Assertions.assertTrue(Files.exists(json)); + Path yaml = Paths.get(directory, OPEN_API_DOT + Format.YAML.toString().toLowerCase()); + Assertions.assertTrue(Files.exists(yaml)); + } +} diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiStoreSchemaTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiStoreSchemaTestCase.java new file mode 100644 index 0000000000000..9e3c3eb476c37 --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiStoreSchemaTestCase.java @@ -0,0 +1,36 @@ +package io.quarkus.smallrye.openapi.test.spring; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.openapi.runtime.io.Format; + +public class OpenApiStoreSchemaTestCase { + + private static String directory = "target/generated/spring/"; + private static final String OPEN_API_DOT = "openapi."; + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(OpenApiController.class) + .addAsResource(new StringAsset("quarkus.smallrye-openapi.store-schema-directory=" + directory), + "application.properties")); + + @Test + public void testOpenApiPathAccessResource() { + Path json = Paths.get(directory, OPEN_API_DOT + Format.JSON.toString().toLowerCase()); + Assertions.assertTrue(Files.exists(json)); + Path yaml = Paths.get(directory, OPEN_API_DOT + Format.YAML.toString().toLowerCase()); + Assertions.assertTrue(Files.exists(yaml)); + } +} diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiStoreSchemaTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiStoreSchemaTestCase.java new file mode 100644 index 0000000000000..470e21cf13921 --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiStoreSchemaTestCase.java @@ -0,0 +1,36 @@ +package io.quarkus.smallrye.openapi.test.vertx; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.openapi.runtime.io.Format; + +public class OpenApiStoreSchemaTestCase { + + private static String directory = "target/generated/vertx/"; + private static final String OPEN_API_DOT = "openapi."; + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(OpenApiRoute.class) + .addAsResource(new StringAsset("quarkus.smallrye-openapi.store-schema-directory=" + directory), + "application.properties")); + + @Test + public void testOpenApiPathAccessResource() { + Path json = Paths.get(directory, OPEN_API_DOT + Format.JSON.toString().toLowerCase()); + Assertions.assertTrue(Files.exists(json)); + Path yaml = Paths.get(directory, OPEN_API_DOT + Format.YAML.toString().toLowerCase()); + Assertions.assertTrue(Files.exists(yaml)); + } +}