diff --git a/README.md b/README.md index 07dc0ca..1d31b03 100644 --- a/README.md +++ b/README.md @@ -504,7 +504,31 @@ openApiConfigure { For generating the OpenAPI document the task `clfGenerateOpenApiDocumentation` has to be run. -To provide custom configuration just add the openApi extension configuration block. +### Grouped Apis + +In case you are using [grouped api configuration](https://springdoc.org/faq.html#_how_can_i_define_multiple_openapi_definitions_in_one_spring_boot_project) in your project, instead of something like this (as described in [springdoc gradle plugin](https://github.com/springdoc/springdoc-openapi-gradle-plugin#customization)) + +```groovy +openApi { + groupedApiMappings = [ + "http://localhost:8080/v3/api-docs/groupA": "groupA.yaml", + "http://localhost:8080/v3/api-docs/groupB": "groupB.yaml" + ] +} +``` + +you have to use the configuration provided by this plugin, since the port of the spring application will be randomly selected + +```groovy +openApiConfigure { + groupedApiMappings = [ + "/v3/api-docs/groupA": "groupA.yaml", + "/v3/api-docs/groupB": "groupB.yaml" + ] +} +``` + +To provide other custom configuration just add the openApi extension configuration block. See https://github.com/springdoc/springdoc-openapi-gradle-plugin#customization. ```groovy diff --git a/build.gradle.kts b/build.gradle.kts index bbca53b..f2b3e9a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.gradle.plugin-publish") version "1.1.0" } -description = "A opinionated approach to configure a gradle project automatically by convention. It supports to automatically configure various plugins to reduce boilerplate code in gradle projects." +description = "An opinionated approach to configure a gradle project automatically by convention. It supports to automatically configure various plugins to reduce boilerplate code in gradle projects." group = "io.cloudflight.gradle" autoConfigure { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 33b625f..f63d5fb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ swagger-codegen-plugin = { module = "gradle.plugin.org.hidetake:gradle-swagger-g # we need to manually upgrade here in order to have the library compatible with jackson swagger-jersey2-jaxrs = { module = "io.swagger:swagger-jersey2-jaxrs", version = { strictly = "1.6.2" } } -springdoc-openapi-plugin = { module = "org.springdoc:springdoc-openapi-gradle-plugin", version = "1.7.0" } +springdoc-openapi-plugin = { module = "org.springdoc:springdoc-openapi-gradle-plugin", version = "1.8.0" } exec-fork-plugin = { module = "com.github.psxpaul:gradle-execfork-plugin", version = "0.2.0" } # we need to manually inject this version because of https://github.com/ronmamo/reflections/issues/273 (we would transitively pull 0.9.2 otherwise) diff --git a/src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigureExtension.kt b/src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigureExtension.kt index 79e1911..0ea4422 100644 --- a/src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigureExtension.kt +++ b/src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigureExtension.kt @@ -1,5 +1,6 @@ package io.cloudflight.gradle.autoconfigure.springdoc.openapi +import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property enum class OpenApiFormat(val extension: String) { @@ -9,4 +10,5 @@ enum class OpenApiFormat(val extension: String) { abstract class SpringDocOpenApiConfigureExtension { abstract val fileFormat: Property + abstract val groupedApiMappings: MapProperty } diff --git a/src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePlugin.kt b/src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePlugin.kt index ce8fbcd..0f72aee 100644 --- a/src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePlugin.kt +++ b/src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePlugin.kt @@ -27,11 +27,11 @@ class SpringDocOpenApiConfigurePlugin : Plugin { target.plugins.apply(OpenApiGradlePlugin::class.java) val extension = target.extensions.create(EXTENSION_NAME, SpringDocOpenApiConfigureExtension::class.java) - extension.fileFormat.convention(OpenApiFormat.YAML); + extension.fileFormat.convention(OpenApiFormat.YAML) + extension.groupedApiMappings.convention(emptyMap()) val openapi = target.extensions.getByType(OpenApiExtension::class.java) configureOpenApiExtension(openapi, extension, target, target.name) val openApiTask = target.tasks.named("generateOpenApiDocs", OpenApiGeneratorTask::class) - makeOpenApiTaskReactive(openApiTask, openapi) val documentationTask = target.tasks.register("clfGenerateOpenApiDocumentation") { it.group = TASK_GROUP @@ -49,23 +49,6 @@ class SpringDocOpenApiConfigurePlugin : Plugin { } } - private fun makeOpenApiTaskReactive(openApiTask: TaskProvider, openapi: OpenApiExtension) { - // for some reason the springdoc plugin reads the values from the extension during task initialization and uses that as convention for the task properties, - // which results in some values being incorrect. Because of that we reconfigure the properties conventions to directly use the extension properties. And - // add conventions to the extension properties to the expected default values see: https://github.com/springdoc/springdoc-openapi-gradle-plugin/blob/master/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt#L46 - openApiTask.configure { - it.apiDocsUrl.convention(openapi.apiDocsUrl) - it.outputFileName.convention(openapi.outputFileName) - it.groupedApiMappings.convention(openapi.groupedApiMappings) - it.outputDir.convention(openapi.outputDir) - } - - openapi.apiDocsUrl.convention("http://localhost:8080/v3/api-docs") - openapi.outputFileName.convention("openapi.json") - openapi.groupedApiMappings.convention(emptyMap()) - openapi.outputDir.convention(openApiTask.flatMap { it.project.layout.buildDirectory }) - } - private fun `setupWorkaroundFor#171`(target: Project, openapi: OpenApiExtension) { val forkedSpringBootRun = target.tasks.named("forkedSpringBootRun", JavaExecFork::class) @@ -102,8 +85,9 @@ class SpringDocOpenApiConfigurePlugin : Plugin { val serverPort = freeServerSocketPort() val managementPort = freeServerSocketPort() val outputFileName = configureExtension.fileFormat.map { "${basename}.${it.extension}" } + val urlPrefix = "http://localhost:${serverPort}" val docsUrl = openapi.outputFileName.map { - val basePath = "http://localhost:${serverPort}/v3/api-docs" + val basePath = "$urlPrefix/v3/api-docs" when { it.endsWith(".${OpenApiFormat.JSON.extension}") -> basePath it.endsWith(".${OpenApiFormat.YAML.extension}") -> "${basePath}.${OpenApiFormat.YAML.extension}" @@ -111,6 +95,12 @@ class SpringDocOpenApiConfigurePlugin : Plugin { } } + openapi.groupedApiMappings.set( + configureExtension.groupedApiMappings.map { actualMap -> + actualMap.mapKeys { "$urlPrefix${it.key}" } + } + ) + openapi.outputDir.set(target.layout.buildDirectory.dir("generated/resources/openapi")) openapi.outputFileName.set(outputFileName) openapi.apiDocsUrl.set(docsUrl) diff --git a/src/test/fixtures/springdocopenapi/grouped-api/.gitignore b/src/test/fixtures/springdocopenapi/grouped-api/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/src/test/fixtures/springdocopenapi/grouped-api/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/src/test/fixtures/springdocopenapi/grouped-api/build.gradle b/src/test/fixtures/springdocopenapi/grouped-api/build.gradle new file mode 100644 index 0000000..0ec9519 --- /dev/null +++ b/src/test/fixtures/springdocopenapi/grouped-api/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'io.cloudflight.autoconfigure.springdoc-openapi-configure' +} + +group = 'io.cloudflight.gradle' +version = '1.0.0' + +repositories { + mavenCentral() +} + +openApiConfigure { + groupedApiMappings = [ + "/v3/api-docs/groupA": "groupA.yaml", + "/v3/api-docs/groupB": "groupB.yaml" + ] +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web:2.7.12' + implementation 'org.springdoc:springdoc-openapi-ui:1.6.0' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/src/test/fixtures/springdocopenapi/grouped-api/settings.gradle b/src/test/fixtures/springdocopenapi/grouped-api/settings.gradle new file mode 100644 index 0000000..34f2f54 --- /dev/null +++ b/src/test/fixtures/springdocopenapi/grouped-api/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'springdoc-openapi' diff --git a/src/test/fixtures/springdocopenapi/grouped-api/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java b/src/test/fixtures/springdocopenapi/grouped-api/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java new file mode 100644 index 0000000..c06e61a --- /dev/null +++ b/src/test/fixtures/springdocopenapi/grouped-api/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java @@ -0,0 +1,53 @@ +package io.cloudflight.springdocopenapi; + +import org.springdoc.core.GroupedOpenApi; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication +public class SpringDocOpenApiApplication { + public static void main(String[] args) { + SpringApplication.run(SpringDocOpenApiApplication.class, args); + } +} + +@RestController +class ControllerA { + @GetMapping("/a") + public String hello() { + return "hello"; + } +} + +@RestController +class ControllerB { + @GetMapping("/b") + public String hello() { + return "hello"; + } +} + +@Configuration +class SpringDocConfig { + @Bean + public GroupedOpenApi aApiGroup() { + String[] paths = {"/a"}; + return GroupedOpenApi.builder() + .group("groupA") + .pathsToMatch(paths) + .build(); + } + + @Bean + public GroupedOpenApi bApiGroup() { + String[] paths = {"/b"}; + return GroupedOpenApi.builder() + .group("groupB") + .pathsToMatch(paths) + .build(); + } +} \ No newline at end of file diff --git a/src/test/fixtures/springdocopenapi/simple-json/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java b/src/test/fixtures/springdocopenapi/simple-json/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java index 7b4c37e..82ee2c5 100644 --- a/src/test/fixtures/springdocopenapi/simple-json/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java +++ b/src/test/fixtures/springdocopenapi/simple-json/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java @@ -10,7 +10,6 @@ public class SpringDocOpenApiApplication { public static void main(String[] args) { SpringApplication.run(SpringDocOpenApiApplication.class, args); } - } @RestController diff --git a/src/test/fixtures/springdocopenapi/simple/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java b/src/test/fixtures/springdocopenapi/simple/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java index 7b4c37e..82ee2c5 100644 --- a/src/test/fixtures/springdocopenapi/simple/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java +++ b/src/test/fixtures/springdocopenapi/simple/src/main/java/io/cloudflight/springdocopenapi/SpringDocOpenApiApplication.java @@ -10,7 +10,6 @@ public class SpringDocOpenApiApplication { public static void main(String[] args) { SpringApplication.run(SpringDocOpenApiApplication.class, args); } - } @RestController diff --git a/src/test/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePluginTest.kt b/src/test/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePluginTest.kt index 183bcd7..627ed3c 100644 --- a/src/test/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePluginTest.kt +++ b/src/test/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePluginTest.kt @@ -1,7 +1,6 @@ package io.cloudflight.gradle.autoconfigure.springdoc.openapi import io.cloudflight.gradle.autoconfigure.test.util.ProjectFixture -import io.cloudflight.gradle.autoconfigure.test.util.normalizedOutput import io.cloudflight.gradle.autoconfigure.test.util.useFixture import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -12,7 +11,7 @@ class SpringDocOpenApiConfigurePluginTest { @Test fun `the openapi document is created in a single module project with the default configuration`(): Unit = springdocFixture("simple") { - val result = run("clean", "clfGenerateOpenApiDocumentation") + run("clean", "clfGenerateOpenApiDocumentation") assertThat(buildDir().resolve("generated/resources/openapi/springdoc-openapi.yaml")).exists() } @@ -20,15 +19,24 @@ class SpringDocOpenApiConfigurePluginTest { @Test fun `the openapi document is created in a single module project with the json configuration`(): Unit = springdocFixture("simple-json") { - val result = run("clean", "clfGenerateOpenApiDocumentation") + run("clean", "clfGenerateOpenApiDocumentation") assertThat(buildDir().resolve("generated/resources/openapi/springdoc-openapi.json")).exists() } + @Test + fun `the openapi documents are created in a single module project with grouped api configuration`(): + Unit = springdocFixture("grouped-api") { + run("clean", "clfGenerateOpenApiDocumentation") + + assertThat(buildDir().resolve("generated/resources/openapi/groupA.yaml")).exists() + assertThat(buildDir().resolve("generated/resources/openapi/groupB.yaml")).exists() + } + @Test fun `the openapi document is created in a multi module project`(): Unit = springdocFixture("kotlin-springboot-angular") { - val result = run("clean", "publishToMavenLocal") + run("clean", "publishToMavenLocal") assertThat(buildDir("skeleton-server").resolve("generated/resources/openapi/custom-openapi.json")).exists() }