Skip to content

Commit

Permalink
move rootPath into plugin config
Browse files Browse the repository at this point in the history
  • Loading branch information
SMILEY4 committed Jan 8, 2025
1 parent 7474d09 commit b8912a1
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import io.ktor.server.routing.get
private val logger = KotlinLogging.logger {}

val OpenApi = createApplicationPlugin(name = "OpenApi", createConfiguration = ::OpenApiPluginConfig) {
OpenApiPlugin.config = pluginConfig.build(OpenApiPluginData.DEFAULT)
OpenApiPlugin.config = pluginConfig.build(
OpenApiPluginData.DEFAULT,
application.environment.config.propertyOrNull("ktor.deployment.rootPath")?.getString()
)
on(MonitoringEvent(ApplicationStarted)) { application ->
try {
OpenApiPlugin.generateOpenApiSpecs(application)
Expand All @@ -37,7 +40,8 @@ object OpenApiPlugin {

internal var config = OpenApiPluginData.DEFAULT

internal val openApiSpecs = mutableMapOf<String, Pair<String, OutputFormat>>()
private val openApiSpecs = mutableMapOf<String, Pair<String, OutputFormat>>()


/**
* Generates new openapi
Expand All @@ -49,8 +53,6 @@ object OpenApiPlugin {
openApiSpecs.putAll(specs)
}

fun getOpenApiSpecNames(): Set<String> = openApiSpecs.keys.toSet()

fun getOpenApiSpec(name: String): String = openApiSpecs[name]?.first
?: throw IllegalArgumentException("No OpenAPI documentation exists with name '$name'")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ internal class OpenApiSpecBuilder {
tagExternalDocumentationBuilder = TagExternalDocumentationBuilder()
),
pathsBuilder = PathsBuilder(
config = config,
pathBuilder = PathBuilder(
operationBuilder = OperationBuilder(
operationTagsBuilder = OperationTagsBuilder(config),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.smiley4.ktoropenapi.builder.openapi

import io.github.smiley4.ktoropenapi.builder.route.RouteMeta
import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
import io.swagger.v3.oas.models.PathItem
import io.swagger.v3.oas.models.Paths

Expand All @@ -9,23 +10,29 @@ import io.swagger.v3.oas.models.Paths
* See [OpenAPI Specification - Paths Object](https://swagger.io/specification/#paths-object).
*/
internal class PathsBuilder(
private val config: OpenApiPluginData,
private val pathBuilder: PathBuilder
) {

fun build(routes: Collection<RouteMeta>): Paths =
Paths().also {
routes.forEach { route ->
val existingPath = it[route.path]
val url = createUrl(route)
val existingPath = it[url]
if (existingPath != null) {
addToExistingPath(existingPath, route)
} else {
addAsNewPath(it, route)
addAsNewPath(url, it, route)
}
}
}

private fun addAsNewPath(paths: Paths, route: RouteMeta) {
paths.addPathItem(route.path, pathBuilder.build(route))
private fun createUrl(route: RouteMeta): String {
return "${config.rootPath ?: ""}${route.path}"
}

private fun addAsNewPath(url: String, paths: Paths, route: RouteMeta) {
paths.addPathItem(url, pathBuilder.build(route))
}

private fun addToExistingPath(existing: PathItem, route: RouteMeta) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ internal class RouteCollector {
val documentation = getDocumentation(route, RouteConfig())
RouteMeta(
method = getMethod(route),
path = getPath(route, config).let { "${config.rootPath ?: ""}${it}" },
path = getPath(route, config),
documentation = documentation.build(),
protected = documentation.protected ?: isProtected(route)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal data class OpenApiPluginData(
securityConfig = SecurityData.DEFAULT,
tagsConfig = TagsData.DEFAULT,
outputFormat = OutputFormat.JSON,
rootPath = "todo" // TODO
rootPath = null
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.github.smiley4.ktoropenapi.dsl.config

import io.github.smiley4.ktoropenapi.data.DataUtils.merge
import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
import io.github.smiley4.ktoropenapi.data.OutputFormat
import io.github.smiley4.ktoropenapi.data.PathFilter
import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
import io.github.smiley4.ktoropenapi.data.PostBuild
import io.github.smiley4.ktoropenapi.data.ServerData
import io.github.smiley4.ktoropenapi.data.SpecAssigner
Expand Down Expand Up @@ -123,11 +123,13 @@ class OpenApiPluginConfig {
*/
var ignoredRouteSelectors: Set<KClass<*>> = OpenApiPluginData.DEFAULT.ignoredRouteSelectors


/**
* List of all [RouteSelector] class names that should be ignored in the resulting url of any route.
*/
var ignoredRouteSelectorClassNames: Set<String> = emptySet()


/**
* The format of the generated api-spec
*/
Expand All @@ -140,11 +142,17 @@ class OpenApiPluginConfig {
var postBuild: PostBuild? = null


/**
* Root path of the ktor-application to prepend to the paths. "null" to automatically fetch from ktor configuration.
*/
var rootPath: String? = OpenApiPluginData.DEFAULT.rootPath


/**
* Build the data object for this config.
* @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
*/
internal fun build(base: OpenApiPluginData): OpenApiPluginData {
internal fun build(base: OpenApiPluginData, ktorRootPath: String?): OpenApiPluginData {
val securityConfig = security.build(base.securityConfig)
return OpenApiPluginData(
info = info.build(base.info),
Expand All @@ -170,11 +178,12 @@ class OpenApiPluginConfig {
specConfigs = mutableMapOf(),
postBuild = merge(base.postBuild, postBuild),
outputFormat = outputFormat,
rootPath = "todo" // todo
rootPath = merge(rootPath ?: ktorRootPath, base.rootPath)
).also {
specConfigs.forEach { (specName, config) ->
it.specConfigs[specName] = config.build(it)
it.specConfigs[specName] = config.build(it, ktorRootPath)
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ class OpenApiBuilderTest : StringSpec({
private val defaultPluginConfig = OpenApiPluginConfig()

private fun schemaContext(routes: List<RouteMeta>, pluginConfig: OpenApiPluginConfig): SchemaContext {
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return SchemaContextImpl(pluginConfigData.schemaConfig).also {
it.addGlobal(pluginConfigData.schemaConfig)
it.add(routes)
}
}

private fun exampleContext(routes: List<RouteMeta>, pluginConfig: OpenApiPluginConfig): ExampleContext {
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return ExampleContextImpl(pluginConfigData.exampleConfig.exampleEncoder).also {
it.addShared(pluginConfigData.exampleConfig)
it.add(routes)
Expand All @@ -125,7 +125,7 @@ class OpenApiBuilderTest : StringSpec({
private fun buildOpenApiObject(routes: List<RouteMeta>, pluginConfig: OpenApiPluginConfig = defaultPluginConfig): OpenAPI {
val schemaContext = schemaContext(routes, pluginConfig)
val exampleContext = exampleContext(routes, pluginConfig)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return OpenApiBuilder(
config = pluginConfigData,
schemaContext = schemaContext,
Expand All @@ -140,6 +140,7 @@ class OpenApiBuilderTest : StringSpec({
tagExternalDocumentationBuilder = TagExternalDocumentationBuilder()
),
pathsBuilder = PathsBuilder(
config = pluginConfigData,
pathBuilder = PathBuilder(
operationBuilder = OperationBuilder(
operationTagsBuilder = OperationTagsBuilder(pluginConfigData),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1002,15 +1002,15 @@ class OperationBuilderTest : StringSpec({


private fun schemaContext(routes: List<RouteMeta>, pluginConfig: OpenApiPluginConfig = defaultPluginConfig): SchemaContext {
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return SchemaContextImpl(pluginConfigData.schemaConfig).also {
it.addGlobal(pluginConfigData.schemaConfig)
it.add(routes)
}
}

private fun exampleContext(routes: List<RouteMeta>, pluginConfig: OpenApiPluginConfig = defaultPluginConfig): ExampleContext {
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return ExampleContextImpl(pluginConfigData.exampleConfig.exampleEncoder).also {
it.addShared(pluginConfigData.exampleConfig)
it.add(routes)
Expand All @@ -1023,7 +1023,7 @@ class OperationBuilderTest : StringSpec({
exampleContext: ExampleContext,
pluginConfig: OpenApiPluginConfig = defaultPluginConfig
): Operation {
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return OperationBuilder(
operationTagsBuilder = OperationTagsBuilder(pluginConfigData),
parameterBuilder = ParameterBuilder(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ class PathsBuilderTest : StringSpec({
}
}

"custom root path" {
val config = defaultPluginConfig.apply {
rootPath = "custom/root/path"
}
val routes = listOf(
route(HttpMethod.Get, "/different/path"),
route(HttpMethod.Get, "/test/path"),
route(HttpMethod.Post, "/test/path"),
)
val schemaContext = schemaContext(routes, config)
val exampleContext = exampleContext(routes, config)
buildPathsObject(routes, schemaContext, exampleContext, config).also { paths ->
paths shouldHaveSize 2
paths.keys shouldContainExactlyInAnyOrder listOf(
"custom/root/path/different/path",
"custom/root/path/test/path"
)
paths["custom/root/path/different/path"]!!.get.shouldNotBeNull()
paths["custom/root/path/test/path"]!!.get.shouldNotBeNull()
paths["custom/root/path/test/path"]!!.post.shouldNotBeNull()
}
}

}) {

companion object {
Expand All @@ -86,15 +109,15 @@ class PathsBuilderTest : StringSpec({
private val defaultPluginConfig = OpenApiPluginConfig()

private fun schemaContext(routes: List<RouteMeta>, pluginConfig: OpenApiPluginConfig): SchemaContext {
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return SchemaContextImpl(pluginConfigData.schemaConfig).also {
it.addGlobal(pluginConfigData.schemaConfig)
it.add(routes)
}
}

private fun exampleContext(routes: List<RouteMeta>, pluginConfig: OpenApiPluginConfig): ExampleContext {
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return ExampleContextImpl(pluginConfigData.exampleConfig.exampleEncoder).also {
it.addShared(pluginConfigData.exampleConfig)
it.add(routes)
Expand All @@ -107,8 +130,9 @@ class PathsBuilderTest : StringSpec({
exampleContext: ExampleContext,
pluginConfig: OpenApiPluginConfig = defaultPluginConfig
): Paths {
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT)
val pluginConfigData = pluginConfig.build(OpenApiPluginData.DEFAULT, null)
return PathsBuilder(
config = pluginConfigData,
pathBuilder = PathBuilder(
operationBuilder = OperationBuilder(
operationTagsBuilder = OperationTagsBuilder(pluginConfigData),
Expand Down

0 comments on commit b8912a1

Please sign in to comment.