Skip to content

Commit 3c3e275

Browse files
authored
Merge pull request #41 from jrmcdonald/feat/issue-40-multiple-grouped-apis
Add ability to generate multiple OpenAPI docs
2 parents 82a59b6 + 2986e64 commit 3c3e275

File tree

7 files changed

+143
-11
lines changed

7 files changed

+143
-11
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ openApi {
7979
outputFileName.set("swagger.json")
8080
waitTimeInSeconds.set(10)
8181
forkProperties.set("-Dspring.profiles.active=special")
82+
groupedApiMappings.set(["https://localhost:8080/v3/api-docs/groupA" to "swagger-groupA.json",
83+
"https://localhost:8080/v3/api-docs/groupB" to "swagger-groupB.json"])
8284
}
8385
```
8486

@@ -89,6 +91,7 @@ Parameter | Description | Required | Default
8991
`outputFileName` | The name of the output file with extension | No | openapi.json
9092
`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
9193
`forkProperties` | Any system property that you would normal need to start your spring boot application. Can either be a static string or a java Properties object | No | ""
94+
`groupedApiMappings` | A map of URLs (from where the OpenAPI docs can be downloaded) to output file names | No | []
9295

9396
### Fork properties examples
9497
Fork properties allows you to send in anything that might be necessary to allow for the forked spring boot application that gets started
@@ -113,6 +116,9 @@ openApi {
113116
}
114117
```
115118

119+
### Grouped API Mappings Notes
120+
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.
121+
116122
# Building the plugin
117123
1. Clone the repo `git@github.com:springdoc/springdoc-openapi-gradle-plugin.git`
118124
2. Build and publish the plugin into your local maven repository by running the following

src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.springdoc.openapi.gradle.plugin
22

33
import org.gradle.api.Project
44
import org.gradle.api.file.DirectoryProperty
5+
import org.gradle.api.provider.MapProperty
56
import org.gradle.api.provider.Property
67
import javax.inject.Inject
78

@@ -11,4 +12,5 @@ open class OpenApiExtension @Inject constructor(project: Project) {
1112
val outputDir: DirectoryProperty = project.objects.directoryProperty()
1213
val waitTimeInSeconds: Property<Int> = project.objects.property(Int::class.java)
1314
val forkProperties: Property<Any> = project.objects.property(Any::class.java)
15+
val groupedApiMappings: MapProperty<String, String> = project.objects.mapProperty(String::class.java, String::class.java)
1416
}

src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import org.awaitility.kotlin.*
99
import org.gradle.api.DefaultTask
1010
import org.gradle.api.GradleException
1111
import org.gradle.api.file.DirectoryProperty
12+
import org.gradle.api.provider.MapProperty
1213
import org.gradle.api.provider.Property
1314
import org.gradle.api.tasks.Input
14-
import org.gradle.api.tasks.Internal
1515
import org.gradle.api.tasks.OutputDirectory
1616
import org.gradle.api.tasks.TaskAction
1717
import java.net.ConnectException
@@ -24,6 +24,8 @@ open class OpenApiGeneratorTask : DefaultTask() {
2424
val apiDocsUrl: Property<String> = project.objects.property(String::class.java)
2525
@get:Input
2626
val outputFileName: Property<String> = project.objects.property(String::class.java)
27+
@get:Input
28+
val groupedApiMappings: MapProperty<String, String> = project.objects.mapProperty(String::class.java, String::class.java)
2729
@get:OutputDirectory
2830
val outputDir: DirectoryProperty = project.objects.directoryProperty()
2931
private val waitTimeInSeconds: Property<Int> = project.objects.property(Int::class.java)
@@ -42,32 +44,41 @@ open class OpenApiGeneratorTask : DefaultTask() {
4244

4345
apiDocsUrl.set(extension.apiDocsUrl.getOrElse(DEFAULT_API_DOCS_URL))
4446
outputFileName.set(extension.outputFileName.getOrElse(DEFAULT_OPEN_API_FILE_NAME))
47+
groupedApiMappings.set(extension.groupedApiMappings.getOrElse(emptyMap()))
4548
outputDir.set(extension.outputDir.getOrElse(defaultOutputDir.get()))
4649
waitTimeInSeconds.set(extension.waitTimeInSeconds.getOrElse(DEFAULT_WAIT_TIME_IN_SECONDS))
4750
}
4851

4952
@TaskAction
5053
fun execute() {
54+
if (groupedApiMappings.isPresent && groupedApiMappings.get().isNotEmpty()) {
55+
groupedApiMappings.get().forEach(this::generateApiDocs)
56+
} else {
57+
generateApiDocs(apiDocsUrl.get(), outputFileName.get())
58+
}
59+
}
60+
61+
fun generateApiDocs(url: String, fileName: String) {
5162
try {
5263
await ignoreException ConnectException::class withPollInterval Durations.ONE_SECOND atMost Duration.of(
5364
waitTimeInSeconds.get().toLong(),
5465
SECONDS
5566
) until {
56-
val statusCode = khttp.get(apiDocsUrl.get()).statusCode
57-
logger.trace("apiDocsUrl = {} status code = {}", apiDocsUrl.get(), statusCode)
67+
val statusCode = khttp.get(url).statusCode
68+
logger.trace("apiDocsUrl = {} status code = {}", url, statusCode)
5869
statusCode < 299
5970
}
6071
logger.info("Generating OpenApi Docs..")
61-
val response: Response = khttp.get(apiDocsUrl.get())
72+
val response: Response = khttp.get(url)
6273

63-
val isYaml = apiDocsUrl.get().toLowerCase().contains(".yaml")
74+
val isYaml = url.toLowerCase().contains(".yaml")
6475
val apiDocs = if (isYaml) response.text else prettifyJson(response)
6576

66-
val outputFile = outputDir.file(outputFileName.get()).get().asFile
77+
val outputFile = outputDir.file(fileName).get().asFile
6778
outputFile.writeText(apiDocs)
6879
} catch (e: ConditionTimeoutException) {
69-
this.logger.error("Unable to connect to ${apiDocsUrl.get()} waited for ${waitTimeInSeconds.get()} seconds", e)
70-
throw GradleException("Unable to connect to ${apiDocsUrl.get()} waited for ${waitTimeInSeconds.get()} seconds")
80+
this.logger.error("Unable to connect to ${url} waited for ${waitTimeInSeconds.get()} seconds", e)
81+
throw GradleException("Unable to connect to ${url} waited for ${waitTimeInSeconds.get()} seconds")
7182
}
7283
}
7384

src/test/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePluginTest.kt

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
package org.springdoc.openapi.gradle.plugin
22

33
import com.beust.klaxon.JsonObject
4-
import com.beust.klaxon.Klaxon
54
import com.beust.klaxon.Parser
65
import org.gradle.internal.impldep.org.apache.commons.lang.RandomStringUtils
76
import org.gradle.testkit.runner.BuildResult
87
import org.gradle.testkit.runner.BuildTask
98
import org.gradle.testkit.runner.GradleRunner
109
import org.gradle.testkit.runner.TaskOutcome
11-
import org.gradle.testkit.runner.internal.FeatureCheckBuildResult
1210
import org.junit.Assert.assertEquals
13-
import org.junit.Assert.assertTrue
11+
import org.junit.Assert.assertFalse
1412
import org.junit.Before
1513
import org.junit.Rule
1614
import org.junit.Test
@@ -204,6 +202,68 @@ class OpenApiGradlePluginTest {
204202
assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 1)
205203
}
206204

205+
@Test
206+
fun `using multiple grouped apis`() {
207+
val outputJsonFileNameGroupA = "openapi-groupA.json"
208+
val outputJsonFileNameGroupB = "openapi-groupB.json"
209+
210+
buildFile.writeText("""$baseBuildGradle
211+
openApi{
212+
groupedApiMappings = ["http://localhost:8080/v3/api-docs/groupA": "$outputJsonFileNameGroupA",
213+
"http://localhost:8080/v3/api-docs/groupB": "$outputJsonFileNameGroupB"]
214+
forkProperties = "-Dspring.profiles.active=multiple-grouped-apis"
215+
}
216+
""".trimMargin())
217+
218+
val result = GradleRunner.create()
219+
.withProjectDir(projectTestDir)
220+
.withArguments("clean", "generateOpenApiDocs")
221+
.withPluginClasspath()
222+
.build()
223+
224+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
225+
226+
val openApiJsonFileGroupA = File(projectBuildDir, outputJsonFileNameGroupA)
227+
assertOpenApiJsonFileIsAsExpected(openApiJsonFileGroupA, 1)
228+
229+
val openApiJsonFileGroupB = File(projectBuildDir, outputJsonFileNameGroupB)
230+
assertOpenApiJsonFileIsAsExpected(openApiJsonFileGroupB, 2)
231+
}
232+
233+
@Test
234+
fun `using multiple grouped apis should ignore single api properties`() {
235+
val outputJsonFileNameSingleGroupA = "openapi-single-groupA.json"
236+
val outputJsonFileNameGroupA = "openapi-groupA.json"
237+
val outputJsonFileNameGroupB = "openapi-groupB.json"
238+
239+
buildFile.writeText("""$baseBuildGradle
240+
openApi{
241+
apiDocsUrl = "http://localhost:8080/v3/api-docs/groupA"
242+
outputFileName = "$outputJsonFileNameSingleGroupA"
243+
groupedApiMappings = ["http://localhost:8080/v3/api-docs/groupA": "$outputJsonFileNameGroupA",
244+
"http://localhost:8080/v3/api-docs/groupB": "$outputJsonFileNameGroupB"]
245+
forkProperties = "-Dspring.profiles.active=multiple-grouped-apis"
246+
}
247+
""".trimMargin())
248+
249+
val result = GradleRunner.create()
250+
.withProjectDir(projectTestDir)
251+
.withArguments("clean", "generateOpenApiDocs")
252+
.withPluginClasspath()
253+
.build()
254+
255+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
256+
257+
val openApiJsonFileSingleGroupA = File(projectBuildDir, outputJsonFileNameSingleGroupA)
258+
assertFalse(openApiJsonFileSingleGroupA.exists())
259+
260+
val openApiJsonFileGroupA = File(projectBuildDir, outputJsonFileNameGroupA)
261+
assertOpenApiJsonFileIsAsExpected(openApiJsonFileGroupA, 1)
262+
263+
val openApiJsonFileGroupB = File(projectBuildDir, outputJsonFileNameGroupB)
264+
assertOpenApiJsonFileIsAsExpected(openApiJsonFileGroupB, 2)
265+
}
266+
207267
private fun assertOpenApiJsonFileIsAsExpected(openApiJsonFile: File, expectedNumberOfPaths: Int) {
208268
val openApiJson = getOpenApiJsonAtLocation(openApiJsonFile)
209269
assertEquals("3.0.1", openApiJson!!.string("openapi"))
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.example.demo;
2+
3+
import org.springdoc.core.GroupedOpenApi;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.context.annotation.Profile;
7+
8+
@Profile("multiple-grouped-apis")
9+
@Configuration
10+
public class GroupedConfiguration {
11+
12+
@Bean
13+
public GroupedOpenApi groupA() {
14+
return GroupedOpenApi.builder()
15+
.group("groupA")
16+
.pathsToMatch("/groupA/**")
17+
.build();
18+
}
19+
20+
@Bean
21+
public GroupedOpenApi groupB() {
22+
return GroupedOpenApi.builder()
23+
.group("groupB")
24+
.pathsToMatch("/groupB/**")
25+
.build();
26+
}
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.example.demo.endpoints;
2+
3+
import org.springframework.context.annotation.Profile;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.RestController;
6+
7+
@Profile("multiple-grouped-apis")
8+
@RestController("/grouped")
9+
public class GroupedController {
10+
11+
@GetMapping("/groupA")
12+
public String groupA(){
13+
return "groupA";
14+
}
15+
16+
@GetMapping("/groupB/first")
17+
public String groupB_first(){
18+
return "groupB_first";
19+
}
20+
21+
@GetMapping("/groupB/second")
22+
public String groupB_second(){
23+
return "groupB_second";
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test.props=So very special

0 commit comments

Comments
 (0)