Skip to content

Commit

Permalink
Merge pull request #107 from rChaoz/feat/examples-encoder
Browse files Browse the repository at this point in the history
Add example encoder config option
  • Loading branch information
SMILEY4 authored Jun 28, 2024
2 parents 058fbbf + be36ee0 commit 68976b4
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.smiley4.ktorswaggerui.examples

import io.github.smiley4.ktorswaggerui.SwaggerUI
import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor
import io.github.smiley4.ktorswaggerui.dsl.routing.get
import io.github.smiley4.ktorswaggerui.routing.openApiSpec
import io.github.smiley4.ktorswaggerui.routing.swaggerUI
Expand All @@ -12,6 +13,7 @@ import io.ktor.server.netty.Netty
import io.ktor.server.response.respondText
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import kotlin.reflect.typeOf

fun main() {
embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
Expand All @@ -37,6 +39,15 @@ private fun Application.myModule() {
)
}

// encoder used in "custom-encoder" example
encoder { type, example ->
// encode just the wrapped value for CustomEncoderData class
if (type is KTypeDescriptor && type.type == typeOf<CustomEncoderData>())
(example as CustomEncoderData).number
// return the example unmodified to fall back to default encoder
else
example
}
}
}

Expand Down Expand Up @@ -86,6 +97,19 @@ private fun Application.myModule() {
call.respondText("...")
}

get("custom-encoder", {
request {
body<CustomEncoderData> {
// The type is CustomEncoderData, but it's actually encoded as a plain number
// See configuration for encoder
example("Example 1") {
value = CustomEncoderData(123)
}
}
}
}) {
call.respondText("...")
}
}

}
Expand All @@ -94,3 +118,7 @@ private fun Application.myModule() {
private data class MyExampleClass(
val someValue: String
)

private data class CustomEncoderData(
val number: Int
)
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta
import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext
import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextImpl
import io.github.smiley4.ktorswaggerui.data.PluginConfigData
import io.github.smiley4.ktorswaggerui.data.TypeDescriptor
import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl
import io.github.smiley4.ktorswaggerui.routing.ApiSpec
import io.ktor.server.application.Application
Expand Down Expand Up @@ -93,7 +94,7 @@ private fun buildOpenApiSpec(pluginConfig: PluginConfigData, routes: List<RouteM
it.addGlobal(pluginConfig.schemaConfig)
it.add(routes)
}
val exampleContext = ExampleContextImpl().also {
val exampleContext = ExampleContextImpl(pluginConfig.exampleConfig.exampleEncoder).also {
it.addShared(pluginConfig.exampleConfig)
it.add(routes)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.swagger.v3.oas.models.examples.Example
interface ExampleContext {

/**
* Get an [Example] (or a ref to an example) by its [ExampleDescriptor]
* Get an [Example] (or a ref to an example) by its [ExampleDescriptor].
*/
fun getExample(descriptor: ExampleDescriptor): Example

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import io.swagger.v3.oas.models.examples.Example
/**
* Implementation of an [ExampleContext].
*/
class ExampleContextImpl : ExampleContext {
class ExampleContextImpl(private val encoder: ExampleEncoder?) : ExampleContext {

private val rootExamples = mutableMapOf<ExampleDescriptor, Example>()
private val componentExamples = mutableMapOf<String, Example>()
Expand All @@ -18,11 +18,11 @@ class ExampleContextImpl : ExampleContext {
*/
fun addShared(config: ExampleConfigData) {
config.sharedExamples.forEach { (_, exampleDescriptor) ->
val example = generateExample(exampleDescriptor)
val example = generateExample(exampleDescriptor, null)
componentExamples[exampleDescriptor.name] = example
}
config.securityExamples.forEach { exampleDescriptor ->
val example = generateExample(exampleDescriptor)
val example = generateExample(exampleDescriptor, null)
rootExamples[exampleDescriptor] = example
}
}
Expand All @@ -32,55 +32,59 @@ class ExampleContextImpl : ExampleContext {
* Collect and add all examples for the given routes
*/
fun add(routes: Collection<RouteMeta>) {
collectExampleDescriptors(routes).forEach { exampleDescriptor ->
rootExamples[exampleDescriptor] = generateExample(exampleDescriptor)
collectExampleDescriptors(routes).forEach { (exampleDescriptor, typeDescriptor) ->
val example = generateExample(exampleDescriptor, typeDescriptor)
rootExamples[exampleDescriptor] = example
}
}


/**
* Collect all [ExampleDescriptor]s from the given routes
*/
private fun collectExampleDescriptors(routes: Collection<RouteMeta>): List<ExampleDescriptor> {
val descriptors = mutableListOf<ExampleDescriptor>()
routes
.filter { !it.documentation.hidden }
.forEach { route ->
route.documentation.request.also { request ->
request.parameters.forEach { parameter ->
parameter.example?.also { descriptors.add(it) }
}
request.body?.also { body ->
if (body is OpenApiSimpleBodyData) {
descriptors.addAll(body.examples)
private fun collectExampleDescriptors(routes: Collection<RouteMeta>): List<Pair<ExampleDescriptor, TypeDescriptor>> =
buildList {
routes
.filter { !it.documentation.hidden }
.forEach { route ->
route.documentation.request.also { request ->
request.parameters.forEach { parameter ->
parameter.example?.also { add(it to parameter.type) }
}
request.body?.also { body ->
if (body is OpenApiSimpleBodyData) {
addAll(body.examples.map { it to body.type })
}
}
}
}
route.documentation.responses.forEach { response ->
response.body?.also { body ->
if (body is OpenApiSimpleBodyData) {
descriptors.addAll(body.examples)
route.documentation.responses.forEach { response ->
response.body?.also { body ->
if (body is OpenApiSimpleBodyData) {
addAll(body.examples.map { it to body.type })
}
}
}
}
}
return descriptors
}
}


/**
* Generate a swagger [Example] from the given [ExampleDescriptor]
*/
private fun generateExample(exampleDescriptor: ExampleDescriptor): Example {
private fun generateExample(exampleDescriptor: ExampleDescriptor, type: TypeDescriptor?): Example {
return when (exampleDescriptor) {
is ValueExampleDescriptor -> Example().also {
it.value = exampleDescriptor.value
it.value =
if (encoder != null) encoder.invoke(type, exampleDescriptor.value)
else exampleDescriptor.value
it.summary = exampleDescriptor.summary
it.description = exampleDescriptor.description
}

is RefExampleDescriptor -> Example().also {
it.`$ref` = "#/components/examples/${exampleDescriptor.refName}"
}

is SwaggerExampleDescriptor -> exampleDescriptor.example
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext
import io.github.smiley4.ktorswaggerui.data.OpenApiBaseBodyData
import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartBodyData
import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData
import io.ktor.http.ContentType
import io.ktor.http.*
import io.swagger.v3.oas.models.media.Content
import io.swagger.v3.oas.models.media.Encoding
import io.swagger.v3.oas.models.media.MediaType
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package io.github.smiley4.ktorswaggerui.data

/**
* Encoder to produce the final example value.
* Return the unmodified example to fall back to the default encoder.
*/
typealias ExampleEncoder = (type: TypeDescriptor?, example: Any?) -> Any?

class ExampleConfigData(
val sharedExamples: Map<String, ExampleDescriptor>,
val securityExamples: List<ExampleDescriptor>
val securityExamples: List<ExampleDescriptor>,
val exampleEncoder: ExampleEncoder?
) {

companion object {
val DEFAULT = ExampleConfigData(
sharedExamples = emptyMap(),
securityExamples = emptyList()
securityExamples = emptyList(),
exampleEncoder = null
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import io.github.smiley4.ktorswaggerui.dsl.routes.ValueExampleDescriptorDsl
import io.swagger.v3.oas.models.examples.Example

/**
* Configuration for schemas
* Configuration for examples
*/
@OpenApiDslMarker
class ExampleConfig {

val sharedExamples = mutableMapOf<String, ExampleDescriptor>()
var exampleEncoder: ExampleEncoder? = null

fun example(example: ExampleDescriptor) {
sharedExamples[example.name] = example
Expand All @@ -32,14 +33,18 @@ class ExampleConfig {
}
)

fun encoder(exampleEncoder: ExampleEncoder) {
this.exampleEncoder = exampleEncoder
}

fun build(securityConfig: SecurityData) = ExampleConfigData(
sharedExamples = sharedExamples,
securityExamples = securityConfig.defaultUnauthorizedResponse?.body?.let {
when (it) {
is OpenApiSimpleBodyData -> it.examples
is OpenApiMultipartBodyData -> emptyList()
}
} ?: emptyList()
} ?: emptyList(),
exampleEncoder = exampleEncoder
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class OpenApiBuilderTest : StringSpec({

private fun exampleContext(routes: List<RouteMeta>, pluginConfig: PluginConfigDsl): ExampleContext {
val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT)
return ExampleContextImpl().also {
return ExampleContextImpl(pluginConfigData.exampleConfig.exampleEncoder).also {
it.addShared(pluginConfigData.exampleConfig)
it.add(routes)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,7 @@ class OperationBuilderTest : StringSpec({

private fun exampleContext(routes: List<RouteMeta>, pluginConfig: PluginConfigDsl = defaultPluginConfig): ExampleContext {
val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT)
return ExampleContextImpl().also {
return ExampleContextImpl(pluginConfigData.exampleConfig.exampleEncoder).also {
it.addShared(pluginConfigData.exampleConfig)
it.add(routes)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class PathsBuilderTest : StringSpec({

private fun exampleContext(routes: List<RouteMeta>, pluginConfig: PluginConfigDsl): ExampleContext {
val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT)
return ExampleContextImpl().also {
return ExampleContextImpl(pluginConfigData.exampleConfig.exampleEncoder).also {
it.addShared(pluginConfigData.exampleConfig)
it.add(routes)
}
Expand Down

0 comments on commit 68976b4

Please sign in to comment.