Skip to content

Commit 0e97203

Browse files
committed
refactor(model-server): merge OpenAPI specs before using them
This commit makes the model-server-openapi folder a module again, which is now responsible for merging the individual OpenAPI specification files into a single one that's then used inside the build and for the SwaggerUI of the model-server. This has several benefits: * The SwaggerUI configuration and usage is easier as we present a single, combined API with a good overview under a single URL. * The duplicated generated of shared models such as the Problem type is prevented as the merging deduplicates the data type. Implementing merging is implemented with redocly, which was the only tool I could find for this tasks that worked. The model-server-openapi contains a decorator plugin for redocly that, as part of the merge process, prepends the server URL paths to routes so that URLs are correct in the merged result specification. The different specifications we maintain had a few duplicated response names that caused issues when merging. These duplications were resolved by renaming some instances of them. To properly categorize the different APIs in the SwaggerUI for the user, tags were added everywhere in the specifications. I could also get rid of several required imports by generating the source code into the same package that's also used for the API implementation.
1 parent 5b1f065 commit 0e97203

24 files changed

+3353
-122
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*.iml
88
kotlin_gen
99
/version.txt
10-
/node_modules
10+
**/node_modules/
1111
**/.ideaconfig
1212
**/.mpsconfig
1313
.kotlin

build.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fun computeVersion(): Any {
5151

5252
dependencies {
5353
// Generate a combined coverage report
54-
project.subprojects.forEach {
54+
project.subprojects.filterNot { it.name in setOf("model-server-openapi") }.forEach {
5555
kover(it)
5656
}
5757
}
@@ -62,7 +62,9 @@ subprojects {
6262
val subproject = this
6363
apply(plugin = "maven-publish")
6464
apply(plugin = "org.jetbrains.dokka")
65-
apply(plugin = "org.jetbrains.kotlinx.kover")
65+
if (subproject.name !in setOf("model-server-openapi")) {
66+
apply(plugin = "org.jetbrains.kotlinx.kover")
67+
}
6668

6769
version = rootProject.version
6870
group = rootProject.group
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import com.github.gradle.node.npm.task.NpxTask
2+
3+
plugins {
4+
base
5+
alias(libs.plugins.node)
6+
}
7+
8+
description = "OpenAPI specification for the model-server"
9+
10+
val specDir = layout.projectDirectory.dir("specifications")
11+
12+
val bundleDir = layout.buildDirectory.dir("bundled").get()
13+
14+
val joinedFile = layout.buildDirectory.file("model-server.yaml").get()
15+
16+
// We bundle the specs to apply the decorator that prepends the server path to each route.
17+
val bundleSpecs = tasks.register<NpxTask>("bundleSpecs") {
18+
description = "preprocesses OpenAPI specifications before joining them"
19+
20+
dependsOn(tasks.getByName("npmInstall"))
21+
22+
inputs.dir(specDir)
23+
24+
outputs.cacheIf { true }
25+
outputs.dir(bundleDir)
26+
27+
command.set("redocly")
28+
args.addAll("bundle", "--output", bundleDir.toString())
29+
}
30+
31+
// We combine all specifications into one to deduplicate things like the Problem type
32+
val joinSpecs = tasks.register<NpxTask>("joinSpecs") {
33+
description = "combines all OpenAPI specifications into a single one"
34+
35+
dependsOn(tasks.getByName("npmInstall"))
36+
dependsOn(bundleSpecs)
37+
38+
inputs.dir(bundleDir)
39+
40+
outputs.cacheIf { true }
41+
outputs.file(joinedFile)
42+
43+
command.set("redocly")
44+
args.addAll("join", "--output", joinedFile.toString())
45+
// We sort the v2 file first because it determines the meta-data of the generated joined file.
46+
// This list of files needs to be kept in sync with the contents of redocly.yaml. We cannot dynamically detect the
47+
// files generated by redocly here as NPM argas can only be passed in the configuration phase of Gradle. At that
48+
// point in time, no files have been generated yet on fresh builds.
49+
args.addAll(
50+
listOf(
51+
"model-server-v2.yaml",
52+
"model-server-v1.yaml",
53+
"model-server-operative.yaml",
54+
).map { bundleDir.file(it).toString() },
55+
)
56+
}
57+
58+
// This provides the resulting joined OpenAPI specification to the model-server project to be declared as a dependency.
59+
// Cf. https://docs.gradle.org/current/userguide/cross_project_publications.html#cross_project_publications
60+
val openApiSpec by configurations.creating {
61+
isCanBeConsumed = true
62+
isCanBeResolved = false
63+
}
64+
artifacts {
65+
add(openApiSpec.name, joinedFile) {
66+
builtBy(joinSpecs)
67+
}
68+
}

0 commit comments

Comments
 (0)