Skip to content

Commit

Permalink
Merge pull request #108 from rChaoz/fix/defaultUnauthorizedResponse
Browse files Browse the repository at this point in the history
Fix "no root-schema for given type-descriptor" in `defaultUnauthorizedResponse`
  • Loading branch information
SMILEY4 authored Jun 15, 2024
2 parents c634fa9 + 87fd9b0 commit 912e69d
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 59 deletions.
2 changes: 1 addition & 1 deletion detekt/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -785,4 +785,4 @@ style:
WildcardImport:
active: false
excludeImports:
- 'java.util.*'
- 'java.util.*'
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package io.github.smiley4.ktorswaggerui.builder.example

import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta
import io.github.smiley4.ktorswaggerui.data.ExampleConfigData
import io.github.smiley4.ktorswaggerui.data.ExampleDescriptor
import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData
import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor
import io.github.smiley4.ktorswaggerui.data.SwaggerExampleDescriptor
import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor
import io.github.smiley4.ktorswaggerui.data.*
import io.swagger.v3.oas.models.examples.Example

/**
Expand All @@ -26,6 +21,10 @@ class ExampleContextImpl : ExampleContext {
val example = generateExample(exampleDescriptor)
componentExamples[exampleDescriptor.name] = example
}
config.securityExamples.forEach { exampleDescriptor ->
val example = generateExample(exampleDescriptor)
rootExamples[exampleDescriptor] = example
}
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
package io.github.smiley4.ktorswaggerui.builder.schema

import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta
import io.github.smiley4.ktorswaggerui.data.AnyOfTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.ArrayTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.EmptyTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartBodyData
import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData
import io.github.smiley4.ktorswaggerui.data.RefTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.SchemaConfigData
import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.TypeDescriptor
import io.github.smiley4.schemakenerator.core.data.TypeId
import io.github.smiley4.ktorswaggerui.data.*
import io.github.smiley4.schemakenerator.core.data.WildcardTypeData
import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema
import io.github.smiley4.schemakenerator.swagger.steps.SwaggerSchemaUtils
Expand All @@ -24,6 +14,10 @@ class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : SchemaCont
private val componentSchemas = mutableMapOf<String, Schema<*>>()

fun addGlobal(config: SchemaConfigData) {
config.securitySchemas.forEach { typeDescriptor ->
val schema = collapseRootRef(generateSchema(typeDescriptor))
rootSchemas[typeDescriptor] = schema.swagger
}
config.schemas.forEach { (schemaId, typeDescriptor) ->
val schema = collapseRootRef(generateSchema(typeDescriptor))
componentSchemas[schemaId] = schema.swagger
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.github.smiley4.ktorswaggerui.data

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import kotlin.reflect.KType
data class SchemaConfigData(
val schemas: Map<String, TypeDescriptor>,
val generator: (type: KType) -> CompiledSwaggerSchema,
val overwrite: Map<KType, TypeDescriptor>
val overwrite: Map<KType, TypeDescriptor>,
val securitySchemas: List<TypeDescriptor>
) {
companion object {
val DEFAULT = SchemaConfigData(
Expand All @@ -34,7 +35,8 @@ data class SchemaConfigData(
.withAutoTitle(TitleType.SIMPLE)
.compileReferencingRoot()
},
overwrite = emptyMap()
overwrite = emptyMap(),
securitySchemas = emptyList()
)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package io.github.smiley4.ktorswaggerui.dsl.config

import io.github.smiley4.ktorswaggerui.data.ExampleConfigData
import io.github.smiley4.ktorswaggerui.data.ExampleDescriptor
import io.github.smiley4.ktorswaggerui.data.SwaggerExampleDescriptor
import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor
import io.github.smiley4.ktorswaggerui.data.*
import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker
import io.github.smiley4.ktorswaggerui.dsl.routes.ValueExampleDescriptorDsl
import io.swagger.v3.oas.models.examples.Example
Expand Down Expand Up @@ -35,8 +32,14 @@ class ExampleConfig {
}
)

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package io.github.smiley4.ktorswaggerui.dsl.config
import io.github.smiley4.ktorswaggerui.data.*
import io.github.smiley4.ktorswaggerui.data.DataUtils.merge
import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker
import io.ktor.http.*
import io.ktor.server.routing.*
import kotlin.collections.component1
import kotlin.collections.component2
Expand Down Expand Up @@ -111,7 +110,7 @@ class PluginConfigDsl {
private val specConfigs = mutableMapOf<String, PluginConfigDsl>()

/**
* Assigns routes without an [io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute.specId] to a specified openapi-spec.
* Assigns routes without an [io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRoute.specId]] to a specified openapi-spec.
*/
var specAssigner: SpecAssigner? = PluginConfigData.DEFAULT.specAssigner

Expand All @@ -136,6 +135,7 @@ class PluginConfigDsl {


internal fun build(base: PluginConfigData): PluginConfigData {
val securityConfig = security.build(base.securityConfig)
return PluginConfigData(
info = info.build(base.info),
externalDocs = externalDocs.build(base.externalDocs),
Expand All @@ -144,10 +144,10 @@ class PluginConfigDsl {
addAll(servers.map { it.build(ServerData.DEFAULT) })
},
swagger = swaggerUI.build(base.swagger),
securityConfig = security.build(base.securityConfig),
securityConfig = securityConfig,
tagsConfig = tags.build(base.tagsConfig),
schemaConfig = schemaConfig.build(),
exampleConfig = exampleConfig.build(),
schemaConfig = schemaConfig.build(securityConfig),
exampleConfig = exampleConfig.build(securityConfig),
specAssigner = merge(base.specAssigner, specAssigner) ?: PluginConfigData.DEFAULT.specAssigner,
pathFilter = merge(base.pathFilter, pathFilter) ?: PluginConfigData.DEFAULT.pathFilter,
ignoredRouteSelectors = buildSet {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package io.github.smiley4.ktorswaggerui.dsl.config

import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.SchemaConfigData
import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.TypeDescriptor
import io.github.smiley4.ktorswaggerui.data.*
import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker
import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema
import io.swagger.v3.oas.models.media.Schema
Expand Down Expand Up @@ -34,7 +31,6 @@ class SchemaConfig {

inline fun <reified T, reified R> overwrite() = overwrite(typeOf<T>(), KTypeDescriptor(typeOf<R>()))


fun schema(schemaId: String, descriptor: TypeDescriptor) {
schemas[schemaId] = descriptor
}
Expand All @@ -45,10 +41,16 @@ class SchemaConfig {

inline fun <reified T> schema(schemaId: String) = schema(schemaId, KTypeDescriptor(typeOf<T>()))

fun build() = SchemaConfigData(
fun build(securityConfig: SecurityData) = SchemaConfigData(
generator = generator,
schemas = schemas,
overwrite = overwrite
overwrite = overwrite,
securitySchemas = securityConfig.defaultUnauthorizedResponse?.body?.let {
when (it) {
is OpenApiSimpleBodyData -> listOf(it.type)
is OpenApiMultipartBodyData -> it.parts.map { it.type }
}
} ?: emptyList()
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,23 @@ package io.github.smiley4.ktorswaggerui.builder

import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext
import io.github.smiley4.ktorswaggerui.builder.example.ExampleContextImpl
import io.github.smiley4.ktorswaggerui.builder.openapi.ContentBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.ExternalDocumentationBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.HeaderBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.OperationBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.OperationTagsBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.ParameterBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.RequestBodyBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.ResponseBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.ResponsesBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.SecurityRequirementsBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.ServerBuilder
import io.github.smiley4.ktorswaggerui.builder.openapi.*
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.KTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.PluginConfigData
import io.github.smiley4.ktorswaggerui.data.RefTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor
import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor
import io.github.smiley4.ktorswaggerui.data.*
import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl
import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRoute
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.maps.shouldBeEmpty
import io.kotest.matchers.maps.shouldHaveSize
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.http.*
import io.swagger.v3.oas.models.Operation
import io.swagger.v3.oas.models.media.Schema
import java.io.File
Expand Down Expand Up @@ -732,6 +717,55 @@ class OperationBuilderTest : StringSpec({
}
}

"automatic unauthorized response with body type and example" {
val config = PluginConfigDsl().also {
it.security {
defaultUnauthorizedResponse {
description = "Default unauthorized Response"
body<SimpleObject> {
example("Example 1") {
value = SimpleObject(text = "Some text", number = 123)
}
}
}
}
}
val route = RouteMeta(
path = "/test",
method = HttpMethod.Get,
documentation = OpenApiRoute().also { route ->
route.response {
default {
description = "Default Response"
}
}
}.build(),
protected = true
)
val schemaContext = schemaContext(listOf(route), config)
val exampleContext = exampleContext(listOf(route), config)
buildOperationObject(route, schemaContext, exampleContext, config).also { operation ->
operation.responses
.also { it shouldHaveSize 2 }
?.also { responses ->
responses["401"]
.also { it.shouldNotBeNull() }
?.also { it.description shouldBe "Default unauthorized Response" }
?.also { response ->
val content = response.content["application/json"]
content.shouldNotBeNull()
content.schema.title shouldBe "SimpleObject"
val example = content.examples["Example 1"]
example.shouldNotBeNull()
example.value shouldBeEqual SimpleObject(text = "Some text", number = 123)
}
responses["default"]
.also { it.shouldNotBeNull() }
?.also { it.description shouldBe "Default Response" }
}
}
}

"automatic unauthorized response for unprotected route" {
val config = PluginConfigDsl().also {
it.security {
Expand Down

0 comments on commit 912e69d

Please sign in to comment.