diff --git a/README.md b/README.md index d6a7f13..4d316d8 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ openApi { outputFileName.set("swagger.json") waitTimeInSeconds.set(10) forkProperties.set("-Dspring.profiles.active=special") + groupedApiMappings.set(["https://localhost:8080/v3/api-docs/groupA" to "swagger-groupA.json", + "https://localhost:8080/v3/api-docs/groupB" to "swagger-groupB.json"]) } ``` @@ -89,6 +91,7 @@ Parameter | Description | Required | Default `outputFileName` | The name of the output file with extension | No | openapi.json `waitTimeInSeconds` | Time to wait in seconds for your Spring Boot application to start, before we make calls to `apiDocsUrl` to download the OpenAPI doc | No | 30 seconds `forkProperties` | Any system property that you would normal need to start your spring boot application. Can either be a static string or a java Properties object | No | "" +`groupedApiMappings` | A map of URLs (from where the OpenAPI docs can be downloaded) to output file names | No | [] ### Fork properties examples Fork properties allows you to send in anything that might be necessary to allow for the forked spring boot application that gets started @@ -113,6 +116,9 @@ openApi { } ``` +### Grouped API Mappings Notes +The `groupedApiMappings` customization allows you to specify multiple URLs/file names for use within this plugin. This configures the plugin to ignore the `apiDocsUrl` and `outputFileName` parameters and only use those found in `groupedApiMappings`. The plugin will then attempt to download each OpenAPI doc in turn as it would for a single OpenAPI doc. + # Building the plugin 1. Clone the repo `git@github.com:springdoc/springdoc-openapi-gradle-plugin.git` 2. Build and publish the plugin into your local maven repository by running the following diff --git a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt index 0ff1cc2..57521a4 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt @@ -2,6 +2,7 @@ package org.springdoc.openapi.gradle.plugin import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property import javax.inject.Inject @@ -11,4 +12,5 @@ open class OpenApiExtension @Inject constructor(project: Project) { val outputDir: DirectoryProperty = project.objects.directoryProperty() val waitTimeInSeconds: Property = project.objects.property(Int::class.java) val forkProperties: Property = project.objects.property(Any::class.java) + val groupedApiMappings: MapProperty = project.objects.mapProperty(String::class.java, String::class.java) } \ No newline at end of file diff --git a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt index 43517a7..a487c47 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt @@ -9,9 +9,9 @@ import org.awaitility.kotlin.* import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import java.net.ConnectException @@ -24,6 +24,8 @@ open class OpenApiGeneratorTask : DefaultTask() { val apiDocsUrl: Property = project.objects.property(String::class.java) @get:Input val outputFileName: Property = project.objects.property(String::class.java) + @get:Input + val groupedApiMappings: MapProperty = project.objects.mapProperty(String::class.java, String::class.java) @get:OutputDirectory val outputDir: DirectoryProperty = project.objects.directoryProperty() private val waitTimeInSeconds: Property = project.objects.property(Int::class.java) @@ -42,32 +44,41 @@ open class OpenApiGeneratorTask : DefaultTask() { apiDocsUrl.set(extension.apiDocsUrl.getOrElse(DEFAULT_API_DOCS_URL)) outputFileName.set(extension.outputFileName.getOrElse(DEFAULT_OPEN_API_FILE_NAME)) + groupedApiMappings.set(extension.groupedApiMappings.getOrElse(emptyMap())) outputDir.set(extension.outputDir.getOrElse(defaultOutputDir.get())) waitTimeInSeconds.set(extension.waitTimeInSeconds.getOrElse(DEFAULT_WAIT_TIME_IN_SECONDS)) } @TaskAction fun execute() { + if (groupedApiMappings.isPresent && groupedApiMappings.get().isNotEmpty()) { + groupedApiMappings.get().forEach(this::generateApiDocs) + } else { + generateApiDocs(apiDocsUrl.get(), outputFileName.get()) + } + } + + fun generateApiDocs(url: String, fileName: String) { try { await ignoreException ConnectException::class withPollInterval Durations.ONE_SECOND atMost Duration.of( waitTimeInSeconds.get().toLong(), SECONDS ) until { - val statusCode = khttp.get(apiDocsUrl.get()).statusCode - logger.trace("apiDocsUrl = {} status code = {}", apiDocsUrl.get(), statusCode) + val statusCode = khttp.get(url).statusCode + logger.trace("apiDocsUrl = {} status code = {}", url, statusCode) statusCode < 299 } logger.info("Generating OpenApi Docs..") - val response: Response = khttp.get(apiDocsUrl.get()) + val response: Response = khttp.get(url) - val isYaml = apiDocsUrl.get().toLowerCase().contains(".yaml") + val isYaml = url.toLowerCase().contains(".yaml") val apiDocs = if (isYaml) response.text else prettifyJson(response) - val outputFile = outputDir.file(outputFileName.get()).get().asFile + val outputFile = outputDir.file(fileName).get().asFile outputFile.writeText(apiDocs) } catch (e: ConditionTimeoutException) { - this.logger.error("Unable to connect to ${apiDocsUrl.get()} waited for ${waitTimeInSeconds.get()} seconds", e) - throw GradleException("Unable to connect to ${apiDocsUrl.get()} waited for ${waitTimeInSeconds.get()} seconds") + this.logger.error("Unable to connect to ${url} waited for ${waitTimeInSeconds.get()} seconds", e) + throw GradleException("Unable to connect to ${url} waited for ${waitTimeInSeconds.get()} seconds") } } diff --git a/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt b/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt index 5578cd5..e49440f 100644 --- a/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt +++ b/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt @@ -1,16 +1,14 @@ package org.springdoc.openapi.gradle.plugin import com.beust.klaxon.JsonObject -import com.beust.klaxon.Klaxon import com.beust.klaxon.Parser import org.gradle.internal.impldep.org.apache.commons.lang.RandomStringUtils import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.BuildTask import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome -import org.gradle.testkit.runner.internal.FeatureCheckBuildResult import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue +import org.junit.Assert.assertFalse import org.junit.Before import org.junit.Rule import org.junit.Test @@ -204,6 +202,68 @@ class OpenApiGradlePluginTest { assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 1) } + @Test + fun `using multiple grouped apis`() { + val outputJsonFileNameGroupA = "openapi-groupA.json" + val outputJsonFileNameGroupB = "openapi-groupB.json" + + buildFile.writeText("""$baseBuildGradle + openApi{ + groupedApiMappings = ["http://localhost:8080/v3/api-docs/groupA": "$outputJsonFileNameGroupA", + "http://localhost:8080/v3/api-docs/groupB": "$outputJsonFileNameGroupB"] + forkProperties = "-Dspring.profiles.active=multiple-grouped-apis" + } + """.trimMargin()) + + val result = GradleRunner.create() + .withProjectDir(projectTestDir) + .withArguments("clean", "generateOpenApiDocs") + .withPluginClasspath() + .build() + + assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome) + + val openApiJsonFileGroupA = File(projectBuildDir, outputJsonFileNameGroupA) + assertOpenApiJsonFileIsAsExpected(openApiJsonFileGroupA, 1) + + val openApiJsonFileGroupB = File(projectBuildDir, outputJsonFileNameGroupB) + assertOpenApiJsonFileIsAsExpected(openApiJsonFileGroupB, 2) + } + + @Test + fun `using multiple grouped apis should ignore single api properties`() { + val outputJsonFileNameSingleGroupA = "openapi-single-groupA.json" + val outputJsonFileNameGroupA = "openapi-groupA.json" + val outputJsonFileNameGroupB = "openapi-groupB.json" + + buildFile.writeText("""$baseBuildGradle + openApi{ + apiDocsUrl = "http://localhost:8080/v3/api-docs/groupA" + outputFileName = "$outputJsonFileNameSingleGroupA" + groupedApiMappings = ["http://localhost:8080/v3/api-docs/groupA": "$outputJsonFileNameGroupA", + "http://localhost:8080/v3/api-docs/groupB": "$outputJsonFileNameGroupB"] + forkProperties = "-Dspring.profiles.active=multiple-grouped-apis" + } + """.trimMargin()) + + val result = GradleRunner.create() + .withProjectDir(projectTestDir) + .withArguments("clean", "generateOpenApiDocs") + .withPluginClasspath() + .build() + + assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome) + + val openApiJsonFileSingleGroupA = File(projectBuildDir, outputJsonFileNameSingleGroupA) + assertFalse(openApiJsonFileSingleGroupA.exists()) + + val openApiJsonFileGroupA = File(projectBuildDir, outputJsonFileNameGroupA) + assertOpenApiJsonFileIsAsExpected(openApiJsonFileGroupA, 1) + + val openApiJsonFileGroupB = File(projectBuildDir, outputJsonFileNameGroupB) + assertOpenApiJsonFileIsAsExpected(openApiJsonFileGroupB, 2) + } + private fun assertOpenApiJsonFileIsAsExpected(openApiJsonFile: File, expectedNumberOfPaths: Int) { val openApiJson = getOpenApiJsonAtLocation(openApiJsonFile) assertEquals("3.0.1", openApiJson!!.string("openapi")) diff --git a/src/test/resources/acceptance-project/src/main/java/com/example/demo/config/GroupedConfiguration.java b/src/test/resources/acceptance-project/src/main/java/com/example/demo/config/GroupedConfiguration.java new file mode 100644 index 0000000..cc533e3 --- /dev/null +++ b/src/test/resources/acceptance-project/src/main/java/com/example/demo/config/GroupedConfiguration.java @@ -0,0 +1,27 @@ +package com.example.demo; + +import org.springdoc.core.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Profile("multiple-grouped-apis") +@Configuration +public class GroupedConfiguration { + + @Bean + public GroupedOpenApi groupA() { + return GroupedOpenApi.builder() + .group("groupA") + .pathsToMatch("/groupA/**") + .build(); + } + + @Bean + public GroupedOpenApi groupB() { + return GroupedOpenApi.builder() + .group("groupB") + .pathsToMatch("/groupB/**") + .build(); + } +} \ No newline at end of file diff --git a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/GroupedController.java b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/GroupedController.java new file mode 100644 index 0000000..c53bb44 --- /dev/null +++ b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/GroupedController.java @@ -0,0 +1,25 @@ +package com.example.demo.endpoints; + +import org.springframework.context.annotation.Profile; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@Profile("multiple-grouped-apis") +@RestController("/grouped") +public class GroupedController { + + @GetMapping("/groupA") + public String groupA(){ + return "groupA"; + } + + @GetMapping("/groupB/first") + public String groupB_first(){ + return "groupB_first"; + } + + @GetMapping("/groupB/second") + public String groupB_second(){ + return "groupB_second"; + } +} diff --git a/src/test/resources/acceptance-project/src/main/resources/application-multiple-grouped-apis.properties b/src/test/resources/acceptance-project/src/main/resources/application-multiple-grouped-apis.properties new file mode 100644 index 0000000..75757df --- /dev/null +++ b/src/test/resources/acceptance-project/src/main/resources/application-multiple-grouped-apis.properties @@ -0,0 +1 @@ +test.props=So very special \ No newline at end of file