Skip to content

Commit 3c9da2d

Browse files
committed
add a code sample project, update docs and add todos
1 parent 30cd002 commit 3c9da2d

File tree

10 files changed

+255
-60
lines changed

10 files changed

+255
-60
lines changed

codeSnippets/build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ buildscript {
1616
}
1717
repositories {
1818
mavenLocal()
19-
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
19+
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
2020
}
2121

2222
configurations.classpath {
@@ -70,14 +70,14 @@ allprojects {
7070
kotlin_version = rootProject.properties['kotlin_snapshot_version']
7171
repositories {
7272
mavenLocal()
73-
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
73+
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
7474
}
7575
}
7676

7777
repositories {
7878
mavenCentral()
7979
maven {
80-
url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev"
80+
url = uri("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
8181
}
8282
}
8383
}
@@ -89,7 +89,7 @@ def ktorRepositoryDir = file("$buildDir/m2")
8989
if (ktorRepositoryDir.exists()) {
9090
allprojects {
9191
repositories {
92-
maven { url ktorRepositoryDir.absolutePath }
92+
maven { url = uri(ktorRepositoryDir.absolutePath) }
9393
}
9494
}
9595
} else {

codeSnippets/gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ kotlin.code.style = official
44
kotlin.native.binary.memoryModel = experimental
55
# gradle configuration
66
org.gradle.configureondemand = false
7+
kotlin.mpp.applyDefaultHierarchyTemplate=false
8+
org.gradle.java.installations.auto-download=false
79
# versions
810
kotlin_version = 2.2.20
911
ktor_version = 3.4.0-eap-1477

codeSnippets/settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ module("snippets", "tutorial-server-websockets")
162162
module("snippets", "tutorial-server-docker-compose")
163163
module("snippets", "htmx-integration")
164164
module("snippets", "server-http-request-lifecycle")
165+
module("snippets", "openapi-spec-gen")
165166

166167
if(!System.getProperty("os.name").startsWith("Windows")) {
167168
module("snippets", "embedded-server-native")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# OpenAPI documentation
2+
3+
A sample Ktor project showing how to build OpenAPI documentation using routing annotations and the compiler
4+
extension of the Ktor Gradle plugin.
5+
6+
> This sample is a part of the [`codeSnippets`](../../README.md) Gradle project.
7+
8+
## Generate the OpenAPI specification
9+
10+
```bash
11+
./gradlew :openapi-spec-gen:buildOpenApi
12+
```
13+
14+
The generated OpenAPI specification is located in `build/ktor/openapi/generated.json`.
15+
16+
## Run the application
17+
18+
To run the application, execute the following command in the repository's root directory:
19+
20+
```bash
21+
./gradlew :openapi-spec-gen:run
22+
```
23+
24+
Navigate to [http://0.0.0.0:8080/docs](http://0.0.0.0:8080/docs) to access the OpenAPI documentation.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
val ktor_version = "3.4.0-eap-1505" //TODO: replace with project version
2+
val kotlin_version: String by project
3+
val logback_version: String by project
4+
5+
plugins {
6+
application
7+
kotlin("jvm")
8+
id("io.ktor.plugin") version "3.3.3"
9+
}
10+
11+
application {
12+
mainClass = "io.ktor.server.netty.EngineMain"
13+
}
14+
15+
repositories {
16+
mavenCentral()
17+
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") }
18+
}
19+
20+
ktor {
21+
@OptIn(io.ktor.plugin.OpenApiPreview::class)
22+
openApi {
23+
title = "OpenAPI example"
24+
version = "2.1"
25+
summary = "This is a sample API"
26+
}
27+
}
28+
29+
// Builds OpenAPI specification automatically
30+
tasks.processResources {
31+
dependsOn("buildOpenApi")
32+
}
33+
34+
dependencies {
35+
implementation("io.ktor:ktor-server-core:$ktor_version")
36+
implementation("io.ktor:ktor-server-routing-annotate:${ktor_version}")
37+
implementation("io.ktor:ktor-server-openapi:${ktor_version}")
38+
implementation("io.ktor:ktor-server-netty:$ktor_version")
39+
implementation("ch.qos.logback:logback-classic:$logback_version")
40+
testImplementation("io.ktor:ktor-server-test-host-jvm:$ktor_version")
41+
testImplementation("org.jetbrains.kotlin:kotlin-test")
42+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package com.example
2+
3+
import io.ktor.annotate.annotate
4+
import io.ktor.http.*
5+
import io.ktor.openapi.jsonSchema
6+
import io.ktor.server.application.*
7+
import io.ktor.server.plugins.openapi.*
8+
import io.ktor.server.request.*
9+
import io.ktor.server.response.*
10+
import io.ktor.server.routing.*
11+
import kotlinx.serialization.Serializable
12+
13+
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
14+
15+
fun Application.module() {
16+
routing {
17+
/**
18+
* Hello, world.
19+
*
20+
* @response 200 text/plaintext Hello
21+
*/
22+
get("/hello") {
23+
call.respondText("Hello")
24+
}
25+
26+
/**
27+
* Data back-end.
28+
*/
29+
route("/data") {
30+
31+
/**
32+
* Users endpoint.
33+
*/
34+
route("/users") {
35+
val list = mutableListOf<User>()
36+
37+
/**
38+
* Get a single user by ID.
39+
*
40+
* @path id [ULong] the ID of the user
41+
* @response 400 The ID parameter is malformatted or missing.
42+
* @response 404 The user for the given ID does not exist.
43+
* @response 200 [User] The user found with the given ID.
44+
*/
45+
get("/{id}") {
46+
val id = call.parameters["id"]?.toULongOrNull()
47+
?: return@get call.respond(HttpStatusCode.BadRequest)
48+
val user = list.find { it.id == id }
49+
?: return@get call.respond(HttpStatusCode.NotFound)
50+
call.respond(user)
51+
}
52+
53+
/**
54+
* Get a list of users.
55+
*
56+
* @response 200 The list of items.
57+
*/
58+
get("/users") {
59+
val query = call.parameters["q"]
60+
val result = if (query != null) {
61+
list.filter {it.name.contains(query, ignoreCase = true) }
62+
} else {
63+
list
64+
}
65+
66+
call.respond(result)
67+
}.annotate {
68+
summary = "Get users"
69+
description = "Retrieves a list of users."
70+
parameters {
71+
query("q") {
72+
description = "An encoded query"
73+
required = false
74+
}
75+
}
76+
responses {
77+
HttpStatusCode.OK {
78+
description = "A list of users"
79+
schema = jsonSchema<List<User>>()
80+
}
81+
HttpStatusCode.BadRequest {
82+
description = "Invalid query"
83+
ContentType.Text.Plain()
84+
}
85+
}
86+
}
87+
88+
/**
89+
* Save a new user.
90+
*
91+
* @response 204 The new user was saved.
92+
*/
93+
post {
94+
list += call.receive<User>()
95+
call.respond(HttpStatusCode.NoContent)
96+
}
97+
98+
/**
99+
* Delete the user with the given ID.
100+
*
101+
* @path id [ULong] the ID of the user to remove
102+
* @response 400 The ID parameter is malformatted or missing.
103+
* @response 404 The user for the given ID does not exist.
104+
* @response 204 The user was deleted.
105+
*/
106+
delete("/{id}") {
107+
val id = call.parameters["id"]?.toULongOrNull()
108+
?: return@delete call.respond(HttpStatusCode.BadRequest)
109+
if (!list.removeIf { it.id == id })
110+
return@delete call.respond(HttpStatusCode.NotFound)
111+
call.respond(HttpStatusCode.NoContent)
112+
}
113+
114+
}
115+
}
116+
openAPI(
117+
path = "/docs",
118+
swaggerFile = "openapi/generated.json"
119+
)
120+
}
121+
}
122+
123+
@Serializable
124+
data class User(val id: ULong, val name: String)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ktor {
2+
deployment {
3+
port = 8080
4+
}
5+
application {
6+
modules = [ com.example.ApplicationKt.module ]
7+
}
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
5+
</encoder>
6+
</appender>
7+
<root level="trace">
8+
<appender-ref ref="STDOUT"/>
9+
</root>
10+
<logger name="org.eclipse.jetty" level="INFO"/>
11+
<logger name="io.netty" level="INFO"/>
12+
</configuration>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.example
2+
3+
import io.ktor.client.request.*
4+
import io.ktor.client.statement.*
5+
import io.ktor.http.*
6+
import io.ktor.server.application.*
7+
import io.ktor.server.testing.*
8+
import kotlin.test.*
9+
10+
class ApplicationTest {
11+
@Test
12+
fun testRoot() = testApplication {
13+
}
14+
}

0 commit comments

Comments
 (0)