Skip to content

OpenAPI仕様の生成

必要な依存関係: io.ktor:ktor-server-routing-openapi

コード例: openapi-spec-gen , openapi-spec-gen-maven

Ktorは、1つ以上のドキュメントソースから実行時にOpenAPI仕様を構築するためのサポートを提供しています。

この機能は、以下を通じて利用可能です:

  • OpenAPIコンパイラ拡張(Ktor Gradleプラグインに含まれています):コンパイル時にルーティングコードを分析し、実行時にOpenAPIメタデータを登録するKotlinコードを生成します。
  • ルーティングアノテーション実行時API:実行中のアプリケーション内のルートにOpenAPIメタデータを直接アタッチします。

これらの一方または両方を使用し、OpenAPIおよびSwaggerUIプラグインと組み合わせることで、インタラクティブなAPIドキュメントを提供できます。

OpenAPI Gradle拡張にはKotlin 2.2.20が必要です。他のバージョンを使用すると、コンパイルエラーが発生する可能性があります。

依存関係の追加

  • OpenAPIメタデータの生成を有効にするには、プロジェクトにKtorコンパイラプラグインを適用します。

    kotlin
    plugins {
        id("io.ktor.plugin") version "3.3.3"
    }
    groovy
    plugins {
        id 'io.ktor.plugin' version "3.3.3"
    }

    Gradleとは異なり、MavenはKtorコンパイラプラグインの組み込み統合を提供していません。OpenAPI仕様の生成を有効にするには、コンパイラプラグインを手動で設定する必要があります。

    1. Ktor Mavenプラグインを適用します(アプリケーションの実行およびパッケージ化に必要です):

      xml
      <build>
          <plugins>
              <plugin>
                  <groupId>io.ktor</groupId>
                  <artifactId>ktor-maven-plugin</artifactId>
                  <version>3.3.3</version>
              </plugin>
          </plugins>
      </build>
    2. コンパイラプラグインはJARファイルとして利用可能である必要があります。以下の設定を追加して、自動的にダウンロードし、安定した場所にコピーするようにします:

      xml
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-dependency-plugin</artifactId>
          <version>3.9.0</version>
          <executions>
              <execution>
                  <id>copy-ktor-compiler-plugin</id>
                  <phase>generate-sources</phase>
                  <goals>
                      <goal>copy</goal>
                  </goals>
                  <configuration>
                      <artifactItems>
                          <artifactItem>
                              <groupId>io.ktor</groupId>
                              <artifactId>ktor-compiler-plugin</artifactId>
                              <version>3.3.3</version>
                              <outputDirectory>${project.build.directory}/kotlin-plugins</outputDirectory>
                              <destFileName>ktor-compiler-plugin.jar</destFileName>
                          </artifactItem>
                      </artifactItems>
                  </configuration>
              </execution>
          </executions>
      </plugin>
    3. Kotlinコンパイラを設定します:

      xml
      <plugin>
          <groupId>org.jetbrains.kotlin</groupId>
          <artifactId>kotlin-maven-plugin</artifactId>
          <version>2.2.20</version>
      
          <configuration>
              <jvmTarget>21</jvmTarget>
      
              <compilerPlugins>
                  <plugin>kotlinx-serialization</plugin>
              </compilerPlugins>
      
              <args>
                  <arg>-Xplugin=${project.build.directory}/kotlin-plugins/ktor-compiler-plugin.jar</arg>
      
                  <arg>-P</arg>
                  <arg>plugin:io.ktor.ktor-compiler-plugin:openApiEnabled=true</arg>
      
                  <arg>-P</arg>
                  <arg>plugin:io.ktor.ktor-compiler-plugin:openApiCodeInference=true</arg>
      
                  <arg>-P</arg>
                  <arg>plugin:io.ktor.ktor-compiler-plugin:openApiOnlyCommented=false</arg>
              </args>
          </configuration>
      
          <dependencies>
              <dependency>
                  <groupId>io.ktor</groupId>
                  <artifactId>ktor-compiler-plugin</artifactId>
                  <version>3.3.3</version>
              </dependency>
              <dependency>
                  <groupId>org.jetbrains.kotlin</groupId>
                  <artifactId>kotlin-maven-serialization</artifactId>
                  <version>${kotlin_version}</version>
              </dependency>
          </dependencies>
          <executions>
              <execution>
                  <id>compile</id>
                  <phase>compile</phase>
                  <goals>
                      <goal>compile</goal>
                  </goals>
              </execution>
              <execution>
                  <id>test-compile</id>
                  <phase>test-compile</phase>
                  <goals>
                      <goal>test-compile</goal>
                  </goals>
              </execution>
          </executions>
      </plugin>
  • 実行時ルートアノテーションを使用するには、ビルドスクリプトにktor-server-routing-openapiアーティファクトを追加します:

    Kotlin
    Groovy
    XML

OpenAPIコンパイラ拡張の設定

OpenAPIコンパイラ拡張は、コンパイル時にルーティングメタデータを収集する方法を制御します。 これは、最終的なOpenAPIドキュメント自体を定義するものではありません。

コンパイル中、プラグインはOpenAPI実行時APIを使用して、ルーティング宣言、コードパターン、およびコメントから派生したメタデータを登録するKotlinコードを生成します。

APIのタイトル、バージョン、サーバー、セキュリティスキーム、詳細なスキーマなどの一般的なOpenAPI情報は、仕様が生成されるときに実行時に提供されます。

コンパイラプラグイン拡張を設定するには、build.gradle.ktsファイルのktor拡張内のopenApi {}ブロックを使用します:

kotlin
ktor {
    openApi {
        enabled = true
        codeInferenceEnabled = true
        onlyCommented = false
    }
}

設定オプション

enabled
<code>enabled</code> OpenAPIルートアノテーションコードの生成を有効または無効にします。デフォルトはfalseです。
codeInferenceEnabled
<code>codeInferenceEnabled</code> コンパイラがルーティングコードからOpenAPIメタデータを推論しようとするかどうかを制御します。デフォルトはtrueです。 推論によって誤った結果が生じる場合や、アノテーションを使用して明示的にメタデータを定義したい場合は、このオプションを無効にしてください。 詳細については、コード推論ルールを参照してください。
onlyCommented
<code>onlyCommented</code> メタデータの生成を、コメントアノテーションを含むルートのみに制限します。デフォルトはfalseで、@ignoreで明示的にマークされたものを除き、すべてのルーティング呼び出しが処理されることを意味します。

ルーティング構造の分析

Ktorコンパイラプラグインは、サーバーのルーティングDSLを分析して、APIの構造的な形状を決定します。この分析はルート宣言のみに基づいて行われ、ルートハンドラーの内容は検査されません。

ルーティングAPIツリーのセレクターから、以下が自動的に推論されます:

  • マージされたパス(例:/api/v1/users/{id})。
  • HTTPメソッド(GETPOSTなど)。
  • パスパラメータ。
kotlin
routing {
    route("/api/v1") {
        get("/users") { }
        get("/users/{id}") { }
        post("/users") { }
    }
}

リクエストパラメータ、ボディ、レスポンスはルートラムダの内部で処理されるため、コンパイラはルーティング構造だけから完全なOpenAPI記述を推論することはできません。生成されるメタデータを充実させるために、Ktorはアノテーションと、一般的なリクエスト処理パターンに基づく自動推論をサポートしています。

コード推論

コード推論が有効な場合、コンパイラプラグインは一般的なKtorの使用パターンを認識し、同等の実行時アノテーションを自動的に生成します。

次の表は、サポートされている推論ルールをまとめたものです:

ルール説明入力出力(annotateスコープから)
リクエストボディContentNegotiationの読み取りからリクエストボディスキーマを提供call.receive<T>()requestBody { schema = jsonSchema<T>() }
レスポンスボディContentNegotiationの書き込みからレスポンスボディスキーマを提供call.respond<T>()responses { HttpStatusCode.OK { schema = jsonSchema<T>() } }
レスポンスヘッダーレスポンスにカスタムヘッダーを含めるcall.response.header("X-Foo", "Bar")responses { HttpStatusCode.OK { headers { header("X-Foo", "Bar") } } }
パスパラメータパスパラメータの参照を検出call.parameters["id"]parameters { path("id") }
クエリパラメータクエリパラメータの参照を検出call.queryParameters["name"]parameters { query("name") }
リクエストヘッダーリクエストヘッダーの参照を検出call.request.headers["X-Foo"]parameters { header("X-Foo") }
Resource APIルートResourcesルーティングAPIの呼び出し構造を推論call.get<List> { /**/ }; @Resource("/list") class List(val name: String)parameters { query("name") }

推論は可能な限り抽出された関数を追跡し、典型的なリクエストおよびレスポンスフローに対して一貫したドキュメントを生成しようとします。

特定のエンドポイントの推論を無効にする

特定のエンドポイントに対して推論が正しくないメタデータを生成する場合、ignoreマーカーを追加することで除外できます:

kotlin
// ignore!
get("/comments") {
    // ...
}

ルートへのアノテーション追加

仕様を充実させるために、Ktorはルートにアノテーションを付ける2つの方法をサポートしています:

どちらのアプローチも使用でき、両方を組み合わせることも可能です。

コメントベースのルートアノテーション

コメントベースのアノテーションは、コードから推論できないメタデータを提供し、既存のルートとシームレスに統合されます。

メタデータは、行の先頭にキーワードを置き、その後にコロン(:)と値を続けることで定義されます。

コメントはルート宣言に直接アタッチできます:

kotlin
/**
 * Get a single user by ID.
 *
 * Path: id [ULong] the ID of the user
 *
 * Responses:
 *   – 400 The ID parameter is malformatted or missing.
 *   – 404 The user for the given ID does not exist.
 *   – 200 [User] The user found with the given ID.
 */
get("/{id}") {
    val id = call.parameters["id"]?.toULongOrNull()
        ?: return@get call.respond(HttpStatusCode.BadRequest)
    val user = list.find { it.id == id }
        ?: return@get call.respond(HttpStatusCode.NotFound)
    call.respond(user)
}

フォーマットルール

  • キーワードは行の先頭にある必要があります。
  • コロン(:)がキーワードと値を区切ります。
  • 複数形(例:TagsResponses)はグループ化された定義を可能にします。
  • 単数形(例:TagResponse)もサポートされています。
  • トップレベルの箇条書き(-)はオプションであり、フォーマットにのみ影響します。

以下のバリアントは同等です:

kotlin
/**
 * Tag: widgets
 * 
 * Tags:
 *   - widgets
 * 
 * - Tags:
 *  - widgets
 */

サポートされているコメントフィールド

タグフォーマット説明
TagTag: nameエンドポイントをタグでグループ化します
PathPath: [Type] name descriptionパスパラメータ
QueryQuery: [Type] name descriptionクエリパラメータ
HeaderHeader: [Type] name descriptionヘッダーパラメータ
CookieCookie: [Type] name descriptionクッキーパラメータ
BodyBody: contentType [Type] descriptionリクエストボディ
ResponseResponse: code contentType [Type] descriptionレスポンス定義
DeprecatedDeprecated: reasonエンドポイントを非推奨としてマークします
DescriptionDescription: text詳細な説明
SecuritySecurity: schemeセキュリティ要件
ExternalDocsExternalDocs: href外部ドキュメントへのリンク
Experimental

実行時ルートアノテーション

動的ルーティング、インターセプター、または条件付きロジックを使用している場合など、コンパイル時の分析が不十分なケースでは、.describe {}拡張関数を使用して実行時にルートにOpenAPIオペレーションメタデータを直接アタッチできます。

アノテーションが付けられた各ルートは、生成されたOpenAPI仕様において単一のHTTP操作(例:GET /users)を表すOpenAPI Operationオブジェクトを定義します。 メタデータは実行時にルーティングツリーにアタッチされ、OpenAPIおよびSwagger UIプラグインによって消費されます。

.describe {} DSLはOpenAPI仕様に直接マップされます。プロパティ名と構造は、パラメータ、リクエストボディ、レスポンス、セキュリティ要件、サーバー、コールバック、仕様拡張(x-*)を含む、Operationオブジェクトに対して定義されたフィールドに対応しています。

実行時ルートアノテーションAPIは実験的であり、@OptIn(ExperimentalKtorApi::class)を使用してオプトインする必要があります:

kotlin
                @OptIn(ExperimentalKtorApi::class)
                get("/users") {
                    val query = call.parameters["q"]
                    val result = if (query != null) {
                        list.filter {it.name.contains(query, ignoreCase = true)  }
                    } else {
                        list
                    }

                    call.respond(result)
                }.describe {
                    summary = "Get users"
                    description = "Retrieves a list of users."
                    parameters {
                        query("q") {
                            description = "An encoded query"
                            required = false
                        }
                    }
                    responses {
                        HttpStatusCode.OK {
                            description = "A list of users"
                            schema = jsonSchema<List<User>>()
                        }
                        HttpStatusCode.BadRequest {
                            description = "Invalid query"
                            ContentType.Text.Plain()
                        }
                    }
                }

利用可能なフィールドの完全なリストについては、OpenAPI仕様を参照してください。

実行時アノテーションは、コンパイラによって生成されたメタデータおよびコメントベースのメタデータとマージされます。 同じOpenAPIフィールドが複数のソースで定義されている場合、実行時アノテーションによって提供される値が優先されます。

OpenAPI仕様からルートを隠す

生成されるOpenAPIドキュメントからルートとその子を除外するには、Route.hide()関数を使用します:

kotlin
@OptIn(ExperimentalKtorApi::class)
get("/routes") {
    // ....
}.hide()

これは、OpenAPI仕様自体を生成するために使用されるルートを含む、公開すべきでない内部用、管理用、または診断用のエンドポイントに役立ちます。

OpenAPIおよびSwagger UIプラグインは自動的に.hide()を呼び出すため、それらのルートは結果のドキュメントから除外されます。

スキーマ推論

Ktorは、OpenAPI仕様を構築する際に、リクエストおよびレスポンス型のJSONスキーマを自動的に生成します。デフォルトでは、データクラス上のkotlinx-serialization記述子を使用した型参照からスキーマが推論されます。これにより、追加の労力をかけずに、ほとんどの一般的なデータモデルをドキュメント化できます。

アノテーションによるスキーマのカスタマイズ

データクラスに@JsonSchemaアノテーションを追加することで、自動生成されたJSONスキーマフィールドをオーバーライドできます。これにより、説明の追加やフィールドの必須化などを行うことができます。

kotlin
@JsonSchema.Description("Represents a news article")
data class Article(
    val title: String,
    val content: String
)

リフレクションベースのスキーマ推論の使用

kotlinx-serializationの代わりにJacksonやGsonを使用しているプロジェクトでは、リフレクションベースのスキーマ推論を使用できます。そのためには、OpenAPIまたはSwaggerUIプラグインのRoutingソースにあるschemaInferenceフィールドを設定します。

kotlin
openAPI("docs") {
    outputPath = "docs/routes"
    info = OpenApiInfo("Books API from routes", "1.0.0")
    source = OpenApiDocSource.Routing(
        contentType = ContentType.Application.Json,
        schemaInference = ReflectionJsonSchemaInference.Default,
    )
}

リフレクションの動作のカスタマイズ

カスタムのSchemaReflectionAdapterを提供して、直接サポートされていないアノテーションや命名規則を処理できます。

SchemaReflectionAdapterReflectionJsonSchemaInferenceのフィールドであり、プロパティ名、無視されるフィールド、null許容ルールなどのデフォルトの動作をオーバーライドできます。

たとえば、Gsonの@SerializedNameアノテーションをサポートするように動作をカスタマイズできます:

kotlin
ReflectionJsonSchemaInference(object : SchemaReflectionAdapter {
    override fun getName(type: KType): String? {
        return (type.classifier as? KClass<*>)?.let {
            findAnnotations(SerializedName::class)?.value ?: it.simpleName
        }
    }
})

カスタムスキーマの提供

自動スキーマ推論やアノテーションが不十分な場合は、JsonSchemaクラスを使用して手動でJSONスキーマを構築できます。

これにより、型、フォーマット、ネストされた構造、またはタプル形式の配列定義用のprefixItemsなどの高度な構成要素を含む、サポートされているすべてのスキーマプロパティを明示的に定義できます。

利用可能なプロパティの完全なリストについては、JsonSchema APIドキュメントを参照してください。

仕様の生成と提供

OpenAPI仕様は、実行時ルートアノテーションとコンパイラプラグインによって生成されたメタデータから実行時に組み立てられます。

以下の方法で仕様を公開できます:

仕様の組み立てと提供

実行時に完全なOpenAPIドキュメントを組み立てるには、OpenApiDocインスタンスを作成し、仕様に含めるべきルートを提供します。

ドキュメントは、ルーティングツリーからのコンパイラ生成メタデータと実行時ルートアノテーションから組み立てられます。結果のOpenApiDocインスタンスは、常にアプリケーションの現在の状態を反映します。

通常、ルートハンドラーからドキュメントを構築し、それを直接レスポンスとして返します:

kotlin

        get("/docs.json") {
            val doc = OpenApiDoc(info = OpenApiInfo("My API", "1.0")) + call.application.routingRoot.descendants()
            call.respond(doc)

この例では、OpenAPIドキュメントはContentNegotiationプラグインを使用してシリアライズされます。これには、JSONシリアライザー(例:kotlinx.serialization)がインストールされていることを前提としています。

追加のビルドや生成ステップは必要ありません。ルートやアノテーションへの変更は、次回仕様がリクエストされたときに自動的に反映されます。

シリアライズを明示的に行いたい場合や、ContentNegotiationに依存したくない場合は、手動でドキュメントをエンコードしてJSONをレスポンスとして返すことができます:

kotlin
call.respondText(
  Json.encodeToString(docs),
  ContentType.Application.Json
)

インタラクティブなドキュメントの提供

インタラクティブなUIを通じてOpenAPI仕様を公開するには、OpenAPIおよびSwagger UIプラグインを使用します。

両方のプラグインは実行時に仕様を組み立て、ルーティングツリーからメタデータを直接読み取ることができます。 これらはドキュメントのレンダリング方法が異なります:

  • OpenAPIプラグインはサーバー上でドキュメントをレンダリングし、事前生成されたHTMLを提供します。
  • Swagger UIプラグインはOpenAPI仕様をJSONまたはYAMLとして提供し、Swagger UIを使用してブラウザでUIをレンダリングします。
kotlin
// OpenAPI UIを提供
openAPI("/openApi")

// Swagger UIを提供
swaggerUI("/swaggerUI") {
    info = OpenApiInfo("My API", "1.0")
    source = OpenApiDocSource.Routing(
        contentType = ContentType.Application.Json,
    )
}

メタデータの優先順位

最終的なOpenAPI仕様は、複数のソースから提供されたメタデータをマージすることによって実行時に組み立てられます。

以下のソースが順番に適用されます:

  1. コンパイラ生成メタデータ(以下を含む):
  2. コメントベースのルートアノテーション
  3. 実行時ルートアノテーション

同じOpenAPIフィールドが複数のソースで定義されている場合、実行時アノテーションによって提供される値は、コメントベースのアノテーションやコンパイラ生成メタデータよりも優先されます。

明示的にオーバーライドされていないメタデータは保持され、最終的なドキュメントにマージされます。