Skip to content

Commit

Permalink
Merge branch 'develop' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
SMILEY4 committed Jul 11, 2024
2 parents 63296fd + 076b9f0 commit 5de8a9b
Show file tree
Hide file tree
Showing 19 changed files with 193 additions and 33 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ install(SwaggerUI) {
}
}
```
```kotlin
routing {
// Create a route for the openapi-spec file.
route("api.json") {
openApiSpec()
}
// Create a route for the swagger-ui using the openapi-spec at "/api.json".
route("swagger") {
swaggerUI("/api.json")
}
}
```

### Routes

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ kotlin.code.style=official
# project id
projectGroupId=io.github.smiley4
projectArtifactIdBase=ktor-swagger-ui
projectVersion=3.1.0
projectVersion=3.2.0

# publishing information
projectNameBase=Ktor Swagger UI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private fun Application.myModule() {
scheme = AuthScheme.BASIC
}
// if no other security scheme is specified for a route, the one with this name is used instead
defaultSecuritySchemeNames = setOf("MySecurityScheme")
defaultSecuritySchemeNames("MySecurityScheme")
// if no other response is documented for "401 Unauthorized", this information is used instead
defaultUnauthorizedResponse {
description = "Username or password is invalid"
Expand Down Expand Up @@ -88,7 +88,7 @@ private fun Application.myModule() {
// 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")
securitySchemeNames("MySecurityScheme")
}) {
call.respondText("Hello World!")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private fun Application.myModule() {
// information about possible responses
response {
// information about a "200 OK" response
HttpStatusCode.OK to {
code(HttpStatusCode.OK) {
// a description of the response
description = "successful request - always returns 'Hello World!'"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private fun Application.myModule() {
defaultUnauthorizedResponse {
description = "Username or password is invalid"
}
defaultSecuritySchemeNames = setOf("MySecurityScheme")
defaultSecuritySchemeNames("MySecurityScheme")
securityScheme("MySecurityScheme") {
type = AuthType.HTTP
scheme = AuthScheme.BASIC
Expand Down Expand Up @@ -157,12 +157,12 @@ private fun Application.myModule() {
operationId = "hello"
summary = "hello world route"
description = "A Hello-World route as an example."
tags = setOf("hello", "example")
tags("hello", "example")
specId = PluginConfigDsl.DEFAULT_SPEC_ID
deprecated = false
hidden = false
protected = false
securitySchemeNames = emptyList()
securitySchemeNames(emptyList())
externalDocs {
url = "example.com/hello"
description = "external documentation of 'hello'-route"
Expand All @@ -179,7 +179,7 @@ private fun Application.myModule() {
body<Unit>()
}
response {
HttpStatusCode.OK to {
code(HttpStatusCode.OK) {
description = "successful request - always returns 'Hello World!'"
header<String>("x-random") {
description = "A header with some random number"
Expand All @@ -189,7 +189,7 @@ private fun Application.myModule() {
}
body<Greeting> {
description = "the greeting object with the name of the person to greet."
mediaTypes = setOf(ContentType.Application.Json)
mediaTypes(ContentType.Application.Json)
required = true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private fun Application.myModule() {
// information about the request
response {
// information about a "200 OK" response
HttpStatusCode.OK to {
code(HttpStatusCode.OK) {
// body of the response
body<MyResponseBody>()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private fun Application.myModule() {
post("single", {
request {
body<File> {
mediaTypes = setOf(
mediaTypes(
ContentType.Image.PNG,
ContentType.Image.JPEG,
ContentType.Image.SVG,
Expand All @@ -63,16 +63,16 @@ private fun Application.myModule() {
post("multipart", {
request {
multipartBody {
mediaTypes = setOf(ContentType.MultiPart.FormData)
mediaTypes(ContentType.MultiPart.FormData)
part<File>("first-image",) {
mediaTypes = setOf(
mediaTypes(
ContentType.Image.PNG,
ContentType.Image.JPEG,
ContentType.Image.SVG
)
}
part<File>("second-image") {
mediaTypes = setOf(
mediaTypes(
ContentType.Image.PNG,
ContentType.Image.JPEG,
ContentType.Image.SVG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private fun Application.myModule() {
}
}
response {
HttpStatusCode.OK to {
code(HttpStatusCode.OK) {
body<List<Pet>> {
description = "the list of available pets"
example("Pet List") {
Expand Down Expand Up @@ -130,7 +130,7 @@ private fun Application.myModule() {
}
}
response {
HttpStatusCode.OK to {
code(HttpStatusCode.OK) {
body<Pet> {
description = "the created pet"
example("Bird") {
Expand Down Expand Up @@ -175,7 +175,7 @@ private fun Application.myModule() {
}
}
response {
HttpStatusCode.OK to {
code(HttpStatusCode.OK) {
body<Pet>{
description = "the pet with the given id"
example("Bird") {
Expand All @@ -194,7 +194,7 @@ private fun Application.myModule() {
}
}
}
HttpStatusCode.NotFound to {
code(HttpStatusCode.NotFound) {
description = "the pet with the given id was not found"
}
default {
Expand All @@ -221,10 +221,10 @@ private fun Application.myModule() {
}
}
response {
HttpStatusCode.NoContent to {
code(HttpStatusCode.NoContent) {
description = "the pet was successfully deleted"
}
HttpStatusCode.NotFound to {
code(HttpStatusCode.NotFound) {
description = "the pet with the given id was not found"
}
default {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ private fun Application.myModule() {
// information the possible responses
response {
// document the "200 OK"-response
HttpStatusCode.OK to {
code(HttpStatusCode.OK) {
description = "Calculation was performed successfully."
// specify the schema of the response-body and some additional information
body<CalculationResult> {
description = "the result of an operation together with the original request"
}
}
// document the "422 UnprocessableEntity"-response
HttpStatusCode.UnprocessableEntity to {
code(HttpStatusCode.UnprocessableEntity) {
description = "The requested calculation could not be performed, e.g. due to division by zero."
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ class ExampleContextImpl(private val encoder: ExampleEncoder?) : ExampleContext
val example = generateExample(exampleDescriptor, null)
componentExamples[exampleDescriptor.name] = example
}
config.securityExamples.forEach { exampleDescriptor ->
val example = generateExample(exampleDescriptor, null)
rootExamples[exampleDescriptor] = example
config.securityExamples?.let {
it.examples.forEach { exampleDescriptor ->
val example = generateExample(exampleDescriptor, it.type)
rootExamples[exampleDescriptor] = example
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ typealias ExampleEncoder = (type: TypeDescriptor?, example: Any?) -> Any?

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

companion object {
val DEFAULT = ExampleConfigData(
sharedExamples = emptyMap(),
securityExamples = emptyList(),
securityExamples = null,
exampleEncoder = null
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,26 @@ class ExampleConfig {
val sharedExamples = mutableMapOf<String, ExampleDescriptor>()
var exampleEncoder: ExampleEncoder? = null


/**
* Add a shared example that can be referenced by all routes.
* The name of the example has to be unique among all shared examples and acts as its id.
* @param example the example data.
*/
fun example(example: ExampleDescriptor) {
sharedExamples[example.name] = example
}

/**
* Add a shared example that can be referenced by all routes by the given name.
* The provided name has to be unique among all shared examples and acts as its id.
*/
fun example(name: String, example: Example) = example(SwaggerExampleDescriptor(name, example))

/**
* Add a shared example that can be referenced by all routes by the given name.
* The provided name has to be unique among all shared examples and acts as its id.
*/
fun example(name: String, example: ValueExampleDescriptorDsl.() -> Unit) = example(
ValueExampleDescriptorDsl()
.apply(example)
Expand All @@ -33,6 +47,10 @@ class ExampleConfig {
}
)


/**
* Specify a custom encoder for example objects
*/
fun encoder(exampleEncoder: ExampleEncoder) {
this.exampleEncoder = exampleEncoder
}
Expand All @@ -41,10 +59,10 @@ class ExampleConfig {
sharedExamples = sharedExamples,
securityExamples = securityConfig.defaultUnauthorizedResponse?.body?.let {
when (it) {
is OpenApiSimpleBodyData -> it.examples
is OpenApiMultipartBodyData -> emptyList()
is OpenApiSimpleBodyData -> it
is OpenApiMultipartBodyData -> null
}
} ?: emptyList(),
},
exampleEncoder = exampleEncoder
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ class OpenApiSecurity {
*/
var defaultSecuritySchemeNames: Collection<String>? = SecurityData.DEFAULT.defaultSecuritySchemeNames

/**
* Set the names of the security schemes available for use for the protected paths
*/
fun defaultSecuritySchemeNames(names: Collection<String>) {
this.defaultSecuritySchemeNames = names
}

/**
* Set the names of the security schemes available for use for the protected paths
*/
fun defaultSecuritySchemeNames(vararg names: String) {
this.defaultSecuritySchemeNames = names.toList()
}

/**
* Defines security schemes that can be used by operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,74 @@ import kotlin.reflect.typeOf
@OpenApiDslMarker
class SchemaConfig {

/**
* The json-schema generator for all schemas. See https://github.com/SMILEY4/schema-kenerator/wiki for more information.
*/
var generator: (type: KType) -> CompiledSwaggerSchema = SchemaConfigData.DEFAULT.generator

val schemas = mutableMapOf<String, TypeDescriptor>()
private val schemas = mutableMapOf<String, TypeDescriptor>()

val overwrite = mutableMapOf<KType, TypeDescriptor>()
private val overwrite = mutableMapOf<KType, TypeDescriptor>()


/**
* Overwrite the given [type] with the given [replacement].
* When the type is specified as the type of a schema, the replacement is used instead.
* This only works for "root"-types and not types of e.g. nested fields.
*/
fun overwrite(type: KType, replacement: TypeDescriptor) {
overwrite[type] = replacement
}

/**
* Overwrite the given type [T] with the given [replacement].
* When the type is specified as the type of a schema, the replacement is used instead.
* This only works for "root"-types and not types of e.g. nested fields.
*/
inline fun <reified T> overwrite(replacement: TypeDescriptor) = overwrite(typeOf<T>(), replacement)

/**
* Overwrite the given type [T] with the given [replacement].
* When the type is specified as the type of a schema, the replacement is used instead.
* This only works for "root"-types and not types of e.g. nested fields.
*/
inline fun <reified T> overwrite(replacement: Schema<*>) = overwrite(typeOf<T>(), SwaggerTypeDescriptor(replacement))

/**
* Overwrite the given type [T] with the given [replacement].
* When the type is specified as the type of a schema, the replacement is used instead.
* This only works for "root"-types and not types of e.g. nested fields.
*/
inline fun <reified T> overwrite(replacement: KType) = overwrite(typeOf<T>(), KTypeDescriptor(replacement))

/**
* Overwrite the given type [T] with the given replacement [R].
* When the type is specified as the type of a schema, the replacement is used instead.
* This only works for "root"-types and not types of e.g. nested fields.
*/
inline fun <reified T, reified R> overwrite() = overwrite(typeOf<T>(), KTypeDescriptor(typeOf<R>()))


/**
* Add a shared schema that can be referenced by all routes by the given id.
*/
fun schema(schemaId: String, descriptor: TypeDescriptor) {
schemas[schemaId] = descriptor
}

/**
* Add a shared schema that can be referenced by all routes by the given id.
*/
fun schema(schemaId: String, schema: Schema<*>) = schema(schemaId, SwaggerTypeDescriptor(schema))

/**
* Add a shared schema that can be referenced by all routes by the given id.
*/
fun schema(schemaId: String, schema: KType) = schema(schemaId, KTypeDescriptor(schema))

/**
* Add a shared schema that can be referenced by all routes by the given id.
*/
inline fun <reified T> schema(schemaId: String) = schema(schemaId, KTypeDescriptor(typeOf<T>()))

fun build(securityConfig: SecurityData) = SchemaConfigData(
Expand Down
Loading

0 comments on commit 5de8a9b

Please sign in to comment.