From b4817955bf9989b9131a1315a0c08fad178e69c0 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 2 May 2024 19:07:48 +0200 Subject: [PATCH 01/32] changes --- NOTES.txt | 6 + build.gradle.kts | 11 +- .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 75 +- .../builder/example/ExampleContext.kt | 52 +- .../builder/example/ExampleContextBuilder.kt | 160 +-- .../builder/openapi/ContentBuilder.kt | 130 ++- .../builder/openapi/ExampleBuilder.kt | 46 +- .../builder/openapi/HeaderBuilder.kt | 4 +- .../builder/openapi/OpenApiBuilder.kt | 5 +- .../builder/openapi/OperationBuilder.kt | 6 +- .../builder/openapi/ParameterBuilder.kt | 15 +- .../builder/openapi/RequestBodyBuilder.kt | 4 +- .../builder/openapi/ResponseBuilder.kt | 8 +- .../builder/openapi/ResponsesBuilder.kt | 10 +- .../openapi/SecurityRequirementsBuilder.kt | 13 +- .../builder/route/RouteCollector.kt | 25 +- .../builder/route/RouteDocumentationMerger.kt | 2 +- .../ktorswaggerui/builder/route/RouteMeta.kt | 4 +- .../builder/schema/SchemaBuilder.kt | 110 -- .../builder/schema/SchemaContext.kt | 244 +---- .../builder/schema/SchemaContextBuilder.kt | 143 --- .../builder/schema/SchemaContextImpl.kt | 137 +++ .../schema/SchemaTypeAttributeOverride.kt | 29 - .../builder/schema/TypeOverwrites.kt | 21 - .../ktorswaggerui/data/BaseCustomSchema.kt | 11 - .../ktorswaggerui/data/EncodingData.kt | 100 -- .../ktorswaggerui/data/OpenApiBaseBodyData.kt | 24 + .../ktorswaggerui/data/OpenApiExampleData.kt | 7 + .../ktorswaggerui/data/OpenApiHeaderData.kt | 8 + .../data/OpenApiMultipartPartData.kt | 10 + .../ktorswaggerui/data/OpenApiRequestData.kt | 6 + .../data/OpenApiRequestParameterData.kt | 14 + .../ktorswaggerui/data/OpenApiResponseData.kt | 8 + .../ktorswaggerui/data/OpenApiRouteData.kt | 15 + .../ktorswaggerui/data/ParameterLocation.kt | 5 + .../smiley4/ktorswaggerui/data/PathFilter.kt | 1 - .../ktorswaggerui/data/PluginConfigData.kt | 11 +- .../ktorswaggerui/data/TypeDescriptor.kt | 17 + .../ktorswaggerui/dsl/BodyTypeDescriptor.kt | 130 --- .../ktorswaggerui/dsl/CustomSchemaRef.kt | 14 - .../ktorswaggerui/dsl/CustomSchemas.kt | 49 - .../ktorswaggerui/dsl/EncodingConfig.kt | 50 - .../smiley4/ktorswaggerui/dsl/Example.kt | 9 - .../ktorswaggerui/dsl/OpenApiHeader.kt | 31 - .../ktorswaggerui/dsl/OpenApiMultipartBody.kt | 68 -- .../ktorswaggerui/dsl/OpenApiMultipartPart.kt | 61 -- .../ktorswaggerui/dsl/OpenApiRequest.kt | 188 ---- .../ktorswaggerui/dsl/OpenApiResponse.kt | 130 --- .../smiley4/ktorswaggerui/dsl/SchemaType.kt | 36 - .../dsl/{ => config}/OpenApiContact.kt | 3 +- .../dsl/{ => config}/OpenApiExternalDocs.kt | 3 +- .../dsl/{ => config}/OpenApiInfo.kt | 3 +- .../dsl/{ => config}/OpenApiLicense.kt | 3 +- .../dsl/{ => config}/OpenApiSecurityScheme.kt | 3 +- .../dsl/{ => config}/OpenApiServer.kt | 3 +- .../dsl/{ => config}/OpenApiTag.kt | 3 +- .../dsl/{ => config}/OpenIdOAuthFlow.kt | 3 +- .../dsl/{ => config}/OpenIdOAuthFlows.kt | 3 +- .../dsl/{ => config}/PluginConfigDsl.kt | 36 +- .../dsl/{ => config}/SwaggerUIDsl.kt | 3 +- .../dsl/{ => routes}/OpenApiBaseBody.kt | 5 +- .../dsl/{ => routes}/OpenApiExample.kt | 11 +- .../ktorswaggerui/dsl/routes/OpenApiHeader.kt | 41 + .../dsl/routes/OpenApiMultipartBody.kt | 77 ++ .../dsl/routes/OpenApiMultipartPart.kt | 73 ++ .../dsl/routes/OpenApiRequest.kt | 188 ++++ .../{ => routes}/OpenApiRequestParameter.kt | 30 +- .../dsl/routes/OpenApiResponse.kt | 143 +++ .../dsl/{ => routes}/OpenApiResponses.kt | 3 +- .../dsl/{ => routes}/OpenApiRoute.kt | 29 +- .../dsl/{ => routes}/OpenApiSimpleBody.kt | 17 +- .../{ => routing}/DocumentedRouteSelector.kt | 3 +- .../resources/DocumentedResourceRoutes.kt | 8 +- .../ktorswaggerui/routing/ControllerUtils.kt | 18 - .../routing/ForwardRouteController.kt | 35 - .../routing/SwaggerController.kt | 130 --- .../routing/{manualRouting.kt => routing.kt} | 4 +- .../{examples/RootPathExample.kt => Test.kt} | 39 +- .../ktorswaggerui/examples/AuthExample.kt | 120 --- .../ktorswaggerui/examples/CompleteExample.kt | 288 ----- .../examples/CompletePluginConfigExample.kt | 112 -- .../examples/CustomSchemaExample.kt | 149 --- .../examples/ExampleAnnotationExample.kt | 119 --- .../examples/FileUploadExample.kt | 68 -- .../ktorswaggerui/examples/KotlinxExample.kt | 92 -- .../ktorswaggerui/examples/ManualRouting.kt | 43 - .../ManualRoutingMultipleSpecsExample.kt | 121 --- .../ktorswaggerui/examples/MinimalExample.kt | 50 - .../ktorswaggerui/examples/MultipartBody.kt | 58 - .../examples/MultipleSpecsExample.kt | 102 -- .../ktorswaggerui/examples/Petstore.kt | 148 --- .../examples/ProtectedSwaggerExample.kt | 57 - .../ktorswaggerui/examples/ResourceExample.kt | 132 --- .../examples/SchemaAnnotationExample.kt | 60 -- .../ktorswaggerui/tests/ApplicationTests.kt | 481 --------- .../tests/example/ExampleTest.kt | 201 ---- .../tests/openapi/ExternalDocsBuilderTest.kt | 39 - .../tests/openapi/InfoBuilderTest.kt | 81 -- .../tests/openapi/OpenApiBuilderTest.kt | 161 --- .../tests/openapi/OperationBuilderTest.kt | 999 ------------------ .../tests/openapi/PathsBuilderTest.kt | 156 --- .../openapi/SecuritySchemesBuilderTest.kt | 210 ---- .../tests/openapi/ServersBuilderTest.kt | 44 - .../tests/openapi/TagsBuilderTest.kt | 55 - .../route/RouteDocumentationMergerTest.kt | 138 --- .../tests/schema/SchemaBuilderTest.kt | 519 --------- .../tests/schema/SchemaContextTest.kt | 553 ---------- 107 files changed, 1200 insertions(+), 7339 deletions(-) create mode 100644 NOTES.txt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaBuilder.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextBuilder.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaTypeAttributeOverride.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/TypeOverwrites.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/BaseCustomSchema.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/EncodingData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/BodyTypeDescriptor.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/CustomSchemaRef.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/CustomSchemas.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/EncodingConfig.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/Example.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiHeader.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiMultipartBody.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiMultipartPart.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRequest.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiResponse.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/SchemaType.kt rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenApiContact.kt (88%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenApiExternalDocs.kt (85%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenApiInfo.kt (94%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenApiLicense.kt (84%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenApiSecurityScheme.kt (95%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenApiServer.kt (88%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenApiTag.kt (89%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenIdOAuthFlow.kt (90%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/OpenIdOAuthFlows.kt (94%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/PluginConfigDsl.kt (85%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => config}/SwaggerUIDsl.kt (97%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => routes}/OpenApiBaseBody.kt (76%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => routes}/OpenApiExample.kt (53%) create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => routes}/OpenApiRequestParameter.kt (65%) create mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => routes}/OpenApiResponses.kt (89%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => routes}/OpenApiRoute.kt (75%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => routes}/OpenApiSimpleBody.kt (53%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => routing}/DocumentedRouteSelector.kt (98%) rename src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/{ => routing}/resources/DocumentedResourceRoutes.kt (93%) delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ControllerUtils.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ForwardRouteController.kt delete mode 100644 src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/SwaggerController.kt rename src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/{manualRouting.kt => routing.kt} (96%) rename src/test/kotlin/io/github/smiley4/ktorswaggerui/{examples/RootPathExample.kt => Test.kt} (58%) delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/AuthExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompletePluginConfigExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemaExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ExampleAnnotationExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUploadExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/KotlinxExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ManualRouting.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ManualRoutingMultipleSpecsExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MinimalExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipartBody.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipleSpecsExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ProtectedSwaggerExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ResourceExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaAnnotationExample.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/ApplicationTests.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/example/ExampleTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ExternalDocsBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/InfoBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OpenApiBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OperationBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/PathsBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/SecuritySchemesBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ServersBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/TagsBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/route/RouteDocumentationMergerTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/schema/SchemaBuilderTest.kt delete mode 100644 src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/schema/SchemaContextTest.kt diff --git a/NOTES.txt b/NOTES.txt new file mode 100644 index 0000000..26fcb49 --- /dev/null +++ b/NOTES.txt @@ -0,0 +1,6 @@ +- clean separation between /data and /dsl +- cleanup /dsl -> remove no longer required getters, make fields private, etc. +- cleaner handling of custom schemas, etc +- cleaner handling of schema generation -> schema-kenerator + nicer config +- drop automatic routing +- general cleanup \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 757ed3c..6900fc5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ import io.gitlab.arturbosch.detekt.Detekt object Meta { const val groupId = "io.github.smiley4" const val artifactId = "ktor-swagger-ui" - const val version = "2.10.0" + const val version = "3.0.0-indev" const val name = "Ktor Swagger-UI" const val description = "Ktor plugin to document routes and provide Swagger UI" const val licenseName = "The Apache License, Version 2.0" @@ -28,6 +28,7 @@ plugins { repositories { mavenCentral() + mavenLocal() maven(url = "https://raw.githubusercontent.com/glureau/json-schema-serialization/mvn-repo") } @@ -51,10 +52,10 @@ dependencies { val swaggerParserVersion = "2.1.19" implementation("io.swagger.parser.v3:swagger-parser:$swaggerParserVersion") - val jsonSchemaGeneratorVersion = "4.33.1" - implementation("com.github.victools:jsonschema-generator:$jsonSchemaGeneratorVersion") - implementation("com.github.victools:jsonschema-module-jackson:$jsonSchemaGeneratorVersion") - implementation("com.github.victools:jsonschema-module-swagger-2:$jsonSchemaGeneratorVersion") + val schemaKeneratorVersion = "0.1" + implementation("io.github.smiley4:schema-kenerator-core:$schemaKeneratorVersion") + implementation("io.github.smiley4:schema-kenerator-reflection:$schemaKeneratorVersion") + implementation("io.github.smiley4:schema-kenerator-swagger:$schemaKeneratorVersion") val jacksonVersion = "2.15.3" implementation("com.fasterxml.jackson.module:jackson-module-kotlin:${jacksonVersion}") diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index b3977bf..cc62213 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -1,25 +1,46 @@ package io.github.smiley4.ktorswaggerui -import com.fasterxml.jackson.databind.ObjectMapper -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContextBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.* +import io.github.smiley4.ktorswaggerui.builder.openapi.ComponentsBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.ContactBuilder +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.InfoBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.LicenseBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.OAuthFlowsBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.OpenApiBuilder +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.PathBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.PathsBuilder +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.SecuritySchemesBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.ServerBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.TagBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.TagExternalDocumentationBuilder import io.github.smiley4.ktorswaggerui.builder.route.RouteCollector import io.github.smiley4.ktorswaggerui.builder.route.RouteDocumentationMerger import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaBuilder import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.TypeOverwrites +import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextImpl import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl +import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl import io.github.smiley4.ktorswaggerui.routing.ApiSpec import io.github.smiley4.ktorswaggerui.routing.ForwardRouteController import io.github.smiley4.ktorswaggerui.routing.SwaggerController -import io.ktor.server.application.* -import io.ktor.server.application.hooks.* -import io.ktor.server.routing.* -import io.ktor.server.webjars.* +import io.ktor.server.application.Application +import io.ktor.server.application.ApplicationStarted +import io.ktor.server.application.createApplicationPlugin +import io.ktor.server.application.hooks.MonitoringEvent +import io.ktor.server.application.install +import io.ktor.server.application.plugin +import io.ktor.server.application.pluginOrNull +import io.ktor.server.routing.Routing +import io.ktor.server.webjars.Webjars import io.swagger.v3.core.util.Json import mu.KotlinLogging import kotlin.collections.component1 @@ -86,9 +107,8 @@ private fun buildOpenApiSpecs(config: PluginConfigData, routes: List) private fun buildOpenApiSpec(pluginConfig: PluginConfigData, routes: List): String { return try { - val schemaContext = schemaContext(pluginConfig, routes) - val exampleContext = exampleContext(pluginConfig, routes) - val openApi = builder(pluginConfig, schemaContext, exampleContext).build(routes) + val schemaContext = SchemaContextImpl().also { it.add(routes) } + val openApi = builder(pluginConfig, schemaContext).build(routes) pluginConfig.whenBuildOpenApiSpecs?.invoke(openApi) Json.pretty(openApi) } catch (e: Exception) { @@ -103,35 +123,13 @@ private fun routes(application: Application, config: PluginConfigData): List): SchemaContext { - return SchemaContextBuilder( - config = config, - schemaBuilder = SchemaBuilder( - definitionsField = config.encoding.schemaDefsField, - schemaEncoder = config.encoding.schemaEncoder, - ObjectMapper(), - TypeOverwrites.get() - ), - ).build(routes.toList()) -} - -private fun exampleContext(config: PluginConfigData, routes: List): ExampleContext { - return ExampleContextBuilder( - exampleBuilder = ExampleBuilder( - config = config - ) - ).build(routes.toList()) -} - private fun builder( config: PluginConfigData, schemaContext: SchemaContext, - exampleContext: ExampleContext ): OpenApiBuilder { return OpenApiBuilder( config = config, schemaContext = schemaContext, - exampleContext = exampleContext, infoBuilder = InfoBuilder( contactBuilder = ContactBuilder(), licenseBuilder = LicenseBuilder() @@ -147,12 +145,10 @@ private fun builder( operationTagsBuilder = OperationTagsBuilder(config), parameterBuilder = ParameterBuilder( schemaContext = schemaContext, - exampleContext = exampleContext ), requestBodyBuilder = RequestBodyBuilder( contentBuilder = ContentBuilder( schemaContext = schemaContext, - exampleContext = exampleContext, headerBuilder = HeaderBuilder(schemaContext) ) ), @@ -161,7 +157,6 @@ private fun builder( headerBuilder = HeaderBuilder(schemaContext), contentBuilder = ContentBuilder( schemaContext = schemaContext, - exampleContext = exampleContext, headerBuilder = HeaderBuilder(schemaContext) ) ), diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt index 8c031c3..6286f79 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt @@ -1,26 +1,26 @@ -package io.github.smiley4.ktorswaggerui.builder.example - -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRequestParameter -import io.github.smiley4.ktorswaggerui.dsl.OpenApiSimpleBody -import io.swagger.v3.oas.models.examples.Example - -class ExampleContext { - - private val examplesParameters = mutableMapOf() - private val examplesBody = mutableMapOf, Example>() - - fun addExample(parameter: OpenApiRequestParameter, value: String) { - examplesParameters[parameter] = value - } - - fun addExample(body: OpenApiSimpleBody, name: String, value: Example) { - examplesBody[body to name] = value - } - - fun getComponentsSection(): Map = emptyMap() - - fun getExample(parameter: OpenApiRequestParameter): String? = examplesParameters[parameter] - - fun getExample(body: OpenApiSimpleBody, name: String): Example? = examplesBody[body to name] - -} +//package io.github.smiley4.ktorswaggerui.builder.example +// +//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRequestParameter +//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiSimpleBody +//import io.swagger.v3.oas.models.examples.Example +// +//class ExampleContext { +// +// private val examplesParameters = mutableMapOf() +// private val examplesBody = mutableMapOf, Example>() +// +// fun addExample(parameter: OpenApiRequestParameter, value: String) { +// examplesParameters[parameter] = value +// } +// +// fun addExample(body: OpenApiSimpleBody, name: String, value: Example) { +// examplesBody[body to name] = value +// } +// +// fun getComponentsSection(): Map = emptyMap() +// +// fun getExample(parameter: OpenApiRequestParameter): String? = examplesParameters[parameter] +// +// fun getExample(body: OpenApiSimpleBody, name: String): Example? = examplesBody[body to name] +// +//} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt index 608eef5..c459274 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt @@ -1,80 +1,80 @@ -package io.github.smiley4.ktorswaggerui.builder.example - -import io.github.smiley4.ktorswaggerui.dsl.OpenApiBaseBody -import io.github.smiley4.ktorswaggerui.dsl.OpenApiExample -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRequestParameter -import io.github.smiley4.ktorswaggerui.dsl.OpenApiResponse -import io.github.smiley4.ktorswaggerui.dsl.OpenApiSimpleBody -import io.github.smiley4.ktorswaggerui.dsl.SchemaType -import io.github.smiley4.ktorswaggerui.dsl.getSchemaType -import io.github.smiley4.ktorswaggerui.builder.openapi.ExampleBuilder -import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.CollectionBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.CustomRefBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.EmptyBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.OneOfBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.SchemaBodyTypeDescriptor -import io.swagger.v3.oas.models.examples.Example - -class ExampleContextBuilder( - private val exampleBuilder: ExampleBuilder -) { - - fun build(routes: Collection): ExampleContext { - return ExampleContext() - .also { ctx -> routes.forEach { handle(ctx, it) } } - } - - - private fun handle(ctx: ExampleContext, route: RouteMeta) { - route.documentation.getRequest().getBody()?.also { handle(ctx, it) } - route.documentation.getRequest().getParameters().forEach { handle(ctx, it) } - route.documentation.getResponses().getResponses().forEach { handle(ctx, it) } - } - - private fun handle(ctx: ExampleContext, response: OpenApiResponse) { - response.getBody()?.also { handle(ctx, it) } - } - - - private fun handle(ctx: ExampleContext, body: OpenApiBaseBody) { - return when (body) { - is OpenApiSimpleBody -> handle(ctx, body) - else -> Unit - } - } - - - private fun handle(ctx: ExampleContext, body: OpenApiSimpleBody) { - body.getExamples().forEach { (name, value) -> - val bodyType = getRelevantSchemaType(body.type, getSchemaType()) - ctx.addExample(body, name, createExample(bodyType, value)) - } - } - - private fun getRelevantSchemaType(typeDescriptor: BodyTypeDescriptor, fallback: SchemaType): SchemaType { - return when(typeDescriptor) { - is EmptyBodyTypeDescriptor -> fallback - is SchemaBodyTypeDescriptor -> typeDescriptor.schemaType - is CollectionBodyTypeDescriptor -> getRelevantSchemaType(typeDescriptor.schemaType, fallback) - is OneOfBodyTypeDescriptor -> fallback - is CustomRefBodyTypeDescriptor -> fallback - } - } - - private fun handle(ctx: ExampleContext, parameter: OpenApiRequestParameter) { - parameter.example?.also { example -> - ctx.addExample(parameter, createExample(parameter.type, example)) - } - } - - private fun createExample(type: SchemaType?, value: Any): String { - return exampleBuilder.buildExampleValue(type, value) - } - - private fun createExample(type: SchemaType?, example: OpenApiExample): Example { - return exampleBuilder.build(type, example) - } - -} +//package io.github.smiley4.ktorswaggerui.builder.example +// +//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiBaseBody +//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiExample +//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRequestParameter +//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiResponse +//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiSimpleBody +//import io.github.smiley4.ktorswaggerui.dsl.SchemaType +//import io.github.smiley4.ktorswaggerui.dsl.getSchemaType +//import io.github.smiley4.ktorswaggerui.builder.openapi.ExampleBuilder +//import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta +//import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor +//import io.github.smiley4.ktorswaggerui.dsl.CollectionBodyTypeDescriptor +//import io.github.smiley4.ktorswaggerui.dsl.CustomRefBodyTypeDescriptor +//import io.github.smiley4.ktorswaggerui.dsl.EmptyBodyTypeDescriptor +//import io.github.smiley4.ktorswaggerui.dsl.OneOfBodyTypeDescriptor +//import io.github.smiley4.ktorswaggerui.dsl.SchemaBodyTypeDescriptor +//import io.swagger.v3.oas.models.examples.Example +// +//class ExampleContextBuilder( +// private val exampleBuilder: ExampleBuilder +//) { +// +// fun build(routes: Collection): ExampleContext { +// return ExampleContext() +// .also { ctx -> routes.forEach { handle(ctx, it) } } +// } +// +// +// private fun handle(ctx: ExampleContext, route: RouteMeta) { +// route.documentation.getRequest().getBody()?.also { handle(ctx, it) } +// route.documentation.getRequest().getParameters().forEach { handle(ctx, it) } +// route.documentation.getResponses().getResponses().forEach { handle(ctx, it) } +// } +// +// private fun handle(ctx: ExampleContext, response: OpenApiResponse) { +// response.getBody()?.also { handle(ctx, it) } +// } +// +// +// private fun handle(ctx: ExampleContext, body: OpenApiBaseBody) { +// return when (body) { +// is OpenApiSimpleBody -> handle(ctx, body) +// else -> Unit +// } +// } +// +// +// private fun handle(ctx: ExampleContext, body: OpenApiSimpleBody) { +// body.getExamples().forEach { (name, value) -> +// val bodyType = getRelevantSchemaType(body.type, getSchemaType()) +// ctx.addExample(body, name, createExample(bodyType, value)) +// } +// } +// +// private fun getRelevantSchemaType(typeDescriptor: BodyTypeDescriptor, fallback: SchemaType): SchemaType { +// return when(typeDescriptor) { +// is EmptyBodyTypeDescriptor -> fallback +// is SchemaBodyTypeDescriptor -> typeDescriptor.schemaType +// is CollectionBodyTypeDescriptor -> getRelevantSchemaType(typeDescriptor.schemaType, fallback) +// is OneOfBodyTypeDescriptor -> fallback +// is CustomRefBodyTypeDescriptor -> fallback +// } +// } +// +// private fun handle(ctx: ExampleContext, parameter: OpenApiRequestParameter) { +// parameter.example?.also { example -> +// ctx.addExample(parameter, createExample(parameter.type, example)) +// } +// } +// +// private fun createExample(type: SchemaType?, value: Any): String { +// return exampleBuilder.buildExampleValue(type, value) +// } +// +// private fun createExample(type: SchemaType?, example: OpenApiExample): Example { +// return exampleBuilder.build(type, example) +// } +// +//} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt index ee2bd7d..c0c6d61 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt @@ -1,17 +1,9 @@ package io.github.smiley4.ktorswaggerui.builder.openapi -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.CollectionBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.CustomRefBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.EmptyBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.OneOfBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.OpenApiBaseBody -import io.github.smiley4.ktorswaggerui.dsl.OpenApiMultipartBody -import io.github.smiley4.ktorswaggerui.dsl.OpenApiMultipartPart -import io.github.smiley4.ktorswaggerui.dsl.OpenApiSimpleBody -import io.github.smiley4.ktorswaggerui.dsl.SchemaBodyTypeDescriptor +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.swagger.v3.oas.models.media.Content import io.swagger.v3.oas.models.media.Encoding @@ -21,24 +13,23 @@ import kotlin.collections.set class ContentBuilder( private val schemaContext: SchemaContext, - private val exampleContext: ExampleContext, private val headerBuilder: HeaderBuilder ) { - fun build(body: OpenApiBaseBody): Content = + fun build(body: OpenApiBaseBodyData): Content = when (body) { - is OpenApiSimpleBody -> buildSimpleBody(body) - is OpenApiMultipartBody -> buildMultipartBody(body) + is OpenApiSimpleBodyData -> buildSimpleBody(body) + is OpenApiMultipartBodyData -> buildMultipartBody(body) } - private fun buildSimpleBody(body: OpenApiSimpleBody): Content = + private fun buildSimpleBody(body: OpenApiSimpleBodyData): Content = Content().also { content -> - buildSimpleMediaTypes(body, getSchema(body)).forEach { (contentType, mediaType) -> + buildSimpleMediaTypes(body, schemaContext.getSchema(body.type)).forEach { (contentType, mediaType) -> content.addMediaType(contentType.toString(), mediaType) } } - private fun buildMultipartBody(body: OpenApiMultipartBody): Content { + private fun buildMultipartBody(body: OpenApiMultipartBodyData): Content { return Content().also { content -> buildMultipartMediaTypes(body).forEach { (contentType, mediaType) -> content.addMediaType(contentType.toString(), mediaType) @@ -46,35 +37,34 @@ class ContentBuilder( } } - private fun buildSimpleMediaTypes(body: OpenApiSimpleBody, schema: Schema<*>?): Map { - val mediaTypes = body.getMediaTypes().ifEmpty { schema?.let { setOf(chooseMediaType(schema)) } ?: setOf() } + private fun buildSimpleMediaTypes(body: OpenApiSimpleBodyData, schema: Schema<*>?): Map { + val mediaTypes = body.mediaTypes.ifEmpty { schema?.let { setOf(chooseMediaType(schema)) } ?: setOf() } return mediaTypes.associateWith { buildSimpleMediaType(schema, body) } } - private fun buildSimpleMediaType(schema: Schema<*>?, body: OpenApiSimpleBody): MediaType { + private fun buildSimpleMediaType(schema: Schema<*>?, body: OpenApiSimpleBodyData): MediaType { return MediaType().also { it.schema = schema - body.getExamples().forEach { (name, _) -> - exampleContext.getExample(body, name) - ?.also { example -> it.addExamples(name, example) } + body.examples.forEach { (name, _) -> +// todo +// exampleContext.getExample(body, name) +// ?.also { example -> it.addExamples(name, example) } } } } - private fun buildMultipartMediaTypes(body: OpenApiMultipartBody): Map { - val mediaTypes = body.getMediaTypes().ifEmpty { setOf(ContentType.MultiPart.FormData) } + private fun buildMultipartMediaTypes(body: OpenApiMultipartBodyData): Map { + val mediaTypes = body.mediaTypes.ifEmpty { setOf(ContentType.MultiPart.FormData) } return mediaTypes.associateWith { buildMultipartMediaType(body) } } - private fun buildMultipartMediaType(body: OpenApiMultipartBody): MediaType { + private fun buildMultipartMediaType(body: OpenApiMultipartBodyData): MediaType { return MediaType().also { mediaType -> mediaType.schema = Schema().also { schema -> schema.type = "object" schema.properties = mutableMapOf?>().also { props -> - body.getParts().forEach { part -> - getSchema(part)?.also { - props[part.name] = getSchema(part) - } + body.parts.forEach { part -> + props[part.name] = schemaContext.getSchema(part.type) } } } @@ -82,57 +72,57 @@ class ContentBuilder( } } - private fun buildMultipartEncoding(body: OpenApiMultipartBody): MutableMap? { - return if (body.getParts().flatMap { it.mediaTypes }.isEmpty()) { + private fun buildMultipartEncoding(body: OpenApiMultipartBodyData): MutableMap? { + return if (body.parts.flatMap { it.mediaTypes }.isEmpty()) { null } else { mutableMapOf().also { encodings -> - body.getParts() - .filter { it.mediaTypes.isNotEmpty() || it.getHeaders().isNotEmpty() } + body.parts + .filter { it.mediaTypes.isNotEmpty() || it.headers.isNotEmpty() } .forEach { part -> encodings[part.name] = Encoding().apply { contentType = part.mediaTypes.joinToString(", ") { it.toString() } - headers = part.getHeaders().mapValues { headerBuilder.build(it.value) } + headers = part.headers.mapValues { headerBuilder.build(it.value) } } } } } } - private fun getSchema(body: OpenApiSimpleBody): Schema<*>? { - return getSchema(body.type) - } - - private fun getSchema(part: OpenApiMultipartPart): Schema<*>? { - return getSchema(part.type) - } - - private fun getSchema(typeDescriptor: BodyTypeDescriptor): Schema<*>? { - return when (typeDescriptor) { - is EmptyBodyTypeDescriptor -> { - null - } - is SchemaBodyTypeDescriptor -> { - schemaContext.getSchema(typeDescriptor.schemaType) - } - is OneOfBodyTypeDescriptor -> { - Schema().also { schema -> - typeDescriptor.elements.forEach { - schema.addOneOfItem(getSchema(it)) - } - } - } - is CollectionBodyTypeDescriptor -> { - Schema().also { schema -> - schema.type = "array" - schema.items = getSchema(typeDescriptor.schemaType) - } - } - is CustomRefBodyTypeDescriptor -> { - schemaContext.getSchema(typeDescriptor.customSchemaId) - } - } - } +// private fun getSchema(body: OpenApiSimpleBodyData): Schema<*>? { +// return getSchema(body.type) +// } +// +// private fun getSchema(part: OpenApiMultipartPartData): Schema<*>? { +// return getSchema(part.type) +// } +// +// private fun getSchema(typeDescriptor: TypeDescriptor): Schema<*>? { +// return when (typeDescriptor) { +// is EmptyBodyTypeDescriptor -> { +// null +// } +// is SchemaBodyTypeDescriptor -> { +// schemaContext.getSchema(typeDescriptor) +// } +// is OneOfBodyTypeDescriptor -> { +// Schema().also { schema -> +// typeDescriptor.elements.forEach { +// schema.addOneOfItem(getSchema(it)) +// } +// } +// } +// is CollectionBodyTypeDescriptor -> { +// Schema().also { schema -> +// schema.type = "array" +// schema.items = getSchema(typeDescriptor.schemaType) +// } +// } +// is CustomRefBodyTypeDescriptor -> { +// schemaContext.getSchema(typeDescriptor.customSchemaId) +// } +// } +// } private fun chooseMediaType(schema: Schema<*>): ContentType { return when (schema.type) { diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt index 2b452fa..89e276c 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt @@ -1,23 +1,23 @@ -package io.github.smiley4.ktorswaggerui.builder.openapi - -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.OpenApiExample -import io.github.smiley4.ktorswaggerui.dsl.SchemaType -import io.swagger.v3.oas.models.examples.Example - -class ExampleBuilder( - private val config: PluginConfigData -) { - - fun build(type: SchemaType?, example: OpenApiExample): Example = - Example().also { - it.value = buildExampleValue(type, example.value) - it.summary = example.summary - it.description = example.description - } - - fun buildExampleValue(type: SchemaType?, value: Any): String { - return config.encoding.exampleEncoder(type, value) ?: value.toString() - } - -} +//package io.github.smiley4.ktorswaggerui.builder.openapi +// +//import io.github.smiley4.ktorswaggerui.data.PluginConfigData +//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiExample +//import io.github.smiley4.ktorswaggerui.dsl.SchemaType +//import io.swagger.v3.oas.models.examples.Example +// +//class ExampleBuilder( +// private val config: PluginConfigData +//) { +// +// fun build(type: SchemaType?, example: OpenApiExample): Example = +// Example().also { +// it.value = buildExampleValue(type, example.value) +// it.summary = example.summary +// it.description = example.description +// } +// +// fun buildExampleValue(type: SchemaType?, value: Any): String { +// return config.encoding.exampleEncoder(type, value) ?: value.toString() +// } +// +//} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt index 4c0fd1a..964f935 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt @@ -1,14 +1,14 @@ package io.github.smiley4.ktorswaggerui.builder.openapi -import io.github.smiley4.ktorswaggerui.dsl.OpenApiHeader import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext +import io.github.smiley4.ktorswaggerui.data.OpenApiHeaderData import io.swagger.v3.oas.models.headers.Header class HeaderBuilder( private val schemaContext: SchemaContext ) { - fun build(header: OpenApiHeader): Header = + fun build(header: OpenApiHeaderData): Header = Header().also { it.description = header.description it.required = header.required diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt index f3adac8..3917c0c 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt @@ -1,7 +1,6 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.swagger.v3.oas.models.OpenAPI @@ -9,7 +8,6 @@ import io.swagger.v3.oas.models.OpenAPI class OpenApiBuilder( private val config: PluginConfigData, private val schemaContext: SchemaContext, - private val exampleContext: ExampleContext, private val infoBuilder: InfoBuilder, private val externalDocumentationBuilder: ExternalDocumentationBuilder, private val serverBuilder: ServerBuilder, @@ -25,7 +23,8 @@ class OpenApiBuilder( it.servers = config.servers.map { server -> serverBuilder.build(server) } it.tags = config.tags.map { tag -> tagBuilder.build(tag) } it.paths = pathsBuilder.build(routes) - it.components = componentsBuilder.build(schemaContext.getComponentsSection(), exampleContext.getComponentsSection()) +// it.components = componentsBuilder.build(schemaContext.getComponentSection(), exampleContext.getComponentsSection()) // todo + it.components = componentsBuilder.build(schemaContext.getComponentSection(), emptyMap()) } } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt index 3e48ce8..dab2bbe 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt @@ -18,11 +18,11 @@ class OperationBuilder( it.operationId = route.documentation.operationId it.deprecated = route.documentation.deprecated it.tags = operationTagsBuilder.build(route) - it.parameters = route.documentation.getRequest().getParameters().map { param -> parameterBuilder.build(param) } - route.documentation.getRequest().getBody()?.let { body -> + it.parameters = route.documentation.request.parameters.map { param -> parameterBuilder.build(param) } + route.documentation.request.body?.let { body -> it.requestBody = requestBodyBuilder.build(body) } - it.responses = responsesBuilder.build(route.documentation.getResponses(), route.protected) + it.responses = responsesBuilder.build(route.documentation.responses, route.protected) if (route.protected) { securityRequirementsBuilder.build(route).also { securityRequirements -> if (securityRequirements.isNotEmpty()) { diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt index bbf7603..9415d54 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt @@ -1,21 +1,20 @@ package io.github.smiley4.ktorswaggerui.builder.openapi -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRequestParameter -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext +import io.github.smiley4.ktorswaggerui.data.OpenApiRequestParameterData +import io.github.smiley4.ktorswaggerui.data.ParameterLocation import io.swagger.v3.oas.models.parameters.Parameter class ParameterBuilder( private val schemaContext: SchemaContext, - private val exampleContext: ExampleContext, ) { - fun build(parameter: OpenApiRequestParameter): Parameter = + fun build(parameter: OpenApiRequestParameterData): Parameter = Parameter().also { it.`in` = when (parameter.location) { - OpenApiRequestParameter.Location.QUERY -> "query" - OpenApiRequestParameter.Location.HEADER -> "header" - OpenApiRequestParameter.Location.PATH -> "path" + ParameterLocation.QUERY -> "query" + ParameterLocation.HEADER -> "header" + ParameterLocation.PATH -> "path" } it.name = parameter.name it.description = parameter.description @@ -23,7 +22,7 @@ class ParameterBuilder( it.deprecated = parameter.deprecated it.allowEmptyValue = parameter.allowEmptyValue it.explode = parameter.explode - it.example = exampleContext.getExample(parameter) +// it.example = exampleContext.getExample(parameter) // todo it.allowReserved = parameter.allowReserved it.schema = schemaContext.getSchema(parameter.type) } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt index f5a6383..8795b1b 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt @@ -1,13 +1,13 @@ package io.github.smiley4.ktorswaggerui.builder.openapi -import io.github.smiley4.ktorswaggerui.dsl.OpenApiBaseBody +import io.github.smiley4.ktorswaggerui.data.OpenApiBaseBodyData import io.swagger.v3.oas.models.parameters.RequestBody class RequestBodyBuilder( private val contentBuilder: ContentBuilder ) { - fun build(body: OpenApiBaseBody): RequestBody = + fun build(body: OpenApiBaseBodyData): RequestBody = RequestBody().also { it.description = body.description it.required = body.required diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt index 7e59eec..e70d69b 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt @@ -1,6 +1,6 @@ package io.github.smiley4.ktorswaggerui.builder.openapi -import io.github.smiley4.ktorswaggerui.dsl.OpenApiResponse +import io.github.smiley4.ktorswaggerui.data.OpenApiResponseData import io.swagger.v3.oas.models.responses.ApiResponse class ResponseBuilder( @@ -8,11 +8,11 @@ class ResponseBuilder( private val contentBuilder: ContentBuilder ) { - fun build(response: OpenApiResponse): Pair = + fun build(response: OpenApiResponseData): Pair = response.statusCode to ApiResponse().also { it.description = response.description - it.headers = response.getHeaders().mapValues { header -> headerBuilder.build(header.value) } - response.getBody()?.let { body -> + it.headers = response.headers.mapValues { header -> headerBuilder.build(header.value) } + response.body?.let { body -> it.content = contentBuilder.build(body) } } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt index bd148dd..cf352ca 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt @@ -1,7 +1,7 @@ package io.github.smiley4.ktorswaggerui.builder.openapi +import io.github.smiley4.ktorswaggerui.data.OpenApiResponseData import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.OpenApiResponses import io.ktor.http.HttpStatusCode import io.swagger.v3.oas.models.responses.ApiResponses @@ -10,9 +10,9 @@ class ResponsesBuilder( private val config: PluginConfigData ) { - fun build(responses: OpenApiResponses, isProtected: Boolean): ApiResponses = + fun build(responses: List, isProtected: Boolean): ApiResponses = ApiResponses().also { - responses.getResponses() + responses .map { response -> responseBuilder.build(response) } .forEach { (name, response) -> it.addApiResponse(name, response) } if (shouldAddUnauthorized(responses, isProtected)) { @@ -22,11 +22,11 @@ class ResponsesBuilder( } } - private fun shouldAddUnauthorized(responses: OpenApiResponses, isProtected: Boolean): Boolean { + private fun shouldAddUnauthorized(responses: List, isProtected: Boolean): Boolean { val unauthorizedCode = HttpStatusCode.Unauthorized.value.toString(); return config.defaultUnauthorizedResponse != null && isProtected - && responses.getResponses().count { it.statusCode == unauthorizedCode } == 0 + && responses.count { it.statusCode == unauthorizedCode } == 0 } } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt index 3b1df67..4166a66 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt @@ -1,7 +1,7 @@ package io.github.smiley4.ktorswaggerui.builder.openapi -import io.github.smiley4.ktorswaggerui.data.PluginConfigData import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta +import io.github.smiley4.ktorswaggerui.data.PluginConfigData import io.swagger.v3.oas.models.security.SecurityRequirement class SecurityRequirementsBuilder( @@ -9,12 +9,11 @@ class SecurityRequirementsBuilder( ) { fun build(route: RouteMeta): List { - val securitySchemes = mutableSetOf().also { schemes -> - route.documentation.securitySchemeName?.also { schemes.add(it) } - route.documentation.securitySchemeNames?.also { schemes.addAll(it) } - } - if (securitySchemes.isEmpty()) { - config.defaultSecuritySchemeNames.also { securitySchemes.addAll(it) } + val securitySchemes = buildSet { + addAll(route.documentation.securitySchemeNames) + if(route.documentation.securitySchemeNames.isEmpty()) { + addAll(config.defaultSecuritySchemeNames) + } } return securitySchemes.map { SecurityRequirement().apply { diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt index a9b257a..a225882 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt @@ -1,11 +1,18 @@ package io.github.smiley4.ktorswaggerui.builder.route import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.DocumentedRouteSelector -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute +import io.github.smiley4.ktorswaggerui.dsl.routing.DocumentedRouteSelector +import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRoute import io.ktor.http.HttpMethod import io.ktor.server.auth.AuthenticationRouteSelector -import io.ktor.server.routing.* +import io.ktor.server.routing.ConstantParameterRouteSelector +import io.ktor.server.routing.HttpMethodRouteSelector +import io.ktor.server.routing.OptionalParameterRouteSelector +import io.ktor.server.routing.ParameterRouteSelector +import io.ktor.server.routing.RootRouteSelector +import io.ktor.server.routing.Route +import io.ktor.server.routing.RouteSelector +import io.ktor.server.routing.TrailingSlashRouteSelector import kotlin.reflect.full.isSubclassOf class RouteCollector( @@ -23,7 +30,7 @@ class RouteCollector( RouteMeta( method = getMethod(route), path = getPath(route, config), - documentation = documentation, + documentation = documentation.build(), protected = documentation.protected ?: isProtected(route) ) } @@ -31,11 +38,11 @@ class RouteCollector( .filter { path -> config.pathFilter(path.method, path.path.split("/").filter { it.isNotEmpty() }) } } + private fun getDocumentation(route: Route, base: OpenApiRoute): OpenApiRoute { var documentation = base if (route.selector is DocumentedRouteSelector) { - documentation = - routeDocumentationMerger.merge(documentation, (route.selector as DocumentedRouteSelector).documentation) + documentation = routeDocumentationMerger.merge(documentation, (route.selector as DocumentedRouteSelector).documentation) } return if (route.parent != null) { getDocumentation(route.parent!!, documentation) @@ -44,10 +51,12 @@ class RouteCollector( } } + private fun getMethod(route: Route): HttpMethod { return (route.selector as HttpMethodRouteSelector).method } + @Suppress("CyclomaticComplexMethod") private fun getPath(route: Route, config: PluginConfigData): String { val selector = route.selector @@ -68,6 +77,7 @@ class RouteCollector( } } + private fun isIgnoredSelector(selector: RouteSelector, config: PluginConfigData): Boolean { return when (selector) { is TrailingSlashRouteSelector -> false @@ -82,6 +92,7 @@ class RouteCollector( } } + private fun isProtected(route: Route): Boolean { return when (route.selector) { is AuthenticationRouteSelector -> true @@ -90,6 +101,7 @@ class RouteCollector( is DocumentedRouteSelector -> route.parent?.let { isProtected(it) } ?: false is HttpMethodRouteSelector -> route.parent?.let { isProtected(it) } ?: false else -> route.parent?.let { isProtected(it) } ?: false + } } @@ -98,5 +110,4 @@ class RouteCollector( .filter { it.selector is HttpMethodRouteSelector } } - } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt index f0509b9..0fd0475 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt @@ -1,6 +1,6 @@ package io.github.smiley4.ktorswaggerui.builder.route -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute +import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRoute class RouteDocumentationMerger { diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt index 2145292..b9d8823 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt @@ -1,6 +1,6 @@ package io.github.smiley4.ktorswaggerui.builder.route -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute +import io.github.smiley4.ktorswaggerui.data.OpenApiRouteData import io.ktor.http.HttpMethod /** @@ -9,6 +9,6 @@ import io.ktor.http.HttpMethod data class RouteMeta( val path: String, val method: HttpMethod, - val documentation: OpenApiRoute, + val documentation: OpenApiRouteData, val protected: Boolean ) diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaBuilder.kt deleted file mode 100644 index 5d85e60..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaBuilder.kt +++ /dev/null @@ -1,110 +0,0 @@ -package io.github.smiley4.ktorswaggerui.builder.schema - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.node.ArrayNode -import com.fasterxml.jackson.databind.node.BooleanNode -import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.databind.node.TextNode -import io.github.smiley4.ktorswaggerui.dsl.SchemaEncoder -import io.github.smiley4.ktorswaggerui.dsl.SchemaType -import io.swagger.v3.core.util.Json -import io.swagger.v3.oas.models.media.Schema - - -data class SchemaDefinitions( - val root: Schema<*>, - val definitions: Map> -) - -class SchemaBuilder( - private val definitionsField: String? = null, - private val schemaEncoder: SchemaEncoder, - private val json: ObjectMapper, - private val typeOverwrites: Map, -) { - - fun create(type: SchemaType): SchemaDefinitions { - val jsonSchema = typeOverwrites[type]?.let { json.readTree(it) } ?: createJsonSchema(type) - return create(jsonSchema) - } - - fun create(jsonSchema: String): SchemaDefinitions { - return create(json.readTree(jsonSchema)) - } - - fun create(jsonSchema: JsonNode): SchemaDefinitions { - normalizeRefs(jsonSchema) { normalizeRef(it) } - val additionalDefinitions = extractAdditionalDefinitions(jsonSchema) - val rootSchema = toOpenApiSchema(jsonSchema) - val additionalSchemas = additionalDefinitions.mapValues { toOpenApiSchema(it.value) } - return SchemaDefinitions( - root = rootSchema, - definitions = additionalSchemas - ) - } - - private fun createJsonSchema(type: SchemaType): JsonNode { - val str = schemaEncoder(type) - return json.readTree(str) - } - - private fun normalizeRefs(jsonSchema: JsonNode, normalizer: (ref: String) -> String) { - iterateTree(jsonSchema) { node -> - if (node is ObjectNode) { - node.get("\$ref")?.also { - node.set("\$ref", TextNode(normalizer(it.asText()))) - } - } - } - } - - private fun normalizeRef(ref: String): String { - val prefix = "#/$definitionsField" - return if (ref.startsWith(prefix)) { - ref.replace(prefix, "#/components/schemas") - } else { - ref - } - } - - private fun extractAdditionalDefinitions(schema: JsonNode): Map { - val definitionsPath = definitionsField?.let { "/$definitionsField" } - return if (definitionsPath == null || schema.at(definitionsPath).isMissingNode) { - emptyMap() - } else { - (schema.at(definitionsPath) as ObjectNode) - .fields().asSequence().toList() - .associate { (key, node) -> key to node } - } - } - - private fun toOpenApiSchema(jsonSchema: JsonNode): Schema<*> { - iterateTree(jsonSchema) { node -> - node.get("type")?.also { typeNode -> - if (typeNode is ArrayNode && node is ObjectNode) { - val types = typeNode.asSequence().filterIsInstance().map { it.asText() }.toSet() - node.set("type", TextNode(types.first { it != "null" })) - if (types.contains("null")) { - node.set("nullable", BooleanNode.TRUE) - } - } - } - } - return Json.mapper().readValue(jsonSchema.toString(), Schema::class.java) - } - - - private fun iterateTree(node: JsonNode, consumer: (node: JsonNode) -> Unit) { - consumer(node) - when (node) { - is ObjectNode -> { - node.elements().asSequence().forEach { iterateTree(it, consumer) } - } - is ArrayNode -> { - node.elements().asSequence().forEach { iterateTree(it, consumer) } - } - } - } - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt index ab0f9a5..14dbc3e 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt @@ -1,243 +1,9 @@ package io.github.smiley4.ktorswaggerui.builder.schema -import io.github.smiley4.ktorswaggerui.dsl.SchemaType -import io.github.smiley4.ktorswaggerui.dsl.getSchemaType -import io.github.smiley4.ktorswaggerui.dsl.getSimpleArrayElementTypeName -import io.github.smiley4.ktorswaggerui.dsl.getSimpleTypeName +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.swagger.v3.oas.models.media.Schema -import kotlin.collections.set - -class SchemaContext { - - companion object { - - private data class SchemaKeyWrapper( - val type: SchemaType, - val schemaId: String, - val isCustom: Boolean - ) { - - companion object { - val PLACEHOLDER_TYPE = getSchemaType() - const val PLACEHOLDER_SCHEMAID = "" - - fun type(type: SchemaType) = SchemaKeyWrapper( - type = type, - schemaId = PLACEHOLDER_SCHEMAID, - isCustom = false - ) - - fun custom(schemaId: String) = SchemaKeyWrapper( - type = PLACEHOLDER_TYPE, - schemaId = schemaId, - isCustom = true - ) - } - } - } - - private val schemas = mutableMapOf() - private val schemasCustom = mutableMapOf() - - private val componentsSection = mutableMapOf>() - private val inlineSchemas = mutableMapOf>() - private val inlineSchemasCustom = mutableMapOf>() - - - fun addSchema(type: SchemaType, schema: SchemaDefinitions) { - schemas[type] = schema - } - - - fun addSchema(customSchemaId: String, schema: SchemaDefinitions) { - schemasCustom[customSchemaId] = schema - } - - - fun getComponentsSection(): Map> = componentsSection - - - fun getSchema(type: SchemaType) = getSchemaOrNull(type) - ?: throw NoSuchElementException("No schema for type '$type'!") - - fun getSchemaOrNull(type: SchemaType) = inlineSchemas[type] - - - fun getSchema(customSchemaId: String) = getSchemaOrNull(customSchemaId) - ?: throw NoSuchElementException("No schema for ref '$customSchemaId'!") - - fun getSchemaOrNull(customSchemaId: String) = inlineSchemasCustom[customSchemaId] - - - fun finalize() { - schemas.forEach { (type, schemaDefinitions) -> - finalize(SchemaKeyWrapper.type(type), schemaDefinitions) - } - schemasCustom.forEach { (schemaId, schemaDefinitions) -> - finalize(SchemaKeyWrapper.custom(schemaId), schemaDefinitions) - } - } - - private fun finalize(key: SchemaKeyWrapper, schemaDefinitions: SchemaDefinitions) { - if (schemaDefinitions.definitions.isEmpty()) { - finalizeOnlyRootDefinition(key, schemaDefinitions) - } - if (schemaDefinitions.definitions.size == 1) { - finalizeOneAdditionalDefinition(key, schemaDefinitions) - } - if (schemaDefinitions.definitions.size > 1) { - finalizeMultipleAdditionalDefinitions(key, schemaDefinitions) - } - } - - private fun finalizeOnlyRootDefinition(key: SchemaKeyWrapper, schemaDefinitions: SchemaDefinitions) { - val root = schemaDefinitions.root - if (root.isPrimitive() || root.isPrimitiveArray()) { - inlineRoot(key, schemaDefinitions) - } else if (root.isObjectArray()) { - unwrapRootArray(key, schemaDefinitions) - } else { - createInlineReference(key, schemaDefinitions) - } - } - - private fun finalizeOneAdditionalDefinition(key: SchemaKeyWrapper, schemaDefinitions: SchemaDefinitions) { - val root = schemaDefinitions.root - val definition = schemaDefinitions.definitions.entries.first().value - if (root.isReference() && (definition.isPrimitive() || definition.isPrimitiveArray())) { - inlineSingleDefinition(key, schemaDefinitions) - } else if (root.isReference() || root.isReferenceArray()) { - inlineRoot(key, schemaDefinitions) - } else if (root.isObjectArray()) { - unwrapRootArray(key, schemaDefinitions) - } else { - createInlineReference(key, schemaDefinitions) - } - } - - private fun finalizeMultipleAdditionalDefinitions(key: SchemaKeyWrapper, schemaDefinitions: SchemaDefinitions) { - val root = schemaDefinitions.root - if (root.isReference() || root.isReferenceArray()) { - inlineRoot(key, schemaDefinitions) - } else if (root.isObjectArray()) { - unwrapRootArray(key, schemaDefinitions) - } else { - createInlineReference(key, schemaDefinitions) - } - } - - - private fun inlineRoot(key: SchemaKeyWrapper, schemaDefinitions: SchemaDefinitions) { - /* - - root-schema: inline - - definitions: in components section - */ - addInline(key, schemaDefinitions.root) - schemaDefinitions.definitions.forEach { (name, schema) -> - addToComponentsSection(name, schema) - } - } - - private fun inlineSingleDefinition(key: SchemaKeyWrapper, schemaDefinitions: SchemaDefinitions) { - /* - - assumption: size(definitions) == 1 - - root-schema: discard - - definition: inline - */ - if (schemaDefinitions.definitions.size != 1) { - throw IllegalArgumentException("Unexpected amount of additional schema-definitions: ${schemaDefinitions.definitions.size}") - } - schemaDefinitions.definitions.entries.first() - .also { addInline(key, it.value) } - } - - private fun createInlineReference(key: SchemaKeyWrapper, schemaDefinitions: SchemaDefinitions) { - /* - - root-schema: in components section - - definitions: in components section - - create inline ref to root - */ - schemaDefinitions.definitions.forEach { (name, schema) -> - addToComponentsSection(name, schema) - } - val rootName = schemaName(key) - addToComponentsSection(rootName, schemaDefinitions.root) - addInline(key, Schema().also { - it.`$ref` = "#/components/schemas/$rootName" - }) - } - - private fun unwrapRootArray(key: SchemaKeyWrapper, schemaDefinitions: SchemaDefinitions) { - /* - - assumption: root schema.type == array - - root-schema: unwrap - - item -> component section - - create inline array-ref to item - - definitions: in components section - */ - if (schemaDefinitions.root.items == null) { - throw IllegalArgumentException("Expected items for array-schema but items were 'null'.") - } - schemaDefinitions.definitions.forEach { (name, schema) -> - addToComponentsSection(name, schema) - } - val rootName = getWrappedSchemaName(key) - addToComponentsSection(rootName, schemaDefinitions.root.items) - addInline(key, Schema().also { array -> - array.type = "array" - array.items = Schema().also { item -> - item.`$ref` = "#/components/schemas/$rootName" - } - }) - } - - private fun schemaName(key: SchemaKeyWrapper): String { - return if (key.isCustom) { - key.schemaId - } else { - key.type.getSimpleTypeName() - } - } - - private fun getWrappedSchemaName(key: SchemaKeyWrapper): String { - return if (key.isCustom) { - key.schemaId - } else { - key.type.getSimpleArrayElementTypeName() - } - } - - private fun addToComponentsSection(name: String, schema: Schema<*>) { - componentsSection[name] = schema - } - - private fun addInline(key: SchemaKeyWrapper, schema: Schema<*>) { - if (key.isCustom) { - inlineSchemasCustom[key.schemaId] = schema - } else { - inlineSchemas[key.type] = schema - } - } - - private fun Schema<*>.isPrimitive(): Boolean { - return type != "object" && type != "array" && type != null - } - - private fun Schema<*>.isPrimitiveArray(): Boolean { - return type == "array" && (items.isPrimitive() || items.isPrimitiveArray()) - } - - private fun Schema<*>.isObjectArray(): Boolean { - return type == "array" && !items.isPrimitive() && !items.isPrimitiveArray() - } - - private fun Schema<*>.isReference(): Boolean { - return type == null && `$ref` != null - } - - private fun Schema<*>.isReferenceArray(): Boolean { - return type == "array" && items.isReference() - } - -} +interface SchemaContext { + fun getSchema(typeDescriptor: TypeDescriptor): Schema<*> + fun getComponentSection(): Map> +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextBuilder.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextBuilder.kt deleted file mode 100644 index 3dbea96..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextBuilder.kt +++ /dev/null @@ -1,143 +0,0 @@ -package io.github.smiley4.ktorswaggerui.builder.schema - -import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta -import io.github.smiley4.ktorswaggerui.data.BaseCustomSchema -import io.github.smiley4.ktorswaggerui.data.CustomJsonSchema -import io.github.smiley4.ktorswaggerui.data.CustomOpenApiSchema -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.data.RemoteSchema -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.CollectionBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.CustomRefBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.EmptyBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.OneOfBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.OpenApiBaseBody -import io.github.smiley4.ktorswaggerui.dsl.OpenApiMultipartBody -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRequestParameter -import io.github.smiley4.ktorswaggerui.dsl.OpenApiResponse -import io.github.smiley4.ktorswaggerui.dsl.OpenApiSimpleBody -import io.github.smiley4.ktorswaggerui.dsl.SchemaBodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.SchemaType -import io.swagger.v3.oas.models.media.Schema - -class SchemaContextBuilder( - private val config: PluginConfigData, - private val schemaBuilder: SchemaBuilder -) { - - fun build(routes: Collection): SchemaContext { - return SchemaContext() - .also { ctx -> routes.forEach { handle(ctx, it) } } - .also { ctx -> - if (config.includeAllCustomSchemas) { - config.customSchemas.forEach { (id, schema) -> - ctx.addSchema(id, createSchema(schema)) - } - } - } - .also { ctx -> ctx.finalize() } - } - - - private fun handle(ctx: SchemaContext, route: RouteMeta) { - route.documentation.getRequest().getBody()?.also { handle(ctx, it) } - route.documentation.getRequest().getParameters().forEach { handle(ctx, it) } - route.documentation.getResponses().getResponses().forEach { handle(ctx, it) } - } - - - private fun handle(ctx: SchemaContext, response: OpenApiResponse) { - response.getHeaders().forEach { (_, header) -> - header.type?.also { headerType -> - ctx.addSchema(headerType, createSchema(headerType)) - } - } - response.getBody()?.also { handle(ctx, it) } - } - - - private fun handle(ctx: SchemaContext, body: OpenApiBaseBody) { - return when (body) { - is OpenApiSimpleBody -> handle(ctx, body) - is OpenApiMultipartBody -> handle(ctx, body) - } - } - - - private fun handle(ctx: SchemaContext, body: OpenApiSimpleBody) { - addSchemas(ctx, body.type) - } - - private fun handle(ctx: SchemaContext, body: OpenApiMultipartBody) { - body.getParts().forEach { part -> - part.type.also { addSchemas(ctx, part.type) } - } - } - - private fun addSchemas(ctx: SchemaContext, typeDescriptor: BodyTypeDescriptor) { - when (typeDescriptor) { - is EmptyBodyTypeDescriptor -> Unit - is SchemaBodyTypeDescriptor -> { - ctx.addSchema(typeDescriptor.schemaType, createSchema(typeDescriptor.schemaType)) - } - is CollectionBodyTypeDescriptor -> { - addSchemas(ctx, typeDescriptor.schemaType) - } - is OneOfBodyTypeDescriptor -> { - typeDescriptor.elements.forEach { addSchemas(ctx, it) } - } - is CustomRefBodyTypeDescriptor -> { - ctx.addSchema(typeDescriptor.customSchemaId, createSchema(typeDescriptor.customSchemaId)) - } - } - } - - - private fun handle(ctx: SchemaContext, parameter: OpenApiRequestParameter) { - ctx.addSchema(parameter.type, createSchema(parameter.type)) - } - - - private fun createSchema(type: SchemaType): SchemaDefinitions { - return schemaBuilder.create(type) - } - - - private fun createSchema(customSchemaId: String): SchemaDefinitions { - val customSchema = config.customSchemas[customSchemaId] - return if (customSchema == null) { - SchemaDefinitions( - root = Schema(), - definitions = emptyMap() - ) - } else { - createSchema(customSchema) - } - } - - - private fun createSchema(customSchema: BaseCustomSchema): SchemaDefinitions { - return when (customSchema) { - is CustomJsonSchema -> { - schemaBuilder.create(customSchema.provider()) - } - is CustomOpenApiSchema -> { - SchemaDefinitions( - // provided schema should not have a 'definitions'-section, i.e. schema should be inline-able as is. - root = customSchema.provider(), - definitions = emptyMap() - ) - } - is RemoteSchema -> { - SchemaDefinitions( - root = Schema().apply { - type = "object" - `$ref` = customSchema.url - }, - definitions = emptyMap() - ) - } - } - } - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt new file mode 100644 index 0000000..9a77628 --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt @@ -0,0 +1,137 @@ +package io.github.smiley4.ktorswaggerui.builder.schema + +import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta +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.OneOfTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartBodyData +import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.schemakenerator.core.data.WildcardTypeData +import io.github.smiley4.schemakenerator.reflection.processReflection +import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot +import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema +import io.github.smiley4.schemakenerator.swagger.data.TitleType +import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema +import io.github.smiley4.schemakenerator.swagger.steps.SwaggerSchemaUtils +import io.github.smiley4.schemakenerator.swagger.withAutoTitle +import io.swagger.v3.oas.models.media.Schema +import kotlin.reflect.KType + +class SchemaContextImpl : SchemaContext { + + private val rootSchemas = mutableMapOf>() + private val componentSchemas = mutableMapOf>() + + + fun add(routes: Collection) { + collectTypeDescriptor(routes).forEach { typeDescriptor -> + val schema = generateSchema(typeDescriptor) + rootSchemas[typeDescriptor] = schema.swagger + schema.componentSchemas.forEach { (k, v) -> + componentSchemas[k.full()] = v + } + } + } + + private fun generateSchema(typeDescriptor: TypeDescriptor): CompiledSwaggerSchema { + return when (typeDescriptor) { + is KTypeDescriptor -> { + generateSchema(typeDescriptor.type) + } + is ArrayTypeDescriptor -> { + val itemSchema = generateSchema(typeDescriptor.type) + CompiledSwaggerSchema( + typeData = WildcardTypeData(), + swagger = SwaggerSchemaUtils().arraySchema( + itemSchema.swagger + ), + componentSchemas = itemSchema.componentSchemas + ) + } + is OneOfTypeDescriptor -> { + val optionSchemas = typeDescriptor.types.map { generateSchema(it) } + CompiledSwaggerSchema( + typeData = WildcardTypeData(), + swagger = SwaggerSchemaUtils().subtypesSchema( + optionSchemas.map { it.swagger } + ), + componentSchemas = buildMap { + optionSchemas.forEach { optionSchema -> + this.putAll(optionSchema.componentSchemas) + } + } + ) + } + is EmptyTypeDescriptor -> { + CompiledSwaggerSchema( + typeData = WildcardTypeData(), + swagger = SwaggerSchemaUtils().anyObjectSchema(), + componentSchemas = emptyMap() + ) + } + } + } + + private fun generateSchema(type: KType): CompiledSwaggerSchema { + return listOf(type) + .processReflection() + .generateSwaggerSchema() + .withAutoTitle(TitleType.SIMPLE) + .compileReferencingRoot() + .first() + } + + private fun collectTypeDescriptor(routes: Collection): List { + val descriptors = mutableListOf() + routes + .filter { !it.documentation.hidden } + .forEach { route -> + route.documentation.request.also { request -> + request.parameters.forEach { parameter -> + descriptors.add(parameter.type) + } + request.body?.also { body -> + when (body) { + is OpenApiSimpleBodyData -> { + descriptors.add(body.type) + } + is OpenApiMultipartBodyData -> { + body.parts.forEach { part -> + descriptors.add(part.type) + } + } + } + } + } + route.documentation.responses.forEach { response -> + response.headers.forEach { (_, header) -> + header.type?.also { descriptors.add(it) } + } + response.body?.also { body -> + when (body) { + is OpenApiSimpleBodyData -> { + descriptors.add(body.type) + } + is OpenApiMultipartBodyData -> { + body.parts.forEach { part -> + descriptors.add(part.type) + } + } + } + } + } + } + return descriptors + } + + override fun getSchema(typeDescriptor: TypeDescriptor): Schema<*> { + return rootSchemas[typeDescriptor] ?: throw Exception("no root-schema for given type-descriptor") + } + + override fun getComponentSection(): Map> { + return componentSchemas + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaTypeAttributeOverride.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaTypeAttributeOverride.kt deleted file mode 100644 index f160b36..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaTypeAttributeOverride.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.smiley4.ktorswaggerui.builder.schema - -import com.fasterxml.jackson.databind.node.ObjectNode -import com.github.victools.jsonschema.generator.FieldScope -import com.github.victools.jsonschema.generator.SchemaGenerationContext -import com.github.victools.jsonschema.generator.TypeAttributeOverrideV2 -import com.github.victools.jsonschema.generator.TypeScope -import io.github.smiley4.ktorswaggerui.dsl.Example -import io.swagger.v3.oas.annotations.media.Schema - -/** - * Customizes the generates json-schema by adding fields from annotations ([Schema], [Example]) - */ -class SchemaTypeAttributeOverride : TypeAttributeOverrideV2 { - - override fun overrideTypeAttributes(objectNode: ObjectNode, scope: TypeScope?, context: SchemaGenerationContext?) { - if (scope is FieldScope) { - scope.getAnnotation(Schema::class.java)?.also { annotation -> - if (annotation.example != "") { - objectNode.put("example", annotation.example) - } - } - scope.getAnnotation(Example::class.java)?.also { annotation -> - objectNode.put("example", annotation.value) - } - } - } - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/TypeOverwrites.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/TypeOverwrites.kt deleted file mode 100644 index c74646f..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/TypeOverwrites.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.smiley4.ktorswaggerui.builder.schema - -import io.github.smiley4.ktorswaggerui.dsl.getSchemaType -import java.io.File - -object TypeOverwrites { - - /** - * overwrite the schemas of the given types with the given custom json-schemas instead of generating them. - */ - val entries = mutableMapOf( - getSchemaType() to """{"type":"string", "format":"binary"}""", - ) - - - /** - * @return the type-overwrite-[entries] - */ - fun get() = entries - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/BaseCustomSchema.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/BaseCustomSchema.kt deleted file mode 100644 index 5be9f7f..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/BaseCustomSchema.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.smiley4.ktorswaggerui.data - -import io.swagger.v3.oas.models.media.Schema - -sealed class BaseCustomSchema - -class CustomJsonSchema(val provider: () -> String) : BaseCustomSchema() - -class CustomOpenApiSchema(val provider: () -> Schema) : BaseCustomSchema() - -class RemoteSchema(val url: String) : BaseCustomSchema() diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/EncodingData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/EncodingData.kt deleted file mode 100644 index 9ee925d..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/EncodingData.kt +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.smiley4.ktorswaggerui.data - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.github.victools.jsonschema.generator.Option -import com.github.victools.jsonschema.generator.OptionPreset -import com.github.victools.jsonschema.generator.SchemaGenerator -import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder -import com.github.victools.jsonschema.generator.SchemaVersion -import com.github.victools.jsonschema.module.jackson.JacksonModule -import com.github.victools.jsonschema.module.swagger2.Swagger2Module -import io.github.smiley4.ktorswaggerui.dsl.ExampleEncoder -import io.github.smiley4.ktorswaggerui.dsl.SchemaEncoder -import io.github.smiley4.ktorswaggerui.dsl.SchemaType -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaTypeAttributeOverride -import kotlin.reflect.jvm.javaType - -data class EncodingData( - val exampleEncoder: ExampleEncoder, - val schemaEncoder: SchemaEncoder, - val schemaDefsField: String -) { - - companion object { - val DEFAULT = EncodingData( - exampleEncoder = defaultExampleEncoder(), - schemaEncoder = defaultSchemaEncoder(), - schemaDefsField = "\$defs" - ) - - - /** - * The default jackson object mapper used for encoding examples to json. - */ - var DEFAULT_EXAMPLE_OBJECT_MAPPER = jacksonObjectMapper() - - - /** - * The default [SchemaGenerator] used to encode types to json-schema. - * See https://victools.github.io/jsonschema-generator/#generator-options for more information. - */ - var DEFAULT_SCHEMA_GENERATOR = SchemaGenerator(schemaGeneratorConfigBuilder().build()) - - - /** - * The default [ExampleEncoder] - */ - fun defaultExampleEncoder(): ExampleEncoder { - return { _, value -> encodeExample(value) } - } - - - /** - * encode the given value to a json string - */ - fun encodeExample(value: Any?): String { - return if (value is String) { - value - } else { - DEFAULT_EXAMPLE_OBJECT_MAPPER.writeValueAsString(value) - } - } - - - /** - * The default [SchemaEncoder] - */ - fun defaultSchemaEncoder(): SchemaEncoder { - return { type -> encodeSchema(type) } - } - - - /** - * encode the given type to a json-schema - */ - fun encodeSchema(type: SchemaType): String { - return DEFAULT_SCHEMA_GENERATOR.generateSchema(type.javaType).toPrettyString() - } - - - /** - * The default [SchemaGeneratorConfigBuilder] - */ - fun schemaGeneratorConfigBuilder(): SchemaGeneratorConfigBuilder = - SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON) - .with(Swagger2Module()) - .with(Option.EXTRA_OPEN_API_FORMAT_VALUES) - .with(Option.ALLOF_CLEANUP_AT_THE_END) - .with(Option.MAP_VALUES_AS_ADDITIONAL_PROPERTIES) - .with(Option.DEFINITIONS_FOR_ALL_OBJECTS) - .with(Option.INLINE_NULLABLE_SCHEMAS) - .with(JacksonModule()) - .without(Option.INLINE_ALL_SCHEMAS) - .also { - it.forTypesInGeneral() - .withTypeAttributeOverride(SchemaTypeAttributeOverride()) - } - - } - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt new file mode 100644 index 0000000..17a8a32 --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt @@ -0,0 +1,24 @@ +package io.github.smiley4.ktorswaggerui.data + +import io.ktor.http.ContentType + +sealed class OpenApiBaseBodyData( + val description: String?, + val required: Boolean, + val mediaTypes: Set, +) + +class OpenApiSimpleBodyData( + description: String?, + required: Boolean, + mediaTypes: Set, + val type: TypeDescriptor, + val examples: Map +) : OpenApiBaseBodyData(description, required, mediaTypes) + +class OpenApiMultipartBodyData( + description: String?, + required: Boolean, + mediaTypes: Set, + val parts: List +) : OpenApiBaseBodyData(description, required, mediaTypes) diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt new file mode 100644 index 0000000..d496c0e --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt @@ -0,0 +1,7 @@ +package io.github.smiley4.ktorswaggerui.data + +data class OpenApiExampleData( + val value: Any, + val summary: String?, + val description: String?, +) \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt new file mode 100644 index 0000000..f9bac91 --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt @@ -0,0 +1,8 @@ +package io.github.smiley4.ktorswaggerui.data + +data class OpenApiHeaderData( + val description: String?, + val type: TypeDescriptor?, + val required: Boolean, + val deprecated: Boolean, +) \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt new file mode 100644 index 0000000..cc39239 --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt @@ -0,0 +1,10 @@ +package io.github.smiley4.ktorswaggerui.data + +import io.ktor.http.ContentType + +data class OpenApiMultipartPartData( + val name: String, + val type: TypeDescriptor, + val mediaTypes: Set, + val headers: Map, +) \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt new file mode 100644 index 0000000..99f00ba --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt @@ -0,0 +1,6 @@ +package io.github.smiley4.ktorswaggerui.data + +data class OpenApiRequestData( + val parameters: List, + val body: OpenApiBaseBodyData?, +) \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt new file mode 100644 index 0000000..ddcf54c --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt @@ -0,0 +1,14 @@ +package io.github.smiley4.ktorswaggerui.data + +data class OpenApiRequestParameterData( + val name: String, + val type: TypeDescriptor, + val location: ParameterLocation, + val description: String?, + val example: Any?, + val required: Boolean, + val deprecated: Boolean, + val allowEmptyValue: Boolean, + val explode: Boolean, + val allowReserved: Boolean +) \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt new file mode 100644 index 0000000..953e96f --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt @@ -0,0 +1,8 @@ +package io.github.smiley4.ktorswaggerui.data + +data class OpenApiResponseData( + val statusCode: String, + val description: String?, + val headers: Map, + val body: OpenApiBaseBodyData?, +) \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt new file mode 100644 index 0000000..627468a --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt @@ -0,0 +1,15 @@ +package io.github.smiley4.ktorswaggerui.data + +data class OpenApiRouteData( + val specId: String?, + val tags: List, + val summary: String?, + val description: String?, + val operationId: String?, + val deprecated: Boolean, + val hidden: Boolean, + val securitySchemeNames: List, + val protected: Boolean?, + val request: OpenApiRequestData, + val responses: List, +) \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt new file mode 100644 index 0000000..6e3e8b4 --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt @@ -0,0 +1,5 @@ +package io.github.smiley4.ktorswaggerui.data + +enum class ParameterLocation { + QUERY, HEADER, PATH +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt index 73a7553..12b9552 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt @@ -2,5 +2,4 @@ package io.github.smiley4.ktorswaggerui.data import io.ktor.http.HttpMethod - typealias PathFilter = (method: HttpMethod, url: List) -> Boolean diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt index 7e07e7c..45e7708 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt @@ -1,11 +1,10 @@ package io.github.smiley4.ktorswaggerui.data -import io.github.smiley4.ktorswaggerui.dsl.OpenApiResponse -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl +import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl import kotlin.reflect.KClass data class PluginConfigData( - val defaultUnauthorizedResponse: OpenApiResponse?, + val defaultUnauthorizedResponse: OpenApiResponseData?, val defaultSecuritySchemeNames: Set, val tagGenerator: TagGenerator, val specAssigner: SpecAssigner, @@ -17,9 +16,6 @@ data class PluginConfigData( val externalDocs: ExternalDocsData, val securitySchemes: List, val tags: List, - val customSchemas: Map, - val includeAllCustomSchemas: Boolean, - val encoding: EncodingData, val specConfigs: MutableMap, val whenBuildOpenApiSpecs: WhenBuildOpenApiSpecs? ) { @@ -38,9 +34,6 @@ data class PluginConfigData( externalDocs = ExternalDocsData.DEFAULT, securitySchemes = emptyList(), tags = emptyList(), - customSchemas = emptyMap(), - includeAllCustomSchemas = false, - encoding = EncodingData.DEFAULT, specConfigs = mutableMapOf(), whenBuildOpenApiSpecs = null ) diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt new file mode 100644 index 0000000..afd7df5 --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -0,0 +1,17 @@ +package io.github.smiley4.ktorswaggerui.data + +import kotlin.reflect.KType + +sealed interface TypeDescriptor { + companion object { + fun todo(v: Any?) = EmptyTypeDescriptor + } +} + +class KTypeDescriptor(val type: KType) : TypeDescriptor + +class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor + +class OneOfTypeDescriptor(val types: List) : TypeDescriptor + +object EmptyTypeDescriptor : TypeDescriptor \ No newline at end of file diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/BodyTypeDescriptor.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/BodyTypeDescriptor.kt deleted file mode 100644 index 72d74f8..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/BodyTypeDescriptor.kt +++ /dev/null @@ -1,130 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -import kotlin.reflect.KClass - -/** - * Describes the type/schema of a request or response body. - * [BodyTypeDescriptor]s can be nested to build more specific bodies from simple types - */ -sealed interface BodyTypeDescriptor { - - companion object { - - /** - * A [BodyTypeDescriptor] of the specific given type (or empty if type is null). - */ - fun typeOf(type: KClass<*>?) = type?.let { SchemaBodyTypeDescriptor(it.asSchemaType()) } ?: EmptyBodyTypeDescriptor() - - - /** - * A [BodyTypeDescriptor] of the specific given type (or empty if type is null). - */ - fun typeOf(type: SchemaType?) = type?.let { SchemaBodyTypeDescriptor(it) } ?: EmptyBodyTypeDescriptor() - - - /** - * A [BodyTypeDescriptor] of the specific given generic type. - */ - inline fun typeOf() = SchemaBodyTypeDescriptor(getSchemaType()) - - - /** - * Type can be any one of the given types. - */ - fun oneOf(vararg type: KClass<*>) = OneOfBodyTypeDescriptor(type.toList().map { typeOf(it.asSchemaType()) }) - - - /** - * Type can be any one of the given types. - */ - @JvmName("oneOfClass") - fun oneOf(types: Collection>) = OneOfBodyTypeDescriptor(types.map { typeOf(it.asSchemaType()) }) - - - /** - * Type can be any one of the given types. - */ - fun oneOf(vararg type: SchemaType) = OneOfBodyTypeDescriptor(type.map { typeOf(it) }) - - - /** - * Type can be any one of the given types. - */ - @JvmName("oneOfType") - fun oneOf(types: Collection) = OneOfBodyTypeDescriptor(types.map { typeOf(it) }) - - - /** - * Type can be any one of the given types. - */ - fun oneOf(vararg type: BodyTypeDescriptor) = OneOfBodyTypeDescriptor(type.toList()) - - - /** - * Type can be any one of the given types. - */ - @JvmName("oneOfDescriptor") - fun oneOf(types: Collection) = OneOfBodyTypeDescriptor(types.toList()) - - - /** - * Type is an array of the specific given type. - */ - fun multipleOf(type: KClass<*>) = CollectionBodyTypeDescriptor(typeOf(type.asSchemaType())) - - - /** - * Type is an array of the specific given type. - */ - fun multipleOf(type: SchemaType) = CollectionBodyTypeDescriptor(typeOf(type)) - - - /** - * Type is an array of the given type. - */ - fun multipleOf(type: BodyTypeDescriptor) = CollectionBodyTypeDescriptor(type) - - - /** - * A [BodyTypeDescriptor] of the specific given custom schema. - */ - fun custom(customSchemaId: String) = CustomRefBodyTypeDescriptor(customSchemaId) - - - /** - * An empty type. - */ - fun empty() = EmptyBodyTypeDescriptor() - } - -} - - -/** - * Describes an empty type - */ -class EmptyBodyTypeDescriptor : BodyTypeDescriptor - - -/** - * Describes a specific type/schema - */ -class SchemaBodyTypeDescriptor(val schemaType: SchemaType) : BodyTypeDescriptor - - -/** - * Describes any one of the given types - */ -class OneOfBodyTypeDescriptor(val elements: List) : BodyTypeDescriptor - - -/** - * Describes an array of the given type - */ -class CollectionBodyTypeDescriptor(val schemaType: BodyTypeDescriptor) : BodyTypeDescriptor - - -/** - * Describes the custom schema/type with the given id - */ -class CustomRefBodyTypeDescriptor(val customSchemaId: String) : BodyTypeDescriptor diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/CustomSchemaRef.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/CustomSchemaRef.kt deleted file mode 100644 index 417bef8..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/CustomSchemaRef.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -@Deprecated( - "Use BodyTypeDescriptor instead", - ReplaceWith("BodyTypeDescriptor.custom(schemaId)") -) -fun obj(schemaId: String) = BodyTypeDescriptor.custom(schemaId) - - -@Deprecated( - "Use BodyTypeDescriptor instead", - ReplaceWith("BodyTypeDescriptor.multipleOf(BodyTypeDescriptor.custom(schemaId))") -) -fun array(schemaId: String) = BodyTypeDescriptor.multipleOf(BodyTypeDescriptor.custom(schemaId)) diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/CustomSchemas.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/CustomSchemas.kt deleted file mode 100644 index 11f5e8f..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/CustomSchemas.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -import io.github.smiley4.ktorswaggerui.data.BaseCustomSchema -import io.github.smiley4.ktorswaggerui.data.CustomJsonSchema -import io.github.smiley4.ktorswaggerui.data.CustomOpenApiSchema -import io.github.smiley4.ktorswaggerui.data.RemoteSchema -import io.swagger.v3.oas.models.media.Schema - -@OpenApiDslMarker -class CustomSchemas { - - private val schemas = mutableMapOf() - - fun getSchema(id: String): BaseCustomSchema? = schemas[id] - - fun getSchemas() = schemas - - /** - * Define the json-schema for an object/body with the given id - */ - fun json(id: String, provider: () -> String) { - schemas[id] = CustomJsonSchema(provider) - } - - - /** - * Define the [Schema] for an object/body with the given id - */ - fun openApi(id: String, provider: () -> Schema) { - schemas[id] = CustomOpenApiSchema(provider) - } - - - /** - * Define the external url for an object/body with the given id - */ - fun remote(id: String, url: String) { - schemas[id] = RemoteSchema(url) - } - - - /** - * Whether to include all custom-schemas or only the ones directly used in any route-documentation - */ - var includeAll = false - -} - - diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/EncodingConfig.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/EncodingConfig.kt deleted file mode 100644 index 1714a22..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/EncodingConfig.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -import io.github.smiley4.ktorswaggerui.data.DataUtils.mergeDefault -import io.github.smiley4.ktorswaggerui.data.EncodingData - - -typealias ExampleEncoder = (type: SchemaType?, example: Any) -> String? - -typealias SchemaEncoder = (type: SchemaType) -> String? - -/** - * Configuration for encoding examples, schemas, ... - */ -@OpenApiDslMarker -class EncodingConfig { - - /** - * Encode the given example object into a json-string. - */ - fun exampleEncoder(encoder: ExampleEncoder) { - exampleEncoder = encoder - } - - private var exampleEncoder: ExampleEncoder = EncodingData.DEFAULT.exampleEncoder - - - /** - * Encode the given type into a valid json-schema. - * This encoder does not affect custom-schemas provided in the plugin-config. - */ - fun schemaEncoder(encoder: SchemaEncoder) { - schemaEncoder = encoder - } - - private var schemaEncoder: SchemaEncoder = EncodingData.DEFAULT.schemaEncoder - - - /** - * the name of the field (if it exists) in the json-schema containing schema-definitions. - */ - var schemaDefinitionsField = EncodingData.DEFAULT.schemaDefsField - - - fun build(base: EncodingData) = EncodingData( - exampleEncoder = mergeDefault(base.exampleEncoder, exampleEncoder, EncodingData.DEFAULT.exampleEncoder), - schemaEncoder = mergeDefault(base.schemaEncoder, schemaEncoder, EncodingData.DEFAULT.schemaEncoder), - schemaDefsField = mergeDefault(base.schemaDefsField, schemaDefinitionsField, EncodingData.DEFAULT.schemaDefsField), - ) - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/Example.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/Example.kt deleted file mode 100644 index 1e4541c..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/Example.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -/** - * Annotation to add an example value to the field of an object. - */ -@Target(AnnotationTarget.FIELD) -annotation class Example( - val value: String -) diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiHeader.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiHeader.kt deleted file mode 100644 index 2a7472b..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiHeader.kt +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - - - -@OpenApiDslMarker -class OpenApiHeader { - - /** - * A description of the header - */ - var description: String? = null - - - /** - * The schema of the header - */ - var type: SchemaType? = null - - - /** - * Determines whether this header is mandatory - */ - var required: Boolean? = null - - - /** - * Specifies that a header is deprecated and SHOULD be transitioned out of usage - */ - var deprecated: Boolean? = null - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiMultipartBody.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiMultipartBody.kt deleted file mode 100644 index fd5a61d..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiMultipartBody.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -import kotlin.reflect.KClass - - -/** - * Describes a single request/response body with multipart content. - * See https://swagger.io/docs/specification/describing-request-body/multipart-requests/ for more info - */ -@OpenApiDslMarker -class OpenApiMultipartBody : OpenApiBaseBody() { - - private val parts = mutableListOf() - - fun getParts(): List = parts - - - /** - * One part of a multipart-body - */ - fun part(name: String, type: BodyTypeDescriptor, block: OpenApiMultipartPart.() -> Unit) { - parts.add(OpenApiMultipartPart(name, type).apply(block)) - } - - - /** - * One part of a multipart-body - */ - fun part(name: String, type: BodyTypeDescriptor) = part(name, type) {} - - - /** - * One part of a multipart-body - */ - fun part(name: String, type: SchemaType, block: OpenApiMultipartPart.() -> Unit) = part(name, BodyTypeDescriptor.typeOf(type), block) - - - /** - * One part of a multipart-body - */ - fun part(name: String, type: KClass<*>) = part(name, type.asSchemaType()) {} - - - /** - * One part of a multipart-body - */ - inline fun part(name: String) = part(name, getSchemaType()) {} - - - /** - * One part of a multipart-body - */ - inline fun part(name: String, noinline block: OpenApiMultipartPart.() -> Unit) = part(name, getSchemaType(), block) - - - /** - * One part of a multipart-body - */ - fun part(name: String, customSchemaId: String, block: OpenApiMultipartPart.() -> Unit) = - part(name, BodyTypeDescriptor.custom(customSchemaId), block) - - - /** - * One part of a multipart-body - */ - fun part(name: String, customSchemaId: String) = part(name, customSchemaId) {} - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiMultipartPart.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiMultipartPart.kt deleted file mode 100644 index 60ae1f7..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiMultipartPart.kt +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -import io.ktor.http.ContentType -import kotlin.reflect.KClass - -/** - * Describes one section of a multipart-body. - * See https://swagger.io/docs/specification/describing-request-body/multipart-requests/ for more info - */ -@OpenApiDslMarker -class OpenApiMultipartPart( - /** - * The name of this part - */ - val name: String, - - val type: BodyTypeDescriptor -) { - - /** - * Set a specific content type for this part - */ - var mediaTypes: Collection = setOf() - - private val headers = mutableMapOf() - - fun getHeaders(): Map = headers - - - /** - * Possible headers for this part - */ - fun header(name: String, type: SchemaType, block: OpenApiHeader.() -> Unit) { - headers[name] = OpenApiHeader().apply(block).apply { - this.type = type - } - } - - /** - * Possible headers for this part - */ - fun header(name: String, type: KClass<*>, block: OpenApiHeader.() -> Unit) = header(name, type.asSchemaType(), block) - - /** - * Possible headers for this part - */ - fun header(name: String, type: KClass<*>) = header(name, type) {} - - - /** - * Possible headers for this part - */ - inline fun header(name: String) = header(name, getSchemaType()) {} - - - /** - * Possible headers for this part - */ - inline fun header(name: String, noinline block: OpenApiHeader.() -> Unit) = header(name, getSchemaType(), block) - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRequest.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRequest.kt deleted file mode 100644 index 0f851e7..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRequest.kt +++ /dev/null @@ -1,188 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRequestParameter.Location -import kotlin.reflect.KClass - - -@OpenApiDslMarker -class OpenApiRequest { - - /** - * A list of parameters that are applicable for this operation - */ - private val parameters = mutableListOf() - - fun getParameters(): List = parameters - - - /** - * A path parameters that is applicable for this operation - */ - fun parameter(location: Location, name: String, type: SchemaType, block: OpenApiRequestParameter.() -> Unit) { - parameters.add(OpenApiRequestParameter(name, type, location).apply(block)) - } - - - /** - * A path parameters that is applicable for this operation - */ - fun pathParameter(name: String, type: KClass<*>, block: OpenApiRequestParameter.() -> Unit) = - parameter(Location.PATH, name, type.asSchemaType(), block) - - - /** - * A path parameters that is applicable for this operation - */ - fun pathParameter(name: String, type: KClass<*>) = pathParameter(name, type) {} - - - /** - * A path parameters that is applicable for this operation - */ - inline fun pathParameter(name: String) = - parameter(Location.PATH, name, getSchemaType()) {} - - - /** - * A path parameters that is applicable for this operation - */ - inline fun pathParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = - parameter(Location.PATH, name, getSchemaType(), block) - - - /** - * A query parameters that is applicable for this operation - */ - fun queryParameter(name: String, type: KClass<*>, block: OpenApiRequestParameter.() -> Unit) = - parameter(Location.QUERY, name, type.asSchemaType(), block) - - - /** - * A query parameters that is applicable for this operation - */ - fun queryParameter(name: String, type: KClass<*>) = queryParameter(name, type) {} - - - /** - * A query parameters that is applicable for this operation - */ - inline fun queryParameter(name: String) = - parameter(Location.QUERY, name, getSchemaType()) {} - - - /** - * A query parameters that is applicable for this operation - */ - inline fun queryParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = - parameter(Location.QUERY, name, getSchemaType(), block) - - - /** - * A header parameters that is applicable for this operation - */ - fun headerParameter(name: String, type: KClass<*>, block: OpenApiRequestParameter.() -> Unit) = - parameter(Location.HEADER, name, type.asSchemaType(), block) - - - /** - * A header parameters that is applicable for this operation - */ - fun headerParameter(name: String, type: SchemaType) = parameter(Location.HEADER, name, type) {} - - - /** - * A header parameters that is applicable for this operation - */ - inline fun headerParameter(name: String) = - parameter(Location.HEADER, name, getSchemaType()) {} - - - /** - * A header parameters that is applicable for this operation - */ - inline fun headerParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = - parameter(Location.HEADER, name, getSchemaType(), block) - - - private var body: OpenApiBaseBody? = null - - fun getBody() = body - - - /** - * The body returned with this request - */ - fun body(typeDescriptor: BodyTypeDescriptor, block: OpenApiSimpleBody.() -> Unit) { - body = OpenApiSimpleBody(typeDescriptor).apply(block) - } - - /** - * The body returned with this request - */ - fun body(typeDescriptor: BodyTypeDescriptor) = body(typeDescriptor) {} - - /** - * The request body applicable for this operation - */ - fun body(type: SchemaType?, block: OpenApiSimpleBody.() -> Unit) = body(BodyTypeDescriptor.typeOf(type), block) - - - /** - * The request body applicable for this operation - */ - fun body(type: KClass<*>) = body(type) {} - - - /** - * The request body applicable for this operation - */ - fun body(type: KClass<*>, block: OpenApiSimpleBody.() -> Unit) = body(type.asSchemaType(), block) - - - /** - * The request body applicable for this operation - */ - @JvmName("bodyGenericType") - inline fun body(noinline block: OpenApiSimpleBody.() -> Unit) = body(getSchemaType(), block) - - - /** - * The request body applicable for this operation - */ - inline fun body() = body(getSchemaType()) {} - - - /** - * The request body applicable for this operation - */ - fun body(block: OpenApiSimpleBody.() -> Unit) = body(BodyTypeDescriptor.empty(), block) - - - /** - * The body returned with this request - */ - fun body(customSchemaId: String) = body(customSchemaId) {} - - - /** - * The body returned with this request - */ - fun body(customSchemaId: String, block: OpenApiSimpleBody.() -> Unit) = body(BodyTypeDescriptor.custom(customSchemaId), block) - - - /** - * The multipart-body returned with this request - */ - fun multipartBody(block: OpenApiMultipartBody.() -> Unit) { - body = OpenApiMultipartBody().apply(block) - } - - - /** - * Set the body of this request. Intended for internal use. - */ - fun setBody(body: OpenApiBaseBody?) { - this.body = body - } - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiResponse.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiResponse.kt deleted file mode 100644 index 4d9c4b9..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiResponse.kt +++ /dev/null @@ -1,130 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -import kotlin.reflect.KClass - -/** - * A container for the expected responses of an operation. The container maps an HTTP response code to the expected response. - * A response code can only have one response object. - */ -@OpenApiDslMarker -class OpenApiResponse(val statusCode: String) { - - /** - * A short description of the response - */ - var description: String? = null - - private val headers = mutableMapOf() - - fun getHeaders(): Map = headers - - - /** - * Possible headers returned with this response - */ - fun header(name: String, type: SchemaType, block: OpenApiHeader.() -> Unit) { - headers[name] = OpenApiHeader().apply(block).apply { - this.type = type - } - } - - - /** - * Possible headers returned with this response - */ - fun header(name: String, type: KClass<*>, block: OpenApiHeader.() -> Unit) = header(name, type.asSchemaType(), block) - - - /** - * Possible headers returned with this response - */ - fun header(name: String, type: KClass<*>) = header(name, type.asSchemaType()) {} - - - /** - * Possible headers returned with this response - */ - inline fun header(name: String) = header(name, getSchemaType()) {} - - - /** - * Possible headers returned with this response - */ - inline fun header(name: String, noinline block: OpenApiHeader.() -> Unit) = header(name, getSchemaType(), block) - - - private var body: OpenApiBaseBody? = null - - fun getBody() = body - - - /** - * The body returned with this response - */ - fun body(type: BodyTypeDescriptor, block: OpenApiSimpleBody.() -> Unit) { - body = OpenApiSimpleBody(type).apply(block) - } - - - /** - * The body returned with this response - */ - fun body(type: BodyTypeDescriptor) = body(type) {} - - - /** - * The body returned with this response - */ - fun body(type: SchemaType?, block: OpenApiSimpleBody.() -> Unit) = body(BodyTypeDescriptor.typeOf(type), block) - - - /** - * The body returned with this response - */ - fun body(type: KClass<*>, block: OpenApiSimpleBody.() -> Unit) = body(type.asSchemaType(), block) - - - /** - * The body returned with this response - */ - @JvmName("bodyGenericType") - inline fun body(noinline block: OpenApiSimpleBody.() -> Unit) = body(getSchemaType(), block) - - - /** - * The body returned with this response - */ - fun body(type: KClass<*>) = body(type) {} - - - /** - * The body returned with this response - */ - inline fun body() = body(getSchemaType()) {} - - - /** - * The body returned with this response - */ - fun body(block: OpenApiSimpleBody.() -> Unit) = body(null, block) - - /** - * The body returned with this response - */ - fun body(customSchemaId: String, block: OpenApiSimpleBody.() -> Unit) = body(BodyTypeDescriptor.custom(customSchemaId), block) - - - /** - * The body returned with this response - */ - fun body(customSchemaId: String) = body(customSchemaId) {} - - - /** - * The multipart-body returned with this response - */ - fun multipartBody(block: OpenApiMultipartBody.() -> Unit) { - body = OpenApiMultipartBody().apply(block) - } - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/SchemaType.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/SchemaType.kt deleted file mode 100644 index b52dcc1..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/SchemaType.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl - -import kotlin.reflect.KClass -import kotlin.reflect.KType -import kotlin.reflect.full.starProjectedType -import kotlin.reflect.typeOf - -typealias SchemaType = KType - - -inline fun getSchemaType(): SchemaType { - return typeOf() -} - -fun SchemaType.getTypeName() = this.toString() - -fun SchemaType.getSimpleTypeName(): String { - val rawName = getTypeName() - return if (rawName.contains("<") || rawName.contains(">")) { - rawName - } else { - (this.classifier as KClass<*>).simpleName ?: rawName - } -} - -fun SchemaType.getSimpleArrayElementTypeName(): String { - if (this.arguments.size != 1) { - throw IllegalArgumentException("Could not determine type of array-elements") - } else { - return this.arguments.first().let { arg -> - arg.type?.getSimpleTypeName() ?: arg.toString() - } - } -} - -fun KClass<*>.asSchemaType() = this.starProjectedType diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiContact.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiContact.kt similarity index 88% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiContact.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiContact.kt index 7ab26b6..3936ef6 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiContact.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiContact.kt @@ -1,7 +1,8 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.ContactData import io.github.smiley4.ktorswaggerui.data.DataUtils.merge +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** * Contact information for the exposed API. diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExternalDocs.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt similarity index 85% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExternalDocs.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt index 7fd85bc..ea10463 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExternalDocs.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt @@ -1,7 +1,8 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.DataUtils import io.github.smiley4.ktorswaggerui.data.ExternalDocsData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** * An object representing external documentation. diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiInfo.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt similarity index 94% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiInfo.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt index 98efa89..9c64120 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiInfo.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt @@ -1,10 +1,11 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.ContactData import io.github.smiley4.ktorswaggerui.data.DataUtils.merge import io.github.smiley4.ktorswaggerui.data.DataUtils.mergeDefault import io.github.smiley4.ktorswaggerui.data.InfoData import io.github.smiley4.ktorswaggerui.data.LicenseData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @OpenApiDslMarker class OpenApiInfo { diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiLicense.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt similarity index 84% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiLicense.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt index 5faf8d9..66fef89 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiLicense.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt @@ -1,7 +1,8 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.DataUtils import io.github.smiley4.ktorswaggerui.data.LicenseData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** * License information for the exposed API. diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiSecurityScheme.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt similarity index 95% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiSecurityScheme.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt index f3b4c29..86e47bc 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiSecurityScheme.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt @@ -1,4 +1,4 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.AuthKeyLocation import io.github.smiley4.ktorswaggerui.data.AuthScheme @@ -6,6 +6,7 @@ import io.github.smiley4.ktorswaggerui.data.AuthType import io.github.smiley4.ktorswaggerui.data.DataUtils.merge import io.github.smiley4.ktorswaggerui.data.OpenIdOAuthFlowsData import io.github.smiley4.ktorswaggerui.data.SecuritySchemeData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiServer.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt similarity index 88% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiServer.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt index 74367b6..36f5dda 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiServer.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt @@ -1,8 +1,9 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.DataUtils.merge import io.github.smiley4.ktorswaggerui.data.DataUtils.mergeDefault import io.github.smiley4.ktorswaggerui.data.ServerData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** * An object representing a Server. diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiTag.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt similarity index 89% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiTag.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt index 2d81c93..952deef 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiTag.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt @@ -1,7 +1,8 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.DataUtils.merge import io.github.smiley4.ktorswaggerui.data.TagData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** * Adds metadata to a single tag. diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenIdOAuthFlow.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt similarity index 90% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenIdOAuthFlow.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt index 55ececa..25cdcd7 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenIdOAuthFlow.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt @@ -1,7 +1,8 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.DataUtils.merge import io.github.smiley4.ktorswaggerui.data.OpenIdOAuthFlowData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** * Configuration details for a supported OAuth Flow diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenIdOAuthFlows.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlows.kt similarity index 94% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenIdOAuthFlows.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlows.kt index b13bb47..5b21dca 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenIdOAuthFlows.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlows.kt @@ -1,7 +1,8 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.OpenIdOAuthFlowData import io.github.smiley4.ktorswaggerui.data.OpenIdOAuthFlowsData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** * An object containing configuration information for the oauth flow types supported diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/PluginConfigDsl.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt similarity index 85% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/PluginConfigDsl.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt index 02e1d1f..b527c65 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/PluginConfigDsl.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt @@ -1,14 +1,14 @@ -package io.github.smiley4.ktorswaggerui.dsl +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.data.DataUtils.mergeBoolean +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiResponse import io.ktor.http.* import io.ktor.server.routing.* import kotlin.collections.Collection import kotlin.collections.Set import kotlin.collections.buildList -import kotlin.collections.buildMap import kotlin.collections.buildSet import kotlin.collections.component1 import kotlin.collections.component2 @@ -45,7 +45,7 @@ class PluginConfigDsl { defaultUnauthorizedResponse = OpenApiResponse(HttpStatusCode.Unauthorized.value.toString()).apply(block) } - private var defaultUnauthorizedResponse: OpenApiResponse? = PluginConfigData.DEFAULT.defaultUnauthorizedResponse + private var defaultUnauthorizedResponse: OpenApiResponse? = null /** @@ -144,26 +144,6 @@ class PluginConfigDsl { private val tags = mutableListOf() - /** - * Custom schemas to reference via [io.github.smiley4.ktorswaggerui.dsl.CustomSchemaRef] - */ - fun customSchemas(block: CustomSchemas.() -> Unit) { - this.customSchemas = CustomSchemas().apply(block) - } - - private var customSchemas = CustomSchemas() - - - /** - * customize the behaviour of different encoders (examples, schemas, ...) - */ - fun encoding(block: EncodingConfig.() -> Unit) { - block(encodingConfig) - } - - val encodingConfig: EncodingConfig = EncodingConfig() - - /** * List of all [RouteSelector] types in that should be ignored in the resulting url of any route. */ @@ -178,7 +158,7 @@ class PluginConfigDsl { internal fun build(base: PluginConfigData): PluginConfigData { return PluginConfigData( - defaultUnauthorizedResponse = merge(base.defaultUnauthorizedResponse, defaultUnauthorizedResponse), + defaultUnauthorizedResponse = merge(base.defaultUnauthorizedResponse, defaultUnauthorizedResponse?.build()), defaultSecuritySchemeNames = buildSet { addAll(base.defaultSecuritySchemeNames) defaultSecuritySchemeNames?.also { addAll(it) } @@ -206,12 +186,6 @@ class PluginConfigDsl { addAll(base.tags) addAll(tags.map { it.build(TagData.DEFAULT) }) }, - customSchemas = buildMap { - putAll(base.customSchemas) - putAll(customSchemas.getSchemas()) - }, - includeAllCustomSchemas = mergeBoolean(base.includeAllCustomSchemas, customSchemas.includeAll), - encoding = encodingConfig.build(base.encoding), specConfigs = mutableMapOf(), whenBuildOpenApiSpecs = whenBuildOpenApiSpecs, ).also { diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/SwaggerUIDsl.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt similarity index 97% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/SwaggerUIDsl.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt index 22c7de2..5ab658e 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/SwaggerUIDsl.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt @@ -1,9 +1,10 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.DataUtils.merge import io.github.smiley4.ktorswaggerui.data.DataUtils.mergeBoolean import io.github.smiley4.ktorswaggerui.data.DataUtils.mergeDefault import io.github.smiley4.ktorswaggerui.data.SwaggerUIData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @OpenApiDslMarker diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiBaseBody.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt similarity index 76% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiBaseBody.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt index 394ff74..e46f90d 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiBaseBody.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt @@ -1,5 +1,7 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.routes +import io.github.smiley4.ktorswaggerui.data.OpenApiBaseBodyData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker import io.ktor.http.ContentType /** @@ -29,4 +31,5 @@ sealed class OpenApiBaseBody { fun getMediaTypes(): Set = mediaTypes + abstract fun build(): OpenApiBaseBodyData } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExample.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt similarity index 53% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExample.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt index 77667cc..147f114 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiExample.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt @@ -1,4 +1,7 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.OpenApiExampleData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** * Documentation for an example object @@ -22,4 +25,10 @@ class OpenApiExample( */ var description: String? = null + + fun build() = OpenApiExampleData( + value = value, + summary = summary, + description = description + ) } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt new file mode 100644 index 0000000..7890d8d --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt @@ -0,0 +1,41 @@ +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.OpenApiHeaderData +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker + + +@OpenApiDslMarker +class OpenApiHeader { + + /** + * A description of the header + */ + var description: String? = null + + + /** + * The schema of the header + */ + var type: TypeDescriptor? = null + + + /** + * Determines whether this header is mandatory + */ + var required: Boolean? = null + + + /** + * Specifies that a header is deprecated and SHOULD be transitioned out of usage + */ + var deprecated: Boolean? = null + + + fun build() = OpenApiHeaderData( + description = description, + type = type, + required = required ?: false, + deprecated = deprecated ?: false + ) +} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt new file mode 100644 index 0000000..150a89b --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt @@ -0,0 +1,77 @@ +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartBodyData +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker + + +/** + * Describes a single request/response body with multipart content. + * See https://swagger.io/docs/specification/describing-request-body/multipart-requests/ for more info + */ +@OpenApiDslMarker +class OpenApiMultipartBody : OpenApiBaseBody() { + + private val parts = mutableListOf() + + fun getParts(): List = parts + + + /** + * One part of a multipart-body + */ + fun part(name: String, type: TypeDescriptor, block: OpenApiMultipartPart.() -> Unit) { + parts.add(OpenApiMultipartPart(name, type).apply(block)) + } + + + /** + * One part of a multipart-body + */ + fun part(name: String, type: TypeDescriptor) = part(name, type) {} + + +// /** +// * One part of a multipart-body +// */ +// fun part(name: String, type: KType, block: OpenApiMultipartPart.() -> Unit) = part(name, KTypeDescriptor(type), block) +// +// +// /** +// * One part of a multipart-body +// */ +// fun part(name: String, type: KClass<*>) = part(name, type.asSchemaType()) {} +// +// +// /** +// * One part of a multipart-body +// */ +// inline fun part(name: String) = part(name, getSchemaType()) {} +// +// +// /** +// * One part of a multipart-body +// */ +// inline fun part(name: String, noinline block: OpenApiMultipartPart.() -> Unit) = part(name, getSchemaType(), block) + + +// /** +// * One part of a multipart-body +// */ +// fun part(name: String, customSchemaId: String, block: OpenApiMultipartPart.() -> Unit) = +// part(name, BodyTypeDescriptor.custom(customSchemaId), block) +// +// +// /** +// * One part of a multipart-body +// */ +// fun part(name: String, customSchemaId: String) = part(name, customSchemaId) {} + + + override fun build() = OpenApiMultipartBodyData( + description = description, + required = required ?: false, + mediaTypes = getMediaTypes(), + parts = getParts().map { it.build() } + ) +} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt new file mode 100644 index 0000000..1f4bb8f --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt @@ -0,0 +1,73 @@ +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartPartData +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.ktor.http.ContentType + +/** + * Describes one section of a multipart-body. + * See https://swagger.io/docs/specification/describing-request-body/multipart-requests/ for more info + */ +@OpenApiDslMarker +class OpenApiMultipartPart( + /** + * The name of this part + */ + val name: String, + + val type: TypeDescriptor +) { + + /** + * Set a specific content type for this part + */ + var mediaTypes: Collection = setOf() + + private val headers = mutableMapOf() + + fun getHeaders(): Map = headers + + + /** + * Possible headers for this part + */ + fun header(name: String, type: TypeDescriptor, block: OpenApiHeader.() -> Unit) { + headers[name] = OpenApiHeader().apply(block).apply { + this.type = type + } + } + + +// /** +// * Possible headers for this part +// */ +// fun header(name: String, type: KClass<*>, block: OpenApiHeader.() -> Unit) = header(name, KTypeDescriptor(type.starProjectedType), block) +// + +// /** +// * Possible headers for this part +// */ +// fun header(name: String, type: KClass<*>) = header(name, type) {} + +// +// /** +// * Possible headers for this part +// */ +// inline fun header(name: String) = header(name, KTypeDescriptor(getKType())) {} +// +// +// /** +// * Possible headers for this part +// */ +// inline fun header(name: String, noinline block: OpenApiHeader.() -> Unit) = header(name, getSchemaType(), block) + + + fun build() = OpenApiMultipartPartData( + name = name, + type = type, + mediaTypes = mediaTypes.toSet(), + headers = headers.mapValues { it.value.build() } + ) + +} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt new file mode 100644 index 0000000..6bbb81a --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt @@ -0,0 +1,188 @@ +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.EmptyTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.OpenApiRequestData +import io.github.smiley4.ktorswaggerui.data.ParameterLocation +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import kotlin.reflect.KType + + +@OpenApiDslMarker +class OpenApiRequest { + + /** + * A list of parameters that are applicable for this operation + */ + private val parameters = mutableListOf() + + fun getParameters(): List = parameters + + + /** + * A path parameters that is applicable for this operation + */ + fun parameter(location: ParameterLocation, name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit) { + parameters.add(OpenApiRequestParameter(name, type, location).apply(block)) + } + + +// /** +// * A path parameters that is applicable for this operation +// */ +// fun pathParameter(name: String, type: KClass<*>, block: OpenApiRequestParameter.() -> Unit) = +// parameter(ParameterLocation.PATH, name, type.asSchemaType(), block) +// +// +// /** +// * A path parameters that is applicable for this operation +// */ +// fun pathParameter(name: String, type: KClass<*>) = pathParameter(name, type) {} +// +// +// /** +// * A path parameters that is applicable for this operation +// */ +// inline fun pathParameter(name: String) = +// parameter(ParameterLocation.PATH, name, getSchemaType()) {} +// +// +// /** +// * A path parameters that is applicable for this operation +// */ +// inline fun pathParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = +// parameter(ParameterLocation.PATH, name, getSchemaType(), block) +// + + /** + * A query parameters that is applicable for this operation + */ + fun queryParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit) = + parameter(ParameterLocation.QUERY, name, type, block) + + +// /** +// * A query parameters that is applicable for this operation +// */ +// fun queryParameter(name: String, type: KClass<*>) = queryParameter(name, type) {} +// +// +// /** +// * A query parameters that is applicable for this operation +// */ +// inline fun queryParameter(name: String) = +// parameter(ParameterLocation.QUERY, name, getSchemaType()) {} +// +// +// /** +// * A query parameters that is applicable for this operation +// */ +// inline fun queryParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = +// parameter(ParameterLocation.QUERY, name, getSchemaType(), block) + + + /** + * A header parameters that is applicable for this operation + */ + fun headerParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit) = + parameter(ParameterLocation.HEADER, name, type, block) + +// +// /** +// * A header parameters that is applicable for this operation +// */ +// fun headerParameter(name: String, type: SchemaType) = parameter(ParameterLocation.HEADER, name, type) {} +// +// +// /** +// * A header parameters that is applicable for this operation +// */ +// inline fun headerParameter(name: String) = +// parameter(ParameterLocation.HEADER, name, getSchemaType()) {} +// +// +// /** +// * A header parameters that is applicable for this operation +// */ +// inline fun headerParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = +// parameter(ParameterLocation.HEADER, name, getSchemaType(), block) + + + private var body: OpenApiBaseBody? = null + + fun getBody() = body + + + /** + * The body returned with this request + */ + fun body(typeDescriptor: TypeDescriptor, block: OpenApiSimpleBody.() -> Unit) { + body = OpenApiSimpleBody(typeDescriptor).apply(block) + } + + + /** + * The body returned with this request + */ + fun body(typeDescriptor: TypeDescriptor) = body(typeDescriptor) {} + + + /** + * The request body applicable for this operation + */ + fun body(type: KType, block: OpenApiSimpleBody.() -> Unit) = body(KTypeDescriptor(type), block) + + +// /** +// * The request body applicable for this operation +// */ +// fun body(type: KClass<*>) = body(type) {} + + +// /** +// * The request body applicable for this operation +// */ +// fun body(type: KClass<*>, block: OpenApiSimpleBody.() -> Unit) = body(type.asSchemaType(), block) +// +// +// /** +// * The request body applicable for this operation +// */ +// @JvmName("bodyGenericType") +// inline fun body(noinline block: OpenApiSimpleBody.() -> Unit) = body(getSchemaType(), block) +// +// +// /** +// * The request body applicable for this operation +// */ +// inline fun body() = body(getSchemaType()) {} + + + /** + * The request body applicable for this operation + */ + fun body(block: OpenApiSimpleBody.() -> Unit) = body(EmptyTypeDescriptor, block) + + + /** + * The multipart-body returned with this request + */ + fun multipartBody(block: OpenApiMultipartBody.() -> Unit) { + body = OpenApiMultipartBody().apply(block) + } + + + /** + * Set the body of this request. Intended for internal use. + */ + fun setBody(body: OpenApiBaseBody?) { + this.body = body + } + + fun build() = OpenApiRequestData( + parameters = parameters.map { it.build() }, + body = body?.build() + ) + +} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRequestParameter.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt similarity index 65% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRequestParameter.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt index 9f07798..86c203d 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRequestParameter.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt @@ -1,4 +1,9 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.OpenApiRequestParameterData +import io.github.smiley4.ktorswaggerui.data.ParameterLocation +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @OpenApiDslMarker @@ -10,18 +15,13 @@ class OpenApiRequestParameter( /** * The type defining the schema used for the parameter. */ - val type: SchemaType, + val type: TypeDescriptor, /** * Location of the parameter */ - val location: Location + val location: ParameterLocation ) { - enum class Location { - QUERY, HEADER, PATH - } - - /** * A brief description of the parameter */ @@ -66,4 +66,18 @@ class OpenApiRequestParameter( */ var allowReserved: Boolean? = null + + fun build() = OpenApiRequestParameterData( + name = name, + type = TypeDescriptor.todo(type), + location = location, + description = description, + example = example, + required = required ?: false, + deprecated = deprecated ?: false, + allowEmptyValue = allowEmptyValue ?: true, + explode = explode ?: false, + allowReserved = allowReserved ?: true, + ) + } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt new file mode 100644 index 0000000..f802b4c --- /dev/null +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt @@ -0,0 +1,143 @@ +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.OpenApiResponseData +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import kotlin.reflect.KType + +/** + * A container for the expected responses of an operation. The container maps an HTTP response code to the expected response. + * A response code can only have one response object. + */ +@OpenApiDslMarker +class OpenApiResponse(val statusCode: String) { + + /** + * A short description of the response + */ + var description: String? = null + + private val headers = mutableMapOf() + + fun getHeaders(): Map = headers + + + /** + * Possible headers returned with this response + */ + fun header(name: String, type: TypeDescriptor, block: OpenApiHeader.() -> Unit) { + headers[name] = OpenApiHeader().apply(block).apply { + this.type = type + } + } + +// +// /** +// * Possible headers returned with this response +// */ +// fun header(name: String, type: KClass<*>, block: OpenApiHeader.() -> Unit) = header(name, type.asSchemaType(), block) +// +// +// /** +// * Possible headers returned with this response +// */ +// fun header(name: String, type: KClass<*>) = header(name, type.asSchemaType()) {} +// +// +// /** +// * Possible headers returned with this response +// */ +// inline fun header(name: String) = header(name, getSchemaType()) {} +// +// +// /** +// * Possible headers returned with this response +// */ +// inline fun header(name: String, noinline block: OpenApiHeader.() -> Unit) = header(name, getSchemaType(), block) + + + private var body: OpenApiBaseBody? = null + + fun getBody() = body + + + /** + * The body returned with this response + */ + fun body(type: TypeDescriptor, block: OpenApiSimpleBody.() -> Unit) { + body = OpenApiSimpleBody(type).apply(block) + } + + + /** + * The body returned with this response + */ + fun body(type: TypeDescriptor) = body(type) {} + + + /** + * The body returned with this response + */ + fun body(type: KType, block: OpenApiSimpleBody.() -> Unit) = body(KTypeDescriptor(type), block) + + +// /** +// * The body returned with this response +// */ +// fun body(type: KClass<*>, block: OpenApiSimpleBody.() -> Unit) = body(type.asSchemaType(), block) +// +// +// /** +// * The body returned with this response +// */ +// @JvmName("bodyGenericType") +// inline fun body(noinline block: OpenApiSimpleBody.() -> Unit) = body(getSchemaType(), block) +// +// +// /** +// * The body returned with this response +// */ +// fun body(type: KClass<*>) = body(type) {} +// +// +// /** +// * The body returned with this response +// */ +// inline fun body() = body(getSchemaType()) {} +// +// +// /** +// * The body returned with this response +// */ +// fun body(block: OpenApiSimpleBody.() -> Unit) = body(EmptyTypeDescriptor, block) + + +// /** +// * The body returned with this response +// */ +// fun body(customSchemaId: String, block: OpenApiSimpleBody.() -> Unit) = body(BodyTypeDescriptor.custom(customSchemaId), block) +// +// +// /** +// * The body returned with this response +// */ +// fun body(customSchemaId: String) = body(customSchemaId) {} + + + /** + * The multipart-body returned with this response + */ + fun multipartBody(block: OpenApiMultipartBody.() -> Unit) { + body = OpenApiMultipartBody().apply(block) + } + + + fun build() = OpenApiResponseData( + statusCode = statusCode, + description = description, + headers = headers.mapValues { it.value.build() }, + body = body?.build() + ) + +} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiResponses.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponses.kt similarity index 89% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiResponses.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponses.kt index 5499ecc..0a41cf2 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiResponses.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponses.kt @@ -1,5 +1,6 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.routes +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker import io.ktor.http.HttpStatusCode /** diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRoute.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt similarity index 75% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRoute.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt index 68dc0db..dc273a3 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiRoute.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt @@ -1,4 +1,7 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.OpenApiRouteData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @OpenApiDslMarker class OpenApiRoute { @@ -8,6 +11,7 @@ class OpenApiRoute { */ var specId: String? = null + /** * A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier */ @@ -60,6 +64,7 @@ class OpenApiRoute { */ var securitySchemeNames: Collection? = null + /** * Specifies whether this operation is protected. * If not specified, the authentication state of the Ktor route will be used @@ -91,4 +96,26 @@ class OpenApiRoute { fun getResponses() = responses + + fun build() = OpenApiRouteData( + specId = specId, + tags = tags, + summary = summary, + description = description, + operationId = operationId, + deprecated = deprecated, + hidden = hidden, + securitySchemeNames = buildList { + if(securitySchemeNames != null) { + addAll(securitySchemeNames!!) + } + if(securitySchemeName != null) { + add(securitySchemeName!!) + } + }, + protected = protected, + request = request.build(), + responses = responses.getResponses().map { it.build() }, + ) + } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiSimpleBody.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt similarity index 53% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiSimpleBody.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt index cd2de62..fc0f321 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiSimpleBody.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt @@ -1,4 +1,8 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.routes + +import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker /** @@ -9,7 +13,7 @@ class OpenApiSimpleBody( /** * The type defining the schema used for the body. */ - val type: BodyTypeDescriptor, + val type: TypeDescriptor, ) : OpenApiBaseBody() { /** @@ -25,4 +29,13 @@ class OpenApiSimpleBody( fun getExamples(): Map = examples + + override fun build() = OpenApiSimpleBodyData( + description = description, + required = required ?: false, + mediaTypes = getMediaTypes(), + type = type, + examples = getExamples().mapValues { it.value.build() }, + ) + } diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/DocumentedRouteSelector.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/DocumentedRouteSelector.kt similarity index 98% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/DocumentedRouteSelector.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/DocumentedRouteSelector.kt index f15f99d..9becb49 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/DocumentedRouteSelector.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/DocumentedRouteSelector.kt @@ -1,5 +1,6 @@ -package io.github.smiley4.ktorswaggerui.dsl +package io.github.smiley4.ktorswaggerui.dsl.routing +import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRoute import io.ktor.http.HttpMethod import io.ktor.server.application.ApplicationCall import io.ktor.server.routing.Route diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/resources/DocumentedResourceRoutes.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/resources/DocumentedResourceRoutes.kt similarity index 93% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/resources/DocumentedResourceRoutes.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/resources/DocumentedResourceRoutes.kt index ed6c553..fb30d31 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/resources/DocumentedResourceRoutes.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/resources/DocumentedResourceRoutes.kt @@ -1,8 +1,8 @@ -package io.github.smiley4.ktorswaggerui.dsl.resources +package io.github.smiley4.ktorswaggerui.dsl.routing.resources -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute -import io.github.smiley4.ktorswaggerui.dsl.documentation -import io.github.smiley4.ktorswaggerui.dsl.method +import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRoute +import io.github.smiley4.ktorswaggerui.dsl.routing.documentation +import io.github.smiley4.ktorswaggerui.dsl.routing.method import io.ktor.http.HttpMethod import io.ktor.server.application.ApplicationCall import io.ktor.server.resources.handle diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ControllerUtils.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ControllerUtils.kt deleted file mode 100644 index bb99093..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ControllerUtils.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.smiley4.ktorswaggerui.routing - -import io.ktor.server.config.ApplicationConfig - -object ControllerUtils { - - fun getRootPath(appConfig: ApplicationConfig): String { - return appConfig.propertyOrNull("ktor.deployment.rootPath")?.getString()?.let { "/${dropSlashes(it)}" } ?: "" - } - - fun dropSlashes(str: String): String { - var value = str - value = if (value.startsWith("/")) value.substring(1) else value - value = if (value.endsWith("/")) value.substring(0, value.length - 1) else value - return value - } - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ForwardRouteController.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ForwardRouteController.kt deleted file mode 100644 index bcaa16b..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ForwardRouteController.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.smiley4.ktorswaggerui.routing - -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.config.ApplicationConfig -import io.ktor.server.response.respondRedirect -import io.ktor.server.routing.get -import io.ktor.server.routing.routing - -class ForwardRouteController( - private val appConfig: ApplicationConfig, - private val swaggerUiConfig: PluginConfigData, -) { - - fun setup(app: Application) { - app.routing { - get { - call.respondRedirect("${getRootUrl()}/index.html") - } - } - } - - private fun getRootUrl(): String { - return "/" + listOf( - ControllerUtils.getRootPath(appConfig), - swaggerUiConfig.swaggerUI.rootHostPath, - swaggerUiConfig.swaggerUI.swaggerUrl, - ) - .filter { it.isNotBlank() } - .map { ControllerUtils.dropSlashes(it) } - .joinToString("/") - } - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/SwaggerController.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/SwaggerController.kt deleted file mode 100644 index bb680ef..0000000 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/SwaggerController.kt +++ /dev/null @@ -1,130 +0,0 @@ -package io.github.smiley4.ktorswaggerui.routing - -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call -import io.ktor.server.auth.authenticate -import io.ktor.server.config.ApplicationConfig -import io.ktor.server.request.uri -import io.ktor.server.response.respond -import io.ktor.server.response.respondRedirect -import io.ktor.server.response.respondText -import io.ktor.server.routing.Route -import io.ktor.server.routing.get -import io.ktor.server.routing.route -import io.ktor.server.routing.routing - -class SwaggerController( - private val appConfig: ApplicationConfig, - private val pluginConfig: PluginConfigData, - private val swaggerWebjarVersion: String, - private val specName: String?, - private val jsonSpec: String, -) { - - companion object { - const val DEFAULT_SPEC_NAME: String = "api" - } - - fun setup(app: Application) { - app.routing { - if (pluginConfig.swaggerUI.authentication == null) { - setup() - } else { - authenticate(pluginConfig.swaggerUI.authentication) { - setup() - } - } - } - } - - private fun Route.setup() { - route(getSubUrl()) { - get { - val rootHostPath = if (pluginConfig.swaggerUI.rootHostPath.isNotBlank()) { - ControllerUtils.dropSlashes("/${pluginConfig.swaggerUI.rootHostPath}") - } else "" - call.respondRedirect("$rootHostPath${call.request.uri}/index.html") - } - get("{filename}") { - serveStaticResource(call.parameters["filename"]!!, call) - } - get("swagger-initializer.js") { - serveSwaggerInitializer(call) - } - get("${specName ?: DEFAULT_SPEC_NAME}.json") { - serveOpenApiSpec(call) - } - } - } - - private suspend fun serveSwaggerInitializer(call: ApplicationCall) { - val swaggerUiConfig = pluginConfig.swaggerUI - // see https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md for reference - val propValidatorUrl = swaggerUiConfig.validatorUrl?.let { "validatorUrl: \"$it\"" } ?: "validatorUrl: false" - val propDisplayOperationId = "displayOperationId: ${swaggerUiConfig.displayOperationId}" - val propFilter = "filter: ${swaggerUiConfig.showTagFilterInput}" - val propSort = "operationsSorter: " + - if (swaggerUiConfig.sort == SwaggerUiSort.NONE) "undefined" - else "\"${swaggerUiConfig.sort.value}\"" - val propSyntaxHighlight = "syntaxHighlight: { theme: \"${swaggerUiConfig.syntaxHighlight.value}\" }" - val content = """ - window.onload = function() { - window.ui = SwaggerUIBundle({ - url: "${getRootUrl(appConfig)}/${specName ?: DEFAULT_SPEC_NAME}.json", - dom_id: '#swagger-ui', - deepLinking: true, - presets: [ - SwaggerUIBundle.presets.apis, - SwaggerUIStandalonePreset - ], - plugins: [ - SwaggerUIBundle.plugins.DownloadUrl - ], - layout: "StandaloneLayout", - withCredentials: ${swaggerUiConfig.withCredentials}, - $propValidatorUrl, - $propDisplayOperationId, - $propFilter, - $propSort, - $propSyntaxHighlight - }); - }; - """.trimIndent() - call.respondText(ContentType.Application.JavaScript, HttpStatusCode.OK) { content } - } - - private suspend fun serveOpenApiSpec(call: ApplicationCall) { - call.respondText(ContentType.Application.Json, HttpStatusCode.OK) { jsonSpec } - } - - private suspend fun serveStaticResource(filename: String, call: ApplicationCall) { - val resource = this::class.java.getResource("/META-INF/resources/webjars/swagger-ui/$swaggerWebjarVersion/$filename") - if (resource != null) { - call.respond(ResourceContent(resource)) - } else { - call.respond(HttpStatusCode.NotFound, "$filename could not be found") - } - } - - private fun getRootUrl(appConfig: ApplicationConfig): String { - return "${ControllerUtils.getRootPath(appConfig)}${getSubUrl(true)}" - } - - private fun getSubUrl(withRootHostPath: Boolean = false): String { - return "/" + listOfNotNull( - if (withRootHostPath) pluginConfig.swaggerUI.rootHostPath else null, - pluginConfig.swaggerUI.swaggerUrl, - specName - ) - .filter { it.isNotBlank() } - .map { ControllerUtils.dropSlashes(it) } - .joinToString("/") - } - - -} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/manualRouting.kt b/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt similarity index 96% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/manualRouting.kt rename to src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt index 1e90969..5363d48 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/manualRouting.kt +++ b/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt @@ -4,8 +4,8 @@ import io.github.smiley4.ktorswaggerui.SWAGGER_UI_WEBJARS_VERSION import io.github.smiley4.ktorswaggerui.SwaggerUI import io.github.smiley4.ktorswaggerui.data.SwaggerUIData import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl -import io.github.smiley4.ktorswaggerui.dsl.route +import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl +import io.github.smiley4.ktorswaggerui.dsl.routing.route import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/RootPathExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt similarity index 58% rename from src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/RootPathExample.kt rename to src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt index e87eb94..357edda 100644 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/RootPathExample.kt +++ b/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt @@ -1,45 +1,36 @@ -package io.github.smiley4.ktorswaggerui.examples +package io.github.smiley4.ktorswaggerui -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.get +import io.github.smiley4.ktorswaggerui.dsl.routing.resources.get import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application import io.ktor.server.application.call import io.ktor.server.application.install import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.EngineMain import io.ktor.server.netty.Netty import io.ktor.server.response.respondText import io.ktor.server.routing.routing - +/** + * A minimal working example + */ fun main() { embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) } private fun Application.myModule() { - - install(SwaggerUI) { - swagger { - swaggerUrl = "swagger-ui" - forwardRoot = true - } - info { - title = "Example API" - version = "latest" - description = "Example api for testing" - } - server { - url = "http://localhost:8080" - description = "Development server" - } - } + install(SwaggerUI) routing { get("hello", { + request { + body { + example("example", 42) + } + } description = "Simple 'Hello World'- Route" response { HttpStatusCode.OK to { + body() description = "Successful Response" } } @@ -48,3 +39,9 @@ private fun Application.myModule() { } } } + + +class MyClass( + val size: Int, + val tags: List +) \ No newline at end of file diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/AuthExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/AuthExample.kt deleted file mode 100644 index e061db7..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/AuthExample.kt +++ /dev/null @@ -1,120 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.AuthScheme -import io.github.smiley4.ktorswaggerui.data.AuthType -import io.github.smiley4.ktorswaggerui.dsl.get -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.auth.Authentication -import io.ktor.server.auth.UserIdPrincipal -import io.ktor.server.auth.authenticate -import io.ktor.server.auth.basic -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing -import io.swagger.v3.oas.models.servers.Server - -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -/** - * Example to show how to access protected routes via swagger-ui - * USERNAME = "user" - * PASSWORD = "pass" - */ -private fun Application.myModule() { - - // Install "Authentication"-Plugin and setup Basic-Auth - install(Authentication) { - basic { - realm = "Access to the API" - validate { credentials -> - if (credentials.name == "user" && credentials.password == "pass") { - UserIdPrincipal(credentials.name) - } else { - null - } - } - } - } - - // Install "Swagger-UI"-Plugin - install(SwaggerUI) { - // default value for "401 Unauthorized"-responses. - // the name of the security scheme (see below) to use for each route when nothing else is specified - defaultSecuritySchemeName = "MySecurityScheme" - defaultUnauthorizedResponse { - description = "Username or password is invalid." - } - // specify a security scheme - securityScheme("MySecurityScheme") { - type = AuthType.HTTP - scheme = AuthScheme.BASIC - } - // specify another security scheme - securityScheme("MyOtherSecurityScheme") { - type = AuthType.HTTP - scheme = AuthScheme.BASIC - } - } - - // configure routes - routing { - authenticate { - // route is in an "authenticate"-block -> default security scheme will be used (see plugin-config "defaultSecuritySchemeName") - get("hello", { - // Set the security schemes to be used by this route - securitySchemeNames = setOf("MyOtherSecurityScheme", "MySecurityScheme") - description = "Protected 'Hello World'-Route" - response { - HttpStatusCode.OK to { - description = "Successful Request" - body { description = "the response" } - } - // response for "401 Unauthorized" is automatically added (see plugin-config "defaultUnauthorizedResponse"). - } - }) { - call.respondText("Hello World!") - } - } - // route is not in an "authenticate"-block and does not set the `protected` property -> security schemes will be ignored - get("hello-unprotected", { - // Security scheme will be ignored since the operation is not protected - securitySchemeNames = setOf("MyOtherSecurityScheme", "MySecurityScheme") - description = "Unprotected 'Hello World'-Route" - response { - HttpStatusCode.OK to { - description = "Successful Request" - body { description = "the response" } - } - // no response for "401 Unauthorized" is added - } - }) { - call.respondText("Hello World!") - } - // route is not in an "authenticate"-block but sets the `protected` property - // -> security scheme (or default security scheme) will be used - get("hello-externally-protected", { - // mark the route as protected even though there is no "authenticate"-block - // (e.g. because the route is protected by an external proxy) - protected = true - // Set the security scheme to be used by this route - securitySchemeName = "MyOtherSecurityScheme" - description = "Externally protected 'Hello World'-Route" - response { - HttpStatusCode.OK to { - description = "Successful Request" - body { description = "the response" } - } - // response for "401 Unauthorized" is automatically added (see plugin-config "defaultUnauthorizedResponse"). - } - }) { - call.respondText("Hello World!") - } - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteExample.kt deleted file mode 100644 index 94593d7..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteExample.kt +++ /dev/null @@ -1,288 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import com.fasterxml.jackson.core.util.DefaultIndenter -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter -import com.fasterxml.jackson.databind.SerializationFeature -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.delete -import io.github.smiley4.ktorswaggerui.dsl.get -import io.github.smiley4.ktorswaggerui.dsl.post -import io.github.smiley4.ktorswaggerui.dsl.route -import io.ktor.http.ContentType -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpStatusCode -import io.ktor.serialization.jackson.jackson -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation -import io.ktor.server.request.receive -import io.ktor.server.response.respond -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing -import io.swagger.v3.oas.models.Operation -import io.swagger.v3.oas.models.PathItem -import io.swagger.v3.oas.models.responses.ApiResponse -import io.swagger.v3.oas.models.responses.ApiResponses -import java.util.Random - -/** - * Arbitrary examples to show (and test) as many features as possible (for authentication see "AuthExample") - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -data class MathRequest( - val a: Int, - val b: Int -) - -data class MathResult( - val value: Int -) - -enum class Color { - RED, GREEN, BLUE -} - -data class GenericResponse( - val success: Boolean, - val data: T -) - -data class SpecificResponseData( - val text: String -) - -private fun Application.myModule() { - - install(SwaggerUI) { - swagger { - swaggerUrl = "swagger-ui" - forwardRoot = true - } - info { - title = "Example API" - version = "latest" - description = "Example API for testing and demonstration purposes." - } - externalDocs { - url = "https://github.com/SMILEY4/ktor-swagger-ui/wiki" - description = "Sample external documentation object" - } - server { - url = "http://localhost:8080" - description = "Development Server" - } - tag("random") { - description = "Routes that return random things" - } - tag("math") { - description = "Routes for math related operations" - } - generateTags { url -> listOf(url.firstOrNull()) } - whenBuildOpenApiSpecs = { spec -> - spec.paths.addPathItem("customPath", PathItem().also { path -> - path.get = Operation().also { op -> - op.description = "This path was added after generating the openapi-spec" - op.responses = ApiResponses().also { responses -> - responses.addApiResponse("200", ApiResponse()) - responses.addApiResponse("404", ApiResponse()) - } - } - }) - } - } - - install(ContentNegotiation) { - jackson { - configure(SerializationFeature.INDENT_OUTPUT, true) - setDefaultPrettyPrinter(DefaultPrettyPrinter().apply { - indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance) - indentObjectsWith(DefaultIndenter(" ", "\n")) - }) - } - } - - routing { - - get("hello", { - tags = listOf("test") - description = "Hello World Endpoint" - operationId = "hello" - response { - default { - description = "Default Response" - } - HttpStatusCode.OK to { - description = "Successful Request" - body { description = "the response" } - } - HttpStatusCode.InternalServerError to { - description = "Something unexpected happened" - } - "Custom" to { - description = "Custom Response" - } - } - }) { - call.respondText("Hello World!") - } - - post("math/{operation}", { - tags = listOf("test") - description = "Performs the given operation on the given values and returns the result" - operationId = "performMathOperation" - request { - pathParameter("operation") { - description = "the math operation to perform. Either 'add' or 'sub'" - example = "add" - } - body { - example("First", MathRequest(13, 19)) { - description = "Either an addition of 13 and 19 or a subtraction of 19 from 13" - } - example("Second", MathRequest(20, 7)) { - description = "Either an addition of 20 and 7 or a subtraction of 7 from 20" - } - } - } - response { - HttpStatusCode.OK to { - description = "The operation was successful" - body { - description = "The result of the operation" - example("First", MathResult(42)) { - summary = "The first example" - description = "For example the result of an addition of 13 and 29" - } - example("Second", MathResult(-13)) { - summary = "The second example" - description = "For example the result of an subtracting 20 from 7" - } - } - } - HttpStatusCode.BadRequest to { - description = "An invalid operation was provided" - } - } - }) { - val operation = call.parameters["operation"]!! - call.receive().let { request -> - when (operation) { - "add" -> call.respond(HttpStatusCode.OK, MathResult(request.a + request.b)) - "sub" -> call.respond(HttpStatusCode.OK, MathResult(request.a - request.b)) - else -> call.respond(HttpStatusCode.BadRequest, Unit) - } - } - } - - post("random/results", { - response { - HttpStatusCode.OK to { - body> { - mediaType(ContentType.Application.Json) - mediaType(ContentType.Application.Xml) - } - } - } - }) { - call.respond(HttpStatusCode.OK, (0..5).map { MathResult(Random().nextInt()) }) - } - - post("random/numbers", { - response { - HttpStatusCode.OK to { - body>() - } - } - }) { - call.respond(HttpStatusCode.OK, (0..5).map { Random().nextInt() }) - } - - post("echo/{color}", { - request { - pathParameter("color") { - example = Color.BLUE - } - } - response { - HttpStatusCode.OK to { - body() - } - } - }) { - call.respond(HttpStatusCode.OK, Color.valueOf(call.parameters["color"]!!).toString()) - } - - get("generics", { - response { - HttpStatusCode.OK to { - body>() - } - } - }) { - call.respond(HttpStatusCode.NotImplemented, "not-inplemented") - } - - route("images", { - tags = listOf("Image Operations") - description = "Access images " - }) { - - post({ - response { - HttpStatusCode.OK to { - body { - mediaType(ContentType.Image.PNG) - mediaType(ContentType.Image.JPEG) - mediaType(ContentType.Image.SVG) - } - header(HttpHeaders.ContentLength) - } - } - }) { - call.respond(HttpStatusCode.NotImplemented, "...") - } - - delete("{id}", { - description = "Delete the image with the given id." - request { - pathParameter("id") { - description = "The id of the image to delete" - } - } - response { - HttpStatusCode.OK to { - description = "The image was deleted" - } - } - }) { - call.respond(HttpStatusCode.NotImplemented, "...") - } - - } - - get("2dIntArray", { - description = "Returns a 2d-array of integers" - response { - HttpStatusCode.OK to { - body>>() - } - } - }) { - call.respond(HttpStatusCode.NotImplemented, "...") - } - - get("hidden", { - hidden = true - description = "This route is hidden and not visible in swagger" - }) { - call.respond(HttpStatusCode.NotImplemented, "...") - } - - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompletePluginConfigExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompletePluginConfigExample.kt deleted file mode 100644 index 70f21ba..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CompletePluginConfigExample.kt +++ /dev/null @@ -1,112 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.github.victools.jsonschema.generator.SchemaGenerator -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.AuthScheme -import io.github.smiley4.ktorswaggerui.data.AuthType -import io.github.smiley4.ktorswaggerui.data.EncodingData -import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort -import io.github.smiley4.ktorswaggerui.data.SwaggerUiSyntaxHighlight -import io.github.smiley4.ktorswaggerui.dsl.EncodingConfig -import io.ktor.server.application.Application -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.swagger.v3.oas.models.media.Schema -import kotlin.reflect.jvm.javaType - -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - - -/** - * Example of an (almost) complete plugin config. - * This config will (probably) not work, but is only supposed to show all/most configuration options. - */ -private fun Application.myModule() { - - install(SwaggerUI) { - securityScheme("ApiAuth") { - type = AuthType.HTTP - scheme = AuthScheme.BASIC - } - securityScheme("SwaggerAuth") { - type = AuthType.HTTP - scheme = AuthScheme.BASIC - } - defaultSecuritySchemeName = "ApiAuth" - defaultUnauthorizedResponse { - description = "invalid username or password" - } - swagger { - forwardRoot = false - swaggerUrl = "/api/swagger-ui" - rootHostPath = "/my-ktor-web-app" - authentication = "SwaggerAuth" - disableSpecValidator() - displayOperationId = true - showTagFilterInput = true - sort = SwaggerUiSort.ALPHANUMERICALLY - syntaxHighlight = SwaggerUiSyntaxHighlight.AGATE - } - pathFilter = { _, url -> url.firstOrNull() != "test" } - info { - title = "Example API" - version = "latest" - description = "This is an example api" - termsOfService = "example.com" - contact { - name = "Mr. Example" - url = "example.com/contact" - email = "example@mail.com" - } - license { - name = "Mr. Example" - url = "example.com/license" - } - } - externalDocs { - url = "https://docs.example.com" - description = "Example external documentation description" - } - server { - url = "localhost:8080" - description = "develop server" - } - server { - url = "127.0.0.1:8080" - description = "production server" - } - tag("greet") { - description = "routes for greeting" - externalDocDescription = "documentation for greetings" - externalDocUrl = "example.com/doc" - } - generateTags { url -> listOf(url.firstOrNull()) } - customSchemas { - json("customSchema1") { - """{"type": "string"}""" - } - openApi("customSchema2") { - Schema().also { - it.type = "string" - } - } - remote("customSchema3", "example.com/schema") - includeAll = false - } - encoding { - schemaEncoder { type -> - SchemaGenerator(EncodingData.schemaGeneratorConfigBuilder().build()) - .generateSchema(type.javaType) - .toPrettyString() - } - schemaDefinitionsField = "\$defs" - exampleEncoder { type, example -> - jacksonObjectMapper().writeValueAsString(example) - } - } - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemaExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemaExample.kt deleted file mode 100644 index 73b71f7..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemaExample.kt +++ /dev/null @@ -1,149 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor.Companion.custom -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor.Companion.multipleOf -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor.Companion.oneOf -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor.Companion.typeOf -import io.github.smiley4.ktorswaggerui.dsl.get -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.request.receive -import io.ktor.server.response.respond -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing - -/** - * An example for defining custom json-schemas - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - - data class MyRequestData( - val someText: String, - val someBoolean: Boolean - ) - - data class MyResponseData( - val someText: String, - val someNumber: Long - ) - - data class Rectangle( - val width: Int, - val height: Int - ) - - data class Circle( - val radius: Int - ) - - data class Point( - val x: Int, - val y: Int - ) - - install(SwaggerUI) { - // don't show the test-routes providing json-schemas - pathFilter = { _, url -> url.firstOrNull() != "schema" } - customSchemas { - // specify a custom json-schema with the id 'myRequestData' - json("myRequestData") { - """ - { - "type": "object", - "properties": { - "someBoolean": { - "type": "boolean" - }, - "someText": { - "type": "string" - } - } - } - """.trimIndent() - } - // specify a remote json-schema with the id 'myRequestData' - remote("myResponseData", "http://localhost:8080/schema/myResponseData") - } - } - - routing { - - get("something", { - request { - // body referencing the custom schema with id 'myRequestData' - body("myRequestData") - } - response { - HttpStatusCode.OK to { - // body referencing the custom schema with id 'myResponseData' - body("myResponseData") - } - } - }) { - val text = call.receive().someText - call.respond(HttpStatusCode.OK, MyResponseData(text, 42)) - } - - get("something/many", { - request { - // body referencing the custom schema with id 'myRequestData' - body(multipleOf(custom("myRequestData"))) - } - response { - HttpStatusCode.OK to { - // body referencing the custom schema with id 'myResponseData' - body(multipleOf(custom("myResponseData"))) - } - } - }) { - val text = call.receive().someText - call.respond(HttpStatusCode.OK, MyResponseData(text, 42)) - } - - get("oneof/shapes", { - request { - // body allowing a mixed list of rectangles, circles and points - body( - multipleOf( - oneOf( - typeOf(Rectangle::class), - typeOf(Circle::class), - typeOf(Point::class), - ) - ) - ) - } - }) { - call.respond(HttpStatusCode.OK, Unit) - } - - // (external) endpoint providing a json-schema - get("schema/myResponseData") { - call.respondText( - """ - { - "type": "object", - "properties": { - "someNumber": { - "type": "integer", - "format": "int64" - }, - "someText": { - "type": "string" - } - } - } - """.trimIndent() - ) - } - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ExampleAnnotationExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ExampleAnnotationExample.kt deleted file mode 100644 index ab810cd..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ExampleAnnotationExample.kt +++ /dev/null @@ -1,119 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.Example -import io.github.smiley4.ktorswaggerui.dsl.get -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing -import io.swagger.v3.oas.annotations.media.ArraySchema -import io.swagger.v3.oas.annotations.media.Schema - -/** - * An example showcasing examples with the [Schema] and [io.github.smiley4.ktorswaggerui.dsl.Example]-Annotation - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - install(SwaggerUI) - routing { - get("person/example", { - request { - body() - } - response { - HttpStatusCode.OK to { - body() - } - } - }) { - call.respondText("...") - } - get("person/schema", { - request { - body() - } - response { - HttpStatusCode.OK to { - body() - } - } - }) { - call.respondText("...") - } - } -} - -data class ExamplePerson( - - @Example("red") - val favColor: String, - - @Example("Steve") - val name: String, - - @Example("42") - val age: Int, - - @Example("172") - val size: Float, - - @Example("false") - val robot: Boolean, - - val address: ExampleAddress, - - val secondaryAddresses: List -) - -data class ExampleAddress( - - @Example("New City") - val city: String, - - @Example("12345") - val code: Int - -) - -@Schema(description = "Schema of some person", title = "Person") -data class SchemaPerson( - - @field:Schema(example = "red") - val favColor: String, - - @field:Schema(example = "Steve", minLength = 1, maxLength = 32) - val name: String, - - @field:Schema(example = "42", minimum = "18", maximum = "99") - val age: Int, - - @field:Schema(example = "172", format = "int32") - val size: Float, - - @field:Schema(example = "false") - val robot: Boolean, - - val address: SchemaAddress, - - @field:ArraySchema(minItems = 1, maxItems = 32, uniqueItems = true) - val secondaryAddresses: List -) - -@Schema(description = "Schema of some address", title = "Address") -data class SchemaAddress( - - @field:Schema(example = "New City") - val city: String, - - @field:Schema(example = "12345") - val code: Int - -) diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUploadExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUploadExample.kt deleted file mode 100644 index 9d249ce..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUploadExample.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.post -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respond -import io.ktor.server.routing.routing -import java.io.File - -/** - * An example showcasing file uploads - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - - install(SwaggerUI) - - routing { - - post("single", { - request { - body { - mediaType(ContentType.Image.PNG) - mediaType(ContentType.Image.JPEG) - mediaType(ContentType.Image.SVG) - } - } - }) { - call.respond(HttpStatusCode.NotImplemented, "...") - } - - post("multipart", { - request { - multipartBody { - mediaType(ContentType.MultiPart.FormData) - part("firstImage") { - mediaTypes = setOf( - ContentType.Image.PNG, - ContentType.Image.JPEG, - ContentType.Image.GIF - ) - } - part("secondImage") { - mediaTypes = setOf( - ContentType.Image.PNG, - ContentType.Image.JPEG, - ContentType.Image.GIF - ) - } - part("name") - } - } - }) { - call.respond(HttpStatusCode.NotImplemented, "...") - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/KotlinxExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/KotlinxExample.kt deleted file mode 100644 index 800a833..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/KotlinxExample.kt +++ /dev/null @@ -1,92 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.get -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing -import kotlinx.datetime.Instant -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.serializer -import com.github.ricky12awesome.jss.encodeToSchema - -/** - * An example showing compatibility with kotlinx serializer and kotlinx multiplatform using: - * - https://github.com/Kotlin/kotlinx.serialization - * - https://github.com/Kotlin/kotlinx-datetime - * - https://github.com/tillersystems/json-schema-serialization - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - // create own [Json] instance - val kotlinxJson = Json { - prettyPrint = true - encodeDefaults = true - } - install(SwaggerUI) { - encoding { - // custom implementation of schema-encoder supporting kotlinx - schemaEncoder { type -> - kotlinxJson.encodeToSchema(serializer(type), generateDefinitions = false) - } - // generated schemas have the definitions in the field 'definitions' - schemaDefinitionsField = "definitions" - // custom implementation of json serializer for converting examples to json supporting kotlinx - exampleEncoder { type, value -> - kotlinxJson.encodeToString(serializer(type!!), value) - } - } - } - routing { - get("example/one", { - request { - body { - example( - "default", ExampleRequest.B( - thisIsB = Instant.fromEpochMilliseconds(System.currentTimeMillis()) - ) - ) - } - } - }) { - call.respondText("...") - } - get("example/many", { - request { - body> { - example("default", listOf( - ExampleRequest.B(Instant.fromEpochMilliseconds(System.currentTimeMillis())), - ExampleRequest.A(true) - )) - } - } - }) { - call.respondText("...") - } - } -} - - -@Serializable -private sealed class ExampleRequest { - - @Serializable - data class A( - val thisIsA: Boolean - ) : ExampleRequest() - - - @Serializable - data class B( - val thisIsB: Instant - ) : ExampleRequest() - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ManualRouting.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ManualRouting.kt deleted file mode 100644 index 50247ea..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ManualRouting.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.get -import io.github.smiley4.ktorswaggerui.routing.openApiSpec -import io.github.smiley4.ktorswaggerui.routing.swaggerUI -import io.ktor.server.application.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* -import io.ktor.server.response.* -import io.ktor.server.routing.* - -/** - * An example showcasing manual swaggerui-routing - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - - install(SwaggerUI) { - swagger { - automaticRouter = false - } - } - - routing { - - route("swagger") { - swaggerUI("/api.json") - } - route("api.json") { - openApiSpec() - } - - get("hello", { - description = "Simple 'Hello World'- Route" - }) { - call.respondText("Hello World!") - } - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ManualRoutingMultipleSpecsExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ManualRoutingMultipleSpecsExample.kt deleted file mode 100644 index a736eb1..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ManualRoutingMultipleSpecsExample.kt +++ /dev/null @@ -1,121 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.get -import io.github.smiley4.ktorswaggerui.dsl.route -import io.github.smiley4.ktorswaggerui.routing.openApiSpec -import io.github.smiley4.ktorswaggerui.routing.swaggerUI -import io.ktor.server.application.* -import io.ktor.server.auth.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* -import io.ktor.server.response.* -import io.ktor.server.routing.* - -/** - * An example showcasing manual routing with multiple openapi-specs in a single application - * - localhost:8080/swagger-ui/v1/index.html - * * /v1/hello - * - localhost:8080/swagger-ui/v2/index.html - * * /v2/hello - * * /hi - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - - install(Authentication) { - basic("auth-swagger") { - realm = "Access to the Swagger UI" - validate { credentials -> - if (credentials.name == "user" && credentials.password == "pass") { - UserIdPrincipal(credentials.name) - } else { - null - } - } - } - } - - install(SwaggerUI) { - swagger { - automaticRouter = false - } - // general configuration - info { - title = "Example API" - } - specAssigner = { _, _ -> "v2" } // assign all unassigned routes to spec "v2" (here e.g. '/hi') - - // configuration specific for spec "v1" - spec("v1") { - info { - version = "1.0" - } - } - - // configuration specific for spec "v2" - spec("v2") { - info { - version = "2.0" - } - swagger { - authentication = "auth-swagger" - } - } - } - - - routing { - - route("api") { - route("version-1.json") { - openApiSpec("v1") - } - route("version-2.json") { - openApiSpec("v2") - } - } - - route("swagger") { - route("version-1") { - swaggerUI("/api/version-1.json") - } - route("version-2") { - swaggerUI("/api/version-2.json") - } - } - - // version 1.0 routes - route("v1", { - specId = "v1" // assign all sub-routes to spec "v1" - }) { - get("hello", { - description = "Simple version 1 'Hello World'-Route" - }) { - call.respondText("Hello World!") - } - } - - // version 2.0 routes - route("v2", { - specId = "v2" // assign all sub-routes to spec "v2" - }) { - get("hello", { - description = "Simple version 2 'Hello World'-Route" - }) { - call.respondText("Improved Hello World!") - } - } - - // other routes - get("hi", { - description = "Alternative version of 'Hello World'-Route" - }) { - call.respondText("Alternative Hello World!") - } - - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MinimalExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MinimalExample.kt deleted file mode 100644 index 3dc897f..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MinimalExample.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.get -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing - -/** - * A minimal working example - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - // Install the "SwaggerUI"-Plugin and use the default configuration - // By default, swagger is available at /swagger-ui (i.e. localhost:8080/swagger-ui) - install(SwaggerUI) - - routing { - // documented "get"-route - get("hello", { - - request { - body { - example("example", 42) - } - } - - // a description of the route - description = "Simple 'Hello World'- Route" - // information about possible responses - response { - // information about a "200 OK" response - HttpStatusCode.OK to { - // the description of the response - description = "Successful Response" - } - } - }) { - call.respondText("Hello World!") - } - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipartBody.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipartBody.kt deleted file mode 100644 index c6a79e9..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipartBody.kt +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.ktor.server.application.Application -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.post -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing -import java.io.File - -/** - * An example of a multipart-body - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - - -private fun Application.myModule() { - - data class Coords( - val lat: Float, - val long: Float - ) - - data class Metadata( - val format: String, - val location: Coords - ) - - install(SwaggerUI) - - routing { - post("example", { - request { - multipartBody { - mediaType(ContentType.MultiPart.FormData) - part("image") { - mediaTypes = setOf( - ContentType.Image.PNG, - ContentType.Image.JPEG, - ContentType.Image.GIF - ) - } - part("metadata") { - mediaTypes = setOf(ContentType.Application.Json) - } - } - } - }) { - call.respondText("Upload complete") - } - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipleSpecsExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipleSpecsExample.kt deleted file mode 100644 index 9b712d4..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipleSpecsExample.kt +++ /dev/null @@ -1,102 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.get -import io.github.smiley4.ktorswaggerui.dsl.route -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.auth.Authentication -import io.ktor.server.auth.UserIdPrincipal -import io.ktor.server.auth.basic -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing - -/** - * An example showcasing multiple openapi-specs in a single application - * - localhost:8080/swagger-ui/v1/index.html - * * /v1/hello - * - localhost:8080/swagger-ui/v2/index.html - * * /v2/hello - * * /hi - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - - install(Authentication) { - basic("auth-swagger") { - realm = "Access to the Swagger UI" - validate { credentials -> - if (credentials.name == "user" && credentials.password == "pass") { - UserIdPrincipal(credentials.name) - } else { - null - } - } - } - } - - install(SwaggerUI) { - // general configuration - info { - title = "Example API" - } - specAssigner = { _, _ -> "v2" } // assign all unassigned routes to spec "v2" (here e.g. '/hi') - - // configuration specific for spec "v1" - spec("v1") { - info { - version = "1.0" - } - } - - // configuration specific for spec "v2" - spec("v2") { - info { - version = "2.0" - } - swagger { - authentication = "auth-swagger" - } - } - } - - - routing { - - // version 1.0 routes - route("v1", { - specId = "v1" // assign all sub-routes to spec "v1" - }) { - get("hello", { - description = "Simple version 1 'Hello World'-Route" - }) { - call.respondText("Hello World!") - } - } - - // version 2.0 routes - route("v2", { - specId = "v2" // assign all sub-routes to spec "v2" - }) { - get("hello", { - description = "Simple version 2 'Hello World'-Route" - }) { - call.respondText("Improved Hello World!") - } - } - - // other routes - get("hi", { - description = "Alternative version of 'Hello World'-Route" - }) { - call.respondText("Alternative Hello World!") - } - - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt deleted file mode 100644 index de3068f..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt +++ /dev/null @@ -1,148 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.ktor.server.application.Application -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.delete -import io.github.smiley4.ktorswaggerui.dsl.get -import io.github.smiley4.ktorswaggerui.dsl.post -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respond -import io.ktor.server.routing.routing - - -/** - * Uses the OpenApi-Example "petstore-simple" to demonstrate ktor with swagger-ui - * https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v2.0/json/petstore-simple.json - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - - data class Pet( - val id: Int, - val name: String, - val tag: String - ) - - data class NewPet( - val name: String, - val tag: String - ) - - install(SwaggerUI) { - info { - title = "Swagger Petstore" - description = "A sample API that uses a petstore as an example" - } - } - - routing { - - get("pets", { - description = "Returns all pets from the system that the user has access to." - request { - queryParameter>("tags") { - description = "tags to filter by" - required = false - } - queryParameter("limit", Int::class) { - description = "maximum number of results to return" - required = false - } - } - response { - HttpStatusCode.OK to { - description = "pet response" - body>() { - mediaType(ContentType.Application.Json) - mediaType(ContentType.Application.Xml) - example( - "example", listOf( - Pet(1, "Chloe", "cat"), - Pet(2, "Oliver", "dog") - ) - ) - } - } - } - }) { - // handle request ... - call.respond(HttpStatusCode.NotImplemented, Unit) - } - - post("pets", { - description = "Creates a new pet in the store. Duplicates are allowed." - request { - body { - description = "Pet to add to the store" - required = true - mediaType(ContentType.Application.Json) - mediaType(ContentType.Application.Xml) - example("example", NewPet("Max", "bird")) - } - } - response { - HttpStatusCode.OK to { - description = "pet response" - body> { - mediaType(ContentType.Application.Json) - mediaType(ContentType.Application.Xml) - example("example", Pet(3, "Max", "bird")) - } - } - } - }) { - // handle request ... - call.respond(HttpStatusCode.NotImplemented, Unit) - } - - get("pets/{id}", { - description = "Returns a pet based on a single ID, if the user has access to the pet." - request { - pathParameter("id") { - description = "ID of pet to fetch" - required = true - } - } - response { - HttpStatusCode.OK to { - description = "pet response" - body { - mediaType(ContentType.Application.Json) - mediaType(ContentType.Application.Xml) - example("example", Pet(4, "Bella", "dog")) - } - } - } - }) { - // handle request ... - call.respond(HttpStatusCode.NotImplemented, Unit) - } - - delete("pets/{id}", { - description = "deletes a single pet based on the ID supplied." - request { - pathParameter("id") { - description = "ID of pet to delete" - required = true - } - } - response { - HttpStatusCode.NoContent to { - description = "pet deleted" - } - } - }) { - // handle request ... - call.respond(HttpStatusCode.NotImplemented, Unit) - } - - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ProtectedSwaggerExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ProtectedSwaggerExample.kt deleted file mode 100644 index feccf6e..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ProtectedSwaggerExample.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.get -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.auth.Authentication -import io.ktor.server.auth.UserIdPrincipal -import io.ktor.server.auth.basic -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing - -/** - * An example demonstrating a swagger protected by custom authentication - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - install(Authentication) { - basic("auth-swagger") { - realm = "Access to the Swagger UI" - validate { credentials -> - if (credentials.name == "user" && credentials.password == "pass") { - UserIdPrincipal(credentials.name) - } else { - null - } - } - } - } - - install(SwaggerUI) { - swagger { - // protect Swagger UI and OpenApi-Spec with the authentication method defined above - authentication = "auth-swagger" - } - } - - routing { - get("hello", { - description = "Simple 'Hello World'- Route" - response { - HttpStatusCode.OK to { - description = "Successful Response" - } - } - }) { - call.respondText("Hello World!") - } - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ResourceExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ResourceExample.kt deleted file mode 100644 index e35898a..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/ResourceExample.kt +++ /dev/null @@ -1,132 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import com.fasterxml.jackson.core.util.DefaultIndenter -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter -import com.fasterxml.jackson.databind.SerializationFeature -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.resources.get -import io.github.smiley4.ktorswaggerui.dsl.resources.post -import io.ktor.http.HttpStatusCode -import io.ktor.resources.Resource -import io.ktor.serialization.jackson.jackson -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation -import io.ktor.server.resources.Resources -import io.ktor.server.response.respond -import io.ktor.server.routing.routing - -/** - * Example to showcase usage with the resources plugin - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -@Resource("articles") -class ArticlesRoute(val sorting: String?) - -data class Article( - val title: String, - val content: String -) - -private fun Application.myModule() { - - install(Resources) - install(SwaggerUI) { - swagger { - swaggerUrl = "swagger-ui" - forwardRoot = true - } - info { - title = "Example API" - version = "latest" - description = "Example API for testing and demonstration purposes." - } - externalDocs { - url = "https://github.com/SMILEY4/ktor-swagger-ui/wiki" - description = "Sample external documentation object" - } - server { - url = "http://localhost:8080" - description = "Development Server" - } - tag("articles") { - description = "Routes that return articles" - } - generateTags { url -> listOf(url.firstOrNull()) } - } - install(ContentNegotiation) { - jackson { - configure(SerializationFeature.INDENT_OUTPUT, true) - setDefaultPrettyPrinter(DefaultPrettyPrinter().apply { - indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance) - indentObjectsWith(DefaultIndenter(" ", "\n")) - }) - } - } - - routing { - get({ - tags = listOf("articles") - description = "Articles endpoint" - operationId = "get-articles" - request { - queryParameter("sorting") { - description = "Optional sorting applied to articles" - } - } - response { - default { - description = "Default Response" - } - HttpStatusCode.OK to { - description = "Successful Request" - body { description = "the response" } - } - HttpStatusCode.InternalServerError to { - description = "Something unexpected happened" - } - } - }) { - if (it.sorting != null) - call.respond(HttpStatusCode.OK, "No articles yet (with ${it.sorting} sorting)") - else - call.respond(HttpStatusCode.OK, "No articles yet") - } - - post({ - tags = listOf("articles") - description = "Creates a new article" - operationId = "createArticle" - request { - pathParameter("id") { - description = "The id of the requested article" - } - body
{ - example("First", Article("Ktor openapi resources", "ktor now support openapi for resources!")) { - description = "Create a ktor article" - } - } - } - response { - default { - description = "Default Response" - } - HttpStatusCode.OK to { - description = "Successful Request" - body { description = "the response" } - } - HttpStatusCode.InternalServerError to { - description = "Something unexpected happened" - } - } - }) { - call.respond(HttpStatusCode.OK, "Article not saved ^^") - } - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaAnnotationExample.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaAnnotationExample.kt deleted file mode 100644 index 4f1073a..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaAnnotationExample.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -import com.fasterxml.jackson.core.util.DefaultIndenter -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter -import com.fasterxml.jackson.databind.SerializationFeature -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.get -import io.ktor.http.HttpStatusCode -import io.ktor.serialization.jackson.jackson -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation -import io.ktor.server.response.respond -import io.ktor.server.routing.routing -import io.swagger.v3.oas.annotations.media.Schema - -/** - * An example showing the [Schema]-annotation, adding additional information to models - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - install(SwaggerUI) - install(ContentNegotiation) { - jackson { - configure(SerializationFeature.INDENT_OUTPUT, true) - setDefaultPrettyPrinter(DefaultPrettyPrinter().apply { - indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance) - indentObjectsWith(DefaultIndenter(" ", "\n")) - }) - } - } - routing { - get("somebody", { - response { - HttpStatusCode.OK to { - body() - } - } - }) { - call.respond(Person("Somebody", 42)) - } - } -} - -@Schema(title = "The Schema for a person") -data class Person( - - @field:Schema(description = "the name of the person") - val name: String, - - @field:Schema(description = "the age of the person in years", nullable = true) - val age: Int - -) diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/ApplicationTests.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/ApplicationTests.kt deleted file mode 100644 index 33ed640..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/ApplicationTests.kt +++ /dev/null @@ -1,481 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests - -import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl -import io.github.smiley4.ktorswaggerui.dsl.get -import io.kotest.matchers.shouldBe -import io.kotest.matchers.string.shouldContain -import io.kotest.matchers.string.shouldNotBeEmpty -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.client.statement.bodyAsText -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.http.contentType -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.auth.Authentication -import io.ktor.server.auth.UserIdPrincipal -import io.ktor.server.auth.basic -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing -import io.ktor.server.testing.testApplication -import kotlin.test.Test - -class ApplicationTests { - - @Test - fun minimal() = swaggerUITestApplication { - get("hello").also { - it.status shouldBe HttpStatusCode.OK - it.body shouldBe "Hello Test" - } - get("/").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/index.html").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.JavaScript - it.body shouldContain "url: \"/swagger-ui/api.json\"" - } - get("/swagger-ui/api.json").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.Json - it.body.shouldNotBeEmpty() - } - } - - - @Test - fun customRootHost() = swaggerUITestApplication( - { - swagger { - rootHostPath = "my-root" - } - }, - followRedirects = false, - ) { - get("hello").also { - it.status shouldBe HttpStatusCode.OK - it.body shouldBe "Hello Test" - } - get("/").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("swagger-ui").also { - it.status shouldBe HttpStatusCode.Found - it.redirect shouldBe "my-root/swagger-ui/index.html" - } - get("swagger-ui/index.html").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("swagger-ui/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.JavaScript - it.body shouldContain "url: \"/my-root/swagger-ui/api.json\"" - } - get("swagger-ui/api.json").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.Json - it.body.shouldNotBeEmpty() - } - } - - - @Test - fun forwardRoot() = swaggerUITestApplication({ - swagger { - forwardRoot = true - } - }) { - get("hello").also { - it.status shouldBe HttpStatusCode.OK - it.body shouldBe "Hello Test" - } - get("/").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/index.html").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.JavaScript - it.body shouldContain "url: \"/swagger-ui/api.json\"" - } - get("/swagger-ui/api.json").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.Json - it.body.shouldNotBeEmpty() - } - } - - - @Test - fun forwardRootWithCustomSwaggerUrl() = swaggerUITestApplication({ - swagger { - forwardRoot = true - swaggerUrl = "test-swagger" - } - }) { - get("/").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/test-swagger").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/test-swagger/index.html").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - } - - - @Test - fun protectedSwaggerUI() = swaggerUITestApplication({ - swagger { - authentication = "my-auth" - } - }) { - get("hello").also { - it.status shouldBe HttpStatusCode.OK - it.body shouldBe "Hello Test" - } - get("/").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/swagger-ui/index.html").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/swagger-ui/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/swagger-ui/api.json").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - } - - - @Test - fun forwardRootAndProtectedSwaggerUI() = swaggerUITestApplication({ - swagger { - authentication = "my-auth" - forwardRoot = true - } - }) { - get("hello").also { - it.status shouldBe HttpStatusCode.OK - it.body shouldBe "Hello Test" - } - get("/").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/swagger-ui").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/swagger-ui/index.html").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/swagger-ui/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/swagger-ui/api.json").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - } - - - @Test - fun customSwaggerUrl() = swaggerUITestApplication({ - swagger { - swaggerUrl = "test-swagger" - } - }) { - get("hello").also { - it.status shouldBe HttpStatusCode.OK - it.body shouldBe "Hello Test" - } - get("/").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui/index.html").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui/api.json").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/test-swagger").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/test-swagger/index.html").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/test-swagger/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.JavaScript - it.body shouldContain "url: \"/test-swagger/api.json\"" - - } - get("/test-swagger/api.json").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.Json - it.body.shouldNotBeEmpty() - } - } - - - @Test - fun customSwaggerUrlAndProtected() = swaggerUITestApplication({ - swagger { - authentication = "my-auth" - swaggerUrl = "test-swagger" - } - }) { - get("hello").also { - it.status shouldBe HttpStatusCode.OK - it.body shouldBe "Hello Test" - } - get("/").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui/index.html").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui/api.json").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/test-swagger").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/test-swagger/index.html").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/test-swagger/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/test-swagger/api.json").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - } - - - @Test - fun multipleSwaggerUI() = swaggerUITestApplication({ - specAssigner = { _, tags -> tags.firstOrNull() ?: "other" } - }) { - get("hello").also { - it.status shouldBe HttpStatusCode.OK - it.body shouldBe "Hello Test" - } - get("/").also { - it.status shouldBe HttpStatusCode.NotFound - } - get("/swagger-ui/hello").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/hello/index.html").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/hello/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.JavaScript - it.body shouldContain "url: \"/swagger-ui/hello/hello.json\"" - } - get("/swagger-ui/hello/hello.json").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.Json - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/world").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/world/index.html").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/world/swagger-initializer.js").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.JavaScript - it.body shouldContain "url: \"/swagger-ui/world/world.json\"" - } - get("/swagger-ui/world/world.json").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.Json - it.body.shouldNotBeEmpty() - } - } - - - @Test - fun multipleSwaggerUIWithDifferentAuthConfig() = swaggerUITestApplication({ - specAssigner = { _, tags -> tags.firstOrNull() ?: "other" } - spec("hello") { - swagger { - authentication = null - } - } - spec("world") { - swagger { - authentication = "my-auth" - } - } - }) { - get("/swagger-ui/hello/index.html").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Text.Html - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/hello/hello.json").also { - it.status shouldBe HttpStatusCode.OK - it.contentType shouldBe ContentType.Application.Json - it.body.shouldNotBeEmpty() - } - get("/swagger-ui/world/index.html").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - get("/swagger-ui/world/world.json").also { - it.status shouldBe HttpStatusCode.Unauthorized - } - } - - - private fun swaggerUITestApplication(followRedirects: Boolean = true, block: suspend TestContext.() -> Unit) { - swaggerUITestApplication({}, followRedirects, block) - } - - private fun swaggerUITestApplication( - pluginConfig: PluginConfigDsl.() -> Unit, - followRedirects: Boolean = true, - block: suspend TestContext.() -> Unit - ) { - testApplication { - val client = createClient { - this.followRedirects = followRedirects - } - application { - install(Authentication) { - basic("my-auth") { - validate { credentials -> - if (credentials.name == "user" && credentials.password == "pass") { - UserIdPrincipal(credentials.name) - } else { - null - } - } - } - } - install(SwaggerUI, pluginConfig) - routing { - get("hello", { - tags = listOf("hello") - description = "Simple 'Hello World'- Route" - response { - HttpStatusCode.OK to { - description = "Successful Response" - } - } - }) { - call.respondText("Hello Test") - } - get("world", { - tags = listOf("world") - description = "Another simple 'Hello World'- Route" - response { - HttpStatusCode.OK to { - description = "Successful Response" - } - } - }) { - call.respondText("Hello World") - } - } - Thread.sleep(500) - } - TestContext(client).apply { block() } - } - } - - class TestContext(private val client: HttpClient) { - - suspend fun get(path: String): GetResult { - return client.get(path) - .let { - GetResult( - path = path, - status = it.status, - contentType = it.contentType(), - body = it.bodyAsText(), - redirect = it.headers["Location"] - ) - } - .also { it.print() } - } - - - private fun GetResult.print() { - println("GET ${this.path} => ${this.status} (${this.contentType}): ${this.body}") - } - } - - - data class GetResult( - val path: String, - val status: HttpStatusCode, - val contentType: ContentType?, - val body: String, - val redirect: String? - ) - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/example/ExampleTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/example/ExampleTest.kt deleted file mode 100644 index 7dae2e5..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/example/ExampleTest.kt +++ /dev/null @@ -1,201 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.example - -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRequestParameter -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute -import io.github.smiley4.ktorswaggerui.dsl.OpenApiSimpleBody -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContextBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.ExampleBuilder -import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe -import io.ktor.http.HttpMethod - -class ExampleTest : StringSpec({ - - "no request parameter example" { - val route = route { - request { - queryParameter("param") - } - } - val exampleContext = exampleContext(listOf(route)) - exampleContext.getExample(route.getRequestParameter("param")) shouldBe null - } - - "primitive request parameter examples" { - val route = route { - request { - queryParameter("stringParam") { - example = "Example Value" - } - queryParameter("intParam") { - example = 42 - } - queryParameter("boolParam") { - example = true - } - } - } - val exampleContext = exampleContext(listOf(route)) - exampleContext.getExample(route.getRequestParameter("stringParam")) - .also { it shouldNotBe null } - ?.also { example -> - example shouldBe "Example Value" - } - exampleContext.getExample(route.getRequestParameter("intParam")) - .also { it shouldNotBe null } - ?.also { example -> - example shouldBe "42" - } - exampleContext.getExample(route.getRequestParameter("boolParam")) - .also { it shouldNotBe null } - ?.also { example -> - example shouldBe "true" - } - } - - - "no body example" { - val route = route { - request { - body { - example("differentExample", "Example Value") - } - } - } - val exampleContext = exampleContext(listOf(route)) - exampleContext.getExample(route.getRequestBody(), "testExample") shouldBe null - } - - "simple body example" { - val route = route { - request { - body { - example("testExample", "Example Value") { - summary = "test summary" - description = "test description" - } - } - } - } - val exampleContext = exampleContext(listOf(route)) - exampleContext.getExample(route.getRequestBody(), "testExample") - .also { it shouldNotBe null } - ?.also { example -> - example.value shouldBe "Example Value" - example.description shouldBe "test description" - example.summary shouldBe "test summary" - } - } - - "object body example" { - val route = route { - request { - body { - example("testExample", SimpleObject("someText", 42)) { - summary = "test summary" - description = "test description" - } - } - } - } - val exampleContext = exampleContext(listOf(route)) - exampleContext.getExample(route.getRequestBody(), "testExample") - .also { it shouldNotBe null } - ?.also { example -> - example.value shouldBe "{\"text\":\"someText\",\"number\":42}" - example.description shouldBe "test description" - example.summary shouldBe "test summary" - } - } - - "object inheritance body example" { - val route = route { - request { - body { - example("a", ExampleRequest.A("test a")) { - summary = "a summary" - description = "a description" - } - example("b", ExampleRequest.B(42)) { - summary = "b summary" - description = "b description" - } - } - } - } - val exampleContext = exampleContext(listOf(route)) - exampleContext.getExample(route.getRequestBody(), "a") - .also { it shouldNotBe null } - ?.also { example -> - example.value shouldBe "{\"thisIsA\":\"test a\"}" - example.description shouldBe "a description" - example.summary shouldBe "a summary" - } - exampleContext.getExample(route.getRequestBody(), "b") - .also { it shouldNotBe null } - ?.also { example -> - example.value shouldBe "{\"thisIsB\":42}" - example.description shouldBe "b description" - example.summary shouldBe "b summary" - } - } - -}) { - - companion object { - - private data class SimpleObject( - val text: String, - val number: Int - ) - - private sealed class ExampleRequest { - - data class A( - val thisIsA: String - ) : ExampleRequest() - - - data class B( - val thisIsB: Int - ) : ExampleRequest() - - } - - private val defaultPluginConfig = PluginConfigDsl() - - private fun exampleContext( - routes: List, - pluginConfig: PluginConfigDsl = defaultPluginConfig - ): ExampleContext { - return ExampleContextBuilder( - exampleBuilder = ExampleBuilder( - config = pluginConfig.build(PluginConfigData.DEFAULT) - ) - ).build(routes.toList()) - } - - fun route(block: OpenApiRoute.() -> Unit): RouteMeta { - return RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().apply(block), - protected = false - ) - } - - fun RouteMeta.getRequestParameter(name: String): OpenApiRequestParameter { - return this.documentation.getRequest().getParameters().find { it.name == name }!! - } - - fun RouteMeta.getRequestBody(): OpenApiSimpleBody { - return this.documentation.getRequest().getBody()!! as OpenApiSimpleBody - } - - } -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ExternalDocsBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ExternalDocsBuilderTest.kt deleted file mode 100644 index d49ab31..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ExternalDocsBuilderTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.openapi - -import io.github.smiley4.ktorswaggerui.data.ExternalDocsData -import io.github.smiley4.ktorswaggerui.dsl.OpenApiExternalDocs -import io.github.smiley4.ktorswaggerui.builder.openapi.ExternalDocumentationBuilder -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.shouldBe -import io.swagger.v3.oas.models.ExternalDocumentation - -class ExternalDocsBuilderTest : StringSpec({ - - "default external docs object" { - buildExternalDocsObject {}.also { docs -> - docs.url shouldBe "/" - docs.description shouldBe null - } - } - - "complete server object" { - buildExternalDocsObject { - url = "Test URL" - description = "Test Description" - }.also { docs -> - docs.url shouldBe "Test URL" - docs.description shouldBe "Test Description" - } - } - -}) { - - companion object { - - private fun buildExternalDocsObject(builder: OpenApiExternalDocs.() -> Unit): ExternalDocumentation { - return ExternalDocumentationBuilder().build(OpenApiExternalDocs().apply(builder).build(ExternalDocsData.DEFAULT)) - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/InfoBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/InfoBuilderTest.kt deleted file mode 100644 index 51475ef..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/InfoBuilderTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.openapi - -import io.github.smiley4.ktorswaggerui.data.InfoData -import io.github.smiley4.ktorswaggerui.dsl.OpenApiInfo -import io.github.smiley4.ktorswaggerui.builder.openapi.ContactBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.InfoBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.LicenseBuilder -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.nulls.shouldNotBeNull -import io.kotest.matchers.shouldBe -import io.swagger.v3.oas.models.info.Info - - -class InfoBuilderTest : StringSpec({ - - "empty info object" { - buildInfoObject {}.also { info -> - info.title shouldBe "API" - info.version shouldBe "latest" - info.description shouldBe null - info.termsOfService shouldBe null - info.contact shouldBe null - info.license shouldBe null - info.extensions shouldBe null - info.summary shouldBe null - } - } - - "full info object" { - buildInfoObject { - title = "Test Api" - version = "1.0" - description = "Api for testing" - termsOfService = "test-tos" - contact { - name = "Test Person" - url = "example.com" - email = "test.mail" - - } - license { - name = "Test License" - url = "example.com" - } - }.also { info -> - info.title shouldBe "Test Api" - info.version shouldBe "1.0" - info.description shouldBe "Api for testing" - info.termsOfService shouldBe "test-tos" - info.contact - .also { contact -> contact.shouldNotBeNull() } - ?.also { contact -> - contact.name shouldBe "Test Person" - contact.url shouldBe "example.com" - contact.email shouldBe "test.mail" - } - info.license - .also { license -> license.shouldNotBeNull() } - ?.also { license -> - license.name shouldBe "Test License" - license.url shouldBe "example.com" - } - info.extensions shouldBe null - info.summary shouldBe null - } - } - -}) { - - companion object { - - private fun buildInfoObject(builder: OpenApiInfo.() -> Unit): Info { - return InfoBuilder( - contactBuilder = ContactBuilder(), - licenseBuilder = LicenseBuilder() - ).build(OpenApiInfo().apply(builder).build(InfoData.DEFAULT)) - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OpenApiBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OpenApiBuilderTest.kt deleted file mode 100644 index f682ce8..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OpenApiBuilderTest.kt +++ /dev/null @@ -1,161 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.openapi - -import com.fasterxml.jackson.databind.ObjectMapper -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContextBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.* -import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.TypeOverwrites -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import io.kotest.matchers.collections.shouldHaveSize -import io.kotest.matchers.maps.shouldHaveSize -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe -import io.swagger.v3.oas.models.OpenAPI - - -class OpenApiBuilderTest : StringSpec({ - - "default openapi object" { - buildOpenApiObject(emptyList()).also { openapi -> - openapi.info shouldNotBe null - openapi.extensions shouldBe null - openapi.servers shouldHaveSize 0 - openapi.externalDocs shouldNotBe null - openapi.security shouldBe null - openapi.tags shouldHaveSize 0 - openapi.paths shouldHaveSize 0 - openapi.components shouldNotBe null - openapi.extensions shouldBe null - } - } - - "multiple servers" { - val config = PluginConfigDsl().also { - it.server { - url = "http://localhost:8080" - description = "Development Server" - } - it.server { - url = "https://127.0.0.1" - description = "Production Server" - } - } - buildOpenApiObject(emptyList(), config).also { openapi -> - openapi.servers shouldHaveSize 2 - openapi.servers.map { it.url } shouldContainExactlyInAnyOrder listOf( - "http://localhost:8080", - "https://127.0.0.1" - ) - } - } - - "multiple tags" { - val config = PluginConfigDsl().also { - it.tag("tag-1") { - description = "first test tag" - } - it.tag("tag-2") { - description = "second test tag" - } - } - buildOpenApiObject(emptyList(), config).also { openapi -> - openapi.tags shouldHaveSize 2 - openapi.tags.map { it.name } shouldContainExactlyInAnyOrder listOf( - "tag-1", - "tag-2" - ) - } - } - -}) { - - companion object { - - private val defaultPluginConfig = PluginConfigDsl() - - private fun schemaContext(routes: List, pluginConfig: PluginConfigDsl): SchemaContext { - val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) - return SchemaContextBuilder( - config =pluginConfigData, - schemaBuilder = SchemaBuilder( - definitionsField = pluginConfigData.encoding.schemaDefsField, - schemaEncoder = pluginConfigData.encoding.schemaEncoder, - ObjectMapper(), - TypeOverwrites.get() - ) - ).build(routes) - } - - private fun exampleContext(routes: List, pluginConfig: PluginConfigDsl): ExampleContext { - return ExampleContextBuilder( - exampleBuilder = ExampleBuilder( - config = pluginConfig.build(PluginConfigData.DEFAULT) - ) - ).build(routes.toList()) - } - - private fun buildOpenApiObject(routes: List, pluginConfig: PluginConfigDsl = defaultPluginConfig): OpenAPI { - val schemaContext = schemaContext(routes, pluginConfig) - val exampleContext = exampleContext(routes, pluginConfig) - val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) - return OpenApiBuilder( - config = pluginConfigData, - schemaContext = schemaContext, - exampleContext = exampleContext, - infoBuilder = InfoBuilder( - contactBuilder = ContactBuilder(), - licenseBuilder = LicenseBuilder() - ), - externalDocumentationBuilder = ExternalDocumentationBuilder(), - serverBuilder = ServerBuilder(), - tagBuilder = TagBuilder( - tagExternalDocumentationBuilder = TagExternalDocumentationBuilder() - ), - pathsBuilder = PathsBuilder( - pathBuilder = PathBuilder( - operationBuilder = OperationBuilder( - operationTagsBuilder = OperationTagsBuilder(pluginConfigData), - parameterBuilder = ParameterBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext - ), - requestBodyBuilder = RequestBodyBuilder( - contentBuilder = ContentBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext, - headerBuilder = HeaderBuilder(schemaContext) - ) - ), - responsesBuilder = ResponsesBuilder( - responseBuilder = ResponseBuilder( - headerBuilder = HeaderBuilder(schemaContext), - contentBuilder = ContentBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext, - headerBuilder = HeaderBuilder(schemaContext) - ) - ), - config = pluginConfigData - ), - securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), - ) - ) - ), - componentsBuilder = ComponentsBuilder( - config = pluginConfigData, - securitySchemesBuilder = SecuritySchemesBuilder( - oAuthFlowsBuilder = OAuthFlowsBuilder() - ) - ) - ).build(routes) - } - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OperationBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OperationBuilderTest.kt deleted file mode 100644 index 39184ed..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/OperationBuilderTest.kt +++ /dev/null @@ -1,999 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.openapi - -import com.fasterxml.jackson.databind.ObjectMapper -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContextBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.ContentBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.ExampleBuilder -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.route.RouteMeta -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.TypeOverwrites -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor.Companion.custom -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.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.swagger.v3.oas.models.Operation -import io.swagger.v3.oas.models.media.Schema -import java.io.File - -class OperationBuilderTest : StringSpec({ - - "empty operation" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute(), - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.tags.shouldBeEmpty() - operation.summary shouldBe null - operation.description shouldBe null - operation.externalDocs shouldBe null - operation.operationId shouldBe null - operation.parameters.shouldBeEmpty() - operation.requestBody shouldBe null - operation.responses.shouldBeEmpty() - operation.deprecated shouldBe false - operation.security shouldBe null - operation.servers shouldBe null - operation.extensions shouldBe null - } - } - - "basic operation" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.tags = listOf("tag1", "tag2") - route.description = "route for testing" - route.summary = "this is some test route" - route.operationId = "testRoute" - route.deprecated = true - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.tags shouldContainExactlyInAnyOrder listOf("tag1", "tag2") - operation.summary shouldBe "this is some test route" - operation.description shouldBe "route for testing" - operation.externalDocs shouldBe null - operation.operationId shouldBe "testRoute" - operation.parameters.shouldBeEmpty() - operation.requestBody shouldBe null - operation.responses.shouldBeEmpty() - operation.deprecated shouldBe true - operation.security shouldBe null - operation.servers shouldBe null - operation.extensions shouldBe null - } - } - - "operation with auto-generated tags" { - val config = PluginConfigDsl().also { - it.generateTags { url -> listOf(url.firstOrNull()) } - } - val routeA = RouteMeta( - path = "a/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.tags = listOf("defaultTag") - }, - protected = false - ) - val routeB = RouteMeta( - path = "b/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.tags = listOf("defaultTag") - }, - protected = false - ) - val schemaContext = schemaContext(listOf(routeA, routeB), config) - val exampleContext = exampleContext(listOf(routeA, routeB), config) - buildOperationObject(routeA, schemaContext, exampleContext, config).also { operation -> - operation.tags shouldContainExactlyInAnyOrder listOf("a", "defaultTag") - } - buildOperationObject(routeB, schemaContext, exampleContext, config).also { operation -> - operation.tags shouldContainExactlyInAnyOrder listOf("b", "defaultTag") - } - } - - "protected route with security-scheme-names" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.securitySchemeNames = listOf("security1", "security2") - }, - protected = true - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.security - .also { it.shouldNotBeNull() } - ?.also { security -> - security shouldHaveSize 2 - security.find { it.containsKey("security1") }.shouldNotBeNull() - security.find { it.containsKey("security2") }.shouldNotBeNull() - } - } - } - - "protected route without security-scheme-names" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute(), - protected = true - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.tags.shouldBeEmpty() - operation.summary shouldBe null - operation.description shouldBe null - operation.externalDocs shouldBe null - operation.operationId shouldBe null - operation.parameters.shouldBeEmpty() - operation.requestBody shouldBe null - operation.responses.shouldBeEmpty() - operation.deprecated shouldBe false - operation.security shouldBe null - operation.servers shouldBe null - operation.extensions shouldBe null - } - } - - "unprotected route with security-scheme-names" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.securitySchemeNames = listOf("security1", "security2") - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.tags.shouldBeEmpty() - operation.summary shouldBe null - operation.description shouldBe null - operation.externalDocs shouldBe null - operation.operationId shouldBe null - operation.parameters.shouldBeEmpty() - operation.requestBody shouldBe null - operation.responses.shouldBeEmpty() - operation.deprecated shouldBe false - operation.security shouldBe null - operation.servers shouldBe null - operation.extensions shouldBe null - } - } - - "route with basic request" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.request { - queryParameter("queryParam") - pathParameter("pathParam") - headerParameter("headerParam") - body>() - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.tags.shouldBeEmpty() - operation.summary shouldBe null - operation.description shouldBe null - operation.externalDocs shouldBe null - operation.operationId shouldBe null - operation.parameters.also { parameters -> - parameters shouldHaveSize 3 - parameters.find { it.name == "queryParam" } - .also { it.shouldNotBeNull() } - ?.also { param -> - param.`in` shouldBe "query" - param.description shouldBe null - param.required shouldBe null - param.deprecated shouldBe null - param.allowEmptyValue shouldBe null - param.`$ref` shouldBe null - param.style shouldBe null - param.explode shouldBe null - param.allowReserved shouldBe null - param.schema - .also { it.shouldNotBeNull() } - ?.also { it.type = "string" } - param.example shouldBe null - param.examples shouldBe null - param.content shouldBe null - param.extensions shouldBe null - } - parameters.find { it.name == "pathParam" } - .also { it.shouldNotBeNull() } - ?.also { param -> - param.`in` shouldBe "path" - param.description shouldBe null - param.required shouldBe null - param.deprecated shouldBe null - param.allowEmptyValue shouldBe null - param.`$ref` shouldBe null - param.style shouldBe null - param.explode shouldBe null - param.allowReserved shouldBe null - param.schema - .also { it.shouldNotBeNull() } - ?.also { it.type = "integer" } - param.example shouldBe null - param.examples shouldBe null - param.content shouldBe null - param.extensions shouldBe null - } - parameters.find { it.name == "headerParam" } - .also { it.shouldNotBeNull() } - ?.also { param -> - param.`in` shouldBe "header" - param.description shouldBe null - param.required shouldBe null - param.deprecated shouldBe null - param.allowEmptyValue shouldBe null - param.`$ref` shouldBe null - param.style shouldBe null - param.explode shouldBe null - param.allowReserved shouldBe null - param.schema - .also { it.shouldNotBeNull() } - ?.also { it.type = "boolean" } - param.example shouldBe null - param.examples shouldBe null - param.content shouldBe null - param.extensions shouldBe null - } - } - operation.requestBody - .also { it.shouldNotBeNull() } - ?.also { body -> - body.description shouldBe null - body.content - .also { it.shouldNotBeNull() } - ?.also { content -> - content shouldHaveSize 1 - content.get("application/json") - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema - .also { it.shouldNotBeNull() } - ?.also { schema -> - schema.type shouldBe "array" - schema.items.also { item -> item.type shouldBe "string" } - } - mediaType.example shouldBe null - mediaType.examples shouldBe null - mediaType.encoding shouldBe null - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - - } - body.required shouldBe null - body.extensions shouldBe null - body.`$ref` shouldBe null - } - operation.responses.shouldBeEmpty() - operation.deprecated shouldBe false - operation.security shouldBe null - operation.servers shouldBe null - operation.extensions shouldBe null - } - } - - "route with basic response" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.response { - "test" to { - description = "Test Response" - header("test-header") - body>() - } - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.tags.shouldBeEmpty() - operation.summary shouldBe null - operation.description shouldBe null - operation.externalDocs shouldBe null - operation.operationId shouldBe null - operation.parameters.shouldBeEmpty() - operation.requestBody shouldBe null - operation.responses - .also { it shouldHaveSize 1 } - .let { it.get("test") } - .also { it.shouldNotBeNull() } - ?.also { response -> - response.description shouldBe "Test Response" - response.headers - .also { it.shouldNotBeNull() } - .let { it["test-header"] } - .also { it.shouldNotBeNull() } - ?.also { header -> - header.schema - .also { it.shouldNotBeNull() } - ?.also { it.type shouldBe "string" } - } - - response.content - .also { it.shouldNotBeNull() } - .let { it.get("application/json") } - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema - .also { it.shouldNotBeNull() } - ?.also { schema -> - schema.type shouldBe "array" - schema.items.also { item -> item.type shouldBe "string" } - } - mediaType.example shouldBe null - mediaType.examples shouldBe null - mediaType.encoding shouldBe null - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - response.links shouldBe null - response.extensions shouldBe null - response.`$ref` shouldBe null - } - operation.deprecated shouldBe false - operation.security shouldBe null - operation.servers shouldBe null - operation.extensions shouldBe null - } - } - - "documented parameter" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.request { - queryParameter("param") { - description = "test parameter" - example = "MyExample" - required = true - deprecated = true - allowEmptyValue = true - explode = true - allowReserved = true - } - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.parameters.also { parameters -> - parameters shouldHaveSize 1 - parameters[0].also { param -> - param.`in` shouldBe "query" - param.description shouldBe "test parameter" - param.required shouldBe true - param.deprecated shouldBe true - param.allowEmptyValue shouldBe true - param.`$ref` shouldBe null - param.style shouldBe null - param.explode shouldBe true - param.allowReserved shouldBe true - param.schema - .also { it.shouldNotBeNull() } - ?.also { it.type = "string" } - param.example shouldBe "MyExample" - param.examples shouldBe null - param.content shouldBe null - param.extensions shouldBe null - } - } - } - } - - "documented body" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.request { - body { - description = "the test body" - required = true - mediaType(ContentType.Application.Json) - mediaType(ContentType.Application.Xml) - example("example 1", "MyExample1") { - summary = "the example 1" - description = "the first example" - } - } - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.requestBody - .also { it.shouldNotBeNull() } - ?.also { body -> - body.description shouldBe "the test body" - body.content - .also { it.shouldNotBeNull() } - ?.also { content -> - content shouldHaveSize 2 - content["application/json"] - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema - .also { it.shouldNotBeNull() } - ?.also { it.type shouldBe "string" } - mediaType.example shouldBe null - mediaType.examples - .also { it shouldHaveSize 1 } - .let { it["example 1"] } - .also { it.shouldNotBeNull() } - ?.also { example -> - example.summary shouldBe "the example 1" - example.description shouldBe "the first example" - example.value shouldBe "MyExample1" - example.externalValue shouldBe null - example.`$ref` shouldBe null - example.extensions shouldBe null - example.valueSetFlag shouldBe true - } - mediaType.encoding shouldBe null - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - content["application/xml"] - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema - .also { it.shouldNotBeNull() } - ?.also { it.type shouldBe "string" } - mediaType.example shouldBe null - mediaType.examples - .also { it shouldHaveSize 1 } - .let { it["example 1"] } - .also { it.shouldNotBeNull() } - ?.also { example -> - example.summary shouldBe "the example 1" - example.description shouldBe "the first example" - example.value shouldBe "MyExample1" - example.externalValue shouldBe null - example.`$ref` shouldBe null - example.extensions shouldBe null - example.valueSetFlag shouldBe true - } - mediaType.encoding shouldBe null - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - } - body.required shouldBe true - body.extensions shouldBe null - body.`$ref` shouldBe null - } - } - } - - "multipart body" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.request { - multipartBody { - mediaType(ContentType.MultiPart.FormData) - part("image") { - mediaTypes = setOf( - ContentType.Image.PNG, - ContentType.Image.JPEG, - ContentType.Image.GIF - ) - } - part("data") { - mediaTypes = setOf(ContentType.Text.Plain) - } - } - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.requestBody - .also { it.shouldNotBeNull() } - ?.also { body -> - body.content - .also { it.shouldNotBeNull() } - ?.also { content -> - content shouldHaveSize 1 - content.get("multipart/form-data") - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema - .also { it.shouldNotBeNull() } - ?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf( - "image", - "data" - ) - - } - mediaType.example shouldBe null - mediaType.examples shouldBe null - mediaType.encoding - .also { it shouldHaveSize 2 } - .also { it.keys shouldContainExactlyInAnyOrder listOf("image", "data") } - .also { encoding -> - encoding.get("image")?.also { image -> - image.contentType shouldBe "image/png, image/jpeg, image/gif" - image.headers shouldHaveSize 0 - image.style shouldBe null - image.explode shouldBe null - image.allowReserved shouldBe null - image.extensions shouldBe null - } - encoding.get("data")?.also { data -> - data.contentType shouldBe "text/plain" - data.headers shouldHaveSize 0 - data.style shouldBe null - data.explode shouldBe null - data.allowReserved shouldBe null - data.extensions shouldBe null - } - } - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - } - } - } - } - - "multipart body without parts" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.request { - multipartBody { - mediaType(ContentType.MultiPart.FormData) - } - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.requestBody - .also { it.shouldNotBeNull() } - ?.also { body -> - body.content - .also { it.shouldNotBeNull() } - ?.also { content -> - content shouldHaveSize 1 - content.get("multipart/form-data") - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema.shouldNotBeNull() - mediaType.example shouldBe null - mediaType.examples shouldBe null - mediaType.encoding shouldBe null - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - } - } - } - } - - "multiple responses" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.response { - default { - description = "Default Response" - } - HttpStatusCode.OK to { - description = "Successful Request" - } - HttpStatusCode.InternalServerError to { - description = "Failed Request" - } - "Custom" to { - description = "Custom Response" - } - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.responses - .also { it shouldHaveSize 4 } - ?.also { responses -> - responses.get("default") - .also { it.shouldNotBeNull() } - ?.also { it.description shouldBe "Default Response" } - responses.get("200") - .also { it.shouldNotBeNull() } - ?.also { it.description shouldBe "Successful Request" } - responses.get("500") - .also { it.shouldNotBeNull() } - ?.also { it.description shouldBe "Failed Request" } - responses.get("Custom") - .also { it.shouldNotBeNull() } - ?.also { it.description shouldBe "Custom Response" } - } - } - } - - "automatic unauthorized response for protected route" { - val config = PluginConfigDsl().also { - it.defaultUnauthorizedResponse { - description = "Default unauthorized Response" - } - } - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.response { - default { - description = "Default Response" - } - } - }, - 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.get("401") - .also { it.shouldNotBeNull() } - ?.also { it.description shouldBe "Default unauthorized Response" } - responses.get("default") - .also { it.shouldNotBeNull() } - ?.also { it.description shouldBe "Default Response" } - } - } - } - - "automatic unauthorized response for unprotected route" { - val config = PluginConfigDsl().also { - it.defaultUnauthorizedResponse { - description = "Default unauthorized Response" - } - } - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.response { - default { - description = "Default Response" - } - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route), config) - val exampleContext = exampleContext(listOf(route), config) - buildOperationObject(route, schemaContext, exampleContext, config).also { operation -> - operation.responses - .also { it shouldHaveSize 1 } - ?.also { responses -> - responses.get("default") - .also { it.shouldNotBeNull() } - ?.also { it.description shouldBe "Default Response" } - } - } - } - - "complex body datatype" { - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.request { - body>() - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route)) - val exampleContext = exampleContext(listOf(route)) - buildOperationObject(route, schemaContext, exampleContext).also { operation -> - operation.requestBody - .also { it.shouldNotBeNull() } - ?.also { body -> - body.description shouldBe null - body.content - .also { it.shouldNotBeNull() } - ?.also { content -> - content shouldHaveSize 1 - content["application/json"] - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema - .also { it.shouldNotBeNull() } - ?.also { schema -> - schema.type shouldBe "array" - schema.items.also { item -> - item.type shouldBe null - item.`$ref` shouldBe "#/components/schemas/SimpleObject" - } - } - mediaType.example shouldBe null - mediaType.examples shouldBe null - mediaType.encoding shouldBe null - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - - } - body.required shouldBe null - body.extensions shouldBe null - body.`$ref` shouldBe null - } - } - schemaContext.getComponentsSection().also { section -> - section.keys shouldContainExactlyInAnyOrder listOf("SimpleObject") - section["SimpleObject"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("number", "text") - } - } - } - - "custom body schema" { - val config = PluginConfigDsl().also { - it.customSchemas { - openApi("myCustomSchema") { - Schema().also { schema -> - schema.type = "object" - schema.properties = mapOf( - "custom" to Schema().also { prop -> - prop.type = "string" - } - ) - } - } - } - } - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.request { - body(custom("myCustomSchema")) - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route), config) - val exampleContext = exampleContext(listOf(route), config) - buildOperationObject(route, schemaContext, exampleContext, config).also { operation -> - operation.requestBody - .also { it.shouldNotBeNull() } - ?.also { body -> - body.description shouldBe null - body.content - .also { it.shouldNotBeNull() } - ?.also { content -> - content shouldHaveSize 1 - content["application/json"] - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema - .also { it.shouldNotBeNull() } - ?.also { schema -> schema.`$ref` shouldBe "#/components/schemas/myCustomSchema" } - mediaType.example shouldBe null - mediaType.examples shouldBe null - mediaType.encoding shouldBe null - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - - } - body.required shouldBe null - body.extensions shouldBe null - body.`$ref` shouldBe null - } - } - } - - "custom multipart-body schema" { - val config = PluginConfigDsl().also { - it.customSchemas { - openApi("myCustomSchema") { - Schema().also { schema -> - schema.type = "object" - schema.properties = mapOf( - "custom" to Schema().also { prop -> - prop.type = "string" - } - ) - } - } - } - } - val route = RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().also { route -> - route.request { - multipartBody { - mediaType(ContentType.MultiPart.FormData) - part("customData", custom("myCustomSchema")) - } - } - }, - protected = false - ) - val schemaContext = schemaContext(listOf(route), config) - val exampleContext = exampleContext(listOf(route), config) - buildOperationObject(route, schemaContext, exampleContext, config).also { operation -> - operation.requestBody - .also { it.shouldNotBeNull() } - ?.also { body -> - body.content - .also { it.shouldNotBeNull() } - ?.also { content -> - content shouldHaveSize 1 - content["multipart/form-data"] - .also { it.shouldNotBeNull() } - ?.also { mediaType -> - mediaType.schema - .also { it.shouldNotBeNull() } - ?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("customData") - schema.properties["customData"]!!.`$ref` shouldBe "#/components/schemas/myCustomSchema" - } - mediaType.example shouldBe null - mediaType.examples shouldBe null - mediaType.encoding shouldBe null - mediaType.extensions shouldBe null - mediaType.exampleSetFlag shouldBe false - } - } - } - } - } - -}) { - - companion object { - - private data class SimpleObject( - val text: String, - val number: Int - ) - - private val defaultPluginConfig = PluginConfigDsl() - - private fun schemaContext( - routes: List, - pluginConfig: PluginConfigDsl = defaultPluginConfig - ): SchemaContext { - val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) - return SchemaContextBuilder( - config = pluginConfigData, - schemaBuilder = SchemaBuilder( - definitionsField = pluginConfigData.encoding.schemaDefsField, - schemaEncoder = pluginConfigData.encoding.schemaEncoder, - ObjectMapper(), - TypeOverwrites.get() - ) - ).build(routes) - } - - private fun exampleContext( - routes: List, - pluginConfig: PluginConfigDsl = defaultPluginConfig - ): ExampleContext { - return ExampleContextBuilder( - exampleBuilder = ExampleBuilder( - config = pluginConfig.build(PluginConfigData.DEFAULT) - ) - ).build(routes.toList()) - } - - - private fun buildOperationObject( - route: RouteMeta, - schemaContext: SchemaContext, - exampleContext: ExampleContext, - pluginConfig: PluginConfigDsl = defaultPluginConfig - ): Operation { - val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) - return OperationBuilder( - operationTagsBuilder = OperationTagsBuilder(pluginConfigData), - parameterBuilder = ParameterBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext - ), - requestBodyBuilder = RequestBodyBuilder( - contentBuilder = ContentBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext, - headerBuilder = HeaderBuilder(schemaContext) - ) - ), - responsesBuilder = ResponsesBuilder( - responseBuilder = ResponseBuilder( - headerBuilder = HeaderBuilder(schemaContext), - contentBuilder = ContentBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext, - headerBuilder = HeaderBuilder(schemaContext) - ) - ), - config = pluginConfigData - ), - securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), - ).build(route) - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/PathsBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/PathsBuilderTest.kt deleted file mode 100644 index 29f69a4..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/PathsBuilderTest.kt +++ /dev/null @@ -1,156 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.openapi - -import com.fasterxml.jackson.databind.ObjectMapper -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext -import io.github.smiley4.ktorswaggerui.builder.example.ExampleContextBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.ContentBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.ExampleBuilder -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.PathBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.PathsBuilder -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.route.RouteMeta -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.TypeOverwrites -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import io.kotest.matchers.maps.shouldHaveSize -import io.kotest.matchers.nulls.shouldNotBeNull -import io.ktor.http.HttpMethod -import io.swagger.v3.oas.models.Paths - -class PathsBuilderTest : StringSpec({ - - "simple paths" { - val routes = listOf( - route(HttpMethod.Get, "/"), - route(HttpMethod.Delete, "/test/path"), - route(HttpMethod.Post, "/other/test/route") - ) - val schemaContext = schemaContext(routes) - val exampleContext = exampleContext(routes) - buildPathsObject(routes, schemaContext, exampleContext).also { paths -> - paths shouldHaveSize 3 - paths.keys shouldContainExactlyInAnyOrder listOf( - "/", - "/test/path", - "/other/test/route" - ) - paths["/"]!!.get.shouldNotBeNull() - paths["/test/path"]!!.delete.shouldNotBeNull() - paths["/other/test/route"]!!.post.shouldNotBeNull() - } - } - "merge paths" { - val config = defaultPluginConfig.also { - it.swagger { - swaggerUrl = "swagger-ui" - forwardRoot = true - } - } - 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( - "/different/path", - "/test/path" - ) - paths["/different/path"]!!.get.shouldNotBeNull() - paths["/test/path"]!!.get.shouldNotBeNull() - paths["/test/path"]!!.post.shouldNotBeNull() - } - } - -}) { - - companion object { - - private fun route(method: HttpMethod, url: String) = RouteMeta( - path = url, - method = method, - documentation = OpenApiRoute(), - protected = false - ) - - private val defaultPluginConfig = PluginConfigDsl() - - private fun schemaContext(routes: List, pluginConfig: PluginConfigDsl = defaultPluginConfig): SchemaContext { - val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) - return SchemaContextBuilder( - config = pluginConfigData, - schemaBuilder = SchemaBuilder( - definitionsField = pluginConfigData.encoding.schemaDefsField, - schemaEncoder = pluginConfigData.encoding.schemaEncoder, - ObjectMapper(), - TypeOverwrites.get() - ) - ).build(routes) - } - - private fun exampleContext(routes: List, pluginConfig: PluginConfigDsl = defaultPluginConfig): ExampleContext { - return ExampleContextBuilder( - exampleBuilder = ExampleBuilder( - config = pluginConfig.build(PluginConfigData.DEFAULT) - ) - ).build(routes.toList()) - } - - private fun buildPathsObject( - routes: Collection, - schemaContext: SchemaContext, - exampleContext: ExampleContext, - pluginConfig: PluginConfigDsl = defaultPluginConfig - ): Paths { - val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) - return PathsBuilder( - pathBuilder = PathBuilder( - operationBuilder = OperationBuilder( - operationTagsBuilder = OperationTagsBuilder(pluginConfigData), - parameterBuilder = ParameterBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext - ), - requestBodyBuilder = RequestBodyBuilder( - contentBuilder = ContentBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext, - headerBuilder = HeaderBuilder(schemaContext) - ) - ), - responsesBuilder = ResponsesBuilder( - responseBuilder = ResponseBuilder( - headerBuilder = HeaderBuilder(schemaContext), - contentBuilder = ContentBuilder( - schemaContext = schemaContext, - exampleContext = exampleContext, - headerBuilder = HeaderBuilder(schemaContext) - ) - ), - config = pluginConfigData - ), - securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), - ) - ) - ).build(routes) - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/SecuritySchemesBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/SecuritySchemesBuilderTest.kt deleted file mode 100644 index 14a47ef..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/SecuritySchemesBuilderTest.kt +++ /dev/null @@ -1,210 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.openapi - -import io.github.smiley4.ktorswaggerui.data.AuthKeyLocation -import io.github.smiley4.ktorswaggerui.data.AuthScheme -import io.github.smiley4.ktorswaggerui.data.AuthType -import io.github.smiley4.ktorswaggerui.data.SecuritySchemeData -import io.github.smiley4.ktorswaggerui.dsl.OpenApiSecurityScheme -import io.github.smiley4.ktorswaggerui.builder.openapi.OAuthFlowsBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.SecuritySchemesBuilder -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import io.kotest.matchers.maps.shouldBeEmpty -import io.kotest.matchers.nulls.shouldNotBeNull -import io.kotest.matchers.shouldBe -import io.swagger.v3.oas.models.security.SecurityScheme - -class SecuritySchemesBuilderTest : StringSpec({ - - "test empty" { - buildSecuritySchemeObjects(mapOf()).also { schemes -> - schemes.shouldBeEmpty() - } - } - - "test default security scheme object" { - buildSecuritySchemeObjects(mapOf("TestAuth" to {})).also { schemes -> - schemes.keys shouldContainExactlyInAnyOrder listOf("TestAuth") - schemes["TestAuth"]!!.also { scheme -> - scheme.type shouldBe null - scheme.description shouldBe null - scheme.name shouldBe null - scheme.`$ref` shouldBe null - scheme.`in` shouldBe null - scheme.scheme shouldBe null - scheme.bearerFormat shouldBe null - scheme.flows shouldBe null - scheme.openIdConnectUrl shouldBe null - scheme.extensions shouldBe null - } - } - } - - "test basic security scheme objects" { - buildSecuritySchemeObjects(mapOf( - "TestAuth1" to { - type = AuthType.HTTP - scheme = AuthScheme.BASIC - }, - "TestAuth2" to { - type = AuthType.API_KEY - location = AuthKeyLocation.COOKIE - }, - "TestAuth3" to { - type = AuthType.API_KEY - location = AuthKeyLocation.QUERY - name = "myQueryParam" - } - )).also { schemes -> - schemes.keys shouldContainExactlyInAnyOrder listOf("TestAuth1", "TestAuth2", "TestAuth3") - schemes["TestAuth1"]!!.also { scheme -> - scheme.name shouldBe null - scheme.type shouldBe SecurityScheme.Type.HTTP - scheme.scheme shouldBe "Basic" - } - schemes["TestAuth2"]!!.also { scheme -> - scheme.name shouldBe "TestAuth2" - scheme.type shouldBe SecurityScheme.Type.APIKEY - scheme.scheme shouldBe null - } - schemes["TestAuth3"]!!.also { scheme -> - scheme.name shouldBe "myQueryParam" - scheme.type shouldBe SecurityScheme.Type.APIKEY - scheme.scheme shouldBe null - } - } - } - - "test complete security scheme object" { - buildSecuritySchemeObjects(mapOf("TestAuth" to { - type = AuthType.HTTP - location = AuthKeyLocation.COOKIE - scheme = AuthScheme.BASIC - bearerFormat = "test" - openIdConnectUrl = "Test IOD-Connect URL" - description = "Test Description" - flows { - implicit { - authorizationUrl = "Implicit Auth Url" - tokenUrl = "Implicity Token Url" - refreshUrl = "Implicity Token Url" - scopes = mapOf( - "implicit1" to "scope1", - "implicit2" to "scope2" - ) - } - password { - authorizationUrl = "Password Auth Url" - tokenUrl = "Password Token Url" - refreshUrl = "Password Token Url" - scopes = mapOf( - "password1" to "scope1", - "password2" to "scope2" - ) - } - clientCredentials { - authorizationUrl = "ClientCredentials Auth Url" - tokenUrl = "ClientCredentials Token Url" - refreshUrl = "ClientCredentials Token Url" - scopes = mapOf( - "clientCredentials1" to "scope1", - "clientCredentials2" to "scope2" - ) - } - authorizationCode { - authorizationUrl = "AuthorizationCode Auth Url" - tokenUrl = "AuthorizationCode Token Url" - refreshUrl = "AuthorizationCode Token Url" - scopes = mapOf( - "authorizationCode1" to "scope1", - "authorizationCode2" to "scope2" - ) - } - } - })).also { schemes -> - schemes.keys shouldContainExactlyInAnyOrder listOf("TestAuth") - schemes["TestAuth"]!!.also { scheme -> - scheme.name shouldBe null - scheme.type shouldBe SecurityScheme.Type.HTTP - scheme.`in` shouldBe SecurityScheme.In.COOKIE - scheme.scheme shouldBe "Basic" - scheme.bearerFormat shouldBe "test" - scheme.openIdConnectUrl shouldBe "Test IOD-Connect URL" - scheme.description shouldBe "Test Description" - scheme.flows - .also { it.shouldNotBeNull() } - ?.also { flows -> - flows.implicit - .also { it.shouldNotBeNull() } - ?.also { implicit -> - implicit.authorizationUrl shouldBe "Implicit Auth Url" - implicit.tokenUrl shouldBe "Implicity Token Url" - implicit.refreshUrl shouldBe "Implicity Token Url" - implicit.scopes - .also { it.shouldNotBeNull() } - ?.also { scopes -> - scopes.keys shouldContainExactlyInAnyOrder listOf("implicit1", "implicit2") - scopes["implicit1"] shouldBe "scope1" - scopes["implicit2"] shouldBe "scope2" - } - } - flows.password - .also { it.shouldNotBeNull() } - ?.also { password -> - password.authorizationUrl shouldBe "Password Auth Url" - password.tokenUrl shouldBe "Password Token Url" - password.refreshUrl shouldBe "Password Token Url" - password.scopes - .also { it.shouldNotBeNull() } - ?.also { scopes -> - scopes.keys shouldContainExactlyInAnyOrder listOf("password1", "password2") - scopes["password1"] shouldBe "scope1" - scopes["password2"] shouldBe "scope2" - } - } - flows.clientCredentials - .also { it.shouldNotBeNull() } - ?.also { clientCredentials -> - clientCredentials.authorizationUrl shouldBe "ClientCredentials Auth Url" - clientCredentials.tokenUrl shouldBe "ClientCredentials Token Url" - clientCredentials.refreshUrl shouldBe "ClientCredentials Token Url" - clientCredentials.scopes - .also { it.shouldNotBeNull() } - ?.also { scopes -> - scopes.keys shouldContainExactlyInAnyOrder listOf("clientCredentials1", "clientCredentials2") - scopes["clientCredentials1"] shouldBe "scope1" - scopes["clientCredentials2"] shouldBe "scope2" - } - } - flows.authorizationCode - .also { it.shouldNotBeNull() } - ?.also { authorizationCode -> - authorizationCode.authorizationUrl shouldBe "AuthorizationCode Auth Url" - authorizationCode.tokenUrl shouldBe "AuthorizationCode Token Url" - authorizationCode.refreshUrl shouldBe "AuthorizationCode Token Url" - authorizationCode.scopes - .also { it.shouldNotBeNull() } - ?.also { scopes -> - scopes.keys shouldContainExactlyInAnyOrder listOf("authorizationCode1", "authorizationCode2") - scopes["authorizationCode1"] shouldBe "scope1" - scopes["authorizationCode2"] shouldBe "scope2" - } - } - } - } - } - } - -}) { - - companion object { - - private fun buildSecuritySchemeObjects(builders: Map Unit>): Map { - return SecuritySchemesBuilder( - oAuthFlowsBuilder = OAuthFlowsBuilder() - ).build(builders.map { (name, entry) -> OpenApiSecurityScheme(name).apply(entry).build(SecuritySchemeData.DEFAULT) }) - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ServersBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ServersBuilderTest.kt deleted file mode 100644 index 44192f6..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/ServersBuilderTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.openapi - -import io.github.smiley4.ktorswaggerui.data.ServerData -import io.github.smiley4.ktorswaggerui.dsl.OpenApiServer -import io.github.smiley4.ktorswaggerui.builder.openapi.ServerBuilder -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.shouldBe -import io.swagger.v3.oas.models.servers.Server - -class ServersBuilderTest : StringSpec({ - - "default server object" { - buildServerObject {}.also { server -> - server.url shouldBe "/" - server.description shouldBe null - server.variables shouldBe null - server.extensions shouldBe null - - } - } - - "complete server object" { - buildServerObject { - url = "Test URL" - description = "Test Description" - }.also { server -> - server.url shouldBe "Test URL" - server.description shouldBe "Test Description" - server.variables shouldBe null - server.extensions shouldBe null - } - } - -}) { - - companion object { - - private fun buildServerObject(builder: OpenApiServer.() -> Unit): Server { - return ServerBuilder().build(OpenApiServer().apply(builder).build(ServerData.DEFAULT)) - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/TagsBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/TagsBuilderTest.kt deleted file mode 100644 index 54123f8..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/openapi/TagsBuilderTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.openapi - -import io.github.smiley4.ktorswaggerui.data.TagData -import io.github.smiley4.ktorswaggerui.dsl.OpenApiTag -import io.github.smiley4.ktorswaggerui.builder.openapi.TagBuilder -import io.github.smiley4.ktorswaggerui.builder.openapi.TagExternalDocumentationBuilder -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.nulls.shouldNotBeNull -import io.kotest.matchers.shouldBe -import io.swagger.v3.oas.models.tags.Tag - - -class TagsBuilderTest : StringSpec({ - - "empty tag object" { - buildTagObject("test-tag") {}.also { tag -> - tag.name shouldBe "test-tag" - tag.description shouldBe null - tag.externalDocs shouldBe null - tag.extensions shouldBe null - } - } - - "full tag object" { - buildTagObject("test-tag") { - description = "Description of tag" - externalDocDescription = "Description of external docs" - externalDocUrl = "example.com" - }.also { tag -> - tag.name shouldBe "test-tag" - tag.description shouldBe "Description of tag" - tag.externalDocs - .also { docs -> docs.shouldNotBeNull() } - ?.also { docs -> - docs.description shouldBe "Description of external docs" - docs.url shouldBe "example.com" - docs.extensions shouldBe null - } - tag.extensions shouldBe null - } - } - -}) { - - companion object { - - private fun buildTagObject(name: String, builder: OpenApiTag.() -> Unit): Tag { - return TagBuilder( - tagExternalDocumentationBuilder = TagExternalDocumentationBuilder() - ).build(OpenApiTag(name).apply(builder).build(TagData.DEFAULT)) - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/route/RouteDocumentationMergerTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/route/RouteDocumentationMergerTest.kt deleted file mode 100644 index d4c8124..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/route/RouteDocumentationMergerTest.kt +++ /dev/null @@ -1,138 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.route - -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute -import io.github.smiley4.ktorswaggerui.builder.route.RouteDocumentationMerger -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.collections.shouldBeEmpty -import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe - -class RouteDocumentationMergerTest : StringSpec({ - - "merge empty routes" { - merge( - route {}, - route {} - ).also { route -> - route.tags.shouldBeEmpty() - route.summary shouldBe null - route.description shouldBe null - route.operationId shouldBe null - route.deprecated shouldBe false - route.hidden shouldBe false - route.securitySchemeName shouldBe null - route.securitySchemeNames.shouldBeEmpty() - route.protected shouldBe null - route.getRequest().also { requests -> - requests.getParameters().shouldBeEmpty() - requests.getBody() shouldBe null - } - route.getResponses().also { responses -> - responses.getResponses().shouldBeEmpty() - } - } - } - - "merge complete routes" { - merge( - route { - specId = "test-spec-a" - tags = listOf("a1", "a2") - summary = "Summary A" - description = "Description A" - operationId = "operationA" - securitySchemeName = "securitySchemeNameA" - securitySchemeNames = listOf("securitySchemeNameA1", "securitySchemeNameA2") - deprecated = true - hidden = false - protected = true - request { - queryParameter("query") - pathParameter("pathA1") - pathParameter("pathA2") - body { - description = "body A" - } - } - response { - "a1" to { description = "response a1" } - "a2" to { description = "response a1" } - } - }, - route { - specId = "test-spec-b" - tags = listOf("b1", "b2") - summary = "Summary B" - description = "Description B" - operationId = "operationB" - securitySchemeName = "securitySchemeNameB" - securitySchemeNames = listOf("securitySchemeNameB1", "securitySchemeNameB2") - deprecated = false - hidden = true - protected = false - request { - queryParameter("query") - pathParameter("pathB1") - pathParameter("pathB2") - body { - description = "body B" - } - } - response { - "b1" to { description = "response b1" } - "b2" to { description = "response b1" } - } - } - ).also { route -> - route.specId shouldBe "test-spec-a" - route.tags shouldContainExactlyInAnyOrder listOf("a1", "a2", "b1", "b2") - route.summary shouldBe "Summary A" - route.description shouldBe "Description A" - route.operationId shouldBe "operationA" - route.deprecated shouldBe true - route.hidden shouldBe true - route.securitySchemeName shouldBe "securitySchemeNameA" - route.securitySchemeNames shouldContainExactlyInAnyOrder listOf( - "securitySchemeNameA1", - "securitySchemeNameA2", - "securitySchemeNameB1", - "securitySchemeNameB2" - ) - route.protected shouldBe true - route.getRequest().also { requests -> - requests.getParameters().map { it.name } shouldContainExactlyInAnyOrder listOf( - "query", - "pathA1", - "pathA2", - "query", - "pathB1", - "pathB2" - ) - requests.getBody() - .also { it shouldNotBe null } - ?.also { it.description shouldBe "body A" } - } - route.getResponses().also { responses -> - responses.getResponses().map { it.statusCode } shouldContainExactlyInAnyOrder listOf( - "b1", "b2", "a1", "a2" - ) - } - } - } - -}) { - - companion object { - - fun route(builder: OpenApiRoute.() -> Unit): OpenApiRoute { - return OpenApiRoute().apply(builder) - } - - fun merge(a: OpenApiRoute, b: OpenApiRoute): OpenApiRoute { - return RouteDocumentationMerger().merge(a, b) - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/schema/SchemaBuilderTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/schema/SchemaBuilderTest.kt deleted file mode 100644 index 7102e82..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/schema/SchemaBuilderTest.kt +++ /dev/null @@ -1,519 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.schema - -import com.fasterxml.jackson.databind.ObjectMapper -import com.github.ricky12awesome.jss.encodeToSchema -import com.github.victools.jsonschema.generator.Option -import com.github.victools.jsonschema.generator.OptionPreset -import com.github.victools.jsonschema.generator.SchemaGenerator -import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder -import com.github.victools.jsonschema.generator.SchemaVersion -import com.github.victools.jsonschema.module.swagger2.Swagger2Module -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaDefinitions -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaTypeAttributeOverride -import io.github.smiley4.ktorswaggerui.builder.schema.TypeOverwrites -import io.github.smiley4.ktorswaggerui.dsl.Example -import io.github.smiley4.ktorswaggerui.dsl.SchemaEncoder -import io.github.smiley4.ktorswaggerui.dsl.getSchemaType -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.collections.shouldContainExactly -import io.kotest.matchers.maps.shouldHaveSize -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe -import io.swagger.v3.oas.annotations.media.Schema -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.serializer -import java.io.File -import kotlin.reflect.jvm.javaType - -class SchemaBuilderTest : StringSpec({ - - "primitive (victools, all definitions)" { - createSchemaVictools(true).also { defs -> - defs.definitions.keys shouldContainExactly setOf("int") - defs.root.also { schema -> - schema.`$ref` shouldBe "#/components/schemas/int" - schema.type shouldBe null - } - defs.definitions["int"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "integer" - } - } - } - - "primitive (victools, no definitions)" { - createSchemaVictools(false).also { defs -> - defs.definitions shouldHaveSize 0 - defs.root.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "integer" - schema.properties shouldBe null - } - } - } - - "primitive (kotlinx, definitions)" { - createSchemaKotlinX(true).also { defs -> - defs.definitions.keys shouldContainExactly setOf("x1xjd3yo2dbzzz") - defs.root.also { schema -> - schema.`$ref` shouldBe "#/components/schemas/x1xjd3yo2dbzzz" - schema.type shouldBe null - } - defs.definitions["x1xjd3yo2dbzzz"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "number" - schema.properties shouldBe null - } - } - } - - "primitive (kotlinx, no definitions)" { - createSchemaKotlinX(false).also { defs -> - defs.definitions shouldHaveSize 0 - defs.root.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "number" - schema.properties shouldBe null - } - } - } - - "simple object (victools, all definitions)" { - createSchemaVictools(true).also { defs -> - defs.definitions.keys shouldContainExactly setOf("Pet") - defs.root.also { schema -> - schema.`$ref` shouldBe "#/components/schemas/Pet" - schema.type shouldBe null - } - defs.definitions["Pet"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("id", "name", "tag") - } - } - } - - "simple object (victools, no definitions)" { - createSchemaVictools(false).also { defs -> - println(defs) - defs.definitions shouldHaveSize 0 - defs.root.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("id", "name", "tag") - schema.properties["id"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "integer" - } - schema.properties["name"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "string" - } - schema.properties["tag"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "string" - } - } - } - } - - "object with special field names (victools)" { - createSchemaVictools(false).also { defs -> - defs.root.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("flag", "getAnotherText", "text", "isSomething", "isSomeText") - schema.properties["flag"]!!.also { prop -> - prop.type shouldBe "boolean" - } - schema.properties["isSomething"]!!.also { prop -> - prop.type shouldBe "boolean" - } - schema.properties["text"]!!.also { prop -> - prop.type shouldBe "string" - } - schema.properties["getAnotherText"]!!.also { prop -> - prop.type shouldBe "string" - } - schema.properties["isSomeText"]!!.also { prop -> - prop.type shouldBe "string" - } - } - } - } - - "simple object (kotlinx, definitions)" { - createSchemaKotlinX(true).also { defs -> - defs.definitions.keys shouldContainExactly setOf("1d8t6cs0dbcap", "x1xjd3yo2dbzzz", "xq0zwcprkn9j3") - defs.root.also { schema -> - schema.`$ref` shouldBe "#/components/schemas/1d8t6cs0dbcap" - schema.type shouldBe null - } - defs.definitions["1d8t6cs0dbcap"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("id", "name", "tag") - schema.properties["id"]!!.also { prop -> - prop.`$ref` shouldBe "#/components/schemas/x1xjd3yo2dbzzz" - prop.type shouldBe null - } - schema.properties["name"]!!.also { prop -> - prop.`$ref` shouldBe "#/components/schemas/xq0zwcprkn9j3" - prop.type shouldBe null - } - schema.properties["tag"]!!.also { prop -> - prop.`$ref` shouldBe "#/components/schemas/xq0zwcprkn9j3" - prop.type shouldBe null - } - } - defs.definitions["x1xjd3yo2dbzzz"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "number" - schema.properties shouldBe null - } - defs.definitions["xq0zwcprkn9j3"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "string" - schema.properties shouldBe null - } - } - } - - "simple object (kotlinx, no definitions)" { - createSchemaKotlinX(false).also { defs -> - defs.definitions shouldHaveSize 0 - defs.root.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("id", "name", "tag") - schema.properties["id"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "number" - } - schema.properties["name"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "string" - } - schema.properties["tag"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "string" - } - } - } - } - - "object with special field names (kotlinx)" { - createSchemaKotlinX(false).also { defs -> - defs.root.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("flag", "getAnotherText", "text", "isSomething", "isSomeText") - schema.properties["flag"]!!.also { prop -> - prop.type shouldBe "boolean" - } - schema.properties["isSomething"]!!.also { prop -> - prop.type shouldBe "boolean" - } - schema.properties["text"]!!.also { prop -> - prop.type shouldBe "string" - } - schema.properties["getAnotherText"]!!.also { prop -> - prop.type shouldBe "string" - } - schema.properties["isSomeText"]!!.also { prop -> - prop.type shouldBe "string" - } - } - } - } - - //==== SIMPLE LIST ===================================================== - - "simple list (victools, all definitions)" { - createSchemaVictools>(true).also { defs -> - defs.definitions.keys shouldContainExactly setOf("List(Pet)", "Pet") - defs.root.also { schema -> - schema.`$ref` shouldBe "#/components/schemas/List(Pet)" - schema.type shouldBe null - } - defs.definitions["List(Pet)"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "array" - schema.items - .also { it shouldNotBe null } - ?.also { item -> - item.type shouldBe null - item.`$ref` shouldBe "#/components/schemas/Pet" - } - } - defs.definitions["Pet"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("id", "name", "tag") - } - } - } - - "simple list (victools, no definitions)" { - createSchemaVictools>(false).also { defs -> - defs.definitions shouldHaveSize 0 - defs.root.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "array" - schema.properties shouldBe null - schema.items - .also { it shouldNotBe null } - ?.also { item -> - item.type shouldBe "object" - item.`$ref` shouldBe null - item.properties.keys shouldContainExactly setOf("id", "name", "tag") - item.properties["id"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "integer" - } - item.properties["name"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "string" - } - item.properties["tag"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "string" - } - } - } - } - } - - - "simple list (kotlinx, definitions)" { - createSchemaKotlinX>(true).also { defs -> - defs.definitions.keys shouldContainExactly setOf( - "1tonzv7il5q0x", - "1d8t6cs0dbcap", - "x1xjd3yo2dbzzz", - "xq0zwcprkn9j3" - ) - defs.root.also { schema -> - schema.`$ref` shouldBe "#/components/schemas/1tonzv7il5q0x" - schema.type shouldBe null - } - defs.definitions["1tonzv7il5q0x"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "array" - schema.properties shouldBe null - schema.items - .also { it shouldNotBe null } - ?.also { item -> - item.type shouldBe null - item.`$ref` shouldBe "#/components/schemas/1d8t6cs0dbcap" - } - } - defs.definitions["1d8t6cs0dbcap"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("id", "name", "tag") - schema.properties["id"]!!.also { prop -> - prop.`$ref` shouldBe "#/components/schemas/x1xjd3yo2dbzzz" - prop.type shouldBe null - } - schema.properties["name"]!!.also { prop -> - prop.`$ref` shouldBe "#/components/schemas/xq0zwcprkn9j3" - prop.type shouldBe null - } - schema.properties["tag"]!!.also { prop -> - prop.`$ref` shouldBe "#/components/schemas/xq0zwcprkn9j3" - prop.type shouldBe null - } - } - defs.definitions["x1xjd3yo2dbzzz"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "number" - schema.properties shouldBe null - } - defs.definitions["xq0zwcprkn9j3"]!!.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "string" - schema.properties shouldBe null - } - } - } - - "simple list (kotlinx, no definitions)" { - createSchemaKotlinX>(false).also { defs -> - defs.definitions shouldHaveSize 0 - defs.root.also { schema -> - schema.`$ref` shouldBe null - schema.type shouldBe "array" - schema.properties shouldBe null - schema.items - .also { it shouldNotBe null } - ?.also { item -> - item.type shouldBe "object" - item.`$ref` shouldBe null - item.properties.keys shouldContainExactly setOf("id", "name", "tag") - item.properties["id"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "number" - } - item.properties["name"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "string" - } - item.properties["tag"]!!.also { prop -> - prop.`$ref` shouldBe null - prop.type shouldBe "string" - } - } - } - } - } - - "schema with Schema-Annotations" { - createSchemaVictools(false).also { defs -> - defs.definitions shouldHaveSize 0 - defs.root.also { schema -> - schema.title = "The Schema for a person" - schema.type shouldBe "object" - schema.properties.keys shouldContainExactly setOf("age", "city_code", "name") - schema.properties["age"]!!.also { age -> - age.type shouldBe "integer" - age.description shouldBe "the age of the person in years" - age.format shouldBe "int32" - age.nullable shouldBe true - age.minimum.toInt() shouldBe 1 - age.maximum.toInt() shouldBe 99 - age.example shouldBe 42 - } - schema.properties["name"]!!.also { name -> - name.type shouldBe "string" - name.description shouldBe "the name of the person" - name.minLength shouldBe 1 - name.maxLength shouldBe 32 - name.example shouldBe "Mr. Example" - } - schema.properties["city_code"]!!.also { cityCode -> - cityCode.type shouldBe "integer" - cityCode.format shouldBe "int32" - cityCode.example shouldBe 12345 - } - } - } - } - - "file type-overwrite" { - createSchemaVictools(false).also { defs -> - defs.definitions shouldHaveSize 0 - defs.root.also { schema -> - schema.type shouldBe "string" - schema.format shouldBe "binary" - } - } - } - -}) { - - companion object { - - @Serializable - private data class Pet( - val id: Int, - val name: String, - val tag: String - ) - - - @Schema(title = "The Schema for a person") - data class Person( - - @field:Schema( - description = "the name of the person", - minLength = 1, - maxLength = 32 - ) - @field:Example("Mr. Example") - val name: String, - - @field:Schema( - description = "the age of the person in years", - nullable = true, - maximum = "99", - minimum = "1", - ) - @field:Example("42") - val age: Int, - - @field:Schema( - name = "city_code", - example = "12345" - ) - val cityCode: Int - - ) - - @Serializable - class WithFieldNames( - val flag: Boolean, - val isSomething: Boolean, - val text: String, - val isSomeText: String, - val getAnotherText: String - ) - - inline fun createSchemaVictools(definitions: Boolean) = - createSchema("\$defs", serializerVictools(definitions)) - - inline fun createSchemaKotlinX(generateDefinitions: Boolean) = - createSchema("definitions", serializerKotlinX(generateDefinitions)) - - inline fun createSchema( - defs: String, - noinline serializer: SchemaEncoder - ): SchemaDefinitions { - return SchemaBuilder(defs, serializer, ObjectMapper(), TypeOverwrites.get()).create(getSchemaType()) - } - - fun serializerVictools(definitions: Boolean): SchemaEncoder { - return { type -> - SchemaGenerator( - SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON) - .with(Swagger2Module()) - .with(Option.EXTRA_OPEN_API_FORMAT_VALUES) - .with(Option.ALLOF_CLEANUP_AT_THE_END) - .with(Option.MAP_VALUES_AS_ADDITIONAL_PROPERTIES).also { - if (definitions) { - it - .with(Option.DEFINITIONS_FOR_ALL_OBJECTS) - .with(Option.DEFINITION_FOR_MAIN_SCHEMA) - .without(Option.INLINE_ALL_SCHEMAS) - } else { - it.with(Option.INLINE_ALL_SCHEMAS) - } - } - .also { - it.forTypesInGeneral() - .withTypeAttributeOverride(SchemaTypeAttributeOverride()) - } - .build() - ).generateSchema(type.javaType).toPrettyString() - } - } - - fun serializerKotlinX(generateDefinitions: Boolean): SchemaEncoder { - val kotlinxJson = Json { - prettyPrint = true - encodeDefaults = true - } - return { type -> - kotlinxJson.encodeToSchema( - serializer(type), - generateDefinitions = generateDefinitions, - exposeClassDiscriminator = false - ) - } - } - - } - -} diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/schema/SchemaContextTest.kt b/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/schema/SchemaContextTest.kt deleted file mode 100644 index f4040d4..0000000 --- a/src/test/kotlin/io/github/smiley4/ktorswaggerui/tests/schema/SchemaContextTest.kt +++ /dev/null @@ -1,553 +0,0 @@ -package io.github.smiley4.ktorswaggerui.tests.schema - -import com.fasterxml.jackson.databind.ObjectMapper -import com.github.victools.jsonschema.generator.Option -import com.github.victools.jsonschema.generator.OptionPreset -import com.github.victools.jsonschema.generator.SchemaGenerator -import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder -import com.github.victools.jsonschema.generator.SchemaVersion -import io.github.smiley4.ktorswaggerui.data.PluginConfigData -import io.github.smiley4.ktorswaggerui.dsl.PluginConfigDsl -import io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute -import io.github.smiley4.ktorswaggerui.dsl.asSchemaType -import io.github.smiley4.ktorswaggerui.dsl.getSchemaType -import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextBuilder -import io.github.smiley4.ktorswaggerui.builder.schema.TypeOverwrites -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor.Companion.custom -import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor.Companion.multipleOf -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.maps.shouldBeEmpty -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe -import io.ktor.http.HttpMethod -import io.swagger.v3.oas.models.media.Schema -import kotlin.reflect.jvm.javaType - -class SchemaContextTest : StringSpec({ - - "route with all schemas" { - val routes = listOf( - route { - request { - queryParameter("queryParam") - pathParameter("pathParam") - headerParameter("headerParam") - body() - } - response { - default { - header("header") - body() - } - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(QueryParamType::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/QueryParamType" - } - schemaContext.getSchema(PathParamType::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/PathParamType" - } - schemaContext.getSchema(HeaderParamType::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/HeaderParamType" - } - schemaContext.getSchema(RequestBodyType::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/RequestBodyType" - } - schemaContext.getSchema(ResponseHeaderType::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/ResponseHeaderType" - } - schemaContext.getSchema(ResponseBodyType::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/ResponseBodyType" - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf( - "QueryParamType", - "PathParamType", - "HeaderParamType", - "RequestBodyType", - "ResponseHeaderType", - "ResponseBodyType", - ) - components.forEach { (_, schema) -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("value") - } - } - } - - "primitive type" { - val routes = listOf( - route { - request { - body() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(Integer::class.asSchemaType()).also { schema -> - schema.type shouldBe "integer" - schema.format shouldBe "int32" - schema.`$ref` shouldBe null - } - schemaContext.getComponentsSection().also { components -> - components.shouldBeEmpty() - } - } - - "primitive array" { - val routes = listOf( - route { - request { - body>() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(getType>()).also { schema -> - schema.type shouldBe "array" - schema.`$ref` shouldBe null - schema.items.also { item -> - item.type shouldBe "string" - } - } - schemaContext.getComponentsSection().also { components -> - components.shouldBeEmpty() - } - } - - "primitive deep array" { - val routes = listOf( - route { - request { - body>>>() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(getType>>>()).also { schema -> - schema.type shouldBe "array" - schema.`$ref` shouldBe null - schema.items.also { item0 -> - item0.type shouldBe "array" - item0.`$ref` shouldBe null - item0.items.also { item1 -> - item1.type shouldBe "array" - item1.`$ref` shouldBe null - item1.items.also { item2 -> - item2.type shouldBe "boolean" - } - } - } - } - schemaContext.getComponentsSection().also { components -> - components.shouldBeEmpty() - } - } - - "object" { - val routes = listOf( - route { - request { - body() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(SimpleDataClass::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/SimpleDataClass" - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf("SimpleDataClass") - components["SimpleDataClass"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("text", "number") - } - } - } - - "object array" { - val routes = listOf( - route { - request { - body>() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(getType>()).also { schema -> - schema.type shouldBe "array" - schema.`$ref` shouldBe null - schema.items.also { item -> - item.type shouldBe null - item.`$ref` shouldBe "#/components/schemas/SimpleDataClass" - } - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf("SimpleDataClass") - components["SimpleDataClass"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("text", "number") - } - } - } - - "nested objects" { - val routes = listOf( - route { - request { - body() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(DataWrapper::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/DataWrapper" - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf("SimpleDataClass", "DataWrapper") - components["SimpleDataClass"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("text", "number") - } - components["DataWrapper"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("data", "enabled") - schema.properties["data"]?.also { nestedSchema -> - nestedSchema.type shouldBe null - nestedSchema.`$ref` shouldBe "#/components/schemas/SimpleDataClass" - } - } - } - } - - "simple enum" { - val routes = listOf( - route { - request { - body() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(SimpleEnum::class.asSchemaType()).also { schema -> - schema.type shouldBe "string" - schema.enum shouldContainExactlyInAnyOrder SimpleEnum.values().map { it.name } - schema.`$ref` shouldBe null - } - schemaContext.getComponentsSection().also { components -> - components.keys.shouldBeEmpty() - } - } - - "maps" { - val routes = listOf( - route { - request { - body() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(DataClassWithMaps::class.asSchemaType()).also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/DataClassWithMaps" - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf( - "DataClassWithMaps", - "Map(String,Long)", - "Map(String,String)" - ) - components["DataClassWithMaps"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("mapStringValues", "mapLongValues") - schema.properties["mapStringValues"]?.also { nestedSchema -> - nestedSchema.type shouldBe null - nestedSchema.`$ref` shouldBe "#/components/schemas/Map(String,String)" - } - schema.properties["mapLongValues"]?.also { nestedSchema -> - nestedSchema.type shouldBe null - nestedSchema.`$ref` shouldBe "#/components/schemas/Map(String,Long)" - } - } - } - } - - "custom schema object" { - val config = PluginConfigDsl().also { - it.customSchemas { - openApi("myCustomSchema") { - Schema().also { schema -> - schema.type = "object" - schema.properties = mapOf( - "custom" to Schema().also { prop -> - prop.type = "string" - } - ) - } - } - } - } - val routes = listOf( - route { - request { - body(custom("myCustomSchema")) - } - } - ) - val schemaContext = schemaContext(routes, config) - schemaContext.getSchema("myCustomSchema").also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/myCustomSchema" - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf( - "myCustomSchema", - ) - components["myCustomSchema"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("custom") - } - } - } - - "custom schema array" { - val config = PluginConfigDsl().also { - it.customSchemas { - openApi("myCustomSchema") { - Schema().also { schema -> - schema.type = "object" - schema.properties = mapOf( - "custom" to Schema().also { prop -> - prop.type = "string" - } - ) - } - } - } - } - val routes = listOf( - route { - request { - body(multipleOf(custom("myCustomSchema"))) - } - } - ) - val schemaContext = schemaContext(routes, config) - schemaContext.getSchema("myCustomSchema").also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/myCustomSchema" - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf( - "myCustomSchema", - ) - components["myCustomSchema"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("custom") - } - } - } - - "unwrap inlined array schema" { - val generator = SchemaGenerator( - SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON) - .without(Option.DEFINITIONS_FOR_ALL_OBJECTS) - .with(Option.INLINE_ALL_SCHEMAS) - .with(Option.EXTRA_OPEN_API_FORMAT_VALUES) - .with(Option.ALLOF_CLEANUP_AT_THE_END) - .build() - ) - val config = PluginConfigDsl().also { - it.encoding { - schemaEncoder { type -> - generator.generateSchema(type.javaType).toString() - } - } - } - val routes = listOf( - route { - request { - body>() - } - } - ) - val schemaContext = schemaContext(routes, config) - schemaContext.getSchema(getSchemaType>()).also { schema -> - schema.type shouldBe "array" - schema.`$ref` shouldBe null - schema.items - .also { it shouldNotBe null } - ?.also { items -> - items.`$ref` shouldBe "#/components/schemas/SimpleDataClass" - } - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf( - "SimpleDataClass", - ) - components["SimpleDataClass"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("number", "text") - } - } - } - - "don't include unused custom schema" { - val config = PluginConfigDsl().also { - it.customSchemas { - includeAll = false - openApi("myCustomSchema") { - Schema().also { schema -> - schema.type = "object" - schema.properties = mapOf( - "custom" to Schema().also { prop -> - prop.type = "string" - } - ) - } - } - } - } - val schemaContext = schemaContext(emptyList(), config) - schemaContext.getSchemaOrNull("myCustomSchema") shouldBe null - schemaContext.getComponentsSection().also { components -> - components.keys shouldHaveSize 0 - } - } - - "include unused custom schema" { - val config = PluginConfigDsl().also { - it.customSchemas { - includeAll = true - openApi("myCustomSchema") { - Schema().also { schema -> - schema.type = "object" - schema.properties = mapOf( - "custom" to Schema().also { prop -> - prop.type = "string" - } - ) - } - } - } - } - val schemaContext = schemaContext(emptyList(), config) - schemaContext.getSchema("myCustomSchema").also { schema -> - schema.type shouldBe null - schema.`$ref` shouldBe "#/components/schemas/myCustomSchema" - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf( - "myCustomSchema", - ) - components["myCustomSchema"]?.also { schema -> - schema.type shouldBe "object" - schema.properties.keys shouldContainExactlyInAnyOrder listOf("custom") - } - } - } - - "array with wildcard-generic" { - val routes = listOf( - route { - request { - body>() - } - } - ) - val schemaContext = schemaContext(routes) - schemaContext.getSchema(getType>()).also { schema -> - schema.type shouldBe "array" - schema.`$ref` shouldBe null - schema.items.also { item -> - item.`$ref` shouldBe "#/components/schemas/*" - } - } - schemaContext.getComponentsSection().also { components -> - components.keys shouldContainExactlyInAnyOrder listOf("*") - components["*"]?.also { schema -> - schema.type shouldBe "object" - } - } - } - -}) { - - companion object { - - inline fun getType() = getSchemaType() - - private val defaultPluginConfig = PluginConfigDsl() - - private fun schemaContext( - routes: Collection, - pluginConfig: PluginConfigDsl = defaultPluginConfig - ): SchemaContext { - val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) - return SchemaContextBuilder( - config = pluginConfigData, - schemaBuilder = SchemaBuilder( - definitionsField = pluginConfigData.encoding.schemaDefsField, - schemaEncoder = pluginConfigData.encoding.schemaEncoder, - ObjectMapper(), - TypeOverwrites.get() - ) - ).build(routes) - } - - fun route(block: OpenApiRoute.() -> Unit): RouteMeta { - return RouteMeta( - path = "/test", - method = HttpMethod.Get, - documentation = OpenApiRoute().apply(block), - protected = false - ) - } - - private data class QueryParamType(val value: String) - - private data class PathParamType(val value: String) - private data class HeaderParamType(val value: String) - private data class RequestBodyType(val value: String) - private data class ResponseHeaderType(val value: String) - private data class ResponseBodyType(val value: String) - - private data class SimpleDataClass( - val text: String, - val number: Int - ) - - private data class DataWrapper( - val enabled: Boolean, - val data: SimpleDataClass - ) - - private enum class SimpleEnum { - RED, GREEN, BLUE - } - - private data class DataClassWithMaps( - val mapStringValues: Map, - val mapLongValues: Map - ) - - } - -} From 96dd726998b870564e0826d05e82219a055c170c Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 2 May 2024 19:27:04 +0200 Subject: [PATCH 02/32] wip: gradle multimodules --- HowToRelease.md | 9 - build.gradle.kts | 177 ++-- config/detekt.yml | 788 ------------------ gradle.properties | 3 + ktor-swagger-ui-examples/build.gradle.kts | 18 + .../src/main/kotlin/Main.kt | 6 + NOTES.txt => ktor-swagger-ui/NOTES.txt | 0 ktor-swagger-ui/build.gradle.kts | 51 ++ .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 5 - .../builder/example/ExampleContext.kt | 0 .../builder/example/ExampleContextBuilder.kt | 0 .../builder/openapi/ComponentsBuilder.kt | 0 .../builder/openapi/ContactBuilder.kt | 0 .../builder/openapi/ContentBuilder.kt | 0 .../builder/openapi/ExampleBuilder.kt | 0 .../openapi/ExternalDocumentationBuilder.kt | 0 .../builder/openapi/HeaderBuilder.kt | 0 .../builder/openapi/InfoBuilder.kt | 0 .../builder/openapi/LicenseBuilder.kt | 0 .../builder/openapi/OAuthFlowsBuilder.kt | 0 .../builder/openapi/OpenApiBuilder.kt | 0 .../builder/openapi/OperationBuilder.kt | 0 .../builder/openapi/OperationTagsBuilder.kt | 0 .../builder/openapi/ParameterBuilder.kt | 0 .../builder/openapi/PathBuilder.kt | 0 .../builder/openapi/PathsBuilder.kt | 0 .../builder/openapi/RequestBodyBuilder.kt | 0 .../builder/openapi/ResponseBuilder.kt | 0 .../builder/openapi/ResponsesBuilder.kt | 0 .../openapi/SecurityRequirementsBuilder.kt | 0 .../builder/openapi/SecuritySchemesBuilder.kt | 0 .../builder/openapi/ServerBuilder.kt | 0 .../builder/openapi/TagBuilder.kt | 0 .../TagExternalDocumentationBuilder.kt | 0 .../builder/route/RouteCollector.kt | 0 .../builder/route/RouteDocumentationMerger.kt | 0 .../ktorswaggerui/builder/route/RouteMeta.kt | 0 .../builder/schema/SchemaContext.kt | 0 .../builder/schema/SchemaContextImpl.kt | 0 .../ktorswaggerui/data/AuthKeyLocation.kt | 0 .../smiley4/ktorswaggerui/data/AuthScheme.kt | 0 .../smiley4/ktorswaggerui/data/AuthType.kt | 0 .../smiley4/ktorswaggerui/data/ContactData.kt | 0 .../smiley4/ktorswaggerui/data/DataUtils.kt | 0 .../ktorswaggerui/data/ExternalDocsData.kt | 0 .../smiley4/ktorswaggerui/data/InfoData.kt | 0 .../smiley4/ktorswaggerui/data/LicenseData.kt | 0 .../ktorswaggerui/data/OpenApiBaseBodyData.kt | 0 .../ktorswaggerui/data/OpenApiExampleData.kt | 0 .../ktorswaggerui/data/OpenApiHeaderData.kt | 0 .../data/OpenApiMultipartPartData.kt | 0 .../ktorswaggerui/data/OpenApiRequestData.kt | 0 .../data/OpenApiRequestParameterData.kt | 0 .../ktorswaggerui/data/OpenApiResponseData.kt | 0 .../ktorswaggerui/data/OpenApiRouteData.kt | 0 .../ktorswaggerui/data/OpenIdOAuthFlowData.kt | 0 .../data/OpenIdOAuthFlowsData.kt | 0 .../ktorswaggerui/data/ParameterLocation.kt | 0 .../smiley4/ktorswaggerui/data/PathFilter.kt | 0 .../ktorswaggerui/data/PluginConfigData.kt | 0 .../ktorswaggerui/data/SecuritySchemeData.kt | 0 .../smiley4/ktorswaggerui/data/ServerData.kt | 0 .../ktorswaggerui/data/SpecAssigned.kt | 0 .../ktorswaggerui/data/SwaggerUIData.kt | 0 .../ktorswaggerui/data/SwaggerUiSort.kt | 0 .../data/SwaggerUiSyntaxHighlight.kt | 0 .../smiley4/ktorswaggerui/data/TagData.kt | 0 .../ktorswaggerui/data/TagGenerator.kt | 0 .../ktorswaggerui/data/TypeDescriptor.kt | 0 .../data/WhenBuildOpenApiSpecs.kt | 0 .../ktorswaggerui/dsl/OpenApiDslMarker.kt | 0 .../dsl/config/OpenApiContact.kt | 0 .../dsl/config/OpenApiExternalDocs.kt | 0 .../ktorswaggerui/dsl/config/OpenApiInfo.kt | 0 .../dsl/config/OpenApiLicense.kt | 0 .../dsl/config/OpenApiSecurityScheme.kt | 0 .../ktorswaggerui/dsl/config/OpenApiServer.kt | 0 .../ktorswaggerui/dsl/config/OpenApiTag.kt | 0 .../dsl/config/OpenIdOAuthFlow.kt | 0 .../dsl/config/OpenIdOAuthFlows.kt | 0 .../dsl/config/PluginConfigDsl.kt | 0 .../ktorswaggerui/dsl/config/SwaggerUIDsl.kt | 0 .../dsl/routes/OpenApiBaseBody.kt | 0 .../dsl/routes/OpenApiExample.kt | 0 .../ktorswaggerui/dsl/routes/OpenApiHeader.kt | 0 .../dsl/routes/OpenApiMultipartBody.kt | 0 .../dsl/routes/OpenApiMultipartPart.kt | 0 .../dsl/routes/OpenApiRequest.kt | 0 .../dsl/routes/OpenApiRequestParameter.kt | 0 .../dsl/routes/OpenApiResponse.kt | 0 .../dsl/routes/OpenApiResponses.kt | 0 .../ktorswaggerui/dsl/routes/OpenApiRoute.kt | 0 .../dsl/routes/OpenApiSimpleBody.kt | 0 .../dsl/routing/DocumentedRouteSelector.kt | 0 .../resources/DocumentedResourceRoutes.kt | 0 .../smiley4/ktorswaggerui/routing/ApiSpec.kt | 0 .../ktorswaggerui/routing/ResourceContent.kt | 0 .../smiley4/ktorswaggerui/routing/routing.kt | 0 .../io/github/smiley4/ktorswaggerui/Test.kt | 0 .../src}/test/resources/logback.xml | 0 settings.gradle.kts | 10 + 101 files changed, 148 insertions(+), 919 deletions(-) delete mode 100644 HowToRelease.md delete mode 100644 config/detekt.yml create mode 100644 ktor-swagger-ui-examples/build.gradle.kts create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/Main.kt rename NOTES.txt => ktor-swagger-ui/NOTES.txt (100%) create mode 100644 ktor-swagger-ui/build.gradle.kts rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt (96%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthScheme.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/DataUtils.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigned.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/data/WhenBuildOpenApiSpecs.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiDslMarker.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiContact.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlows.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponses.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/DocumentedRouteSelector.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/resources/DocumentedResourceRoutes.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ResourceContent.kt (100%) rename {src => ktor-swagger-ui/src}/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt (100%) rename {src => ktor-swagger-ui/src}/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt (100%) rename {src => ktor-swagger-ui/src}/test/resources/logback.xml (100%) diff --git a/HowToRelease.md b/HowToRelease.md deleted file mode 100644 index afa9565..0000000 --- a/HowToRelease.md +++ /dev/null @@ -1,9 +0,0 @@ -# How to release - -https://vanniktech.github.io/gradle-maven-publish-plugin/central/ - -1. Credentials should be configured in a gradle.properties file (in user home) - -2. `./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache` - -3. `./gradlew closeAndReleaseRepository` \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 6900fc5..1fa00c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,136 +1,79 @@ -import com.vanniktech.maven.publish.SonatypeHost import io.gitlab.arturbosch.detekt.Detekt -object Meta { - const val groupId = "io.github.smiley4" - const val artifactId = "ktor-swagger-ui" - const val version = "3.0.0-indev" - const val name = "Ktor Swagger-UI" - const val description = "Ktor plugin to document routes and provide Swagger UI" - const val licenseName = "The Apache License, Version 2.0" - const val licenseUrl = "https://www.apache.org/licenses/LICENSE-2.0.txt" - const val scmUrl = "https://github.com/SMILEY4/ktor-swagger-ui" - const val scmConnection = "scm:git:git://github.com/SMILEY4/ktor-swagger-ui.git" - const val developerName = "smiley4" - const val developerUrl = "https://github.com/SMILEY4" -} - -group = Meta.groupId -version = Meta.version - plugins { kotlin("jvm") version "1.8.21" - kotlin("plugin.serialization") version "1.8.21" id("org.owasp.dependencycheck") version "8.2.1" - id("com.vanniktech.maven.publish") version "0.25.2" id("io.gitlab.arturbosch.detekt") version "1.23.0" } -repositories { - mavenCentral() - mavenLocal() - maven(url = "https://raw.githubusercontent.com/glureau/json-schema-serialization/mvn-repo") -} - -dependencies { - - val ktorVersion = "2.3.7" - implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") - implementation("io.ktor:ktor-server-webjars:$ktorVersion") - implementation("io.ktor:ktor-server-auth:$ktorVersion") - implementation("io.ktor:ktor-server-resources:$ktorVersion") - testImplementation("io.ktor:ktor-server-netty-jvm:$ktorVersion") - testImplementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") - testImplementation("io.ktor:ktor-serialization-jackson:$ktorVersion") - testImplementation("io.ktor:ktor-server-auth:$ktorVersion") - testImplementation("io.ktor:ktor-server-call-logging:$ktorVersion") - testImplementation("io.ktor:ktor-server-test-host:$ktorVersion") - - val swaggerUiVersion = "5.9.0" // this version must match the version declared in the code (SwaggerPlugin#SWAGGER_UI_WEBJARS_VERSION) - implementation("org.webjars:swagger-ui:$swaggerUiVersion") - - val swaggerParserVersion = "2.1.19" - implementation("io.swagger.parser.v3:swagger-parser:$swaggerParserVersion") - - val schemaKeneratorVersion = "0.1" - implementation("io.github.smiley4:schema-kenerator-core:$schemaKeneratorVersion") - implementation("io.github.smiley4:schema-kenerator-reflection:$schemaKeneratorVersion") - implementation("io.github.smiley4:schema-kenerator-swagger:$schemaKeneratorVersion") - - val jacksonVersion = "2.15.3" - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:${jacksonVersion}") - - val kotlinLoggingVersion = "3.0.5" - implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") - - val logbackVersion = "1.4.11" - testImplementation("ch.qos.logback:logback-classic:$logbackVersion") - - val versionMockk = "1.13.8" - testImplementation("io.mockk:mockk:$versionMockk") +subprojects { - val versionKotest = "5.8.0" - testImplementation("io.kotest:kotest-runner-junit5:$versionKotest") - testImplementation("io.kotest:kotest-assertions-core:$versionKotest") + val ktorSwaggerUiVersion: String by project + val ktorSwaggerUiGroupId: String by project + group = ktorSwaggerUiGroupId + version = ktorSwaggerUiVersion - val versionKotlinTest = "1.8.21" - testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest") + apply(plugin = "org.jetbrains.kotlin.jvm") + apply(plugin = "io.gitlab.arturbosch.detekt") + apply(plugin = "org.owasp.dependencycheck") - testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") - testImplementation("com.github.Ricky12Awesome:json-schema-serialization:0.9.9") - testImplementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.1") -} - -kotlin { - jvmToolchain(11) -} + repositories { + mavenLocal() + mavenCentral() + } -tasks.withType().configureEach { - useJUnitPlatform() -} + dependencies {} -detekt { - buildUponDefaultConfig = true - allRules = false - config.setFrom("$projectDir/config/detekt.yml") - baseline = file("$projectDir/config/baseline.xml") -} + kotlin { + jvmToolchain(11) + } -tasks.withType().configureEach { - reports { - html.required.set(true) - md.required.set(true) - xml.required.set(false) - txt.required.set(false) - sarif.required.set(false) + tasks.withType().configureEach { + useJUnitPlatform() } -} -mavenPublishing { - publishToMavenCentral(SonatypeHost.S01) - signAllPublications() - coordinates(Meta.groupId, Meta.artifactId, Meta.version) - pom { - name.set(Meta.name) - description.set(Meta.description) - url.set(Meta.scmUrl) - licenses { - license { - name.set(Meta.licenseName) - url.set(Meta.licenseUrl) - distribution.set(Meta.licenseUrl) - } - } - scm { - url.set(Meta.scmUrl) - connection.set(Meta.scmConnection) - } - developers { - developer { - id.set(Meta.developerName) - name.set(Meta.developerName) - url.set(Meta.developerUrl) - } + tasks.withType().configureEach { + ignoreFailures = true // todo: temporary + buildUponDefaultConfig = true + allRules = false + config.setFrom("$projectDir/config/detekt.yml") + baseline.set(file("$projectDir/config/baseline.xml")) + reports { + html.required.set(true) + md.required.set(true) + xml.required.set(false) + txt.required.set(false) + sarif.required.set(false) } } + } + +//mavenPublishing { +// publishToMavenCentral(SonatypeHost.S01) +// signAllPublications() +// coordinates(Meta.groupId, Meta.artifactId, Meta.version) +// pom { +// name.set(Meta.name) +// description.set(Meta.description) +// url.set(Meta.scmUrl) +// licenses { +// license { +// name.set(Meta.licenseName) +// url.set(Meta.licenseUrl) +// distribution.set(Meta.licenseUrl) +// } +// } +// scm { +// url.set(Meta.scmUrl) +// connection.set(Meta.scmConnection) +// } +// developers { +// developer { +// id.set(Meta.developerName) +// name.set(Meta.developerName) +// url.set(Meta.developerUrl) +// } +// } +// } +//} diff --git a/config/detekt.yml b/config/detekt.yml deleted file mode 100644 index db9252e..0000000 --- a/config/detekt.yml +++ /dev/null @@ -1,788 +0,0 @@ -build: - maxIssues: 0 - excludeCorrectable: false - weights: - # complexity: 2 - # LongParameterList: 1 - # style: 1 - # comments: 1 - -config: - validation: true - warningsAsErrors: false - checkExhaustiveness: false - # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' - excludes: '' - -processors: - active: true - exclude: - - 'DetektProgressListener' - # - 'KtFileCountProcessor' - # - 'PackageCountProcessor' - # - 'ClassCountProcessor' - # - 'FunctionCountProcessor' - # - 'PropertyCountProcessor' - # - 'ProjectComplexityProcessor' - # - 'ProjectCognitiveComplexityProcessor' - # - 'ProjectLLOCProcessor' - # - 'ProjectCLOCProcessor' - # - 'ProjectLOCProcessor' - # - 'ProjectSLOCProcessor' - # - 'LicenseHeaderLoaderExtension' - -console-reports: - active: true - exclude: - - 'ProjectStatisticsReport' - - 'ComplexityReport' - - 'NotificationReport' - - 'FindingsReport' - - 'FileBasedFindingsReport' - # - 'LiteFindingsReport' - -output-reports: - active: true - exclude: - # - 'TxtOutputReport' - # - 'XmlOutputReport' - # - 'HtmlOutputReport' - # - 'MdOutputReport' - # - 'SarifOutputReport' - -comments: - active: true - AbsentOrWrongFileLicense: - active: false - licenseTemplateFile: 'license.template' - licenseTemplateIsRegex: false - CommentOverPrivateFunction: - active: false - CommentOverPrivateProperty: - active: false - DeprecatedBlockTag: - active: false - EndOfSentenceFormat: - active: false - endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' - KDocReferencesNonPublicProperty: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - OutdatedDocumentation: - active: false - matchTypeParameters: true - matchDeclarationsOrder: true - allowParamOnConstructorProperties: false - UndocumentedPublicClass: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - searchInNestedClass: true - searchInInnerClass: true - searchInInnerObject: true - searchInInnerInterface: true - searchInProtectedClass: false - UndocumentedPublicFunction: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - searchProtectedFunction: false - UndocumentedPublicProperty: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - searchProtectedProperty: false - -complexity: - active: true - CognitiveComplexMethod: - active: false - threshold: 15 - ComplexCondition: - active: true - threshold: 4 - ComplexInterface: - active: false - threshold: 10 - includeStaticDeclarations: false - includePrivateDeclarations: false - ignoreOverloaded: false - CyclomaticComplexMethod: - active: true - threshold: 20 - ignoreSingleWhenExpression: false - ignoreSimpleWhenEntries: false - ignoreNestingFunctions: false - nestingFunctions: - - 'also' - - 'apply' - - 'forEach' - - 'isNotNull' - - 'ifNull' - - 'let' - - 'run' - - 'use' - - 'with' - LabeledExpression: - active: false - ignoredLabels: [] - LargeClass: - active: true - threshold: 600 - excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] - LongMethod: - active: true - threshold: 60 - excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] - LongParameterList: - active: true - functionThreshold: 6 - constructorThreshold: 15 - ignoreDefaultParameters: false - ignoreDataClasses: true - ignoreAnnotatedParameter: [] - MethodOverloading: - active: false - threshold: 6 - NamedArguments: - active: false - threshold: 3 - ignoreArgumentsMatchingNames: false - NestedBlockDepth: - active: true - threshold: 4 - NestedScopeFunctions: - active: false - threshold: 1 - functions: - - 'kotlin.apply' - - 'kotlin.run' - - 'kotlin.with' - - 'kotlin.let' - - 'kotlin.also' - ReplaceSafeCallChainWithRun: - active: false - StringLiteralDuplication: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - threshold: 3 - ignoreAnnotation: true - excludeStringsWithLessThan5Characters: true - ignoreStringsRegex: '$^' - TooManyFunctions: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - thresholdInFiles: 11 - thresholdInClasses: 11 - thresholdInInterfaces: 11 - thresholdInObjects: 11 - thresholdInEnums: 11 - ignoreDeprecated: false - ignorePrivate: false - ignoreOverridden: false - -coroutines: - active: true - GlobalCoroutineUsage: - active: false - InjectDispatcher: - active: true - dispatcherNames: - - 'IO' - - 'Default' - - 'Unconfined' - RedundantSuspendModifier: - active: true - SleepInsteadOfDelay: - active: true - SuspendFunSwallowedCancellation: - active: false - SuspendFunWithCoroutineScopeReceiver: - active: false - SuspendFunWithFlowReturnType: - active: true - -empty-blocks: - active: true - EmptyCatchBlock: - active: true - allowedExceptionNameRegex: '_|(ignore|expected).*' - EmptyClassBlock: - active: true - EmptyDefaultConstructor: - active: true - EmptyDoWhileBlock: - active: true - EmptyElseBlock: - active: true - EmptyFinallyBlock: - active: true - EmptyForBlock: - active: true - EmptyFunctionBlock: - active: true - ignoreOverridden: false - EmptyIfBlock: - active: true - EmptyInitBlock: - active: true - EmptyKtFile: - active: true - EmptySecondaryConstructor: - active: true - EmptyTryBlock: - active: true - EmptyWhenBlock: - active: true - EmptyWhileBlock: - active: true - -exceptions: - active: true - ExceptionRaisedInUnexpectedLocation: - active: true - methodNames: - - 'equals' - - 'finalize' - - 'hashCode' - - 'toString' - InstanceOfCheckForException: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - NotImplementedDeclaration: - active: false - ObjectExtendsThrowable: - active: false - PrintStackTrace: - active: true - RethrowCaughtException: - active: true - ReturnFromFinally: - active: true - ignoreLabeled: false - SwallowedException: - active: true - ignoredExceptionTypes: - - 'InterruptedException' - - 'MalformedURLException' - - 'NumberFormatException' - - 'ParseException' - allowedExceptionNameRegex: '_|(ignore|expected).*' - ThrowingExceptionFromFinally: - active: true - ThrowingExceptionInMain: - active: false - ThrowingExceptionsWithoutMessageOrCause: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - exceptions: - - 'ArrayIndexOutOfBoundsException' - - 'Exception' - - 'IllegalArgumentException' - - 'IllegalMonitorStateException' - - 'IllegalStateException' - - 'IndexOutOfBoundsException' - - 'NullPointerException' - - 'RuntimeException' - - 'Throwable' - ThrowingNewInstanceOfSameException: - active: true - TooGenericExceptionCaught: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - exceptionNames: - - 'ArrayIndexOutOfBoundsException' - - 'Error' - - 'Exception' - - 'IllegalMonitorStateException' - - 'IndexOutOfBoundsException' - - 'NullPointerException' - - 'RuntimeException' - - 'Throwable' - allowedExceptionNameRegex: '_|(ignore|expected).*' - TooGenericExceptionThrown: - active: true - exceptionNames: - - 'Error' - - 'Exception' - - 'RuntimeException' - - 'Throwable' - -naming: - active: true - BooleanPropertyNaming: - active: false - allowedPattern: '^(is|has|are)' - ClassNaming: - active: true - classPattern: '[A-Z][a-zA-Z0-9]*' - ConstructorParameterNaming: - active: true - parameterPattern: '[a-z][A-Za-z0-9]*' - privateParameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - EnumNaming: - active: true - enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' - ForbiddenClassName: - active: false - forbiddenName: [] - FunctionMaxLength: - active: false - maximumFunctionNameLength: 30 - FunctionMinLength: - active: false - minimumFunctionNameLength: 3 - FunctionNaming: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - functionPattern: '[a-z][a-zA-Z0-9]*' - excludeClassPattern: '$^' - FunctionParameterNaming: - active: true - parameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - InvalidPackageDeclaration: - active: true - rootPackage: '' - requireRootInDeclaration: false - LambdaParameterNaming: - active: false - parameterPattern: '[a-z][A-Za-z0-9]*|_' - MatchingDeclarationName: - active: true - mustBeFirst: true - MemberNameEqualsClassName: - active: true - ignoreOverridden: true - NoNameShadowing: - active: true - NonBooleanPropertyPrefixedWithIs: - active: false - ObjectPropertyNaming: - active: true - constantPattern: '[A-Za-z][_A-Za-z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' - PackageNaming: - active: true - packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' - TopLevelPropertyNaming: - active: true - constantPattern: '[A-Z][_A-Z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' - VariableMaxLength: - active: false - maximumVariableNameLength: 64 - VariableMinLength: - active: false - minimumVariableNameLength: 1 - VariableNaming: - active: true - variablePattern: '[a-z][A-Za-z0-9]*' - privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - -performance: - active: true - ArrayPrimitive: - active: true - CouldBeSequence: - active: false - threshold: 3 - ForEachOnRange: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - SpreadOperator: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - UnnecessaryPartOfBinaryExpression: - active: false - UnnecessaryTemporaryInstantiation: - active: true - -potential-bugs: - active: true - AvoidReferentialEquality: - active: true - forbiddenTypePatterns: - - 'kotlin.String' - CastNullableToNonNullableType: - active: false - CastToNullableType: - active: false - Deprecation: - active: false - DontDowncastCollectionTypes: - active: false - DoubleMutabilityForCollection: - active: true - mutableTypes: - - 'kotlin.collections.MutableList' - - 'kotlin.collections.MutableMap' - - 'kotlin.collections.MutableSet' - - 'java.util.ArrayList' - - 'java.util.LinkedHashSet' - - 'java.util.HashSet' - - 'java.util.LinkedHashMap' - - 'java.util.HashMap' - ElseCaseInsteadOfExhaustiveWhen: - active: false - ignoredSubjectTypes: [] - EqualsAlwaysReturnsTrueOrFalse: - active: true - EqualsWithHashCodeExist: - active: true - ExitOutsideMain: - active: false - ExplicitGarbageCollectionCall: - active: true - HasPlatformType: - active: true - IgnoredReturnValue: - active: true - restrictToConfig: true - returnValueAnnotations: - - 'CheckResult' - - '*.CheckResult' - - 'CheckReturnValue' - - '*.CheckReturnValue' - ignoreReturnValueAnnotations: - - 'CanIgnoreReturnValue' - - '*.CanIgnoreReturnValue' - returnValueTypes: - - 'kotlin.sequences.Sequence' - - 'kotlinx.coroutines.flow.*Flow' - - 'java.util.stream.*Stream' - ignoreFunctionCall: [] - ImplicitDefaultLocale: - active: true - ImplicitUnitReturnType: - active: false - allowExplicitReturnType: true - InvalidRange: - active: true - IteratorHasNextCallsNextMethod: - active: true - IteratorNotThrowingNoSuchElementException: - active: true - LateinitUsage: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - ignoreOnClassesPattern: '' - MapGetWithNotNullAssertionOperator: - active: true - MissingPackageDeclaration: - active: false - excludes: ['**/*.kts'] - NullCheckOnMutableProperty: - active: false - NullableToStringCall: - active: false - PropertyUsedBeforeDeclaration: - active: false - UnconditionalJumpStatementInLoop: - active: false - UnnecessaryNotNullCheck: - active: false - UnnecessaryNotNullOperator: - active: true - UnnecessarySafeCall: - active: true - UnreachableCatchBlock: - active: true - UnreachableCode: - active: true - UnsafeCallOnNullableType: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - UnsafeCast: - active: true - UnusedUnaryOperator: - active: true - UselessPostfixExpression: - active: true - WrongEqualsTypeParameter: - active: true - -style: - active: true - AlsoCouldBeApply: - active: false - BracesOnIfStatements: - active: false - singleLine: 'never' - multiLine: 'always' - BracesOnWhenStatements: - active: false - singleLine: 'necessary' - multiLine: 'consistent' - CanBeNonNullable: - active: false - CascadingCallWrapping: - active: false - includeElvis: true - ClassOrdering: - active: false - CollapsibleIfStatements: - active: false - DataClassContainsFunctions: - active: false - conversionFunctionPrefix: - - 'to' - allowOperators: false - DataClassShouldBeImmutable: - active: false - DestructuringDeclarationWithTooManyEntries: - active: true - maxDestructuringEntries: 3 - DoubleNegativeLambda: - active: false - negativeFunctions: - - reason: 'Use `takeIf` instead.' - value: 'takeUnless' - - reason: 'Use `all` instead.' - value: 'none' - negativeFunctionNameParts: - - 'not' - - 'non' - EqualsNullCall: - active: true - EqualsOnSignatureLine: - active: false - ExplicitCollectionElementAccessMethod: - active: false - ExplicitItLambdaParameter: - active: true - ExpressionBodySyntax: - active: false - includeLineWrapping: false - ForbiddenAnnotation: - active: false - annotations: - - reason: 'it is a java annotation. Use `Suppress` instead.' - value: 'java.lang.SuppressWarnings' - - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' - value: 'java.lang.Deprecated' - - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' - value: 'java.lang.annotation.Documented' - - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' - value: 'java.lang.annotation.Target' - - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' - value: 'java.lang.annotation.Retention' - - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' - value: 'java.lang.annotation.Repeatable' - - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265' - value: 'java.lang.annotation.Inherited' - ForbiddenComment: - active: true - comments: - - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' - value: 'FIXME:' - - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' - value: 'STOPSHIP:' - - reason: 'Forbidden TODO todo marker in comment, please do the changes.' - value: 'TODO:' - allowedPatterns: '' - ForbiddenImport: - active: false - imports: [] - forbiddenPatterns: '' - ForbiddenMethodCall: - active: false - methods: - - reason: 'print does not allow you to configure the output stream. Use a logger instead.' - value: 'kotlin.io.print' - - reason: 'println does not allow you to configure the output stream. Use a logger instead.' - value: 'kotlin.io.println' - ForbiddenSuppress: - active: false - rules: [] - ForbiddenVoid: - active: true - ignoreOverridden: false - ignoreUsageInGenerics: false - FunctionOnlyReturningConstant: - active: true - ignoreOverridableFunction: true - ignoreActualFunction: true - excludedFunctions: [] - LoopWithTooManyJumpStatements: - active: true - maxJumpCount: 1 - MagicNumber: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts'] - ignoreNumbers: - - '-1' - - '0' - - '1' - - '2' - ignoreHashCodeFunction: true - ignorePropertyDeclaration: false - ignoreLocalVariableDeclaration: false - ignoreConstantDeclaration: true - ignoreCompanionObjectPropertyDeclaration: true - ignoreAnnotation: false - ignoreNamedArgument: true - ignoreEnums: false - ignoreRanges: false - ignoreExtensionFunctions: true - MandatoryBracesLoops: - active: false - MaxChainedCallsOnSameLine: - active: false - maxChainedCalls: 5 - MaxLineLength: - active: true - maxLineLength: 140 - excludePackageStatements: true - excludeImportStatements: true - excludeCommentStatements: false - excludeRawStrings: true - MayBeConst: - active: true - ModifierOrder: - active: true - MultilineLambdaItParameter: - active: false - MultilineRawStringIndentation: - active: false - indentSize: 4 - trimmingMethods: - - 'trimIndent' - - 'trimMargin' - NestedClassesVisibility: - active: true - NewLineAtEndOfFile: - active: true - NoTabs: - active: false - NullableBooleanCheck: - active: false - ObjectLiteralToLambda: - active: true - OptionalAbstractKeyword: - active: true - OptionalUnit: - active: false - OptionalWhenBraces: - active: false - PreferToOverPairSyntax: - active: false - ProtectedMemberInFinalClass: - active: true - RedundantExplicitType: - active: false - RedundantHigherOrderMapUsage: - active: true - RedundantVisibilityModifierRule: - active: false - ReturnCount: - active: true - max: 2 - excludedFunctions: - - 'equals' - excludeLabeled: false - excludeReturnFromLambda: true - excludeGuardClauses: false - SafeCast: - active: true - SerialVersionUIDInSerializableClass: - active: true - SpacingBetweenPackageAndImports: - active: false - StringShouldBeRawString: - active: false - maxEscapedCharacterCount: 2 - ignoredCharacters: [] - ThrowsCount: - active: true - max: 2 - excludeGuardClauses: false - TrailingWhitespace: - active: false - TrimMultilineRawString: - active: false - trimmingMethods: - - 'trimIndent' - - 'trimMargin' - UnderscoresInNumericLiterals: - active: false - acceptableLength: 4 - allowNonStandardGrouping: false - UnnecessaryAbstractClass: - active: true - UnnecessaryAnnotationUseSiteTarget: - active: false - UnnecessaryApply: - active: true - UnnecessaryBackticks: - active: false - UnnecessaryBracesAroundTrailingLambda: - active: false - UnnecessaryFilter: - active: true - UnnecessaryInheritance: - active: true - UnnecessaryInnerClass: - active: false - UnnecessaryLet: - active: false - UnnecessaryParentheses: - active: false - allowForUnclearPrecedence: false - UntilInsteadOfRangeTo: - active: false - UnusedImports: - active: false - UnusedParameter: - active: true - allowedNames: 'ignored|expected' - UnusedPrivateClass: - active: true - UnusedPrivateMember: - active: true - allowedNames: '' - UnusedPrivateProperty: - active: true - allowedNames: '_|ignored|expected|serialVersionUID' - UseAnyOrNoneInsteadOfFind: - active: true - UseArrayLiteralsInAnnotations: - active: true - UseCheckNotNull: - active: true - UseCheckOrError: - active: true - UseDataClass: - active: false - allowVars: false - UseEmptyCounterpart: - active: false - UseIfEmptyOrIfBlank: - active: false - UseIfInsteadOfWhen: - active: false - ignoreWhenContainingVariableDeclaration: false - UseIsNullOrEmpty: - active: true - UseLet: - active: false - UseOrEmpty: - active: true - UseRequire: - active: false - UseRequireNotNull: - active: false - UseSumOfInsteadOfFlatMapSize: - active: false - UselessCallOnNotNull: - active: true - UtilityClassWithPublicConstructor: - active: true - VarCouldBeVal: - active: true - ignoreLateinitVar: false - WildcardImport: - active: false - excludeImports: - - 'java.util.*' diff --git a/gradle.properties b/gradle.properties index 7fc6f1f..c2f6923 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,4 @@ kotlin.code.style=official + +ktorSwaggerUiGroupId=io.github.smiley4 +ktorSwaggerUiVersion=3-indev diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts new file mode 100644 index 0000000..62cd9c8 --- /dev/null +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + kotlin("jvm") +} + +group = "io.github.smiley4" +version = "3.0.0-indev" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/Main.kt b/ktor-swagger-ui-examples/src/main/kotlin/Main.kt new file mode 100644 index 0000000..550e0dc --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/Main.kt @@ -0,0 +1,6 @@ +package io.github.smiley4 + +fun main() { + println("Hello World!") + val x = mapOf() +} \ No newline at end of file diff --git a/NOTES.txt b/ktor-swagger-ui/NOTES.txt similarity index 100% rename from NOTES.txt rename to ktor-swagger-ui/NOTES.txt diff --git a/ktor-swagger-ui/build.gradle.kts b/ktor-swagger-ui/build.gradle.kts new file mode 100644 index 0000000..520c582 --- /dev/null +++ b/ktor-swagger-ui/build.gradle.kts @@ -0,0 +1,51 @@ +object Meta { + const val artifactId = "ktor-swagger-ui" +} + +dependencies { + val ktorVersion = "2.3.7" + implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") + implementation("io.ktor:ktor-server-webjars:$ktorVersion") + implementation("io.ktor:ktor-server-auth:$ktorVersion") + implementation("io.ktor:ktor-server-resources:$ktorVersion") + testImplementation("io.ktor:ktor-server-netty-jvm:$ktorVersion") + testImplementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") + testImplementation("io.ktor:ktor-serialization-jackson:$ktorVersion") + testImplementation("io.ktor:ktor-server-auth:$ktorVersion") + testImplementation("io.ktor:ktor-server-call-logging:$ktorVersion") + testImplementation("io.ktor:ktor-server-test-host:$ktorVersion") + + val swaggerUiVersion = "5.9.0" // this version must match the version declared in the code (SwaggerPlugin#SWAGGER_UI_WEBJARS_VERSION) + implementation("org.webjars:swagger-ui:$swaggerUiVersion") + + val swaggerParserVersion = "2.1.19" + implementation("io.swagger.parser.v3:swagger-parser:$swaggerParserVersion") + + val schemaKeneratorVersion = "0.1" + implementation("io.github.smiley4:schema-kenerator-core:$schemaKeneratorVersion") + implementation("io.github.smiley4:schema-kenerator-reflection:$schemaKeneratorVersion") + implementation("io.github.smiley4:schema-kenerator-swagger:$schemaKeneratorVersion") + + val jacksonVersion = "2.15.3" + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:${jacksonVersion}") + + val kotlinLoggingVersion = "3.0.5" + implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") + + val logbackVersion = "1.4.11" + testImplementation("ch.qos.logback:logback-classic:$logbackVersion") + + val versionMockk = "1.13.8" + testImplementation("io.mockk:mockk:$versionMockk") + + val versionKotest = "5.8.0" + testImplementation("io.kotest:kotest-runner-junit5:$versionKotest") + testImplementation("io.kotest:kotest-assertions-core:$versionKotest") + + val versionKotlinTest = "1.8.21" + testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest") + + testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + testImplementation("com.github.Ricky12Awesome:json-schema-serialization:0.9.9") + testImplementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.1") +} diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt similarity index 96% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index cc62213..7f15d06 100644 --- a/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -30,8 +30,6 @@ import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextImpl import io.github.smiley4.ktorswaggerui.data.PluginConfigData import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl import io.github.smiley4.ktorswaggerui.routing.ApiSpec -import io.github.smiley4.ktorswaggerui.routing.ForwardRouteController -import io.github.smiley4.ktorswaggerui.routing.SwaggerController import io.ktor.server.application.Application import io.ktor.server.application.ApplicationStarted import io.ktor.server.application.createApplicationPlugin @@ -43,9 +41,6 @@ import io.ktor.server.routing.Routing import io.ktor.server.webjars.Webjars import io.swagger.v3.core.util.Json import mu.KotlinLogging -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.set /** * This version must match the version of the gradle dependency diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteMeta.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthScheme.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthScheme.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthScheme.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthScheme.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/DataUtils.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/DataUtils.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/DataUtils.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/DataUtils.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigned.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigned.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigned.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigned.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/WhenBuildOpenApiSpecs.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/WhenBuildOpenApiSpecs.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/data/WhenBuildOpenApiSpecs.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/WhenBuildOpenApiSpecs.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiDslMarker.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiDslMarker.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiDslMarker.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/OpenApiDslMarker.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiContact.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiContact.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiContact.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiContact.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlows.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlows.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlows.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlows.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponses.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponses.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponses.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponses.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/DocumentedRouteSelector.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/DocumentedRouteSelector.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/DocumentedRouteSelector.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/DocumentedRouteSelector.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/resources/DocumentedResourceRoutes.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/resources/DocumentedResourceRoutes.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/resources/DocumentedResourceRoutes.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routing/resources/DocumentedResourceRoutes.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ResourceContent.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ResourceContent.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ResourceContent.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ResourceContent.kt diff --git a/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt similarity index 100% rename from src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt diff --git a/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt similarity index 100% rename from src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt rename to ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt diff --git a/src/test/resources/logback.xml b/ktor-swagger-ui/src/test/resources/logback.xml similarity index 100% rename from src/test/resources/logback.xml rename to ktor-swagger-ui/src/test/resources/logback.xml diff --git a/settings.gradle.kts b/settings.gradle.kts index d7fed3a..5a7009c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,11 @@ rootProject.name = "ktor-swagger-ui" + +include("ktor-swagger-ui-examples") +include("ktor-swagger-ui") + +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} From 3ad928e772f847136eece883d74e52164c75f4f4 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Fri, 3 May 2024 19:37:47 +0200 Subject: [PATCH 03/32] wip: gradle multimodules --- ktor-swagger-ui-examples/build.gradle.kts | 19 ++++-- .../src/main/kotlin/Main.kt | 6 -- .../ktorswaggerui/examples/HelloWorld.kt | 61 +++++++++++++++++++ .../smiley4/ktorswaggerui/examples/todo.txt | 23 +++++++ .../src/main/resources/logback.xml | 18 ++++++ .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 16 ----- .../ktorswaggerui/data/SwaggerUIData.kt | 10 --- .../ktorswaggerui/dsl/config/SwaggerUIDsl.kt | 35 ----------- 8 files changed, 116 insertions(+), 72 deletions(-) delete mode 100644 ktor-swagger-ui-examples/src/main/kotlin/Main.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/HelloWorld.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/todo.txt create mode 100644 ktor-swagger-ui-examples/src/main/resources/logback.xml diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts index 62cd9c8..70661af 100644 --- a/ktor-swagger-ui-examples/build.gradle.kts +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -10,9 +10,18 @@ repositories { } dependencies { - testImplementation(kotlin("test")) -} -tasks.test { - useJUnitPlatform() -} \ No newline at end of file + implementation(project(":ktor-swagger-ui")) + + val ktorVersion = "2.3.7" + implementation("io.ktor:ktor-server-netty-jvm:$ktorVersion") + implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-jackson:$ktorVersion") + implementation("io.ktor:ktor-server-auth:$ktorVersion") + implementation("io.ktor:ktor-server-call-logging:$ktorVersion") + implementation("io.ktor:ktor-server-test-host:$ktorVersion") + + val logbackVersion = "1.4.11" + testImplementation("ch.qos.logback:logback-classic:$logbackVersion") + +} diff --git a/ktor-swagger-ui-examples/src/main/kotlin/Main.kt b/ktor-swagger-ui-examples/src/main/kotlin/Main.kt deleted file mode 100644 index 550e0dc..0000000 --- a/ktor-swagger-ui-examples/src/main/kotlin/Main.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.smiley4 - -fun main() { - println("Hello World!") - val x = mapOf() -} \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/HelloWorld.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/HelloWorld.kt new file mode 100644 index 0000000..8e245a4 --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/HelloWorld.kt @@ -0,0 +1,61 @@ +package io.github.smiley4.ktorswaggerui.examples + +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.dsl.routing.get +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.response.respondText +import io.ktor.server.routing.route +import io.ktor.server.routing.routing + +fun main() { + embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) +} + + +/** + * A "minimal" working example + */ +private fun Application.myModule() { + + // Install the "SwaggerUI"-Plugin and use the default configuration + install(SwaggerUI) + + routing { + + // Create a route for the swagger-ui pointing the to the openapi-spec at "api.json. + // This route will not be included in the spec. + route("swagger") { + swaggerUI("/api.json") + } + // Create a route for the openapi-spec file. + // This route will not be included in the spec. + route("api.json") { + openApiSpec() + } + + // a documented route + get("hello", { + // description of the route + description = "A Hello-World route" + // information about possible responses + response { + // information about a "200 OK" response + HttpStatusCode.OK to { + // a description of the response + description = "successful request - always returns 'Hello World!'" + } + } + }) { + call.respondText("Hello World!") + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/todo.txt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/todo.txt new file mode 100644 index 0000000..bfee06a --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/todo.txt @@ -0,0 +1,23 @@ +- hello world + - basic config + - manual routing + - simple route + +- authenticated routes + +- request/response bodies + - basic documentation + - body-types/-schemas + - examples + +- advanced schemas + - customizing schema generation + - custom schemas + +- multiple specs + +- resource-plugin + +- "real world" examples + - petshop, using more features + - file download \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/resources/logback.xml b/ktor-swagger-ui-examples/src/main/resources/logback.xml new file mode 100644 index 0000000..52ce2ed --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/resources/logback.xml @@ -0,0 +1,18 @@ + + + + + + %cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) | %highlight(%-5.5level{5}) | %gray(%-16.16thread{16}) | %magenta(%-25.25logger{25}) | %m%n + + + + + + + + + + + + \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index 7f15d06..a2c9546 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -66,22 +66,6 @@ val SwaggerUI = createApplicationPlugin(name = "SwaggerUI", createConfiguration logger.error("Error during application startup in swagger-ui-plugin", e) } - if (config.swaggerUI.automaticRouter) { - ApiSpec.getAll().forEach { (specId, json) -> - val specConfig = config.specConfigs[specId] ?: config - SwaggerController( - applicationConfig!!, - specConfig, - SWAGGER_UI_WEBJARS_VERSION, - if (ApiSpec.getAll().size > 1) specId else null, - json - ).setup(application) - if (ApiSpec.getAll().size == 1 && config.swaggerUI.forwardRoot) { - ForwardRouteController(applicationConfig!!, config).setup(application) - } - } - } - } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt index 7bb2b67..cf306c8 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt @@ -1,11 +1,6 @@ package io.github.smiley4.ktorswaggerui.data data class SwaggerUIData( - val automaticRouter: Boolean, - val forwardRoot: Boolean, - val swaggerUrl: String, - val rootHostPath: String, - val authentication: String?, val validatorUrl: String?, val displayOperationId: Boolean, val showTagFilterInput: Boolean, @@ -16,11 +11,6 @@ data class SwaggerUIData( companion object { val DEFAULT = SwaggerUIData( - automaticRouter = true, - forwardRoot = false, - swaggerUrl = "swagger-ui", - rootHostPath = "", - authentication = null, validatorUrl = null, displayOperationId = false, showTagFilterInput = false, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt index 5ab658e..c61cbdc 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt @@ -10,36 +10,6 @@ import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @OpenApiDslMarker class SwaggerUIDsl { - /** - * Whether to use the automatic swagger-ui router or create swagger-ui router manually. - * 'false' results in [forwardRoot], [swaggerUrl], [rootHostPath], [authentication] being ignored. - */ - var automaticRouter: Boolean = SwaggerUIData.DEFAULT.automaticRouter - - /** - * Whether to forward the root-url to the swagger-url - */ - var forwardRoot: Boolean = SwaggerUIData.DEFAULT.forwardRoot - - - /** - * the url to the swagger-ui - */ - var swaggerUrl: String = SwaggerUIData.DEFAULT.swaggerUrl - - - /** - * the path under which the KTOR app gets deployed. can be useful if reverse proxy is in use. - */ - var rootHostPath: String = SwaggerUIData.DEFAULT.rootHostPath - - - /** - * The name of the authentication to use for the swagger routes. Null to not protect the swagger-ui. - */ - var authentication: String? = SwaggerUIData.DEFAULT.authentication - - /** * Swagger UI can attempt to validate specs against swagger.io's online validator. * You can use this parameter to set a different validator URL, for example for locally deployed validators. @@ -94,11 +64,6 @@ class SwaggerUIDsl { internal fun build(base: SwaggerUIData): SwaggerUIData { return SwaggerUIData( - automaticRouter = automaticRouter, - forwardRoot = mergeBoolean(base.forwardRoot, this.forwardRoot), - swaggerUrl = mergeDefault(base.swaggerUrl, this.swaggerUrl, SwaggerUIData.DEFAULT.swaggerUrl), - rootHostPath = mergeDefault(base.rootHostPath, this.rootHostPath, SwaggerUIData.DEFAULT.rootHostPath), - authentication = merge(base.authentication, this.authentication), validatorUrl = merge(base.validatorUrl, this.validatorUrl), displayOperationId = mergeBoolean(base.displayOperationId, this.displayOperationId), showTagFilterInput = mergeBoolean(base.showTagFilterInput, this.showTagFilterInput), From 4dfd7f3f7b23c150ef9514963b898161f301632e Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sat, 4 May 2024 19:23:07 +0200 Subject: [PATCH 04/32] samples --- ktor-swagger-ui-examples/build.gradle.kts | 7 +- .../ktorswaggerui/examples/Authentication.kt | 102 +++++++ .../smiley4/ktorswaggerui/examples/Basics.kt | 86 ++++++ .../ktorswaggerui/examples/CustomSchemas.kt | 3 + .../examples/CustomizeSchemaGeneration.kt | 3 + .../ktorswaggerui/examples/FileUpload.kt | 69 +++++ .../examples/{HelloWorld.kt => Minimal.kt} | 19 +- .../ktorswaggerui/examples/MultipleSpecs.kt | 109 ++++++++ .../ktorswaggerui/examples/Petstore.kt | 256 ++++++++++++++++++ .../ktorswaggerui/examples/RequestResponse.kt | 144 ++++++++++ .../smiley4/ktorswaggerui/examples/todo.txt | 23 -- ktor-swagger-ui/build.gradle.kts | 9 - .../builder/route/RouteDocumentationMerger.kt | 1 - .../builder/schema/SchemaContextImpl.kt | 3 + .../dsl/routes/OpenApiRequest.kt | 13 +- .../ktorswaggerui/dsl/routes/OpenApiRoute.kt | 19 +- .../smiley4/ktorswaggerui/routing/routing.kt | 1 + 17 files changed, 794 insertions(+), 73 deletions(-) create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Basics.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemas.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt rename ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/{HelloWorld.kt => Minimal.kt} (70%) create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipleSpecs.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt delete mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/todo.txt diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts index 70661af..4ba5d63 100644 --- a/ktor-swagger-ui-examples/build.gradle.kts +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -21,7 +21,12 @@ dependencies { implementation("io.ktor:ktor-server-call-logging:$ktorVersion") implementation("io.ktor:ktor-server-test-host:$ktorVersion") + val kotlinLoggingVersion = "3.0.5" + implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") + val logbackVersion = "1.4.11" - testImplementation("ch.qos.logback:logback-classic:$logbackVersion") + implementation("ch.qos.logback:logback-classic:$logbackVersion") + + } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt new file mode 100644 index 0000000..9c0862e --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt @@ -0,0 +1,102 @@ +package io.github.smiley4.ktorswaggerui.examples + +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.data.AuthScheme +import io.github.smiley4.ktorswaggerui.data.AuthType +import io.github.smiley4.ktorswaggerui.dsl.routing.get +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.auth.Authentication +import io.ktor.server.auth.UserIdPrincipal +import io.ktor.server.auth.authenticate +import io.ktor.server.auth.basic +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.response.respondText +import io.ktor.server.routing.route +import io.ktor.server.routing.routing + +fun main() { + embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) +} + +private fun Application.myModule() { + + // Install "Authentication"-Plugin and setup e.g. Basic-Auth + // username = "user", password = "pass" + install(Authentication) { + basic { + realm = "Access to the API" + validate { credentials -> + if (credentials.name == "user" && credentials.password == "pass") { + UserIdPrincipal(credentials.name) + } else { + null + } + } + } + } + + // Install and configure the "SwaggerUI"-Plugin + install(SwaggerUI) { + // configure a basic-auth security scheme + securityScheme("MySecurityScheme") { + type = AuthType.HTTP + scheme = AuthScheme.BASIC + } + // if no other security scheme is specified for a route, the one with this name is used instead + defaultSecuritySchemeName = "MySecurityScheme" + // if no other response is documented for "401 Unauthorized", this information is used instead + defaultUnauthorizedResponse { + description = "Username or password is invalid" + } + } + + routing { + + // Create a route for the swagger-ui using the openapi-spec at "/api.json". + // This route will not be included in the spec. + route("swagger") { + swaggerUI("/api.json") + } + // Create a route for the openapi-spec file. + // This route will not be included in the spec. + route("api.json") { + openApiSpec() + } + + authenticate { + // route is in an "authenticate"-block -> default security-scheme will be used (if not specified otherwise) + get("protected", { + // response for "401 Unauthorized" is automatically added if configured in the plugin-config and not specified otherwise + }) { + call.respondText("Hello World!") + } + } + + // route is not in an "authenticate"-block but "protected"-flag is set (e.g. because is it protected by an external reverse-proxy + // -> specified or default security scheme is used and default "401 Unauthorized" is added if not specified otherwise + get("externally-protected", { + // manually specify that this route requires authentication + protected = true + }) { + call.respondText("Hello World!") + } + + // route is not in an "authenticate"-block and "protected"-flag is not set + // -> security schemes will be ignored and not default "401 Unauthorized" response is added + get("unprotected", { + securitySchemeNames = listOf("MySecurityScheme") + }) { + call.respondText("Hello World!") + } + + + + + } + +} diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Basics.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Basics.kt new file mode 100644 index 0000000..b0d1ced --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Basics.kt @@ -0,0 +1,86 @@ +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 +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +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) +} + +private fun Application.myModule() { + + // Install and configure the "SwaggerUI"-Plugin + install(SwaggerUI) { + // configure basic information about the api + info { + title = "Example API" + description = "An example api to showcase basic swagger-ui functionality." + } + // provide a reference to an external documentation + externalDocs { + url = "https://github.com/SMILEY4/ktor-swagger-ui/wiki" + description = "Sample external documentation" + } + // configure the servers from where the api is being served + server { + url = "http://localhost:8080" + description = "Development Server" + } + server { + url = "https://www.example.com" + description = "Production Server" + } + } + + routing { + + // Create a route for the swagger-ui using the openapi-spec at "/api.json". + // This route will not be included in the spec. + route("swagger") { + swaggerUI("/api.json") + } + // Create a route for the openapi-spec file. + // This route will not be included in the spec. + route("api.json") { + openApiSpec() + } + + // a documented route + get("hello", { + // description of the route + description = "A Hello-World route" + // information about the request + request { + // information about the query-parameter "name" of type "string" + queryParameter("name", KTypeDescriptor(typeOf())) { + description = "the name to greet" + } + } + // information about possible responses + response { + // information about a "200 OK" response + HttpStatusCode.OK to { + // a description of the response + description = "successful request - always returns 'Hello World!'" + } + } + }) { + call.respondText("Hello ${call.request.queryParameters["name"]}") + } + + } + +} diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemas.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemas.kt new file mode 100644 index 0000000..862928a --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemas.kt @@ -0,0 +1,3 @@ +package io.github.smiley4.ktorswaggerui.examples + +// TODO \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt new file mode 100644 index 0000000..862928a --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt @@ -0,0 +1,3 @@ +package io.github.smiley4.ktorswaggerui.examples + +// TODO \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt new file mode 100644 index 0000000..3a64cc8 --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt @@ -0,0 +1,69 @@ +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.post +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.response.respond +import io.ktor.server.routing.routing +import java.io.File +import kotlin.reflect.typeOf + +fun main() { + embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) +} + +private fun Application.myModule() { + + // Install the "SwaggerUI"-Plugin and use the default configuration + install(SwaggerUI) + + routing { + + // upload a single file, either as png, jpeg or svg + post("single", { + request { + body(KTypeDescriptor(typeOf())) { // todo: type overwrite for "File" + mediaType(ContentType.Image.PNG) + mediaType(ContentType.Image.JPEG) + mediaType(ContentType.Image.SVG) + } + } + }) { + call.respond(HttpStatusCode.NotImplemented, "...") + } + + // upload multiple files + post("multipart", { + request { + multipartBody { + mediaType(ContentType.MultiPart.FormData) + part("first-image", KTypeDescriptor(typeOf())) { + mediaTypes = setOf( + ContentType.Image.PNG, // todo: why setOf here and not for single body? + ContentType.Image.JPEG, + ContentType.Image.SVG + ) + } + part("second-image", KTypeDescriptor(typeOf())) { + mediaTypes = setOf( + ContentType.Image.PNG, + ContentType.Image.JPEG, + ContentType.Image.SVG + ) + } + } + } + }) { + call.respond(HttpStatusCode.NotImplemented, "...") + } + + } + +} diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/HelloWorld.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Minimal.kt similarity index 70% rename from ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/HelloWorld.kt rename to ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Minimal.kt index 8e245a4..81d105e 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/HelloWorld.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Minimal.kt @@ -4,7 +4,6 @@ import io.github.smiley4.ktorswaggerui.SwaggerUI import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.routing.openApiSpec import io.github.smiley4.ktorswaggerui.routing.swaggerUI -import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application import io.ktor.server.application.call import io.ktor.server.application.install @@ -18,10 +17,6 @@ fun main() { embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) } - -/** - * A "minimal" working example - */ private fun Application.myModule() { // Install the "SwaggerUI"-Plugin and use the default configuration @@ -29,7 +24,7 @@ private fun Application.myModule() { routing { - // Create a route for the swagger-ui pointing the to the openapi-spec at "api.json. + // Create a route for the swagger-ui using the openapi-spec at "/api.json". // This route will not be included in the spec. route("swagger") { swaggerUI("/api.json") @@ -42,20 +37,12 @@ private fun Application.myModule() { // a documented route get("hello", { - // description of the route + // description of this route description = "A Hello-World route" - // information about possible responses - response { - // information about a "200 OK" response - HttpStatusCode.OK to { - // a description of the response - description = "successful request - always returns 'Hello World!'" - } - } }) { call.respondText("Hello World!") } } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipleSpecs.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipleSpecs.kt new file mode 100644 index 0000000..00d0bc9 --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/MultipleSpecs.kt @@ -0,0 +1,109 @@ +package io.github.smiley4.ktorswaggerui.examples + +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.dsl.routing.get +import io.github.smiley4.ktorswaggerui.dsl.routing.route +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.response.respondText +import io.ktor.server.routing.route +import io.ktor.server.routing.routing + +fun main() { + embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) +} + +private fun Application.myModule() { + + // Install and configure the "SwaggerUI"-Plugin + install(SwaggerUI) { + // "global" configuration for all specs + info { + title = "Example API" + } + // configuration specific for spec "v1", overwrites global config + spec("version1") { + info { + version = "1.0" + } + } + // configuration specific for spec "v2", overwrites global config + spec("version2") { + info { + version = "2.0" + } + } + // assign all unassigned routes to spec "v2" (here only route '/greet') + specAssigner = {_, _ -> "version2"} + } + + routing { + + // add routes for "v1"-spec and swagger-ui + route("v1") { + route("swagger") { + // swagger-ui using '/v1/api.json' + swaggerUI("/v1/api.json") + } + route("api.json") { + // api-spec containing all routes assigned to "v1" + openApiSpec("version1") + } + } + + // add routes for "v2"-spec and swagger-ui + route("v2") { + route("swagger") { + // swagger-ui using '/v2/api.json' + swaggerUI("/v2/api.json") + } + route("api.json") { + // api-spec containing all routes assigned to "v2" + openApiSpec("version2") + } + } + + + // version 1.0 routes + route("v1", { + specId = "version1" + }) { + + // "hello"-route in version 1.0 + get("hello", { + description = "Version 1 'Hello World'" + }) { + call.respondText("Hello World!") + } + + } + + // version 2.0 routes + route("v2", { + specId = "version2" + }) { + + // "hello"-route in version 2.0 + get("hello", { + description = "Version 2 'Hello World'" + }) { + call.respondText("Hello World! (improved)") + } + + } + + // unassigned route + get("greet", { + description = "Alternative route not manually assigned to any spec." + }) { + call.respondText("Alternative Hello World!") + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt new file mode 100644 index 0000000..8552fc8 --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt @@ -0,0 +1,256 @@ +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.delete +import io.github.smiley4.ktorswaggerui.dsl.routing.get +import io.github.smiley4.ktorswaggerui.dsl.routing.post +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.response.respond +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) +} + + +/** + * Uses the OpenApi-Example "petstore-simple" to demonstrate ktor with swagger-ui + * https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v2.0/json/petstore-simple.json + */ +private fun Application.myModule() { + + install(SwaggerUI) { + info { + title = "Swagger Petstore" + version = "1.0.0" + description = "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification" + termsOfService = "http://swagger.io/terms/" + contact { + name = "Swagger API Team" + } + license { + name = "MIT" + } + } + } + + routing { + + route("swagger") { + swaggerUI("/api.json") + } + route("api.json") { + openApiSpec() + } + + route("/pets") { + + get({ + operationId = "findPets" + description = "Returns all pets from the system that the user has access to" + request { + queryParameter("tags", KTypeDescriptor(typeOf>())) { + description = "tags to filter by" + required = false + example = "dog" + } + queryParameter("limit", KTypeDescriptor(typeOf())) { + description = "maximum number of results to return" + required = false + example = 100 + } + } + response { + HttpStatusCode.OK to { + body(KTypeDescriptor(typeOf>())) { + description = "the list of available pets" + example( + "Pet List", listOf( + Pet( + id = 123, + name = "Big Bird", + tag = "bird" + ), + Pet( + id = 456, + name = "Charlie", + tag = "dog" + ) + ) + ) + } + } + default { + body(KTypeDescriptor(typeOf())) { + description = "unexpected error" + example("Error", ErrorModel("unexpected error")) + } + } + } + }) { + call.respond(HttpStatusCode.NotImplemented, Unit) + } + + post({ + operationId = "addPet" + description = "Creates a new pet in the store. Duplicates are allowed" + request { + body(KTypeDescriptor(typeOf())) { + description = "Pet to add to the store" + required = true + example( + "New Bird", + NewPet( + name = "Big Bird", + tag = "bird" + ) + ) + example( + "New Dog", + NewPet( + name = "Charlie", + tag = "dog" + ) + ) + } + } + response { + HttpStatusCode.OK to { + body(KTypeDescriptor(typeOf())) { + description = "the created pet" + example( + "Bird", + Pet( + id = 123, + name = "Big Bird", + tag = "bird" + ) + ) + example( + "Dog", + Pet( + id = 456, + name = "Charlie", + tag = "dog" + ) + ) + } + } + default { + body(KTypeDescriptor(typeOf())) { + description = "unexpected error" + example("Error", ErrorModel("unexpected error")) + } + } + } + }) { + call.respond(HttpStatusCode.NotImplemented, Unit) + } + + route("{id}") { + + get({ + operationId = "findBetById" + description = "Returns a pet based on a single ID." + request { + pathParameter("id", KTypeDescriptor(typeOf())) { + description = "Id of pet to fetch" + required = true + example = 123 + } + } + response { + HttpStatusCode.OK to { + body(KTypeDescriptor(typeOf())) { + description = "the pet with the given id" + example( + "Bird", + Pet( + id = 123, + name = "Big Bird", + tag = "bird" + ) + ) + example( + "Dog", + Pet( + id = 123, + name = "Charlie", + tag = "dog" + ) + ) + } + } + HttpStatusCode.NotFound to { + description = "the pet with the given id was not found" + } + default { + body(KTypeDescriptor(typeOf())) { + description = "unexpected error" + example("Error", ErrorModel("unexpected error")) + } + } + } + }) { + call.respond(HttpStatusCode.NotImplemented, Unit) + } + + delete({ + operationId = "deletePet" + description = "deletes a single pet based on the supplied ID" + request { + pathParameter("id", KTypeDescriptor(typeOf())) { + description = "Id of pet to delete" + required = true + example = 123 + } + } + response { + HttpStatusCode.NoContent to { + description = "the pet was successfully deleted" + } + HttpStatusCode.NotFound to { + description = "the pet with the given id was not found" + } + default { + body(KTypeDescriptor(typeOf())) { + description = "unexpected error" + example("Error", ErrorModel("unexpected error")) + } + } + } + }) { + call.respond(HttpStatusCode.NotImplemented, Unit) + } + + } + } + + } + +} + +private data class Pet( + val id: Long, + val name: String, + val tag: String +) + +private data class NewPet( + val name: String, + val tag: String +) + +private data class ErrorModel( + val message: String +) \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt new file mode 100644 index 0000000..9d1fd47 --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt @@ -0,0 +1,144 @@ +package io.github.smiley4.ktorswaggerui.examples + +import com.fasterxml.jackson.core.util.DefaultIndenter +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter +import com.fasterxml.jackson.databind.SerializationFeature +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.dsl.routing.post +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.jackson.jackson +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.request.receive +import io.ktor.server.response.respond +import io.ktor.server.routing.post +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) +} + +private fun Application.myModule() { + + // Install the "SwaggerUI"-Plugin and use the default configuration + install(SwaggerUI) + + install(ContentNegotiation) { + jackson { + configure(SerializationFeature.INDENT_OUTPUT, true) + setDefaultPrettyPrinter(DefaultPrettyPrinter().apply { + indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance) + indentObjectsWith(DefaultIndenter(" ", "\n")) + }) + } + } + + routing { + + // Create a route for the swagger-ui using the openapi-spec at "/api.json". + // This route will not be included in the spec. + route("swagger") { + swaggerUI("/api.json") + } + // Create a route for the openapi-spec file. + // This route will not be included in the spec. + route("api.json") { + openApiSpec() + } + + // a documented route + post("calculate", { + // information about the request + request { + // specify the schema of the request-body and some additional information + body(KTypeDescriptor(typeOf())) { + description = "the requested operation and values to perform the operation on" + required = true + } + } + // information the possible responses + response { + // document the "200 OK"-response + HttpStatusCode.OK to { + description = "Calculation was performed successfully." + // specify the schema of the response-body and some additional information + body(KTypeDescriptor(typeOf())) { + description = "the result of an operation together with the original request" + } + } + // document the "422 UnprocessableEntity"-response + HttpStatusCode.UnprocessableEntity to { + description = "The requested calculation could not be performed, e.g. due to division by zero." + } + } + }) { + call.receive().let { calculation -> + when (calculation.operation) { + OperationType.ADD -> { + call.respond( + HttpStatusCode.OK, CalculationResult( + calculation = calculation, + result = calculation.a + calculation.b + ) + ) + } + OperationType.SUB -> { + call.respond( + HttpStatusCode.OK, CalculationResult( + calculation = calculation, + result = calculation.a - calculation.b + ) + ) + } + OperationType.MUL -> { + call.respond( + HttpStatusCode.OK, CalculationResult( + calculation = calculation, + result = calculation.a * calculation.b + ) + ) + } + OperationType.DIV -> { + if (calculation.b == 0f) { + call.respond(HttpStatusCode.UnprocessableEntity) + } else { + call.respond( + HttpStatusCode.OK, CalculationResult( + calculation = calculation, + result = calculation.a / calculation.b + ) + ) + } + } + } + } + } + + } + +} + +private enum class OperationType { + ADD, SUB, MUL, DIV +} + +private data class Calculation( + val operation: OperationType, + val a: Float, + val b: Float +) + +private data class CalculationResult( + val calculation: Calculation, + val result: Float +) \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/todo.txt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/todo.txt deleted file mode 100644 index bfee06a..0000000 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/todo.txt +++ /dev/null @@ -1,23 +0,0 @@ -- hello world - - basic config - - manual routing - - simple route - -- authenticated routes - -- request/response bodies - - basic documentation - - body-types/-schemas - - examples - -- advanced schemas - - customizing schema generation - - custom schemas - -- multiple specs - -- resource-plugin - -- "real world" examples - - petshop, using more features - - file download \ No newline at end of file diff --git a/ktor-swagger-ui/build.gradle.kts b/ktor-swagger-ui/build.gradle.kts index 520c582..4bde684 100644 --- a/ktor-swagger-ui/build.gradle.kts +++ b/ktor-swagger-ui/build.gradle.kts @@ -26,15 +26,9 @@ dependencies { implementation("io.github.smiley4:schema-kenerator-reflection:$schemaKeneratorVersion") implementation("io.github.smiley4:schema-kenerator-swagger:$schemaKeneratorVersion") - val jacksonVersion = "2.15.3" - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:${jacksonVersion}") - val kotlinLoggingVersion = "3.0.5" implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") - val logbackVersion = "1.4.11" - testImplementation("ch.qos.logback:logback-classic:$logbackVersion") - val versionMockk = "1.13.8" testImplementation("io.mockk:mockk:$versionMockk") @@ -45,7 +39,4 @@ dependencies { val versionKotlinTest = "1.8.21" testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest") - testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") - testImplementation("com.github.Ricky12Awesome:json-schema-serialization:0.9.9") - testImplementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.1") } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt index 0fd0475..046baaa 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt @@ -17,7 +17,6 @@ class RouteDocumentationMerger { summary = a.summary ?: b.summary description = a.description ?: b.description operationId = a.operationId ?: b.operationId - securitySchemeName = a.securitySchemeName ?: b.securitySchemeName securitySchemeNames = mutableSetOf().also { merged -> a.securitySchemeNames?.let { merged.addAll(it) } b.securitySchemeNames?.let { merged.addAll(it) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt index 9a77628..220dd35 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt @@ -17,12 +17,14 @@ import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema import io.github.smiley4.schemakenerator.swagger.steps.SwaggerSchemaUtils import io.github.smiley4.schemakenerator.swagger.withAutoTitle import io.swagger.v3.oas.models.media.Schema +import mu.KotlinLogging import kotlin.reflect.KType class SchemaContextImpl : SchemaContext { private val rootSchemas = mutableMapOf>() private val componentSchemas = mutableMapOf>() + private val logger = KotlinLogging.logger {} fun add(routes: Collection) { @@ -75,6 +77,7 @@ class SchemaContextImpl : SchemaContext { } private fun generateSchema(type: KType): CompiledSwaggerSchema { + logger.debug { "Generating schema for type $type" } return listOf(type) .processReflection() .generateSwaggerSchema() diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt index 6bbb81a..d74f1c9 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt @@ -6,6 +6,7 @@ import io.github.smiley4.ktorswaggerui.data.OpenApiRequestData import io.github.smiley4.ktorswaggerui.data.ParameterLocation import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import kotlin.reflect.KClass import kotlin.reflect.KType @@ -28,12 +29,12 @@ class OpenApiRequest { } -// /** -// * A path parameters that is applicable for this operation -// */ -// fun pathParameter(name: String, type: KClass<*>, block: OpenApiRequestParameter.() -> Unit) = -// parameter(ParameterLocation.PATH, name, type.asSchemaType(), block) -// + /** + * A path parameters that is applicable for this operation + */ + fun pathParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit) = + parameter(ParameterLocation.PATH, name, type, block) + // // /** // * A path parameters that is applicable for this operation diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt index dc273a3..90fa050 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt @@ -49,17 +49,9 @@ class OpenApiRoute { var hidden: Boolean = false - /** - * A declaration of which security mechanism can be used for this operation. - * If not specified (and none specified with [securitySchemeNames]), defaultSecuritySchemeName (global plugin config) will be used. - * Only applied to [protected] operations. - */ - var securitySchemeName: String? = null - - /** * A declaration of which security mechanisms can be used for this operation (i.e. any of the specified ones). - * If none specified (and none with [securitySchemeName]), defaultSecuritySchemeName (global plugin config) will be used. + * If none is specified, defaultSecuritySchemeName (global plugin config) will be used. * Only applied to [protected] operations. */ var securitySchemeNames: Collection? = null @@ -105,14 +97,7 @@ class OpenApiRoute { operationId = operationId, deprecated = deprecated, hidden = hidden, - securitySchemeNames = buildList { - if(securitySchemeNames != null) { - addAll(securitySchemeNames!!) - } - if(securitySchemeName != null) { - add(securitySchemeName!!) - } - }, + securitySchemeNames = securitySchemeNames?.toList() ?: emptyList(), protected = protected, request = request.build(), responses = responses.getResponses().map { it.build() }, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt index 5363d48..a2dc789 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt @@ -11,6 +11,7 @@ import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +import mu.KotlinLogging fun Route.openApiSpec(specId: String = PluginConfigDsl.DEFAULT_SPEC_ID) { From f571a4122e8500e86482b60d74632b9d22551ae3 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 5 May 2024 23:52:30 +0200 Subject: [PATCH 05/32] wip --- .../ktorswaggerui/examples/Petstore.kt | 114 ++++++++++-------- .../ktorswaggerui/examples/RequestResponse.kt | 2 - ktor-swagger-ui/NOTES.txt | 4 +- .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 10 +- .../builder/example/ExampleContext.kt | 35 ++---- .../builder/example/ExampleContextBuilder.kt | 80 ------------ .../builder/example/ExampleContextImpl.kt | 83 +++++++++++++ .../builder/openapi/ContentBuilder.kt | 43 +------ .../builder/openapi/ExampleBuilder.kt | 23 ---- .../builder/openapi/OpenApiBuilder.kt | 5 +- .../builder/openapi/ParameterBuilder.kt | 4 +- .../builder/schema/SchemaContextImpl.kt | 3 - .../ktorswaggerui/data/ExampleDescriptor.kt | 15 +++ .../ktorswaggerui/data/OpenApiBaseBodyData.kt | 2 +- .../data/OpenApiRequestParameterData.kt | 2 +- .../ktorswaggerui/data/TypeDescriptor.kt | 12 +- .../ktorswaggerui/dsl/config/ExampleConfig.kt | 21 ++++ .../dsl/config/PluginConfigDsl.kt | 12 ++ .../ktorswaggerui/dsl/config/SchemaConfig.kt | 19 +++ .../dsl/routes/OpenApiExample.kt | 34 ------ .../dsl/routes/OpenApiRequest.kt | 3 +- .../dsl/routes/OpenApiRequestParameter.kt | 5 +- .../dsl/routes/OpenApiSimpleBody.kt | 17 +-- 23 files changed, 266 insertions(+), 282 deletions(-) delete mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt delete mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt delete mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt index 8552fc8..4dce146 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt @@ -2,6 +2,8 @@ package io.github.smiley4.ktorswaggerui.examples import io.github.smiley4.ktorswaggerui.SwaggerUI import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor +import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.delete import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.dsl.routing.post @@ -42,6 +44,9 @@ private fun Application.myModule() { name = "MIT" } } + examples { + example(ValueExampleDescriptor("Unexpected Error", ErrorModel("Unexpected Error"), null, null, null)) + } } routing { @@ -62,12 +67,12 @@ private fun Application.myModule() { queryParameter("tags", KTypeDescriptor(typeOf>())) { description = "tags to filter by" required = false - example = "dog" + example = ValueExampleDescriptor("dog", "default", null, null, null) } queryParameter("limit", KTypeDescriptor(typeOf())) { description = "maximum number of results to return" required = false - example = 100 + example = ValueExampleDescriptor("default", 100, null, null, null) } } response { @@ -75,17 +80,20 @@ private fun Application.myModule() { body(KTypeDescriptor(typeOf>())) { description = "the list of available pets" example( - "Pet List", listOf( - Pet( - id = 123, - name = "Big Bird", - tag = "bird" - ), - Pet( - id = 456, - name = "Charlie", - tag = "dog" - ) + ValueExampleDescriptor( + "Pet List", + listOf( + Pet( + id = 123, + name = "Big Bird", + tag = "bird" + ), + Pet( + id = 456, + name = "Charlie", + tag = "dog" + ) + ), null, null, true ) ) } @@ -93,7 +101,7 @@ private fun Application.myModule() { default { body(KTypeDescriptor(typeOf())) { description = "unexpected error" - example("Error", ErrorModel("unexpected error")) + example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) } } } @@ -109,17 +117,21 @@ private fun Application.myModule() { description = "Pet to add to the store" required = true example( - "New Bird", - NewPet( - name = "Big Bird", - tag = "bird" + ValueExampleDescriptor( + "New Bird", + NewPet( + name = "Big Bird", + tag = "bird" + ), null, null, true ) ) example( - "New Dog", - NewPet( - name = "Charlie", - tag = "dog" + ValueExampleDescriptor( + "New Dog", + NewPet( + name = "Charlie", + tag = "dog" + ), null, null, true ) ) } @@ -129,19 +141,23 @@ private fun Application.myModule() { body(KTypeDescriptor(typeOf())) { description = "the created pet" example( - "Bird", - Pet( - id = 123, - name = "Big Bird", - tag = "bird" + ValueExampleDescriptor( + "Bird", + Pet( + id = 123, + name = "Big Bird", + tag = "bird" + ), null, null, true ) ) example( - "Dog", - Pet( - id = 456, - name = "Charlie", - tag = "dog" + ValueExampleDescriptor( + "Dog", + Pet( + id = 456, + name = "Charlie", + tag = "dog" + ), null, null, true ) ) } @@ -149,7 +165,7 @@ private fun Application.myModule() { default { body(KTypeDescriptor(typeOf())) { description = "unexpected error" - example("Error", ErrorModel("unexpected error")) + example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) } } } @@ -166,7 +182,7 @@ private fun Application.myModule() { pathParameter("id", KTypeDescriptor(typeOf())) { description = "Id of pet to fetch" required = true - example = 123 + example = ValueExampleDescriptor("default", 123L, null, null, null) } } response { @@ -174,19 +190,23 @@ private fun Application.myModule() { body(KTypeDescriptor(typeOf())) { description = "the pet with the given id" example( - "Bird", - Pet( - id = 123, - name = "Big Bird", - tag = "bird" + ValueExampleDescriptor( + "Bird", + Pet( + id = 123, + name = "Big Bird", + tag = "bird" + ), null, null, true ) ) example( - "Dog", - Pet( - id = 123, - name = "Charlie", - tag = "dog" + ValueExampleDescriptor( + "Dog", + Pet( + id = 123, + name = "Charlie", + tag = "dog" + ), null, null, true ) ) } @@ -197,7 +217,7 @@ private fun Application.myModule() { default { body(KTypeDescriptor(typeOf())) { description = "unexpected error" - example("Error", ErrorModel("unexpected error")) + example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) } } } @@ -212,7 +232,7 @@ private fun Application.myModule() { pathParameter("id", KTypeDescriptor(typeOf())) { description = "Id of pet to delete" required = true - example = 123 + example = ValueExampleDescriptor("default", 123L, null, null, true) } } response { @@ -225,7 +245,7 @@ private fun Application.myModule() { default { body(KTypeDescriptor(typeOf())) { description = "unexpected error" - example("Error", ErrorModel("unexpected error")) + example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) } } } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt index 9d1fd47..b62bab7 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter import com.fasterxml.jackson.databind.SerializationFeature 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.dsl.routing.post import io.github.smiley4.ktorswaggerui.routing.openApiSpec import io.github.smiley4.ktorswaggerui.routing.swaggerUI @@ -19,7 +18,6 @@ import io.ktor.server.netty.Netty import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.request.receive import io.ktor.server.response.respond -import io.ktor.server.routing.post import io.ktor.server.routing.route import io.ktor.server.routing.routing import kotlin.reflect.typeOf diff --git a/ktor-swagger-ui/NOTES.txt b/ktor-swagger-ui/NOTES.txt index 26fcb49..4302830 100644 --- a/ktor-swagger-ui/NOTES.txt +++ b/ktor-swagger-ui/NOTES.txt @@ -3,4 +3,6 @@ - cleaner handling of custom schemas, etc - cleaner handling of schema generation -> schema-kenerator + nicer config - drop automatic routing -- general cleanup \ No newline at end of file +- general cleanup +- cleaner schema & examples config -> top level "schema", "examples" +- define examples in config and only ref by name \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index a2c9546..b119afa 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -1,5 +1,7 @@ package io.github.smiley4.ktorswaggerui +import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext +import io.github.smiley4.ktorswaggerui.builder.example.ExampleContextImpl import io.github.smiley4.ktorswaggerui.builder.openapi.ComponentsBuilder import io.github.smiley4.ktorswaggerui.builder.openapi.ContactBuilder import io.github.smiley4.ktorswaggerui.builder.openapi.ContentBuilder @@ -87,7 +89,8 @@ private fun buildOpenApiSpecs(config: PluginConfigData, routes: List) private fun buildOpenApiSpec(pluginConfig: PluginConfigData, routes: List): String { return try { val schemaContext = SchemaContextImpl().also { it.add(routes) } - val openApi = builder(pluginConfig, schemaContext).build(routes) + val exampleContext = ExampleContextImpl().also { it.add(routes) } + val openApi = builder(pluginConfig, schemaContext, exampleContext).build(routes) pluginConfig.whenBuildOpenApiSpecs?.invoke(openApi) Json.pretty(openApi) } catch (e: Exception) { @@ -105,10 +108,12 @@ private fun routes(application: Application, config: PluginConfigData): List() -// private val examplesBody = mutableMapOf, Example>() -// -// fun addExample(parameter: OpenApiRequestParameter, value: String) { -// examplesParameters[parameter] = value -// } -// -// fun addExample(body: OpenApiSimpleBody, name: String, value: Example) { -// examplesBody[body to name] = value -// } -// -// fun getComponentsSection(): Map = emptyMap() -// -// fun getExample(parameter: OpenApiRequestParameter): String? = examplesParameters[parameter] -// -// fun getExample(body: OpenApiSimpleBody, name: String): Example? = examplesBody[body to name] -// -//} +package io.github.smiley4.ktorswaggerui.builder.example + +import io.github.smiley4.ktorswaggerui.data.ExampleDescriptor +import io.swagger.v3.oas.models.examples.Example + +interface ExampleContext { + fun getExample(descriptor: ExampleDescriptor): Example + fun getComponentSection(): Map +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt deleted file mode 100644 index c459274..0000000 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextBuilder.kt +++ /dev/null @@ -1,80 +0,0 @@ -//package io.github.smiley4.ktorswaggerui.builder.example -// -//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiBaseBody -//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiExample -//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRequestParameter -//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiResponse -//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiSimpleBody -//import io.github.smiley4.ktorswaggerui.dsl.SchemaType -//import io.github.smiley4.ktorswaggerui.dsl.getSchemaType -//import io.github.smiley4.ktorswaggerui.builder.openapi.ExampleBuilder -//import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta -//import io.github.smiley4.ktorswaggerui.dsl.BodyTypeDescriptor -//import io.github.smiley4.ktorswaggerui.dsl.CollectionBodyTypeDescriptor -//import io.github.smiley4.ktorswaggerui.dsl.CustomRefBodyTypeDescriptor -//import io.github.smiley4.ktorswaggerui.dsl.EmptyBodyTypeDescriptor -//import io.github.smiley4.ktorswaggerui.dsl.OneOfBodyTypeDescriptor -//import io.github.smiley4.ktorswaggerui.dsl.SchemaBodyTypeDescriptor -//import io.swagger.v3.oas.models.examples.Example -// -//class ExampleContextBuilder( -// private val exampleBuilder: ExampleBuilder -//) { -// -// fun build(routes: Collection): ExampleContext { -// return ExampleContext() -// .also { ctx -> routes.forEach { handle(ctx, it) } } -// } -// -// -// private fun handle(ctx: ExampleContext, route: RouteMeta) { -// route.documentation.getRequest().getBody()?.also { handle(ctx, it) } -// route.documentation.getRequest().getParameters().forEach { handle(ctx, it) } -// route.documentation.getResponses().getResponses().forEach { handle(ctx, it) } -// } -// -// private fun handle(ctx: ExampleContext, response: OpenApiResponse) { -// response.getBody()?.also { handle(ctx, it) } -// } -// -// -// private fun handle(ctx: ExampleContext, body: OpenApiBaseBody) { -// return when (body) { -// is OpenApiSimpleBody -> handle(ctx, body) -// else -> Unit -// } -// } -// -// -// private fun handle(ctx: ExampleContext, body: OpenApiSimpleBody) { -// body.getExamples().forEach { (name, value) -> -// val bodyType = getRelevantSchemaType(body.type, getSchemaType()) -// ctx.addExample(body, name, createExample(bodyType, value)) -// } -// } -// -// private fun getRelevantSchemaType(typeDescriptor: BodyTypeDescriptor, fallback: SchemaType): SchemaType { -// return when(typeDescriptor) { -// is EmptyBodyTypeDescriptor -> fallback -// is SchemaBodyTypeDescriptor -> typeDescriptor.schemaType -// is CollectionBodyTypeDescriptor -> getRelevantSchemaType(typeDescriptor.schemaType, fallback) -// is OneOfBodyTypeDescriptor -> fallback -// is CustomRefBodyTypeDescriptor -> fallback -// } -// } -// -// private fun handle(ctx: ExampleContext, parameter: OpenApiRequestParameter) { -// parameter.example?.also { example -> -// ctx.addExample(parameter, createExample(parameter.type, example)) -// } -// } -// -// private fun createExample(type: SchemaType?, value: Any): String { -// return exampleBuilder.buildExampleValue(type, value) -// } -// -// private fun createExample(type: SchemaType?, example: OpenApiExample): Example { -// return exampleBuilder.build(type, example) -// } -// -//} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt new file mode 100644 index 0000000..31d4ccc --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt @@ -0,0 +1,83 @@ +package io.github.smiley4.ktorswaggerui.builder.example + +import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta +import io.github.smiley4.ktorswaggerui.data.ExampleDescriptor +import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData +import io.github.smiley4.ktorswaggerui.data.PluginConfigData +import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor +import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor +import io.swagger.v3.oas.models.examples.Example + +class ExampleContextImpl : ExampleContext { + + private val rootExamples = mutableMapOf() + private val componentExamples = mutableMapOf() + + fun addGlobal(config: PluginConfigData) { + TODO("add global examples from config to components") + } + + fun add(routes: Collection) { + collectExampleDescriptors(routes).forEach { exampleDescriptor -> + val example = generateExample(exampleDescriptor) + if (exampleDescriptor is ValueExampleDescriptor && exampleDescriptor.inComponents == true) { + rootExamples[exampleDescriptor] = Example().also { + it.`$ref` = "#/components/examples/${exampleDescriptor.name}" + } + componentExamples[exampleDescriptor.name] = example + } else { + rootExamples[exampleDescriptor] = example + } + } + } + + private fun collectExampleDescriptors(routes: Collection): List { + val descriptors = mutableListOf() + + 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) + } + } + } + route.documentation.responses.forEach { response -> + response.body?.also { body -> + if (body is OpenApiSimpleBodyData) { + descriptors.addAll(body.examples) + } + } + } + } + + return descriptors + } + + private fun generateExample(exampleDescriptor: ExampleDescriptor): Example { + return when (exampleDescriptor) { + is ValueExampleDescriptor -> Example().also { + it.value = exampleDescriptor.value + it.summary = exampleDescriptor.summary + it.description = exampleDescriptor.description + } + is RefExampleDescriptor -> Example().also { + it.`$ref` = "#/components/examples/${exampleDescriptor.refName}" + } + } + } + + override fun getExample(descriptor: ExampleDescriptor): Example { + return rootExamples[descriptor] ?: throw Exception("no root-example for given example-descriptor") + } + + override fun getComponentSection(): Map { + return componentExamples + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt index c0c6d61..64ea8bd 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt @@ -1,5 +1,6 @@ package io.github.smiley4.ktorswaggerui.builder.openapi +import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.github.smiley4.ktorswaggerui.data.OpenApiBaseBodyData import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartBodyData @@ -13,6 +14,7 @@ import kotlin.collections.set class ContentBuilder( private val schemaContext: SchemaContext, + private val exampleContext: ExampleContext, private val headerBuilder: HeaderBuilder ) { @@ -45,10 +47,8 @@ class ContentBuilder( private fun buildSimpleMediaType(schema: Schema<*>?, body: OpenApiSimpleBodyData): MediaType { return MediaType().also { it.schema = schema - body.examples.forEach { (name, _) -> -// todo -// exampleContext.getExample(body, name) -// ?.also { example -> it.addExamples(name, example) } + body.examples.forEach { descriptor -> + it.addExamples(descriptor.name, exampleContext.getExample(descriptor)) } } } @@ -89,41 +89,6 @@ class ContentBuilder( } } -// private fun getSchema(body: OpenApiSimpleBodyData): Schema<*>? { -// return getSchema(body.type) -// } -// -// private fun getSchema(part: OpenApiMultipartPartData): Schema<*>? { -// return getSchema(part.type) -// } -// -// private fun getSchema(typeDescriptor: TypeDescriptor): Schema<*>? { -// return when (typeDescriptor) { -// is EmptyBodyTypeDescriptor -> { -// null -// } -// is SchemaBodyTypeDescriptor -> { -// schemaContext.getSchema(typeDescriptor) -// } -// is OneOfBodyTypeDescriptor -> { -// Schema().also { schema -> -// typeDescriptor.elements.forEach { -// schema.addOneOfItem(getSchema(it)) -// } -// } -// } -// is CollectionBodyTypeDescriptor -> { -// Schema().also { schema -> -// schema.type = "array" -// schema.items = getSchema(typeDescriptor.schemaType) -// } -// } -// is CustomRefBodyTypeDescriptor -> { -// schemaContext.getSchema(typeDescriptor.customSchemaId) -// } -// } -// } - private fun chooseMediaType(schema: Schema<*>): ContentType { return when (schema.type) { "integer" -> ContentType.Text.Plain diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt deleted file mode 100644 index 89e276c..0000000 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExampleBuilder.kt +++ /dev/null @@ -1,23 +0,0 @@ -//package io.github.smiley4.ktorswaggerui.builder.openapi -// -//import io.github.smiley4.ktorswaggerui.data.PluginConfigData -//import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiExample -//import io.github.smiley4.ktorswaggerui.dsl.SchemaType -//import io.swagger.v3.oas.models.examples.Example -// -//class ExampleBuilder( -// private val config: PluginConfigData -//) { -// -// fun build(type: SchemaType?, example: OpenApiExample): Example = -// Example().also { -// it.value = buildExampleValue(type, example.value) -// it.summary = example.summary -// it.description = example.description -// } -// -// fun buildExampleValue(type: SchemaType?, value: Any): String { -// return config.encoding.exampleEncoder(type, value) ?: value.toString() -// } -// -//} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt index 3917c0c..4259417 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt @@ -1,5 +1,6 @@ package io.github.smiley4.ktorswaggerui.builder.openapi +import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext import io.github.smiley4.ktorswaggerui.data.PluginConfigData import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext @@ -8,6 +9,7 @@ import io.swagger.v3.oas.models.OpenAPI class OpenApiBuilder( private val config: PluginConfigData, private val schemaContext: SchemaContext, + private val exampleContext: ExampleContext, private val infoBuilder: InfoBuilder, private val externalDocumentationBuilder: ExternalDocumentationBuilder, private val serverBuilder: ServerBuilder, @@ -23,8 +25,7 @@ class OpenApiBuilder( it.servers = config.servers.map { server -> serverBuilder.build(server) } it.tags = config.tags.map { tag -> tagBuilder.build(tag) } it.paths = pathsBuilder.build(routes) -// it.components = componentsBuilder.build(schemaContext.getComponentSection(), exampleContext.getComponentsSection()) // todo - it.components = componentsBuilder.build(schemaContext.getComponentSection(), emptyMap()) + it.components = componentsBuilder.build(schemaContext.getComponentSection(), exampleContext.getComponentSection()) } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt index 9415d54..ff5d1d0 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt @@ -1,5 +1,6 @@ package io.github.smiley4.ktorswaggerui.builder.openapi +import io.github.smiley4.ktorswaggerui.builder.example.ExampleContext import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.github.smiley4.ktorswaggerui.data.OpenApiRequestParameterData import io.github.smiley4.ktorswaggerui.data.ParameterLocation @@ -7,6 +8,7 @@ import io.swagger.v3.oas.models.parameters.Parameter class ParameterBuilder( private val schemaContext: SchemaContext, + private val exampleContext: ExampleContext ) { fun build(parameter: OpenApiRequestParameterData): Parameter = @@ -22,7 +24,7 @@ class ParameterBuilder( it.deprecated = parameter.deprecated it.allowEmptyValue = parameter.allowEmptyValue it.explode = parameter.explode -// it.example = exampleContext.getExample(parameter) // todo + it.example = parameter.example?.let { e -> exampleContext.getExample(e).value } it.allowReserved = parameter.allowReserved it.schema = schemaContext.getSchema(parameter.type) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt index 220dd35..9a77628 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt @@ -17,14 +17,12 @@ import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema import io.github.smiley4.schemakenerator.swagger.steps.SwaggerSchemaUtils import io.github.smiley4.schemakenerator.swagger.withAutoTitle import io.swagger.v3.oas.models.media.Schema -import mu.KotlinLogging import kotlin.reflect.KType class SchemaContextImpl : SchemaContext { private val rootSchemas = mutableMapOf>() private val componentSchemas = mutableMapOf>() - private val logger = KotlinLogging.logger {} fun add(routes: Collection) { @@ -77,7 +75,6 @@ class SchemaContextImpl : SchemaContext { } private fun generateSchema(type: KType): CompiledSwaggerSchema { - logger.debug { "Generating schema for type $type" } return listOf(type) .processReflection() .generateSwaggerSchema() diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt new file mode 100644 index 0000000..4fdce35 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt @@ -0,0 +1,15 @@ +package io.github.smiley4.ktorswaggerui.data + +sealed class ExampleDescriptor( + val name: String, +) + +class ValueExampleDescriptor( + name: String, + val value: Any?, + val summary: String?, + val description: String?, + val inComponents: Boolean?, +) : ExampleDescriptor(name) + +class RefExampleDescriptor(name: String, val refName: String): ExampleDescriptor(name) \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt index 17a8a32..25489b3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt @@ -13,7 +13,7 @@ class OpenApiSimpleBodyData( required: Boolean, mediaTypes: Set, val type: TypeDescriptor, - val examples: Map + val examples: List ) : OpenApiBaseBodyData(description, required, mediaTypes) class OpenApiMultipartBodyData( diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt index ddcf54c..4833a2b 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt @@ -5,7 +5,7 @@ data class OpenApiRequestParameterData( val type: TypeDescriptor, val location: ParameterLocation, val description: String?, - val example: Any?, + val example: ExampleDescriptor?, val required: Boolean, val deprecated: Boolean, val allowEmptyValue: Boolean, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt index afd7df5..3f92d29 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -2,16 +2,8 @@ package io.github.smiley4.ktorswaggerui.data import kotlin.reflect.KType -sealed interface TypeDescriptor { - companion object { - fun todo(v: Any?) = EmptyTypeDescriptor - } -} - +sealed interface TypeDescriptor class KTypeDescriptor(val type: KType) : TypeDescriptor - class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor - class OneOfTypeDescriptor(val types: List) : TypeDescriptor - -object EmptyTypeDescriptor : TypeDescriptor \ No newline at end of file +class EmptyTypeDescriptor : TypeDescriptor \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt new file mode 100644 index 0000000..b7b1d03 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt @@ -0,0 +1,21 @@ +package io.github.smiley4.ktorswaggerui.dsl.config + +import io.github.smiley4.ktorswaggerui.data.ExampleDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.swagger.v3.oas.models.examples.Example + +/** + * Configuration for schemas + */ +@OpenApiDslMarker +class ExampleConfig { + + var inComponents: Boolean = false + + private val examples = mutableMapOf() + + fun example(exampleDescriptor: ExampleDescriptor) { + TODO() + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt index b527c65..a791d5d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt @@ -1,5 +1,6 @@ package io.github.smiley4.ktorswaggerui.dsl.config +import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.github.smiley4.ktorswaggerui.data.* import io.github.smiley4.ktorswaggerui.data.DataUtils.merge import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @@ -156,6 +157,17 @@ class PluginConfigDsl { var whenBuildOpenApiSpecs: WhenBuildOpenApiSpecs? = null + + fun schemas(block: SchemaConfig.() -> Unit) { + TODO() + } + + + fun examples(block: ExampleConfig.() -> Unit) { + TODO() + } + + internal fun build(base: PluginConfigData): PluginConfigData { return PluginConfigData( defaultUnauthorizedResponse = merge(base.defaultUnauthorizedResponse, defaultUnauthorizedResponse?.build()), diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt new file mode 100644 index 0000000..7e65b99 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt @@ -0,0 +1,19 @@ +package io.github.smiley4.ktorswaggerui.dsl.config + +import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.swagger.v3.oas.models.media.Schema + +/** + * Configuration for schemas + */ +@OpenApiDslMarker +class SchemaConfig { + + private val schemas = mutableMapOf>() + + fun schema(descriptor: TypeDescriptor) { + TODO() + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt deleted file mode 100644 index 147f114..0000000 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiExample.kt +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.smiley4.ktorswaggerui.dsl.routes - -import io.github.smiley4.ktorswaggerui.data.OpenApiExampleData -import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker - -/** - * Documentation for an example object - */ -@OpenApiDslMarker -class OpenApiExample( - /** - * The actual example object/value - */ - val value: Any -) { - - /** - * A short description of the example - */ - var summary: String? = null - - - /** - * A long description of the example - */ - var description: String? = null - - - fun build() = OpenApiExampleData( - value = value, - summary = summary, - description = description - ) -} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt index d74f1c9..db392f8 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt @@ -6,7 +6,6 @@ import io.github.smiley4.ktorswaggerui.data.OpenApiRequestData import io.github.smiley4.ktorswaggerui.data.ParameterLocation import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker -import kotlin.reflect.KClass import kotlin.reflect.KType @@ -163,7 +162,7 @@ class OpenApiRequest { /** * The request body applicable for this operation */ - fun body(block: OpenApiSimpleBody.() -> Unit) = body(EmptyTypeDescriptor, block) + fun body(block: OpenApiSimpleBody.() -> Unit) = body(EmptyTypeDescriptor(), block) /** diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt index 86c203d..41f4fd3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt @@ -1,5 +1,6 @@ package io.github.smiley4.ktorswaggerui.dsl.routes +import io.github.smiley4.ktorswaggerui.data.ExampleDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiRequestParameterData import io.github.smiley4.ktorswaggerui.data.ParameterLocation import io.github.smiley4.ktorswaggerui.data.TypeDescriptor @@ -31,7 +32,7 @@ class OpenApiRequestParameter( /** * An example value for this parameter */ - var example: Any? = null + var example: ExampleDescriptor? = null /** @@ -69,7 +70,7 @@ class OpenApiRequestParameter( fun build() = OpenApiRequestParameterData( name = name, - type = TypeDescriptor.todo(type), + type = type, location = location, description = description, example = example, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt index fc0f321..328335f 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt @@ -1,5 +1,6 @@ package io.github.smiley4.ktorswaggerui.dsl.routes +import io.github.smiley4.ktorswaggerui.data.ExampleDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @@ -19,15 +20,17 @@ class OpenApiSimpleBody( /** * Examples for this body */ - private val examples = mutableMapOf() + private val examples = mutableListOf() - fun example(name: String, value: Any, block: OpenApiExample.() -> Unit) { - examples[name] = OpenApiExample(value).apply(block) - } +// fun example(name: String, value: Any, block: OpenApiExample.() -> Unit) { +// examples[name] = OpenApiExample(value).apply(block) +// } - fun example(name: String, value: Any) = example(name, value) {} + fun example(example: ExampleDescriptor) { + examples.add(example) + } - fun getExamples(): Map = examples + fun getExamples(): List = examples override fun build() = OpenApiSimpleBodyData( @@ -35,7 +38,7 @@ class OpenApiSimpleBody( required = required ?: false, mediaTypes = getMediaTypes(), type = type, - examples = getExamples().mapValues { it.value.build() }, + examples = getExamples(), ) } From e48b13a4be7387dfb31dc8f1ec1bcfe9779f4c3d Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Tue, 7 May 2024 17:06:17 +0200 Subject: [PATCH 06/32] wip --- .../smiley4/ktorswaggerui/examples/Petstore.kt | 5 +++++ .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 10 ++++++++-- .../builder/example/ExampleContextImpl.kt | 13 ++++++++----- .../builder/schema/SchemaContextImpl.kt | 18 ++++++++++++++++++ .../ktorswaggerui/data/ExampleConfigData.kt | 13 +++++++++++++ .../ktorswaggerui/data/ExampleDescriptor.kt | 6 +++++- .../ktorswaggerui/data/PluginConfigData.kt | 8 ++++++-- .../ktorswaggerui/data/SchemaConfigData.kt | 12 ++++++++++++ .../ktorswaggerui/data/TypeDescriptor.kt | 2 ++ .../ktorswaggerui/dsl/config/ExampleConfig.kt | 11 ++++++----- .../dsl/config/PluginConfigDsl.kt | 8 ++++++-- .../ktorswaggerui/dsl/config/SchemaConfig.kt | 12 ++++++++---- 12 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt index 4dce146..0b0c063 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt @@ -47,6 +47,11 @@ private fun Application.myModule() { examples { example(ValueExampleDescriptor("Unexpected Error", ErrorModel("Unexpected Error"), null, null, null)) } + schemas { + schema("Pet", KTypeDescriptor(typeOf())) + schema("NewPet", KTypeDescriptor(typeOf())) + schema("PetList", KTypeDescriptor(typeOf>())) + } } routing { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index b119afa..34e7bc3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -88,8 +88,14 @@ private fun buildOpenApiSpecs(config: PluginConfigData, routes: List) private fun buildOpenApiSpec(pluginConfig: PluginConfigData, routes: List): String { return try { - val schemaContext = SchemaContextImpl().also { it.add(routes) } - val exampleContext = ExampleContextImpl().also { it.add(routes) } + val schemaContext = SchemaContextImpl().also { + it.addGlobal(pluginConfig.schemaConfig) + it.add(routes) + } + val exampleContext = ExampleContextImpl().also { + it.addGlobal(pluginConfig.exampleConfig) + it.add(routes) + } val openApi = builder(pluginConfig, schemaContext, exampleContext).build(routes) pluginConfig.whenBuildOpenApiSpecs?.invoke(openApi) Json.pretty(openApi) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt index 31d4ccc..5744c70 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt @@ -1,10 +1,11 @@ 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.PluginConfigData import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor +import io.github.smiley4.ktorswaggerui.data.SwaggerExampleDescriptor import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.swagger.v3.oas.models.examples.Example @@ -13,8 +14,11 @@ class ExampleContextImpl : ExampleContext { private val rootExamples = mutableMapOf() private val componentExamples = mutableMapOf() - fun addGlobal(config: PluginConfigData) { - TODO("add global examples from config to components") + fun addGlobal(config: ExampleConfigData) { + config.examples.forEach { (_, exampleDescriptor) -> + val example = generateExample(exampleDescriptor) + componentExamples[exampleDescriptor.name] = example + } } fun add(routes: Collection) { @@ -33,7 +37,6 @@ class ExampleContextImpl : ExampleContext { private fun collectExampleDescriptors(routes: Collection): List { val descriptors = mutableListOf() - routes .filter { !it.documentation.hidden } .forEach { route -> @@ -55,7 +58,6 @@ class ExampleContextImpl : ExampleContext { } } } - return descriptors } @@ -69,6 +71,7 @@ class ExampleContextImpl : ExampleContext { is RefExampleDescriptor -> Example().also { it.`$ref` = "#/components/examples/${exampleDescriptor.refName}" } + is SwaggerExampleDescriptor -> exampleDescriptor.example } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt index 9a77628..d1bdc70 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt @@ -7,6 +7,8 @@ import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.OneOfTypeDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartBodyData import io.github.smiley4.ktorswaggerui.data.OpenApiSimpleBodyData +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.WildcardTypeData import io.github.smiley4.schemakenerator.reflection.processReflection @@ -24,6 +26,15 @@ class SchemaContextImpl : SchemaContext { private val rootSchemas = mutableMapOf>() private val componentSchemas = mutableMapOf>() + fun addGlobal(config: SchemaConfigData) { + config.schemas.forEach { (schemaId, typeDescriptor) -> + val schema = generateSchema(typeDescriptor) + componentSchemas[schemaId] = schema.swagger + schema.componentSchemas.forEach { (k, v) -> + componentSchemas[k.full()] = v + } + } + } fun add(routes: Collection) { collectTypeDescriptor(routes).forEach { typeDescriptor -> @@ -40,6 +51,13 @@ class SchemaContextImpl : SchemaContext { is KTypeDescriptor -> { generateSchema(typeDescriptor.type) } + is SwaggerTypeDescriptor -> { + CompiledSwaggerSchema( + typeData = WildcardTypeData(), + swagger = typeDescriptor.schema, + componentSchemas = emptyMap() + ) + } is ArrayTypeDescriptor -> { val itemSchema = generateSchema(typeDescriptor.type) CompiledSwaggerSchema( diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt new file mode 100644 index 0000000..c9d0728 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt @@ -0,0 +1,13 @@ +package io.github.smiley4.ktorswaggerui.data + +class ExampleConfigData( + val examples: Map +) { + + companion object { + val DEFAULT = ExampleConfigData( + examples = emptyMap() + ) + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt index 4fdce35..23cd445 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt @@ -1,5 +1,7 @@ package io.github.smiley4.ktorswaggerui.data +import io.swagger.v3.oas.models.examples.Example + sealed class ExampleDescriptor( val name: String, ) @@ -12,4 +14,6 @@ class ValueExampleDescriptor( val inComponents: Boolean?, ) : ExampleDescriptor(name) -class RefExampleDescriptor(name: String, val refName: String): ExampleDescriptor(name) \ No newline at end of file +class RefExampleDescriptor(name: String, val refName: String): ExampleDescriptor(name) + +class SwaggerExampleDescriptor(name: String, val example: Example): ExampleDescriptor(name) \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt index 45e7708..527e201 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt @@ -17,7 +17,9 @@ data class PluginConfigData( val securitySchemes: List, val tags: List, val specConfigs: MutableMap, - val whenBuildOpenApiSpecs: WhenBuildOpenApiSpecs? + val whenBuildOpenApiSpecs: WhenBuildOpenApiSpecs?, + val schemaConfig: SchemaConfigData, + val exampleConfig: ExampleConfigData, ) { companion object { @@ -35,7 +37,9 @@ data class PluginConfigData( securitySchemes = emptyList(), tags = emptyList(), specConfigs = mutableMapOf(), - whenBuildOpenApiSpecs = null + whenBuildOpenApiSpecs = null, + schemaConfig = SchemaConfigData.DEFAULT, + exampleConfig = ExampleConfigData.DEFAULT, ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt new file mode 100644 index 0000000..caf5916 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt @@ -0,0 +1,12 @@ +package io.github.smiley4.ktorswaggerui.data + + +data class SchemaConfigData( + val schemas: Map +) { + companion object { + val DEFAULT = SchemaConfigData( + schemas = emptyMap() + ) + } +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt index 3f92d29..7e1f4b7 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -1,8 +1,10 @@ package io.github.smiley4.ktorswaggerui.data +import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType sealed interface TypeDescriptor +class SwaggerTypeDescriptor(val schema: Schema<*>) : TypeDescriptor class KTypeDescriptor(val type: KType) : TypeDescriptor class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor class OneOfTypeDescriptor(val types: List) : TypeDescriptor diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt index b7b1d03..17af710 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt @@ -1,8 +1,8 @@ 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.dsl.OpenApiDslMarker -import io.swagger.v3.oas.models.examples.Example /** * Configuration for schemas @@ -10,12 +10,13 @@ import io.swagger.v3.oas.models.examples.Example @OpenApiDslMarker class ExampleConfig { - var inComponents: Boolean = false - - private val examples = mutableMapOf() + private val examples = mutableMapOf() fun example(exampleDescriptor: ExampleDescriptor) { - TODO() } + fun build() = ExampleConfigData( + examples = examples + ) + } \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt index a791d5d..6954fcc 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt @@ -157,14 +157,16 @@ class PluginConfigDsl { var whenBuildOpenApiSpecs: WhenBuildOpenApiSpecs? = null + private val schemaConfig = SchemaConfig() fun schemas(block: SchemaConfig.() -> Unit) { - TODO() + schemaConfig.also(block) } + private val exampleConfig = ExampleConfig() fun examples(block: ExampleConfig.() -> Unit) { - TODO() + exampleConfig.apply(block) } @@ -200,6 +202,8 @@ class PluginConfigDsl { }, specConfigs = mutableMapOf(), whenBuildOpenApiSpecs = whenBuildOpenApiSpecs, + schemaConfig = schemaConfig.build(), + exampleConfig = exampleConfig.build() ).also { specConfigs.forEach { (specId, config) -> it.specConfigs[specId] = config.build(it) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt index 7e65b99..0f5a8ad 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt @@ -1,8 +1,8 @@ package io.github.smiley4.ktorswaggerui.dsl.config +import io.github.smiley4.ktorswaggerui.data.SchemaConfigData import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker -import io.swagger.v3.oas.models.media.Schema /** * Configuration for schemas @@ -10,10 +10,14 @@ import io.swagger.v3.oas.models.media.Schema @OpenApiDslMarker class SchemaConfig { - private val schemas = mutableMapOf>() + private val schemas = mutableMapOf() - fun schema(descriptor: TypeDescriptor) { - TODO() + fun schema(schemaId: String, descriptor: TypeDescriptor) { + schemas[schemaId] = descriptor } + fun build() = SchemaConfigData( + schemas = schemas + ) + } \ No newline at end of file From 910fb98658fc91e8e7731b27a0f641cd15f9da2d Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 9 May 2024 00:20:45 +0200 Subject: [PATCH 07/32] schema config --- ktor-swagger-ui-examples/build.gradle.kts | 9 + .../examples/CustomizeSchemaGeneration.kt | 163 +++++++++++++++++- .../examples/SchemaCostomization.kt | 3 + .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 2 +- .../builder/schema/SchemaContextImpl.kt | 51 ++++-- .../ktorswaggerui/data/SchemaConfigData.kt | 22 ++- .../ktorswaggerui/data/TypeDescriptor.kt | 5 +- .../ktorswaggerui/dsl/config/SchemaConfig.kt | 10 +- 8 files changed, 242 insertions(+), 23 deletions(-) create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaCostomization.kt diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts index 4ba5d63..1a86424 100644 --- a/ktor-swagger-ui-examples/build.gradle.kts +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -21,6 +21,15 @@ dependencies { implementation("io.ktor:ktor-server-call-logging:$ktorVersion") implementation("io.ktor:ktor-server-test-host:$ktorVersion") + val schemaKeneratorVersion = "0.1" + implementation("io.github.smiley4:schema-kenerator-core:$schemaKeneratorVersion") + implementation("io.github.smiley4:schema-kenerator-reflection:$schemaKeneratorVersion") + implementation("io.github.smiley4:schema-kenerator-swagger:$schemaKeneratorVersion") + implementation("io.github.smiley4:schema-kenerator-jackson:$schemaKeneratorVersion") + + val swaggerParserVersion = "2.1.19" + implementation("io.swagger.parser.v3:swagger-parser:$swaggerParserVersion") + val kotlinLoggingVersion = "3.0.5" implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt index 862928a..4a5bafe 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt @@ -1,3 +1,164 @@ package io.github.smiley4.ktorswaggerui.examples -// TODO \ No newline at end of file +import com.fasterxml.jackson.annotation.JsonSubTypes +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.data.AnyOfTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.ArrayTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.RefTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor +import io.github.smiley4.ktorswaggerui.dsl.routing.get +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.github.smiley4.schemakenerator.core.connectSubTypes +import io.github.smiley4.schemakenerator.jackson.collectJacksonSubTypes +import io.github.smiley4.schemakenerator.reflection.processReflection +import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot +import io.github.smiley4.schemakenerator.swagger.data.TitleType +import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema +import io.github.smiley4.schemakenerator.swagger.withAutoTitle +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +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 io.swagger.v3.oas.models.media.Schema +import java.time.LocalDateTime +import kotlin.reflect.typeOf + +fun main() { + embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) +} + +private fun Application.myModule() { + + // Install the "SwaggerUI"-Plugin and use the default configuration + install(SwaggerUI) { + schemas { + + // add a swagger schema to the component-section of the api-spec with the id "swagger-schema" + schema("swagger-schema", SwaggerTypeDescriptor( + Schema().also { + it.type = "number" + it.title = "Custom Type" + } + )) + + // add a type to the component-section of the api-spec with the id "type-schema" + schema("type-schema", KTypeDescriptor(typeOf())) + + // overwrite 'LocalDateTime' with custom schema (root only) + overwrite[typeOf()] = SwaggerTypeDescriptor( + Schema().also { + it.title = "timestamp" + it.type = "integer" + } + ) + + // customized schema generation pipeline + generator = { type -> + type + .collectJacksonSubTypes(typeProcessing = { types -> types.processReflection() }) // include types from jackson subtype-annotation + .processReflection() + .connectSubTypes() // connect the supertypes with their subtypes + .generateSwaggerSchema() + .withAutoTitle(TitleType.SIMPLE) + .compileReferencingRoot() + } + + } + } + + routing { + route("swagger") { + swaggerUI("/api.json") + } + route("api.json") { + openApiSpec() + } + + get("global-swagger-schema", { + request { + // reference and use the schema from the component-section with the id "swagger-schema" + body(RefTypeDescriptor("swagger-schema")) + } + }) { + call.respondText("...") + } + + get("global-type-schema", { + request { + // reference and use the schema from the component-section with the id "type-schema" + body(RefTypeDescriptor("type-schema")) + } + }) { + call.respondText("...") + } + + get("array-schema", { + request { + // an array of items with the referenced schema with the id "type-schema" + body( + ArrayTypeDescriptor( + RefTypeDescriptor("type-schema") + ) + ) + } + }) { + call.respondText("...") + } + + get("anyof-schema", { + request { + // either the referenced schema with id "type-schema" or "swagger-schema" + body( + AnyOfTypeDescriptor( + listOf( + RefTypeDescriptor("type-schema"), + RefTypeDescriptor("swagger-schema") + ) + ) + ) + } + }) { + call.respondText("...") + } + + get("type-overwrite", { + request { + // schema is not generated the normal way but the overwriting schema from the config is used instead + body(KTypeDescriptor(typeOf())) + } + }) { + call.respondText("...") + } + + get("jackson-subtypes", { + request { + // jackson subtypes are detected automatically + body(KTypeDescriptor(typeOf())) + } + }) { + call.respondText("...") + } + + } + +} + + +private data class MyClass( + val someValue: String +) + + +@JsonSubTypes( + JsonSubTypes.Type(value = SubTypeA::class), + JsonSubTypes.Type(value = SubTypeB::class), +) +open class BaseType(val base: String) +class SubTypeA(base: String, val a: Int) : BaseType(base) +class SubTypeB(base: String, val b: Boolean) : BaseType(base) \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaCostomization.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaCostomization.kt new file mode 100644 index 0000000..862928a --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaCostomization.kt @@ -0,0 +1,3 @@ +package io.github.smiley4.ktorswaggerui.examples + +// TODO \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index 34e7bc3..7fcdeaf 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -88,7 +88,7 @@ private fun buildOpenApiSpecs(config: PluginConfigData, routes: List) private fun buildOpenApiSpec(pluginConfig: PluginConfigData, routes: List): String { return try { - val schemaContext = SchemaContextImpl().also { + val schemaContext = SchemaContextImpl(pluginConfig.schemaConfig).also { it.addGlobal(pluginConfig.schemaConfig) it.add(routes) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt index d1bdc70..d65bea6 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt @@ -1,34 +1,31 @@ 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.OneOfTypeDescriptor 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.schemakenerator.core.data.WildcardTypeData -import io.github.smiley4.schemakenerator.reflection.processReflection -import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema -import io.github.smiley4.schemakenerator.swagger.data.TitleType -import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema import io.github.smiley4.schemakenerator.swagger.steps.SwaggerSchemaUtils -import io.github.smiley4.schemakenerator.swagger.withAutoTitle import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType -class SchemaContextImpl : SchemaContext { +class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : SchemaContext { private val rootSchemas = mutableMapOf>() private val componentSchemas = mutableMapOf>() fun addGlobal(config: SchemaConfigData) { config.schemas.forEach { (schemaId, typeDescriptor) -> - val schema = generateSchema(typeDescriptor) + val schema = collapseRootRef(generateSchema(typeDescriptor)) componentSchemas[schemaId] = schema.swagger schema.componentSchemas.forEach { (k, v) -> componentSchemas[k.full()] = v @@ -36,6 +33,22 @@ class SchemaContextImpl : SchemaContext { } } + private fun collapseRootRef(schema: CompiledSwaggerSchema): CompiledSwaggerSchema { + if (schema.swagger.`$ref` == null) { + return schema + } else { + val referencedSchemaId = TypeId.parse(schema.swagger.`$ref`!!.replace("#/components/schemas/", "")) + val referencedSchema = schema.componentSchemas[referencedSchemaId]!! + return CompiledSwaggerSchema( + typeData = schema.typeData, + swagger = referencedSchema, + componentSchemas = schema.componentSchemas.toMutableMap().also { + it.remove(referencedSchemaId) + } + ) + } + } + fun add(routes: Collection) { collectTypeDescriptor(routes).forEach { typeDescriptor -> val schema = generateSchema(typeDescriptor) @@ -49,7 +62,11 @@ class SchemaContextImpl : SchemaContext { private fun generateSchema(typeDescriptor: TypeDescriptor): CompiledSwaggerSchema { return when (typeDescriptor) { is KTypeDescriptor -> { - generateSchema(typeDescriptor.type) + if (schemaConfig.overwrite.containsKey(typeDescriptor.type)) { + generateSchema(schemaConfig.overwrite[typeDescriptor.type]!!) + } else { + generateSchema(typeDescriptor.type) + } } is SwaggerTypeDescriptor -> { CompiledSwaggerSchema( @@ -68,7 +85,7 @@ class SchemaContextImpl : SchemaContext { componentSchemas = itemSchema.componentSchemas ) } - is OneOfTypeDescriptor -> { + is AnyOfTypeDescriptor -> { val optionSchemas = typeDescriptor.types.map { generateSchema(it) } CompiledSwaggerSchema( typeData = WildcardTypeData(), @@ -89,16 +106,18 @@ class SchemaContextImpl : SchemaContext { componentSchemas = emptyMap() ) } + is RefTypeDescriptor -> { + CompiledSwaggerSchema( + typeData = WildcardTypeData(), + swagger = SwaggerSchemaUtils().referenceSchema(typeDescriptor.schemaId, true), + componentSchemas = emptyMap() + ) + } } } private fun generateSchema(type: KType): CompiledSwaggerSchema { - return listOf(type) - .processReflection() - .generateSwaggerSchema() - .withAutoTitle(TitleType.SIMPLE) - .compileReferencingRoot() - .first() + return schemaConfig.generator(type) } private fun collectTypeDescriptor(routes: Collection): List { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt index caf5916..292f8d3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt @@ -1,12 +1,30 @@ package io.github.smiley4.ktorswaggerui.data +import io.github.smiley4.schemakenerator.reflection.processReflection +import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot +import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema +import io.github.smiley4.schemakenerator.swagger.data.TitleType +import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema +import io.github.smiley4.schemakenerator.swagger.withAutoTitle +import kotlin.reflect.KType + data class SchemaConfigData( - val schemas: Map + val schemas: Map, + val generator: (type: KType) -> CompiledSwaggerSchema, + val overwrite: Map ) { companion object { val DEFAULT = SchemaConfigData( - schemas = emptyMap() + schemas = emptyMap(), + generator = { type -> + type + .processReflection() + .generateSwaggerSchema() + .withAutoTitle(TitleType.SIMPLE) + .compileReferencingRoot() + }, + overwrite = emptyMap() ) } } \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt index 7e1f4b7..8a13d2d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -7,5 +7,6 @@ sealed interface TypeDescriptor class SwaggerTypeDescriptor(val schema: Schema<*>) : TypeDescriptor class KTypeDescriptor(val type: KType) : TypeDescriptor class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor -class OneOfTypeDescriptor(val types: List) : TypeDescriptor -class EmptyTypeDescriptor : TypeDescriptor \ No newline at end of file +class AnyOfTypeDescriptor(val types: List) : TypeDescriptor +class EmptyTypeDescriptor : TypeDescriptor +class RefTypeDescriptor(val schemaId: String) : TypeDescriptor \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt index 0f5a8ad..a98b0d0 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt @@ -3,6 +3,8 @@ package io.github.smiley4.ktorswaggerui.dsl.config import io.github.smiley4.ktorswaggerui.data.SchemaConfigData import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema +import kotlin.reflect.KType /** * Configuration for schemas @@ -16,8 +18,14 @@ class SchemaConfig { schemas[schemaId] = descriptor } + var generator: (type: KType) -> CompiledSwaggerSchema = SchemaConfigData.DEFAULT.generator + + val overwrite = mutableMapOf() + fun build() = SchemaConfigData( - schemas = schemas + schemas = schemas, + generator = generator, + overwrite = overwrite ) } \ No newline at end of file From 0ea65717da4eb25ba1d65d6753165a00f4ae2184 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 9 May 2024 00:41:05 +0200 Subject: [PATCH 08/32] example config --- .../ktorswaggerui/examples/Authentication.kt | 8 +- .../ktorswaggerui/examples/CustomSchemas.kt | 3 - .../ktorswaggerui/examples/Examples.kt | 113 ++++++++++++++++++ .../ktorswaggerui/examples/FileUpload.kt | 27 ++++- .../ktorswaggerui/examples/Petstore.kt | 24 ++-- .../ktorswaggerui/examples/RequestResponse.kt | 5 +- .../examples/SchemaCostomization.kt | 3 - ...ustomizeSchemaGeneration.kt => Schemas.kt} | 32 ++++- .../builder/example/ExampleContextImpl.kt | 12 +- .../ktorswaggerui/data/ExampleConfigData.kt | 4 +- .../ktorswaggerui/data/ExampleDescriptor.kt | 5 +- .../ktorswaggerui/dsl/config/ExampleConfig.kt | 5 +- 12 files changed, 187 insertions(+), 54 deletions(-) delete mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemas.kt create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt delete mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaCostomization.kt rename ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/{CustomizeSchemaGeneration.kt => Schemas.kt} (89%) diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt index 9c0862e..159407d 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt @@ -57,13 +57,10 @@ private fun Application.myModule() { routing { - // Create a route for the swagger-ui using the openapi-spec at "/api.json". - // This route will not be included in the spec. + // add the routes for swagger-ui and api-spec route("swagger") { swaggerUI("/api.json") } - // Create a route for the openapi-spec file. - // This route will not be included in the spec. route("api.json") { openApiSpec() } @@ -94,9 +91,6 @@ private fun Application.myModule() { call.respondText("Hello World!") } - - - } } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemas.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemas.kt deleted file mode 100644 index 862928a..0000000 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomSchemas.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -// TODO \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt new file mode 100644 index 0000000..1a0c5c1 --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt @@ -0,0 +1,113 @@ +package io.github.smiley4.ktorswaggerui.examples + +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor +import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor +import io.github.smiley4.ktorswaggerui.dsl.routing.get +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +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) +} + +private fun Application.myModule() { + + // Install and customize the "SwaggerUI"-Plugin + install(SwaggerUI) { + examples { + + // specify two shared examples + example(ValueExampleDescriptor( + name = "Shared A", + description = "first shared example", + value = MyExampleClass( + someValue = "shared a" + ) + )) + example(ValueExampleDescriptor( + name = "Shared B", + description = "second shared example", + value = MyExampleClass( + someValue = "shared b" + ) + )) + + } + } + + routing { + + // add the routes for swagger-ui and api-spec + route("swagger") { + swaggerUI("/api.json") + } + route("api.json") { + openApiSpec() + } + + + get("basic", { + request { + body(KTypeDescriptor(typeOf())) { + // specify two example values + example( + ValueExampleDescriptor( + name = "Example 1", + description = "A first example value", + value = MyExampleClass( + someValue = "example 1" + ), + ) + ) + example( + ValueExampleDescriptor( + name = "Example 2", + description = "A second example value", + value = MyExampleClass( + someValue = "example 2" + ), + ) + ) + } + } + }) { + call.respondText("...") + } + + + get("reference-shared", { + request { + body(KTypeDescriptor(typeOf())) { + // reference two shared examples specified in the plugin-config (and placed in the component section) + example( + RefExampleDescriptor("Example 1", "Shared A") + ) + example( + RefExampleDescriptor("Example 2", "Shared B") + ) + } + } + }) { + call.respondText("...") + } + + } + +} + + +private data class MyExampleClass( + val someValue: String +) + diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt index 3a64cc8..e55f989 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt @@ -2,7 +2,10 @@ package io.github.smiley4.ktorswaggerui.examples import io.github.smiley4.ktorswaggerui.SwaggerUI import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.post +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application @@ -11,7 +14,9 @@ import io.ktor.server.application.install import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import io.ktor.server.response.respond +import io.ktor.server.routing.route import io.ktor.server.routing.routing +import io.swagger.v3.oas.models.media.Schema import java.io.File import kotlin.reflect.typeOf @@ -22,14 +27,32 @@ fun main() { private fun Application.myModule() { // Install the "SwaggerUI"-Plugin and use the default configuration - install(SwaggerUI) + install(SwaggerUI) { + schemas { + // overwrite type "File" with custom schema for binary data + overwrite[typeOf()] = SwaggerTypeDescriptor( + Schema().also { + it.type = "string" + it.format = "binary" + } + ) + } + } routing { + // add the routes for swagger-ui and api-spec + route("swagger") { + swaggerUI("/api.json") + } + route("api.json") { + openApiSpec() + } + // upload a single file, either as png, jpeg or svg post("single", { request { - body(KTypeDescriptor(typeOf())) { // todo: type overwrite for "File" + body(KTypeDescriptor(typeOf())) { mediaType(ContentType.Image.PNG) mediaType(ContentType.Image.JPEG) mediaType(ContentType.Image.SVG) diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt index 0b0c063..22c9e2f 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt @@ -45,7 +45,7 @@ private fun Application.myModule() { } } examples { - example(ValueExampleDescriptor("Unexpected Error", ErrorModel("Unexpected Error"), null, null, null)) + example(ValueExampleDescriptor("Unexpected Error", ErrorModel("Unexpected Error"), null, null)) } schemas { schema("Pet", KTypeDescriptor(typeOf())) @@ -72,12 +72,12 @@ private fun Application.myModule() { queryParameter("tags", KTypeDescriptor(typeOf>())) { description = "tags to filter by" required = false - example = ValueExampleDescriptor("dog", "default", null, null, null) + example = ValueExampleDescriptor("dog", "default", null, null) } queryParameter("limit", KTypeDescriptor(typeOf())) { description = "maximum number of results to return" required = false - example = ValueExampleDescriptor("default", 100, null, null, null) + example = ValueExampleDescriptor("default", 100, null, null) } } response { @@ -98,7 +98,7 @@ private fun Application.myModule() { name = "Charlie", tag = "dog" ) - ), null, null, true + ), null, null ) ) } @@ -127,7 +127,7 @@ private fun Application.myModule() { NewPet( name = "Big Bird", tag = "bird" - ), null, null, true + ), null, null ) ) example( @@ -136,7 +136,7 @@ private fun Application.myModule() { NewPet( name = "Charlie", tag = "dog" - ), null, null, true + ), null, null ) ) } @@ -152,7 +152,7 @@ private fun Application.myModule() { id = 123, name = "Big Bird", tag = "bird" - ), null, null, true + ), null, null ) ) example( @@ -162,7 +162,7 @@ private fun Application.myModule() { id = 456, name = "Charlie", tag = "dog" - ), null, null, true + ), null, null ) ) } @@ -187,7 +187,7 @@ private fun Application.myModule() { pathParameter("id", KTypeDescriptor(typeOf())) { description = "Id of pet to fetch" required = true - example = ValueExampleDescriptor("default", 123L, null, null, null) + example = ValueExampleDescriptor("default", 123L, null, null) } } response { @@ -201,7 +201,7 @@ private fun Application.myModule() { id = 123, name = "Big Bird", tag = "bird" - ), null, null, true + ), null, null ) ) example( @@ -211,7 +211,7 @@ private fun Application.myModule() { id = 123, name = "Charlie", tag = "dog" - ), null, null, true + ), null, null ) ) } @@ -237,7 +237,7 @@ private fun Application.myModule() { pathParameter("id", KTypeDescriptor(typeOf())) { description = "Id of pet to delete" required = true - example = ValueExampleDescriptor("default", 123L, null, null, true) + example = ValueExampleDescriptor("default", 123L, null, null) } } response { diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt index b62bab7..ccd372e 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt @@ -43,13 +43,10 @@ private fun Application.myModule() { routing { - // Create a route for the swagger-ui using the openapi-spec at "/api.json". - // This route will not be included in the spec. + // add the routes for swagger-ui and api-spec route("swagger") { swaggerUI("/api.json") } - // Create a route for the openapi-spec file. - // This route will not be included in the spec. route("api.json") { openApiSpec() } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaCostomization.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaCostomization.kt deleted file mode 100644 index 862928a..0000000 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/SchemaCostomization.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.smiley4.ktorswaggerui.examples - -// TODO \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt similarity index 89% rename from ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt rename to ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt index 4a5bafe..b3f83f3 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizeSchemaGeneration.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt @@ -35,7 +35,7 @@ fun main() { private fun Application.myModule() { - // Install the "SwaggerUI"-Plugin and use the default configuration + // Install and customize the "SwaggerUI"-Plugin install(SwaggerUI) { schemas { @@ -48,7 +48,7 @@ private fun Application.myModule() { )) // add a type to the component-section of the api-spec with the id "type-schema" - schema("type-schema", KTypeDescriptor(typeOf())) + schema("type-schema", KTypeDescriptor(typeOf())) // overwrite 'LocalDateTime' with custom schema (root only) overwrite[typeOf()] = SwaggerTypeDescriptor( @@ -73,6 +73,8 @@ private fun Application.myModule() { } routing { + + // add the routes for swagger-ui and api-spec route("swagger") { swaggerUI("/api.json") } @@ -80,6 +82,17 @@ private fun Application.myModule() { openApiSpec() } + + get("basic", { + request { + // directly specify the schema type + body(KTypeDescriptor(typeOf())) + } + }) { + call.respondText("...") + } + + get("global-swagger-schema", { request { // reference and use the schema from the component-section with the id "swagger-schema" @@ -89,6 +102,7 @@ private fun Application.myModule() { call.respondText("...") } + get("global-type-schema", { request { // reference and use the schema from the component-section with the id "type-schema" @@ -98,6 +112,7 @@ private fun Application.myModule() { call.respondText("...") } + get("array-schema", { request { // an array of items with the referenced schema with the id "type-schema" @@ -111,6 +126,7 @@ private fun Application.myModule() { call.respondText("...") } + get("anyof-schema", { request { // either the referenced schema with id "type-schema" or "swagger-schema" @@ -127,6 +143,7 @@ private fun Application.myModule() { call.respondText("...") } + get("type-overwrite", { request { // schema is not generated the normal way but the overwriting schema from the config is used instead @@ -136,6 +153,7 @@ private fun Application.myModule() { call.respondText("...") } + get("jackson-subtypes", { request { // jackson subtypes are detected automatically @@ -150,7 +168,7 @@ private fun Application.myModule() { } -private data class MyClass( +private data class MySchemaClass( val someValue: String ) @@ -159,6 +177,8 @@ private data class MyClass( JsonSubTypes.Type(value = SubTypeA::class), JsonSubTypes.Type(value = SubTypeB::class), ) -open class BaseType(val base: String) -class SubTypeA(base: String, val a: Int) : BaseType(base) -class SubTypeB(base: String, val b: Boolean) : BaseType(base) \ No newline at end of file +private open class BaseType(val base: String) + +private class SubTypeA(base: String, val a: Int) : BaseType(base) + +private class SubTypeB(base: String, val b: Boolean) : BaseType(base) \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt index 5744c70..22a8489 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt @@ -15,7 +15,7 @@ class ExampleContextImpl : ExampleContext { private val componentExamples = mutableMapOf() fun addGlobal(config: ExampleConfigData) { - config.examples.forEach { (_, exampleDescriptor) -> + config.sharedExamples.forEach { (_, exampleDescriptor) -> val example = generateExample(exampleDescriptor) componentExamples[exampleDescriptor.name] = example } @@ -23,15 +23,7 @@ class ExampleContextImpl : ExampleContext { fun add(routes: Collection) { collectExampleDescriptors(routes).forEach { exampleDescriptor -> - val example = generateExample(exampleDescriptor) - if (exampleDescriptor is ValueExampleDescriptor && exampleDescriptor.inComponents == true) { - rootExamples[exampleDescriptor] = Example().also { - it.`$ref` = "#/components/examples/${exampleDescriptor.name}" - } - componentExamples[exampleDescriptor.name] = example - } else { - rootExamples[exampleDescriptor] = example - } + rootExamples[exampleDescriptor] = generateExample(exampleDescriptor) } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt index c9d0728..6872d03 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt @@ -1,12 +1,12 @@ package io.github.smiley4.ktorswaggerui.data class ExampleConfigData( - val examples: Map + val sharedExamples: Map ) { companion object { val DEFAULT = ExampleConfigData( - examples = emptyMap() + sharedExamples = emptyMap() ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt index 23cd445..8337fdf 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt @@ -9,9 +9,8 @@ sealed class ExampleDescriptor( class ValueExampleDescriptor( name: String, val value: Any?, - val summary: String?, - val description: String?, - val inComponents: Boolean?, + val summary: String? = null, + val description: String? = null, ) : ExampleDescriptor(name) class RefExampleDescriptor(name: String, val refName: String): ExampleDescriptor(name) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt index 17af710..0d207eb 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt @@ -10,13 +10,14 @@ import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @OpenApiDslMarker class ExampleConfig { - private val examples = mutableMapOf() + private val sharedExamples = mutableMapOf() fun example(exampleDescriptor: ExampleDescriptor) { + sharedExamples[exampleDescriptor.name] = exampleDescriptor } fun build() = ExampleConfigData( - examples = examples + sharedExamples = sharedExamples ) } \ No newline at end of file From 776808d2e46a55f66727681ecddf4d6a7274177b Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 9 May 2024 00:58:26 +0200 Subject: [PATCH 09/32] cleanup config --- .../ktorswaggerui/dsl/config/OpenApiExternalDocs.kt | 2 ++ .../smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt | 1 + .../smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt | 2 +- .../smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt | 2 -- .../smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt | 3 --- .../smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt | 9 ++++++++- .../smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt | 2 +- .../smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt | 4 +--- .../ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt | 7 ++----- .../ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt | 2 -- .../smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt | 6 ------ .../ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt | 6 ++---- 12 files changed, 18 insertions(+), 28 deletions(-) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt index ea10463..6510081 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiExternalDocs.kt @@ -9,6 +9,7 @@ import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker */ @OpenApiDslMarker class OpenApiExternalDocs { + /** * A short description of the external documentation */ @@ -20,6 +21,7 @@ class OpenApiExternalDocs { */ var url: String = "/" + fun build(base: ExternalDocsData) = ExternalDocsData( url = DataUtils.mergeDefault(base.url, url, ExternalDocsData.DEFAULT.url), description = DataUtils.merge(base.description, description) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt index 9c64120..84e2387 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt @@ -54,6 +54,7 @@ class OpenApiInfo { license = OpenApiLicense().apply(block) } + fun build(base: InfoData): InfoData { return InfoData( title = mergeDefault(base.title, this.title, InfoData.DEFAULT.title), diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt index 36f5dda..da249d1 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt @@ -17,12 +17,12 @@ class OpenApiServer { */ var url: String = ServerData.DEFAULT.url - /** * An optional string describing the host designated by the URL */ var description: String? = ServerData.DEFAULT.description + fun build(base: ServerData) = ServerData( url = mergeDefault(base.url, url, ServerData.DEFAULT.url), description = merge(base.description, description) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt index 952deef..1f162e0 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt @@ -20,13 +20,11 @@ class OpenApiTag( */ var description: String? = null - /** * A short description of additional external documentation for this tag. */ var externalDocDescription: String? = null - /** *The URL for additional external documentation for this tag. */ diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt index 25cdcd7..d0d0664 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenIdOAuthFlow.kt @@ -15,19 +15,16 @@ class OpenIdOAuthFlow { */ var authorizationUrl: String? = null - /** * The token URL to be used for this flow */ var tokenUrl: String? = null - /** * The URL to be used for obtaining refresh tokens */ var refreshUrl: String? = null - /** * The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it */ diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt index 6954fcc..080e6f6 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt @@ -1,6 +1,5 @@ package io.github.smiley4.ktorswaggerui.dsl.config -import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.github.smiley4.ktorswaggerui.data.* import io.github.smiley4.ktorswaggerui.data.DataUtils.merge import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @@ -159,12 +158,20 @@ class PluginConfigDsl { private val schemaConfig = SchemaConfig() + + /** + * Configure schemas + */ fun schemas(block: SchemaConfig.() -> Unit) { schemaConfig.also(block) } private val exampleConfig = ExampleConfig() + + /** + * Configure examples + */ fun examples(block: ExampleConfig.() -> Unit) { exampleConfig.apply(block) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt index c61cbdc..15b68d5 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt @@ -69,7 +69,7 @@ class SwaggerUIDsl { showTagFilterInput = mergeBoolean(base.showTagFilterInput, this.showTagFilterInput), sort = mergeDefault(base.sort, this.sort, SwaggerUIData.DEFAULT.sort), syntaxHighlight = mergeDefault(base.syntaxHighlight, this.syntaxHighlight, SwaggerUIData.DEFAULT.syntaxHighlight), - withCredentials = base.withCredentials + withCredentials = mergeBoolean(base.withCredentials, this.withCredentials) ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt index e46f90d..f6e3408 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt @@ -23,13 +23,11 @@ sealed class OpenApiBaseBody { /** * Allowed Media Types for this body. If none specified, a media type will be chosen automatically based on the provided schema */ - private val mediaTypes = mutableSetOf() + protected val mediaTypes = mutableSetOf() fun mediaType(type: ContentType) { mediaTypes.add(type) } - fun getMediaTypes(): Set = mediaTypes - abstract fun build(): OpenApiBaseBodyData } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt index 150a89b..f31a4e5 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt @@ -14,9 +14,6 @@ class OpenApiMultipartBody : OpenApiBaseBody() { private val parts = mutableListOf() - fun getParts(): List = parts - - /** * One part of a multipart-body */ @@ -71,7 +68,7 @@ class OpenApiMultipartBody : OpenApiBaseBody() { override fun build() = OpenApiMultipartBodyData( description = description, required = required ?: false, - mediaTypes = getMediaTypes(), - parts = getParts().map { it.build() } + mediaTypes = mediaTypes, + parts = parts.map { it.build() } ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt index 1f4bb8f..06a0316 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt @@ -26,8 +26,6 @@ class OpenApiMultipartPart( private val headers = mutableMapOf() - fun getHeaders(): Map = headers - /** * Possible headers for this part diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt index f802b4c..49c40cb 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt @@ -20,9 +20,6 @@ class OpenApiResponse(val statusCode: String) { private val headers = mutableMapOf() - fun getHeaders(): Map = headers - - /** * Possible headers returned with this response */ @@ -59,9 +56,6 @@ class OpenApiResponse(val statusCode: String) { private var body: OpenApiBaseBody? = null - fun getBody() = body - - /** * The body returned with this response */ diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt index 328335f..8960585 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt @@ -30,15 +30,13 @@ class OpenApiSimpleBody( examples.add(example) } - fun getExamples(): List = examples - override fun build() = OpenApiSimpleBodyData( description = description, required = required ?: false, - mediaTypes = getMediaTypes(), + mediaTypes = mediaTypes, type = type, - examples = getExamples(), + examples = examples, ) } From 68552b9bc899bdffd819f6f983e289b4f64163a5 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 9 May 2024 13:42:54 +0200 Subject: [PATCH 10/32] cleanup config --- .../ktorswaggerui/examples/CompleteConfig.kt | 169 ++++++++++++++++++ ktor-swagger-ui/NOTES.txt | 8 - .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 2 +- .../ktorswaggerui/data/PluginConfigData.kt | 22 +-- ...{WhenBuildOpenApiSpecs.kt => PostBuild.kt} | 2 +- .../ktorswaggerui/data/SecurityData.kt | 15 ++ .../data/{SpecAssigned.kt => SpecAssigner.kt} | 0 .../smiley4/ktorswaggerui/data/TagsData.kt | 15 ++ .../dsl/config/OpenApiSecurity.kt | 54 ++++++ .../ktorswaggerui/dsl/config/OpenApiTags.kt | 42 +++++ .../dsl/config/PluginConfigDsl.kt | 161 ++++++----------- .../ktorswaggerui/dsl/config/SwaggerUIDsl.kt | 3 - .../smiley4/ktorswaggerui/routing/ApiSpec.kt | 2 +- 13 files changed, 359 insertions(+), 136 deletions(-) create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt delete mode 100644 ktor-swagger-ui/NOTES.txt rename ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/{WhenBuildOpenApiSpecs.kt => PostBuild.kt} (73%) create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt rename ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/{SpecAssigned.kt => SpecAssigner.kt} (100%) create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurity.kt create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTags.kt diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt new file mode 100644 index 0000000..631acc8 --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt @@ -0,0 +1,169 @@ +package io.github.smiley4.ktorswaggerui.examples + +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.data.AuthScheme +import io.github.smiley4.ktorswaggerui.data.AuthType +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort +import io.github.smiley4.ktorswaggerui.data.SwaggerUiSyntaxHighlight +import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor +import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl +import io.github.smiley4.ktorswaggerui.dsl.routing.get +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.github.smiley4.schemakenerator.reflection.processReflection +import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot +import io.github.smiley4.schemakenerator.swagger.data.TitleType +import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema +import io.github.smiley4.schemakenerator.swagger.withAutoTitle +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +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 io.swagger.v3.oas.models.media.Schema +import java.io.File +import kotlin.reflect.typeOf + +fun main() { + embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) +} + + +/** + * A (nearly) complete - and mostly nonsensical - plugin configuration + */ +private fun Application.myModule() { + + install(SwaggerUI) { + info { + title = "Example API" + version = "latest" + description = "An example api." + termsOfService = "example.com" + contact { + name = "Mr. Example" + url = "example.com" + email = "example@example.com" + } + license { + name = "Example License" + url = "example.com" + } + } + externalDocs { + url = "example.com" + description = "Project documentation" + } + server { + url = "localhost" + description = "local dev-server" + } + server { + url = "example.com" + description = "productive server" + } + swagger { + displayOperationId = true + showTagFilterInput = true + sort = SwaggerUiSort.HTTP_METHOD + syntaxHighlight = SwaggerUiSyntaxHighlight.MONOKAI + withCredentials = false + } + security { + defaultUnauthorizedResponse { + description = "Username or password is invalid" + } + defaultSecuritySchemeNames = setOf("MySecurityScheme") + securityScheme("MySecurityScheme") { + type = AuthType.HTTP + scheme = AuthScheme.BASIC + } + } + tags { + tagGenerator = { url -> listOf(url.firstOrNull()) } + tag("users") { + description = "routes to manage users" + externalDocUrl = "example.com" + externalDocDescription = "Users documentation" + } + tag("documents") { + description = "routes to manage documents" + externalDocUrl = "example.com" + externalDocDescription = "Document documentation" + } + } + schemas { + schema("string", KTypeDescriptor(typeOf())) + generator = { type -> + type + .processReflection() + .generateSwaggerSchema() + .withAutoTitle(TitleType.SIMPLE) + .compileReferencingRoot() + } + overwrite[typeOf()] = SwaggerTypeDescriptor( + Schema().also { + it.type = "string" + it.format = "binary" + } + ) + } + examples { + example( + ValueExampleDescriptor( + name = "Id 1", + description = "First example id", + value = "12345" + ) + ) + example( + ValueExampleDescriptor( + name = "Id 2", + description = "Second example id", + value = "54321" + ) + ) + } + specAssigner = { url, tags -> PluginConfigDsl.DEFAULT_SPEC_ID } + pathFilter = { method, url -> url.firstOrNull() != "hidden" } + ignoredRouteSelectors = emptySet() + postBuild = { api -> println("Completed api: $api") } + } + + + routing { + + // add the routes for swagger-ui and api-spec + route("swagger") { + swaggerUI("/api.json") + } + route("api.json") { + openApiSpec() + } + + // a documented route + get("hello", { + description = "A Hello-World route" + request { + queryParameter("name", KTypeDescriptor(typeOf())) { + description = "the name to greet" + } + } + response { + HttpStatusCode.OK to { + description = "successful request - always returns 'Hello World!'" + } + } + }) { + call.respondText("Hello ${call.request.queryParameters["name"]}") + } + + } + +} diff --git a/ktor-swagger-ui/NOTES.txt b/ktor-swagger-ui/NOTES.txt deleted file mode 100644 index 4302830..0000000 --- a/ktor-swagger-ui/NOTES.txt +++ /dev/null @@ -1,8 +0,0 @@ -- clean separation between /data and /dsl -- cleanup /dsl -> remove no longer required getters, make fields private, etc. -- cleaner handling of custom schemas, etc -- cleaner handling of schema generation -> schema-kenerator + nicer config -- drop automatic routing -- general cleanup -- cleaner schema & examples config -> top level "schema", "examples" -- define examples in config and only ref by name \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index 7fcdeaf..83d674d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -97,7 +97,7 @@ private fun buildOpenApiSpec(pluginConfig: PluginConfigData, routes: List, - val tagGenerator: TagGenerator, val specAssigner: SpecAssigner, val pathFilter: PathFilter, val ignoredRouteSelectors: Set>, - val swaggerUI: SwaggerUIData, + val swagger: SwaggerUIData, val info: InfoData, val servers: List, val externalDocs: ExternalDocsData, - val securitySchemes: List, - val tags: List, val specConfigs: MutableMap, - val whenBuildOpenApiSpecs: WhenBuildOpenApiSpecs?, + val postBuild: PostBuild?, val schemaConfig: SchemaConfigData, val exampleConfig: ExampleConfigData, + val securityConfig: SecurityData, + val tagsConfig: TagsData ) { companion object { val DEFAULT = PluginConfigData( - defaultUnauthorizedResponse = null, - defaultSecuritySchemeNames = emptySet(), - tagGenerator = { emptyList() }, specAssigner = { _, _ -> PluginConfigDsl.DEFAULT_SPEC_ID }, pathFilter = { _, _ -> true }, ignoredRouteSelectors = emptySet(), - swaggerUI = SwaggerUIData.DEFAULT, + swagger = SwaggerUIData.DEFAULT, info = InfoData.DEFAULT, servers = emptyList(), externalDocs = ExternalDocsData.DEFAULT, - securitySchemes = emptyList(), - tags = emptyList(), specConfigs = mutableMapOf(), - whenBuildOpenApiSpecs = null, + postBuild = null, schemaConfig = SchemaConfigData.DEFAULT, exampleConfig = ExampleConfigData.DEFAULT, + securityConfig = SecurityData.DEFAULT, + tagsConfig = TagsData.DEFAULT ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/WhenBuildOpenApiSpecs.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PostBuild.kt similarity index 73% rename from ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/WhenBuildOpenApiSpecs.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PostBuild.kt index 175ddc9..3865e46 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/WhenBuildOpenApiSpecs.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PostBuild.kt @@ -5,4 +5,4 @@ import io.swagger.v3.oas.models.OpenAPI /** * @author yuefeng in 2024/3/25. */ -typealias WhenBuildOpenApiSpecs = (openApi: OpenAPI) -> Unit +typealias PostBuild = (openApi: OpenAPI) -> Unit diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt new file mode 100644 index 0000000..25b9f48 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt @@ -0,0 +1,15 @@ +package io.github.smiley4.ktorswaggerui.data + +data class SecurityData( + val defaultUnauthorizedResponse: OpenApiResponseData?, + val defaultSecuritySchemeNames: Set, + val securitySchemes: List, +) { + companion object { + val DEFAULT = SecurityData( + defaultUnauthorizedResponse = null, + defaultSecuritySchemeNames = emptySet(), + securitySchemes = emptyList() + ) + } +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigned.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigner.kt similarity index 100% rename from ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigned.kt rename to ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigner.kt diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt new file mode 100644 index 0000000..b628fbe --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt @@ -0,0 +1,15 @@ +package io.github.smiley4.ktorswaggerui.data + +data class TagsData( + val tags: List, + val generator: TagGenerator, +) { + + companion object { + val DEFAULT = TagsData( + tags = emptyList(), + generator = { emptyList() } + ) + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurity.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurity.kt new file mode 100644 index 0000000..02ad1a9 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurity.kt @@ -0,0 +1,54 @@ +package io.github.smiley4.ktorswaggerui.dsl.config + +import io.github.smiley4.ktorswaggerui.data.DataUtils.merge +import io.github.smiley4.ktorswaggerui.data.SecurityData +import io.github.smiley4.ktorswaggerui.data.SecuritySchemeData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiResponse +import io.ktor.http.HttpStatusCode + +/** + * Configuration for security and authentication. + */ +@OpenApiDslMarker +class OpenApiSecurity { + + /** + * Default response to automatically add to each protected route for the "Unauthorized"-Response-Code. + * Generated response can be overwritten with custom response. + */ + fun defaultUnauthorizedResponse(block: OpenApiResponse.() -> Unit) { + defaultUnauthorizedResponse = OpenApiResponse(HttpStatusCode.Unauthorized.value.toString()).apply(block) + } + + private var defaultUnauthorizedResponse: OpenApiResponse? = null + + + /** + * The names of the security schemes available for use for the protected paths + */ + var defaultSecuritySchemeNames: Collection? = SecurityData.DEFAULT.defaultSecuritySchemeNames + + + /** + * Defines security schemes that can be used by operations + */ + fun securityScheme(name: String, block: OpenApiSecurityScheme.() -> Unit) { + securitySchemes.add(OpenApiSecurityScheme(name).apply(block)) + } + + private val securitySchemes = mutableListOf() + + fun build(base: SecurityData) = SecurityData( + defaultUnauthorizedResponse = merge(base.defaultUnauthorizedResponse, defaultUnauthorizedResponse?.build()), + defaultSecuritySchemeNames = buildSet { + addAll(base.defaultSecuritySchemeNames) + defaultSecuritySchemeNames?.also { addAll(it) } + }, + securitySchemes = buildList { + addAll(base.securitySchemes) + addAll(securitySchemes.map { it.build(SecuritySchemeData.DEFAULT) }) + } + ) + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTags.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTags.kt new file mode 100644 index 0000000..8d2928b --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTags.kt @@ -0,0 +1,42 @@ +package io.github.smiley4.ktorswaggerui.dsl.config + +import io.github.smiley4.ktorswaggerui.data.DataUtils.merge +import io.github.smiley4.ktorswaggerui.data.PluginConfigData +import io.github.smiley4.ktorswaggerui.data.TagData +import io.github.smiley4.ktorswaggerui.data.TagGenerator +import io.github.smiley4.ktorswaggerui.data.TagsData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker + +/** + * Configuration for tags + */ +@OpenApiDslMarker +class OpenApiTags { + + private val tags = mutableListOf() + + + /** + * Tags used by the specification with additional metadata. Not all tags that are used must be declared + */ + fun tag(name: String, block: OpenApiTag.() -> Unit) { + tags.add(OpenApiTag(name).apply(block)) + } + + + /** + * Automatically add tags to the route with the given url. + * The returned (non-null) tags will be added to the tags specified in the route-specific documentation. + */ + var tagGenerator: TagGenerator = TagsData.DEFAULT.generator + + + fun build(base: TagsData) = TagsData( + tags = buildList { + addAll(base.tags) + addAll(tags.map { it.build(TagData.DEFAULT) }) + }, + generator = merge(base.generator, tagGenerator) ?: TagsData.DEFAULT.generator, + ) + +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt index 080e6f6..addbd55 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/PluginConfigDsl.kt @@ -3,19 +3,10 @@ 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.github.smiley4.ktorswaggerui.dsl.routes.OpenApiResponse import io.ktor.http.* import io.ktor.server.routing.* -import kotlin.collections.Collection -import kotlin.collections.Set -import kotlin.collections.buildList -import kotlin.collections.buildSet import kotlin.collections.component1 import kotlin.collections.component2 -import kotlin.collections.forEach -import kotlin.collections.map -import kotlin.collections.mutableListOf -import kotlin.collections.mutableMapOf import kotlin.collections.set import kotlin.reflect.KClass @@ -30,58 +21,34 @@ class PluginConfigDsl { } - private val specConfigs = mutableMapOf() - - fun spec(specId: String, block: PluginConfigDsl.() -> Unit) { - specConfigs[specId] = PluginConfigDsl().apply(block) - } - - /** - * Default response to automatically add to each protected route for the "Unauthorized"-Response-Code. - * Generated response can be overwritten with custom response. + * OpenAPI info configuration - provides metadata about the API */ - fun defaultUnauthorizedResponse(block: OpenApiResponse.() -> Unit) { - defaultUnauthorizedResponse = OpenApiResponse(HttpStatusCode.Unauthorized.value.toString()).apply(block) + fun info(block: OpenApiInfo.() -> Unit) { + info = OpenApiInfo().apply(block) } - private var defaultUnauthorizedResponse: OpenApiResponse? = null - - - /** - * The name of the security scheme to use for the protected paths - */ - var defaultSecuritySchemeName: String? = null - - - /** - * The names of the security schemes available for use for the protected paths - */ - var defaultSecuritySchemeNames: Collection? = PluginConfigData.DEFAULT.defaultSecuritySchemeNames + private var info = OpenApiInfo() /** - * Automatically add tags to the route with the given url. - * The returned (non-null) tags will be added to the tags specified in the route-specific documentation. + * OpenAPI external docs configuration - link and description of an external documentation */ - fun generateTags(generator: TagGenerator) { - tagGenerator = generator + fun externalDocs(block: OpenApiExternalDocs.() -> Unit) { + externalDocs = OpenApiExternalDocs().apply(block) } - private var tagGenerator: TagGenerator? = PluginConfigData.DEFAULT.tagGenerator + private var externalDocs = OpenApiExternalDocs() /** - * Assigns routes without an [io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute.specId] to a specified openapi-spec. + * OpenAPI server configuration - an array of servers, which provide connectivity information to a target server */ - var specAssigner: SpecAssigner? = PluginConfigData.DEFAULT.specAssigner - + fun server(block: OpenApiServer.() -> Unit) { + servers.add(OpenApiServer().apply(block)) + } - /** - * Filter to apply to all routes. Return 'false' for routes to not include them in the OpenApi-Spec and Swagger-UI. - * The url of the paths are already split at '/'. - */ - var pathFilter: PathFilter? = PluginConfigData.DEFAULT.pathFilter + private val servers = mutableListOf() /** @@ -95,122 +62,100 @@ class PluginConfigDsl { /** - * OpenAPI info configuration - provides metadata about the API + * Configuration for security and authentication. */ - fun info(block: OpenApiInfo.() -> Unit) { - info = OpenApiInfo().apply(block) + fun security(block: OpenApiSecurity.() -> Unit) { + security.apply(block) } - private var info = OpenApiInfo() + private val security = OpenApiSecurity() /** - * OpenAPI server configuration - an array of servers, which provide connectivity information to a target server + * Configuration for openapi-tags */ - fun server(block: OpenApiServer.() -> Unit) { - servers.add(OpenApiServer().apply(block)) + fun tags(block: OpenApiTags.() -> Unit) { + tags.also(block) } - private val servers = mutableListOf() + private val tags = OpenApiTags() /** - * OpenAPI external docs configuration - link and description of an external documentation + * Configure schemas */ - fun externalDocs(block: OpenApiExternalDocs.() -> Unit) { - externalDocs = OpenApiExternalDocs().apply(block) + fun schemas(block: SchemaConfig.() -> Unit) { + schemaConfig.also(block) } - private var externalDocs = OpenApiExternalDocs() + private val schemaConfig = SchemaConfig() /** - * Defines security schemes that can be used by operations + * Configure examples */ - fun securityScheme(name: String, block: OpenApiSecurityScheme.() -> Unit) { - securitySchemes.add(OpenApiSecurityScheme(name).apply(block)) + fun examples(block: ExampleConfig.() -> Unit) { + exampleConfig.apply(block) } - private val securitySchemes = mutableListOf() + private val exampleConfig = ExampleConfig() /** - * Tags used by the specification with additional metadata. Not all tags that are used must be declared + * Configure specific separate specs */ - fun tag(name: String, block: OpenApiTag.() -> Unit) { - tags.add(OpenApiTag(name).apply(block)) + fun spec(specId: String, block: PluginConfigDsl.() -> Unit) { + specConfigs[specId] = PluginConfigDsl().apply(block) } - private val tags = mutableListOf() - + private val specConfigs = mutableMapOf() /** - * List of all [RouteSelector] types in that should be ignored in the resulting url of any route. + * Assigns routes without an [io.github.smiley4.ktorswaggerui.dsl.OpenApiRoute.specId] to a specified openapi-spec. */ - var ignoredRouteSelectors: Set> = PluginConfigData.DEFAULT.ignoredRouteSelectors + var specAssigner: SpecAssigner? = PluginConfigData.DEFAULT.specAssigner /** - * Invoked after generating the openapi-spec. Can be to e.g. further customize the spec. + * Filter to apply to all routes. Return 'false' for routes to not include them in the OpenApi-Spec and Swagger-UI. + * The url of the paths are already split at '/'. */ - var whenBuildOpenApiSpecs: WhenBuildOpenApiSpecs? = null - - - private val schemaConfig = SchemaConfig() + var pathFilter: PathFilter? = PluginConfigData.DEFAULT.pathFilter /** - * Configure schemas + * List of all [RouteSelector] types in that should be ignored in the resulting url of any route. */ - fun schemas(block: SchemaConfig.() -> Unit) { - schemaConfig.also(block) - } - - private val exampleConfig = ExampleConfig() + var ignoredRouteSelectors: Set> = PluginConfigData.DEFAULT.ignoredRouteSelectors /** - * Configure examples + * Invoked after generating the openapi-spec. Can be to e.g. further customize the spec. */ - fun examples(block: ExampleConfig.() -> Unit) { - exampleConfig.apply(block) - } + var postBuild: PostBuild? = null internal fun build(base: PluginConfigData): PluginConfigData { return PluginConfigData( - defaultUnauthorizedResponse = merge(base.defaultUnauthorizedResponse, defaultUnauthorizedResponse?.build()), - defaultSecuritySchemeNames = buildSet { - addAll(base.defaultSecuritySchemeNames) - defaultSecuritySchemeNames?.also { addAll(it) } - defaultSecuritySchemeName?.also { add(it) } + info = info.build(base.info), + externalDocs = externalDocs.build(base.externalDocs), + servers = buildList { + addAll(base.servers) + addAll(servers.map { it.build(ServerData.DEFAULT) }) }, - tagGenerator = merge(base.tagGenerator, tagGenerator) ?: PluginConfigData.DEFAULT.tagGenerator, + swagger = swaggerUI.build(base.swagger), + securityConfig = security.build(base.securityConfig), + tagsConfig = tags.build(base.tagsConfig), + schemaConfig = schemaConfig.build(), + exampleConfig = exampleConfig.build(), specAssigner = merge(base.specAssigner, specAssigner) ?: PluginConfigData.DEFAULT.specAssigner, pathFilter = merge(base.pathFilter, pathFilter) ?: PluginConfigData.DEFAULT.pathFilter, ignoredRouteSelectors = buildSet { addAll(base.ignoredRouteSelectors) addAll(ignoredRouteSelectors) }, - swaggerUI = swaggerUI.build(base.swaggerUI), - info = info.build(base.info), - servers = buildList { - addAll(base.servers) - addAll(servers.map { it.build(ServerData.DEFAULT) }) - }, - externalDocs = externalDocs.build(base.externalDocs), - securitySchemes = buildList { - addAll(base.securitySchemes) - addAll(securitySchemes.map { it.build(SecuritySchemeData.DEFAULT) }) - }, - tags = buildList { - addAll(base.tags) - addAll(tags.map { it.build(TagData.DEFAULT) }) - }, specConfigs = mutableMapOf(), - whenBuildOpenApiSpecs = whenBuildOpenApiSpecs, - schemaConfig = schemaConfig.build(), - exampleConfig = exampleConfig.build() + postBuild = postBuild, ).also { specConfigs.forEach { (specId, config) -> it.specConfigs[specId] = config.build(it) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt index 15b68d5..53c54c4 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt @@ -31,9 +31,6 @@ class SwaggerUIDsl { specValidator("https://validator.swagger.io/validator") } - fun getSpecValidatorUrl() = validatorUrl - - /** * Whether to show the operation-id of endpoints in the list */ diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt index 5d97431..3e0a897 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt @@ -14,7 +14,7 @@ object ApiSpec { } fun get(name: String): String { - return apiSpecs[name] ?: throw NoSuchElementException("No api-spec with name $name registered.") + return apiSpecs[name] ?: throw NoSuchElementException("No api-spec with name '$name' registered.") } fun getAll(): Map { From 2e12f8cf50a773368afbda60bef8b2720771ef52 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 9 May 2024 15:47:13 +0200 Subject: [PATCH 11/32] add tests --- ktor-swagger-ui-examples/build.gradle.kts | 2 - ktor-swagger-ui/build.gradle.kts | 5 +- .../builder/openapi/ComponentsBuilder.kt | 4 +- .../builder/openapi/OpenApiBuilder.kt | 2 +- .../builder/openapi/OperationTagsBuilder.kt | 2 +- .../builder/openapi/ResponsesBuilder.kt | 4 +- .../openapi/SecurityRequirementsBuilder.kt | 2 +- .../io/github/smiley4/ktorswaggerui/Test.kt | 47 - .../builder/ExternalDocsBuilderTest.kt | 39 + .../ktorswaggerui/builder/InfoBuilderTest.kt | 80 ++ .../builder/OpenApiBuilderTest.kt | 176 +++ .../builder/OperationBuilderTest.kt | 998 ++++++++++++++++++ .../ktorswaggerui/builder/PathsBuilderTest.kt | 143 +++ .../builder/SecuritySchemesBuilderTest.kt | 210 ++++ .../builder/ServersBuilderTest.kt | 44 + .../ktorswaggerui/builder/TagsBuilderTest.kt | 55 + .../misc/RouteDocumentationMergerTest.kt | 136 +++ .../ktorswaggerui/misc/RoutingTests.kt | 106 ++ 18 files changed, 1996 insertions(+), 59 deletions(-) delete mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/SecuritySchemesBuilderTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt create mode 100644 ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts index 1a86424..24f2e53 100644 --- a/ktor-swagger-ui-examples/build.gradle.kts +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -36,6 +36,4 @@ dependencies { val logbackVersion = "1.4.11" implementation("ch.qos.logback:logback-classic:$logbackVersion") - - } diff --git a/ktor-swagger-ui/build.gradle.kts b/ktor-swagger-ui/build.gradle.kts index 4bde684..e804020 100644 --- a/ktor-swagger-ui/build.gradle.kts +++ b/ktor-swagger-ui/build.gradle.kts @@ -29,9 +29,6 @@ dependencies { val kotlinLoggingVersion = "3.0.5" implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") - val versionMockk = "1.13.8" - testImplementation("io.mockk:mockk:$versionMockk") - val versionKotest = "5.8.0" testImplementation("io.kotest:kotest-runner-junit5:$versionKotest") testImplementation("io.kotest:kotest-assertions-core:$versionKotest") @@ -39,4 +36,6 @@ dependencies { val versionKotlinTest = "1.8.21" testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest") + val versionMockk = "1.13.8" + testImplementation("io.mockk:mockk:$versionMockk") } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt index 7d09097..4404018 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt @@ -14,8 +14,8 @@ class ComponentsBuilder( return Components().also { it.schemas = schemas it.examples = examples - if (config.securitySchemes.isNotEmpty()) { - it.securitySchemes = securitySchemesBuilder.build(config.securitySchemes) + if (config.securityConfig.securitySchemes.isNotEmpty()) { + it.securitySchemes = securitySchemesBuilder.build(config.securityConfig.securitySchemes) } } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt index 4259417..fc73482 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt @@ -23,7 +23,7 @@ class OpenApiBuilder( it.info = infoBuilder.build(config.info) it.externalDocs = externalDocumentationBuilder.build(config.externalDocs) it.servers = config.servers.map { server -> serverBuilder.build(server) } - it.tags = config.tags.map { tag -> tagBuilder.build(tag) } + it.tags = config.tagsConfig.tags.map { tag -> tagBuilder.build(tag) } it.paths = pathsBuilder.build(routes) it.components = componentsBuilder.build(schemaContext.getComponentSection(), exampleContext.getComponentSection()) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt index 962ae04..f941bff 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt @@ -16,6 +16,6 @@ class OperationTagsBuilder( private fun getRouteTags(route: RouteMeta) = route.documentation.tags - private fun getGeneratedTags(route: RouteMeta) = config.tagGenerator(route.path.split("/").filter { it.isNotEmpty() }) + private fun getGeneratedTags(route: RouteMeta) = config.tagsConfig.generator(route.path.split("/").filter { it.isNotEmpty() }) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt index cf352ca..3078c2b 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt @@ -16,7 +16,7 @@ class ResponsesBuilder( .map { response -> responseBuilder.build(response) } .forEach { (name, response) -> it.addApiResponse(name, response) } if (shouldAddUnauthorized(responses, isProtected)) { - config.defaultUnauthorizedResponse + config.securityConfig.defaultUnauthorizedResponse ?.let { response -> responseBuilder.build(response) } ?.also { (name, response) -> it.addApiResponse(name, response) } } @@ -24,7 +24,7 @@ class ResponsesBuilder( private fun shouldAddUnauthorized(responses: List, isProtected: Boolean): Boolean { val unauthorizedCode = HttpStatusCode.Unauthorized.value.toString(); - return config.defaultUnauthorizedResponse != null + return config.securityConfig.defaultUnauthorizedResponse != null && isProtected && responses.count { it.statusCode == unauthorizedCode } == 0 } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt index 4166a66..db143ec 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt @@ -12,7 +12,7 @@ class SecurityRequirementsBuilder( val securitySchemes = buildSet { addAll(route.documentation.securitySchemeNames) if(route.documentation.securitySchemeNames.isEmpty()) { - addAll(config.defaultSecuritySchemeNames) + addAll(config.securityConfig.defaultSecuritySchemeNames) } } return securitySchemes.map { diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt deleted file mode 100644 index 357edda..0000000 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/Test.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.smiley4.ktorswaggerui - -import io.github.smiley4.ktorswaggerui.dsl.routing.resources.get -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.response.respondText -import io.ktor.server.routing.routing - -/** - * A minimal working example - */ -fun main() { - embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) -} - -private fun Application.myModule() { - install(SwaggerUI) - - routing { - get("hello", { - request { - body { - example("example", 42) - } - } - description = "Simple 'Hello World'- Route" - response { - HttpStatusCode.OK to { - body() - description = "Successful Response" - } - } - }) { - call.respondText("Hello World!") - } - } -} - - -class MyClass( - val size: Int, - val tags: List -) \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt new file mode 100644 index 0000000..b3ac3ba --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt @@ -0,0 +1,39 @@ +package io.github.smiley4.ktorswaggerui.builder + +import io.github.smiley4.ktorswaggerui.data.ExternalDocsData +import io.github.smiley4.ktorswaggerui.builder.openapi.ExternalDocumentationBuilder +import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiExternalDocs +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import io.swagger.v3.oas.models.ExternalDocumentation + +class ExternalDocsBuilderTest : StringSpec({ + + "default external docs object" { + buildExternalDocsObject {}.also { docs -> + docs.url shouldBe "/" + docs.description shouldBe null + } + } + + "complete server object" { + buildExternalDocsObject { + url = "Test URL" + description = "Test Description" + }.also { docs -> + docs.url shouldBe "Test URL" + docs.description shouldBe "Test Description" + } + } + +}) { + + companion object { + + private fun buildExternalDocsObject(builder: OpenApiExternalDocs.() -> Unit): ExternalDocumentation { + return ExternalDocumentationBuilder().build(OpenApiExternalDocs().apply(builder).build(ExternalDocsData.DEFAULT)) + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt new file mode 100644 index 0000000..edffd08 --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt @@ -0,0 +1,80 @@ +package io.github.smiley4.ktorswaggerui.builder +import io.github.smiley4.ktorswaggerui.data.InfoData +import io.github.smiley4.ktorswaggerui.builder.openapi.ContactBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.InfoBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.LicenseBuilder +import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiInfo +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.swagger.v3.oas.models.info.Info + + +class InfoBuilderTest : StringSpec({ + + "empty info object" { + buildInfoObject {}.also { info -> + info.title shouldBe "API" + info.version shouldBe "latest" + info.description shouldBe null + info.termsOfService shouldBe null + info.contact shouldBe null + info.license shouldBe null + info.extensions shouldBe null + info.summary shouldBe null + } + } + + "full info object" { + buildInfoObject { + title = "Test Api" + version = "1.0" + description = "Api for testing" + termsOfService = "test-tos" + contact { + name = "Test Person" + url = "example.com" + email = "test.mail" + + } + license { + name = "Test License" + url = "example.com" + } + }.also { info -> + info.title shouldBe "Test Api" + info.version shouldBe "1.0" + info.description shouldBe "Api for testing" + info.termsOfService shouldBe "test-tos" + info.contact + .also { contact -> contact.shouldNotBeNull() } + ?.also { contact -> + contact.name shouldBe "Test Person" + contact.url shouldBe "example.com" + contact.email shouldBe "test.mail" + } + info.license + .also { license -> license.shouldNotBeNull() } + ?.also { license -> + license.name shouldBe "Test License" + license.url shouldBe "example.com" + } + info.extensions shouldBe null + info.summary shouldBe null + } + } + +}) { + + companion object { + + private fun buildInfoObject(builder: OpenApiInfo.() -> Unit): Info { + return InfoBuilder( + contactBuilder = ContactBuilder(), + licenseBuilder = LicenseBuilder() + ).build(OpenApiInfo().apply(builder).build(InfoData.DEFAULT)) + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt new file mode 100644 index 0000000..074ec00 --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt @@ -0,0 +1,176 @@ +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.ComponentsBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.ContactBuilder +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.InfoBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.LicenseBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.OAuthFlowsBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.OpenApiBuilder +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.PathBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.PathsBuilder +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.SecuritySchemesBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.ServerBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.TagBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.TagExternalDocumentationBuilder +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.dsl.config.PluginConfigDsl +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.maps.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.swagger.v3.oas.models.OpenAPI + + +class OpenApiBuilderTest : StringSpec({ + + "default openapi object" { + buildOpenApiObject(emptyList()).also { openapi -> + openapi.info shouldNotBe null + openapi.extensions shouldBe null + openapi.servers shouldHaveSize 0 + openapi.externalDocs shouldNotBe null + openapi.security shouldBe null + openapi.tags shouldHaveSize 0 + openapi.paths shouldHaveSize 0 + openapi.components shouldNotBe null + openapi.extensions shouldBe null + } + } + + "multiple servers" { + val config = PluginConfigDsl().also { + it.server { + url = "http://localhost:8080" + description = "Development Server" + } + it.server { + url = "https://127.0.0.1" + description = "Production Server" + } + } + buildOpenApiObject(emptyList(), config).also { openapi -> + openapi.servers shouldHaveSize 2 + openapi.servers.map { it.url } shouldContainExactlyInAnyOrder listOf( + "http://localhost:8080", + "https://127.0.0.1" + ) + } + } + + "multiple tags" { + val config = PluginConfigDsl().also { + it.tags { + tag("tag-1") { + description = "first test tag" + } + tag("tag-2") { + description = "second test tag" + } + } + } + buildOpenApiObject(emptyList(), config).also { openapi -> + openapi.tags shouldHaveSize 2 + openapi.tags.map { it.name } shouldContainExactlyInAnyOrder listOf( + "tag-1", + "tag-2" + ) + } + } + +}) { + + companion object { + + private val defaultPluginConfig = PluginConfigDsl() + + private fun schemaContext(routes: List, pluginConfig: PluginConfigDsl): SchemaContext { + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return SchemaContextImpl(pluginConfigData.schemaConfig).also { + it.addGlobal(pluginConfigData.schemaConfig) + it.add(routes) + } + } + + private fun exampleContext(routes: List, pluginConfig: PluginConfigDsl): ExampleContext { + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return ExampleContextImpl().also { + it.addGlobal(pluginConfigData.exampleConfig) + it.add(routes) + } + } + + private fun buildOpenApiObject(routes: List, pluginConfig: PluginConfigDsl = defaultPluginConfig): OpenAPI { + val schemaContext = schemaContext(routes, pluginConfig) + val exampleContext = exampleContext(routes, pluginConfig) + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return OpenApiBuilder( + config = pluginConfigData, + schemaContext = schemaContext, + exampleContext = exampleContext, + infoBuilder = InfoBuilder( + contactBuilder = ContactBuilder(), + licenseBuilder = LicenseBuilder() + ), + externalDocumentationBuilder = ExternalDocumentationBuilder(), + serverBuilder = ServerBuilder(), + tagBuilder = TagBuilder( + tagExternalDocumentationBuilder = TagExternalDocumentationBuilder() + ), + pathsBuilder = PathsBuilder( + pathBuilder = PathBuilder( + operationBuilder = OperationBuilder( + operationTagsBuilder = OperationTagsBuilder(pluginConfigData), + parameterBuilder = ParameterBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext + ), + requestBodyBuilder = RequestBodyBuilder( + contentBuilder = ContentBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext, + headerBuilder = HeaderBuilder(schemaContext) + ) + ), + responsesBuilder = ResponsesBuilder( + responseBuilder = ResponseBuilder( + headerBuilder = HeaderBuilder(schemaContext), + contentBuilder = ContentBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext, + headerBuilder = HeaderBuilder(schemaContext) + ) + ), + config = pluginConfigData + ), + securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), + ) + ) + ), + componentsBuilder = ComponentsBuilder( + config = pluginConfigData, + securitySchemesBuilder = SecuritySchemesBuilder( + oAuthFlowsBuilder = OAuthFlowsBuilder() + ) + ) + ).build(routes) + } + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt new file mode 100644 index 0000000..f3429fd --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt @@ -0,0 +1,998 @@ +package io.github.smiley4.ktorswaggerui.builder + +import com.fasterxml.jackson.databind.ObjectMapper +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.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.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.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.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.swagger.v3.oas.models.Operation +import io.swagger.v3.oas.models.media.Schema +import java.io.File +import kotlin.reflect.typeOf + +class OperationBuilderTest : StringSpec({ + + "empty operation" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.tags.shouldBeEmpty() + operation.summary shouldBe null + operation.description shouldBe null + operation.externalDocs shouldBe null + operation.operationId shouldBe null + operation.parameters.shouldBeEmpty() + operation.requestBody shouldBe null + operation.responses.shouldBeEmpty() + operation.deprecated shouldBe false + operation.security shouldBe null + operation.servers shouldBe null + operation.extensions shouldBe null + } + } + + "basic operation" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.tags = listOf("tag1", "tag2") + route.description = "route for testing" + route.summary = "this is some test route" + route.operationId = "testRoute" + route.deprecated = true + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.tags shouldContainExactlyInAnyOrder listOf("tag1", "tag2") + operation.summary shouldBe "this is some test route" + operation.description shouldBe "route for testing" + operation.externalDocs shouldBe null + operation.operationId shouldBe "testRoute" + operation.parameters.shouldBeEmpty() + operation.requestBody shouldBe null + operation.responses.shouldBeEmpty() + operation.deprecated shouldBe true + operation.security shouldBe null + operation.servers shouldBe null + operation.extensions shouldBe null + } + } + + "operation with auto-generated tags" { + val config = PluginConfigDsl().also { + it.tags { + tagGenerator = { url -> listOf(url.firstOrNull()) } + } + } + val routeA = RouteMeta( + path = "a/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.tags = listOf("defaultTag") + }.build(), + protected = false + ) + val routeB = RouteMeta( + path = "b/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.tags = listOf("defaultTag") + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(routeA, routeB), config) + val exampleContext = exampleContext(listOf(routeA, routeB), config) + buildOperationObject(routeA, schemaContext, exampleContext, config).also { operation -> + operation.tags shouldContainExactlyInAnyOrder listOf("a", "defaultTag") + } + buildOperationObject(routeB, schemaContext, exampleContext, config).also { operation -> + operation.tags shouldContainExactlyInAnyOrder listOf("b", "defaultTag") + } + } + + "protected route with security-scheme-names" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.securitySchemeNames = listOf("security1", "security2") + }.build(), + protected = true + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.security + .also { it.shouldNotBeNull() } + ?.also { security -> + security shouldHaveSize 2 + security.find { it.containsKey("security1") }.shouldNotBeNull() + security.find { it.containsKey("security2") }.shouldNotBeNull() + } + } + } + + "protected route without security-scheme-names" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().build(), + protected = true + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.tags.shouldBeEmpty() + operation.summary shouldBe null + operation.description shouldBe null + operation.externalDocs shouldBe null + operation.operationId shouldBe null + operation.parameters.shouldBeEmpty() + operation.requestBody shouldBe null + operation.responses.shouldBeEmpty() + operation.deprecated shouldBe false + operation.security shouldBe null + operation.servers shouldBe null + operation.extensions shouldBe null + } + } + + "unprotected route with security-scheme-names" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.securitySchemeNames = listOf("security1", "security2") + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.tags.shouldBeEmpty() + operation.summary shouldBe null + operation.description shouldBe null + operation.externalDocs shouldBe null + operation.operationId shouldBe null + operation.parameters.shouldBeEmpty() + operation.requestBody shouldBe null + operation.responses.shouldBeEmpty() + operation.deprecated shouldBe false + operation.security shouldBe null + operation.servers shouldBe null + operation.extensions shouldBe null + } + } + + "route with basic request" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.request { + queryParameter("queryParam", KTypeDescriptor(typeOf())) {} + pathParameter("pathParam", KTypeDescriptor(typeOf())) {} + headerParameter("headerParam", KTypeDescriptor(typeOf())) {} + body(KTypeDescriptor(typeOf>())) + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.tags.shouldBeEmpty() + operation.summary shouldBe null + operation.description shouldBe null + operation.externalDocs shouldBe null + operation.operationId shouldBe null + operation.parameters.also { parameters -> + parameters shouldHaveSize 3 + parameters.find { it.name == "queryParam" } + .also { it.shouldNotBeNull() } + ?.also { param -> + param.`in` shouldBe "query" + param.description shouldBe null + param.required shouldBe false + param.deprecated shouldBe false + param.allowEmptyValue shouldBe true + param.`$ref` shouldBe null + param.style shouldBe null + param.explode shouldBe false + param.allowReserved shouldBe true + param.schema + .also { it.shouldNotBeNull() } + ?.also { it.type = "string" } + param.example shouldBe null + param.examples shouldBe null + param.content shouldBe null + param.extensions shouldBe null + } + parameters.find { it.name == "pathParam" } + .also { it.shouldNotBeNull() } + ?.also { param -> + param.`in` shouldBe "path" + param.description shouldBe null + param.required shouldBe false + param.deprecated shouldBe false + param.allowEmptyValue shouldBe true + param.`$ref` shouldBe null + param.style shouldBe null + param.explode shouldBe false + param.allowReserved shouldBe true + param.schema + .also { it.shouldNotBeNull() } + ?.also { it.type = "integer" } + param.example shouldBe null + param.examples shouldBe null + param.content shouldBe null + param.extensions shouldBe null + } + parameters.find { it.name == "headerParam" } + .also { it.shouldNotBeNull() } + ?.also { param -> + param.`in` shouldBe "header" + param.description shouldBe null + param.required shouldBe false + param.deprecated shouldBe false + param.allowEmptyValue shouldBe true + param.`$ref` shouldBe null + param.style shouldBe null + param.explode shouldBe false + param.allowReserved shouldBe true + param.schema + .also { it.shouldNotBeNull() } + ?.also { it.type = "boolean" } + param.example shouldBe null + param.examples shouldBe null + param.content shouldBe null + param.extensions shouldBe null + } + } + operation.requestBody + .also { it.shouldNotBeNull() } + ?.also { body -> + body.description shouldBe null + body.content + .also { it.shouldNotBeNull() } + ?.also { content -> + content shouldHaveSize 1 + content["application/json"] + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema + .also { it.shouldNotBeNull() } + ?.also { schema -> + schema.type shouldBe "array" + schema.items.also { item -> item.type shouldBe "string" } + } + mediaType.example shouldBe null + mediaType.examples shouldBe null + mediaType.encoding shouldBe null + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + + } + body.required shouldBe false + body.extensions shouldBe null + body.`$ref` shouldBe null + } + operation.responses.shouldBeEmpty() + operation.deprecated shouldBe false + operation.security shouldBe null + operation.servers shouldBe null + operation.extensions shouldBe null + } + } + + "route with basic response" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.response { + "test" to { + description = "Test Response" + header("test-header", KTypeDescriptor(typeOf())) {} + body(KTypeDescriptor(typeOf>())) + } + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.tags.shouldBeEmpty() + operation.summary shouldBe null + operation.description shouldBe null + operation.externalDocs shouldBe null + operation.operationId shouldBe null + operation.parameters.shouldBeEmpty() + operation.requestBody shouldBe null + operation.responses + .also { it shouldHaveSize 1 } + .let { it["test"] } + .also { it.shouldNotBeNull() } + ?.also { response -> + response.description shouldBe "Test Response" + response.headers + .also { it.shouldNotBeNull() } + .let { it["test-header"] } + .also { it.shouldNotBeNull() } + ?.also { header -> + header.schema + .also { it.shouldNotBeNull() } + ?.also { it.type shouldBe "string" } + } + + response.content + .also { it.shouldNotBeNull() } + .let { it["application/json"] } + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema + .also { it.shouldNotBeNull() } + ?.also { schema -> + schema.type shouldBe "array" + schema.items.also { item -> item.type shouldBe "string" } + } + mediaType.example shouldBe null + mediaType.examples shouldBe null + mediaType.encoding shouldBe null + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + response.links shouldBe null + response.extensions shouldBe null + response.`$ref` shouldBe null + } + operation.deprecated shouldBe false + operation.security shouldBe null + operation.servers shouldBe null + operation.extensions shouldBe null + } + } + + "documented parameter" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.request { + queryParameter("param", KTypeDescriptor(typeOf())) { + description = "test parameter" + example = ValueExampleDescriptor(name = "Example", value = "MyExample") + required = true + deprecated = true + allowEmptyValue = true + explode = true + allowReserved = true + } + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.parameters.also { parameters -> + parameters shouldHaveSize 1 + parameters[0].also { param -> + param.`in` shouldBe "query" + param.description shouldBe "test parameter" + param.required shouldBe true + param.deprecated shouldBe true + param.allowEmptyValue shouldBe true + param.`$ref` shouldBe null + param.style shouldBe null + param.explode shouldBe true + param.allowReserved shouldBe true + param.schema + .also { it.shouldNotBeNull() } + ?.also { it.type = "string" } + param.example shouldBe "MyExample" + param.examples shouldBe null + param.content shouldBe null + param.extensions shouldBe null + } + } + } + } + + "documented body" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.request { + body(KTypeDescriptor(typeOf())) { + description = "the test body" + required = true + mediaType(ContentType.Application.Json) + mediaType(ContentType.Application.Xml) + example( + ValueExampleDescriptor( + name = "example 1", + value = "MyExample1", + summary = "the example 1", + description = "the first example" + ) + ) + } + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.requestBody + .also { it.shouldNotBeNull() } + ?.also { body -> + body.description shouldBe "the test body" + body.content + .also { it.shouldNotBeNull() } + ?.also { content -> + content shouldHaveSize 2 + content["application/json"] + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema + .also { it.shouldNotBeNull() } + ?.also { it.type shouldBe "string" } + mediaType.example shouldBe null + mediaType.examples + .also { it shouldHaveSize 1 } + .let { it["example 1"] } + .also { it.shouldNotBeNull() } + ?.also { example -> + example.summary shouldBe "the example 1" + example.description shouldBe "the first example" + example.value shouldBe "MyExample1" + example.externalValue shouldBe null + example.`$ref` shouldBe null + example.extensions shouldBe null + example.valueSetFlag shouldBe true + } + mediaType.encoding shouldBe null + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + content["application/xml"] + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema + .also { it.shouldNotBeNull() } + ?.also { it.type shouldBe "string" } + mediaType.example shouldBe null + mediaType.examples + .also { it shouldHaveSize 1 } + .let { it["example 1"] } + .also { it.shouldNotBeNull() } + ?.also { example -> + example.summary shouldBe "the example 1" + example.description shouldBe "the first example" + example.value shouldBe "MyExample1" + example.externalValue shouldBe null + example.`$ref` shouldBe null + example.extensions shouldBe null + example.valueSetFlag shouldBe true + } + mediaType.encoding shouldBe null + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + } + body.required shouldBe true + body.extensions shouldBe null + body.`$ref` shouldBe null + } + } + } + + "multipart body" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.request { + multipartBody { + mediaType(ContentType.MultiPart.FormData) + part("image", KTypeDescriptor(typeOf())) { + mediaTypes = setOf( + ContentType.Image.PNG, + ContentType.Image.JPEG, + ContentType.Image.GIF + ) + } + part("data", KTypeDescriptor(typeOf())) { + mediaTypes = setOf(ContentType.Text.Plain) + } + } + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.requestBody + .also { it.shouldNotBeNull() } + ?.also { body -> + body.content + .also { it.shouldNotBeNull() } + ?.also { content -> + content shouldHaveSize 1 + content["multipart/form-data"] + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema + .also { it.shouldNotBeNull() } + ?.also { schema -> + schema.type shouldBe "object" + schema.properties.keys shouldContainExactlyInAnyOrder listOf( + "image", + "data" + ) + + } + mediaType.example shouldBe null + mediaType.examples shouldBe null + mediaType.encoding + .also { it shouldHaveSize 2 } + .also { it.keys shouldContainExactlyInAnyOrder listOf("image", "data") } + .also { encoding -> + encoding["image"]?.also { image -> + image.contentType shouldBe "image/png, image/jpeg, image/gif" + image.headers shouldHaveSize 0 + image.style shouldBe null + image.explode shouldBe null + image.allowReserved shouldBe null + image.extensions shouldBe null + } + encoding["data"]?.also { data -> + data.contentType shouldBe "text/plain" + data.headers shouldHaveSize 0 + data.style shouldBe null + data.explode shouldBe null + data.allowReserved shouldBe null + data.extensions shouldBe null + } + } + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + } + } + } + } + + "multipart body without parts" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.request { + multipartBody { + mediaType(ContentType.MultiPart.FormData) + } + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.requestBody + .also { it.shouldNotBeNull() } + ?.also { body -> + body.content + .also { it.shouldNotBeNull() } + ?.also { content -> + content shouldHaveSize 1 + content["multipart/form-data"] + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema.shouldNotBeNull() + mediaType.example shouldBe null + mediaType.examples shouldBe null + mediaType.encoding shouldBe null + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + } + } + } + } + + "multiple responses" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.response { + default { + description = "Default Response" + } + HttpStatusCode.OK to { + description = "Successful Request" + } + HttpStatusCode.InternalServerError to { + description = "Failed Request" + } + "Custom" to { + description = "Custom Response" + } + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.responses + .also { it shouldHaveSize 4 } + ?.also { responses -> + responses["default"] + .also { it.shouldNotBeNull() } + ?.also { it.description shouldBe "Default Response" } + responses["200"] + .also { it.shouldNotBeNull() } + ?.also { it.description shouldBe "Successful Request" } + responses["500"] + .also { it.shouldNotBeNull() } + ?.also { it.description shouldBe "Failed Request" } + responses["Custom"] + .also { it.shouldNotBeNull() } + ?.also { it.description shouldBe "Custom Response" } + } + } + } + + "automatic unauthorized response for protected route" { + val config = PluginConfigDsl().also { + it.security { + defaultUnauthorizedResponse { + description = "Default unauthorized Response" + } + } + } + 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" } + responses["default"] + .also { it.shouldNotBeNull() } + ?.also { it.description shouldBe "Default Response" } + } + } + } + + "automatic unauthorized response for unprotected route" { + val config = PluginConfigDsl().also { + it.security { + defaultUnauthorizedResponse { + description = "Default unauthorized Response" + } + } + } + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.response { + default { + description = "Default Response" + } + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route), config) + val exampleContext = exampleContext(listOf(route), config) + buildOperationObject(route, schemaContext, exampleContext, config).also { operation -> + operation.responses + .also { it shouldHaveSize 1 } + ?.also { responses -> + responses["default"] + .also { it.shouldNotBeNull() } + ?.also { it.description shouldBe "Default Response" } + } + } + } + + "complex body datatype" { + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.request { + body(KTypeDescriptor(typeOf>())) {} + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route)) + val exampleContext = exampleContext(listOf(route)) + buildOperationObject(route, schemaContext, exampleContext).also { operation -> + operation.requestBody + .also { it.shouldNotBeNull() } + ?.also { body -> + body.description shouldBe null + body.content + .also { it.shouldNotBeNull() } + ?.also { content -> + content shouldHaveSize 1 + content["application/json"] + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema + .also { it.shouldNotBeNull() } + ?.also { schema -> + schema.type shouldBe "array" + schema.items.also { item -> + item.type shouldBe null + item.`$ref` shouldBe "#/components/schemas/io.github.smiley4.ktorswaggerui.builder.OperationBuilderTest.Companion.SimpleObject" + } + } + mediaType.example shouldBe null + mediaType.examples shouldBe null + mediaType.encoding shouldBe null + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + + } + body.required shouldBe false + body.extensions shouldBe null + body.`$ref` shouldBe null + } + } + schemaContext.getComponentSection().also { section -> + section.keys shouldContainExactlyInAnyOrder listOf("io.github.smiley4.ktorswaggerui.builder.OperationBuilderTest.Companion.SimpleObject") + section["io.github.smiley4.ktorswaggerui.builder.OperationBuilderTest.Companion.SimpleObject"]?.also { schema -> + schema.type shouldBe "object" + schema.properties.keys shouldContainExactlyInAnyOrder listOf("number", "text") + } + } + } + + "custom body schema" { + val config = PluginConfigDsl().also { + it.schemas { + schema("myCustomSchema", SwaggerTypeDescriptor( + Schema().also { schema -> + schema.type = "object" + schema.properties = mapOf( + "custom" to Schema().also { prop -> + prop.type = "string" + } + ) + } + )) + } + } + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.request { + body(RefTypeDescriptor("myCustomSchema")) + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route), config) + val exampleContext = exampleContext(listOf(route), config) + buildOperationObject(route, schemaContext, exampleContext, config).also { operation -> + operation.requestBody + .also { it.shouldNotBeNull() } + ?.also { body -> + body.description shouldBe null + body.content + .also { it.shouldNotBeNull() } + ?.also { content -> + content shouldHaveSize 1 + content["application/json"] + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema + .also { it.shouldNotBeNull() } + ?.also { schema -> schema.`$ref` shouldBe "#/components/schemas/myCustomSchema" } + mediaType.example shouldBe null + mediaType.examples shouldBe null + mediaType.encoding shouldBe null + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + + } + body.required shouldBe false + body.extensions shouldBe null + body.`$ref` shouldBe null + } + } + } + + "custom multipart-body schema" { + val config = PluginConfigDsl().also { + it.schemas { + schema("myCustomSchema", SwaggerTypeDescriptor( + Schema().also { schema -> + schema.type = "object" + schema.properties = mapOf( + "custom" to Schema().also { prop -> + prop.type = "string" + } + ) + } + )) + } + } + val route = RouteMeta( + path = "/test", + method = HttpMethod.Get, + documentation = OpenApiRoute().also { route -> + route.request { + multipartBody { + mediaType(ContentType.MultiPart.FormData) + part("customData", RefTypeDescriptor("myCustomSchema")) + } + } + }.build(), + protected = false + ) + val schemaContext = schemaContext(listOf(route), config) + val exampleContext = exampleContext(listOf(route), config) + buildOperationObject(route, schemaContext, exampleContext, config).also { operation -> + operation.requestBody + .also { it.shouldNotBeNull() } + ?.also { body -> + body.content + .also { it.shouldNotBeNull() } + ?.also { content -> + content shouldHaveSize 1 + content["multipart/form-data"] + .also { it.shouldNotBeNull() } + ?.also { mediaType -> + mediaType.schema + .also { it.shouldNotBeNull() } + ?.also { schema -> + schema.type shouldBe "object" + schema.properties.keys shouldContainExactlyInAnyOrder listOf("customData") + schema.properties["customData"]!!.`$ref` shouldBe "#/components/schemas/myCustomSchema" + } + mediaType.example shouldBe null + mediaType.examples shouldBe null + mediaType.encoding shouldBe null + mediaType.extensions shouldBe null + mediaType.exampleSetFlag shouldBe false + } + } + } + } + } + +}) { + + companion object { + + private data class SimpleObject( + val text: String, + val number: Int + ) + + private val defaultPluginConfig = PluginConfigDsl() + + + private fun schemaContext(routes: List, pluginConfig: PluginConfigDsl = defaultPluginConfig): SchemaContext { + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return SchemaContextImpl(pluginConfigData.schemaConfig).also { + it.addGlobal(pluginConfigData.schemaConfig) + it.add(routes) + } + } + + private fun exampleContext(routes: List, pluginConfig: PluginConfigDsl = defaultPluginConfig): ExampleContext { + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return ExampleContextImpl().also { + it.addGlobal(pluginConfigData.exampleConfig) + it.add(routes) + } + } + + private fun buildOperationObject( + route: RouteMeta, + schemaContext: SchemaContext, + exampleContext: ExampleContext, + pluginConfig: PluginConfigDsl = defaultPluginConfig + ): Operation { + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return OperationBuilder( + operationTagsBuilder = OperationTagsBuilder(pluginConfigData), + parameterBuilder = ParameterBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext + ), + requestBodyBuilder = RequestBodyBuilder( + contentBuilder = ContentBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext, + headerBuilder = HeaderBuilder(schemaContext) + ) + ), + responsesBuilder = ResponsesBuilder( + responseBuilder = ResponseBuilder( + headerBuilder = HeaderBuilder(schemaContext), + contentBuilder = ContentBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext, + headerBuilder = HeaderBuilder(schemaContext) + ) + ), + config = pluginConfigData + ), + securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), + ).build(route) + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt new file mode 100644 index 0000000..66b0355 --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt @@ -0,0 +1,143 @@ +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.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.PathBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.PathsBuilder +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.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.dsl.config.PluginConfigDsl +import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRoute +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.maps.shouldHaveSize +import io.kotest.matchers.nulls.shouldNotBeNull +import io.ktor.http.HttpMethod +import io.swagger.v3.oas.models.Paths + +class PathsBuilderTest : StringSpec({ + + "simple paths" { + val routes = listOf( + route(HttpMethod.Get, "/"), + route(HttpMethod.Delete, "/test/path"), + route(HttpMethod.Post, "/other/test/route") + ) + val schemaContext = schemaContext(routes, defaultPluginConfig) + val exampleContext = exampleContext(routes, defaultPluginConfig) + buildPathsObject(routes, schemaContext, exampleContext).also { paths -> + paths shouldHaveSize 3 + paths.keys shouldContainExactlyInAnyOrder listOf( + "/", + "/test/path", + "/other/test/route" + ) + paths["/"]!!.get.shouldNotBeNull() + paths["/test/path"]!!.delete.shouldNotBeNull() + paths["/other/test/route"]!!.post.shouldNotBeNull() + } + } + + "merge paths" { + val config = defaultPluginConfig + 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( + "/different/path", + "/test/path" + ) + paths["/different/path"]!!.get.shouldNotBeNull() + paths["/test/path"]!!.get.shouldNotBeNull() + paths["/test/path"]!!.post.shouldNotBeNull() + } + } + +}) { + + companion object { + + private fun route(method: HttpMethod, url: String) = RouteMeta( + path = url, + method = method, + documentation = OpenApiRoute().build(), + protected = false + ) + + private val defaultPluginConfig = PluginConfigDsl() + + private fun schemaContext(routes: List, pluginConfig: PluginConfigDsl): SchemaContext { + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return SchemaContextImpl(pluginConfigData.schemaConfig).also { + it.addGlobal(pluginConfigData.schemaConfig) + it.add(routes) + } + } + + private fun exampleContext(routes: List, pluginConfig: PluginConfigDsl): ExampleContext { + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return ExampleContextImpl().also { + it.addGlobal(pluginConfigData.exampleConfig) + it.add(routes) + } + } + + private fun buildPathsObject( + routes: Collection, + schemaContext: SchemaContext, + exampleContext: ExampleContext, + pluginConfig: PluginConfigDsl = defaultPluginConfig + ): Paths { + val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) + return PathsBuilder( + pathBuilder = PathBuilder( + operationBuilder = OperationBuilder( + operationTagsBuilder = OperationTagsBuilder(pluginConfigData), + parameterBuilder = ParameterBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext + ), + requestBodyBuilder = RequestBodyBuilder( + contentBuilder = ContentBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext, + headerBuilder = HeaderBuilder(schemaContext) + ) + ), + responsesBuilder = ResponsesBuilder( + responseBuilder = ResponseBuilder( + headerBuilder = HeaderBuilder(schemaContext), + contentBuilder = ContentBuilder( + schemaContext = schemaContext, + exampleContext = exampleContext, + headerBuilder = HeaderBuilder(schemaContext) + ) + ), + config = pluginConfigData + ), + securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), + ) + ) + ).build(routes) + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/SecuritySchemesBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/SecuritySchemesBuilderTest.kt new file mode 100644 index 0000000..f842d9d --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/SecuritySchemesBuilderTest.kt @@ -0,0 +1,210 @@ +package io.github.smiley4.ktorswaggerui.builder + +import io.github.smiley4.ktorswaggerui.data.AuthKeyLocation +import io.github.smiley4.ktorswaggerui.data.AuthScheme +import io.github.smiley4.ktorswaggerui.data.AuthType +import io.github.smiley4.ktorswaggerui.data.SecuritySchemeData +import io.github.smiley4.ktorswaggerui.builder.openapi.OAuthFlowsBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.SecuritySchemesBuilder +import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiSecurityScheme +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.maps.shouldBeEmpty +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.swagger.v3.oas.models.security.SecurityScheme + +class SecuritySchemesBuilderTest : StringSpec({ + + "test empty" { + buildSecuritySchemeObjects(mapOf()).also { schemes -> + schemes.shouldBeEmpty() + } + } + + "test default security scheme object" { + buildSecuritySchemeObjects(mapOf("TestAuth" to {})).also { schemes -> + schemes.keys shouldContainExactlyInAnyOrder listOf("TestAuth") + schemes["TestAuth"]!!.also { scheme -> + scheme.type shouldBe null + scheme.description shouldBe null + scheme.name shouldBe null + scheme.`$ref` shouldBe null + scheme.`in` shouldBe null + scheme.scheme shouldBe null + scheme.bearerFormat shouldBe null + scheme.flows shouldBe null + scheme.openIdConnectUrl shouldBe null + scheme.extensions shouldBe null + } + } + } + + "test basic security scheme objects" { + buildSecuritySchemeObjects(mapOf( + "TestAuth1" to { + type = AuthType.HTTP + scheme = AuthScheme.BASIC + }, + "TestAuth2" to { + type = AuthType.API_KEY + location = AuthKeyLocation.COOKIE + }, + "TestAuth3" to { + type = AuthType.API_KEY + location = AuthKeyLocation.QUERY + name = "myQueryParam" + } + )).also { schemes -> + schemes.keys shouldContainExactlyInAnyOrder listOf("TestAuth1", "TestAuth2", "TestAuth3") + schemes["TestAuth1"]!!.also { scheme -> + scheme.name shouldBe null + scheme.type shouldBe SecurityScheme.Type.HTTP + scheme.scheme shouldBe "Basic" + } + schemes["TestAuth2"]!!.also { scheme -> + scheme.name shouldBe "TestAuth2" + scheme.type shouldBe SecurityScheme.Type.APIKEY + scheme.scheme shouldBe null + } + schemes["TestAuth3"]!!.also { scheme -> + scheme.name shouldBe "myQueryParam" + scheme.type shouldBe SecurityScheme.Type.APIKEY + scheme.scheme shouldBe null + } + } + } + + "test complete security scheme object" { + buildSecuritySchemeObjects(mapOf("TestAuth" to { + type = AuthType.HTTP + location = AuthKeyLocation.COOKIE + scheme = AuthScheme.BASIC + bearerFormat = "test" + openIdConnectUrl = "Test IOD-Connect URL" + description = "Test Description" + flows { + implicit { + authorizationUrl = "Implicit Auth Url" + tokenUrl = "Implicity Token Url" + refreshUrl = "Implicity Token Url" + scopes = mapOf( + "implicit1" to "scope1", + "implicit2" to "scope2" + ) + } + password { + authorizationUrl = "Password Auth Url" + tokenUrl = "Password Token Url" + refreshUrl = "Password Token Url" + scopes = mapOf( + "password1" to "scope1", + "password2" to "scope2" + ) + } + clientCredentials { + authorizationUrl = "ClientCredentials Auth Url" + tokenUrl = "ClientCredentials Token Url" + refreshUrl = "ClientCredentials Token Url" + scopes = mapOf( + "clientCredentials1" to "scope1", + "clientCredentials2" to "scope2" + ) + } + authorizationCode { + authorizationUrl = "AuthorizationCode Auth Url" + tokenUrl = "AuthorizationCode Token Url" + refreshUrl = "AuthorizationCode Token Url" + scopes = mapOf( + "authorizationCode1" to "scope1", + "authorizationCode2" to "scope2" + ) + } + } + })).also { schemes -> + schemes.keys shouldContainExactlyInAnyOrder listOf("TestAuth") + schemes["TestAuth"]!!.also { scheme -> + scheme.name shouldBe null + scheme.type shouldBe SecurityScheme.Type.HTTP + scheme.`in` shouldBe SecurityScheme.In.COOKIE + scheme.scheme shouldBe "Basic" + scheme.bearerFormat shouldBe "test" + scheme.openIdConnectUrl shouldBe "Test IOD-Connect URL" + scheme.description shouldBe "Test Description" + scheme.flows + .also { it.shouldNotBeNull() } + ?.also { flows -> + flows.implicit + .also { it.shouldNotBeNull() } + ?.also { implicit -> + implicit.authorizationUrl shouldBe "Implicit Auth Url" + implicit.tokenUrl shouldBe "Implicity Token Url" + implicit.refreshUrl shouldBe "Implicity Token Url" + implicit.scopes + .also { it.shouldNotBeNull() } + ?.also { scopes -> + scopes.keys shouldContainExactlyInAnyOrder listOf("implicit1", "implicit2") + scopes["implicit1"] shouldBe "scope1" + scopes["implicit2"] shouldBe "scope2" + } + } + flows.password + .also { it.shouldNotBeNull() } + ?.also { password -> + password.authorizationUrl shouldBe "Password Auth Url" + password.tokenUrl shouldBe "Password Token Url" + password.refreshUrl shouldBe "Password Token Url" + password.scopes + .also { it.shouldNotBeNull() } + ?.also { scopes -> + scopes.keys shouldContainExactlyInAnyOrder listOf("password1", "password2") + scopes["password1"] shouldBe "scope1" + scopes["password2"] shouldBe "scope2" + } + } + flows.clientCredentials + .also { it.shouldNotBeNull() } + ?.also { clientCredentials -> + clientCredentials.authorizationUrl shouldBe "ClientCredentials Auth Url" + clientCredentials.tokenUrl shouldBe "ClientCredentials Token Url" + clientCredentials.refreshUrl shouldBe "ClientCredentials Token Url" + clientCredentials.scopes + .also { it.shouldNotBeNull() } + ?.also { scopes -> + scopes.keys shouldContainExactlyInAnyOrder listOf("clientCredentials1", "clientCredentials2") + scopes["clientCredentials1"] shouldBe "scope1" + scopes["clientCredentials2"] shouldBe "scope2" + } + } + flows.authorizationCode + .also { it.shouldNotBeNull() } + ?.also { authorizationCode -> + authorizationCode.authorizationUrl shouldBe "AuthorizationCode Auth Url" + authorizationCode.tokenUrl shouldBe "AuthorizationCode Token Url" + authorizationCode.refreshUrl shouldBe "AuthorizationCode Token Url" + authorizationCode.scopes + .also { it.shouldNotBeNull() } + ?.also { scopes -> + scopes.keys shouldContainExactlyInAnyOrder listOf("authorizationCode1", "authorizationCode2") + scopes["authorizationCode1"] shouldBe "scope1" + scopes["authorizationCode2"] shouldBe "scope2" + } + } + } + } + } + } + +}) { + + companion object { + + private fun buildSecuritySchemeObjects(builders: Map Unit>): Map { + return SecuritySchemesBuilder( + oAuthFlowsBuilder = OAuthFlowsBuilder() + ).build(builders.map { (name, entry) -> OpenApiSecurityScheme(name).apply(entry).build(SecuritySchemeData.DEFAULT) }) + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt new file mode 100644 index 0000000..276c8d3 --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt @@ -0,0 +1,44 @@ +package io.github.smiley4.ktorswaggerui.builder + +import io.github.smiley4.ktorswaggerui.builder.openapi.ServerBuilder +import io.github.smiley4.ktorswaggerui.data.ServerData +import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiServer +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import io.swagger.v3.oas.models.servers.Server + +class ServersBuilderTest : StringSpec({ + + "default server object" { + buildServerObject {}.also { server -> + server.url shouldBe "/" + server.description shouldBe null + server.variables shouldBe null + server.extensions shouldBe null + + } + } + + "complete server object" { + buildServerObject { + url = "Test URL" + description = "Test Description" + }.also { server -> + server.url shouldBe "Test URL" + server.description shouldBe "Test Description" + server.variables shouldBe null + server.extensions shouldBe null + } + } + +}) { + + companion object { + + private fun buildServerObject(builder: OpenApiServer.() -> Unit): Server { + return ServerBuilder().build(OpenApiServer().apply(builder).build(ServerData.DEFAULT)) + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt new file mode 100644 index 0000000..ed4d4ce --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt @@ -0,0 +1,55 @@ +package io.github.smiley4.ktorswaggerui.builder + +import io.github.smiley4.ktorswaggerui.builder.openapi.TagBuilder +import io.github.smiley4.ktorswaggerui.builder.openapi.TagExternalDocumentationBuilder +import io.github.smiley4.ktorswaggerui.data.TagData +import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiTag +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.swagger.v3.oas.models.tags.Tag + + +class TagsBuilderTest : StringSpec({ + + "empty tag object" { + buildTagObject("test-tag") {}.also { tag -> + tag.name shouldBe "test-tag" + tag.description shouldBe null + tag.externalDocs shouldBe null + tag.extensions shouldBe null + } + } + + "full tag object" { + buildTagObject("test-tag") { + description = "Description of tag" + externalDocDescription = "Description of external docs" + externalDocUrl = "example.com" + }.also { tag -> + tag.name shouldBe "test-tag" + tag.description shouldBe "Description of tag" + tag.externalDocs + .also { docs -> docs.shouldNotBeNull() } + ?.also { docs -> + docs.description shouldBe "Description of external docs" + docs.url shouldBe "example.com" + docs.extensions shouldBe null + } + tag.extensions shouldBe null + } + } + +}) { + + companion object { + + private fun buildTagObject(name: String, builder: OpenApiTag.() -> Unit): Tag { + return TagBuilder( + tagExternalDocumentationBuilder = TagExternalDocumentationBuilder() + ).build(OpenApiTag(name).apply(builder).build(TagData.DEFAULT)) + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt new file mode 100644 index 0000000..c60dddb --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt @@ -0,0 +1,136 @@ +package io.github.smiley4.ktorswaggerui.misc + +import io.github.smiley4.ktorswaggerui.builder.route.RouteDocumentationMerger +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor +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.shouldBe +import io.kotest.matchers.shouldNotBe +import kotlin.reflect.typeOf + +class RouteDocumentationMergerTest : StringSpec({ + + "merge empty routes" { + merge( + route {}, + route {} + ).also { route -> + route.tags.shouldBeEmpty() + route.summary shouldBe null + route.description shouldBe null + route.operationId shouldBe null + route.deprecated shouldBe false + route.hidden shouldBe false + route.securitySchemeNames.shouldBeEmpty() + route.protected shouldBe null + route.getRequest().also { requests -> + requests.getParameters().shouldBeEmpty() + requests.getBody() shouldBe null + } + route.getResponses().also { responses -> + responses.getResponses().shouldBeEmpty() + } + } + } + + "merge complete routes" { + merge( + route { + specId = "test-spec-a" + tags = listOf("a1", "a2") + summary = "Summary A" + description = "Description A" + operationId = "operationA" + securitySchemeNames = listOf("securitySchemeNameA1", "securitySchemeNameA2") + deprecated = true + hidden = false + protected = true + request { + queryParameter("query", KTypeDescriptor(typeOf())) {} + pathParameter("pathA1", KTypeDescriptor(typeOf())) {} + pathParameter("pathA2", KTypeDescriptor(typeOf())) {} + body(KTypeDescriptor(typeOf())) { + description = "body A" + } + } + response { + "a1" to { description = "response a1" } + "a2" to { description = "response a1" } + } + }, + route { + specId = "test-spec-b" + tags = listOf("b1", "b2") + summary = "Summary B" + description = "Description B" + operationId = "operationB" + securitySchemeNames = listOf("securitySchemeNameB1", "securitySchemeNameB2") + deprecated = false + hidden = true + protected = false + request { + queryParameter("query", KTypeDescriptor(typeOf())) {} + pathParameter("pathB1", KTypeDescriptor(typeOf())) {} + pathParameter("pathB2", KTypeDescriptor(typeOf())) {} + body(KTypeDescriptor(typeOf())) { + description = "body B" + } + } + response { + "b1" to { description = "response b1" } + "b2" to { description = "response b1" } + } + } + ).also { route -> + route.specId shouldBe "test-spec-a" + route.tags shouldContainExactlyInAnyOrder listOf("a1", "a2", "b1", "b2") + route.summary shouldBe "Summary A" + route.description shouldBe "Description A" + route.operationId shouldBe "operationA" + route.deprecated shouldBe true + route.hidden shouldBe true + route.securitySchemeNames shouldContainExactlyInAnyOrder listOf( + "securitySchemeNameA1", + "securitySchemeNameA2", + "securitySchemeNameB1", + "securitySchemeNameB2" + ) + route.protected shouldBe true + route.getRequest().also { requests -> + requests.getParameters().map { it.name } shouldContainExactlyInAnyOrder listOf( + "query", + "pathA1", + "pathA2", + "query", + "pathB1", + "pathB2" + ) + requests.getBody() + .also { it shouldNotBe null } + ?.also { it.description shouldBe "body A" } + } + route.getResponses().also { responses -> + responses.getResponses().map { it.statusCode } shouldContainExactlyInAnyOrder listOf( + "b1", "b2", "a1", "a2" + ) + } + } + } + +}) { + + companion object { + + fun route(builder: OpenApiRoute.() -> Unit): OpenApiRoute { + return OpenApiRoute().apply(builder) + } + + fun merge(a: OpenApiRoute, b: OpenApiRoute): OpenApiRoute { + return RouteDocumentationMerger().merge(a, b) + } + + } + +} \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt new file mode 100644 index 0000000..7ea0247 --- /dev/null +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt @@ -0,0 +1,106 @@ +package io.github.smiley4.ktorswaggerui.misc + +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldNotBeEmpty +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.server.application.call +import io.ktor.server.response.respondText +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.testing.testApplication +import kotlin.test.Test + +class RoutingTests { + + @Test + fun basicRouting() = swaggerUITestApplication { + get("hello").also { + it.status shouldBe HttpStatusCode.OK + it.body shouldBe "Hello Test" + } + get("/").also { + it.status shouldBe HttpStatusCode.NotFound + } + get("/swagger").also { + it.status shouldBe HttpStatusCode.OK + it.contentType shouldBe ContentType.Text.Html + it.body.shouldNotBeEmpty() + } + get("/swagger/index.html").also { + it.status shouldBe HttpStatusCode.OK + it.contentType shouldBe ContentType.Text.Html + it.body.shouldNotBeEmpty() + } + get("/swagger/swagger-initializer.js").also { + it.status shouldBe HttpStatusCode.OK + it.contentType shouldBe ContentType.Application.JavaScript + it.body shouldContain "url: \"/api.json\"" + } + get("/api.json").also { + it.status shouldBe HttpStatusCode.OK + it.contentType shouldBe ContentType.Application.Json + it.body.shouldNotBeEmpty() + } + } + + private fun swaggerUITestApplication(block: suspend TestContext.() -> Unit) { + testApplication { + val client = createClient { + this.followRedirects = followRedirects + } + install(SwaggerUI) + routing { + route("api.json") { + openApiSpec() + } + route("swagger") { + swaggerUI("/api.json") + } + get("hello") { + call.respondText("Hello Test") + } + } + TestContext(client).apply { block() } + } + } + + class TestContext(private val client: HttpClient) { + + suspend fun get(path: String): GetResult { + return client.get(path) + .let { + GetResult( + path = path, + status = it.status, + contentType = it.contentType(), + body = it.bodyAsText(), + redirect = it.headers["Location"] + ) + } + .also { it.print() } + } + + + private fun GetResult.print() { + println("GET ${this.path} => ${this.status} (${this.contentType}): ${this.body}") + } + } + + data class GetResult( + val path: String, + val status: HttpStatusCode, + val contentType: ContentType?, + val body: String, + val redirect: String? + ) + +} \ No newline at end of file From 437dc07f82434647de02a4fd84e9eda2f35a5112 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 9 May 2024 16:01:06 +0200 Subject: [PATCH 12/32] fix detekt codestyles --- build.gradle.kts | 34 +- detekt/detekt.yml | 788 ++++++++++++++++++ ktor-swagger-ui-examples/build.gradle.kts | 7 + .../ktorswaggerui/examples/Authentication.kt | 22 +- .../ktorswaggerui/examples/CompleteConfig.kt | 4 +- ktor-swagger-ui/build.gradle.kts | 3 + .../builder/example/ExampleContext.kt | 2 +- .../builder/example/ExampleContextImpl.kt | 4 +- .../builder/schema/SchemaContext.kt | 2 +- .../builder/schema/SchemaContextImpl.kt | 4 +- .../ktorswaggerui/data/ExampleConfigData.kt | 2 +- .../ktorswaggerui/data/ExampleDescriptor.kt | 2 +- .../ktorswaggerui/data/OpenApiExampleData.kt | 7 - .../ktorswaggerui/data/OpenApiHeaderData.kt | 2 +- .../data/OpenApiMultipartPartData.kt | 2 +- .../ktorswaggerui/data/OpenApiRequestData.kt | 2 +- .../data/OpenApiRequestParameterData.kt | 2 +- .../ktorswaggerui/data/OpenApiResponseData.kt | 2 +- .../ktorswaggerui/data/OpenApiRouteData.kt | 2 +- .../ktorswaggerui/data/ParameterLocation.kt | 2 +- .../ktorswaggerui/data/SchemaConfigData.kt | 2 +- .../ktorswaggerui/data/SecurityData.kt | 2 +- .../smiley4/ktorswaggerui/data/TagsData.kt | 2 +- .../ktorswaggerui/data/TypeDescriptor.kt | 2 +- .../ktorswaggerui/dsl/config/ExampleConfig.kt | 2 +- .../dsl/config/OpenApiSecurity.kt | 2 +- .../ktorswaggerui/dsl/config/SchemaConfig.kt | 2 +- .../dsl/routes/OpenApiMultipartBody.kt | 44 - .../dsl/routes/OpenApiMultipartPart.kt | 26 +- .../builder/ExternalDocsBuilderTest.kt | 2 +- .../ktorswaggerui/builder/InfoBuilderTest.kt | 2 +- .../builder/OpenApiBuilderTest.kt | 2 +- .../builder/OperationBuilderTest.kt | 11 +- .../ktorswaggerui/builder/PathsBuilderTest.kt | 2 +- .../builder/SecuritySchemesBuilderTest.kt | 2 +- .../builder/ServersBuilderTest.kt | 2 +- .../ktorswaggerui/builder/TagsBuilderTest.kt | 2 +- .../misc/RouteDocumentationMergerTest.kt | 2 +- .../ktorswaggerui/misc/RoutingTests.kt | 2 +- 39 files changed, 853 insertions(+), 155 deletions(-) create mode 100644 detekt/detekt.yml delete mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt diff --git a/build.gradle.kts b/build.gradle.kts index 1fa00c1..62057ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,11 +33,10 @@ subprojects { } tasks.withType().configureEach { - ignoreFailures = true // todo: temporary + ignoreFailures = false buildUponDefaultConfig = true allRules = false - config.setFrom("$projectDir/config/detekt.yml") - baseline.set(file("$projectDir/config/baseline.xml")) + config.setFrom("$projectDir/../detekt/detekt.yml") reports { html.required.set(true) md.required.set(true) @@ -48,32 +47,3 @@ subprojects { } } - -//mavenPublishing { -// publishToMavenCentral(SonatypeHost.S01) -// signAllPublications() -// coordinates(Meta.groupId, Meta.artifactId, Meta.version) -// pom { -// name.set(Meta.name) -// description.set(Meta.description) -// url.set(Meta.scmUrl) -// licenses { -// license { -// name.set(Meta.licenseName) -// url.set(Meta.licenseUrl) -// distribution.set(Meta.licenseUrl) -// } -// } -// scm { -// url.set(Meta.scmUrl) -// connection.set(Meta.scmConnection) -// } -// developers { -// developer { -// id.set(Meta.developerName) -// name.set(Meta.developerName) -// url.set(Meta.developerUrl) -// } -// } -// } -//} diff --git a/detekt/detekt.yml b/detekt/detekt.yml new file mode 100644 index 0000000..960d68d --- /dev/null +++ b/detekt/detekt.yml @@ -0,0 +1,788 @@ +build: + maxIssues: 0 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + warningsAsErrors: false + checkExhaustiveness: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'FindingsReport' + - 'FileBasedFindingsReport' + # - 'LiteFindingsReport' + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + # - 'MdOutputReport' + # - 'SarifOutputReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + searchInProtectedClass: false + UndocumentedPublicFunction: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedFunction: false + UndocumentedPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedProperty: false + +complexity: + active: true + CognitiveComplexMethod: + active: false + threshold: 15 + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ignoreOverloaded: false + CyclomaticComplexMethod: + active: true + threshold: 20 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: true + threshold: 600 + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + LongMethod: + active: true + threshold: 60 + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 15 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [] + MethodOverloading: + active: false + threshold: 6 + NamedArguments: + active: false + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 4 + NestedScopeFunctions: + active: false + threshold: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + InjectDispatcher: + active: true + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunSwallowedCancellation: + active: false + SuspendFunWithCoroutineScopeReceiver: + active: false + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: false + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: true + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: false + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + threshold: 3 + ForEachOnRange: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + SpreadOperator: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + UnnecessaryPartOfBinaryExpression: + active: false + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastNullableToNonNullableType: + active: false + CastToNullableType: + active: false + Deprecation: + active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + ignoredSubjectTypes: [] + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - 'CheckResult' + - '*.CheckResult' + - 'CheckReturnValue' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - 'CanIgnoreReturnValue' + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.*Flow' + - 'java.util.stream.*Stream' + ignoreFunctionCall: [] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: false + excludes: ['**/*.kts'] + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + PropertyUsedBeforeDeclaration: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullCheck: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AlsoCouldBeApply: + active: false + BracesOnIfStatements: + active: false + singleLine: 'never' + multiLine: 'always' + BracesOnWhenStatements: + active: false + singleLine: 'necessary' + multiLine: 'consistent' + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + allowOperators: false + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + DoubleNegativeLambda: + active: false + negativeFunctions: + - reason: 'Use `takeIf` instead.' + value: 'takeUnless' + - reason: 'Use `all` instead.' + value: 'none' + negativeFunctionNameParts: + - 'not' + - 'non' + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenAnnotation: + active: false + annotations: + - reason: 'it is a java annotation. Use `Suppress` instead.' + value: 'java.lang.SuppressWarnings' + - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' + value: 'java.lang.Deprecated' + - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' + value: 'java.lang.annotation.Documented' + - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' + value: 'java.lang.annotation.Target' + - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' + value: 'java.lang.annotation.Retention' + - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' + value: 'java.lang.annotation.Repeatable' + - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265' + value: 'java.lang.annotation.Inherited' + ForbiddenComment: + active: true + comments: + - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + value: 'FIXME:' + - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + value: 'STOPSHIP:' + - reason: 'Forbidden TODO todo marker in comment, please do the changes.' + value: 'TODO:' + allowedPatterns: '' + ForbiddenImport: + active: false + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + ForbiddenSuppress: + active: false + rules: [] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesLoops: + active: false + MaxChainedCallsOnSameLine: + active: false + maxChainedCalls: 5 + MaxLineLength: + active: true + maxLineLength: 140 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + excludeRawStrings: true + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: false + MultilineRawStringIndentation: + active: false + indentSize: 4 + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + NullableBooleanCheck: + active: false + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + OptionalWhenBraces: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: false + StringShouldBeRawString: + active: false + maxEscapedCharacterCount: 2 + ignoredCharacters: [] + ThrowsCount: + active: true + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + TrimMultilineRawString: + active: false + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: false + UnnecessaryBracesAroundTrailingLambda: + active: false + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + allowForUnclearPrecedence: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedParameter: + active: true + allowedNames: 'ignored|expected' + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '' + UnusedPrivateProperty: + active: true + allowedNames: '_|ignored|expected|serialVersionUID' + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + ignoreWhenContainingVariableDeclaration: false + UseIsNullOrEmpty: + active: true + UseLet: + active: false + UseOrEmpty: + active: true + UseRequire: + active: false + UseRequireNotNull: + active: false + UseSumOfInsteadOfFlatMapSize: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: false + excludeImports: + - 'java.util.*' \ No newline at end of file diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts index 24f2e53..ef023fd 100644 --- a/ktor-swagger-ui-examples/build.gradle.kts +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -1,3 +1,5 @@ +import io.gitlab.arturbosch.detekt.Detekt + plugins { kotlin("jvm") } @@ -37,3 +39,8 @@ dependencies { implementation("ch.qos.logback:logback-classic:$logbackVersion") } + + +tasks.withType().configureEach { + ignoreFailures = true +} \ No newline at end of file diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt index 159407d..fcd0520 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Authentication.kt @@ -42,16 +42,18 @@ private fun Application.myModule() { // Install and configure the "SwaggerUI"-Plugin install(SwaggerUI) { - // configure a basic-auth security scheme - securityScheme("MySecurityScheme") { - type = AuthType.HTTP - scheme = AuthScheme.BASIC - } - // if no other security scheme is specified for a route, the one with this name is used instead - defaultSecuritySchemeName = "MySecurityScheme" - // if no other response is documented for "401 Unauthorized", this information is used instead - defaultUnauthorizedResponse { - description = "Username or password is invalid" + security { + // configure a basic-auth security scheme + securityScheme("MySecurityScheme") { + type = AuthType.HTTP + scheme = AuthScheme.BASIC + } + // if no other security scheme is specified for a route, the one with this name is used instead + defaultSecuritySchemeNames = setOf("MySecurityScheme") + // if no other response is documented for "401 Unauthorized", this information is used instead + defaultUnauthorizedResponse { + description = "Username or password is invalid" + } } } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt index 631acc8..6b7c521 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt @@ -130,8 +130,8 @@ private fun Application.myModule() { ) ) } - specAssigner = { url, tags -> PluginConfigDsl.DEFAULT_SPEC_ID } - pathFilter = { method, url -> url.firstOrNull() != "hidden" } + specAssigner = { _, _ -> PluginConfigDsl.DEFAULT_SPEC_ID } + pathFilter = { _, url -> url.firstOrNull() != "hidden" } ignoredRouteSelectors = emptySet() postBuild = { api -> println("Completed api: $api") } } diff --git a/ktor-swagger-ui/build.gradle.kts b/ktor-swagger-ui/build.gradle.kts index e804020..355dc0e 100644 --- a/ktor-swagger-ui/build.gradle.kts +++ b/ktor-swagger-ui/build.gradle.kts @@ -1,7 +1,10 @@ +import io.gitlab.arturbosch.detekt.Detekt + object Meta { const val artifactId = "ktor-swagger-ui" } + dependencies { val ktorVersion = "2.3.7" implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt index 0ca10e7..3acbbd1 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContext.kt @@ -6,4 +6,4 @@ import io.swagger.v3.oas.models.examples.Example interface ExampleContext { fun getExample(descriptor: ExampleDescriptor): Example fun getComponentSection(): Map -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt index 22a8489..45038d4 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt @@ -68,11 +68,11 @@ class ExampleContextImpl : ExampleContext { } override fun getExample(descriptor: ExampleDescriptor): Example { - return rootExamples[descriptor] ?: throw Exception("no root-example for given example-descriptor") + return rootExamples[descriptor] ?: throw NoSuchElementException("no root-example for given example-descriptor") } override fun getComponentSection(): Map { return componentExamples } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt index 14dbc3e..89f2e53 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt @@ -6,4 +6,4 @@ import io.swagger.v3.oas.models.media.Schema interface SchemaContext { fun getSchema(typeDescriptor: TypeDescriptor): Schema<*> fun getComponentSection(): Map> -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt index d65bea6..3648f16 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt @@ -164,11 +164,11 @@ class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : SchemaCont } override fun getSchema(typeDescriptor: TypeDescriptor): Schema<*> { - return rootSchemas[typeDescriptor] ?: throw Exception("no root-schema for given type-descriptor") + return rootSchemas[typeDescriptor] ?: throw NoSuchElementException("no root-schema for given type-descriptor") } override fun getComponentSection(): Map> { return componentSchemas } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt index 6872d03..878569f 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleConfigData.kt @@ -10,4 +10,4 @@ class ExampleConfigData( ) } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt index 8337fdf..ebc7129 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt @@ -15,4 +15,4 @@ class ValueExampleDescriptor( class RefExampleDescriptor(name: String, val refName: String): ExampleDescriptor(name) -class SwaggerExampleDescriptor(name: String, val example: Example): ExampleDescriptor(name) \ No newline at end of file +class SwaggerExampleDescriptor(name: String, val example: Example): ExampleDescriptor(name) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt deleted file mode 100644 index d496c0e..0000000 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiExampleData.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.smiley4.ktorswaggerui.data - -data class OpenApiExampleData( - val value: Any, - val summary: String?, - val description: String?, -) \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt index f9bac91..a734189 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt @@ -5,4 +5,4 @@ data class OpenApiHeaderData( val type: TypeDescriptor?, val required: Boolean, val deprecated: Boolean, -) \ No newline at end of file +) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt index cc39239..7b1e7a3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt @@ -7,4 +7,4 @@ data class OpenApiMultipartPartData( val type: TypeDescriptor, val mediaTypes: Set, val headers: Map, -) \ No newline at end of file +) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt index 99f00ba..0f6a289 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt @@ -3,4 +3,4 @@ package io.github.smiley4.ktorswaggerui.data data class OpenApiRequestData( val parameters: List, val body: OpenApiBaseBodyData?, -) \ No newline at end of file +) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt index 4833a2b..c947e66 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt @@ -11,4 +11,4 @@ data class OpenApiRequestParameterData( val allowEmptyValue: Boolean, val explode: Boolean, val allowReserved: Boolean -) \ No newline at end of file +) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt index 953e96f..60a8e8d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt @@ -5,4 +5,4 @@ data class OpenApiResponseData( val description: String?, val headers: Map, val body: OpenApiBaseBodyData?, -) \ No newline at end of file +) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt index 627468a..8b980a8 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt @@ -12,4 +12,4 @@ data class OpenApiRouteData( val protected: Boolean?, val request: OpenApiRequestData, val responses: List, -) \ No newline at end of file +) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt index 6e3e8b4..5577540 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt @@ -2,4 +2,4 @@ package io.github.smiley4.ktorswaggerui.data enum class ParameterLocation { QUERY, HEADER, PATH -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt index 292f8d3..4a667c0 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt @@ -27,4 +27,4 @@ data class SchemaConfigData( overwrite = emptyMap() ) } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt index 25b9f48..db09ff8 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt @@ -12,4 +12,4 @@ data class SecurityData( securitySchemes = emptyList() ) } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt index b628fbe..abce2eb 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt @@ -12,4 +12,4 @@ data class TagsData( ) } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt index 8a13d2d..1f9c4ff 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -9,4 +9,4 @@ class KTypeDescriptor(val type: KType) : TypeDescriptor class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor class AnyOfTypeDescriptor(val types: List) : TypeDescriptor class EmptyTypeDescriptor : TypeDescriptor -class RefTypeDescriptor(val schemaId: String) : TypeDescriptor \ No newline at end of file +class RefTypeDescriptor(val schemaId: String) : TypeDescriptor diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt index 0d207eb..e819d7a 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt @@ -20,4 +20,4 @@ class ExampleConfig { sharedExamples = sharedExamples ) -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurity.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurity.kt index 02ad1a9..112af92 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurity.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurity.kt @@ -51,4 +51,4 @@ class OpenApiSecurity { } ) -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt index a98b0d0..1f686b5 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt @@ -28,4 +28,4 @@ class SchemaConfig { overwrite = overwrite ) -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt index f31a4e5..8b1119a 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt @@ -21,50 +21,6 @@ class OpenApiMultipartBody : OpenApiBaseBody() { parts.add(OpenApiMultipartPart(name, type).apply(block)) } - - /** - * One part of a multipart-body - */ - fun part(name: String, type: TypeDescriptor) = part(name, type) {} - - -// /** -// * One part of a multipart-body -// */ -// fun part(name: String, type: KType, block: OpenApiMultipartPart.() -> Unit) = part(name, KTypeDescriptor(type), block) -// -// -// /** -// * One part of a multipart-body -// */ -// fun part(name: String, type: KClass<*>) = part(name, type.asSchemaType()) {} -// -// -// /** -// * One part of a multipart-body -// */ -// inline fun part(name: String) = part(name, getSchemaType()) {} -// -// -// /** -// * One part of a multipart-body -// */ -// inline fun part(name: String, noinline block: OpenApiMultipartPart.() -> Unit) = part(name, getSchemaType(), block) - - -// /** -// * One part of a multipart-body -// */ -// fun part(name: String, customSchemaId: String, block: OpenApiMultipartPart.() -> Unit) = -// part(name, BodyTypeDescriptor.custom(customSchemaId), block) -// -// -// /** -// * One part of a multipart-body -// */ -// fun part(name: String, customSchemaId: String) = part(name, customSchemaId) {} - - override fun build() = OpenApiMultipartBodyData( description = description, required = required ?: false, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt index 06a0316..da2fccd 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt @@ -24,8 +24,6 @@ class OpenApiMultipartPart( */ var mediaTypes: Collection = setOf() - private val headers = mutableMapOf() - /** * Possible headers for this part @@ -36,29 +34,7 @@ class OpenApiMultipartPart( } } - -// /** -// * Possible headers for this part -// */ -// fun header(name: String, type: KClass<*>, block: OpenApiHeader.() -> Unit) = header(name, KTypeDescriptor(type.starProjectedType), block) -// - -// /** -// * Possible headers for this part -// */ -// fun header(name: String, type: KClass<*>) = header(name, type) {} - -// -// /** -// * Possible headers for this part -// */ -// inline fun header(name: String) = header(name, KTypeDescriptor(getKType())) {} -// -// -// /** -// * Possible headers for this part -// */ -// inline fun header(name: String, noinline block: OpenApiHeader.() -> Unit) = header(name, getSchemaType(), block) + private val headers = mutableMapOf() fun build() = OpenApiMultipartPartData( diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt index b3ac3ba..b82b2cc 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt @@ -36,4 +36,4 @@ class ExternalDocsBuilderTest : StringSpec({ } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt index edffd08..d2f215c 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt @@ -77,4 +77,4 @@ class InfoBuilderTest : StringSpec({ } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt index 074ec00..fa359f7 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt @@ -173,4 +173,4 @@ class OpenApiBuilderTest : StringSpec({ } } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt index f3429fd..a9ea316 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt @@ -789,7 +789,8 @@ class OperationBuilderTest : StringSpec({ schema.type shouldBe "array" schema.items.also { item -> item.type shouldBe null - item.`$ref` shouldBe "#/components/schemas/io.github.smiley4.ktorswaggerui.builder.OperationBuilderTest.Companion.SimpleObject" + item.`$ref` shouldBe "#/components/schemas/io.github.smiley4." + + "ktorswaggerui.builder.OperationBuilderTest.Companion.SimpleObject" } } mediaType.example shouldBe null @@ -806,7 +807,9 @@ class OperationBuilderTest : StringSpec({ } } schemaContext.getComponentSection().also { section -> - section.keys shouldContainExactlyInAnyOrder listOf("io.github.smiley4.ktorswaggerui.builder.OperationBuilderTest.Companion.SimpleObject") + section.keys shouldContainExactlyInAnyOrder listOf( + "io.github.smiley4.ktorswaggerui.builder.OperationBuilderTest.Companion.SimpleObject" + ) section["io.github.smiley4.ktorswaggerui.builder.OperationBuilderTest.Companion.SimpleObject"]?.also { schema -> schema.type shouldBe "object" schema.properties.keys shouldContainExactlyInAnyOrder listOf("number", "text") @@ -893,7 +896,7 @@ class OperationBuilderTest : StringSpec({ route.request { multipartBody { mediaType(ContentType.MultiPart.FormData) - part("customData", RefTypeDescriptor("myCustomSchema")) + part("customData", RefTypeDescriptor("myCustomSchema")) {} } } }.build(), @@ -995,4 +998,4 @@ class OperationBuilderTest : StringSpec({ } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt index 66b0355..f31cb74 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt @@ -140,4 +140,4 @@ class PathsBuilderTest : StringSpec({ } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/SecuritySchemesBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/SecuritySchemesBuilderTest.kt index f842d9d..9484aa3 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/SecuritySchemesBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/SecuritySchemesBuilderTest.kt @@ -207,4 +207,4 @@ class SecuritySchemesBuilderTest : StringSpec({ } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt index 276c8d3..aefda0d 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt @@ -41,4 +41,4 @@ class ServersBuilderTest : StringSpec({ } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt index ed4d4ce..0c1e537 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt @@ -52,4 +52,4 @@ class TagsBuilderTest : StringSpec({ } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt index c60dddb..74e8d81 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt @@ -133,4 +133,4 @@ class RouteDocumentationMergerTest : StringSpec({ } -} \ No newline at end of file +} diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt index 7ea0247..de02725 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt @@ -103,4 +103,4 @@ class RoutingTests { val redirect: String? ) -} \ No newline at end of file +} From 4213bcf796ca98a3ccea21898780f1a93f4efab9 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 9 May 2024 16:47:44 +0200 Subject: [PATCH 13/32] minor cleanup --- build.gradle.kts | 2 +- ktor-swagger-ui-examples/build.gradle.kts | 2 +- .../smiley4/ktorswaggerui/examples/Examples.kt | 1 - .../ktorswaggerui/examples/FileUpload.kt | 12 +++++++----- ktor-swagger-ui/build.gradle.kts | 8 +------- .../dsl/routes/OpenApiBaseBody.kt | 6 +----- .../dsl/routes/OpenApiSimpleBody.kt | 4 ---- .../builder/OperationBuilderTest.kt | 18 +++++++++++++----- 8 files changed, 24 insertions(+), 29 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 62057ba..5b97bd8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ import io.gitlab.arturbosch.detekt.Detekt plugins { kotlin("jvm") version "1.8.21" id("org.owasp.dependencycheck") version "8.2.1" - id("io.gitlab.arturbosch.detekt") version "1.23.0" + id("io.gitlab.arturbosch.detekt") version "1.23.6" } subprojects { diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts index ef023fd..4740d60 100644 --- a/ktor-swagger-ui-examples/build.gradle.kts +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation(project(":ktor-swagger-ui")) - val ktorVersion = "2.3.7" + val ktorVersion = "2.3.11" implementation("io.ktor:ktor-server-netty-jvm:$ktorVersion") implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-jackson:$ktorVersion") diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt index 1a0c5c1..7bb95d6 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt @@ -110,4 +110,3 @@ private fun Application.myModule() { private data class MyExampleClass( val someValue: String ) - diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt index e55f989..095c11a 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt @@ -53,9 +53,11 @@ private fun Application.myModule() { post("single", { request { body(KTypeDescriptor(typeOf())) { - mediaType(ContentType.Image.PNG) - mediaType(ContentType.Image.JPEG) - mediaType(ContentType.Image.SVG) + mediaTypes = setOf( + ContentType.Image.PNG, + ContentType.Image.JPEG, + ContentType.Image.SVG, + ) } } }) { @@ -66,10 +68,10 @@ private fun Application.myModule() { post("multipart", { request { multipartBody { - mediaType(ContentType.MultiPart.FormData) + mediaTypes = setOf(ContentType.MultiPart.FormData) part("first-image", KTypeDescriptor(typeOf())) { mediaTypes = setOf( - ContentType.Image.PNG, // todo: why setOf here and not for single body? + ContentType.Image.PNG, ContentType.Image.JPEG, ContentType.Image.SVG ) diff --git a/ktor-swagger-ui/build.gradle.kts b/ktor-swagger-ui/build.gradle.kts index 355dc0e..1ef6d32 100644 --- a/ktor-swagger-ui/build.gradle.kts +++ b/ktor-swagger-ui/build.gradle.kts @@ -1,12 +1,6 @@ -import io.gitlab.arturbosch.detekt.Detekt - -object Meta { - const val artifactId = "ktor-swagger-ui" -} - dependencies { - val ktorVersion = "2.3.7" + val ktorVersion = "2.3.11" implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") implementation("io.ktor:ktor-server-webjars:$ktorVersion") implementation("io.ktor:ktor-server-auth:$ktorVersion") diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt index f6e3408..20f846c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt @@ -23,11 +23,7 @@ sealed class OpenApiBaseBody { /** * Allowed Media Types for this body. If none specified, a media type will be chosen automatically based on the provided schema */ - protected val mediaTypes = mutableSetOf() - - fun mediaType(type: ContentType) { - mediaTypes.add(type) - } + var mediaTypes: Set = emptySet() abstract fun build(): OpenApiBaseBodyData } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt index 8960585..677e1ea 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt @@ -22,10 +22,6 @@ class OpenApiSimpleBody( */ private val examples = mutableListOf() -// fun example(name: String, value: Any, block: OpenApiExample.() -> Unit) { -// examples[name] = OpenApiExample(value).apply(block) -// } - fun example(example: ExampleDescriptor) { examples.add(example) } diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt index a9ea316..005bd77 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt @@ -448,8 +448,10 @@ class OperationBuilderTest : StringSpec({ body(KTypeDescriptor(typeOf())) { description = "the test body" required = true - mediaType(ContentType.Application.Json) - mediaType(ContentType.Application.Xml) + mediaTypes = setOf( + ContentType.Application.Json, + ContentType.Application.Xml + ) example( ValueExampleDescriptor( name = "example 1", @@ -537,7 +539,9 @@ class OperationBuilderTest : StringSpec({ documentation = OpenApiRoute().also { route -> route.request { multipartBody { - mediaType(ContentType.MultiPart.FormData) + mediaTypes = setOf( + ContentType.MultiPart.FormData + ) part("image", KTypeDescriptor(typeOf())) { mediaTypes = setOf( ContentType.Image.PNG, @@ -614,7 +618,9 @@ class OperationBuilderTest : StringSpec({ documentation = OpenApiRoute().also { route -> route.request { multipartBody { - mediaType(ContentType.MultiPart.FormData) + mediaTypes = setOf( + ContentType.MultiPart.FormData + ) } } }.build(), @@ -895,7 +901,9 @@ class OperationBuilderTest : StringSpec({ documentation = OpenApiRoute().also { route -> route.request { multipartBody { - mediaType(ContentType.MultiPart.FormData) + mediaTypes = setOf( + ContentType.MultiPart.FormData + ) part("customData", RefTypeDescriptor("myCustomSchema")) {} } } From 8a9d2a11e559547188e7d6a1864818b00bb34377 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 16 May 2024 13:22:48 +0200 Subject: [PATCH 14/32] cleanup build config --- build.gradle.kts | 52 ++--------- gradle.properties | 27 +++++- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 10 +-- ktor-swagger-ui-examples/build.gradle.kts | 56 ++++++------ ktor-swagger-ui/build.gradle.kts | 101 +++++++++++++++------- 7 files changed, 134 insertions(+), 114 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5b97bd8..1d6ed8a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,49 +1,7 @@ -import io.gitlab.arturbosch.detekt.Detekt - plugins { - kotlin("jvm") version "1.8.21" - id("org.owasp.dependencycheck") version "8.2.1" - id("io.gitlab.arturbosch.detekt") version "1.23.6" -} - -subprojects { - - val ktorSwaggerUiVersion: String by project - val ktorSwaggerUiGroupId: String by project - group = ktorSwaggerUiGroupId - version = ktorSwaggerUiVersion - - apply(plugin = "org.jetbrains.kotlin.jvm") - apply(plugin = "io.gitlab.arturbosch.detekt") - apply(plugin = "org.owasp.dependencycheck") - - repositories { - mavenLocal() - mavenCentral() - } - - dependencies {} - - kotlin { - jvmToolchain(11) - } - - tasks.withType().configureEach { - useJUnitPlatform() - } - - tasks.withType().configureEach { - ignoreFailures = false - buildUponDefaultConfig = true - allRules = false - config.setFrom("$projectDir/../detekt/detekt.yml") - reports { - html.required.set(true) - md.required.set(true) - xml.required.set(false) - txt.required.set(false) - sarif.required.set(false) - } - } - + kotlin("jvm") version "1.9.21" + id("org.jetbrains.dokka") version "1.9.20" apply false + id("org.owasp.dependencycheck") version "8.2.1" apply false + id("io.gitlab.arturbosch.detekt") version "1.23.0" apply false + id("com.vanniktech.maven.publish") version "0.28.0" apply false } diff --git a/gradle.properties b/gradle.properties index c2f6923..a403dbc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,27 @@ kotlin.code.style=official -ktorSwaggerUiGroupId=io.github.smiley4 -ktorSwaggerUiVersion=3-indev +# project id +projectGroupId=io.github.smiley4 +projectArtifactIdBase=ktor-swagger-ui +projectVersion=3.0-indev + +# publishing information -> todo +#projectNameBase=Schema-Kenerator +#projectDescriptionBase=Kotlin library for automatically generating various schemas +#projectScmUrl=https://github.com/SMILEY4/schema-kenerator +#projectScmConnection=scm:git:git://github.com/SMILEY4/schema-kenerator.git +#projectLicenseName=The Apache License, Version 2.0 +#projectLicenseUrl=https://www.apache.org/licenses/LICENSE-2.0.txt +#projectDeveloperName=smiley4 +#projectDeveloperUrl=https://github.com/SMILEY4 + +# dependency versions +versionKtor=2.3.11 +versionSwaggerUI=5.9.0 +versionSwaggerParser=2.1.19 +versionSchemaKenerator=0.1 +versionKotlinLogging=3.0.5 +versionKotest=5.8.0 +versionKotlinTest=1.8.21 +versionMockk=1.13.8 +versionLogback=1.4.11 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 zcmY+KWl$VIlZIh&f(Hri?gR<$?iyT!TL`X;1^2~W7YVSq1qtqM!JWlDxLm%}UESUM zndj}Uny%^UnjhVhFb!8V3s(a#fIy>`VW15{5nuy;_V&a5O#0S&!a4dSkUMz_VHu3S zGA@p9Q$T|Sj}tYGWdjH;Mpp8m&yu&YURcrt{K;R|kM~(*{v%QwrBJIUF+K1kX5ZmF zty3i{d`y0;DgE+de>vN@yYqFPe1Ud{!&G*Q?iUc^V=|H%4~2|N zW+DM)W!`b&V2mQ0Y4u_)uB=P@-2`v|Wm{>CxER1P^ z>c}ZPZ)xxdOCDu59{X^~2id7+6l6x)U}C4Em?H~F`uOxS1?}xMxTV|5@}PlN%Cg$( zwY6c}r60=z5ZA1L zTMe;84rLtYvcm?M(H~ZqU;6F7Evo{P7!LGcdwO|qf1w+)MsnvK5^c@Uzj<{ zUoej1>95tuSvDJ|5K6k%&UF*uE6kBn47QJw^yE&#G;u^Z9oYWrK(+oL97hBsUMc_^ z;-lmxebwlB`Er_kXp2$`&o+rPJAN<`WX3ws2K{q@qUp}XTfV{t%KrsZ5vM!Q#4{V& zq>iO$MCiLq#%wXj%`W$_%FRg_WR*quv65TdHhdpV&jlq<=K^K`&!Kl5mA6p4n~p3u zWE{20^hYpn1M}}VmSHBXl1*-)2MP=0_k)EPr#>EoZukiXFDz?Di1I>2@Z^P$pvaF+ zN+qUy63jek2m59;YG)`r^F3-O)0RDIXPhf)XOOdkmu`3SMMSW(g+`Ajt{=h1dt~ks ztrhhP|L4G%5x79N#kwAHh5N){@{fzE7n&%dnisCm65Za<8r_hKvfx4Bg*`%-*-Mvn zFvn~)VP@}1sAyD+B{{8l{EjD10Av&Mz9^Xff*t`lU=q=S#(|>ls520;n3<}X#pyh& z*{CJf7$*&~!9jMnw_D~ikUKJ2+UnXmN6qak{xx%W;BKuXt7@ky!LPI1qk?gDwG@@o zkY+BkIie>{{q==5)kXw(*t#I?__Kwi>`=+s?Gq6X+vtSsaAO&Tf+Bl$vKnzc&%BHM z=loWOQq~n}>l=EL(5&6((ESsQC3^@4jlO5Od{qN#sWV)vqXw}aA>*uvwZopNN(|-T zRTF%5Y_k1R$;(d-)n;hWex{;7b6KgdAVE@&0pd(*qDzBO#YZV%kh%pYt1`hnQ(Fa& zYiDrOTDqk5M7hzp9kI2h!PxNnuJ&xl*zF8sx6!67bA49R1bmUF5bpK&&{eI0U~cH}PM z3aW1$lRb|ItkG5~_eBNu$|I|vYIdAA9a!pVq<+UTx*M}fG`23zxXp&E=FfnY- zEzKj;Cu_s4v>leO7M2-mE(UzKHL4c$c`3dS*19OpLV^4NI*hWWnJQ9lvzP4c;c?do zqrcsKT*i~eIHl0D3r4N{)+RsB6XhrC^;sp2cf_Eq#6*CV;t8v=V!ISe>>9kPgh}NI z=1UZutslxcT$Ad;_P^;Oouoa(cs!Ctpvi>%aQ+Zp=1d|h{W9Wmf7JWxa(~<#tSZ?C%wu4_5F!fc!<@PIBeJ)Nr^$bB6!_Gic_7}c3J{QI~Gg5g5jTp9}V6KYgrgaX>pJt}7$!wOht&KO|+z{Iw@YL|@~D zMww}+lG}rm2^peNx>58ME||ZQxFQeVSX8iogHLq_vXb`>RnoEKaTWBF-$JD#Q4BMv zt2(2Qb*x-?ur1Y(NsW8AdtX0#rDB?O(Vs4_xA(u-o!-tBG03OI!pQD+2UytbL5>lG z*(F)KacHqMa4?dxa(Vcrw>IIAeB$3cx#;;5r2X;HE8|}eYdAgCw#tpXNy7C3w1q`9 zGxZ6;@1G%8shz9e+!K2MO*{_RjO}Jo6eL3{TSZ>nY7)Qs`Dhi5><@oh0r)gT7H-?3 zLDsd^@m%JvrS8sta5`QiZNs^*GT}Hiy^zjK2^Ni%`Z|ma)D2 zuyumbvw$M8$haCTI~6M%d4+P)uX%u{Sfg4Al+F7c6;O-*)DKI7E8izSOKB#FcV{M+ zEvY0FBkq!$J0EW$Cxl}3{JwV^ki-T?q6C30Y5e&p@8Rd?$ST-Ghn*-`tB{k54W<>F z5I)TFpUC!E9298=sk>m#FI4sUDy_!8?51FqqW!9LN1(zuDnB3$!pEUjL>N>RNgAG~-9Xm|1lqHseW(%v&6K(DZ3Pano(1-Qe?3%J&>0`~w^Q-p&@ zg@HjvhJk?*hpF7$9P|gkzz`zBz_5Z!C4_-%fCcAgiSilzFQef!@amHDrW!YZS@?7C zs2Y9~>yqO+rkih?kXztzvnB^6W=f52*iyuZPv$c42$WK7>PHb z6%MYIr5D32KPdwL1hJf{_#jn?`k(taW?mwmZVvrr=y~fNcV$`}v(8};o9AjOJumS4 z`889O91^pkF+|@$d9wVoZ3;^j;^sUs&Ubo_qD&MTL%O z&*SE0ujG~zm;?x)8TLC&ft))nyI zcg44@*Q{cYT+qGrA=In_X{NNCD+B0w#;@g)jvBU;_8od6U>;7HIo@F*=g8CQUo(u^ z3r4FJ7#<@)MXO&5+DgKE&^>^`r!loe7CWE*1k0*0wLFzSOV8jvlX~WOQ?$1v zk$Or}!;ix0g78^6W;+<=J>z@CBs!<<)HvF(Ls-&`matpesJ5kkjC)6nGB@b{ii6-Uoho$BT%iJgugTOeZ$5Xo4D7Pd< zC*LJh5V@2#5%aBZCgzlQi3@<_!VfiL07ywc)ZbwKPfcR|ElQoS(8x|a7#IR}7#Io= zwg4$8S{egr-NffD)Fg&X9bJSoM25pF&%hf>(T&9bI}=#dPQyNYz;ZZ7EZ=u1n701SWKkZ9n(-qU ztN`sdWL1uxQ1mKS@x11;O|@^AD9!NeoPx}?EKIr!2>1Qq4gjfGU)tr6?Z5l7JAS3j zZeq{vG{rb%DFE4%$szK}d2UzB{4>L?Tv+NAlE*&Nq6g+XauaSI+N2Y8PJLw+aNg1p zbxr|hI8wcMP&&+(Cu|%+Jq|r>+BHk@{AvfBXKiVldN)@}TBS0LdIpnANCVE26WL-} zV}HJ^?m&$Rkq;Zf*i-hoasnpJVyTH__dbGWrB_R55d*>pTyl6(?$EO@>RCmTX1Hzr zT2)rOng?D4FfZ_C49hjMV*UonG2DlG$^+k=Y%|?Dqae4}JOU=8=fgY4Uh!pa9eEqf zFX&WLPu!jArN*^(>|H>dj~g`ONZhaaD%h_HHrHkk%d~TR_RrX{&eM#P@3x=S^%_6h zh=A)A{id16$zEFq@-D7La;kTuE!oopx^9{uA3y<}9 z^bQ@U<&pJV6kq7LRF47&!UAvgkBx=)KS_X!NY28^gQr27P=gKh0+E>$aCx&^vj2uc}ycsfSEP zedhTgUwPx%?;+dESs!g1z}5q9EC+fol}tAH9#fhZQ?q1GjyIaR@}lGCSpM-014T~l zEwriqt~ftwz=@2tn$xP&-rJt?nn5sy8sJ5Roy;pavj@O+tm}d_qmAlvhG(&k>(arz z;e|SiTr+0<&6(-An0*4{7akwUk~Yf4M!!YKj^swp9WOa%al`%R>V7mi z+5+UodFAaPdi4(8_FO&O!Ymb#@yxkuVMrog(7gkj$G@FLA#ENMxG)4f<}S%Fn?Up$+C%{02AgMKa^ z4SFGWp6U>{Q6VRJV}yjxXT*e`1XaX}(dW1F&RNhpTzvCtzuu;LMhMfJ2LBEy?{^GHG!OF!! zDvs64TG)?MX&9NCE#H3(M0K>O>`ca0WT2YR>PTe&tn?~0FV!MRtdb@v?MAUG&Ef7v zW%7>H(;Mm)RJkt18GXv!&np z?RUxOrCfs;m{fBz5MVlq59idhov21di5>WXWD-594L-X5;|@kyWi@N+(jLuh=o+5l zGGTi~)nflP_G}Yg5Pi%pl88U4+^*ihDoMP&zA*^xJE_X*Ah!jODrijCqQ^{=&hD7& z^)qv3;cu?olaT3pc{)Kcy9jA2E8I)#Kn8qO>70SQ5P8YSCN=_+_&)qg)OYBg|-k^d3*@jRAeB?;yd-O1A0wJ z?K*RDm|wE<(PBz~+C%2CTtzCTUohxP2*1kE8Of~{KRAvMrO_}NN&@P7SUO{;zx0iK z@or9R8ydYOFZf(cHASCAatL%;62IL27~SmASr(7F&NMr+#gNw@z1VM z_ALFwo3)SoANEwRerBdRV`>y`t72#aF2ConmWQp(Xy|msN9$yxhZ1jAQ67lq{vbC5 zujj|MlGo`6Bfn0TfKgi(k=gq0`K~W+X(@GzYlPI4g0M;owH3yG14rhK>lG8lS{`!K z+Nc@glT-DGz?Ym?v#Hq|_mEdPAlHH5jZuh*6glq!+>Lk$S%ED2@+ea6CE@&1-9a?s znglt|fmIK}fg<9@XgHe4*q!aO<-;Xj$T?IzB-{&2`#eA6rdtCi80mpP&vw(Uytxu$#YzNI_cB>LS zmim>ys;ir;*Dzbr22ZDxO2s;671&J0U<9(n1yj)J zHFNz=ufPcQVEG+ePjB<5C;=H0{>Mi*xD>hQq8`Vi7TjJ$V04$`h3EZGL|}a07oQdR z?{cR(z+d>arn^AUug&voOzzi$ZqaS)blz-z3zr;10x;oP2)|Cyb^WtN2*wNn`YX!Y z+$Pji<7|!XyMCEw4so}xXLU)p)BA~2fl>y2Tt}o9*BPm?AXA8UE8a;>rOgyCwZBFa zyl42y`bc3}+hiZL_|L_LY29vVerM+BVE@YxK>TGm@dHi@Uw*7AIq?QA9?THL603J% zIBJ4y3n8OFzsOI;NH%DZ!MDwMl<#$)d9eVVeqVl(5ZX$PPbt*p_(_9VSXhaUPa9Qu z7)q4vqYKX7ieVSjOmVEbLj4VYtnDpe*0Y&+>0dS^bJ<8s*eHq3tjRAw^+Mu4W^-E= z4;&namG4G;3pVDyPkUw#0kWEO1;HI6M51(1<0|*pa(I!sj}F^)avrE`ShVMKBz}nE zzKgOPMSEp6M>hJzyTHHcjV%W*;Tdb}1xJjCP#=iQuBk_Eho6yCRVp&e!}4IBJ&?ksVc&u#g3+G$oNlJ?mWfADjeBS-Ph3`DKk-~Z70XugH8sq2eba@4 zIC1H_J$`9b$K`J)sGX3d!&>OmC@@rx1TL~NinQOYy72Q_+^&Mg>Ku(fTgaXdr$p_V z#gav1o{k~c>#)u3r@~6v^o)Lf=C{rAlL@!s457pq)pO;Cojx7U{urO4cvXP|E>+dV zmr2?!-5)tk-&*ap^D^2x7NG6nOop2zNFQ9v8-EZ{WCz-h36C)<^|f{V#R_WE^@(T0+d-at5hXX{U?zak*ac-XnyINo+yBD~~3O1I=a z99|CI>502&s-Qi5bv>^2#cQ%ut<4d7KgQ^kE|=%6#VlGiY8$rdJUH{sra;P~cyb_i zeX(kS%w0C?mjhJl9TZp8RS;N~y3(EXEz13oPhOSE4WaTljGkVXWd~|#)vsG6_76I)Kb z8ro?;{j^lxNsaxE-cfP;g(e;mhh3)&ba}li?woV2#7ByioiD>s%L_D;?#;C#z;a(N z-_WY<=SH42m9bFQ>Nb z@4K$@4l8pD7AKxCR>t0%`Qoy9=hA?<<^Vcj8;-E+oBe3ReW1`el8np8E$k{LgFQ}2 z2t8a`wOXFdJ9!5$&mEfD1CnJ)TB+RJih88-Zos9@HZ# zL#{qfbF0ARTXkR@G{lwlOH~nnL)1jcyu!qv2`57S&%oKz0}r{~l9U_UHaJ5!8#nrs z?2FrL`mxnzu&{bweD&62)ilz*?pYIvt`T!XFVVA78})p1YEy7 z8fK#s?b~Yo$n7&_a?EBdXH-_W)Z44?!;DFx6pZ?~RArtBI*Qm4~6nX6Z_T*i$bQPE;Qz?DAPstpGSqr-AJ zo%m9cA`oDDm?&dTaoh_>@F>a?!y4qt_;NGN9Z<%SS;fX-cSu|>+Pba22`CRb#|HZa z;{)yHE>M-pc1C0mrnT~80!u&dvVTYFV8xTQ#g;6{c<9d!FDqU%TK5T6h*w*p980D~ zUyCb`y3{-?(mJFP)0*-Nt;mI$-gc4VQumh|rs&j_^R{sgTPF`1Xja2YWstsKFuQ(d zmZMxV$p$|qQUXchu&8%J(9|)B?`~rIx&)LqDS>ob5%gTeTP#Sbny#y*rnJ&?(l=!( zoV~}LJ1DPLnF8oyM(2ScrQ0{Q4m4-BWnS4wilgCW-~~;}pw=&<+HggRD_3c@3RQIr z9+-%!%}u_{`YS=&>h%kPO3ce}>y!d-zqiniNR-b5r97u;+K6HA2tS>Z#cV{+eFI`* zd8RMGAUtX1KWfPV;q<-5JAykS+2sY$2~UX+4461a(%{P#{rwFPu0xpIuYlbgD{C7C z=U{FUarVTYX6ZUq3wE@G^QT4H2Re;n$Fz9cJ>hABl)9T8pozqbA1)H-%1=WKm^QMu zjnUZ&Pu>q+X&6Co*y#@pxc-4waKMInEPGmE_>3@Ym3S*dedSradmc5mlJn`i0vMW6 zhBnGQD^Z;&S0lnS0curqDO@({J7kTtRE+Ra?nl^HP9<)W&C>~`!258f$XDbyQOQXG zP8hhySnarOpgu8xv8@WlXnm(Uk~)_3$Sg0vTbU3 z{W!5B(L3{Yy3K5PN<@jEarAtja`}@KYva&zFRF*s+_%jIXh$T(S=an8?=Ry3H*NRqWgsM`&!#|@kf1>=4q%bFw7^Rhz!z5I zyI^zU8_R1WN9`88Z=n>pIZQ`Ixr~_9G%Q}@A7rd#*%y7G zXl^Id=^ZL?Rx}}gWXCqzj9C6;x(~mAH|$JteXa1MH<6UQig@!Hf~t}B%tP0I|H&;y zO6N0}svOa1a^PyP9N5?4W6VF%=Bj{qHUgc8@siw4bafT=UPFSoQqKgyUX>sXTBZ=x zOh^Ad!{kOM9v{%5y}`-8u*T&C7Vq6mD%GR}UeU(*epO&qgC-CkD;%=l)ZuinSzHM` z{@`j&_vC6dDe{Yb9k@1zeV_K6!l(@=6ucoI=R^cH=6{i71%4W3$J-?<8Qn#$-DMtA z6Qqi)t?4ifrt%3jSA#6ji#{f(($KBL-iQh-xrC||3U3lq`9>r)>X%oLvtimuHW-)} zy}>9~|M>w4eES`g7;iBM%Se5-OP%1U6gNWp3AZqT8C6OlFFfQ$|7LL;tBV)(qlp4K zruar^K8FnJN3@_}B;G`a~H`t|3+6d>q3#`ctTkE-D^1#d9NalQ04lH*qUW2!V zhk7#z8OwHhSl8w14;KctfO8ubZJ4$dEdpXE78wABz=n5*=q9ex3S}`e7x~~V-jmHOhtX2*n+pBslo3uosdE7xABK=V#-t{1Hd~?i z{i~%Bw6NYF+F$aK$M`r#xe=NxhA5=p%i7!$);sd>Q}#`G?Q~fygrMXmZw?0#5#17W}6Tj+&kFexG{!mYl5FoA99}3G9l;3lVQ^ z48^~gsVppE*x91WheqI(A%F0Z#$#1UJP1R12Mj9r)y(A?a+iquX+d8WD4WAQJ_!oq z9rTISr7bPd(GTP57xm$}C}&kjMivi;zi^Y9g3&X0A;ovdJ?{%_wHgt%%9P&N4H z^XzV(uNA4 zAP`hgP6BEN5`YXh|DF~6Pud?~gWfhUKoPX4>z|}0aocC&K+AoV%|SX*N!wGq3|y< zg4lP(04XIPmt6}$N!dTk+pZv>u;MTB{L4hp9uXk7>aS!6jqM2lVr%{)H3$O127TSZ z0x9hi0k-P?nWFdQ0K`pykqUIT&jD~B0tHP{ffS(}fZ(aW$oBWTSfHO!A^><6vA?qar%tzN-5NQO zL&|F{nGiQyzNJ+bM$Y`n=Lx^3wTG^o2bGB@cwr1eb+6c-1tN=U+Db;bc~eJ!hwM{SbI=#g?$!PjDB+) zPgU_2EIxocr*EOJG52-~!gml&|D|C2OQ3Y(zAhL}iae4-Ut0F*!z!VEdfw8#`LAi# zhJ_EM*~;S|FMV6y%-SduHjPOI3cFM(GpH|HES<}*=vqY+64%dJYc|k?n6Br7)D#~# zEqO(xepfaf2F{>{E2`xb=AO%A<7RtUq6kU_Iu0m?@0K(+<}u3gVw5fy=Y4CC*{IE3 zLP3YBJ7x+U(os5=&NT%gKi23bbaZ`@;%ln)wp4GpDUT$J8NtFDHJzIe_-t}{!HAsh zJ4<^WovY};)9IKAskSebdQiXv$y5}THuJZ}ouoElIZRui=6lrupV|_Jz=9^&;@HwL;J#@23k?A;k`0Bgf;ioO>W`IQ+4? z7A)eKoY4%+g%=w;=Vm8}H>@U*=*AWNtPqgWRqib#5RTGA@Q=43FrQn3J`GkTUV5yp0U`EOTqjfp+-9;0F8!dMEwwcK%(6`8sDD^aR04 zd6O5vh|Xk?&3dy4f|1QK&Ulf{h6Iq;d-&*ti#Ck>wZFG;GHwc?b;X~eBITx49>2d8 z4HcK&1&DvEGT6kXdzAm4oO8%c}8OBt~8H956_;YP-ss*uMf==a+%w~F>Qkm7r)IAuxuoX}h92$gHqbFUun#8m zWHdy`Zrm#=Pa98x8cO0vd@Tgkr*lm0{dky+Gocr0P8y%HGEI#c3qLqIRc`Oq_C%*; zG+QTr(#Q|yHKv6R@!DmLlwJQ3FAB)Yor-I4zyDyqM4yp5n2TrQH>gRt*Zw0+WI-Sj`EgmYHh=t9! zF6lz^xpqGGpo6!5`sc0a^FVhy_Uxq|@~(1@IIzV)nTpY9sY`CV!?8e&bB8=M&sYEb z2i}fvKdhp9Hs68Y-!QJ<=wE(iQ5+49tqt;Rh|jhYrI5VW-mIz|UY{h8E=rC5sh#DU z?wGgk-Tn!I?+Zer7pHlF_Z^!Kd1qkS3&lv#%s6-<5Y%jQL${cge5=G5Ab?D&|9$Y~ zf%rJC2+=2vg;y0-SJb3<@3%}BO$T$C66q$L_H33a`VUbgW~N(4B=v5(<=My|#|J7q z*Ox4wL4kbJd_~EjLTABSu4U7Jk#`y(6O*U6(k6XxM}CtGZB(H@3~kh*zaGRXM}Iwp zQ%xFk2>@wiZrVCV_G4G~v;NebCQ%T7{SDyPpSv&dT@Cn)Mx@IK*IdNrj{*4pkV4wv z)y0J538h>cpB7iPSzA~x24T`{dzNkpvGIqvt1Dvdq@o-`B=$hkczX8$yFMhsWNK-X zxr$kR$tMD0@W)Vxe1^t9qVmsg&K^F@u84)(n2dttIEAZFN6VD$&tskpG%SI7whGL3 z)DeRiwe&?8m7U{G`oW8!SCi*dM>oYL%UKQnKxV_0RXAEBQg1kStExGEUVwLJ0orGGwb7uv+kPDl7_E2*iD|J*=8A@;XCvwq0aw5oJYN*Yh&o=l} z2z8YKb-fIAH5spql4eXqp*)o2*b>#1@DSt?zZi{GPj0gH&Nm+EI<3^z0w%YTEV4xw zI6$+=Faa|Y4o5i0zm5lOg|&tmnJ806DBovU@Ll6XsA;NRrTK~t*AAJIAS=v-UZ%Pr z$oddI@NRir&erzCwq|)ciJemr-E061j{0Vc@Ys7K(mW|JYj*$+i1Q8XlIK8T?TYS(AXu$`2U zQ@fHxc=AVHl_}cRZQ)w0anMEoqRKKIvS^`<-aMf*FM`NsG&Uowneo+Ji$7DUDYc7*Hjg;-&aHM%3 zXO6cz$$G};Uqh+iY7Wpme>PHG4cu(q;xyskNLs$^uRRMfEg?8Cj~aE-ajM%CXkx0F z>C?g3tIA#9sBQOpe`J+04{q7^TqhFk^F1jFtk4JDRO*`d-fx`GYHb=&(JiaM1b?Y^ zO3Kj3sj76ieol|N$;>j@t#tKj=@*gP+mv}KwlTcPYgR$+)2(gk)2JNE=jSauPq!$< z<|?Sb%W)wS)b>b6i{8!x!^!xIdU3{CJFVnTcw0j{M%DUCF=_>eYYEUWnA-|B(+KYL z_W_`JI&&u^@t0})@DH^1LDuT0s3dMpCHIbYBgOT4Zh_4yHbSqRbtIKndeT4Q*Jg91 z@>rO!^t-G~*AIW;FQ$3J=b;oGg8?CTa~qNCb>&cgp@e;?0AqA&paz~(%PYO+QBo4( zp?}ZdSMWx0iJm7HVNk9A#^9Osa#GPJ!_pYEW}($8>&2}fbr@&ygZ?${A7_9?X$(&5 z#~-hxdPQwCNEpf=^+WH-3`2LxrrBMTa}~qJC9S;VzhG!On^JLyW6WkF{8aAE$sM+( zxr8xLW(KIjI`Rm(24r3OJBk<3GF=G!uSP0-G&AY32mLm8q=#Xom&Pqv=1C{d3>1^ zAjsmV@XZ%BKq^eUfBpa8KvO8ob|F3hAjJv*yo2Bhl0)KUus{qA9m8jf)KnOGGTa6~4>3@J_VzkL|vYPl*uL+Ot*Q7W!f5rJw5+AsjP_IfL+-S*2p| zB7!FhjvkUTxQkGWGSg{X;h~dK>gAJivW?88Nu!3o>ySDaABn$rAYt086#27fbjPQS zhq>55ASvm*60qRdVOY9=bU^+{Pi#!OaZwENN;zy5?EztOHK-Q5;rCuiFl}BSc1YaQ zC-S{=KsGDz@Ji9O5W;XxE0xI|@3o6(2~i4b8Ii9VT;^G$*dRw(V?=br)D&q^XkeBX z+gl~+R@rVD-Hwv@7RHV?Bip5KMI)aV^&snt?H<$Nt=OPx#VxF&BGi?2A2+lNOYywNUGMeGL;|(=UjGDtLG0sN&LpGx;|U;xa13s z;W_|SPk^G}!M9_^pO zA3bt3-tca%^42sHeDtfcC0S3w3H1ny!Bxpa=*k?XRPpx9Bb-gx1J9Yvx)4J(8cG+q z(iCPZ9dsf3#QVyZgD_MW#G#qgV)olu$59&3(PzQfw@%4uZ~<5J=ABvdY43(Qnp{;G zHg3>@T#>DbTuhFl3)fb3TFqdh)V2aq7!;&JOHseTWukvA7}(iGUq;v-{2J0iHSNHq z;+)h!p6Ok^+Sp8-jgL($n6Qu47xyE`cFO5SdZR6;R!FET`tm#0D37z339Suxjpv+s z*=%2-N$N?X&0?x_uut3erF@aBGj;9$k9?3FlbDO{RQa1_qtxrh4!4#fjp4x~akvdTp@ zos?^Q&XE;3N93s4rHQGPrV7+au1$$aB6$hLy*Yz_kN$~dweb9PcB!eYVQTGjFuJP> zZCEwBtb>TIgIO^qAzq@Bv-qud_ZD-2W<_at&ml-gv`tPt$@DF5`HlA zM>DmmMkpv&Zm-8)Y#0bLQf4MpD4_-7M8eu6rh(tL8dq8onHs#R9J~dGd2IaXXMC~h z91pKhnQa%Fsn29nAA1;x(%oC zhca~qQDJaMf?wFrl-Pj;e$bZMYmMF!Y3Lv&Sb?Sjn#!NVx&NDyc^$b4uYyo2OmERa zRz;yDGd@JTykzFLe|Wk-y7#3x`6$wt$zR8r48mdUvfbeL+4D|Z``~7$PrE@qc7rZe zVsIoIbCwzjLZ@_M1*bD{HaYn();Z1-q*-I{tEnTZ(}Zmk&%MXSNBX>o| z-u*RNkAyKC-Srp7c-=@5f)xMWg>o2WWl}j6j9=8+D8;T z>0*0q#;qw8%U8i;6s0fu#I*%(g*@@a2Er@@nyI}{=@W{Z-;`=wN4N~>6Xrh&z#g}l zN1g5}0-#(nHUTv_rl2{yUZ;h#t&Fd?tY!7L%ClY)>uH-Ny2ET$lW$S)IQiN79H)D^ zb&0AXYkupy0~w8)*>Sj_p9}4L?lGTq%VG|2p`nWGhnM^!g|j-|O{%9Q%swOq63|*W zw$(N_laI}`ilB+o!a-wl?er~;;3+)$_akSQ!8YO_&-e*SI7n^(QQ;X0ZE`{4f!gAl z5$d+9CKVNonM!NO_frREICIAxOv)wm>}-k?iRisM`R7;=lyo|E_YR~FpS&PS`Lg0f zl-ON<0S%Uix8J%#yZdkCz4YNhcec<|7*P(JsM#>-L>+tYg_71q9~70FAc^6KW5jql zw!crdgVLH1G_eET=|SEc977;)ezVC|{PJZfra|}@rD;0s&@61mTEBJtILllg{%{vN zfhb&lq0yChaLhnJ-Qb62MB7`>M;|_ceHKZAeeh@#8tbrK!ArP6oXIhMK;dhEJTY`@ z0Tq>MIe0`7tGv)N*F0IGYSJv0vN?Az8g+4K9S!pW2~9F4W(_U_T=jCZrzuZ3*|__T zONp_UWmyePv8C~rckc?Xji;Z5OEqg zC*Um)i;Wh4TEwqReQdVVbUKT^2>Tpi6z_^-uF*adUFug4i@JhzpWT^Sk&E>CyP2?H zWf6x}ehuTs6wvzCnTU&gYzT029Nz19(In1WC z`(1IGmi!O%2AR|BjQa4Q0~u)kM%}?xQyjWuQ16^Gp++;`vr7!k--UZWM*~7Zl|ceO@I3`OpaRhD;YoCuo5IC0uHx>9 z478hu@H|e0Zlo)Zj@01#;8BDs@991xe~^9uG2}UXLM(m7fa}AMwX*tjioBeV&Q8Gx zSq$6wZFkRBK`cMI>R(@W@+lo2t)L+4q-negWRLWZBz*|%=W4v62JrmzNuOtA*x)QE z5L%=OH#@KMdB%Jp^r?0tE}5-*6oP`-lO7Sf)0)n*e<{HA=&qhLR)oD8-+V}Z4=md) z+k9lKf64DB2hAT)UaCP~di?-V3~JBH7itYyk~L6hrnxM%?RKntqd`=!b|e7eFnAcu z3*V;g{xr7TSTm$}DY%~SMpl>m{Sj!We+WfxSEor?YeiAxYUy25pn(?T()E>ByP^c@ zipwvWrhIK((R((VU+;@LmOnDu)ZXB3YArzzin!Z^0;PyJWnlfflo|q8(QY;o1*5CO z##hnkO{uynTMdk`~DOC#1 zdiYxQoy}=@7(ke#A8$YZZVtk4wo$8x28&I;cY3Ro-|kW=*yiiHgCLZeAr)UtVx>Tu z|LvL0hq|1-jC0I4x#>&QZCfrVB=zT!nR|~Uz`9%~2 znl{uZ{VEszW`Fad^q_HB!K9*|U-stK%?~;g?&&+12A}Rq$z($Bzuk^2X(Y=hF?-dQ ztc3DsQKI;qhWIV`99Q#R3xnU0AvY!i*BECj-z9l74|%O=V@nlv|qqC^r^-~C?E zGW%c|uYgnfJ(gjsTm_cIqcv*mYM{+i+&@F@+69ZQOK&u#v4oxUSQJ=tvqQ3W=*m;| z>SkBi8LYb-qRY7Sthh*0%3XAC%$z1rhOJzuX=PkTOa=DlocZUpE#KxVNH5)_4n=T( zGi3YrH7e~sPNYVBd~Grcq#CF~rN{p9Zza-Ntnwfma@TB)=3g36*0lSZg#ixEjFe%+ zX=&LDZ5zqculZ`=RYc^ln(~;nN|Qh6gN=!6f9-N2h+3NWbIxYud&;4SX*tWf5slk4 z{q@@l71UAZgj~*6edXb57fBUxvAS7s(RI=X868JM0+^DCn2yC>;v%S;qPOjB>YVsz(Zx9a>>BK&M zIQK>7_n)4ud0X5YM}^i*keH{ehLsiy9@NvOpsFeQjdI6anLGvVbBw_*fU1TzdVS$i z*4j7z!I5RF#rSz|8ibi$;qE{4`aqWYik7QB5U&F5C*;TO_x+gtzPGpzNt!7~nsBT7)Ckc(K~%uv&{{6A`mmBJVAk-{s~52Vu|HbCH7_W1~ZCX^RflOakGg=jo2Z z<*s;5-J+2@^LRDZ-7EV&Pq+FTErw@pfFqvx^i%E7Fx#^n(E`m2(c>K-O5`M`Yek9el zzTGs5qD6*G;y#~xu3>qWuO?-amKYtvRA}I9z#UspEeM;wOERYeot_n_EUMJf$4_u?E!6X~?q)tPoZb^_;8Y_Ox2h1m<+Le-fsRd|T8db<8#$bqez zua^Z|>h%zdnuU^ww$#-dZ9NTM`FN+!IlLkz*FqWb!x^Z|C{KyGjZ+>G;;7Mb@LY|H zc+Gp`L((Dw7pnDlHNm&;SfHedhx*kad$I^uGz{`0BYelq0yEUHpNKSkvj$|dpvY3{7*YGyhXA^LP0&wOw9oNoC=QoVx1<2Dne8qqZL zm>nFh5DX(-RnQwvHCZQwn^#Z=E!SPVlaRJ78Bo@}!!9dRt^qZy?-*`Pt4WSmgucJv zV1yFkcjlEM^uz-;b#Q7ZCP@Lk)m}uPX={R4B=56k7WNh11BN~0T*vr@!!ow^B0hOR zQ)4)&(e%>bNNL%bm<&8H{*l_L7s0$2GUgX2Vd;=4d9Dm2v3TaL+;L>{K7h7 zV#k?xDPm(NDE31$ z<}|X)pEY6myjK+^gaIMk&Yj2~F0rSKemNqlsVm4c|N7mp_C*L01s;GNx#D-*&gk!qQr}^?_r@q!8fuXw!)fA7xkd} zb>vHvdx~H$5qqAWrow7}+8zBM65-JOt5z za=T6f7MK`XJuQog8kIEboPdhcaVJeHy)5z7EBLK5NRr()E|#K0L0N^JD@pUA^Czb` zbUZ_558y+vqAGeyHCbrvOvLD67Ph}06959VzQ_|>RrXQAqE+AQ(-AaKdxoWaF8hdt z{O3W@b^*o#-f1VuU>YMV03ELF7zkCN4Q&b#prz%3Nne0lSbRo@@ z^ihv%oIl~Qyl6Q;a#$*jOC%x0_;eis*)J7=f@Ct*)xF5 zo}u~@-I}2|$b%5L7>@+Z?4o+1r&v6ceIy+vroK&jCQ<4q&45HP2wCol4hVm3pZtjf zHz1D7oyaSKJ~T{Gx}7ONLA)D5k(%%`WswrDyzX*rn}i}}TB4^y#@mAwPzoC)`?rYv zHgx|trUN#mu*VzUV~8TnJM2Qh*ZM5B{x&y>5An`(M7=Z*Q>TdiH@j*2=moNuOtvpz z+G`@~-`%~+AgPKgke@XiRPgndh@bp*-HRsh;HTtz@-y_uhb%7ylVOTqG0#u?Vn5c5 zEp*XRo|8hcgG^$#{$O9CJ&NE;TrfRpSnLmes&MO{m=N%zc`}gb!eQ7odl$oy1%PI} z#AIxx%oRVy&{O~9xnK4$EY>(eQj}!HKIV$Fz*H=-=Kn)N0D6u`(;iO|VraI4fu_W` z;b5{7;Lyx4za}DU#+U7}=H0dAS#YJJ&g2!P@Htu-AL&w=-)*%P9h2{wR|@?Ff9~)b z^+e_3Hetq7W%ls{!?<6&Y$Z;NNB41pvrv)|MET6AZXFXJeFqbFW5@i5WGzl?bP+~? z*&_puH;wKv2)9T_d+P`bLvJFqX#j&xa*-;0nGBbQf0DC>o~=J_Wmtf*2SZQr?{i~X z9-IbRH8{iy?<0v9Ir1?$66+igy|yDQ5J~A9sFX@Pe<*kCY8+MwH?I z`P}zfQ6l^AO8ehZ=l^ZR;R%uu4;BK*=?W9t|0{+-at(MQZ(CtG=EJFNaFMlKCMXu30(gJUqj5+ z`GM|!keqcj;FKTa_qq;{*dHRXAq157hlB@kL#8%yAm2AgfU|*rDKX@FLlp=HL8ddv zAWLCHe@DcDeB2}fl7#=0+#<05c3=VqM*O3bkr@9X4nO|)q0hU;Gye{L8ZN*NH8Id@mP-u;Fmb8YuorjLrW&ndip8CN%_qp982r w1WEnz9^$&s1hkp_3#lPJQ~!HI7WYYjA7>z!`?f%npAh2%rB@vD|Lau$2O)#1n*aa+ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41dfb87..15de902 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index c53aefa..1b6c787 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts index 4740d60..9c7f459 100644 --- a/ktor-swagger-ui-examples/build.gradle.kts +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -1,46 +1,42 @@ -import io.gitlab.arturbosch.detekt.Detekt +val projectGroupId: String by project +val projectVersion: String by project +group = projectGroupId +version = projectVersion plugins { kotlin("jvm") } -group = "io.github.smiley4" -version = "3.0.0-indev" - repositories { mavenCentral() } dependencies { + val versionKtor: String by project + val versionSwaggerParser: String by project + val versionSchemaKenerator: String by project + val versionKotlinLogging: String by project + val versionLogback: String by project implementation(project(":ktor-swagger-ui")) - val ktorVersion = "2.3.11" - implementation("io.ktor:ktor-server-netty-jvm:$ktorVersion") - implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") - implementation("io.ktor:ktor-serialization-jackson:$ktorVersion") - implementation("io.ktor:ktor-server-auth:$ktorVersion") - implementation("io.ktor:ktor-server-call-logging:$ktorVersion") - implementation("io.ktor:ktor-server-test-host:$ktorVersion") - - val schemaKeneratorVersion = "0.1" - implementation("io.github.smiley4:schema-kenerator-core:$schemaKeneratorVersion") - implementation("io.github.smiley4:schema-kenerator-reflection:$schemaKeneratorVersion") - implementation("io.github.smiley4:schema-kenerator-swagger:$schemaKeneratorVersion") - implementation("io.github.smiley4:schema-kenerator-jackson:$schemaKeneratorVersion") - - val swaggerParserVersion = "2.1.19" - implementation("io.swagger.parser.v3:swagger-parser:$swaggerParserVersion") - - val kotlinLoggingVersion = "3.0.5" - implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") - - val logbackVersion = "1.4.11" - implementation("ch.qos.logback:logback-classic:$logbackVersion") - + implementation("io.ktor:ktor-server-netty-jvm:$versionKtor") + implementation("io.ktor:ktor-server-content-negotiation:$versionKtor") + implementation("io.ktor:ktor-serialization-jackson:$versionKtor") + implementation("io.ktor:ktor-server-auth:$versionKtor") + implementation("io.ktor:ktor-server-call-logging:$versionKtor") + implementation("io.ktor:ktor-server-test-host:$versionKtor") + + implementation("io.github.smiley4:schema-kenerator-core:$versionSchemaKenerator") + implementation("io.github.smiley4:schema-kenerator-reflection:$versionSchemaKenerator") + implementation("io.github.smiley4:schema-kenerator-swagger:$versionSchemaKenerator") + implementation("io.github.smiley4:schema-kenerator-jackson:$versionSchemaKenerator") + + implementation("io.swagger.parser.v3:swagger-parser:$versionSwaggerParser") + implementation("io.github.microutils:kotlin-logging-jvm:$versionKotlinLogging") + implementation("ch.qos.logback:logback-classic:$versionLogback") } - -tasks.withType().configureEach { - ignoreFailures = true +kotlin { + jvmToolchain(11) } \ No newline at end of file diff --git a/ktor-swagger-ui/build.gradle.kts b/ktor-swagger-ui/build.gradle.kts index 1ef6d32..92f56db 100644 --- a/ktor-swagger-ui/build.gradle.kts +++ b/ktor-swagger-ui/build.gradle.kts @@ -1,38 +1,81 @@ +import io.gitlab.arturbosch.detekt.Detekt + +val projectGroupId: String by project +val projectVersion: String by project +group = projectGroupId +version = projectVersion + +plugins { + kotlin("jvm") + id("org.owasp.dependencycheck") + id("io.gitlab.arturbosch.detekt") + id("com.vanniktech.maven.publish") + id("org.jetbrains.dokka") +} + +repositories { + mavenCentral() +} dependencies { - val ktorVersion = "2.3.11" - implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") - implementation("io.ktor:ktor-server-webjars:$ktorVersion") - implementation("io.ktor:ktor-server-auth:$ktorVersion") - implementation("io.ktor:ktor-server-resources:$ktorVersion") - testImplementation("io.ktor:ktor-server-netty-jvm:$ktorVersion") - testImplementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") - testImplementation("io.ktor:ktor-serialization-jackson:$ktorVersion") - testImplementation("io.ktor:ktor-server-auth:$ktorVersion") - testImplementation("io.ktor:ktor-server-call-logging:$ktorVersion") - testImplementation("io.ktor:ktor-server-test-host:$ktorVersion") - - val swaggerUiVersion = "5.9.0" // this version must match the version declared in the code (SwaggerPlugin#SWAGGER_UI_WEBJARS_VERSION) - implementation("org.webjars:swagger-ui:$swaggerUiVersion") - - val swaggerParserVersion = "2.1.19" - implementation("io.swagger.parser.v3:swagger-parser:$swaggerParserVersion") - - val schemaKeneratorVersion = "0.1" - implementation("io.github.smiley4:schema-kenerator-core:$schemaKeneratorVersion") - implementation("io.github.smiley4:schema-kenerator-reflection:$schemaKeneratorVersion") - implementation("io.github.smiley4:schema-kenerator-swagger:$schemaKeneratorVersion") - - val kotlinLoggingVersion = "3.0.5" - implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") - - val versionKotest = "5.8.0" + val versionKtor: String by project + val versionSwaggerUI: String by project + val versionSwaggerParser: String by project + val versionSchemaKenerator: String by project + val versionKotlinLogging: String by project + val versionKotest: String by project + val versionKotlinTest: String by project + val versionMockk: String by project + + implementation("io.ktor:ktor-server-core-jvm:$versionKtor") + implementation("io.ktor:ktor-server-webjars:$versionKtor") + implementation("io.ktor:ktor-server-auth:$versionKtor") + implementation("io.ktor:ktor-server-resources:$versionKtor") + testImplementation("io.ktor:ktor-server-netty-jvm:$versionKtor") + testImplementation("io.ktor:ktor-server-content-negotiation:$versionKtor") + testImplementation("io.ktor:ktor-serialization-jackson:$versionKtor") + testImplementation("io.ktor:ktor-server-auth:$versionKtor") + testImplementation("io.ktor:ktor-server-call-logging:$versionKtor") + testImplementation("io.ktor:ktor-server-test-host:$versionKtor") + + implementation("org.webjars:swagger-ui:$versionSwaggerUI") + + implementation("io.swagger.parser.v3:swagger-parser:$versionSwaggerParser") + + implementation("io.github.smiley4:schema-kenerator-core:$versionSchemaKenerator") + implementation("io.github.smiley4:schema-kenerator-reflection:$versionSchemaKenerator") + implementation("io.github.smiley4:schema-kenerator-swagger:$versionSchemaKenerator") + + implementation("io.github.microutils:kotlin-logging-jvm:$versionKotlinLogging") + testImplementation("io.kotest:kotest-runner-junit5:$versionKotest") testImplementation("io.kotest:kotest-assertions-core:$versionKotest") - val versionKotlinTest = "1.8.21" testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest") - val versionMockk = "1.13.8" testImplementation("io.mockk:mockk:$versionMockk") } + +kotlin { + jvmToolchain(11) +} + +detekt { + ignoreFailures = false + buildUponDefaultConfig = true + allRules = false + config.setFrom("$projectDir/../detekt/detekt.yml") +} +tasks.withType().configureEach { + reports { + html.required.set(true) + md.required.set(true) + xml.required.set(false) + txt.required.set(false) + sarif.required.set(false) + } +} + +//mavenPublishing { +// todo +//} \ No newline at end of file From 020905de1852475db3a8a53883888ca1b365286c Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Fri, 17 May 2024 19:59:21 +0200 Subject: [PATCH 15/32] minor changes --- gradle.properties | 2 +- ktor-swagger-ui/build.gradle.kts | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/gradle.properties b/gradle.properties index a403dbc..8a236b3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ projectVersion=3.0-indev versionKtor=2.3.11 versionSwaggerUI=5.9.0 versionSwaggerParser=2.1.19 -versionSchemaKenerator=0.1 +versionSchemaKenerator=0.1.1 versionKotlinLogging=3.0.5 versionKotest=5.8.0 versionKotlinTest=1.8.21 diff --git a/ktor-swagger-ui/build.gradle.kts b/ktor-swagger-ui/build.gradle.kts index 92f56db..e91366b 100644 --- a/ktor-swagger-ui/build.gradle.kts +++ b/ktor-swagger-ui/build.gradle.kts @@ -31,12 +31,6 @@ dependencies { implementation("io.ktor:ktor-server-webjars:$versionKtor") implementation("io.ktor:ktor-server-auth:$versionKtor") implementation("io.ktor:ktor-server-resources:$versionKtor") - testImplementation("io.ktor:ktor-server-netty-jvm:$versionKtor") - testImplementation("io.ktor:ktor-server-content-negotiation:$versionKtor") - testImplementation("io.ktor:ktor-serialization-jackson:$versionKtor") - testImplementation("io.ktor:ktor-server-auth:$versionKtor") - testImplementation("io.ktor:ktor-server-call-logging:$versionKtor") - testImplementation("io.ktor:ktor-server-test-host:$versionKtor") implementation("org.webjars:swagger-ui:$versionSwaggerUI") @@ -48,11 +42,15 @@ dependencies { implementation("io.github.microutils:kotlin-logging-jvm:$versionKotlinLogging") + testImplementation("io.ktor:ktor-server-netty-jvm:$versionKtor") + testImplementation("io.ktor:ktor-server-content-negotiation:$versionKtor") + testImplementation("io.ktor:ktor-serialization-jackson:$versionKtor") + testImplementation("io.ktor:ktor-server-auth:$versionKtor") + testImplementation("io.ktor:ktor-server-call-logging:$versionKtor") + testImplementation("io.ktor:ktor-server-test-host:$versionKtor") testImplementation("io.kotest:kotest-runner-junit5:$versionKotest") testImplementation("io.kotest:kotest-assertions-core:$versionKotest") - testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest") - testImplementation("io.mockk:mockk:$versionMockk") } @@ -60,6 +58,10 @@ kotlin { jvmToolchain(11) } +tasks.withType().configureEach { + useJUnitPlatform() +} + detekt { ignoreFailures = false buildUponDefaultConfig = true From e00a7cd15738200237df28ce46cd7135cfa2282f Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Fri, 17 May 2024 20:51:06 +0200 Subject: [PATCH 16/32] prepare publishing --- gradle.properties | 18 ++++++------ ktor-swagger-ui/build.gradle.kts | 47 ++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8a236b3..bda5e0e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,15 +5,15 @@ projectGroupId=io.github.smiley4 projectArtifactIdBase=ktor-swagger-ui projectVersion=3.0-indev -# publishing information -> todo -#projectNameBase=Schema-Kenerator -#projectDescriptionBase=Kotlin library for automatically generating various schemas -#projectScmUrl=https://github.com/SMILEY4/schema-kenerator -#projectScmConnection=scm:git:git://github.com/SMILEY4/schema-kenerator.git -#projectLicenseName=The Apache License, Version 2.0 -#projectLicenseUrl=https://www.apache.org/licenses/LICENSE-2.0.txt -#projectDeveloperName=smiley4 -#projectDeveloperUrl=https://github.com/SMILEY4 +# publishing information +projectNameBase=Ktor Swagger UI +projectDescriptionBase=Ktor plugin to document routes and provide a Swagger-UI +projectScmUrl=https://github.com/SMILEY4/ktor-swagger-ui +projectScmConnection=scm:git:git://github.com/SMILEY4/ktor-swagger-ui.git +projectLicenseName=The Apache License, Version 2.0 +projectLicenseUrl=https://www.apache.org/licenses/LICENSE-2.0.txt +projectDeveloperName=smiley4 +projectDeveloperUrl=https://github.com/SMILEY4 # dependency versions versionKtor=2.3.11 diff --git a/ktor-swagger-ui/build.gradle.kts b/ktor-swagger-ui/build.gradle.kts index e91366b..50b4389 100644 --- a/ktor-swagger-ui/build.gradle.kts +++ b/ktor-swagger-ui/build.gradle.kts @@ -1,3 +1,6 @@ +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.KotlinJvm +import com.vanniktech.maven.publish.SonatypeHost import io.gitlab.arturbosch.detekt.Detekt val projectGroupId: String by project @@ -78,6 +81,44 @@ tasks.withType().configureEach { } } -//mavenPublishing { -// todo -//} \ No newline at end of file +mavenPublishing { + val projectGroupId: String by project + val projectVersion: String by project + val projectArtifactIdBase: String by project + val projectNameBase: String by project + val projectDescriptionBase: String by project + val projectScmUrl: String by project + val projectScmConnection: String by project + val projectLicenseName: String by project + val projectLicenseUrl: String by project + val projectDeveloperName: String by project + val projectDeveloperUrl: String by project + + configure(KotlinJvm(JavadocJar.Dokka("dokkaHtml"), true)) + publishToMavenCentral(SonatypeHost.S01) + signAllPublications() + coordinates(projectGroupId, projectArtifactIdBase, projectVersion) + pom { + name.set(projectNameBase) + description.set(projectDescriptionBase) + url.set(projectScmUrl) + licenses { + license { + name.set(projectLicenseName) + url.set(projectLicenseUrl) + distribution.set(projectLicenseUrl) + } + } + scm { + url.set(projectScmUrl) + connection.set(projectScmConnection) + } + developers { + developer { + id.set(projectDeveloperName) + name.set(projectDeveloperName) + url.set(projectDeveloperUrl) + } + } + } +} \ No newline at end of file From e092a7492e3c308b7b179a83c073d6c89afb2d45 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sat, 18 May 2024 12:37:10 +0200 Subject: [PATCH 17/32] minor readme change --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f40b538..03ca9a9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.smiley4/ktor-swagger-ui/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.smiley4/ktor-swagger-ui) [![Checks Passing](https://github.com/SMILEY4/ktor-swagger-ui/actions/workflows/checks.yml/badge.svg?branch=develop)](https://github.com/SMILEY4/ktor-swagger-ui/actions/workflows/checks.yml) - This library provides a Ktor plugin to document routes, generate an OpenApi Specification and serve a Swagger UI. It is meant to be minimally invasive, meaning it can be plugged into existing application without requiring immediate changes to the code. Routes can then be gradually enhanced with documentation. @@ -32,9 +31,12 @@ dependencies { } ``` + ## Example Full examples can be found in [src/test/examples](https://github.com/SMILEY4/ktor-swagger-ui/tree/develop/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples). + ### Configuration + ```kotlin install(SwaggerUI) { swagger { @@ -52,7 +54,9 @@ install(SwaggerUI) { } } ``` + ### Routes + ```kotlin get("hello", { description = "Hello World Endpoint." From 72f111b1ef40c47f9e4eef0d6fc8600864a8e201 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 19 May 2024 15:20:32 +0200 Subject: [PATCH 18/32] set version to 3.0.0-beta1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index bda5e0e..bcb36f0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.code.style=official # project id projectGroupId=io.github.smiley4 projectArtifactIdBase=ktor-swagger-ui -projectVersion=3.0-indev +projectVersion=3.0.0-beta1 # publishing information projectNameBase=Ktor Swagger UI From 4158f81536861ce29425fcd20b2282d67ca7609b Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 19 May 2024 15:27:35 +0200 Subject: [PATCH 19/32] fix gh publich action for newer plugin version --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5f3d55d..d7de140 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,7 +29,7 @@ jobs: - name: Publish run: | ./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache - ./gradlew closeAndReleaseRepository + ./gradlew releaseRepository env: SONATYPE_CONNECT_TIMEOUT_SECONDS: 180 SONATYPE_CLOSE_TIMEOUT_SECONDS: 900 From 9f27d1d056f6fbcc40b591c76f1b1fe83eff0210 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 19 May 2024 15:52:48 +0200 Subject: [PATCH 20/32] set version to 2.11.0 --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 757ed3c..815c17d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ import io.gitlab.arturbosch.detekt.Detekt object Meta { const val groupId = "io.github.smiley4" const val artifactId = "ktor-swagger-ui" - const val version = "2.10.0" + const val version = "2.11.0" const val name = "Ktor Swagger-UI" const val description = "Ktor plugin to document routes and provide Swagger UI" const val licenseName = "The Apache License, Version 2.0" From 0fbe6168b5849d32200e134348af65269be591a6 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 19 May 2024 20:49:12 +0200 Subject: [PATCH 21/32] proper dsl for schema (direct TypeDescriptors) --- .../smiley4/ktorswaggerui/examples/Basics.kt | 4 +- .../ktorswaggerui/examples/CompleteConfig.kt | 17 +- .../ktorswaggerui/examples/Examples.kt | 6 +- .../ktorswaggerui/examples/FileUpload.kt | 19 +- .../ktorswaggerui/examples/Petstore.kt | 31 ++-- .../ktorswaggerui/examples/RequestResponse.kt | 6 +- .../smiley4/ktorswaggerui/examples/Schemas.kt | 29 ++- .../builder/route/RouteDocumentationMerger.kt | 6 +- .../ktorswaggerui/data/TypeDescriptor.kt | 2 +- .../dsl/config/OpenApiSecurityScheme.kt | 2 +- .../ktorswaggerui/dsl/config/SchemaConfig.kt | 31 +++- .../ktorswaggerui/dsl/routes/OpenApiHeader.kt | 31 ++++ .../dsl/routes/OpenApiMultipartBody.kt | 28 ++- .../dsl/routes/OpenApiMultipartPart.kt | 32 +++- .../dsl/routes/OpenApiRequest.kt | 167 +++++++----------- .../dsl/routes/OpenApiResponse.kt | 106 ++++------- .../misc/RouteDocumentationMergerTest.kt | 4 +- todo.txt | 9 + 18 files changed, 273 insertions(+), 257 deletions(-) create mode 100644 todo.txt diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Basics.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Basics.kt index b0d1ced..95222a4 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Basics.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Basics.kt @@ -1,7 +1,6 @@ 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 @@ -14,7 +13,6 @@ 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) @@ -65,7 +63,7 @@ private fun Application.myModule() { // information about the request request { // information about the query-parameter "name" of type "string" - queryParameter("name", KTypeDescriptor(typeOf())) { + queryParameter("name") { description = "the name to greet" } } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt index 6b7c521..b5f612e 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt @@ -3,8 +3,6 @@ package io.github.smiley4.ktorswaggerui.examples import io.github.smiley4.ktorswaggerui.SwaggerUI import io.github.smiley4.ktorswaggerui.data.AuthScheme import io.github.smiley4.ktorswaggerui.data.AuthType -import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor -import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort import io.github.smiley4.ktorswaggerui.data.SwaggerUiSyntaxHighlight import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor @@ -28,7 +26,6 @@ import io.ktor.server.routing.route import io.ktor.server.routing.routing import io.swagger.v3.oas.models.media.Schema import java.io.File -import kotlin.reflect.typeOf fun main() { embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) @@ -99,7 +96,7 @@ private fun Application.myModule() { } } schemas { - schema("string", KTypeDescriptor(typeOf())) + schema("string") generator = { type -> type .processReflection() @@ -107,12 +104,10 @@ private fun Application.myModule() { .withAutoTitle(TitleType.SIMPLE) .compileReferencingRoot() } - overwrite[typeOf()] = SwaggerTypeDescriptor( - Schema().also { - it.type = "string" - it.format = "binary" - } - ) + overwrite(Schema().also { + it.type = "string" + it.format = "binary" + }) } examples { example( @@ -151,7 +146,7 @@ private fun Application.myModule() { get("hello", { description = "A Hello-World route" request { - queryParameter("name", KTypeDescriptor(typeOf())) { + queryParameter("name") { description = "the name to greet" } } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt index 7bb95d6..66cead3 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt @@ -1,7 +1,6 @@ package io.github.smiley4.ktorswaggerui.examples import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.get @@ -15,7 +14,6 @@ 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) @@ -59,7 +57,7 @@ private fun Application.myModule() { get("basic", { request { - body(KTypeDescriptor(typeOf())) { + body { // specify two example values example( ValueExampleDescriptor( @@ -88,7 +86,7 @@ private fun Application.myModule() { get("reference-shared", { request { - body(KTypeDescriptor(typeOf())) { + body { // reference two shared examples specified in the plugin-config (and placed in the component section) example( RefExampleDescriptor("Example 1", "Shared A") diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt index 095c11a..348af73 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/FileUpload.kt @@ -1,8 +1,6 @@ package io.github.smiley4.ktorswaggerui.examples import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor -import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.post import io.github.smiley4.ktorswaggerui.routing.openApiSpec import io.github.smiley4.ktorswaggerui.routing.swaggerUI @@ -18,7 +16,6 @@ import io.ktor.server.routing.route import io.ktor.server.routing.routing import io.swagger.v3.oas.models.media.Schema import java.io.File -import kotlin.reflect.typeOf fun main() { embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) @@ -30,12 +27,10 @@ private fun Application.myModule() { install(SwaggerUI) { schemas { // overwrite type "File" with custom schema for binary data - overwrite[typeOf()] = SwaggerTypeDescriptor( - Schema().also { - it.type = "string" - it.format = "binary" - } - ) + overwrite(Schema().also { + it.type = "string" + it.format = "binary" + }) } } @@ -52,7 +47,7 @@ private fun Application.myModule() { // upload a single file, either as png, jpeg or svg post("single", { request { - body(KTypeDescriptor(typeOf())) { + body { mediaTypes = setOf( ContentType.Image.PNG, ContentType.Image.JPEG, @@ -69,14 +64,14 @@ private fun Application.myModule() { request { multipartBody { mediaTypes = setOf(ContentType.MultiPart.FormData) - part("first-image", KTypeDescriptor(typeOf())) { + part("first-image",) { mediaTypes = setOf( ContentType.Image.PNG, ContentType.Image.JPEG, ContentType.Image.SVG ) } - part("second-image", KTypeDescriptor(typeOf())) { + part("second-image") { mediaTypes = setOf( ContentType.Image.PNG, ContentType.Image.JPEG, diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt index 22c9e2f..d5ad900 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt @@ -1,7 +1,6 @@ package io.github.smiley4.ktorswaggerui.examples import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.delete @@ -18,7 +17,6 @@ import io.ktor.server.netty.Netty import io.ktor.server.response.respond 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) @@ -47,11 +45,6 @@ private fun Application.myModule() { examples { example(ValueExampleDescriptor("Unexpected Error", ErrorModel("Unexpected Error"), null, null)) } - schemas { - schema("Pet", KTypeDescriptor(typeOf())) - schema("NewPet", KTypeDescriptor(typeOf())) - schema("PetList", KTypeDescriptor(typeOf>())) - } } routing { @@ -69,12 +62,12 @@ private fun Application.myModule() { operationId = "findPets" description = "Returns all pets from the system that the user has access to" request { - queryParameter("tags", KTypeDescriptor(typeOf>())) { + queryParameter>("tags") { description = "tags to filter by" required = false example = ValueExampleDescriptor("dog", "default", null, null) } - queryParameter("limit", KTypeDescriptor(typeOf())) { + queryParameter("limit") { description = "maximum number of results to return" required = false example = ValueExampleDescriptor("default", 100, null, null) @@ -82,7 +75,7 @@ private fun Application.myModule() { } response { HttpStatusCode.OK to { - body(KTypeDescriptor(typeOf>())) { + body> { description = "the list of available pets" example( ValueExampleDescriptor( @@ -104,7 +97,7 @@ private fun Application.myModule() { } } default { - body(KTypeDescriptor(typeOf())) { + body { description = "unexpected error" example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) } @@ -118,7 +111,7 @@ private fun Application.myModule() { operationId = "addPet" description = "Creates a new pet in the store. Duplicates are allowed" request { - body(KTypeDescriptor(typeOf())) { + body { description = "Pet to add to the store" required = true example( @@ -143,7 +136,7 @@ private fun Application.myModule() { } response { HttpStatusCode.OK to { - body(KTypeDescriptor(typeOf())) { + body { description = "the created pet" example( ValueExampleDescriptor( @@ -168,7 +161,7 @@ private fun Application.myModule() { } } default { - body(KTypeDescriptor(typeOf())) { + body { description = "unexpected error" example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) } @@ -184,7 +177,7 @@ private fun Application.myModule() { operationId = "findBetById" description = "Returns a pet based on a single ID." request { - pathParameter("id", KTypeDescriptor(typeOf())) { + pathParameter("id") { description = "Id of pet to fetch" required = true example = ValueExampleDescriptor("default", 123L, null, null) @@ -192,7 +185,7 @@ private fun Application.myModule() { } response { HttpStatusCode.OK to { - body(KTypeDescriptor(typeOf())) { + body{ description = "the pet with the given id" example( ValueExampleDescriptor( @@ -220,7 +213,7 @@ private fun Application.myModule() { description = "the pet with the given id was not found" } default { - body(KTypeDescriptor(typeOf())) { + body { description = "unexpected error" example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) } @@ -234,7 +227,7 @@ private fun Application.myModule() { operationId = "deletePet" description = "deletes a single pet based on the supplied ID" request { - pathParameter("id", KTypeDescriptor(typeOf())) { + pathParameter("id") { description = "Id of pet to delete" required = true example = ValueExampleDescriptor("default", 123L, null, null) @@ -248,7 +241,7 @@ private fun Application.myModule() { description = "the pet with the given id was not found" } default { - body(KTypeDescriptor(typeOf())) { + body { description = "unexpected error" example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt index ccd372e..3dc9229 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/RequestResponse.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.util.DefaultIndenter import com.fasterxml.jackson.core.util.DefaultPrettyPrinter import com.fasterxml.jackson.databind.SerializationFeature import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.post import io.github.smiley4.ktorswaggerui.routing.openApiSpec import io.github.smiley4.ktorswaggerui.routing.swaggerUI @@ -20,7 +19,6 @@ import io.ktor.server.request.receive import io.ktor.server.response.respond 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) @@ -56,7 +54,7 @@ private fun Application.myModule() { // information about the request request { // specify the schema of the request-body and some additional information - body(KTypeDescriptor(typeOf())) { + body { description = "the requested operation and values to perform the operation on" required = true } @@ -67,7 +65,7 @@ private fun Application.myModule() { HttpStatusCode.OK to { description = "Calculation was performed successfully." // specify the schema of the response-body and some additional information - body(KTypeDescriptor(typeOf())) { + body { description = "the result of an operation together with the original request" } } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt index b3f83f3..b400b2e 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt @@ -6,7 +6,6 @@ import io.github.smiley4.ktorswaggerui.data.AnyOfTypeDescriptor import io.github.smiley4.ktorswaggerui.data.ArrayTypeDescriptor import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.RefTypeDescriptor -import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.routing.openApiSpec import io.github.smiley4.ktorswaggerui.routing.swaggerUI @@ -40,23 +39,19 @@ private fun Application.myModule() { schemas { // add a swagger schema to the component-section of the api-spec with the id "swagger-schema" - schema("swagger-schema", SwaggerTypeDescriptor( - Schema().also { - it.type = "number" - it.title = "Custom Type" - } - )) + schema("swagger-schema", Schema().also { + it.type = "number" + it.title = "Custom Type" + }) // add a type to the component-section of the api-spec with the id "type-schema" - schema("type-schema", KTypeDescriptor(typeOf())) + schema("type-schema") // overwrite 'LocalDateTime' with custom schema (root only) - overwrite[typeOf()] = SwaggerTypeDescriptor( - Schema().also { - it.title = "timestamp" - it.type = "integer" - } - ) + overwrite(Schema().also { + it.title = "timestamp" + it.type = "integer" + }) // customized schema generation pipeline generator = { type -> @@ -86,7 +81,7 @@ private fun Application.myModule() { get("basic", { request { // directly specify the schema type - body(KTypeDescriptor(typeOf())) + body() } }) { call.respondText("...") @@ -147,7 +142,7 @@ private fun Application.myModule() { get("type-overwrite", { request { // schema is not generated the normal way but the overwriting schema from the config is used instead - body(KTypeDescriptor(typeOf())) + body() } }) { call.respondText("...") @@ -157,7 +152,7 @@ private fun Application.myModule() { get("jackson-subtypes", { request { // jackson subtypes are detected automatically - body(KTypeDescriptor(typeOf())) + body() } }) { call.respondText("...") diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt index 046baaa..5981858 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteDocumentationMerger.kt @@ -25,9 +25,9 @@ class RouteDocumentationMerger { hidden = a.hidden || b.hidden protected = a.protected ?: b.protected request { - (getParameters() as MutableList).also { - it.addAll(a.getRequest().getParameters()) - it.addAll(b.getRequest().getParameters()) + parameters.also { + it.addAll(a.getRequest().parameters) + it.addAll(b.getRequest().parameters) } setBody(a.getRequest().getBody() ?: b.getRequest().getBody()) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt index 1f9c4ff..8a13d2d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -9,4 +9,4 @@ class KTypeDescriptor(val type: KType) : TypeDescriptor class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor class AnyOfTypeDescriptor(val types: List) : TypeDescriptor class EmptyTypeDescriptor : TypeDescriptor -class RefTypeDescriptor(val schemaId: String) : TypeDescriptor +class RefTypeDescriptor(val schemaId: String) : TypeDescriptor \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt index 86e47bc..b2e69f0 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt @@ -18,7 +18,7 @@ class OpenApiSecurityScheme( /** * The name of the security scheme. */ - val schemeName: String + private val schemeName: String ) { /** diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt index 1f686b5..8d48bdd 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SchemaConfig.kt @@ -1,10 +1,14 @@ 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.dsl.OpenApiDslMarker import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema +import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * Configuration for schemas @@ -12,19 +16,38 @@ import kotlin.reflect.KType @OpenApiDslMarker class SchemaConfig { - private val schemas = mutableMapOf() + var generator: (type: KType) -> CompiledSwaggerSchema = SchemaConfigData.DEFAULT.generator + + val schemas = mutableMapOf() + + val overwrite = mutableMapOf() + + fun overwrite(type: KType, replacement: TypeDescriptor) { + overwrite[type] = replacement + } + + inline fun overwrite(replacement: TypeDescriptor) = overwrite(typeOf(), replacement) + + inline fun overwrite(replacement: Schema<*>) = overwrite(typeOf(), SwaggerTypeDescriptor(replacement)) + + inline fun overwrite(replacement: KType) = overwrite(typeOf(), KTypeDescriptor(replacement)) + + inline fun overwrite() = overwrite(typeOf(), KTypeDescriptor(typeOf())) + fun schema(schemaId: String, descriptor: TypeDescriptor) { schemas[schemaId] = descriptor } - var generator: (type: KType) -> CompiledSwaggerSchema = SchemaConfigData.DEFAULT.generator + fun schema(schemaId: String, schema: Schema<*>) = schema(schemaId, SwaggerTypeDescriptor(schema)) - val overwrite = mutableMapOf() + fun schema(schemaId: String, schema: KType) = schema(schemaId, KTypeDescriptor(schema)) + + inline fun schema(schemaId: String) = schema(schemaId, KTypeDescriptor(typeOf())) fun build() = SchemaConfigData( - schemas = schemas, generator = generator, + schemas = schemas, overwrite = overwrite ) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt index 7890d8d..1a2b6ef 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt @@ -1,8 +1,13 @@ package io.github.smiley4.ktorswaggerui.dsl.routes +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiHeaderData +import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.swagger.v3.oas.models.media.Schema +import kotlin.reflect.KType +import kotlin.reflect.typeOf @OpenApiDslMarker @@ -20,6 +25,32 @@ class OpenApiHeader { var type: TypeDescriptor? = null + /** + * The schema of the header + */ + fun type(type: TypeDescriptor) { + this.type = type + } + + + /** + * The schema of the header + */ + fun type(type: Schema<*>) = type(SwaggerTypeDescriptor(type)) + + + /** + * The schema of the header + */ + fun type(type: KType) = type(KTypeDescriptor(type)) + + + /** + * The schema of the header + */ + inline fun type() = type(KTypeDescriptor(typeOf())) + + /** * Determines whether this header is mandatory */ diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt index 8b1119a..e1ee012 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt @@ -1,8 +1,13 @@ package io.github.smiley4.ktorswaggerui.dsl.routes +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartBodyData +import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.swagger.v3.oas.models.media.Schema +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** @@ -14,13 +19,34 @@ class OpenApiMultipartBody : OpenApiBaseBody() { private val parts = mutableListOf() + /** * One part of a multipart-body */ - fun part(name: String, type: TypeDescriptor, block: OpenApiMultipartPart.() -> Unit) { + fun part(name: String, type: TypeDescriptor, block: OpenApiMultipartPart.() -> Unit = {}) { parts.add(OpenApiMultipartPart(name, type).apply(block)) } + + /** + * One part of a multipart-body + */ + fun part(name: String, type: Schema<*>, block: OpenApiMultipartPart.() -> Unit = {}) = part(name, SwaggerTypeDescriptor(type), block) + + + /** + * One part of a multipart-body + */ + fun part(name: String, type: KType, block: OpenApiMultipartPart.() -> Unit = {}) = part(name, KTypeDescriptor(type), block) + + + /** + * One part of a multipart-body + */ + inline fun part(name: String, noinline block: OpenApiMultipartPart.() -> Unit = {}) = + part(name, KTypeDescriptor(typeOf()), block) + + override fun build() = OpenApiMultipartBodyData( description = description, required = required ?: false, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt index da2fccd..558ff95 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt @@ -1,9 +1,14 @@ package io.github.smiley4.ktorswaggerui.dsl.routes +import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiMultipartPartData +import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker import io.ktor.http.ContentType +import io.swagger.v3.oas.models.media.Schema +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * Describes one section of a multipart-body. @@ -25,16 +30,39 @@ class OpenApiMultipartPart( var mediaTypes: Collection = setOf() + /** + * List of headers of this part + */ + val headers = mutableMapOf() + + /** * Possible headers for this part */ - fun header(name: String, type: TypeDescriptor, block: OpenApiHeader.() -> Unit) { + fun header(name: String, type: TypeDescriptor, block: OpenApiHeader.() -> Unit = {}) { headers[name] = OpenApiHeader().apply(block).apply { this.type = type } } - private val headers = mutableMapOf() + + /** + * Possible headers for this part + */ + fun header(name: String, type: Schema<*>, block: OpenApiHeader.() -> Unit = {}) = header(name, SwaggerTypeDescriptor(type), block) + + + /** + * Possible headers for this part + */ + fun header(name: String, type: KType, block: OpenApiHeader.() -> Unit = {}) = header(name, KTypeDescriptor(type), block) + + + /** + * Possible headers for this part + */ + inline fun header(name: String, noinline block: OpenApiHeader.() -> Unit = {}) = + header(name, KTypeDescriptor(typeOf()), block) fun build() = OpenApiMultipartPartData( diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt index db392f8..d307b07 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt @@ -1,12 +1,14 @@ package io.github.smiley4.ktorswaggerui.dsl.routes -import io.github.smiley4.ktorswaggerui.data.EmptyTypeDescriptor import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiRequestData import io.github.smiley4.ktorswaggerui.data.ParameterLocation +import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType +import kotlin.reflect.typeOf @OpenApiDslMarker @@ -15,15 +17,12 @@ class OpenApiRequest { /** * A list of parameters that are applicable for this operation */ - private val parameters = mutableListOf() - - fun getParameters(): List = parameters - + val parameters = mutableListOf() /** * A path parameters that is applicable for this operation */ - fun parameter(location: ParameterLocation, name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit) { + fun parameter(location: ParameterLocation, name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit = {}) { parameters.add(OpenApiRequestParameter(name, type, location).apply(block)) } @@ -31,82 +30,76 @@ class OpenApiRequest { /** * A path parameters that is applicable for this operation */ - fun pathParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit) = + fun pathParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.PATH, name, type, block) -// -// /** -// * A path parameters that is applicable for this operation -// */ -// fun pathParameter(name: String, type: KClass<*>) = pathParameter(name, type) {} -// -// -// /** -// * A path parameters that is applicable for this operation -// */ -// inline fun pathParameter(name: String) = -// parameter(ParameterLocation.PATH, name, getSchemaType()) {} -// -// -// /** -// * A path parameters that is applicable for this operation -// */ -// inline fun pathParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = -// parameter(ParameterLocation.PATH, name, getSchemaType(), block) -// + /** + * A path parameters that is applicable for this operation + */ + fun pathParameter(name: String, type: Schema<*>, block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.PATH, name, SwaggerTypeDescriptor(type), block) + + /** + * A path parameters that is applicable for this operation + */ + fun pathParameter(name: String, type: KType, block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.PATH, name, KTypeDescriptor(type), block) + + /** + * A path parameters that is applicable for this operation + */ + inline fun pathParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.PATH, name, KTypeDescriptor(typeOf()), block) + /** * A query parameters that is applicable for this operation */ - fun queryParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit) = + fun queryParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.QUERY, name, type, block) + /** + * A query parameters that is applicable for this operation + */ + fun queryParameter(name: String, type: Schema<*>, block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.QUERY, name, SwaggerTypeDescriptor(type), block) -// /** -// * A query parameters that is applicable for this operation -// */ -// fun queryParameter(name: String, type: KClass<*>) = queryParameter(name, type) {} -// -// -// /** -// * A query parameters that is applicable for this operation -// */ -// inline fun queryParameter(name: String) = -// parameter(ParameterLocation.QUERY, name, getSchemaType()) {} -// -// -// /** -// * A query parameters that is applicable for this operation -// */ -// inline fun queryParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = -// parameter(ParameterLocation.QUERY, name, getSchemaType(), block) + /** + * A query parameters that is applicable for this operation + */ + fun queryParameter(name: String, type: KType, block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.QUERY, name, KTypeDescriptor(type), block) + + /** + * A query parameters that is applicable for this operation + */ + inline fun queryParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.QUERY, name, KTypeDescriptor(typeOf()), block) /** * A header parameters that is applicable for this operation */ - fun headerParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit) = + fun headerParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.HEADER, name, type, block) -// -// /** -// * A header parameters that is applicable for this operation -// */ -// fun headerParameter(name: String, type: SchemaType) = parameter(ParameterLocation.HEADER, name, type) {} -// -// -// /** -// * A header parameters that is applicable for this operation -// */ -// inline fun headerParameter(name: String) = -// parameter(ParameterLocation.HEADER, name, getSchemaType()) {} -// -// -// /** -// * A header parameters that is applicable for this operation -// */ -// inline fun headerParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit) = -// parameter(ParameterLocation.HEADER, name, getSchemaType(), block) + /** + * A header parameters that is applicable for this operation + */ + fun headerParameter(name: String, type: Schema<*>, block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.HEADER, name, SwaggerTypeDescriptor(type), block) + + /** + * A header parameters that is applicable for this operation + */ + fun headerParameter(name: String, type: KType, block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.HEADER, name, KTypeDescriptor(type), block) + + /** + * A header parameters that is applicable for this operation + */ + inline fun headerParameter(name: String, noinline block: OpenApiRequestParameter.() -> Unit = {}) = + parameter(ParameterLocation.HEADER, name, KTypeDescriptor(typeOf()), block) private var body: OpenApiBaseBody? = null @@ -117,52 +110,24 @@ class OpenApiRequest { /** * The body returned with this request */ - fun body(typeDescriptor: TypeDescriptor, block: OpenApiSimpleBody.() -> Unit) { - body = OpenApiSimpleBody(typeDescriptor).apply(block) + fun body(type: TypeDescriptor, block: OpenApiSimpleBody.() -> Unit = {}) { + body = OpenApiSimpleBody(type).apply(block) } - /** * The body returned with this request */ - fun body(typeDescriptor: TypeDescriptor) = body(typeDescriptor) {} - + fun body(type: Schema<*>, block: OpenApiSimpleBody.() -> Unit = {}) = body(SwaggerTypeDescriptor(type), block) /** - * The request body applicable for this operation + * The body returned with this request */ - fun body(type: KType, block: OpenApiSimpleBody.() -> Unit) = body(KTypeDescriptor(type), block) - - -// /** -// * The request body applicable for this operation -// */ -// fun body(type: KClass<*>) = body(type) {} - - -// /** -// * The request body applicable for this operation -// */ -// fun body(type: KClass<*>, block: OpenApiSimpleBody.() -> Unit) = body(type.asSchemaType(), block) -// -// -// /** -// * The request body applicable for this operation -// */ -// @JvmName("bodyGenericType") -// inline fun body(noinline block: OpenApiSimpleBody.() -> Unit) = body(getSchemaType(), block) -// -// -// /** -// * The request body applicable for this operation -// */ -// inline fun body() = body(getSchemaType()) {} - + fun body(type: KType, block: OpenApiSimpleBody.() -> Unit = {}) = body(KTypeDescriptor(type), block) /** - * The request body applicable for this operation + * The body returned with this request */ - fun body(block: OpenApiSimpleBody.() -> Unit) = body(EmptyTypeDescriptor(), block) + inline fun body(noinline block: OpenApiSimpleBody.() -> Unit = {}) = body(KTypeDescriptor(typeOf()), block) /** diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt index 49c40cb..5e0af62 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiResponse.kt @@ -2,9 +2,12 @@ package io.github.smiley4.ktorswaggerui.dsl.routes import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiResponseData +import io.github.smiley4.ktorswaggerui.data.SwaggerTypeDescriptor import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * A container for the expected responses of an operation. The container maps an HTTP response code to the expected response. @@ -18,105 +21,64 @@ class OpenApiResponse(val statusCode: String) { */ var description: String? = null - private val headers = mutableMapOf() + val headers = mutableMapOf() + /** * Possible headers returned with this response */ - fun header(name: String, type: TypeDescriptor, block: OpenApiHeader.() -> Unit) { + fun header(name: String, type: TypeDescriptor, block: OpenApiHeader.() -> Unit = {}) { headers[name] = OpenApiHeader().apply(block).apply { this.type = type } } -// -// /** -// * Possible headers returned with this response -// */ -// fun header(name: String, type: KClass<*>, block: OpenApiHeader.() -> Unit) = header(name, type.asSchemaType(), block) -// -// -// /** -// * Possible headers returned with this response -// */ -// fun header(name: String, type: KClass<*>) = header(name, type.asSchemaType()) {} -// -// -// /** -// * Possible headers returned with this response -// */ -// inline fun header(name: String) = header(name, getSchemaType()) {} -// -// -// /** -// * Possible headers returned with this response -// */ -// inline fun header(name: String, noinline block: OpenApiHeader.() -> Unit) = header(name, getSchemaType(), block) + + /** + * Possible headers returned with this response + */ + fun header(name: String, type: Schema<*>, block: OpenApiHeader.() -> Unit = {}) = header(name, SwaggerTypeDescriptor(type), block) + + + /** + * Possible headers returned with this response + */ + fun header(name: String, type: KType, block: OpenApiHeader.() -> Unit = {}) = header(name, KTypeDescriptor(type), block) + + + /** + * Possible headers returned with this response + */ + inline fun header(name: String, noinline block: OpenApiHeader.() -> Unit = {}) = + header(name, KTypeDescriptor(typeOf()), block) private var body: OpenApiBaseBody? = null + /** * The body returned with this response */ - fun body(type: TypeDescriptor, block: OpenApiSimpleBody.() -> Unit) { + fun body(type: TypeDescriptor, block: OpenApiSimpleBody.() -> Unit = {}) { body = OpenApiSimpleBody(type).apply(block) } - /** * The body returned with this response */ - fun body(type: TypeDescriptor) = body(type) {} + fun body(type: Schema<*>, block: OpenApiSimpleBody.() -> Unit = {}) = body(SwaggerTypeDescriptor(type), block) + /** + * The body returned with this response + */ + fun body(type: KType, block: OpenApiSimpleBody.() -> Unit = {}) = body(KTypeDescriptor(type), block) /** * The body returned with this response */ - fun body(type: KType, block: OpenApiSimpleBody.() -> Unit) = body(KTypeDescriptor(type), block) - - -// /** -// * The body returned with this response -// */ -// fun body(type: KClass<*>, block: OpenApiSimpleBody.() -> Unit) = body(type.asSchemaType(), block) -// -// -// /** -// * The body returned with this response -// */ -// @JvmName("bodyGenericType") -// inline fun body(noinline block: OpenApiSimpleBody.() -> Unit) = body(getSchemaType(), block) -// -// -// /** -// * The body returned with this response -// */ -// fun body(type: KClass<*>) = body(type) {} -// -// -// /** -// * The body returned with this response -// */ -// inline fun body() = body(getSchemaType()) {} -// -// -// /** -// * The body returned with this response -// */ -// fun body(block: OpenApiSimpleBody.() -> Unit) = body(EmptyTypeDescriptor, block) - - -// /** -// * The body returned with this response -// */ -// fun body(customSchemaId: String, block: OpenApiSimpleBody.() -> Unit) = body(BodyTypeDescriptor.custom(customSchemaId), block) -// -// -// /** -// * The body returned with this response -// */ -// fun body(customSchemaId: String) = body(customSchemaId) {} + inline fun body(noinline block: OpenApiSimpleBody.() -> Unit = {}) = body(KTypeDescriptor(typeOf()), block) + + /** diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt index 74e8d81..5f9095c 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RouteDocumentationMergerTest.kt @@ -26,7 +26,7 @@ class RouteDocumentationMergerTest : StringSpec({ route.securitySchemeNames.shouldBeEmpty() route.protected shouldBe null route.getRequest().also { requests -> - requests.getParameters().shouldBeEmpty() + requests.parameters.shouldBeEmpty() requests.getBody() shouldBe null } route.getResponses().also { responses -> @@ -99,7 +99,7 @@ class RouteDocumentationMergerTest : StringSpec({ ) route.protected shouldBe true route.getRequest().also { requests -> - requests.getParameters().map { it.name } shouldContainExactlyInAnyOrder listOf( + requests.parameters.map { it.name } shouldContainExactlyInAnyOrder listOf( "query", "pathA1", "pathA2", diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..ceeff37 --- /dev/null +++ b/todo.txt @@ -0,0 +1,9 @@ +todo: + +- keep up to date with schema-kenerator +- simplify dsl for schemas (TypeDescriptors) + - utility methods for adding different TypeDescriptors ✅ + - dsl for array, anyof, ref, empty, ... +- simplify dsl for examples (ExampleDescriptors) +- add rootHostPath to operations (see 2.10.0) +- post build hook (?) \ No newline at end of file From 73c03fbaab8571313d51a32f083d8ecaaa3983f8 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 19 May 2024 21:15:37 +0200 Subject: [PATCH 22/32] proper dsl for composite TypeDescriptors (array, anyof, ...) --- .../smiley4/ktorswaggerui/examples/Schemas.kt | 24 ++++++++----------- .../ktorswaggerui/data/TypeDescriptor.kt | 23 +++++++++++++++++- todo.txt | 4 ++-- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt index b400b2e..b078b9a 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Schemas.kt @@ -2,10 +2,9 @@ package io.github.smiley4.ktorswaggerui.examples import com.fasterxml.jackson.annotation.JsonSubTypes import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.AnyOfTypeDescriptor -import io.github.smiley4.ktorswaggerui.data.ArrayTypeDescriptor -import io.github.smiley4.ktorswaggerui.data.KTypeDescriptor -import io.github.smiley4.ktorswaggerui.data.RefTypeDescriptor +import io.github.smiley4.ktorswaggerui.data.anyOf +import io.github.smiley4.ktorswaggerui.data.array +import io.github.smiley4.ktorswaggerui.data.ref import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.routing.openApiSpec import io.github.smiley4.ktorswaggerui.routing.swaggerUI @@ -26,7 +25,6 @@ import io.ktor.server.routing.route import io.ktor.server.routing.routing import io.swagger.v3.oas.models.media.Schema import java.time.LocalDateTime -import kotlin.reflect.typeOf fun main() { embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) @@ -91,7 +89,7 @@ private fun Application.myModule() { get("global-swagger-schema", { request { // reference and use the schema from the component-section with the id "swagger-schema" - body(RefTypeDescriptor("swagger-schema")) + body(ref("swagger-schema")) } }) { call.respondText("...") @@ -101,7 +99,7 @@ private fun Application.myModule() { get("global-type-schema", { request { // reference and use the schema from the component-section with the id "type-schema" - body(RefTypeDescriptor("type-schema")) + body(ref("type-schema")) } }) { call.respondText("...") @@ -112,8 +110,8 @@ private fun Application.myModule() { request { // an array of items with the referenced schema with the id "type-schema" body( - ArrayTypeDescriptor( - RefTypeDescriptor("type-schema") + array( + ref("type-schema") ) ) } @@ -126,11 +124,9 @@ private fun Application.myModule() { request { // either the referenced schema with id "type-schema" or "swagger-schema" body( - AnyOfTypeDescriptor( - listOf( - RefTypeDescriptor("type-schema"), - RefTypeDescriptor("swagger-schema") - ) + anyOf( + ref("type-schema"), + ref("swagger-schema") ) ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt index 8a13d2d..0372529 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -2,6 +2,7 @@ package io.github.smiley4.ktorswaggerui.data import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType +import kotlin.reflect.typeOf sealed interface TypeDescriptor class SwaggerTypeDescriptor(val schema: Schema<*>) : TypeDescriptor @@ -9,4 +10,24 @@ class KTypeDescriptor(val type: KType) : TypeDescriptor class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor class AnyOfTypeDescriptor(val types: List) : TypeDescriptor class EmptyTypeDescriptor : TypeDescriptor -class RefTypeDescriptor(val schemaId: String) : TypeDescriptor \ No newline at end of file +class RefTypeDescriptor(val schemaId: String) : TypeDescriptor + +inline fun type() = KTypeDescriptor(typeOf()) + +fun empty() = EmptyTypeDescriptor() + +fun ref(schemaId: String) = RefTypeDescriptor(schemaId) + +fun array(type: TypeDescriptor) = ArrayTypeDescriptor(type) +fun array(type: Schema<*>) = ArrayTypeDescriptor(SwaggerTypeDescriptor(type)) +fun array(type: KType) = ArrayTypeDescriptor(KTypeDescriptor(type)) +inline fun array() = ArrayTypeDescriptor(KTypeDescriptor(typeOf())) + +fun anyOf(vararg types: TypeDescriptor) = AnyOfTypeDescriptor(types.toList()) +fun anyOf(types: Collection) = AnyOfTypeDescriptor(types.toList()) + +fun anyOf(vararg types: Schema<*>) = AnyOfTypeDescriptor(types.map { SwaggerTypeDescriptor(it) }) +fun anyOf(types: Collection>) = AnyOfTypeDescriptor(types.map { SwaggerTypeDescriptor(it) }) + +fun anyOf(vararg types: KType) = AnyOfTypeDescriptor(types.map { KTypeDescriptor(it) }) +fun anyOf(types: Collection) = AnyOfTypeDescriptor(types.map { KTypeDescriptor(it) }) diff --git a/todo.txt b/todo.txt index ceeff37..f36337f 100644 --- a/todo.txt +++ b/todo.txt @@ -1,9 +1,9 @@ todo: - keep up to date with schema-kenerator -- simplify dsl for schemas (TypeDescriptors) +- simplify dsl for schemas (TypeDescriptors) ✅ - utility methods for adding different TypeDescriptors ✅ - - dsl for array, anyof, ref, empty, ... + - dsl for array, anyof, ref, empty, ... ✅ - simplify dsl for examples (ExampleDescriptors) - add rootHostPath to operations (see 2.10.0) - post build hook (?) \ No newline at end of file From 11b2934850a3cc123379ab8b210a4b9168309641 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 19 May 2024 21:31:24 +0200 Subject: [PATCH 23/32] proper dsl for examples --- .../ktorswaggerui/examples/CompleteConfig.kt | 24 ++- .../ktorswaggerui/examples/Examples.kt | 48 ++---- .../ktorswaggerui/examples/Petstore.kt | 145 ++++++++---------- .../ktorswaggerui/data/ExampleDescriptor.kt | 6 +- .../ktorswaggerui/dsl/config/ExampleConfig.kt | 25 ++- .../dsl/routes/OpenApiRequestParameter.kt | 31 ++++ .../dsl/routes/OpenApiSimpleBody.kt | 42 +++++ .../dsl/routes/ValueExampleDescriptorDsl.kt | 22 +++ todo.txt | 2 +- 9 files changed, 214 insertions(+), 131 deletions(-) create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt index b5f612e..627a1c8 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt @@ -5,7 +5,6 @@ import io.github.smiley4.ktorswaggerui.data.AuthScheme import io.github.smiley4.ktorswaggerui.data.AuthType import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort import io.github.smiley4.ktorswaggerui.data.SwaggerUiSyntaxHighlight -import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.routing.openApiSpec @@ -110,20 +109,15 @@ private fun Application.myModule() { }) } examples { - example( - ValueExampleDescriptor( - name = "Id 1", - description = "First example id", - value = "12345" - ) - ) - example( - ValueExampleDescriptor( - name = "Id 2", - description = "Second example id", - value = "54321" - ) - ) + example("Id 1") { + description = "First example id" + value = "12345" + } + example("Id 2") { + description = "Second example id" + value = "54321" + + } } specAssigner = { _, _ -> PluginConfigDsl.DEFAULT_SPEC_ID } pathFilter = { _, url -> url.firstOrNull() != "hidden" } diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt index 66cead3..4c47d4d 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Examples.kt @@ -1,8 +1,6 @@ package io.github.smiley4.ktorswaggerui.examples import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor -import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.routing.openApiSpec import io.github.smiley4.ktorswaggerui.routing.swaggerUI @@ -26,20 +24,18 @@ private fun Application.myModule() { examples { // specify two shared examples - example(ValueExampleDescriptor( - name = "Shared A", - description = "first shared example", + example("Shared A") { + description = "first shared example" value = MyExampleClass( someValue = "shared a" ) - )) - example(ValueExampleDescriptor( - name = "Shared B", - description = "second shared example", + } + example("Shared B") { + description = "second shared example" value = MyExampleClass( someValue = "shared b" ) - )) + } } } @@ -59,24 +55,18 @@ private fun Application.myModule() { request { body { // specify two example values - example( - ValueExampleDescriptor( - name = "Example 1", - description = "A first example value", + example("Example 1") { + description = "A first example value" value = MyExampleClass( someValue = "example 1" - ), - ) - ) - example( - ValueExampleDescriptor( - name = "Example 2", - description = "A second example value", - value = MyExampleClass( - someValue = "example 2" - ), + ) + } + example("Example 2") { + description = "A second example value" + value = MyExampleClass( + someValue = "example 2" ) - ) + } } } }) { @@ -88,12 +78,8 @@ private fun Application.myModule() { request { body { // reference two shared examples specified in the plugin-config (and placed in the component section) - example( - RefExampleDescriptor("Example 1", "Shared A") - ) - example( - RefExampleDescriptor("Example 2", "Shared B") - ) + exampleRef("Example 1", "Shared A") + exampleRef("Example 2", "Shared B") } } }) { diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt index d5ad900..6cb299f 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/Petstore.kt @@ -1,8 +1,6 @@ package io.github.smiley4.ktorswaggerui.examples import io.github.smiley4.ktorswaggerui.SwaggerUI -import io.github.smiley4.ktorswaggerui.data.RefExampleDescriptor -import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.routing.delete import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.dsl.routing.post @@ -43,7 +41,9 @@ private fun Application.myModule() { } } examples { - example(ValueExampleDescriptor("Unexpected Error", ErrorModel("Unexpected Error"), null, null)) + example("Unexpected Error") { + value = ErrorModel("Unexpected Error") + } } } @@ -65,41 +65,42 @@ private fun Application.myModule() { queryParameter>("tags") { description = "tags to filter by" required = false - example = ValueExampleDescriptor("dog", "default", null, null) + example("dog") { + value = "default" + } } queryParameter("limit") { description = "maximum number of results to return" required = false - example = ValueExampleDescriptor("default", 100, null, null) + example("default") { + value = 100 + } } } response { HttpStatusCode.OK to { body> { description = "the list of available pets" - example( - ValueExampleDescriptor( - "Pet List", - listOf( - Pet( - id = 123, - name = "Big Bird", - tag = "bird" - ), - Pet( - id = 456, - name = "Charlie", - tag = "dog" - ) - ), null, null + example("Pet List") { + value = listOf( + Pet( + id = 123, + name = "Big Bird", + tag = "bird" + ), + Pet( + id = 456, + name = "Charlie", + tag = "dog" + ) ) - ) + } } } default { body { description = "unexpected error" - example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) + exampleRef("Unexpected Error") } } } @@ -114,56 +115,44 @@ private fun Application.myModule() { body { description = "Pet to add to the store" required = true - example( - ValueExampleDescriptor( - "New Bird", - NewPet( - name = "Big Bird", - tag = "bird" - ), null, null + example("New Bird") { + value = NewPet( + name = "Big Bird", + tag = "bird" ) - ) - example( - ValueExampleDescriptor( - "New Dog", - NewPet( - name = "Charlie", - tag = "dog" - ), null, null + } + example("New Dog") { + value = NewPet( + name = "Charlie", + tag = "dog" ) - ) + } } } response { HttpStatusCode.OK to { body { description = "the created pet" - example( - ValueExampleDescriptor( - "Bird", - Pet( - id = 123, - name = "Big Bird", - tag = "bird" - ), null, null + example("Bird") { + value = Pet( + id = 123, + name = "Big Bird", + tag = "bird" ) - ) - example( - ValueExampleDescriptor( - "Dog", - Pet( - id = 456, - name = "Charlie", - tag = "dog" - ), null, null + } + example("Dog") { + value = Pet( + id = 456, + name = "Charlie", + tag = "dog" ) - ) + } } } default { body { description = "unexpected error" - example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) + exampleRef("Unexpected Error") } } } @@ -180,33 +169,29 @@ private fun Application.myModule() { pathParameter("id") { description = "Id of pet to fetch" required = true - example = ValueExampleDescriptor("default", 123L, null, null) + example("default") { + value = 123L + } } } response { HttpStatusCode.OK to { body{ description = "the pet with the given id" - example( - ValueExampleDescriptor( - "Bird", - Pet( - id = 123, - name = "Big Bird", - tag = "bird" - ), null, null + example("Bird") { + value = Pet( + id = 123, + name = "Big Bird", + tag = "bird" ) - ) - example( - ValueExampleDescriptor( - "Dog", - Pet( - id = 123, - name = "Charlie", - tag = "dog" - ), null, null + } + example("Dog") { + value = Pet( + id = 123, + name = "Charlie", + tag = "dog" ) - ) + } } } HttpStatusCode.NotFound to { @@ -215,7 +200,7 @@ private fun Application.myModule() { default { body { description = "unexpected error" - example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) + exampleRef("Unexpected Error") } } } @@ -230,7 +215,9 @@ private fun Application.myModule() { pathParameter("id") { description = "Id of pet to delete" required = true - example = ValueExampleDescriptor("default", 123L, null, null) + example("default") { + value = 123L + } } } response { @@ -243,7 +230,7 @@ private fun Application.myModule() { default { body { description = "unexpected error" - example(RefExampleDescriptor("Unexpected Error", "Unexpected Error")) + exampleRef("Unexpected Error") } } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt index ebc7129..7df0f74 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt @@ -13,6 +13,8 @@ class ValueExampleDescriptor( val description: String? = null, ) : ExampleDescriptor(name) -class RefExampleDescriptor(name: String, val refName: String): ExampleDescriptor(name) +class RefExampleDescriptor(name: String, val refName: String) : ExampleDescriptor(name) + +class SwaggerExampleDescriptor(name: String, val example: Example) : ExampleDescriptor(name) + -class SwaggerExampleDescriptor(name: String, val example: Example): ExampleDescriptor(name) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt index e819d7a..542d110 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/ExampleConfig.kt @@ -2,7 +2,11 @@ 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.dsl.OpenApiDslMarker +import io.github.smiley4.ktorswaggerui.dsl.routes.ValueExampleDescriptorDsl +import io.swagger.v3.oas.models.examples.Example /** * Configuration for schemas @@ -10,12 +14,27 @@ import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker @OpenApiDslMarker class ExampleConfig { - private val sharedExamples = mutableMapOf() + val sharedExamples = mutableMapOf() - fun example(exampleDescriptor: ExampleDescriptor) { - sharedExamples[exampleDescriptor.name] = exampleDescriptor + fun example(example: ExampleDescriptor) { + sharedExamples[example.name] = example } + fun example(name: String, example: Example) = example(SwaggerExampleDescriptor(name, example)) + + fun example(name: String, example: ValueExampleDescriptorDsl.() -> Unit) = example( + ValueExampleDescriptorDsl() + .apply(example) + .let { result -> + ValueExampleDescriptor( + name = name, + value = result.value, + summary = result.summary, + description = result.description + ) + } + ) + fun build() = ExampleConfigData( sharedExamples = sharedExamples ) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt index 41f4fd3..d828998 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt @@ -3,8 +3,11 @@ package io.github.smiley4.ktorswaggerui.dsl.routes import io.github.smiley4.ktorswaggerui.data.ExampleDescriptor import io.github.smiley4.ktorswaggerui.data.OpenApiRequestParameterData import io.github.smiley4.ktorswaggerui.data.ParameterLocation +import io.github.smiley4.ktorswaggerui.data.SwaggerExampleDescriptor import io.github.smiley4.ktorswaggerui.data.TypeDescriptor +import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.swagger.v3.oas.models.examples.Example @OpenApiDslMarker @@ -34,6 +37,34 @@ class OpenApiRequestParameter( */ var example: ExampleDescriptor? = null + /** + * An example value for this parameter + */ + fun example(example: ExampleDescriptor) { + this.example = example + } + + /** + * An example value for this parameter + */ + fun example(name: String, example: Example) = example(SwaggerExampleDescriptor(name, example)) + + /** + * An example value for this parameter + */ + fun example(name: String, example: ValueExampleDescriptorDsl.() -> Unit) = example( + ValueExampleDescriptorDsl() + .apply(example) + .let { result -> + ValueExampleDescriptor( + name = name, + value = result.value, + summary = result.summary, + description = result.description + ) + } + ) + /** * Determines whether this parameter is mandatory diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt index 677e1ea..e32c5c3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt @@ -2,8 +2,12 @@ package io.github.smiley4.ktorswaggerui.dsl.routes 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.TypeDescriptor +import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.swagger.v3.oas.models.examples.Example /** @@ -22,10 +26,48 @@ class OpenApiSimpleBody( */ private val examples = mutableListOf() + /** + * Add the given example as an example to this body + */ fun example(example: ExampleDescriptor) { examples.add(example) } + /** + * Add the given example as an example to this body + */ + fun example(name: String, example: Example) = example(SwaggerExampleDescriptor(name, example)) + + /** + * Add the given example as an example to this body + */ + fun example(name: String, example: ValueExampleDescriptorDsl.() -> Unit) = example( + ValueExampleDescriptorDsl() + .apply(example) + .let { result -> + ValueExampleDescriptor( + name = name, + value = result.value, + summary = result.summary, + description = result.description + ) + } + ) + + + /** + * Add the given example as an example to this body + * @param name the name of the example to display at this body + * @param refName the name of the referenced example + */ + fun exampleRef(name: String, refName: String) = example(RefExampleDescriptor(name, refName)) + + /** + * Add the given example as an example to this body + * @param name the name of the example + */ + fun exampleRef(name: String) = example(RefExampleDescriptor(name, name)) + override fun build() = OpenApiSimpleBodyData( description = description, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt new file mode 100644 index 0000000..329a720 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt @@ -0,0 +1,22 @@ +package io.github.smiley4.ktorswaggerui.dsl.routes + +class ValueExampleDescriptorDsl { + + /** + * the example value + */ + var value: Any? = null + + + /** + * a short summary of the example + */ + var summary: String? = null + + + /** + * a description of the example + */ + var description: String? = null + +} \ No newline at end of file diff --git a/todo.txt b/todo.txt index f36337f..c4de784 100644 --- a/todo.txt +++ b/todo.txt @@ -4,6 +4,6 @@ todo: - simplify dsl for schemas (TypeDescriptors) ✅ - utility methods for adding different TypeDescriptors ✅ - dsl for array, anyof, ref, empty, ... ✅ -- simplify dsl for examples (ExampleDescriptors) +- simplify dsl for examples (ExampleDescriptors) ✅ - add rootHostPath to operations (see 2.10.0) - post build hook (?) \ No newline at end of file From 54c8c3aaf112bb33b30bf8cd16d7cbdf9b7cef9d Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Mon, 20 May 2024 20:09:01 +0200 Subject: [PATCH 24/32] move to schema-kenerator 0.2.0 --- gradle.properties | 4 ++-- .../ktorswaggerui/builder/schema/SchemaContextImpl.kt | 6 +++--- .../github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt | 2 -- .../io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt | 2 ++ .../ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt | 2 +- todo.txt | 7 ++++--- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/gradle.properties b/gradle.properties index bcb36f0..5f99287 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.code.style=official # project id projectGroupId=io.github.smiley4 projectArtifactIdBase=ktor-swagger-ui -projectVersion=3.0.0-beta1 +projectVersion=3.0.0-beta2 # publishing information projectNameBase=Ktor Swagger UI @@ -19,7 +19,7 @@ projectDeveloperUrl=https://github.com/SMILEY4 versionKtor=2.3.11 versionSwaggerUI=5.9.0 versionSwaggerParser=2.1.19 -versionSchemaKenerator=0.1.1 +versionSchemaKenerator=0.2.0 versionKotlinLogging=3.0.5 versionKotest=5.8.0 versionKotlinTest=1.8.21 diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt index 3648f16..13a3d38 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContextImpl.kt @@ -28,7 +28,7 @@ class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : SchemaCont val schema = collapseRootRef(generateSchema(typeDescriptor)) componentSchemas[schemaId] = schema.swagger schema.componentSchemas.forEach { (k, v) -> - componentSchemas[k.full()] = v + componentSchemas[k] = v } } } @@ -37,7 +37,7 @@ class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : SchemaCont if (schema.swagger.`$ref` == null) { return schema } else { - val referencedSchemaId = TypeId.parse(schema.swagger.`$ref`!!.replace("#/components/schemas/", "")) + val referencedSchemaId = schema.swagger.`$ref`!!.replace("#/components/schemas/", "") val referencedSchema = schema.componentSchemas[referencedSchemaId]!! return CompiledSwaggerSchema( typeData = schema.typeData, @@ -54,7 +54,7 @@ class SchemaContextImpl(private val schemaConfig: SchemaConfigData) : SchemaCont val schema = generateSchema(typeDescriptor) rootSchemas[typeDescriptor] = schema.swagger schema.componentSchemas.forEach { (k, v) -> - componentSchemas[k.full()] = v + componentSchemas[k] = v } } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt index 7df0f74..59d4a4d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt @@ -16,5 +16,3 @@ class ValueExampleDescriptor( class RefExampleDescriptor(name: String, val refName: String) : ExampleDescriptor(name) class SwaggerExampleDescriptor(name: String, val example: Example) : ExampleDescriptor(name) - - diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt index 0372529..2b9a028 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -27,7 +27,9 @@ fun anyOf(vararg types: TypeDescriptor) = AnyOfTypeDescriptor(types.toList()) fun anyOf(types: Collection) = AnyOfTypeDescriptor(types.toList()) fun anyOf(vararg types: Schema<*>) = AnyOfTypeDescriptor(types.map { SwaggerTypeDescriptor(it) }) +@JvmName("anyOfSwagger") fun anyOf(types: Collection>) = AnyOfTypeDescriptor(types.map { SwaggerTypeDescriptor(it) }) fun anyOf(vararg types: KType) = AnyOfTypeDescriptor(types.map { KTypeDescriptor(it) }) +@JvmName("anyOfKType") fun anyOf(types: Collection) = AnyOfTypeDescriptor(types.map { KTypeDescriptor(it) }) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt index 329a720..a66ac23 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/ValueExampleDescriptorDsl.kt @@ -19,4 +19,4 @@ class ValueExampleDescriptorDsl { */ var description: String? = null -} \ No newline at end of file +} diff --git a/todo.txt b/todo.txt index c4de784..aa3177b 100644 --- a/todo.txt +++ b/todo.txt @@ -1,9 +1,10 @@ todo: -- keep up to date with schema-kenerator +- keep up to date with schema-kenerator features - simplify dsl for schemas (TypeDescriptors) ✅ - utility methods for adding different TypeDescriptors ✅ - dsl for array, anyof, ref, empty, ... ✅ - simplify dsl for examples (ExampleDescriptors) ✅ -- add rootHostPath to operations (see 2.10.0) -- post build hook (?) \ No newline at end of file +- add rootHostPath to operations (see 2.10.0) (is this required? -> see servers) +- post build hook (check if already exists?) +- wiki / documentation \ No newline at end of file From b81cfb50cb70cd229ba721194e6fc228b7a105d3 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Fri, 24 May 2024 22:59:39 +0200 Subject: [PATCH 25/32] fix config for routing, option to disabled syntax highlighting --- .../io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt | 1 + .../ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt | 1 + .../io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt | 4 ++++ .../io/github/smiley4/ktorswaggerui/routing/routing.kt | 8 +++++--- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index 83d674d..8c3dd7e 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -64,6 +64,7 @@ val SwaggerUI = createApplicationPlugin(name = "SwaggerUI", createConfiguration try { val routes = routes(application, config) ApiSpec.setAll(buildOpenApiSpecs(config, routes)) + ApiSpec.swaggerUiConfig = config.swagger } catch (e: Exception) { logger.error("Error during application startup in swagger-ui-plugin", e) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt index 3d38d95..f31ffcc 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt @@ -1,6 +1,7 @@ package io.github.smiley4.ktorswaggerui.data enum class SwaggerUiSyntaxHighlight(val value: String) { + DISABLED("disabled"), AGATE("agate"), ARTA("arta"), MONOKAI("monokai"), diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt index 3e0a897..c3ff84e 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/ApiSpec.kt @@ -1,7 +1,11 @@ package io.github.smiley4.ktorswaggerui.routing +import io.github.smiley4.ktorswaggerui.data.SwaggerUIData + object ApiSpec { + var swaggerUiConfig: SwaggerUIData = SwaggerUIData.DEFAULT + private val apiSpecs = mutableMapOf() fun setAll(specs: Map) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt index a2dc789..3e4bf2a 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt @@ -4,6 +4,7 @@ import io.github.smiley4.ktorswaggerui.SWAGGER_UI_WEBJARS_VERSION import io.github.smiley4.ktorswaggerui.SwaggerUI import io.github.smiley4.ktorswaggerui.data.SwaggerUIData import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort +import io.github.smiley4.ktorswaggerui.data.SwaggerUiSyntaxHighlight import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl import io.github.smiley4.ktorswaggerui.dsl.routing.route import io.ktor.http.* @@ -11,7 +12,6 @@ import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import mu.KotlinLogging fun Route.openApiSpec(specId: String = PluginConfigDsl.DEFAULT_SPEC_ID) { @@ -31,7 +31,7 @@ fun Route.swaggerUI(apiUrl: String) { serveStaticResource(call.parameters["filename"]!!, SWAGGER_UI_WEBJARS_VERSION, call) } get("swagger-initializer.js") { - serveSwaggerInitializer(call, SwaggerUIData.DEFAULT, apiUrl) + serveSwaggerInitializer(call, ApiSpec.swaggerUiConfig, apiUrl) } } } @@ -44,7 +44,9 @@ private suspend fun serveSwaggerInitializer(call: ApplicationCall, swaggerUiConf val propSort = "operationsSorter: " + if (swaggerUiConfig.sort == SwaggerUiSort.NONE) "undefined" else "\"${swaggerUiConfig.sort.value}\"" - val propSyntaxHighlight = "syntaxHighlight: { theme: \"${swaggerUiConfig.syntaxHighlight.value}\" }" + val propSyntaxHighlight = "syntaxHighlight: " + + if(swaggerUiConfig.syntaxHighlight == SwaggerUiSyntaxHighlight.DISABLED) "false" + else "{ theme: \"${swaggerUiConfig.syntaxHighlight.value}\" }" val content = """ window.onload = function() { window.ui = SwaggerUIBundle({ From 3e6f528fcb1d49efeaa1f8b8d1e9e7027810b86f Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Thu, 30 May 2024 19:16:54 +0200 Subject: [PATCH 26/32] update schema-kenerator to 0.4.0 --- gradle.properties | 2 +- ktor-swagger-ui-examples/src/main/resources/logback.xml | 1 - ktor-swagger-ui/src/test/resources/logback.xml | 1 - todo.txt | 7 +------ 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/gradle.properties b/gradle.properties index 5f99287..ccec52f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ projectDeveloperUrl=https://github.com/SMILEY4 versionKtor=2.3.11 versionSwaggerUI=5.9.0 versionSwaggerParser=2.1.19 -versionSchemaKenerator=0.2.0 +versionSchemaKenerator=0.4.0 versionKotlinLogging=3.0.5 versionKotest=5.8.0 versionKotlinTest=1.8.21 diff --git a/ktor-swagger-ui-examples/src/main/resources/logback.xml b/ktor-swagger-ui-examples/src/main/resources/logback.xml index 52ce2ed..b397c54 100644 --- a/ktor-swagger-ui-examples/src/main/resources/logback.xml +++ b/ktor-swagger-ui-examples/src/main/resources/logback.xml @@ -13,6 +13,5 @@ - \ No newline at end of file diff --git a/ktor-swagger-ui/src/test/resources/logback.xml b/ktor-swagger-ui/src/test/resources/logback.xml index 52ce2ed..b397c54 100644 --- a/ktor-swagger-ui/src/test/resources/logback.xml +++ b/ktor-swagger-ui/src/test/resources/logback.xml @@ -13,6 +13,5 @@ - \ No newline at end of file diff --git a/todo.txt b/todo.txt index aa3177b..11c5860 100644 --- a/todo.txt +++ b/todo.txt @@ -1,10 +1,5 @@ todo: - keep up to date with schema-kenerator features -- simplify dsl for schemas (TypeDescriptors) ✅ - - utility methods for adding different TypeDescriptors ✅ - - dsl for array, anyof, ref, empty, ... ✅ -- simplify dsl for examples (ExampleDescriptors) ✅ -- add rootHostPath to operations (see 2.10.0) (is this required? -> see servers) -- post build hook (check if already exists?) + - wiki / documentation \ No newline at end of file From 47bca53e18d7ee39d761da086acf459271df7be1 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Fri, 31 May 2024 15:07:14 +0200 Subject: [PATCH 27/32] upgrade to openapi 3.1.0 (from 3.0.1) and add more properties to dsl --- gradle.properties | 2 +- .../ktorswaggerui/examples/CompleteConfig.kt | 56 ++++++++++++++++++- .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 4 +- .../builder/openapi/HeaderBuilder.kt | 2 + .../builder/openapi/InfoBuilder.kt | 1 + .../builder/openapi/LicenseBuilder.kt | 1 + .../builder/openapi/OpenApiBuilder.kt | 3 + .../builder/openapi/OperationBuilder.kt | 8 ++- .../builder/openapi/ParameterBuilder.kt | 3 +- .../builder/openapi/ServerBuilder.kt | 13 +++++ .../smiley4/ktorswaggerui/data/InfoData.kt | 6 +- .../smiley4/ktorswaggerui/data/LicenseData.kt | 2 + .../ktorswaggerui/data/OpenApiHeaderData.kt | 1 + .../data/OpenApiRequestParameterData.kt | 5 +- .../ktorswaggerui/data/OpenApiRouteData.kt | 4 +- .../smiley4/ktorswaggerui/data/ServerData.kt | 4 +- .../ktorswaggerui/data/ServerVariableData.kt | 8 +++ .../ktorswaggerui/dsl/config/OpenApiInfo.kt | 8 ++- .../dsl/config/OpenApiLicense.kt | 6 ++ .../ktorswaggerui/dsl/config/OpenApiServer.kt | 15 ++++- .../dsl/config/OpenApiServerVariable.kt | 41 ++++++++++++++ .../dsl/routes/OpenApiBaseBody.kt | 2 +- .../ktorswaggerui/dsl/routes/OpenApiHeader.kt | 4 +- .../dsl/routes/OpenApiMultipartBody.kt | 2 +- .../dsl/routes/OpenApiRequest.kt | 28 +++++++++- .../dsl/routes/OpenApiRequestParameter.kt | 7 +++ .../ktorswaggerui/dsl/routes/OpenApiRoute.kt | 46 +++++++++++---- .../dsl/routes/OpenApiSimpleBody.kt | 2 +- .../ktorswaggerui/builder/InfoBuilderTest.kt | 5 +- .../builder/OpenApiBuilderTest.kt | 17 ++++-- .../builder/OperationBuilderTest.kt | 5 +- .../ktorswaggerui/builder/PathsBuilderTest.kt | 4 ++ .../builder/ServersBuilderTest.kt | 25 ++++++++- 33 files changed, 305 insertions(+), 35 deletions(-) create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerVariableData.kt create mode 100644 ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServerVariable.kt diff --git a/gradle.properties b/gradle.properties index ccec52f..cd97b42 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ projectDeveloperUrl=https://github.com/SMILEY4 # dependency versions versionKtor=2.3.11 versionSwaggerUI=5.9.0 -versionSwaggerParser=2.1.19 +versionSwaggerParser=2.1.22 versionSchemaKenerator=0.4.0 versionKotlinLogging=3.0.5 versionKotest=5.8.0 diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt index 627a1c8..f6312db 100644 --- a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CompleteConfig.kt @@ -14,6 +14,7 @@ import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot import io.github.smiley4.schemakenerator.swagger.data.TitleType import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema import io.github.smiley4.schemakenerator.swagger.withAutoTitle +import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application import io.ktor.server.application.call @@ -30,12 +31,16 @@ fun main() { embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) } +class Greeting( + val name: String +) /** * A (nearly) complete - and mostly nonsensical - plugin configuration */ private fun Application.myModule() { + install(SwaggerUI) { info { title = "Example API" @@ -50,6 +55,7 @@ private fun Application.myModule() { license { name = "Example License" url = "example.com" + identifier = "Apache-2.0" } } externalDocs { @@ -59,10 +65,20 @@ private fun Application.myModule() { server { url = "localhost" description = "local dev-server" + variable("version") { + default = "1.0" + enum = setOf("1.0", "2.0", "3.0") + description = "the version of the server api" + } } server { url = "example.com" description = "productive server" + variable("version") { + default = "1.0" + enum = setOf("1.0", "2.0") + description = "the version of the server api" + } } swagger { displayOperationId = true @@ -138,15 +154,53 @@ private fun Application.myModule() { // a documented route get("hello", { - description = "A Hello-World route" + operationId = "hello" + summary = "hello world route" + description = "A Hello-World route as an example." + tags = setOf("hello", "example") + specId = PluginConfigDsl.DEFAULT_SPEC_ID + deprecated = false + hidden = false + protected = false + securitySchemeNames = emptyList() + externalDocs { + url = "example.com/hello" + description = "external documentation of 'hello'-route" + } request { queryParameter("name") { description = "the name to greet" + example("Josh") { + value = "Josh" + summary = "Example name" + description = "An example name for this query parameter" + } } + body() } response { HttpStatusCode.OK to { description = "successful request - always returns 'Hello World!'" + header("x-random") { + description = "A header with some random number" + required = true + deprecated = false + explode = false + } + body { + description = "the greeting object with the name of the person to greet." + mediaTypes = setOf(ContentType.Application.Json) + required = true + } + } + } + server { + url = "example.com" + description = "productive server for 'hello'-route" + variable("version") { + default = "1.0" + enum = setOf("1.0", "2.0") + description = "the version of the server api" } } }) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index 8c3dd7e..dae653b 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -75,7 +75,7 @@ val SwaggerUI = createApplicationPlugin(name = "SwaggerUI", createConfiguration private fun buildOpenApiSpecs(config: PluginConfigData, routes: List): Map { val routesBySpec = buildMap> { routes.forEach { route -> - val specName = route.documentation.specId ?: config.specAssigner(route.path, route.documentation.tags) + val specName = route.documentation.specId ?: config.specAssigner(route.path, route.documentation.tags.toList()) computeIfAbsent(specName) { mutableListOf() }.add(route) } } @@ -157,6 +157,8 @@ private fun builder( config = config ), securityRequirementsBuilder = SecurityRequirementsBuilder(config), + externalDocumentationBuilder = ExternalDocumentationBuilder(), + serverBuilder = ServerBuilder() ) ) ), diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt index 964f935..63330ea 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt @@ -14,6 +14,8 @@ class HeaderBuilder( it.required = header.required it.deprecated = header.deprecated it.schema = header.type?.let { t -> schemaContext.getSchema(t) } + it.explode = header.explode +// it.example = TODO() } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt index bfc03dc..0fb0e99 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt @@ -20,6 +20,7 @@ class InfoBuilder( info.license?.also { license -> it.license = licenseBuilder.build(license) } + it.summary = info.summary } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt index 30f727c..056662b 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt @@ -9,6 +9,7 @@ class LicenseBuilder { License().also { it.name = license.name it.url = license.url + it.identifier = license.identifier } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt index fc73482..b0830ab 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt @@ -5,6 +5,7 @@ import io.github.smiley4.ktorswaggerui.data.PluginConfigData import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.SpecVersion class OpenApiBuilder( private val config: PluginConfigData, @@ -20,6 +21,8 @@ class OpenApiBuilder( fun build(routes: Collection): OpenAPI { return OpenAPI().also { + it.specVersion = SpecVersion.V31 + it.openapi = "3.1.0" it.info = infoBuilder.build(config.info) it.externalDocs = externalDocumentationBuilder.build(config.externalDocs) it.servers = config.servers.map { server -> serverBuilder.build(server) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt index dab2bbe..339faed 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt @@ -8,7 +8,9 @@ class OperationBuilder( private val parameterBuilder: ParameterBuilder, private val requestBodyBuilder: RequestBodyBuilder, private val responsesBuilder: ResponsesBuilder, - private val securityRequirementsBuilder: SecurityRequirementsBuilder + private val securityRequirementsBuilder: SecurityRequirementsBuilder, + private val externalDocumentationBuilder: ExternalDocumentationBuilder, + private val serverBuilder: ServerBuilder ) { fun build(route: RouteMeta): Operation = @@ -30,6 +32,10 @@ class OperationBuilder( } } } + it.externalDocs = route.documentation.externalDocs?.let { docs -> externalDocumentationBuilder.build(docs) } + if (route.documentation.servers.isNotEmpty()) { + it.servers = route.documentation.servers.map { server -> serverBuilder.build(server) } + } } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt index ff5d1d0..221b33d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt @@ -24,9 +24,10 @@ class ParameterBuilder( it.deprecated = parameter.deprecated it.allowEmptyValue = parameter.allowEmptyValue it.explode = parameter.explode - it.example = parameter.example?.let { e -> exampleContext.getExample(e).value } + it.example = parameter.example?.let { e -> exampleContext.getExample(e).value } // todo: example"S" ? it.allowReserved = parameter.allowReserved it.schema = schemaContext.getSchema(parameter.type) + it.style = parameter.style } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt index 5878483..dfe2ba6 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt @@ -2,6 +2,8 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.ServerData import io.swagger.v3.oas.models.servers.Server +import io.swagger.v3.oas.models.servers.ServerVariable +import io.swagger.v3.oas.models.servers.ServerVariables class ServerBuilder { @@ -9,6 +11,17 @@ class ServerBuilder { Server().also { it.url = server.url it.description = server.description + if (server.variables.isNotEmpty()) { + it.variables = ServerVariables().also { variables -> + server.variables.forEach { entry -> + variables.addServerVariable(entry.name, ServerVariable().also { variable -> + variable.enum = entry.enum.toList() + variable.default = entry.default + variable.description = entry.description + }) + } + } + } } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt index fe8e164..c8ee025 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt @@ -4,18 +4,20 @@ data class InfoData( val title: String, val version: String?, val description: String?, + val summary: String?, val termsOfService: String?, val contact: ContactData?, - val license: LicenseData? + val license: LicenseData?, ) { companion object { val DEFAULT = InfoData( title = "API", version = null, description = null, + summary = null, termsOfService = null, contact = null, - license = null + license = null, ) } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt index 154be47..5ae2002 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt @@ -3,11 +3,13 @@ package io.github.smiley4.ktorswaggerui.data data class LicenseData( val name: String?, val url: String?, + val identifier: String? ) { companion object { val DEFAULT = LicenseData( name = null, url = null, + identifier = null ) } } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt index a734189..08eb738 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt @@ -5,4 +5,5 @@ data class OpenApiHeaderData( val type: TypeDescriptor?, val required: Boolean, val deprecated: Boolean, + val explode: Boolean?, ) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt index c947e66..6946d1c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt @@ -1,5 +1,7 @@ package io.github.smiley4.ktorswaggerui.data +import io.swagger.v3.oas.models.parameters.Parameter + data class OpenApiRequestParameterData( val name: String, val type: TypeDescriptor, @@ -10,5 +12,6 @@ data class OpenApiRequestParameterData( val deprecated: Boolean, val allowEmptyValue: Boolean, val explode: Boolean, - val allowReserved: Boolean + val allowReserved: Boolean, + val style: Parameter.StyleEnum?, ) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt index 8b980a8..1add5d6 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt @@ -2,7 +2,7 @@ package io.github.smiley4.ktorswaggerui.data data class OpenApiRouteData( val specId: String?, - val tags: List, + val tags: Set, val summary: String?, val description: String?, val operationId: String?, @@ -12,4 +12,6 @@ data class OpenApiRouteData( val protected: Boolean?, val request: OpenApiRequestData, val responses: List, + val externalDocs: ExternalDocsData?, + val servers: List, ) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt index a98ca61..8fec541 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt @@ -3,12 +3,14 @@ package io.github.smiley4.ktorswaggerui.data data class ServerData( val url: String, val description: String?, + val variables: List ) { companion object { val DEFAULT = ServerData( url = "/", - description = null + description = null, + variables = emptyList() ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerVariableData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerVariableData.kt new file mode 100644 index 0000000..415c98e --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerVariableData.kt @@ -0,0 +1,8 @@ +package io.github.smiley4.ktorswaggerui.data + +data class ServerVariableData( + val name: String, + val enum: Set, + val default: String, + val description: String? +) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt index 84e2387..83923bd 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt @@ -27,6 +27,11 @@ class OpenApiInfo { */ var description: String? = null + /** + * A short summary of the API + */ + var summary: String? = null + /** * A URL to the Terms of Service for the API. MUST be in the format of a URL. @@ -62,7 +67,8 @@ class OpenApiInfo { description = merge(base.description, this.description), termsOfService = merge(base.termsOfService, this.termsOfService), contact = contact?.build(base.contact ?: ContactData.DEFAULT) ?: base.contact, - license = license?.build(base.license ?: LicenseData.DEFAULT) ?: base.license + license = license?.build(base.license ?: LicenseData.DEFAULT) ?: base.license, + summary = merge(base.summary, this.summary) ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt index 66fef89..3d05b85 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiLicense.kt @@ -22,8 +22,14 @@ class OpenApiLicense { var url: String? = LicenseData.DEFAULT.url + /** + * An SPDX (https://spdx.org/licenses/) license expression for the API. The identifier field is mutually exclusive of the url field. + */ + var identifier: String? = LicenseData.DEFAULT.identifier + fun build(base: LicenseData) = LicenseData( name = DataUtils.merge(base.name, name), url = DataUtils.merge(base.url, url), + identifier = DataUtils.merge(base.identifier, identifier) ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt index da249d1..5c667d6 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServer.kt @@ -22,10 +22,23 @@ class OpenApiServer { */ var description: String? = ServerData.DEFAULT.description + private val variables = mutableMapOf() + + + /** + * Adds a new server variable with the given name + */ + fun variable(name: String, block: OpenApiServerVariable.() -> Unit) { + variables[name] = OpenApiServerVariable(name).apply(block) + } fun build(base: ServerData) = ServerData( url = mergeDefault(base.url, url, ServerData.DEFAULT.url), - description = merge(base.description, description) + description = merge(base.description, description), + variables = buildMap { + base.variables.forEach { this[it.name] = it } + variables.values.map { it.build() }.forEach { this[it.name] = it } + }.values.toList() ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServerVariable.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServerVariable.kt new file mode 100644 index 0000000..dd2fc39 --- /dev/null +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiServerVariable.kt @@ -0,0 +1,41 @@ +package io.github.smiley4.ktorswaggerui.dsl.config + +import io.github.smiley4.ktorswaggerui.data.ServerVariableData +import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker + +/** + * An object representing a Server Variable for server URL template substitution. + */ +@OpenApiDslMarker +class OpenApiServerVariable( + /** + * The name of this variable + */ + private val name: String +) { + + /** + * An enumeration of string values to be used if the substitution options are from a limited set. Must not be empty. + */ + var enum: Collection = emptyList() + + + /** + * The default value to use for substitution. Must be in the list of enums. + */ + var default: String? = null + + + /** + * An optional description for this server variable. + */ + var description: String? = null + + fun build() = ServerVariableData( + name = name, + enum = enum.toSet(), + default = (default ?: enum.firstOrNull()) ?: "", + description = description + ) + +} diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt index 20f846c..20adb16 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiBaseBody.kt @@ -23,7 +23,7 @@ sealed class OpenApiBaseBody { /** * Allowed Media Types for this body. If none specified, a media type will be chosen automatically based on the provided schema */ - var mediaTypes: Set = emptySet() + var mediaTypes: Collection = emptySet() abstract fun build(): OpenApiBaseBodyData } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt index 1a2b6ef..73227c7 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt @@ -62,11 +62,13 @@ class OpenApiHeader { */ var deprecated: Boolean? = null + var explode: Boolean? = null fun build() = OpenApiHeaderData( description = description, type = type, required = required ?: false, - deprecated = deprecated ?: false + deprecated = deprecated ?: false, + explode = explode, ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt index e1ee012..fc89dee 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartBody.kt @@ -50,7 +50,7 @@ class OpenApiMultipartBody : OpenApiBaseBody() { override fun build() = OpenApiMultipartBodyData( description = description, required = required ?: false, - mediaTypes = mediaTypes, + mediaTypes = mediaTypes.toSet(), parts = parts.map { it.build() } ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt index d307b07..d078edd 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt @@ -19,6 +19,7 @@ class OpenApiRequest { */ val parameters = mutableListOf() + /** * A path parameters that is applicable for this operation */ @@ -33,18 +34,21 @@ class OpenApiRequest { fun pathParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.PATH, name, type, block) + /** * A path parameters that is applicable for this operation */ fun pathParameter(name: String, type: Schema<*>, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.PATH, name, SwaggerTypeDescriptor(type), block) + /** * A path parameters that is applicable for this operation */ fun pathParameter(name: String, type: KType, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.PATH, name, KTypeDescriptor(type), block) + /** * A path parameters that is applicable for this operation */ @@ -58,18 +62,21 @@ class OpenApiRequest { fun queryParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.QUERY, name, type, block) + /** * A query parameters that is applicable for this operation */ fun queryParameter(name: String, type: Schema<*>, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.QUERY, name, SwaggerTypeDescriptor(type), block) + /** * A query parameters that is applicable for this operation */ fun queryParameter(name: String, type: KType, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.QUERY, name, KTypeDescriptor(type), block) + /** * A query parameters that is applicable for this operation */ @@ -83,18 +90,21 @@ class OpenApiRequest { fun headerParameter(name: String, type: TypeDescriptor, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.HEADER, name, type, block) + /** * A header parameters that is applicable for this operation */ fun headerParameter(name: String, type: Schema<*>, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.HEADER, name, SwaggerTypeDescriptor(type), block) + /** * A header parameters that is applicable for this operation */ fun headerParameter(name: String, type: KType, block: OpenApiRequestParameter.() -> Unit = {}) = parameter(ParameterLocation.HEADER, name, KTypeDescriptor(type), block) + /** * A header parameters that is applicable for this operation */ @@ -111,19 +121,25 @@ class OpenApiRequest { * The body returned with this request */ fun body(type: TypeDescriptor, block: OpenApiSimpleBody.() -> Unit = {}) { - body = OpenApiSimpleBody(type).apply(block) + val result = OpenApiSimpleBody(type).apply(block) + if (!result.isEmptyBody()) { + body = result + } } + /** * The body returned with this request */ fun body(type: Schema<*>, block: OpenApiSimpleBody.() -> Unit = {}) = body(SwaggerTypeDescriptor(type), block) + /** * The body returned with this request */ fun body(type: KType, block: OpenApiSimpleBody.() -> Unit = {}) = body(KTypeDescriptor(type), block) + /** * The body returned with this request */ @@ -150,4 +166,14 @@ class OpenApiRequest { body = body?.build() ) + private fun OpenApiBaseBody.isEmptyBody(): Boolean { + return when (this) { + is OpenApiSimpleBody -> when (type) { + is KTypeDescriptor -> type.type == typeOf() + else -> false + } + is OpenApiMultipartBody -> false + } + } + } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt index d828998..93f822e 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt @@ -8,6 +8,7 @@ import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker import io.swagger.v3.oas.models.examples.Example +import io.swagger.v3.oas.models.parameters.Parameter @OpenApiDslMarker @@ -99,6 +100,11 @@ class OpenApiRequestParameter( var allowReserved: Boolean? = null + /** + * Describes how the parameter value will be serialized depending on the type of the parameter value. + */ + var style: Parameter.StyleEnum? = null + fun build() = OpenApiRequestParameterData( name = name, type = type, @@ -110,6 +116,7 @@ class OpenApiRequestParameter( allowEmptyValue = allowEmptyValue ?: true, explode = explode ?: false, allowReserved = allowReserved ?: true, + style = style ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt index 90fa050..2b2def4 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt @@ -1,7 +1,11 @@ package io.github.smiley4.ktorswaggerui.dsl.routes +import io.github.smiley4.ktorswaggerui.data.ExternalDocsData import io.github.smiley4.ktorswaggerui.data.OpenApiRouteData +import io.github.smiley4.ktorswaggerui.data.ServerData import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiExternalDocs +import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiServer @OpenApiDslMarker class OpenApiRoute { @@ -15,7 +19,7 @@ class OpenApiRoute { /** * A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier */ - var tags: List = emptyList() + var tags: Collection = emptyList() /** @@ -49,14 +53,6 @@ class OpenApiRoute { var hidden: Boolean = false - /** - * A declaration of which security mechanisms can be used for this operation (i.e. any of the specified ones). - * If none is specified, defaultSecuritySchemeName (global plugin config) will be used. - * Only applied to [protected] operations. - */ - var securitySchemeNames: Collection? = null - - /** * Specifies whether this operation is protected. * If not specified, the authentication state of the Ktor route will be used @@ -64,6 +60,14 @@ class OpenApiRoute { */ var protected: Boolean? = null + + /** + * A declaration of which security mechanisms can be used for this operation (i.e. any of the specified ones). + * If none is specified, defaultSecuritySchemeName (global plugin config) will be used. + * Only applied to [protected] operations. + */ + var securitySchemeNames: Collection? = null + private val request = OpenApiRequest() @@ -89,9 +93,29 @@ class OpenApiRoute { fun getResponses() = responses + /** + * OpenAPI external docs configuration - link and description of an external documentation + */ + fun externalDocs(block: OpenApiExternalDocs.() -> Unit) { + externalDocs = OpenApiExternalDocs().apply(block) + } + + private var externalDocs: OpenApiExternalDocs? = null + + + /** + * OpenAPI server configuration - an array of servers, which provide connectivity information to a target server + */ + fun server(block: OpenApiServer.() -> Unit) { + servers.add(OpenApiServer().apply(block)) + } + + private val servers = mutableListOf() + + fun build() = OpenApiRouteData( specId = specId, - tags = tags, + tags = tags.toSet(), summary = summary, description = description, operationId = operationId, @@ -101,6 +125,8 @@ class OpenApiRoute { protected = protected, request = request.build(), responses = responses.getResponses().map { it.build() }, + externalDocs = externalDocs?.build(ExternalDocsData.DEFAULT), + servers = servers.map { it.build(ServerData.DEFAULT) } ) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt index e32c5c3..517dc52 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiSimpleBody.kt @@ -72,7 +72,7 @@ class OpenApiSimpleBody( override fun build() = OpenApiSimpleBodyData( description = description, required = required ?: false, - mediaTypes = mediaTypes, + mediaTypes = mediaTypes.toSet(), type = type, examples = examples, ) diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt index d2f215c..efa98ce 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt @@ -29,6 +29,7 @@ class InfoBuilderTest : StringSpec({ buildInfoObject { title = "Test Api" version = "1.0" + summary = "testing api" description = "Api for testing" termsOfService = "test-tos" contact { @@ -40,10 +41,12 @@ class InfoBuilderTest : StringSpec({ license { name = "Test License" url = "example.com" + identifier = "Example" } }.also { info -> info.title shouldBe "Test Api" info.version shouldBe "1.0" + info.summary shouldBe "testing api" info.description shouldBe "Api for testing" info.termsOfService shouldBe "test-tos" info.contact @@ -58,9 +61,9 @@ class InfoBuilderTest : StringSpec({ ?.also { license -> license.name shouldBe "Test License" license.url shouldBe "example.com" + license.identifier shouldBe "Example" } info.extensions shouldBe null - info.summary shouldBe null } } diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt index fa359f7..b805c6d 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt @@ -33,6 +33,7 @@ import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.maps.shouldHaveSize +import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.swagger.v3.oas.models.OpenAPI @@ -67,10 +68,16 @@ class OpenApiBuilderTest : StringSpec({ } buildOpenApiObject(emptyList(), config).also { openapi -> openapi.servers shouldHaveSize 2 - openapi.servers.map { it.url } shouldContainExactlyInAnyOrder listOf( - "http://localhost:8080", - "https://127.0.0.1" - ) + openapi.servers.find { it.url == "http://localhost:8080" }!!.also { server -> + server.url shouldBe "http://localhost:8080" + server.description shouldBe "Development Server" + server.variables shouldBe null + } + openapi.servers.find { it.url == "https://127.0.0.1" }!!.also { server -> + server.url shouldBe "https://127.0.0.1" + server.description shouldBe "Production Server" + server.variables shouldBe null + } } } @@ -160,6 +167,8 @@ class OpenApiBuilderTest : StringSpec({ config = pluginConfigData ), securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), + externalDocumentationBuilder = ExternalDocumentationBuilder(), + serverBuilder = ServerBuilder() ) ) ), diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt index 005bd77..f35e072 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt @@ -1,9 +1,9 @@ package io.github.smiley4.ktorswaggerui.builder -import com.fasterxml.jackson.databind.ObjectMapper 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 @@ -12,6 +12,7 @@ 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.route.RouteMeta import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextImpl @@ -1001,6 +1002,8 @@ class OperationBuilderTest : StringSpec({ config = pluginConfigData ), securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), + externalDocumentationBuilder = ExternalDocumentationBuilder(), + serverBuilder = ServerBuilder() ).build(route) } diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt index f31cb74..f55f767 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt @@ -3,6 +3,7 @@ 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 @@ -13,6 +14,7 @@ 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.route.RouteMeta import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContextImpl @@ -133,6 +135,8 @@ class PathsBuilderTest : StringSpec({ config = pluginConfigData ), securityRequirementsBuilder = SecurityRequirementsBuilder(pluginConfigData), + externalDocumentationBuilder = ExternalDocumentationBuilder(), + serverBuilder = ServerBuilder() ) ) ).build(routes) diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt index aefda0d..afa7dec 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt @@ -4,6 +4,8 @@ import io.github.smiley4.ktorswaggerui.builder.openapi.ServerBuilder import io.github.smiley4.ktorswaggerui.data.ServerData import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiServer import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.swagger.v3.oas.models.servers.Server @@ -15,7 +17,6 @@ class ServersBuilderTest : StringSpec({ server.description shouldBe null server.variables shouldBe null server.extensions shouldBe null - } } @@ -23,10 +24,30 @@ class ServersBuilderTest : StringSpec({ buildServerObject { url = "Test URL" description = "Test Description" + variable("version") { + description = "the version of the api" + default = "2" + enum = setOf("1", "2", "3") + } + variable("region") { + description = "the region of the api" + default = "somewhere" + enum = setOf("somewhere", "else") + } }.also { server -> server.url shouldBe "Test URL" server.description shouldBe "Test Description" - server.variables shouldBe null + server.variables.keys shouldContainExactlyInAnyOrder listOf("version", "region") + server.variables["version"]!!.also { variable -> + variable.description shouldBe "the version of the api" + variable.default shouldBe "2" + variable.enum shouldContainExactlyInAnyOrder listOf("1", "2", "3") + } + server.variables["region"]!!.also { variable -> + variable.description shouldBe "the region of the api" + variable.default shouldBe "somewhere" + variable.enum shouldContainExactlyInAnyOrder listOf("somewhere", "else") + } server.extensions shouldBe null } } From f4afb558fcfe0e346648ecf8eaf0899c47d1725a Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Fri, 31 May 2024 15:28:12 +0200 Subject: [PATCH 28/32] upgrade to swagger-ui 5.17.11 --- gradle.properties | 2 +- .../kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index cd97b42..7620400 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ projectDeveloperUrl=https://github.com/SMILEY4 # dependency versions versionKtor=2.3.11 -versionSwaggerUI=5.9.0 +versionSwaggerUI=5.17.11 versionSwaggerParser=2.1.22 versionSchemaKenerator=0.4.0 versionKotlinLogging=3.0.5 diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index dae653b..cb16b0c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -47,7 +47,7 @@ import mu.KotlinLogging /** * This version must match the version of the gradle dependency */ -internal const val SWAGGER_UI_WEBJARS_VERSION = "5.9.0" +internal const val SWAGGER_UI_WEBJARS_VERSION = "5.17.11" private val logger = KotlinLogging.logger {} From ef9f60973320ac75f5300d8cabaeb371facb2b15 Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Fri, 31 May 2024 16:08:49 +0200 Subject: [PATCH 29/32] set version to 3.0.0-beta3 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7620400..4f4df1a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.code.style=official # project id projectGroupId=io.github.smiley4 projectArtifactIdBase=ktor-swagger-ui -projectVersion=3.0.0-beta2 +projectVersion=3.0.0-beta3 # publishing information projectNameBase=Ktor Swagger UI From 48faa190b3661d5b6e5fc552c1a5173bf7ff3d9c Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sat, 8 Jun 2024 14:52:38 +0200 Subject: [PATCH 30/32] improve default schema generator, minor improvements --- ktor-swagger-ui-examples/build.gradle.kts | 2 + .../examples/CustomizedSchemaGenerator.kt | 80 +++++++++++++++++++ .../ktorswaggerui/data/SchemaConfigData.kt | 8 ++ .../dsl/config/OpenApiSecurityScheme.kt | 4 +- .../ktorswaggerui/dsl/config/OpenApiTag.kt | 2 +- .../ktorswaggerui/dsl/routes/OpenApiHeader.kt | 4 + .../dsl/routes/OpenApiMultipartPart.kt | 2 +- 7 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizedSchemaGenerator.kt diff --git a/ktor-swagger-ui-examples/build.gradle.kts b/ktor-swagger-ui-examples/build.gradle.kts index 9c7f459..8109498 100644 --- a/ktor-swagger-ui-examples/build.gradle.kts +++ b/ktor-swagger-ui-examples/build.gradle.kts @@ -5,6 +5,7 @@ version = projectVersion plugins { kotlin("jvm") + kotlin("plugin.serialization") version "1.9.21" } repositories { @@ -29,6 +30,7 @@ dependencies { implementation("io.github.smiley4:schema-kenerator-core:$versionSchemaKenerator") implementation("io.github.smiley4:schema-kenerator-reflection:$versionSchemaKenerator") + implementation("io.github.smiley4:schema-kenerator-serialization:$versionSchemaKenerator") implementation("io.github.smiley4:schema-kenerator-swagger:$versionSchemaKenerator") implementation("io.github.smiley4:schema-kenerator-jackson:$versionSchemaKenerator") diff --git a/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizedSchemaGenerator.kt b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizedSchemaGenerator.kt new file mode 100644 index 0000000..5940244 --- /dev/null +++ b/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples/CustomizedSchemaGenerator.kt @@ -0,0 +1,80 @@ +package io.github.smiley4.ktorswaggerui.examples + +import io.github.smiley4.ktorswaggerui.SwaggerUI +import io.github.smiley4.ktorswaggerui.dsl.routing.get +import io.github.smiley4.ktorswaggerui.routing.openApiSpec +import io.github.smiley4.ktorswaggerui.routing.swaggerUI +import io.github.smiley4.schemakenerator.serialization.processKotlinxSerialization +import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot +import io.github.smiley4.schemakenerator.swagger.data.TitleType +import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema +import io.github.smiley4.schemakenerator.swagger.withAutoTitle +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.response.respond +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import kotlinx.serialization.Serializable + +fun main() { + embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true) +} + +private fun Application.myModule() { + + // Install and configure the "SwaggerUI"-Plugin + install(SwaggerUI) { + schemas { + // replace default schema-generator with customized one + generator = { type -> + type + // process type using kotlinx-serialization instead of reflection + // requires additional dependency "io.github.smiley4:schema-kenerator-kotlinx-serialization:" + // see https://github.com/SMILEY4/schema-kenerator for more information + .processKotlinxSerialization() + .generateSwaggerSchema() + .withAutoTitle(TitleType.SIMPLE) + .compileReferencingRoot() + } + } + } + + routing { + + // Create a route for the swagger-ui using the openapi-spec at "/api.json". + // This route will not be included in the spec. + route("swagger") { + swaggerUI("/api.json") + } + // Create a route for the openapi-spec file. + // This route will not be included in the spec. + route("api.json") { + openApiSpec() + } + + // a documented route + get("hello", { + // information about the request + response { + // information about a "200 OK" response + HttpStatusCode.OK to { + // body of the response + body() + } + } + }) { + call.respond(HttpStatusCode.NotImplemented, "...") + } + + } + +} + +@Serializable +private class MyResponseBody( + val name: String, +) \ No newline at end of file diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt index 4a667c0..d785074 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt @@ -1,10 +1,14 @@ package io.github.smiley4.ktorswaggerui.data +import io.github.smiley4.schemakenerator.core.connectSubTypes +import io.github.smiley4.schemakenerator.core.handleNameAnnotation +import io.github.smiley4.schemakenerator.reflection.collectSubTypes import io.github.smiley4.schemakenerator.reflection.processReflection import io.github.smiley4.schemakenerator.swagger.compileReferencingRoot import io.github.smiley4.schemakenerator.swagger.data.CompiledSwaggerSchema import io.github.smiley4.schemakenerator.swagger.data.TitleType import io.github.smiley4.schemakenerator.swagger.generateSwaggerSchema +import io.github.smiley4.schemakenerator.swagger.handleCoreAnnotations import io.github.smiley4.schemakenerator.swagger.withAutoTitle import kotlin.reflect.KType @@ -19,8 +23,12 @@ data class SchemaConfigData( schemas = emptyMap(), generator = { type -> type + .collectSubTypes() .processReflection() + .connectSubTypes() + .handleNameAnnotation() .generateSwaggerSchema() + .handleCoreAnnotations() .withAutoTitle(TitleType.SIMPLE) .compileReferencingRoot() }, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt index b2e69f0..83f8bf6 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiSecurityScheme.kt @@ -55,7 +55,7 @@ class OpenApiSecurityScheme( /** - * information for the oauth flow types supported. + * Information for the oauth flow types supported. * Required for type [AuthType.OAUTH2] */ fun flows(block: OpenIdOAuthFlows.() -> Unit) { @@ -71,7 +71,7 @@ class OpenApiSecurityScheme( /** - * A short description for security scheme. + * A short description of the security scheme. */ var description: String? = null diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt index 1f162e0..27b38c6 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiTag.kt @@ -26,7 +26,7 @@ class OpenApiTag( var externalDocDescription: String? = null /** - *The URL for additional external documentation for this tag. + * The URL for additional external documentation for this tag. */ var externalDocUrl: String? = null diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt index 73227c7..e7d9b2e 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt @@ -62,6 +62,10 @@ class OpenApiHeader { */ var deprecated: Boolean? = null + + /** + * Specifies whether arrays and objects should generate separate parameters for each array item or object property. + */ var explode: Boolean? = null fun build() = OpenApiHeaderData( diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt index 558ff95..98d0902 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiMultipartPart.kt @@ -25,7 +25,7 @@ class OpenApiMultipartPart( ) { /** - * Set a specific content type for this part + * Set a specific content types for this part */ var mediaTypes: Collection = setOf() From 0535620307757815eac20858410cdac69a4e970d Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 9 Jun 2024 13:14:00 +0200 Subject: [PATCH 31/32] cleanup, documentation, set version 3.0.0 --- README.md | 20 +++++++------ gradle.properties | 4 +-- .../smiley4/ktorswaggerui/SwaggerPlugin.kt | 2 +- .../builder/example/ExampleContext.kt | 12 ++++++++ .../builder/example/ExampleContextImpl.kt | 21 +++++++++++++- .../builder/openapi/ComponentsBuilder.kt | 4 +++ .../builder/openapi/ContactBuilder.kt | 4 +++ .../builder/openapi/ContentBuilder.kt | 5 ++++ .../openapi/ExternalDocumentationBuilder.kt | 4 +++ .../builder/openapi/HeaderBuilder.kt | 5 ++++ .../builder/openapi/InfoBuilder.kt | 4 +++ .../builder/openapi/LicenseBuilder.kt | 4 +++ .../builder/openapi/OAuthFlowsBuilder.kt | 4 +++ .../builder/openapi/OpenApiBuilder.kt | 4 +++ .../builder/openapi/OperationBuilder.kt | 4 +++ .../builder/openapi/OperationTagsBuilder.kt | 3 ++ .../builder/openapi/ParameterBuilder.kt | 4 +++ .../builder/openapi/PathBuilder.kt | 4 +++ .../builder/openapi/PathsBuilder.kt | 4 +++ .../builder/openapi/RequestBodyBuilder.kt | 4 +++ .../builder/openapi/ResponseBuilder.kt | 4 +++ .../builder/openapi/ResponsesBuilder.kt | 4 +++ .../openapi/SecurityRequirementsBuilder.kt | 9 ++++-- .../builder/openapi/SecuritySchemesBuilder.kt | 4 +++ .../builder/openapi/ServerBuilder.kt | 4 +++ .../builder/openapi/TagBuilder.kt | 4 +++ .../TagExternalDocumentationBuilder.kt | 4 +++ .../builder/route/RouteCollector.kt | 3 ++ .../builder/schema/SchemaContext.kt | 11 +++++++ .../ktorswaggerui/data/AuthKeyLocation.kt | 3 ++ .../smiley4/ktorswaggerui/data/AuthType.kt | 3 ++ .../smiley4/ktorswaggerui/data/ContactData.kt | 3 ++ .../ktorswaggerui/data/ExampleDescriptor.kt | 17 +++++++++++ .../ktorswaggerui/data/ExternalDocsData.kt | 3 ++ .../smiley4/ktorswaggerui/data/InfoData.kt | 3 ++ .../smiley4/ktorswaggerui/data/LicenseData.kt | 3 ++ .../ktorswaggerui/data/OpenApiBaseBodyData.kt | 11 +++++++ .../ktorswaggerui/data/OpenApiHeaderData.kt | 3 ++ .../data/OpenApiMultipartPartData.kt | 3 ++ .../ktorswaggerui/data/OpenApiRequestData.kt | 3 ++ .../data/OpenApiRequestParameterData.kt | 3 ++ .../ktorswaggerui/data/OpenApiResponseData.kt | 3 ++ .../ktorswaggerui/data/OpenApiRouteData.kt | 3 ++ .../ktorswaggerui/data/OpenIdOAuthFlowData.kt | 3 ++ .../data/OpenIdOAuthFlowsData.kt | 3 ++ .../ktorswaggerui/data/ParameterLocation.kt | 3 ++ .../smiley4/ktorswaggerui/data/PathFilter.kt | 3 ++ .../ktorswaggerui/data/PluginConfigData.kt | 3 ++ .../smiley4/ktorswaggerui/data/PostBuild.kt | 1 + .../ktorswaggerui/data/SchemaConfigData.kt | 4 ++- .../ktorswaggerui/data/SecurityData.kt | 3 ++ .../ktorswaggerui/data/SecuritySchemeData.kt | 3 ++ .../smiley4/ktorswaggerui/data/ServerData.kt | 3 ++ .../ktorswaggerui/data/ServerVariableData.kt | 4 +++ .../ktorswaggerui/data/SpecAssigner.kt | 1 + .../ktorswaggerui/data/SwaggerUIData.kt | 3 ++ .../ktorswaggerui/data/SwaggerUiSort.kt | 7 ++--- .../data/SwaggerUiSyntaxHighlight.kt | 3 ++ .../smiley4/ktorswaggerui/data/TagData.kt | 3 ++ .../ktorswaggerui/data/TagGenerator.kt | 1 + .../smiley4/ktorswaggerui/data/TagsData.kt | 3 ++ .../ktorswaggerui/data/TypeDescriptor.kt | 29 +++++++++++++++++++ .../ktorswaggerui/dsl/config/OpenApiInfo.kt | 3 ++ .../ktorswaggerui/dsl/config/SwaggerUIDsl.kt | 4 ++- .../ktorswaggerui/dsl/routes/OpenApiHeader.kt | 4 ++- .../dsl/routes/OpenApiRequest.kt | 4 ++- .../dsl/routes/OpenApiRequestParameter.kt | 4 ++- .../ktorswaggerui/dsl/routes/OpenApiRoute.kt | 3 ++ .../smiley4/ktorswaggerui/routing/routing.kt | 7 ++++- .../builder/OpenApiBuilderTest.kt | 3 +- .../builder/OperationBuilderTest.kt | 2 +- .../ktorswaggerui/builder/PathsBuilderTest.kt | 2 +- todo.txt | 5 ---- 73 files changed, 320 insertions(+), 35 deletions(-) delete mode 100644 todo.txt diff --git a/README.md b/README.md index 03ca9a9..b65f7d0 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ This library provides a Ktor plugin to document routes, generate an OpenApi Spec ## Features - minimally invasive (no immediate change to existing code required) -- provides swagger-ui with no initial configuration required -- supports most of the [OpenAPI 3.0.3 Specification](https://swagger.io/specification/) -- automatic json-schema generation from arbitrary types/classes for bodies and parameters -- use custom encoder/serializers for examples and json-schemas -- provide custom schemas or a custom schema-builder -- external/custom json-schemas for bodies -- protect Swagger-UI and OpenApi-Spec with custom authentication +- provides swagger-ui and openapi-spec with minimal configuration +- supports most of the [OpenAPI 3.1.0 Specification](https://swagger.io/specification/) +- automatic [json-schema generation](https://github.com/SMILEY4/schema-kenerator) from arbitrary types/classes for bodies and parameters + - supports generics, inheritance, collections, ... + - support for Jackson-annotations and swagger Schema-annotations (optional) + - use with reflection or kotlinx-serialization + - customizable schema-generation ## Documentation @@ -32,8 +32,10 @@ dependencies { ``` -## Example -Full examples can be found in [src/test/examples](https://github.com/SMILEY4/ktor-swagger-ui/tree/develop/src/test/kotlin/io/github/smiley4/ktorswaggerui/examples). +## Examples + +Runnable examples can be found in [ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples](https://github.com/SMILEY4/ktor-swagger-ui/tree/release/ktor-swagger-ui-examples/src/main/kotlin/io/github/smiley4/ktorswaggerui/examples). + ### Configuration diff --git a/gradle.properties b/gradle.properties index 4f4df1a..dad08b3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.code.style=official # project id projectGroupId=io.github.smiley4 projectArtifactIdBase=ktor-swagger-ui -projectVersion=3.0.0-beta3 +projectVersion=3.0.0 # publishing information projectNameBase=Ktor Swagger UI @@ -19,7 +19,7 @@ projectDeveloperUrl=https://github.com/SMILEY4 versionKtor=2.3.11 versionSwaggerUI=5.17.11 versionSwaggerParser=2.1.22 -versionSchemaKenerator=0.4.0 +versionSchemaKenerator=1.0.0 versionKotlinLogging=3.0.5 versionKotest=5.8.0 versionKotlinTest=1.8.21 diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt index cb16b0c..6833c6a 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerPlugin.kt @@ -94,7 +94,7 @@ private fun buildOpenApiSpec(pluginConfig: PluginConfigData, routes: List } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt index 45038d4..5673180 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/example/ExampleContextImpl.kt @@ -9,24 +9,39 @@ import io.github.smiley4.ktorswaggerui.data.SwaggerExampleDescriptor import io.github.smiley4.ktorswaggerui.data.ValueExampleDescriptor import io.swagger.v3.oas.models.examples.Example +/** + * Implementation of an [ExampleContext]. + */ class ExampleContextImpl : ExampleContext { private val rootExamples = mutableMapOf() private val componentExamples = mutableMapOf() - fun addGlobal(config: ExampleConfigData) { + + /** + * Add all global/shared examples from the config that are placed in the components-section of the openapi-spec + */ + fun addShared(config: ExampleConfigData) { config.sharedExamples.forEach { (_, exampleDescriptor) -> val example = generateExample(exampleDescriptor) componentExamples[exampleDescriptor.name] = example } } + + /** + * Collect and add all examples for the given routes + */ fun add(routes: Collection) { collectExampleDescriptors(routes).forEach { exampleDescriptor -> rootExamples[exampleDescriptor] = generateExample(exampleDescriptor) } } + + /** + * Collect all [ExampleDescriptor]s from the given routes + */ private fun collectExampleDescriptors(routes: Collection): List { val descriptors = mutableListOf() routes @@ -53,6 +68,10 @@ class ExampleContextImpl : ExampleContext { return descriptors } + + /** + * Generate a swagger [Example] from the given [ExampleDescriptor] + */ private fun generateExample(exampleDescriptor: ExampleDescriptor): Example { return when (exampleDescriptor) { is ValueExampleDescriptor -> Example().also { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt index 4404018..c39dc3b 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ComponentsBuilder.kt @@ -5,6 +5,10 @@ import io.swagger.v3.oas.models.Components import io.swagger.v3.oas.models.examples.Example import io.swagger.v3.oas.models.media.Schema +/** + * Builds the openapi [Components]-object containing shared reusable schemas and examples. + * See [OpenAPI Specification - Components Object](https://swagger.io/specification/#components-object). + */ class ComponentsBuilder( private val config: PluginConfigData, private val securitySchemesBuilder: SecuritySchemesBuilder diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt index 5fc0e48..b09e227 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContactBuilder.kt @@ -3,6 +3,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.ContactData import io.swagger.v3.oas.models.info.Contact +/** + * Builds the openapi [Contact]-object. Holds Contact information for the exposed API. + * See [OpenAPI Specification - Contact Object](https://swagger.io/specification/#contact-object). + */ class ContactBuilder { fun build(contact: ContactData): Contact = diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt index 64ea8bd..bd9b970 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ContentBuilder.kt @@ -12,6 +12,11 @@ import io.swagger.v3.oas.models.media.MediaType import io.swagger.v3.oas.models.media.Schema import kotlin.collections.set +/** + * Builds the openapi [Content]-object for request and response bodies. + * See [OpenAPI Specification - Request Body Object](https://swagger.io/specification/#request-body-object) + * and [OpenAPI Specification - Response Object](https://swagger.io/specification/#response-object). + */ class ContentBuilder( private val schemaContext: SchemaContext, private val exampleContext: ExampleContext, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt index 49f4fd2..437dd49 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ExternalDocumentationBuilder.kt @@ -3,6 +3,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.ExternalDocsData import io.swagger.v3.oas.models.ExternalDocumentation +/** + * Build the openapi [ExternalDocumentation]-object. Allows referencing an external resource for extended documentation. + * See [OpenAPI Specification - External Documentation Object](https://swagger.io/specification/#external-documentation-object). + */ class ExternalDocumentationBuilder { fun build(externalDocs: ExternalDocsData): ExternalDocumentation = diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt index 63330ea..0ecece3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/HeaderBuilder.kt @@ -2,8 +2,13 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.github.smiley4.ktorswaggerui.data.OpenApiHeaderData +import io.swagger.v3.oas.models.ExternalDocumentation import io.swagger.v3.oas.models.headers.Header +/** + * Build the openapi [Header]-object. + * See [OpenAPI Specification - Header Object](https://swagger.io/specification/#header-object). + */ class HeaderBuilder( private val schemaContext: SchemaContext ) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt index 0fb0e99..e620be4 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/InfoBuilder.kt @@ -3,6 +3,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.InfoData import io.swagger.v3.oas.models.info.Info +/** + * Build the openapi [Info]-object. Holds metadata about the API. + * See [OpenAPI Specification - Info Object](https://swagger.io/specification/#info-object). + */ class InfoBuilder( private val contactBuilder: ContactBuilder, private val licenseBuilder: LicenseBuilder diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt index 056662b..a6d5e1f 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/LicenseBuilder.kt @@ -3,6 +3,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.LicenseData import io.swagger.v3.oas.models.info.License +/** + * Build the openapi [License]-object. Holds license information for the exposed API. + * See [OpenAPI Specification - License Object](https://swagger.io/specification/#license-object). + */ class LicenseBuilder { fun build(license: LicenseData): License = diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt index c8f8c96..4469201 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OAuthFlowsBuilder.kt @@ -6,6 +6,10 @@ import io.swagger.v3.oas.models.security.OAuthFlow import io.swagger.v3.oas.models.security.OAuthFlows import io.swagger.v3.oas.models.security.Scopes +/** + * Build the openapi [OAuthFlows]-object. Holds configuration of the supported OAuth Flows. + * See [OpenAPI Specification - OAuth Flows Object](https://swagger.io/specification/#oauth-flows-object). + */ class OAuthFlowsBuilder { fun build(flows: OpenIdOAuthFlowsData): OAuthFlows { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt index b0830ab..359217a 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OpenApiBuilder.kt @@ -7,6 +7,10 @@ import io.github.smiley4.ktorswaggerui.builder.schema.SchemaContext import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.SpecVersion +/** + * Build the openapi [OpenAPI]-object. Is the root of the openapi document. + * See [OpenAPI Specification - OpenAPI Object](https://swagger.io/specification/#openapi-object). + */ class OpenApiBuilder( private val config: PluginConfigData, private val schemaContext: SchemaContext, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt index 339faed..d793cb7 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationBuilder.kt @@ -3,6 +3,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta import io.swagger.v3.oas.models.Operation +/** + * Build the openapi [Operation]-object. Holds information describing a single API operation on a path. + * See [OpenAPI Specification - Operation Object](https://swagger.io/specification/#operation-object). + */ class OperationBuilder( private val operationTagsBuilder: OperationTagsBuilder, private val parameterBuilder: ParameterBuilder, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt index f941bff..3e8f7bf 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/OperationTagsBuilder.kt @@ -3,6 +3,9 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.PluginConfigData import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta +/** + * Builds the list of tags for a single route. + */ class OperationTagsBuilder( private val config: PluginConfigData ) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt index 221b33d..6f852c6 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ParameterBuilder.kt @@ -6,6 +6,10 @@ import io.github.smiley4.ktorswaggerui.data.OpenApiRequestParameterData import io.github.smiley4.ktorswaggerui.data.ParameterLocation import io.swagger.v3.oas.models.parameters.Parameter +/** + * Build the openapi [Parameter]-object. Holds information describing a single operation (query, path or header) parameter. + * See [OpenAPI Specification - Parameter Object](https://swagger.io/specification/#parameter-object). + */ class ParameterBuilder( private val schemaContext: SchemaContext, private val exampleContext: ExampleContext diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt index a6c5c93..a0328e5 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathBuilder.kt @@ -4,6 +4,10 @@ import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta import io.ktor.http.HttpMethod import io.swagger.v3.oas.models.PathItem +/** + * Build the openapi [PathItem]-object. Holds information describing the operations available on a single path. + * See [OpenAPI Specification - Path Item Object](https://swagger.io/specification/#path-item-object). + */ class PathBuilder( private val operationBuilder: OperationBuilder ) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt index 46a3c6e..f34ad9f 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/PathsBuilder.kt @@ -4,6 +4,10 @@ import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta import io.swagger.v3.oas.models.PathItem import io.swagger.v3.oas.models.Paths +/** + * Build the openapi [Paths]-object. Holds the relative paths to the individual endpoints and their operations. + * See [OpenAPI Specification - Paths Object](https://swagger.io/specification/#paths-object). + */ class PathsBuilder( private val pathBuilder: PathBuilder ) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt index 8795b1b..9e5c82f 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/RequestBodyBuilder.kt @@ -3,6 +3,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.OpenApiBaseBodyData import io.swagger.v3.oas.models.parameters.RequestBody +/** + * Build the openapi [RequestBody]-object. Holds information describing a single request body. + * See [OpenAPI Specification - Request Body Object](https://swagger.io/specification/#request-body-object). + */ class RequestBodyBuilder( private val contentBuilder: ContentBuilder ) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt index e70d69b..27512d4 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponseBuilder.kt @@ -3,6 +3,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.OpenApiResponseData import io.swagger.v3.oas.models.responses.ApiResponse +/** + * Build the openapi [ApiResponse]-objects by status-code. Holds information describing status-codes and responses from an API Operation. + * See [OpenAPI Specification - Response Object](https://swagger.io/specification/#response-object). + */ class ResponseBuilder( private val headerBuilder: HeaderBuilder, private val contentBuilder: ContentBuilder diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt index 3078c2b..957dacc 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ResponsesBuilder.kt @@ -5,6 +5,10 @@ import io.github.smiley4.ktorswaggerui.data.PluginConfigData import io.ktor.http.HttpStatusCode import io.swagger.v3.oas.models.responses.ApiResponses +/** + * Build the openapi [ApiResponses]-object. A container for the expected responses of an operation. + * See [OpenAPI Specification - Responses Object](https://swagger.io/specification/#responses-object). + */ class ResponsesBuilder( private val responseBuilder: ResponseBuilder, private val config: PluginConfigData diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt index db143ec..52d3ec2 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecurityRequirementsBuilder.kt @@ -4,18 +4,21 @@ import io.github.smiley4.ktorswaggerui.builder.route.RouteMeta import io.github.smiley4.ktorswaggerui.data.PluginConfigData import io.swagger.v3.oas.models.security.SecurityRequirement +/** + * Build the openapi [SecurityRequirement]-objects. + * See [OpenAPI Specification - Security Requirement Object](https://swagger.io/specification/#security-requirement-object). + */ class SecurityRequirementsBuilder( private val config: PluginConfigData ) { fun build(route: RouteMeta): List { - val securitySchemes = buildSet { + return buildSet { addAll(route.documentation.securitySchemeNames) if(route.documentation.securitySchemeNames.isEmpty()) { addAll(config.securityConfig.defaultSecuritySchemeNames) } - } - return securitySchemes.map { + }.map { SecurityRequirement().apply { addList(it, emptyList()) } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt index 086aa89..1a354e8 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/SecuritySchemesBuilder.kt @@ -4,6 +4,10 @@ import io.github.smiley4.ktorswaggerui.data.AuthType import io.github.smiley4.ktorswaggerui.data.SecuritySchemeData import io.swagger.v3.oas.models.security.SecurityScheme +/** + * Build the openapi [SecurityScheme]-objects with their names. Holds information defining security schemes that can be used by operations. + * See [OpenAPI Specification - Security Scheme Object](https://swagger.io/specification/#security-scheme-object). + */ class SecuritySchemesBuilder( private val oAuthFlowsBuilder: OAuthFlowsBuilder ) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt index dfe2ba6..09d41fe 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/ServerBuilder.kt @@ -5,6 +5,10 @@ import io.swagger.v3.oas.models.servers.Server import io.swagger.v3.oas.models.servers.ServerVariable import io.swagger.v3.oas.models.servers.ServerVariables +/** + * Build the openapi [Server]-object. Holds information representing a Server. + * See [OpenAPI Specification - Server Object](https://swagger.io/specification/#server-object). + */ class ServerBuilder { fun build(server: ServerData): Server = diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt index d550e6a..c1d9ed4 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagBuilder.kt @@ -3,6 +3,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.github.smiley4.ktorswaggerui.data.TagData import io.swagger.v3.oas.models.tags.Tag +/** + * Build the openapi [Tag]-object. Holds metadata of a single tag. + * See [OpenAPI Specification - Tag Object](https://swagger.io/specification/#tag-object). + */ class TagBuilder( private val tagExternalDocumentationBuilder: TagExternalDocumentationBuilder ) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt index 4e53c08..a388349 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/openapi/TagExternalDocumentationBuilder.kt @@ -2,6 +2,10 @@ package io.github.smiley4.ktorswaggerui.builder.openapi import io.swagger.v3.oas.models.ExternalDocumentation +/** + * Build the openapi [ExternalDocumentation]-object for a tag. + * See [OpenAPI Specification - External Documentation Object](https://swagger.io/specification/#external-documentation-object). + */ class TagExternalDocumentationBuilder { fun build(url: String, description: String): ExternalDocumentation = diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt index a225882..da25fd3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/route/RouteCollector.kt @@ -15,6 +15,9 @@ import io.ktor.server.routing.RouteSelector import io.ktor.server.routing.TrailingSlashRouteSelector import kotlin.reflect.full.isSubclassOf +/** + * Collect all routes from the given application + */ class RouteCollector( private val routeDocumentationMerger: RouteDocumentationMerger ) { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt index 89f2e53..3af0259 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/builder/schema/SchemaContext.kt @@ -3,7 +3,18 @@ package io.github.smiley4.ktorswaggerui.builder.schema import io.github.smiley4.ktorswaggerui.data.TypeDescriptor import io.swagger.v3.oas.models.media.Schema +/** + * Provides schemas for an openapi-spec + */ interface SchemaContext { + + /** + * Get a [Schema] (or a ref to a schema) by its [TypeDescriptor] + */ fun getSchema(typeDescriptor: TypeDescriptor): Schema<*> + + /** + * Get all schemas placed in the components-section of the spec. + */ fun getComponentSection(): Map> } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt index d94ab76..5865f60 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthKeyLocation.kt @@ -2,6 +2,9 @@ package io.github.smiley4.ktorswaggerui.data import io.swagger.v3.oas.models.security.SecurityScheme +/** + * The locations of the API key. + */ enum class AuthKeyLocation(val swaggerType: SecurityScheme.In) { QUERY(SecurityScheme.In.QUERY), HEADER(SecurityScheme.In.HEADER), diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt index 897ae47..e2f053e 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/AuthType.kt @@ -2,6 +2,9 @@ package io.github.smiley4.ktorswaggerui.data import io.swagger.v3.oas.models.security.SecurityScheme +/** + * The type of security schemes + */ enum class AuthType(val swaggerType: SecurityScheme.Type) { API_KEY(SecurityScheme.Type.APIKEY), HTTP(SecurityScheme.Type.HTTP), diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt index 4a5f696..920ecaf 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ContactData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - Contact Object](https://swagger.io/specification/#contact-object). + */ data class ContactData( val name: String?, val url: String?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt index 59d4a4d..dfbe272 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExampleDescriptor.kt @@ -2,10 +2,17 @@ package io.github.smiley4.ktorswaggerui.data import io.swagger.v3.oas.models.examples.Example +/** + * Identifier and description of an example + */ sealed class ExampleDescriptor( val name: String, ) + +/** + * Describes an example as an object. + */ class ValueExampleDescriptor( name: String, val value: Any?, @@ -13,6 +20,16 @@ class ValueExampleDescriptor( val description: String? = null, ) : ExampleDescriptor(name) + +/** + * Describes a reference to a shared example placed in the components section + * @param name the name of the example in the operation + * @param refName the name/id of the example to reference in the components section + */ class RefExampleDescriptor(name: String, val refName: String) : ExampleDescriptor(name) + +/** + * Describes an example as a swagger [Example]-object + */ class SwaggerExampleDescriptor(name: String, val example: Example) : ExampleDescriptor(name) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt index 8ef5db0..ac3d72a 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ExternalDocsData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - External Documentation Object](https://swagger.io/specification/#external-documentation-object). + */ data class ExternalDocsData( val url: String, val description: String?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt index c8ee025..4de183a 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/InfoData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - Info Object](https://swagger.io/specification/#info-object). + */ data class InfoData( val title: String, val version: String?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt index 5ae2002..bea8f45 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/LicenseData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - License Object](https://swagger.io/specification/#license-object). + */ data class LicenseData( val name: String?, val url: String?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt index 25489b3..eb91c3d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiBaseBodyData.kt @@ -2,12 +2,19 @@ package io.github.smiley4.ktorswaggerui.data import io.ktor.http.ContentType +/** + * The common information for request and response bodies. + */ sealed class OpenApiBaseBodyData( val description: String?, val required: Boolean, val mediaTypes: Set, ) + +/** + * Information for a "simple" request or response body. + */ class OpenApiSimpleBodyData( description: String?, required: Boolean, @@ -16,6 +23,10 @@ class OpenApiSimpleBodyData( val examples: List ) : OpenApiBaseBodyData(description, required, mediaTypes) + +/** + * Information for a multipart request or response body. + */ class OpenApiMultipartBodyData( description: String?, required: Boolean, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt index 08eb738..aa4c4f9 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiHeaderData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - Header Object](https://swagger.io/specification/#header-object). + */ data class OpenApiHeaderData( val description: String?, val type: TypeDescriptor?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt index 7b1e7a3..4f2da8c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiMultipartPartData.kt @@ -2,6 +2,9 @@ package io.github.smiley4.ktorswaggerui.data import io.ktor.http.ContentType +/** + * Information about a part of a multipart request or response body. + */ data class OpenApiMultipartPartData( val name: String, val type: TypeDescriptor, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt index 0f6a289..6405acc 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * Information about a request + */ data class OpenApiRequestData( val parameters: List, val body: OpenApiBaseBodyData?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt index 6946d1c..5f7f83c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRequestParameterData.kt @@ -2,6 +2,9 @@ package io.github.smiley4.ktorswaggerui.data import io.swagger.v3.oas.models.parameters.Parameter +/** + * Information about a request (query, path or header) parameter. + */ data class OpenApiRequestParameterData( val name: String, val type: TypeDescriptor, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt index 60a8e8d..6693df3 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiResponseData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * Information about a response for a status-code. + */ data class OpenApiResponseData( val statusCode: String, val description: String?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt index 1add5d6..67364ef 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenApiRouteData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * Information about a single route. + */ data class OpenApiRouteData( val specId: String?, val tags: Set, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt index 3072dec..a9563d2 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - OAuth Flow Object](https://swagger.io/specification/#oauth-flow-object). + */ data class OpenIdOAuthFlowData( val authorizationUrl: String? = null, val tokenUrl: String? = null, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt index 44acedf..639820a 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/OpenIdOAuthFlowsData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - OAuth Flows Object](https://swagger.io/specification/#oauth-flows-object). + */ data class OpenIdOAuthFlowsData( val implicit: OpenIdOAuthFlowData?, val password: OpenIdOAuthFlowData?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt index 5577540..7052726 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ParameterLocation.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * Locations for request parameters. + */ enum class ParameterLocation { QUERY, HEADER, PATH } diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt index 12b9552..6eaa326 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PathFilter.kt @@ -2,4 +2,7 @@ package io.github.smiley4.ktorswaggerui.data import io.ktor.http.HttpMethod +/** + * Filters paths to determine which to include (return 'true') in the spec and which to hide (return 'true'). + */ typealias PathFilter = (method: HttpMethod, url: List) -> Boolean diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt index 2221b98..1ba537c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PluginConfigData.kt @@ -3,6 +3,9 @@ package io.github.smiley4.ktorswaggerui.data import io.github.smiley4.ktorswaggerui.dsl.config.PluginConfigDsl import kotlin.reflect.KClass +/** + * Complete plugin configuration + */ data class PluginConfigData( val specAssigner: SpecAssigner, val pathFilter: PathFilter, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PostBuild.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PostBuild.kt index 3865e46..5934a97 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PostBuild.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/PostBuild.kt @@ -3,6 +3,7 @@ package io.github.smiley4.ktorswaggerui.data import io.swagger.v3.oas.models.OpenAPI /** + * Function executed after building the openapi-spec. * @author yuefeng in 2024/3/25. */ typealias PostBuild = (openApi: OpenAPI) -> Unit diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt index d785074..807eb0c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SchemaConfigData.kt @@ -12,7 +12,9 @@ import io.github.smiley4.schemakenerator.swagger.handleCoreAnnotations import io.github.smiley4.schemakenerator.swagger.withAutoTitle import kotlin.reflect.KType - +/** + * Common configuration for schemas. + */ data class SchemaConfigData( val schemas: Map, val generator: (type: KType) -> CompiledSwaggerSchema, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt index db09ff8..ee66378 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecurityData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * Common security configuration information. + */ data class SecurityData( val defaultUnauthorizedResponse: OpenApiResponseData?, val defaultSecuritySchemeNames: Set, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt index 8537866..ddb0505 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SecuritySchemeData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - Security Scheme Object](https://swagger.io/specification/#security-scheme-object). + */ data class SecuritySchemeData( val schemeName: String, val type: AuthType?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt index 8fec541..7e9e334 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - Server Object](https://swagger.io/specification/#server-object). + */ data class ServerData( val url: String, val description: String?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerVariableData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerVariableData.kt index 415c98e..c61ecc2 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerVariableData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/ServerVariableData.kt @@ -1,5 +1,9 @@ package io.github.smiley4.ktorswaggerui.data + +/** + * See [OpenAPI Specification - Server Variable Object](https://swagger.io/specification/#server-variable-object). + */ data class ServerVariableData( val name: String, val enum: Set, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigner.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigner.kt index 28a5e84..f87522d 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigner.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SpecAssigner.kt @@ -1,6 +1,7 @@ package io.github.smiley4.ktorswaggerui.data /** + * Assigns (unassigned) routes to api-specs. * url - the parts of the route-url split at all `/`. * tags - the tags assigned to the route */ diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt index cf306c8..c57dfb2 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUIData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * Common configuration for the swagger-ui. + */ data class SwaggerUIData( val validatorUrl: String?, val displayOperationId: Boolean, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt index 84a40b1..8dccbc5 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSort.kt @@ -1,18 +1,17 @@ package io.github.smiley4.ktorswaggerui.data +/** + * Determines the order to sort the operations in the swagger-ui. + */ enum class SwaggerUiSort(val value: String) { /** * The order returned by the server unchanged */ NONE("undefined"), - - /** * sort by paths alphanumerically */ ALPHANUMERICALLY("alpha"), - - /** * sort by HTTP method */ diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt index f31ffcc..07c17db 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/SwaggerUiSyntaxHighlight.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * The syntax-highlight theme to use for code-blocks in swagger-ui. + */ enum class SwaggerUiSyntaxHighlight(val value: String) { DISABLED("disabled"), AGATE("agate"), diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt index 29b30b6..60b438f 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * See [OpenAPI Specification - Tag Object](https://swagger.io/specification/#tag-object). + */ data class TagData( val name: String, val description: String?, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt index 6ba542b..0a6372b 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagGenerator.kt @@ -1,6 +1,7 @@ package io.github.smiley4.ktorswaggerui.data /** + * Generates additional tags for routes. * url - the parts of the route-url split at all `/`. * return a collection of tags. "Null"-entries will be ignored. */ diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt index abce2eb..d54479e 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TagsData.kt @@ -1,5 +1,8 @@ package io.github.smiley4.ktorswaggerui.data +/** + * Common configuration for tags. + */ data class TagsData( val tags: List, val generator: TagGenerator, diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt index 2b9a028..69d4b4c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/data/TypeDescriptor.kt @@ -4,12 +4,41 @@ import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType import kotlin.reflect.typeOf +/** + * Describes and identifies types and schemas. + */ sealed interface TypeDescriptor + + +/** + * Describes a type from a swagger [Schema] + */ class SwaggerTypeDescriptor(val schema: Schema<*>) : TypeDescriptor + + +/** + * Describes a type from a kotlin [KType] + */ class KTypeDescriptor(val type: KType) : TypeDescriptor + +/** + * Describes an array of types. + */ class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor + +/** + * Describes an object matching any of the given types. + */ class AnyOfTypeDescriptor(val types: List) : TypeDescriptor + +/** + * Describes an empty type/schema. + */ class EmptyTypeDescriptor : TypeDescriptor + +/** + * Describes a reference to a schema in the component section. + */ class RefTypeDescriptor(val schemaId: String) : TypeDescriptor inline fun type() = KTypeDescriptor(typeOf()) diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt index 83923bd..f78facd 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/OpenApiInfo.kt @@ -7,6 +7,9 @@ import io.github.smiley4.ktorswaggerui.data.InfoData import io.github.smiley4.ktorswaggerui.data.LicenseData import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker +/** + * Basic information for the exposed API. + */ @OpenApiDslMarker class OpenApiInfo { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt index 53c54c4..1aa4fe0 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/config/SwaggerUIDsl.kt @@ -6,7 +6,9 @@ import io.github.smiley4.ktorswaggerui.data.DataUtils.mergeDefault import io.github.smiley4.ktorswaggerui.data.SwaggerUIData import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker - +/** + * Configuration for the swagger-ui + */ @OpenApiDslMarker class SwaggerUIDsl { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt index e7d9b2e..099214b 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiHeader.kt @@ -9,7 +9,9 @@ import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType import kotlin.reflect.typeOf - +/** + * Describes a single header. + */ @OpenApiDslMarker class OpenApiHeader { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt index d078edd..21a34a1 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequest.kt @@ -10,7 +10,9 @@ import io.swagger.v3.oas.models.media.Schema import kotlin.reflect.KType import kotlin.reflect.typeOf - +/** + * Describes a single request. + */ @OpenApiDslMarker class OpenApiRequest { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt index 93f822e..41ce904 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRequestParameter.kt @@ -10,7 +10,9 @@ import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker import io.swagger.v3.oas.models.examples.Example import io.swagger.v3.oas.models.parameters.Parameter - +/** + * Describes a single request parameter. + */ @OpenApiDslMarker class OpenApiRequestParameter( /** diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt index 2b2def4..c85084c 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/dsl/routes/OpenApiRoute.kt @@ -7,6 +7,9 @@ import io.github.smiley4.ktorswaggerui.dsl.OpenApiDslMarker import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiExternalDocs import io.github.smiley4.ktorswaggerui.dsl.config.OpenApiServer +/** + * Describes a single route including request and responses. + */ @OpenApiDslMarker class OpenApiRoute { diff --git a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt index 3e4bf2a..ecff6b2 100644 --- a/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt +++ b/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/routing/routing.kt @@ -13,7 +13,9 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* - +/** + * Registers the route for serving an openapi-spec. When multiple specs are configured, the id of the one to serve has to be provided. + */ fun Route.openApiSpec(specId: String = PluginConfigDsl.DEFAULT_SPEC_ID) { route({ hidden = true }) { get { @@ -22,6 +24,9 @@ fun Route.openApiSpec(specId: String = PluginConfigDsl.DEFAULT_SPEC_ID) { } } +/** + * Registers the route for serving all swagger-ui resources. The path to the openapi-spec file to use has to be given. + */ fun Route.swaggerUI(apiUrl: String) { route({ hidden = true }) { get { diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt index b805c6d..b4a3fa3 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OpenApiBuilderTest.kt @@ -33,7 +33,6 @@ import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.maps.shouldHaveSize -import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.swagger.v3.oas.models.OpenAPI @@ -118,7 +117,7 @@ class OpenApiBuilderTest : StringSpec({ private fun exampleContext(routes: List, pluginConfig: PluginConfigDsl): ExampleContext { val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) return ExampleContextImpl().also { - it.addGlobal(pluginConfigData.exampleConfig) + it.addShared(pluginConfigData.exampleConfig) it.add(routes) } } diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt index f35e072..775c562 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/OperationBuilderTest.kt @@ -965,7 +965,7 @@ class OperationBuilderTest : StringSpec({ private fun exampleContext(routes: List, pluginConfig: PluginConfigDsl = defaultPluginConfig): ExampleContext { val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) return ExampleContextImpl().also { - it.addGlobal(pluginConfigData.exampleConfig) + it.addShared(pluginConfigData.exampleConfig) it.add(routes) } } diff --git a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt index f55f767..ff5534d 100644 --- a/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt +++ b/ktor-swagger-ui/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/PathsBuilderTest.kt @@ -96,7 +96,7 @@ class PathsBuilderTest : StringSpec({ private fun exampleContext(routes: List, pluginConfig: PluginConfigDsl): ExampleContext { val pluginConfigData = pluginConfig.build(PluginConfigData.DEFAULT) return ExampleContextImpl().also { - it.addGlobal(pluginConfigData.exampleConfig) + it.addShared(pluginConfigData.exampleConfig) it.add(routes) } } diff --git a/todo.txt b/todo.txt deleted file mode 100644 index 11c5860..0000000 --- a/todo.txt +++ /dev/null @@ -1,5 +0,0 @@ -todo: - -- keep up to date with schema-kenerator features - -- wiki / documentation \ No newline at end of file From 9062daeac8f3c92a57e241402ab33b6bd139858a Mon Sep 17 00:00:00 2001 From: Lukas Ruegner Date: Sun, 9 Jun 2024 13:31:48 +0200 Subject: [PATCH 32/32] fix gradle build --- build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 1d6ed8a..6557668 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,3 +5,7 @@ plugins { id("io.gitlab.arturbosch.detekt") version "1.23.0" apply false id("com.vanniktech.maven.publish") version "0.28.0" apply false } + +repositories { + mavenCentral() +} \ No newline at end of file