From 06fa3535e3c4a42a2353a9e539d661ae12e476bc Mon Sep 17 00:00:00 2001 From: Badr NASS LAHSEN Date: Wed, 14 Dec 2022 19:58:14 +0100 Subject: [PATCH 01/18] Create main.yml --- main.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 main.yml diff --git a/main.yml b/main.yml new file mode 100644 index 0000000..4fdd333 --- /dev/null +++ b/main.yml @@ -0,0 +1,33 @@ +name: Test CI +on: [ push, fork ] + +jobs: + TEST_ALL: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '11', '17' ] + steps: + - name: Checkout + uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: 🪜 Setup java ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + distribution: temurin + + - name: 🦞 chmod /gradlew + run: chmod +x ./gradlew + + - name: 🔦 Test + run: ./gradlew test --info From 4a46f81f6398282f2d9741e6ef8c61b64224fb0e Mon Sep 17 00:00:00 2001 From: Badr NASS LAHSEN Date: Wed, 14 Dec 2022 20:56:07 +0100 Subject: [PATCH 02/18] Create gradle.yml --- .github/workflows/gradle.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..3dbc8dd --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,34 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: build From bb1a68af9598629716bca7a3fc3b1cc577bfd8b1 Mon Sep 17 00:00:00 2001 From: Badr NASS LAHSEN Date: Wed, 14 Dec 2022 20:59:04 +0100 Subject: [PATCH 03/18] Delete gradle.yml --- .github/workflows/gradle.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 3dbc8dd..0000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,34 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle - -name: Java CI with Gradle - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: '11' - distribution: 'temurin' - - name: Build with Gradle - uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 - with: - arguments: build From ee7d90a6e0dc1b3f259098822f8b96fb1970add1 Mon Sep 17 00:00:00 2001 From: Sergey Koroblyov Date: Thu, 19 Jan 2023 21:04:07 +0400 Subject: [PATCH 04/18] Fix JetBrains alt text and image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc5d941..bb05997 100644 --- a/README.md +++ b/README.md @@ -151,4 +151,4 @@ The `groupedApiMappings` customization allows you to specify multiple URLs/file * Thanks a lot [JetBrains](https://www.jetbrains.com/?from=springdoc-openapi) for supporting springdoc-openapi project. -![JenBrains logo](https://springdoc.org/images/jetbrains.svg) +![JetBrains logo](https://springdoc.org/img/jetbrains.svg) From b1c9f0a69f76b09fe48d5f7d47fe7254f6380f9a Mon Sep 17 00:00:00 2001 From: Clayton Walker Date: Wed, 1 Mar 2023 15:29:48 -0700 Subject: [PATCH 05/18] Fix a few gradle issues --- .../openapi/gradle/plugin/OpenApiExtension.kt | 40 +++++++++++-------- .../gradle/plugin/OpenApiGeneratorTask.kt | 14 +++---- .../gradle/plugin/OpenApiGradlePlugin.kt | 16 ++++---- 3 files changed, 39 insertions(+), 31 deletions(-) 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 95a587a..80d833a 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt @@ -1,35 +1,41 @@ package org.springdoc.openapi.gradle.plugin import org.gradle.api.Action -import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFileProperty +import org.gradle.api.file.ProjectLayout +import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property import javax.inject.Inject -open class OpenApiExtension @Inject constructor(project: Project) { - val apiDocsUrl: Property = project.objects.property(String::class.java) - val outputFileName: Property = project.objects.property(String::class.java) - val outputDir: DirectoryProperty = project.objects.directoryProperty() - val waitTimeInSeconds: Property = project.objects.property(Int::class.java) - val groupedApiMappings: MapProperty = project.objects.mapProperty(String::class.java, String::class.java) - val customBootRun: CustomBootRunAction = project.objects.newInstance(CustomBootRunAction::class.java, project) +open class OpenApiExtension @Inject constructor( + objects: ObjectFactory, +) { + val apiDocsUrl: Property = objects.property(String::class.java) + val outputFileName: Property = objects.property(String::class.java) + val outputDir: DirectoryProperty = objects.directoryProperty() + val waitTimeInSeconds: Property = objects.property(Int::class.java) + val groupedApiMappings: MapProperty = objects.mapProperty(String::class.java, String::class.java) + val customBootRun: CustomBootRunAction = objects.newInstance(CustomBootRunAction::class.java) fun customBootRun(action: Action) { action.execute(customBootRun) } } open class CustomBootRunAction @Inject constructor( - project: Project, + objects: ObjectFactory, + layout: ProjectLayout, ) { - val systemProperties: MapProperty = project.objects.mapProperty(String::class.java, Any::class.java) - val workingDir: RegularFileProperty = project.objects.fileProperty() - val mainClass: Property = project.objects.property(String::class.java) - val args: ListProperty = project.objects.listProperty(String::class.java) - val classpath: ConfigurableFileCollection = project.objects.fileCollection() - val jvmArgs: ListProperty = project.objects.listProperty(String::class.java) - val environment: MapProperty = project.objects.mapProperty(String::class.java, Any::class.java) + val systemProperties: MapProperty = objects.mapProperty(String::class.java, Any::class.java) + val workingDir: DirectoryProperty = objects.directoryProperty() + val mainClass: Property = objects.property(String::class.java) + val args: ListProperty = objects.listProperty(String::class.java) + val classpath: ConfigurableFileCollection = objects.fileCollection() + val jvmArgs: ListProperty = objects.listProperty(String::class.java) + val environment: MapProperty = objects.mapProperty(String::class.java, Any::class.java) + init { + workingDir.convention(layout.buildDirectory.dir("openapi")) + } } 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 957ff86..13d628e 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt @@ -40,17 +40,17 @@ open class OpenApiGeneratorTask : DefaultTask() { description = OPEN_API_TASK_DESCRIPTION group = GROUP_NAME // load my extensions - val extension: OpenApiExtension = project.extensions.run { getByName(EXTENSION_NAME) as OpenApiExtension } + val extension: OpenApiExtension = project.extensions.getByName(EXTENSION_NAME) as OpenApiExtension // set a default value if not provided val defaultOutputDir = project.objects.directoryProperty() - defaultOutputDir.set(project.buildDir) + defaultOutputDir.convention(project.layout.buildDirectory) - 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)) + apiDocsUrl.convention(extension.apiDocsUrl.getOrElse(DEFAULT_API_DOCS_URL)) + outputFileName.convention(extension.outputFileName.getOrElse(DEFAULT_OPEN_API_FILE_NAME)) + groupedApiMappings.convention(extension.groupedApiMappings.getOrElse(emptyMap())) + outputDir.convention(extension.outputDir.getOrElse(defaultOutputDir.get())) + waitTimeInSeconds.convention(extension.waitTimeInSeconds.getOrElse(DEFAULT_WAIT_TIME_IN_SECONDS)) } @TaskAction diff --git a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt index 5899e2c..b2e9fad 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt @@ -18,7 +18,9 @@ open class OpenApiGradlePlugin : Plugin { plugins.apply(SPRING_BOOT_PLUGIN) plugins.apply(EXEC_FORK_PLUGIN) - extensions.create(EXTENSION_NAME, OpenApiExtension::class.java, this) + extensions.create(EXTENSION_NAME, OpenApiExtension::class.java) + tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) + tasks.register(OPEN_API_TASK_NAME, OpenApiGeneratorTask::class.java) afterEvaluate { generate(this) } } @@ -30,24 +32,24 @@ open class OpenApiGradlePlugin : Plugin { // The task, used to run the Spring Boot application (`bootRun`) val bootRunTask = tasks.named(SPRING_BOOT_RUN_TASK_NAME) // The task, used to resolve the application's main class (`bootRunMainClassName`) - val bootRunMainClassNameTask = tasks.find { it.name == SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME} - ?:tasks.named(SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME) + val bootRunMainClassNameTask = tasks.find { it.name == SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME } + ?: tasks.named(SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME) val extension = extensions.findByName(EXTENSION_NAME) as OpenApiExtension val customBootRun = extension.customBootRun // Create a forked version spring boot run task - val forkedSpringBoot = tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) { fork -> + val forkedSpringBoot = tasks.named(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) { fork -> fork.dependsOn(bootRunMainClassNameTask) fork.onlyIf { needToFork(bootRunTask, customBootRun, fork) } } // This is my task. Before I can run it, I have to run the dependent tasks - tasks.register(OPEN_API_TASK_NAME, OpenApiGeneratorTask::class.java) { + val openApiTask = tasks.named(OPEN_API_TASK_NAME, OpenApiGeneratorTask::class.java) { it.dependsOn(forkedSpringBoot) } // The forked task need to be terminated as soon as my task is finished - forkedSpringBoot.get().stopAfter = tasks.named(OPEN_API_TASK_NAME) + forkedSpringBoot.get().stopAfter = openApiTask as TaskProvider } private fun Project.springBoot3CompatibilityCheck() { @@ -75,7 +77,7 @@ open class OpenApiGradlePlugin : Plugin { // use original bootRun parameter if the list-type customBootRun properties are empty workingDir = customBootRun.workingDir.asFile.orNull - ?: bootRun.workingDir + ?: fork.temporaryDir args = customBootRun.args.orNull?.takeIf { it.isNotEmpty() }?.toMutableList() ?: bootRun.args?.toMutableList() ?: mutableListOf() classpath = customBootRun.classpath.takeIf { !it.isEmpty } From c193d146160a4d2a87488f73f1b654ffda60c808 Mon Sep 17 00:00:00 2001 From: Clayton Walker Date: Wed, 1 Mar 2023 16:41:46 -0700 Subject: [PATCH 06/18] Move outputdir default to task --- .../org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt | 4 ---- .../springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) 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 80d833a..2046228 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt @@ -26,7 +26,6 @@ open class OpenApiExtension @Inject constructor( open class CustomBootRunAction @Inject constructor( objects: ObjectFactory, - layout: ProjectLayout, ) { val systemProperties: MapProperty = objects.mapProperty(String::class.java, Any::class.java) val workingDir: DirectoryProperty = objects.directoryProperty() @@ -35,7 +34,4 @@ open class CustomBootRunAction @Inject constructor( val classpath: ConfigurableFileCollection = objects.fileCollection() val jvmArgs: ListProperty = objects.listProperty(String::class.java) val environment: MapProperty = objects.mapProperty(String::class.java, Any::class.java) - init { - workingDir.convention(layout.buildDirectory.dir("openapi")) - } } 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 13d628e..22d0290 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt @@ -44,7 +44,7 @@ open class OpenApiGeneratorTask : DefaultTask() { // set a default value if not provided val defaultOutputDir = project.objects.directoryProperty() - defaultOutputDir.convention(project.layout.buildDirectory) + defaultOutputDir.convention(project.layout.buildDirectory.dir("openapi")) apiDocsUrl.convention(extension.apiDocsUrl.getOrElse(DEFAULT_API_DOCS_URL)) outputFileName.convention(extension.outputFileName.getOrElse(DEFAULT_OPEN_API_FILE_NAME)) From 67af4ef72feaf26b5d2f727511f451a960373088 Mon Sep 17 00:00:00 2001 From: Soonoh Jung Date: Thu, 6 Apr 2023 01:24:32 +0900 Subject: [PATCH 07/18] Show more informative error message on parsing error If the API documentation JSON file is incorrect, it only shows GSON's error message, which is quite difficult to understand at first. Here is the old error message: Caused by: com.google.gson.JsonSyntaxException: Expected a com.google.gson.JsonObject but was com.google.gson.JsonPrimitive --- .../gradle/plugin/OpenApiGeneratorTask.kt | 10 +++++-- .../gradle/plugin/OpenApiGradlePluginTest.kt | 27 +++++++++++++++++++ .../demo/endpoints/HelloWorldController.java | 4 +++ 3 files changed, 39 insertions(+), 2 deletions(-) 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 957ff86..52b1aaf 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt @@ -2,6 +2,7 @@ package org.springdoc.openapi.gradle.plugin import com.google.gson.GsonBuilder import com.google.gson.JsonObject +import com.google.gson.JsonSyntaxException import org.awaitility.Durations import org.awaitility.core.ConditionTimeoutException import org.awaitility.kotlin.* @@ -95,7 +96,12 @@ open class OpenApiGeneratorTask : DefaultTask() { private fun prettifyJson(response: String): String { val gson = GsonBuilder().setPrettyPrinting().create() - val googleJsonObject = gson.fromJson(response, JsonObject::class.java) - return gson.toJson(googleJsonObject) + try { + val googleJsonObject = gson.fromJson(response, JsonObject::class.java) + return gson.toJson(googleJsonObject) + } catch (e: RuntimeException) { + throw JsonSyntaxException("Failed to parse the API docs response string. " + + "Please ensure that the response is in the correct format. response=$response", e) + } } } 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 b1912a3..904984e 100644 --- a/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt +++ b/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt @@ -11,8 +11,11 @@ import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.slf4j.Logger +import org.slf4j.LoggerFactory import java.io.File import java.io.FileReader import java.nio.file.Files @@ -47,6 +50,10 @@ class OpenApiGradlePluginTest { } """.trimIndent() + companion object { + val logger: Logger = LoggerFactory.getLogger(OpenApiGradlePluginTest::class.java) + } + @BeforeEach fun createTemporaryAcceptanceProjectFromTemplate() { File(javaClass.classLoader.getResource("acceptance-project")!!.path).copyRecursively(projectTestDir) @@ -278,6 +285,26 @@ class OpenApiGradlePluginTest { assertOpenApiJsonFile(2, outputJsonFileNameGroupB) } + @Test + fun `using invalid doc url`() { + buildFile.writeText( + """$baseBuildGradle + openApi{ + apiDocsUrl = "http://localhost:8080/hello/world" + } + """.trimMargin() + ) + + try { + openApiDocsTask(runTheBuild()) + } catch (e: RuntimeException) { + logger.error(e.message) + assertNotNull(e.message?.lines()?.find { it.contains( + "Failed to parse the API docs response string. " + + "Please ensure that the response is in the correct format.") }) + } + } + private fun runTheBuild(vararg additionalArguments: String = emptyArray()) = GradleRunner.create() .withProjectDir(projectTestDir) .withArguments("clean", "generateOpenApiDocs", *additionalArguments) diff --git a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/HelloWorldController.java b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/HelloWorldController.java index 95c7d89..f53424e 100644 --- a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/HelloWorldController.java +++ b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/HelloWorldController.java @@ -1,12 +1,16 @@ package com.example.demo.endpoints; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController("/hello") +@RequestMapping("/hello") public class HelloWorldController { @GetMapping("/world") + @ResponseBody public String helloWorld() { return "Hello World!"; } From 4728413b7e1893b623bc813843dca9d05e30ece6 Mon Sep 17 00:00:00 2001 From: Badr NASS LAHSEN Date: Sat, 3 Jun 2023 15:02:04 +0200 Subject: [PATCH 08/18] Update CI/CD URL --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index fc5d941..37282fd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -[![Build Status](https://travis-ci.org/springdoc/springdoc-openapi-gradle-plugin.svg?branch=master)](https://travis-ci.org/springdoc/springdoc-openapi-gradle-plugin) -[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=springdoc_springdoc-openapi-gradle-plugin&metric=alert_status)](https://sonarcloud.io/dashboard?id=springdoc_springdoc-openapi-gradle-plugin) +[![Build Status](https://ci-cd.springdoc.org:8443/buildStatus/icon?job=springdoc-openapi-gradle-IC)](https://ci-cd.springdoc.org:8443/view/springdoc-openapi/job/springdoc-openapi-gradle-IC/) # Introducing springdoc-openapi-gradle-plugin From e9474b9469bf1dec43e0246e982630b6524e0db6 Mon Sep 17 00:00:00 2001 From: Martin Ahrer Date: Mon, 5 Jun 2023 09:59:02 +0200 Subject: [PATCH 09/18] Do not use afterEvaluate as this is not needed but causes side-effects with convention plugins. --- build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/plugin/OpenApiGradlePlugin.kt | 22 +++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3a9b4f2..d456295 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "org.springdoc" -version = "1.6.0" +version = "1.6.1-SNAPSHOT" sonarqube { properties { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643..070cb70 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt index 5899e2c..37e1b80 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt @@ -4,13 +4,12 @@ import com.github.psxpaul.task.JavaExecFork import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.logging.Logging +import org.gradle.api.UnknownDomainObjectException import org.gradle.api.tasks.TaskProvider import org.gradle.internal.jvm.Jvm import org.springframework.boot.gradle.tasks.run.BootRun open class OpenApiGradlePlugin : Plugin { - private val logger = Logging.getLogger(OpenApiGradlePlugin::class.java) override fun apply(project: Project) { with(project) { @@ -20,7 +19,7 @@ open class OpenApiGradlePlugin : Plugin { extensions.create(EXTENSION_NAME, OpenApiExtension::class.java, this) - afterEvaluate { generate(this) } + generate(this) } } @@ -30,14 +29,22 @@ open class OpenApiGradlePlugin : Plugin { // The task, used to run the Spring Boot application (`bootRun`) val bootRunTask = tasks.named(SPRING_BOOT_RUN_TASK_NAME) // The task, used to resolve the application's main class (`bootRunMainClassName`) - val bootRunMainClassNameTask = tasks.find { it.name == SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME} - ?:tasks.named(SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME) + val bootRunMainClassNameTask = + try { + val task=tasks.named(SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME) + logger.debug("Detected Spring Boot task {}", SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME) + task + } catch (e: UnknownDomainObjectException) { + val task=tasks.named(SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME) + logger.debug("Detected Spring Boot task {}", SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME) + task + } val extension = extensions.findByName(EXTENSION_NAME) as OpenApiExtension val customBootRun = extension.customBootRun // Create a forked version spring boot run task val forkedSpringBoot = tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) { fork -> - fork.dependsOn(bootRunMainClassNameTask) + fork.dependsOn(tasks.named(bootRunMainClassNameTask.name)) fork.onlyIf { needToFork(bootRunTask, customBootRun, fork) } } @@ -54,8 +61,9 @@ open class OpenApiGradlePlugin : Plugin { val tasksNames = tasks.names val boot2TaskName = "bootRunMainClassName" val boot3TaskName = "resolveMainClassName" - if (!tasksNames.contains(boot2TaskName) && tasksNames.contains(boot3TaskName)) + if (!tasksNames.contains(boot2TaskName) && tasksNames.contains(boot3TaskName)) { tasks.register(boot2TaskName) { it.dependsOn(tasks.named(boot3TaskName)) } + } } private fun needToFork( From f54749d54e5b74aa687586a3046c46b335f54a91 Mon Sep 17 00:00:00 2001 From: Martin Ahrer Date: Mon, 5 Jun 2023 13:52:02 +0200 Subject: [PATCH 10/18] Update Kotlin (as it wouldn't compile anymore with 1.4...) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index d456295..e18b894 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-gradle-plugin` id("com.gradle.plugin-publish") version "0.14.0" id("org.sonarqube") version "3.1.1" - kotlin("jvm") version "1.4.31" + kotlin("jvm") version "1.8.21" `maven-publish` id("com.github.ben-manes.versions") version "0.38.0" id("io.gitlab.arturbosch.detekt") version "1.16.0" From 798bac36a0ed20e78bfa341963abc4aa64e9d7c2 Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Tue, 18 Jul 2023 09:10:47 -0700 Subject: [PATCH 11/18] Update description for outputFileName parameter in Customizations This change will provide users with more clarity on how the outputFileName parameter works, making the tool easier to use and understand. It took me a lot of digging to understand that changing the extension for the `outputFileName` resulted in a different format for the output of the file. This will clarify it for future folks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37282fd..1ee3947 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ openApi { |----------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------|--------------------------------------| | `apiDocsUrl` | The URL from where the OpenAPI doc can be downloaded | No | http://localhost:8080/v3/api-docs | | `outputDir` | The output directory for the generated OpenAPI file | No | $buildDir - Your project's build dir | -| `outputFileName` | The name of the output file with extension | No | openapi.json | +| `outputFileName` | Specifies the output file name and format. Use extension `.yaml` for YAML output, `.json` for JSON output | 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 | | `groupedApiMappings` | A map of URLs (from where the OpenAPI docs can be downloaded) to output file names | No | [] | | `customBootRun` | Any bootRun property that you would normal need to start your spring boot application. | No | (N/A) | From a2281b0891e030ca6c197b5707d48b826435e5da Mon Sep 17 00:00:00 2001 From: "Badr.NassLahsen" Date: Tue, 15 Aug 2023 17:15:38 +0200 Subject: [PATCH 12/18] code review --- build.gradle.kts | 6 +++--- .../openapi/gradle/plugin/OpenApiGeneratorTask.kt | 3 ++- .../openapi/gradle/plugin/OpenApiGradlePlugin.kt | 9 +++------ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e18b894..7f4c18e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "org.springdoc" -version = "1.6.1-SNAPSHOT" +version = "1.7.0" sonarqube { properties { @@ -46,7 +46,7 @@ publishing { dependencies { implementation(kotlin("reflect")) - implementation("com.google.code.gson:gson:2.8.6") + implementation("com.google.code.gson:gson:2.8.9") implementation("org.awaitility:awaitility-kotlin:4.0.3") implementation("com.github.psxpaul:gradle-execfork-plugin:0.2.0") implementation("org.springframework.boot:spring-boot-gradle-plugin:2.5.6") @@ -55,7 +55,7 @@ dependencies { testImplementation(platform("org.junit:junit-bom:5.7.1")) testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("com.beust:klaxon:5.5") - testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.2") + testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2") testImplementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.2") detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.16.0") 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 2174ede..1d51797 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt @@ -19,6 +19,7 @@ import java.net.HttpURLConnection import java.net.URL import java.time.Duration import java.time.temporal.ChronoUnit.SECONDS +import java.util.* private const val MAX_HTTP_STATUS_CODE = 299 @@ -65,7 +66,7 @@ open class OpenApiGeneratorTask : DefaultTask() { private fun generateApiDocs(url: String, fileName: String) { try { - val isYaml = url.toLowerCase().matches(Regex(".+[./]yaml(/.+)*")) + val isYaml = url.lowercase(Locale.getDefault()).matches(Regex(".+[./]yaml(/.+)*")) await ignoreException ConnectException::class withPollInterval Durations.ONE_SECOND atMost Duration.of( waitTimeInSeconds.get().toLong(), SECONDS diff --git a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt index 51e5875..e1546eb 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt @@ -21,7 +21,7 @@ open class OpenApiGradlePlugin : Plugin { tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) tasks.register(OPEN_API_TASK_NAME, OpenApiGeneratorTask::class.java) - generate(this) + generate(this) } } @@ -31,7 +31,6 @@ open class OpenApiGradlePlugin : Plugin { // The task, used to run the Spring Boot application (`bootRun`) val bootRunTask = tasks.named(SPRING_BOOT_RUN_TASK_NAME) // The task, used to resolve the application's main class (`bootRunMainClassName`) - val bootRunMainClassNameTask = try { val task=tasks.named(SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME) @@ -46,8 +45,7 @@ open class OpenApiGradlePlugin : Plugin { val extension = extensions.findByName(EXTENSION_NAME) as OpenApiExtension val customBootRun = extension.customBootRun // Create a forked version spring boot run task - - val forkedSpringBoot = tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) { fork -> + val forkedSpringBoot = tasks.named(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) { fork -> fork.dependsOn(tasks.named(bootRunMainClassNameTask.name)) fork.onlyIf { needToFork(bootRunTask, customBootRun, fork) } } @@ -65,9 +63,8 @@ open class OpenApiGradlePlugin : Plugin { val tasksNames = tasks.names val boot2TaskName = "bootRunMainClassName" val boot3TaskName = "resolveMainClassName" - if (!tasksNames.contains(boot2TaskName) && tasksNames.contains(boot3TaskName)) { + if (!tasksNames.contains(boot2TaskName) && tasksNames.contains(boot3TaskName)) tasks.register(boot2TaskName) { it.dependsOn(tasks.named(boot3TaskName)) } - } } private fun needToFork( From fe91fb146b734d7496706d43e9d9f527b3dc8a0d Mon Sep 17 00:00:00 2001 From: "Badr.NassLahsen" Date: Tue, 15 Aug 2023 17:45:02 +0200 Subject: [PATCH 13/18] gradle upgrade to 8.2.1 --- build.gradle.kts | 15 ++++++--------- gradle/wrapper/gradle-wrapper.properties | 4 +++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7f4c18e..7e499a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { `java-gradle-plugin` - id("com.gradle.plugin-publish") version "0.14.0" + id("com.gradle.plugin-publish") version "1.2.0" id("org.sonarqube") version "3.1.1" kotlin("jvm") version "1.8.21" `maven-publish` @@ -62,27 +62,24 @@ dependencies { } gradlePlugin { + website = "https://github.com/springdoc/springdoc-openapi-gradle-plugin" + vcsUrl = "https://github.com/springdoc/springdoc-openapi-gradle-plugin.git" plugins { create("springdoc-gradle-plugin") { id = "org.springdoc.openapi-gradle-plugin" displayName = "A Gradle plugin for the springdoc-openapi library" description = " This plugin uses springdoc-openapi to generate an OpenAPI description at build time" implementationClass = "org.springdoc.openapi.gradle.plugin.OpenApiGradlePlugin" + tags = listOf("springdoc", "openapi", "swagger") } } } -pluginBundle { - website = "https://github.com/springdoc/springdoc-openapi-gradle-plugin" - vcsUrl = "https://github.com/springdoc/springdoc-openapi-gradle-plugin.git" - tags = listOf("springdoc", "openapi", "swagger") -} - val jvmVersion: JavaLanguageVersion = JavaLanguageVersion.of(8) tasks.withType { kotlinOptions { - jvmTarget = "1.${jvmVersion.toString()}" + jvmTarget = "1.$jvmVersion" } } @@ -96,5 +93,5 @@ detekt { parallel = true } tasks.withType().configureEach { - jvmTarget = "1.${jvmVersion.toString()}" + jvmTarget = "1.$jvmVersion" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb70..9f4197d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From ef3a150aefadb1327b8333231df07281b2d3ef37 Mon Sep 17 00:00:00 2001 From: "Badr.NassLahsen" Date: Tue, 15 Aug 2023 17:50:44 +0200 Subject: [PATCH 14/18] force jvmVersion to 1.8 --- build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 7e499a0..cf37482 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -77,6 +77,10 @@ gradlePlugin { val jvmVersion: JavaLanguageVersion = JavaLanguageVersion.of(8) +java { + toolchain.languageVersion.set(jvmVersion) +} + tasks.withType { kotlinOptions { jvmTarget = "1.$jvmVersion" From 5b8b97299e46c7d0484761e2c7790a4035269fc1 Mon Sep 17 00:00:00 2001 From: "Badr.NassLahsen" Date: Tue, 15 Aug 2023 17:57:30 +0200 Subject: [PATCH 15/18] code formatting review --- .github/workflows/master.yml | 2 +- .run/TEST.run.xml | 63 +-- CHANGELOG.md | 2 + CODE_OF_CONDUCT.adoc | 28 +- CONTRIBUTING.adoc | 128 +++--- README.md | 69 +++- build.gradle.kts | 19 +- main.yml | 2 +- settings.gradle.kts | 8 +- .../openapi/gradle/plugin/OpenApiExtension.kt | 42 +- .../gradle/plugin/OpenApiGeneratorTask.kt | 163 ++++---- .../gradle/plugin/OpenApiGradlePlugin.kt | 33 +- .../gradle/plugin/OpenApiGradlePluginTest.kt | 376 +++++++++--------- .../com/example/demo/DemoApplication.java | 37 +- .../demo/config/GroupedConfiguration.java | 29 +- .../demo/endpoints/ConditionalController.java | 10 +- .../demo/endpoints/GroupedController.java | 24 +- .../demo/endpoints/HelloWorldController.java | 10 +- .../demo/endpoints/ProfileController.java | 12 +- 19 files changed, 562 insertions(+), 495 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 4fdd333..f00604c 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -27,7 +27,7 @@ jobs: distribution: temurin - name: 🦞 chmod /gradlew - run: chmod +x ./gradlew + run: chmod +x ./gradlew - name: 🔦 Test run: ./gradlew test --info diff --git a/.run/TEST.run.xml b/.run/TEST.run.xml index 7376a7c..e4d820e 100644 --- a/.run/TEST.run.xml +++ b/.run/TEST.run.xml @@ -1,33 +1,34 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e76c74..b600025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [UnReleased] - + ## Added diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc index 4ecea8e..50ea179 100644 --- a/CODE_OF_CONDUCT.adoc +++ b/CODE_OF_CONDUCT.adoc @@ -1,14 +1,8 @@ = Contributor Code of Conduct -As contributors and maintainers of this project, and in the interest of fostering an open -and welcoming community, we pledge to respect all people who contribute through reporting -issues, posting feature requests, updating documentation, submitting pull requests or -patches, and other activities. +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. -We are committed to making participation in this project a harassment-free experience for -everyone, regardless of level of experience, gender, gender identity and expression, -sexual orientation, disability, personal appearance, body size, race, ethnicity, age, -religion, or nationality. +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: @@ -16,23 +10,15 @@ Examples of unacceptable behavior by participants include: * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment -* Publishing other's private information, such as physical or electronic addresses, - without explicit permission +* Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct -Project maintainers have the right and responsibility to remove, edit, or reject comments, -commits, code, wiki edits, issues, and other contributions that are not aligned to this -Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors -that they deem inappropriate, threatening, offensive, or harmful. +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. -By adopting this Code of Conduct, project maintainers commit themselves to fairly and -consistently applying these principles to every aspect of managing this project. Project -maintainers who do not follow or enforce the Code of Conduct may be permanently removed -from the project team. - -This Code of Conduct applies both within project spaces and in public spaces when an -individual is representing the project or its community. +By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. +Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. This Code of Conduct is adapted from the https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index 17cbe59..5c2d7c0 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -1,99 +1,90 @@ = Contributing to springdoc-openapi -springdoc-openapi is released under the Apache 2.0 license. If you would like to contribute -something, or simply want to hack on the code this document should help you get started. - - +springdoc-openapi is released under the Apache 2.0 license. +If you would like to contribute something, or simply want to hack on the code this document should help you get started. == Code of Conduct -This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of -conduct]. By participating, you are expected to uphold this code. +This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of +conduct]. +By participating, you are expected to uphold this code. == Using GitHub Issues -We use GitHub issues to track bugs and enhancements. If you have a general usage question -please ask on https://stackoverflow.com[Stack Overflow]. The springdoc-openapi team and the -broader community monitor the https://stackoverflow.com/tags/springdoc[`springdoc`] + +We use GitHub issues to track bugs and enhancements. +If you have a general usage question please ask on https://stackoverflow.com[Stack Overflow]. +The springdoc-openapi team and the broader community monitor the https://stackoverflow.com/tags/springdoc[`springdoc`] tag. -These are some basic guidelines before opening an issue. First of all you need to make sure, you don't create duplicate issues, and there no question already answred on https://stackoverflow.com/tags/springdoc. +These are some basic guidelines before opening an issue. +First of all you need to make sure, you don't create duplicate issues, and there no question already answred on https://stackoverflow.com/tags/springdoc. If you are starting using springdoc-openapi, we advise you to use the last available release. Then refer to the relevant documentation: 1. For OpenAPI specification 3: - - https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md +- https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md 2. For swagger2-annotations: - - https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations +- https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations 3. For swagger-ui configuration: - - https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md +- https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md 4. For springdoc-openapi: - - https://springdoc.github.io/springdoc-openapi-demos/ - - https://springdoc.github.io/springdoc-openapi-demos/faq.html - - https://springdoc.github.io/springdoc-openapi-demos/migrating-from-springfox.html - +- https://springdoc.github.io/springdoc-openapi-demos/ +- https://springdoc.github.io/springdoc-openapi-demos/faq.html +- https://springdoc.github.io/springdoc-openapi-demos/migrating-from-springfox.html -If you are reporting a bug, please help to speed up problem diagnosis by providing as -much information as possible: - - You need to describe your context (the title of an issue is not enough) - - What version of spring-boot you are using? - - What modules and versions of springdoc-openapi are you using? - - What are the actual and the expected result using OpenAPI Description (yml or json)? - - Provide with a sample code (HelloController) or Test that reproduces the problem +If you are reporting a bug, please help to speed up problem diagnosis by providing as much information as possible: +- You need to describe your context (the title of an issue is not enough) +- What version of spring-boot you are using? +- What modules and versions of springdoc-openapi are you using? +- What are the actual and the expected result using OpenAPI Description (yml or json)? +- Provide with a sample code (HelloController) or Test that reproduces the problem == Reporting Security Vulnerabilities -If you think you have found a security vulnerability in Spring Boot please *DO NOT* -disclose it publicly until we've had a chance to fix it. Please don't report security -vulnerabilities using GitHub issues, instead head over to support@springdoc.org and -learn how to disclose them responsibly. +If you think you have found a security vulnerability in Spring Boot please *DO NOT* +disclose it publicly until we've had a chance to fix it. +Please don't report security vulnerabilities using GitHub issues, instead head over to support@springdoc.org and learn how to disclose them responsibly. == Code Conventions and Housekeeping -None of these is essential for a pull request, but they will all help. They can also be -added after the original pull request but before a merge. - -* We use the https://github.com/spring-io/spring-javaformat/[Spring JavaFormat] project - to apply code formatting conventions. If you use Eclipse and you follow the '`Importing - into eclipse`' instructions below you should get project specific formatting - automatically. You can also install the - https://github.com/spring-io/spring-javaformat/#intellij-idea[Spring JavaFormat IntelliJ - Plugin] + +None of these is essential for a pull request, but they will all help. +They can also be added after the original pull request but before a merge. + +* We use the https://github.com/spring-io/spring-javaformat/[Spring JavaFormat] project to apply code formatting conventions. +If you use Eclipse and you follow the '`Importing into eclipse`' instructions below you should get project specific formatting automatically. +You can also install the +https://github.com/spring-io/spring-javaformat/#intellij-idea[Spring JavaFormat IntelliJ + Plugin] * Make sure all new `.java` files to have a simple Javadoc class comment with at least an - `@author` tag identifying you, and preferably at least a paragraph on what the class is - for. -* Add the ASF license header comment to all new `.java` files (copy from existing files - in the project) -* Add yourself as an `@author` to the `.java` files that you modify substantially (more - than cosmetic changes). +`@author` tag identifying you, and preferably at least a paragraph on what the class is for. +* Add the ASF license header comment to all new `.java` files (copy from existing files in the project) +* Add yourself as an `@author` to the `.java` files that you modify substantially (more than cosmetic changes). * Add some Javadocs. * A few unit tests would help a lot as well -- someone has to do it. -* If no-one else is using your branch, please rebase it against the current master (or - other target branch in the main project). -* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions], - if you are fixing an existing issue please add `Fixes #XXXX` at the end of the commit - message (where `XXXX` is the issue number). - - +* If no-one else is using your branch, please rebase it against the current master (or other target branch in the main project). +* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions], if you are fixing an existing issue please add `Fixes #XXXX` at the end of the commit message (where `XXXX` is the issue number). == Working with the Code + If you don't have an IDE preference we would recommend that you use IntellIJ. === Importing into IntelliJ IDEA -If you have performed a checkout of this repository already, use "`File`" -> "`Open`" and -then select the root `build.gradle` file to import the code. -Alternatively, you can let IntellIJ IDEA checkout the code for you. Use "`File`" -> -"`New`" -> "`Project from Version Control`" and -`https://github.com/spring-projects/spring-boot` for the URL. Once the checkout has -completed, a pop-up will suggest to open the project. +If you have performed a checkout of this repository already, use "`File`" -> "`Open`" and then select the root `build.gradle` file to import the code. +Alternatively, you can let IntellIJ IDEA checkout the code for you. +Use "`File`" -> +"`New`" -> "`Project from Version Control`" and +`https://github.com/spring-projects/spring-boot` for the URL. +Once the checkout has completed, a pop-up will suggest to open the project. ==== Install the Spring Formatter plugin -If you haven't done so, install the formatter plugin so that proper formatting rules are -applied automatically when you reformat code in the IDE. + +If you haven't done so, install the formatter plugin so that proper formatting rules are applied automatically when you reformat code in the IDE. * Download the latest https://search.maven.org/search?q=g:io.spring.javaformat%20AND%20a:spring-javaformat-intellij-plugin[IntelliJ IDEA plugin]. * Select "`IntelliJ IDEA`" -> "`Preferences`". @@ -101,10 +92,9 @@ applied automatically when you reformat code in the IDE. * Select the wheel and "`Install Plugin from Disk...`". * Select the jar file you've downloaded. - ==== Import additional code style -The formatter does not cover all rules (such as order of imports) and an additional file -needs to be added. + +The formatter does not cover all rules (such as order of imports) and an additional file needs to be added. * Select "`IntelliJ IDEA`" -> "`Preferences`". * Select "`Editor`" -> "`Code Style`". @@ -115,18 +105,18 @@ needs to be added. You can use Spring Boot project specific source formatting settings. - ===== Install the Spring Formatter plugin + * Select "`Help`" -> "`Install New Software`". * Add `https://repo.spring.io/javaformat-eclipse-update-site/` as a site. * Install "Spring Java Format". -NOTE: The plugin is optional. Projects can be imported without the plugins, your code -changes just won't be automatically formatted. +NOTE: The plugin is optional. +Projects can be imported without the plugins, your code changes just won't be automatically formatted. === Building from Source -springdoc-openapi source can be built from the command line using https://maven.apache.org/[Maven] on -JDK 1.8 or above. + +springdoc-openapi source can be built from the command line using https://maven.apache.org/[Maven] on JDK 1.8 or above. The project can be built from the root directory using the standard maven command: @@ -135,10 +125,10 @@ The project can be built from the root directory using the standard maven comman $ ./mvn install ---- - == Cloning the git repository on Windows -Some files in the git repository may exceed the Windows maximum file path (260 -characters), depending on where you clone the repository. If you get `Filename too long` + +Some files in the git repository may exceed the Windows maximum file path (260 characters), depending on where you clone the repository. +If you get `Filename too long` errors, set the `core.longPaths=true` git option: ``` diff --git a/README.md b/README.md index baa50fc..114641f 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,12 @@ Gradle plugin for springdoc-openapi. -This plugin allows you to generate an OpenAPI 3 specification for a Spring Boot application from a Gradle build. +This plugin allows you to generate an OpenAPI 3 specification for a Spring Boot +application from a Gradle build. Compatibility Notes ------------------- -The plugin is built on Gradle version 7.0. +The plugin is built on Gradle version 7.0. Dependencies ------------ @@ -17,7 +18,6 @@ This plugin has a runtime dependency on the following plugins: 1. Spring Boot Gradle plugin - `org.springframework.boot` 2. Gradle process plugin - `com.github.psxpaul.execfork` - How To Use ---------- @@ -31,6 +31,7 @@ plugins { ``` Gradle Kotlin DSL + ```groovy plugins { id("org.springframework.boot") version "2.7.0" @@ -38,31 +39,37 @@ plugins { } ``` -Note: For latest versions of the plugins please check the [Gradle Plugins portal](https://plugins.gradle.org/). +Note: For latest versions of the plugins please check +the [Gradle Plugins portal](https://plugins.gradle.org/). How the plugin works? ------------ -When you add this plugin and its runtime dependency plugins to your build file, the plugin creates the following tasks: +When you add this plugin and its runtime dependency plugins to your build file, the plugin +creates the following tasks: 1. forkedSpringBootRun 2. generateOpenApiDocs -Running the task `generateOpenApiDocs` writes the OpenAPI spec into a `openapi.json` file in your project's build dir. +Running the task `generateOpenApiDocs` writes the OpenAPI spec into a `openapi.json` file +in your project's build dir. ```bash gradle clean generateOpenApiDocs ``` -When you run the gradle task **generateOpenApiDocs**, it starts your spring boot application in the background using **forkedSpringBootRun** task. -Once your application is up and running **generateOpenApiDocs** makes a rest call to your applications doc url to download and store the open api docs file as json. +When you run the gradle task **generateOpenApiDocs**, it starts your spring boot +application in the background using **forkedSpringBootRun** task. +Once your application is up and running **generateOpenApiDocs** makes a rest call to your +applications doc url to download and store the open api docs file as json. Customization ------------- -The following customizations can be done on task generateOpenApiDocs using extension openApi as follows +The following customizations can be done on task generateOpenApiDocs using extension +openApi as follows ```kotlin openApi { @@ -88,13 +95,20 @@ openApi { | `customBootRun` | Any bootRun property that you would normal need to start your spring boot application. | No | (N/A) | ### `customBootRun` properties examples -`customBootRun` allows you to send in the properties that might be necessary to allow for the forked spring boot application that gets started + +`customBootRun` allows you to send in the properties that might be necessary to allow for +the forked spring boot application that gets started to be able to start (profiles, other custom properties, etc.) -`customBootRun` allows you can specify bootRun style parameter, such as `args`, `jvmArgs`, `systemProperties` and `workingDir`. -If you don't specify `customBootRun` parameter, this plugin uses the parameter specified to `bootRun` in Spring Boot Gradle Plugin. +`customBootRun` allows you can specify bootRun style parameter, such +as `args`, `jvmArgs`, `systemProperties` and `workingDir`. +If you don't specify `customBootRun` parameter, this plugin uses the parameter specified +to `bootRun` in Spring Boot Gradle Plugin. #### Passing static args -This allows for you to be able to just send the static properties when executing Spring application in `generateOpenApiDocs`. + +This allows for you to be able to just send the static properties when executing Spring +application in `generateOpenApiDocs`. + ``` openApi { customBootRun { @@ -104,11 +118,14 @@ openApi { ``` #### Passing straight from gradle -This allows for you to be able to just send in whatever you need when you generate docs. + +This allows for you to be able to just send in whatever you need when you generate docs. `./gradlew clean generateOpenApiDocs -Dspring.profiles.active=special` -and as long as the config looks as follows that value will be passed into the forked spring boot application. +and as long as the config looks as follows that value will be passed into the forked +spring boot application. + ``` openApi { customBootRun { @@ -118,18 +135,27 @@ 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. + +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 +2. Build and publish the plugin into your local maven repository by running the following ``` ./gradlew clean pTML ``` - + # Testing the plugin -1. Create a new spring boot application or use an existing spring boot app and follow the `How To Use` section above to configure this plugin. -2. Update the version for the plugin to match the current version found in `build.gradle.kts` + +1. Create a new spring boot application or use an existing spring boot app and follow + the `How To Use` section above to configure this plugin. +2. Update the version for the plugin to match the current version found + in `build.gradle.kts` ``` id("org.springdoc.openapi-gradle-plugin") version "1.6.0" @@ -148,6 +174,7 @@ The `groupedApiMappings` customization allows you to specify multiple URLs/file # **Thank you for the support** -* Thanks a lot [JetBrains](https://www.jetbrains.com/?from=springdoc-openapi) for supporting springdoc-openapi project. +* Thanks a lot [JetBrains](https://www.jetbrains.com/?from=springdoc-openapi) for + supporting springdoc-openapi project. ![JetBrains logo](https://springdoc.org/img/jetbrains.svg) diff --git a/build.gradle.kts b/build.gradle.kts index cf37482..d9f4f2f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,9 +33,13 @@ publishing { repositories { maven { // change URLs to point to your repos, e.g. http://my.org/repo - val releasesRepoUrl = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") - val snapshotsRepoUrl = uri("https://oss.sonatype.org/content/repositories/snapshots") - url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl + val releasesRepoUrl = + uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") + val snapshotsRepoUrl = + uri("https://oss.sonatype.org/content/repositories/snapshots") + url = if (version.toString() + .endsWith("SNAPSHOT") + ) snapshotsRepoUrl else releasesRepoUrl credentials { username = System.getenv("OSSRH_USER") password = System.getenv("OSSRH_PASS") @@ -68,8 +72,10 @@ gradlePlugin { create("springdoc-gradle-plugin") { id = "org.springdoc.openapi-gradle-plugin" displayName = "A Gradle plugin for the springdoc-openapi library" - description = " This plugin uses springdoc-openapi to generate an OpenAPI description at build time" - implementationClass = "org.springdoc.openapi.gradle.plugin.OpenApiGradlePlugin" + description = + " This plugin uses springdoc-openapi to generate an OpenAPI description at build time" + implementationClass = + "org.springdoc.openapi.gradle.plugin.OpenApiGradlePlugin" tags = listOf("springdoc", "openapi", "swagger") } } @@ -89,7 +95,8 @@ tasks.withType { tasks.withType().configureEach { useJUnitPlatform() - maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).takeIf { it > 0 } ?: 1 + maxParallelForks = + (Runtime.getRuntime().availableProcessors() / 2).takeIf { it > 0 } ?: 1 } detekt { diff --git a/main.yml b/main.yml index 4fdd333..f00604c 100644 --- a/main.yml +++ b/main.yml @@ -27,7 +27,7 @@ jobs: distribution: temurin - name: 🦞 chmod /gradlew - run: chmod +x ./gradlew + run: chmod +x ./gradlew - name: 🔦 Test run: ./gradlew test --info diff --git a/settings.gradle.kts b/settings.gradle.kts index 1012b6d..0331211 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,8 +1,8 @@ rootProject.name = "springdoc-openapi-gradle-plugin" pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - } + repositories { + mavenCentral() + gradlePluginPortal() + } } 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 2046228..136ecb9 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt @@ -3,7 +3,6 @@ package org.springdoc.openapi.gradle.plugin import org.gradle.api.Action import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.ProjectLayout import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty @@ -11,27 +10,32 @@ import org.gradle.api.provider.Property import javax.inject.Inject open class OpenApiExtension @Inject constructor( - objects: ObjectFactory, + objects: ObjectFactory, ) { - val apiDocsUrl: Property = objects.property(String::class.java) - val outputFileName: Property = objects.property(String::class.java) - val outputDir: DirectoryProperty = objects.directoryProperty() - val waitTimeInSeconds: Property = objects.property(Int::class.java) - val groupedApiMappings: MapProperty = objects.mapProperty(String::class.java, String::class.java) - val customBootRun: CustomBootRunAction = objects.newInstance(CustomBootRunAction::class.java) - fun customBootRun(action: Action) { - action.execute(customBootRun) - } + val apiDocsUrl: Property = objects.property(String::class.java) + val outputFileName: Property = objects.property(String::class.java) + val outputDir: DirectoryProperty = objects.directoryProperty() + val waitTimeInSeconds: Property = objects.property(Int::class.java) + val groupedApiMappings: MapProperty = + objects.mapProperty(String::class.java, String::class.java) + val customBootRun: CustomBootRunAction = + objects.newInstance(CustomBootRunAction::class.java) + + fun customBootRun(action: Action) { + action.execute(customBootRun) + } } open class CustomBootRunAction @Inject constructor( - objects: ObjectFactory, + objects: ObjectFactory, ) { - val systemProperties: MapProperty = objects.mapProperty(String::class.java, Any::class.java) - val workingDir: DirectoryProperty = objects.directoryProperty() - val mainClass: Property = objects.property(String::class.java) - val args: ListProperty = objects.listProperty(String::class.java) - val classpath: ConfigurableFileCollection = objects.fileCollection() - val jvmArgs: ListProperty = objects.listProperty(String::class.java) - val environment: MapProperty = objects.mapProperty(String::class.java, Any::class.java) + val systemProperties: MapProperty = + objects.mapProperty(String::class.java, Any::class.java) + val workingDir: DirectoryProperty = objects.directoryProperty() + val mainClass: Property = objects.property(String::class.java) + val args: ListProperty = objects.listProperty(String::class.java) + val classpath: ConfigurableFileCollection = objects.fileCollection() + val jvmArgs: ListProperty = objects.listProperty(String::class.java) + val environment: MapProperty = + objects.mapProperty(String::class.java, Any::class.java) } 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 1d51797..6c5f3a7 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt @@ -5,7 +5,11 @@ import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import org.awaitility.Durations import org.awaitility.core.ConditionTimeoutException -import org.awaitility.kotlin.* +import org.awaitility.kotlin.atMost +import org.awaitility.kotlin.await +import org.awaitility.kotlin.ignoreException +import org.awaitility.kotlin.until +import org.awaitility.kotlin.withPollInterval import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty @@ -24,85 +28,104 @@ import java.util.* private const val MAX_HTTP_STATUS_CODE = 299 open class OpenApiGeneratorTask : DefaultTask() { - @get:Input - val apiDocsUrl: Property = project.objects.property(String::class.java) + @get:Input + val apiDocsUrl: Property = project.objects.property(String::class.java) - @get:Input - val outputFileName: 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: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) + @get:OutputDirectory + val outputDir: DirectoryProperty = project.objects.directoryProperty() + private val waitTimeInSeconds: Property = + project.objects.property(Int::class.java) - init { - description = OPEN_API_TASK_DESCRIPTION - group = GROUP_NAME - // load my extensions - val extension: OpenApiExtension = project.extensions.getByName(EXTENSION_NAME) as OpenApiExtension + init { + description = OPEN_API_TASK_DESCRIPTION + group = GROUP_NAME + // load my extensions + val extension: OpenApiExtension = + project.extensions.getByName(EXTENSION_NAME) as OpenApiExtension - // set a default value if not provided - val defaultOutputDir = project.objects.directoryProperty() - defaultOutputDir.convention(project.layout.buildDirectory.dir("openapi")) + // set a default value if not provided + val defaultOutputDir = project.objects.directoryProperty() + defaultOutputDir.convention(project.layout.buildDirectory.dir("openapi")) - apiDocsUrl.convention(extension.apiDocsUrl.getOrElse(DEFAULT_API_DOCS_URL)) - outputFileName.convention(extension.outputFileName.getOrElse(DEFAULT_OPEN_API_FILE_NAME)) - groupedApiMappings.convention(extension.groupedApiMappings.getOrElse(emptyMap())) - outputDir.convention(extension.outputDir.getOrElse(defaultOutputDir.get())) - waitTimeInSeconds.convention(extension.waitTimeInSeconds.getOrElse(DEFAULT_WAIT_TIME_IN_SECONDS)) - } + apiDocsUrl.convention(extension.apiDocsUrl.getOrElse(DEFAULT_API_DOCS_URL)) + outputFileName.convention( + extension.outputFileName.getOrElse( + DEFAULT_OPEN_API_FILE_NAME + ) + ) + groupedApiMappings.convention(extension.groupedApiMappings.getOrElse(emptyMap())) + outputDir.convention(extension.outputDir.getOrElse(defaultOutputDir.get())) + waitTimeInSeconds.convention( + 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()) - } - } + @TaskAction + fun execute() { + if (groupedApiMappings.isPresent && groupedApiMappings.get().isNotEmpty()) { + groupedApiMappings.get().forEach(this::generateApiDocs) + } else { + generateApiDocs(apiDocsUrl.get(), outputFileName.get()) + } + } - private fun generateApiDocs(url: String, fileName: String) { - try { - val isYaml = url.lowercase(Locale.getDefault()).matches(Regex(".+[./]yaml(/.+)*")) - await ignoreException ConnectException::class withPollInterval Durations.ONE_SECOND atMost Duration.of( - waitTimeInSeconds.get().toLong(), - SECONDS - ) until { - val connection: HttpURLConnection = URL(url).openConnection() as HttpURLConnection - connection.requestMethod = "GET" - connection.connect() - val statusCode = connection.responseCode - logger.trace("apiDocsUrl = {} status code = {}", url, statusCode) - statusCode < MAX_HTTP_STATUS_CODE - } - logger.info("Generating OpenApi Docs..") - val connection: HttpURLConnection = URL(url).openConnection() as HttpURLConnection - connection.requestMethod = "GET" - connection.connect() + private fun generateApiDocs(url: String, fileName: String) { + try { + val isYaml = + url.lowercase(Locale.getDefault()).matches(Regex(".+[./]yaml(/.+)*")) + await ignoreException ConnectException::class withPollInterval Durations.ONE_SECOND atMost Duration.of( + waitTimeInSeconds.get().toLong(), + SECONDS + ) until { + val connection: HttpURLConnection = + URL(url).openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.connect() + val statusCode = connection.responseCode + logger.trace("apiDocsUrl = {} status code = {}", url, statusCode) + statusCode < MAX_HTTP_STATUS_CODE + } + logger.info("Generating OpenApi Docs..") + val connection: HttpURLConnection = + URL(url).openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.connect() - val response = String(connection.inputStream.readBytes(), Charsets.UTF_8) + val response = String(connection.inputStream.readBytes(), Charsets.UTF_8) - val apiDocs = if (isYaml) response else prettifyJson(response) + val apiDocs = if (isYaml) response else prettifyJson(response) - val outputFile = outputDir.file(fileName).get().asFile - outputFile.writeText(apiDocs) - } catch (e: ConditionTimeoutException) { - 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") - } - } + val outputFile = outputDir.file(fileName).get().asFile + outputFile.writeText(apiDocs) + } catch (e: ConditionTimeoutException) { + 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") + } + } - private fun prettifyJson(response: String): String { - val gson = GsonBuilder().setPrettyPrinting().create() - try { - val googleJsonObject = gson.fromJson(response, JsonObject::class.java) - return gson.toJson(googleJsonObject) - } catch (e: RuntimeException) { - throw JsonSyntaxException("Failed to parse the API docs response string. " + - "Please ensure that the response is in the correct format. response=$response", e) - } - } + private fun prettifyJson(response: String): String { + val gson = GsonBuilder().setPrettyPrinting().create() + try { + val googleJsonObject = gson.fromJson(response, JsonObject::class.java) + return gson.toJson(googleJsonObject) + } catch (e: RuntimeException) { + throw JsonSyntaxException( + "Failed to parse the API docs response string. " + + "Please ensure that the response is in the correct format. response=$response", + e + ) + } + } } diff --git a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt index e1546eb..a10a0a4 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt @@ -21,7 +21,7 @@ open class OpenApiGradlePlugin : Plugin { tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) tasks.register(OPEN_API_TASK_NAME, OpenApiGeneratorTask::class.java) - generate(this) + generate(this) } } @@ -33,27 +33,37 @@ open class OpenApiGradlePlugin : Plugin { // The task, used to resolve the application's main class (`bootRunMainClassName`) val bootRunMainClassNameTask = try { - val task=tasks.named(SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME) - logger.debug("Detected Spring Boot task {}", SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME) + val task = tasks.named(SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME) + logger.debug( + "Detected Spring Boot task {}", + SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME + ) task } catch (e: UnknownDomainObjectException) { - val task=tasks.named(SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME) - logger.debug("Detected Spring Boot task {}", SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME) + val task = tasks.named(SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME) + logger.debug( + "Detected Spring Boot task {}", + SPRING_BOOT_3_RUN_MAIN_CLASS_NAME_TASK_NAME + ) task } val extension = extensions.findByName(EXTENSION_NAME) as OpenApiExtension val customBootRun = extension.customBootRun // Create a forked version spring boot run task - val forkedSpringBoot = tasks.named(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) { fork -> + val forkedSpringBoot = tasks.named( + FORKED_SPRING_BOOT_RUN_TASK_NAME, + JavaExecFork::class.java + ) { fork -> fork.dependsOn(tasks.named(bootRunMainClassNameTask.name)) fork.onlyIf { needToFork(bootRunTask, customBootRun, fork) } } // This is my task. Before I can run it, I have to run the dependent tasks - val openApiTask = tasks.named(OPEN_API_TASK_NAME, OpenApiGeneratorTask::class.java) { - it.dependsOn(forkedSpringBoot) - } + val openApiTask = + tasks.named(OPEN_API_TASK_NAME, OpenApiGeneratorTask::class.java) { + it.dependsOn(forkedSpringBoot) + } // The forked task need to be terminated as soon as my task is finished forkedSpringBoot.get().stopAfter = openApiTask as TaskProvider @@ -74,8 +84,9 @@ open class OpenApiGradlePlugin : Plugin { ): Boolean { val bootRun = bootRunTask.get() as BootRun - val baseSystemProperties = customBootRun.systemProperties.orNull?.takeIf { it.isNotEmpty() } - ?: bootRun.systemProperties + val baseSystemProperties = + customBootRun.systemProperties.orNull?.takeIf { it.isNotEmpty() } + ?: bootRun.systemProperties with(fork) { // copy all system properties, excluding those starting with `java.class.path` systemProperties = baseSystemProperties.filter { 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 904984e..2780274 100644 --- a/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt +++ b/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt @@ -22,14 +22,14 @@ import java.nio.file.Files class OpenApiGradlePluginTest { - private val projectTestDir = Files.createTempDirectory("acceptance-project").toFile() - private val buildFile = File(projectTestDir, "build.gradle") - private val projectBuildDir = File(projectTestDir, "build") + private val projectTestDir = Files.createTempDirectory("acceptance-project").toFile() + private val buildFile = File(projectTestDir, "build.gradle") + private val projectBuildDir = File(projectTestDir, "build") - private val pathsField = "paths" - private val openapiField = "openapi" + private val pathsField = "paths" + private val openapiField = "openapi" - private val baseBuildGradle = """plugins { + private val baseBuildGradle = """plugins { id 'java' id 'org.springframework.boot' version '2.7.6' id 'io.spring.dependency-management' version '1.1.15.RELEASE' @@ -50,105 +50,113 @@ class OpenApiGradlePluginTest { } """.trimIndent() - companion object { - val logger: Logger = LoggerFactory.getLogger(OpenApiGradlePluginTest::class.java) - } + companion object { + val logger: Logger = LoggerFactory.getLogger(OpenApiGradlePluginTest::class.java) + } - @BeforeEach - fun createTemporaryAcceptanceProjectFromTemplate() { - File(javaClass.classLoader.getResource("acceptance-project")!!.path).copyRecursively(projectTestDir) - } + @BeforeEach + fun createTemporaryAcceptanceProjectFromTemplate() { + File(javaClass.classLoader.getResource("acceptance-project")!!.path).copyRecursively( + projectTestDir + ) + } - @Test - fun `default build no options`() { - buildFile.writeText(baseBuildGradle) + @Test + fun `default build no options`() { + buildFile.writeText(baseBuildGradle) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiJsonFile(1) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiJsonFile(1) + } - @Test - fun `different output dir`() { - val specialOutputDir = File(projectTestDir, "specialDir") - specialOutputDir.mkdirs() + @Test + fun `different output dir`() { + val specialOutputDir = File(projectTestDir, "specialDir") + specialOutputDir.mkdirs() - buildFile.writeText( - """$baseBuildGradle + buildFile.writeText( + """$baseBuildGradle openApi{ outputDir = file("${specialOutputDir.toURI().path}") } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiJsonFile(1, buildDir = specialOutputDir) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiJsonFile(1, buildDir = specialOutputDir) + } - @Test - fun `different output file name`() { - val specialOutputJsonFileName = RandomStringUtils.randomAlphanumeric(15) + @Test + fun `different output file name`() { + val specialOutputJsonFileName = RandomStringUtils.randomAlphanumeric(15) - buildFile.writeText( - """$baseBuildGradle + buildFile.writeText( + """$baseBuildGradle openApi{ outputFileName = "$specialOutputJsonFileName" } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiJsonFile(1, specialOutputJsonFileName) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiJsonFile(1, specialOutputJsonFileName) + } - @Test - fun `using properties`() { - buildFile.writeText( - """$baseBuildGradle + @Test + fun `using properties`() { + buildFile.writeText( + """$baseBuildGradle bootRun { args = ["--spring.profiles.active=multiple-endpoints", "--some.second.property=someValue"] } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiJsonFile(3) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiJsonFile(3) + } - @Test - fun `using forked properties via System properties`() { - buildFile.writeText( - """$baseBuildGradle + @Test + fun `using forked properties via System properties`() { + buildFile.writeText( + """$baseBuildGradle bootRun { systemProperties = System.properties } """.trimMargin() - ) - - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild("-Dspring.profiles.active=multiple-endpoints")).outcome) - assertOpenApiJsonFile(2) - } - - @Test - fun `using forked properties via System properties with customBootRun`() { - buildFile.writeText( - """$baseBuildGradle + ) + + assertEquals( + TaskOutcome.SUCCESS, + openApiDocsTask(runTheBuild("-Dspring.profiles.active=multiple-endpoints")).outcome + ) + assertOpenApiJsonFile(2) + } + + @Test + fun `using forked properties via System properties with customBootRun`() { + buildFile.writeText( + """$baseBuildGradle openApi { customBootRun { systemProperties = System.properties } } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild("-Dspring.profiles.active=multiple-endpoints")).outcome) - assertOpenApiJsonFile(2) - } + assertEquals( + TaskOutcome.SUCCESS, + openApiDocsTask(runTheBuild("-Dspring.profiles.active=multiple-endpoints")).outcome + ) + assertOpenApiJsonFile(2) + } - @Test - fun `configurable wait time`() { - buildFile.writeText( - """$baseBuildGradle + @Test + fun `configurable wait time`() { + buildFile.writeText( + """$baseBuildGradle bootRun { args = ["--spring.profiles.active=slower"] } @@ -156,16 +164,16 @@ class OpenApiGradlePluginTest { waitTimeInSeconds = 60 } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiJsonFile(1) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiJsonFile(1) + } - @Test - fun `using different api url`() { - buildFile.writeText( - """$baseBuildGradle + @Test + fun `using different api url`() { + buildFile.writeText( + """$baseBuildGradle bootRun { args = ["--spring.profiles.active=different-url"] } @@ -173,16 +181,16 @@ class OpenApiGradlePluginTest { apiDocsUrl = "http://localhost:8080/secret-api-docs" } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiJsonFile(1) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiJsonFile(1) + } - @Test - fun `using different api url via customBootRun`() { - buildFile.writeText( - """$baseBuildGradle + @Test + fun `using different api url via customBootRun`() { + buildFile.writeText( + """$baseBuildGradle openApi{ apiDocsUrl = "http://localhost:8080/secret-api-docs" customBootRun { @@ -190,38 +198,38 @@ class OpenApiGradlePluginTest { } } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiJsonFile(1) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiJsonFile(1) + } - @Test - fun `yaml generation`() { - val outputYamlFileName = "openapi.yaml" + @Test + fun `yaml generation`() { + val outputYamlFileName = "openapi.yaml" - buildFile.writeText( - """$baseBuildGradle + buildFile.writeText( + """$baseBuildGradle openApi{ apiDocsUrl = "http://localhost:8080/v3/api-docs.yaml" outputFileName = "$outputYamlFileName" } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiYamlFile(1, outputYamlFileName) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiYamlFile(1, outputYamlFileName) + } - @Test - fun `using multiple grouped apis`() { - val outputJsonFileNameGroupA = "openapi-groupA.json" - val outputJsonFileNameGroupB = "openapi-groupB.json" + @Test + fun `using multiple grouped apis`() { + val outputJsonFileNameGroupA = "openapi-groupA.json" + val outputJsonFileNameGroupB = "openapi-groupB.json" - buildFile.writeText( - """$baseBuildGradle + buildFile.writeText( + """$baseBuildGradle bootRun { args = ["--spring.profiles.active=multiple-grouped-apis"] } @@ -230,20 +238,20 @@ class OpenApiGradlePluginTest { "http://localhost:8080/v3/api-docs/groupB": "$outputJsonFileNameGroupB"] } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiJsonFile(1, outputJsonFileNameGroupA) - assertOpenApiJsonFile(2, outputJsonFileNameGroupB) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiJsonFile(1, outputJsonFileNameGroupA) + assertOpenApiJsonFile(2, outputJsonFileNameGroupB) + } - @Test - fun `using multiple grouped apis with yaml`() { - val outputYamlFileNameGroupA = "openapi-groupA.yaml" - val outputYamlFileNameGroupB = "openapi-groupB.yaml" + @Test + fun `using multiple grouped apis with yaml`() { + val outputYamlFileNameGroupA = "openapi-groupA.yaml" + val outputYamlFileNameGroupB = "openapi-groupB.yaml" - buildFile.writeText( - """$baseBuildGradle + buildFile.writeText( + """$baseBuildGradle bootRun { args = ["--spring.profiles.active=multiple-grouped-apis"] } @@ -252,21 +260,21 @@ class OpenApiGradlePluginTest { "http://localhost:8080/v3/api-docs.yaml/groupB": "$outputYamlFileNameGroupB"] } """.trimMargin() - ) + ) - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertOpenApiYamlFile(1, outputYamlFileNameGroupA) - assertOpenApiYamlFile(2, outputYamlFileNameGroupB) - } + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertOpenApiYamlFile(1, outputYamlFileNameGroupA) + assertOpenApiYamlFile(2, outputYamlFileNameGroupB) + } - @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" + @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 + buildFile.writeText( + """$baseBuildGradle bootRun { args = ["--spring.profiles.active=multiple-grouped-apis"] } @@ -277,63 +285,69 @@ class OpenApiGradlePluginTest { "http://localhost:8080/v3/api-docs/groupB": "$outputJsonFileNameGroupB"] } """.trimMargin() - ) - - assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) - assertFalse(File(projectBuildDir, outputJsonFileNameSingleGroupA).exists()) - assertOpenApiJsonFile(1, outputJsonFileNameGroupA) - assertOpenApiJsonFile(2, outputJsonFileNameGroupB) - } - - @Test - fun `using invalid doc url`() { - buildFile.writeText( - """$baseBuildGradle + ) + + assertEquals(TaskOutcome.SUCCESS, openApiDocsTask(runTheBuild()).outcome) + assertFalse(File(projectBuildDir, outputJsonFileNameSingleGroupA).exists()) + assertOpenApiJsonFile(1, outputJsonFileNameGroupA) + assertOpenApiJsonFile(2, outputJsonFileNameGroupB) + } + + @Test + fun `using invalid doc url`() { + buildFile.writeText( + """$baseBuildGradle openApi{ apiDocsUrl = "http://localhost:8080/hello/world" } """.trimMargin() - ) - - try { - openApiDocsTask(runTheBuild()) - } catch (e: RuntimeException) { - logger.error(e.message) - assertNotNull(e.message?.lines()?.find { it.contains( - "Failed to parse the API docs response string. " + - "Please ensure that the response is in the correct format.") }) - } - } - - private fun runTheBuild(vararg additionalArguments: String = emptyArray()) = GradleRunner.create() - .withProjectDir(projectTestDir) - .withArguments("clean", "generateOpenApiDocs", *additionalArguments) - .withPluginClasspath() - .build() - - private fun assertOpenApiJsonFile( - expectedPathCount: Int, - outputJsonFileName: String = DEFAULT_OPEN_API_FILE_NAME, - buildDir: File = projectBuildDir - ) { - val openApiJson = getOpenApiJsonAtLocation(File(buildDir, outputJsonFileName)) - assertEquals("3.0.1", openApiJson.string(openapiField)) - assertEquals(expectedPathCount, openApiJson.obj(pathsField)!!.size) - } - - private fun getOpenApiJsonAtLocation(path: File) = Parser.default().parse(FileReader(path)) as JsonObject - - private fun assertOpenApiYamlFile( - expectedPathCount: Int, - outputJsonFileName: String = DEFAULT_OPEN_API_FILE_NAME, - buildDir: File = projectBuildDir - ) { - val mapper = ObjectMapper(YAMLFactory()) - mapper.registerModule(KotlinModule.Builder().build()) - val node = mapper.readTree(File(buildDir, outputJsonFileName)) - assertEquals("3.0.1", node.get(openapiField).asText()) - assertEquals(expectedPathCount, node.get(pathsField)!!.size()) - } - - private fun openApiDocsTask(result: BuildResult) = result.tasks.find { it.path.contains("generateOpenApiDocs") }!! + ) + + try { + openApiDocsTask(runTheBuild()) + } catch (e: RuntimeException) { + logger.error(e.message) + assertNotNull(e.message?.lines()?.find { + it.contains( + "Failed to parse the API docs response string. " + + "Please ensure that the response is in the correct format." + ) + }) + } + } + + private fun runTheBuild(vararg additionalArguments: String = emptyArray()) = + GradleRunner.create() + .withProjectDir(projectTestDir) + .withArguments("clean", "generateOpenApiDocs", *additionalArguments) + .withPluginClasspath() + .build() + + private fun assertOpenApiJsonFile( + expectedPathCount: Int, + outputJsonFileName: String = DEFAULT_OPEN_API_FILE_NAME, + buildDir: File = projectBuildDir + ) { + val openApiJson = getOpenApiJsonAtLocation(File(buildDir, outputJsonFileName)) + assertEquals("3.0.1", openApiJson.string(openapiField)) + assertEquals(expectedPathCount, openApiJson.obj(pathsField)!!.size) + } + + private fun getOpenApiJsonAtLocation(path: File) = + Parser.default().parse(FileReader(path)) as JsonObject + + private fun assertOpenApiYamlFile( + expectedPathCount: Int, + outputJsonFileName: String = DEFAULT_OPEN_API_FILE_NAME, + buildDir: File = projectBuildDir + ) { + val mapper = ObjectMapper(YAMLFactory()) + mapper.registerModule(KotlinModule.Builder().build()) + val node = mapper.readTree(File(buildDir, outputJsonFileName)) + assertEquals("3.0.1", node.get(openapiField).asText()) + assertEquals(expectedPathCount, node.get(pathsField)!!.size()) + } + + private fun openApiDocsTask(result: BuildResult) = + result.tasks.find { it.path.contains("generateOpenApiDocs") }!! } diff --git a/src/test/resources/acceptance-project/src/main/java/com/example/demo/DemoApplication.java b/src/test/resources/acceptance-project/src/main/java/com/example/demo/DemoApplication.java index 0f77539..24f27db 100644 --- a/src/test/resources/acceptance-project/src/main/java/com/example/demo/DemoApplication.java +++ b/src/test/resources/acceptance-project/src/main/java/com/example/demo/DemoApplication.java @@ -1,30 +1,31 @@ package com.example.demo; +import java.time.Duration; + +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import java.time.Duration; - @SpringBootApplication -public class DemoApplication implements ApplicationRunner{ +public class DemoApplication implements ApplicationRunner { + + @Value("${slower:false}") + boolean slower; - @Value("${slower:false}") - boolean slower; + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } - public static void main(String[] args) { - SpringApplication.run(DemoApplication.class, args); - } - @Override - public void run(ApplicationArguments arg0) throws Exception { - System.out.println("Hello World from Application Runner"); - if (slower) { - Duration waitTime = Duration.ofSeconds(40); - System.out.println("Waiting for " + waitTime + " before starting"); - Thread.sleep(waitTime.toMillis()); - } - } + @Override + public void run(ApplicationArguments arg0) throws Exception { + System.out.println("Hello World from Application Runner"); + if (slower) { + Duration waitTime = Duration.ofSeconds(40); + System.out.println("Waiting for " + waitTime + " before starting"); + Thread.sleep(waitTime.toMillis()); + } + } } 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 index c37c7b7..f648861 100644 --- 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 @@ -1,6 +1,7 @@ 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; @@ -9,19 +10,19 @@ @Configuration public class GroupedConfiguration { - @Bean - public GroupedOpenApi groupA() { - return GroupedOpenApi.builder() - .group("groupA") - .pathsToMatch("/groupA/**") - .build(); - } + @Bean + public GroupedOpenApi groupA() { + return GroupedOpenApi.builder() + .group("groupA") + .pathsToMatch("/groupA/**") + .build(); + } - @Bean - public GroupedOpenApi groupB() { - return GroupedOpenApi.builder() - .group("groupB") - .pathsToMatch("/groupB/**") - .build(); - } + @Bean + public GroupedOpenApi groupB() { + return GroupedOpenApi.builder() + .group("groupB") + .pathsToMatch("/groupB/**") + .build(); + } } diff --git a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/ConditionalController.java b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/ConditionalController.java index 44f5cc9..1813c7f 100644 --- a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/ConditionalController.java +++ b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/ConditionalController.java @@ -4,12 +4,12 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -@ConditionalOnProperty(value = {"some.second.property"}, havingValue = "someValue") +@ConditionalOnProperty(value = { "some.second.property" }, havingValue = "someValue") @RestController("/conditional") public class ConditionalController { - @GetMapping("/conditional") - public String conditional() { - return "conditional"; - } + @GetMapping("/conditional") + public String conditional() { + return "conditional"; + } } 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 index 2df2fdd..8c87777 100644 --- 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 @@ -8,18 +8,18 @@ @RestController("/grouped") public class GroupedController { - @GetMapping("/groupA") - public String groupA() { - return "groupA"; - } + @GetMapping("/groupA") + public String groupA() { + return "groupA"; + } - @GetMapping("/groupB/first") - public String groupB_first() { - return "groupB_first"; - } + @GetMapping("/groupB/first") + public String groupB_first() { + return "groupB_first"; + } - @GetMapping("/groupB/second") - public String groupB_second() { - return "groupB_second"; - } + @GetMapping("/groupB/second") + public String groupB_second() { + return "groupB_second"; + } } diff --git a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/HelloWorldController.java b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/HelloWorldController.java index f53424e..3873f80 100644 --- a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/HelloWorldController.java +++ b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/HelloWorldController.java @@ -9,9 +9,9 @@ @RequestMapping("/hello") public class HelloWorldController { - @GetMapping("/world") - @ResponseBody - public String helloWorld() { - return "Hello World!"; - } + @GetMapping("/world") + @ResponseBody + public String helloWorld() { + return "Hello World!"; + } } diff --git a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/ProfileController.java b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/ProfileController.java index a482b63..625ac6e 100644 --- a/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/ProfileController.java +++ b/src/test/resources/acceptance-project/src/main/java/com/example/demo/endpoints/ProfileController.java @@ -9,11 +9,11 @@ @RestController("/special") public class ProfileController { - @Value("${test.props}") - String profileRelatedValue; + @Value("${test.props}") + String profileRelatedValue; - @GetMapping("/") - public String special() { - return profileRelatedValue; - } + @GetMapping("/") + public String special() { + return profileRelatedValue; + } } From 1ed3458d43c2234d5ee32d1183090e9659afe056 Mon Sep 17 00:00:00 2001 From: "Badr.NassLahsen" Date: Tue, 15 Aug 2023 20:58:53 +0200 Subject: [PATCH 16/18] downgrade gradle version to v7.6 --- build.gradle.kts | 23 +++++++++++-------- config/detekt/detekt.yml | 6 +---- gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/plugin/OpenApiGeneratorTask.kt | 5 ++-- .../gradle/plugin/OpenApiGradlePlugin.kt | 7 +++--- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d9f4f2f..81d16d8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-gradle-plugin` id("com.gradle.plugin-publish") version "1.2.0" id("org.sonarqube") version "3.1.1" - kotlin("jvm") version "1.8.21" + kotlin("jvm") version "1.7.10" `maven-publish` id("com.github.ben-manes.versions") version "0.38.0" id("io.gitlab.arturbosch.detekt") version "1.16.0" @@ -39,7 +39,11 @@ publishing { uri("https://oss.sonatype.org/content/repositories/snapshots") url = if (version.toString() .endsWith("SNAPSHOT") - ) snapshotsRepoUrl else releasesRepoUrl + ) { + snapshotsRepoUrl + } else { + releasesRepoUrl + } credentials { username = System.getenv("OSSRH_USER") password = System.getenv("OSSRH_PASS") @@ -66,21 +70,22 @@ dependencies { } gradlePlugin { - website = "https://github.com/springdoc/springdoc-openapi-gradle-plugin" - vcsUrl = "https://github.com/springdoc/springdoc-openapi-gradle-plugin.git" plugins { create("springdoc-gradle-plugin") { id = "org.springdoc.openapi-gradle-plugin" displayName = "A Gradle plugin for the springdoc-openapi library" - description = - " This plugin uses springdoc-openapi to generate an OpenAPI description at build time" - implementationClass = - "org.springdoc.openapi.gradle.plugin.OpenApiGradlePlugin" - tags = listOf("springdoc", "openapi", "swagger") + description = " This plugin uses springdoc-openapi to generate an OpenAPI description at build time" + implementationClass = "org.springdoc.openapi.gradle.plugin.OpenApiGradlePlugin" } } } +pluginBundle { + website = "https://github.com/springdoc/springdoc-openapi-gradle-plugin" + vcsUrl = "https://github.com/springdoc/springdoc-openapi-gradle-plugin.git" + tags = listOf("springdoc", "openapi", "swagger") +} + val jvmVersion: JavaLanguageVersion = JavaLanguageVersion.of(8) java { diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index d145605..37e6a2c 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -206,6 +206,7 @@ exceptions: - NumberFormatException - ParseException - MalformedURLException + - UnknownDomainObjectException allowedExceptionNameRegex: '_|(ignore|expected).*' ThrowingExceptionFromFinally: active: true @@ -230,7 +231,6 @@ exceptions: - IllegalMonitorStateException - NullPointerException - IndexOutOfBoundsException - - RuntimeException - Throwable allowedExceptionNameRegex: '_|(ignore|expected).*' TooGenericExceptionThrown: @@ -325,10 +325,6 @@ formatting: PackageName: active: true autoCorrect: true - ParameterListWrapping: - active: true - autoCorrect: true - indentSize: 4 SpacingAroundAngleBrackets: active: false autoCorrect: true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9f4197d..164080a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME 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 6c5f3a7..1a7d6f5 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt @@ -52,7 +52,7 @@ open class OpenApiGeneratorTask : DefaultTask() { // set a default value if not provided val defaultOutputDir = project.objects.directoryProperty() - defaultOutputDir.convention(project.layout.buildDirectory.dir("openapi")) + defaultOutputDir.set(project.buildDir) apiDocsUrl.convention(extension.apiDocsUrl.getOrElse(DEFAULT_API_DOCS_URL)) outputFileName.convention( @@ -80,8 +80,7 @@ open class OpenApiGeneratorTask : DefaultTask() { private fun generateApiDocs(url: String, fileName: String) { try { - val isYaml = - url.lowercase(Locale.getDefault()).matches(Regex(".+[./]yaml(/.+)*")) + val isYaml = url.toLowerCase().matches(Regex(".+[./]yaml(/.+)*")) await ignoreException ConnectException::class withPollInterval Durations.ONE_SECOND atMost Duration.of( waitTimeInSeconds.get().toLong(), SECONDS diff --git a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt index a10a0a4..a4abb62 100644 --- a/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt +++ b/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt @@ -73,8 +73,9 @@ open class OpenApiGradlePlugin : Plugin { val tasksNames = tasks.names val boot2TaskName = "bootRunMainClassName" val boot3TaskName = "resolveMainClassName" - if (!tasksNames.contains(boot2TaskName) && tasksNames.contains(boot3TaskName)) - tasks.register(boot2TaskName) { it.dependsOn(tasks.named(boot3TaskName)) } + if (!tasksNames.contains(boot2TaskName) && tasksNames.contains(boot3TaskName)) { + tasks.register(boot2TaskName) { it.dependsOn(tasks.named(boot3TaskName)) } + } } private fun needToFork( @@ -95,7 +96,7 @@ open class OpenApiGradlePlugin : Plugin { // use original bootRun parameter if the list-type customBootRun properties are empty workingDir = customBootRun.workingDir.asFile.orNull - ?: fork.temporaryDir + ?: fork.workingDir args = customBootRun.args.orNull?.takeIf { it.isNotEmpty() }?.toMutableList() ?: bootRun.args?.toMutableList() ?: mutableListOf() classpath = customBootRun.classpath.takeIf { !it.isEmpty } From a629dae12a6879907de7b634d31bcf2dfef1648c Mon Sep 17 00:00:00 2001 From: "Badr.NassLahsen" Date: Tue, 15 Aug 2023 21:52:07 +0200 Subject: [PATCH 17/18] upgrade gradle version to v8.2.1 --- build.gradle.kts | 21 ++++++++----------- gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/plugin/OpenApiGradlePluginTest.kt | 4 +--- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 81d16d8..cf775f1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,10 +2,10 @@ plugins { `java-gradle-plugin` id("com.gradle.plugin-publish") version "1.2.0" id("org.sonarqube") version "3.1.1" - kotlin("jvm") version "1.7.10" + kotlin("jvm") version "1.8.20" `maven-publish` id("com.github.ben-manes.versions") version "0.38.0" - id("io.gitlab.arturbosch.detekt") version "1.16.0" + id("io.gitlab.arturbosch.detekt") version "1.23.1" } group = "org.springdoc" @@ -17,12 +17,12 @@ sonarqube { } } repositories { + gradlePluginPortal() mavenCentral() maven { name = "Spring Repositories" url = uri("https://repo.spring.io/libs-release/") } - gradlePluginPortal() maven { name = "Gradle Plugins Maven Repository" url = uri("https://plugins.gradle.org/m2/") @@ -57,7 +57,7 @@ dependencies { implementation("com.google.code.gson:gson:2.8.9") implementation("org.awaitility:awaitility-kotlin:4.0.3") implementation("com.github.psxpaul:gradle-execfork-plugin:0.2.0") - implementation("org.springframework.boot:spring-boot-gradle-plugin:2.5.6") + implementation("org.springframework.boot:spring-boot-gradle-plugin:2.7.14") testImplementation(gradleTestKit()) testImplementation(platform("org.junit:junit-bom:5.7.1")) @@ -66,26 +66,23 @@ dependencies { testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2") testImplementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.2") - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.16.0") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.1") } gradlePlugin { + website = "https://github.com/springdoc/springdoc-openapi-gradle-plugin" + vcsUrl = "https://github.com/springdoc/springdoc-openapi-gradle-plugin.git" plugins { create("springdoc-gradle-plugin") { id = "org.springdoc.openapi-gradle-plugin" displayName = "A Gradle plugin for the springdoc-openapi library" description = " This plugin uses springdoc-openapi to generate an OpenAPI description at build time" implementationClass = "org.springdoc.openapi.gradle.plugin.OpenApiGradlePlugin" + tags = listOf("springdoc", "openapi", "swagger") } } } -pluginBundle { - website = "https://github.com/springdoc/springdoc-openapi-gradle-plugin" - vcsUrl = "https://github.com/springdoc/springdoc-openapi-gradle-plugin.git" - tags = listOf("springdoc", "openapi", "swagger") -} - val jvmVersion: JavaLanguageVersion = JavaLanguageVersion.of(8) java { @@ -105,7 +102,7 @@ tasks.withType().configureEach { } detekt { - config = files("config/detekt/detekt.yml") + config.setFrom("config/detekt/detekt.yml") parallel = true } tasks.withType().configureEach { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 164080a..9f4197d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME 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 2780274..192e7b1 100644 --- a/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt +++ b/src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt @@ -32,7 +32,7 @@ class OpenApiGradlePluginTest { private val baseBuildGradle = """plugins { id 'java' id 'org.springframework.boot' version '2.7.6' - id 'io.spring.dependency-management' version '1.1.15.RELEASE' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' id 'org.springdoc.openapi-gradle-plugin' } @@ -152,7 +152,6 @@ class OpenApiGradlePluginTest { assertOpenApiJsonFile(2) } - @Test fun `configurable wait time`() { buildFile.writeText( @@ -204,7 +203,6 @@ class OpenApiGradlePluginTest { assertOpenApiJsonFile(1) } - @Test fun `yaml generation`() { val outputYamlFileName = "openapi.yaml" From f3ee06a312babf1b9ffef8475fc576f5f3809a6d Mon Sep 17 00:00:00 2001 From: "Badr.NassLahsen" Date: Tue, 15 Aug 2023 22:15:04 +0200 Subject: [PATCH 18/18] docs update to v1.7.0 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 114641f..b9b06ed 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Gradle Groovy DSL ```groovy plugins { id "org.springframework.boot" version "2.7.0" - id "org.springdoc.openapi-gradle-plugin" version "1.6.0" + id "org.springdoc.openapi-gradle-plugin" version "1.7.0" } ``` @@ -35,7 +35,7 @@ Gradle Kotlin DSL ```groovy plugins { id("org.springframework.boot") version "2.7.0" - id("org.springdoc.openapi-gradle-plugin") version "1.6.0" + id("org.springdoc.openapi-gradle-plugin") version "1.7.0" } ``` @@ -158,7 +158,7 @@ OpenAPI doc. in `build.gradle.kts` ``` - id("org.springdoc.openapi-gradle-plugin") version "1.6.0" + id("org.springdoc.openapi-gradle-plugin") version "1.7.0" ``` 3. Add the following to the spring boot apps `settings.gradle`