CLOVER🍀

That was when it all began.

Quarkus × SmallRye OpenAPIで、ビルド時にOpenAPIの定義ファイルを作成する

これは、なにをしたくて書いたもの?

OpenAPIでのAPI定義を書こうとした時に、どうするのがいいのかなと思ったりしていたのですが。

Quarkusの場合、ビルド時にOpenAPIの定義ファイルを作成できそうなのでこちらを試してみようかなということで。

Quarkus × SmallRye OpenAPI

QuarkusのOpenAPIに関するドキュメントは、こちら。

Using OpenAPI and Swagger UI - Quarkus

QuarkusではEclipse MicroProfile OpenAPIを使用していて、その実装にはSmallRye OpenAPIを使用しています。

GitHub - eclipse/microprofile-open-api: Microprofile open api

GitHub - smallrye/smallrye-open-api: SmallRye implementation of Eclipse MicroProfile OpenAPI

現時点のQuarkus(2.14.3.Final)が使用しているSmallRye OpenAPIのバージョンは2.3.1で、Eclipse MicroProfile OpenAPIのバージョンは
2.0.1になります。

https://github.com/smallrye/smallrye-open-api/blob/2.3.1/pom.xml#L25

Eclipse MicroProfile OpenAPIの仕様書はこちら。

https://github.com/eclipse/microprofile-open-api/blob/2.0.1/spec/src/main/asciidoc/microprofile-openapi-spec.adoc

SmallRye OpenAPIが使用するデフォルトのOpenAPIのバージョンは、3.0.3です。

https://github.com/smallrye/smallrye-open-api/blob/2.3.1/core/src/main/java/io/smallrye/openapi/api/constants/OpenApiConstants.java#L110

OpenAPI 3.0.3の仕様書はこちら。

OpenAPI Specification v3.0.3 | Introduction, Definitions, & More

それで、今回の目的のビルド時のOpenAPI定義ファイルの出力ですが、quarkus.smallrye-openapi.store-schema-directoryという
プロパティ(またはQUARKUS_SMALLRYE_OPENAPI_STORE_SCHEMA_DIRECTORY環境変数)を使用すれば実現できそうです。

Using OpenAPI and Swagger UI / Configuration Reference / Configuration property / quarkus.smallrye-openapi.store-schema-directory

このあたりですね。

https://github.com/quarkusio/quarkus/blob/2.14.3.Final/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java#L1070-L1078

こちらを試してみましょう。書籍をお題にして、簡単なREST APIを書いてOpenAPI定義ファイルを生成してみたいと思います。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.5 2022-10-18
OpenJDK Runtime Environment (build 17.0.5+8-Ubuntu-2ubuntu120.04)
OpenJDK 64-Bit Server VM (build 17.0.5+8-Ubuntu-2ubuntu120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.5, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-135-generic", arch: "amd64", family: "unix"

Quarkusアプリケーションを作成する

まずは、Quarkusアプリケーションを作成します。SmallRye OpenAPI Extensionを加えつつ、RESTEasy Reactiveに関するExtensionを
加えてプロジェクトを作成。

$ mvn io.quarkus.platform:quarkus-maven-plugin:2.14.3.Final:create \
    -DprojectGroupId=org.littlewings \
    -DprojectArtifactId=openapi-definition-file-generate \
    -DprojectVersion=0.0.1-SNAPSHOT \
    -Dextensions='resteasy-reactive,resteasy-reactive-jackson,quarkus-smallrye-openapi' \
    -DnoCode

選択されたExtensionなど。

[INFO] -----------
[INFO] selected extensions:
- io.quarkus:quarkus-resteasy-reactive
- io.quarkus:quarkus-smallrye-openapi
- io.quarkus:quarkus-resteasy-reactive-jackson

[INFO]
applying codestarts...
[INFO] 📚  java
🔨  maven
📦  quarkus
📝  config-properties
🔧  dockerfiles
🔧  maven-wrapper
[INFO]
-----------

プロジェクト内へ移動。

$ cd openapi-definition-file-generate

Maven依存関係など。

  <properties>
    <compiler-plugin.version>3.8.1</compiler-plugin.version>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
    <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
    <quarkus.platform.version>2.14.3.Final</quarkus.platform.version>
    <skipITs>true</skipITs>
    <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>${quarkus.platform.group-id}</groupId>
        <artifactId>${quarkus.platform.artifact-id}</artifactId>
        <version>${quarkus.platform.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-reactive</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-openapi</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-arc</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

ソースコードを書いていきましょう。

お題は書籍にしたので、エンティティ相当のクラスを作っておきます。

src/main/java/org/littlewings/quarkus/openapi/Book.java

package org.littlewings.quarkus.openapi;

import java.util.List;

public class Book {
    String isbn;
    String title;
    int price;
    List<String> tags;

    // getter/setterは省略
}

リクエストとレスポンスにはこのクラスは直接使わず、専用にクラスを作成することにします。

リクエスト用のクラス。

src/main/java/org/littlewings/quarkus/openapi/BookRequest.java

package org.littlewings.quarkus.openapi;

import java.util.List;

import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Schema(name = "Book Create Request")
public class BookRequest {
    @Schema(required = true, example = "978-4621303252")
    String isbn;
    @Schema(required = true, example = "Effective Java 第3版")
    String title;
    @Schema(required = true, example = "4400")
    int price;
    @Schema(example = "[\"java\", \"programming\"]")
    List<String> tags;

    public Book toBook() {
        Book book = new Book();
        book.setIsbn(getIsbn());
        book.setTitle(getTitle());
        book.setPrice(getPrice());
        book.setTags(getTags());
        return book;
    }

    // getter/setterは省略
}

レスポンス用のクラス。

src/main/java/org/littlewings/quarkus/openapi/BookResponse.java

package org.littlewings.quarkus.openapi;

import java.util.List;

import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Schema(name = "Book Response")
public class BookResponse {
    @Schema(example = "978-4621303252")
     String isbn;
     @Schema(example = "Effective Java 第3版")
     String title;
     @Schema(example = "4400")
     int price;
     @Schema(example = "[\"java\", \"programming\"]")
     List<String> tags;

    public static BookResponse fromBook(Book book) {
        BookResponse response = new BookResponse();
        response.setIsbn(book.getIsbn());
        response.setTitle(book.getTitle());
        response.setPrice(book.getPrice());
        response.setTags(book.getTags());
        return response;
    }

    // getter/setterは省略
}

見るとわかりますが、Eclipse MicroProfile OpenAPIのアノテーションを使ってスキーマ定義や例を記述しています。

MicroProfile OpenAPI Specification / Documentation Mechanisms / Annotations

また、それぞれにエンティティ相当のクラスと変換するメソッドを入れています。

JAX-RSリソースクラス。

src/main/java/org/littlewings/quarkus/openapi/BooksResource.java

package org.littlewings.quarkus.openapi;

import java.util.Comparator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestResponse;

@Path("books")
@Tag(name = "book", description = "Book operations")
public class BooksResource {
    ConcurrentMap<String, Book> store = new ConcurrentHashMap<>();

    @GET
    @Path("{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Get book by isbn", operationId = "getBook")
    public Uni<BookResponse> get(@RestPath String isbn) {
        return Uni
                .createFrom()
                .item(store.get(isbn))
                .onItem()
                .ifNull()
                .failWith(() -> new NotFoundException(String.format("Not found: %s", isbn)))
                .onItem()
                .transform(BookResponse::fromBook);
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "List books", operationId = "listBooks")
    public Multi<BookResponse> list() {
        System.out.println(Thread.currentThread().getName());
        return Multi
                .createFrom()
                .items(store.values().stream().sorted(Comparator.comparing(Book::getPrice).reversed()))
                .onItem()
                .transform(BookResponse::fromBook);
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Operation(summary = "Create book", operationId = "createBook")
    public Uni<RestResponse<Void>> create(@Context UriInfo uriInfo, BookRequest bookRequest) {
        store.put(bookRequest.getIsbn(), bookRequest.toBook());

        return Uni
                .createFrom()
                .item(RestResponse.created(UriBuilder.fromPath(uriInfo.getPath()).path(bookRequest.getIsbn()).build()));
    }

    @DELETE
    @Path("{isbn}")
    @Operation(summary = "Delete book by isbn", operationId = "deleteBook")
    public Uni<RestResponse<Void>> delete(@RestPath String isbn) {
        store.remove(isbn);

        return Uni.createFrom().item(RestResponse.noContent());
    }
}

こちらもEclipse MicroProfile OpenAPIのアノテーションを使用しています。

@Operationの情報はある程度Quarkusでも自動生成できそうですが、今回は明示的に付与することにしました。

Using OpenAPI and Swagger UI / Auto-generation of Operation Id

application.propertiesでは、アプリケーションレベルのプロパティ設定を少ししておきました。

src/main/resources/application.properties

quarkus.smallrye-openapi.info-title=OpenAPI Definition Example
quarkus.smallrye-openapi.info-version=0.0.1

これでアプリケーションとしての準備は完了です。

開発モードで動作確認してみましょう。

$ mvn compile quarkus:dev

データの登録。

$ curl -i -XPOST -H 'Content-Type: application/json' localhost:8080/books -d '{"isbn": "978-4621303252", "title": "Effective Java 第3版", "price": 4400, "tags": ["java", "programming"]}'
HTTP/1.1 201 Created
Location: http://localhost:8080/books/978-4621303252
content-length: 0


$ curl -i -XPOST -H 'Content-Type: application/json' localhost:8080/books -d '{"isbn": "978-1782169970", "title": "Infinispan Data Grid Platform Definitive Guide", "price": 5242, "tags": ["in-memory-data-grid"]}'
HTTP/1.1 201 Created
Location: http://localhost:8080/books/978-1782169970
content-length: 0


$ curl -i -XPOST -H 'Content-Type: application/json' localhost:8080/books -d '{"isbn": "978-4798161488", "title": "MySQL徹底入門 第4版 MySQL 8.0対応", "price": 4180, "tags": ["mysql", "database"]}'
HTTP/1.1 201 Created
Location: http://localhost:8080/books/978-4798161488
content-length: 0


$ curl -i -XPOST -H 'Content-Type: application/json' localhost:8080/books -d '{"isbn": "978-1098116743", "title": "Terraform: Up & Running; Writing Infrastructure As Code", "price": 7404, "tags": ["terraform", "infra-structure-as-code"]}'
HTTP/1.1 201 Created
Location: http://localhost:8080/books/978-1098116743
content-length: 0

複数件のデータの取得。

$ curl -i localhost:8080/books
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
transfer-encoding: chunked

[{"isbn":"978-1098116743","title":"Terraform: Up & Running; Writing Infrastructure As Code","price":7404,"tags":["terraform","infra-structure-as-code"]},{"isbn":"978-1782169970","title":"Infinispan Data Grid Platform Definitive Guide","price":5242,"tags":["in-memory-data-grid"]},{"isbn":"978-4621303252","title":"Effective Java 第3版","price":4400,"tags":["java","programming"]},{"isbn":"978-4798161488","title":"MySQL徹底入門 第4版 MySQL 8.0対応","price":4180,"tags":["mysql","database"]}]


$ curl -s localhost:8080/books | jq
[
  {
    "isbn": "978-1098116743",
    "title": "Terraform: Up & Running; Writing Infrastructure As Code",
    "price": 7404,
    "tags": [
      "terraform",
      "infra-structure-as-code"
    ]
  },
  {
    "isbn": "978-1782169970",
    "title": "Infinispan Data Grid Platform Definitive Guide",
    "price": 5242,
    "tags": [
      "in-memory-data-grid"
    ]
  },
  {
    "isbn": "978-4621303252",
    "title": "Effective Java 第3版",
    "price": 4400,
    "tags": [
      "java",
      "programming"
    ]
  },
  {
    "isbn": "978-4798161488",
    "title": "MySQL徹底入門 第4版 MySQL 8.0対応",
    "price": 4180,
    "tags": [
      "mysql",
      "database"
    ]
  }
]

単一データの取得。

$ curl -i localhost:8080/books/978-4798161488
HTTP/1.1 200 OK
content-length: 118
Content-Type: application/json;charset=UTF-8

{"isbn":"978-4798161488","title":"MySQL徹底入門 第4版 MySQL 8.0対応","price":4180,"tags":["mysql","database"]}

データの削除。

$ curl -i -XDELETE localhost:8080/books/978-4798161488
HTTP/1.1 204 No Content

削除確認。

$ curl -i localhost:8080/books/978-4798161488
HTTP/1.1 404 Not Found
Content-Type: application/json
content-length: 0

OKですね。

Quarkusが生成するOpenAPIとSwagger UIも見ておきましょう。

$ curl localhost:8080/q/openapi
---
openapi: 3.0.3
info:
  title: OpenAPI Definition Example
  version: 0.0.1
tags:
- name: book
  description: Book operations
paths:
  /books:
    get:
      tags:
      - book
      summary: List books
      operationId: listBooks
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Book Response'
    post:
      tags:
      - book
      summary: Create book
      operationId: createBook
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Book Create Request'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
  /books/{isbn}:
    get:
      tags:
      - book
      summary: Get book by isbn
      operationId: getBook
      parameters:
      - name: isbn
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Book Response'
    delete:
      tags:
      - book
      summary: Delete book by isbn
      operationId: deleteBook
      parameters:
      - name: isbn
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
components:
  schemas:
    Book Create Request:
      required:
      - isbn
      - title
      - price
      type: object
      properties:
        isbn:
          type: string
          example: 978-4621303252
        title:
          type: string
          example: Effective Java 第3版
        price:
          format: int32
          type: integer
          example: 4400
        tags:
          type: array
          items:
            type: string
          example:
          - java
          - programming
    Book Response:
      type: object
      properties:
        isbn:
          type: string
          example: 978-4621303252
        title:
          type: string
          example: Effective Java 第3版
        price:
          format: int32
          type: integer
          example: 4400
        tags:
          type: array
          items:
            type: string
          example:
          - java
          - programming

Swagger UIは、http://localhost:8080/q/swagger-ui/にアクセスして確認します。

こちらもOKですね。

ビルド時にOpenAPIの定義ファイルを生成する

では、quarkus.smallrye-openapi.store-schema-directoryプロパティを使ってみます。

ここで、quarkus.smallrye-openapi.store-schema-directoryプロパティの説明を見てみます。

If set, the generated OpenAPI schema documents will be stored here on build. Both openapi.json and openapi.yaml will be stored here if this is set.

Using OpenAPI and Swagger UI / Configuration Reference / Configuration property / quarkus.smallrye-openapi.store-schema-directory

このプロパティを設定すると、指定したディレクトリにopenapi.jsonとopenapi.yamlの2つのファイルをビルド時に作成するようです。

まずはapplication.propertiesにquarkus.smallrye-openapi.store-schema-directoryプロパティを追加してみます。

src/main/resources/application.properties

quarkus.smallrye-openapi.info-title=OpenAPI Definition Example
quarkus.smallrye-openapi.info-version=0.0.1

quarkus.smallrye-openapi.store-schema-directory=openapi-definition

openapi-definitionディレクトリに出力するようにしてみましょう。

ビルド。

$ mvn compile

これでは出力されないようです。

$ ll openapi-definition
ls: 'openapi-definition' にアクセスできません: そのようなファイルやディレクトリはありません

パッケージングしてみます。

$ mvn package

すると、こんなログが現れました。

[INFO] [io.quarkus.smallrye.openapi] OpenAPI JSON saved: /path/to/openapi-definition/openapi.json
[INFO] [io.quarkus.smallrye.openapi] OpenAPI YAML saved: /path/to/openapi-definition/openapi.yaml

今度は生成されたようです。

$ ll openapi-definition
合計 20
drwxrwxr-x 2 xxxxx xxxxx 4096 12月 16 00:08 ./
drwxrwxr-x 7 xxxxx xxxxx 4096 12月 16 00:08 ../
-rw-rw-r-- 1 xxxxx xxxxx 4190 12月 16 00:08 openapi.json
-rw-rw-r-- 1 xxxxx xxxxx 2749 12月 16 00:08 openapi.yaml

確認。

openapi-definition/openapi.yaml

---
openapi: 3.0.3
info:
  title: OpenAPI Definition Example
  version: 0.0.1
servers:
- url: http://localhost:8080
  description: Auto generated value
- url: http://0.0.0.0:8080
  description: Auto generated value
tags:
- name: book
  description: Book operations
paths:
  /books:
    get:
      tags:
      - book
      summary: List books
      operationId: listBooks
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Book Response'
    post:
      tags:
      - book
      summary: Create book
      operationId: createBook
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Book Create Request'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
  /books/{isbn}:
    get:
      tags:
      - book
      summary: Get book by isbn
      operationId: getBook
      parameters:
      - name: isbn
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Book Response'
    delete:
      tags:
      - book
      summary: Delete book by isbn
      operationId: deleteBook
      parameters:
      - name: isbn
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
components:
  schemas:
    Book Create Request:
      required:
      - isbn
      - title
      - price
      type: object
      properties:
        isbn:
          type: string
          example: 978-4621303252
        title:
          type: string
          example: Effective Java 第3版
        price:
          format: int32
          type: integer
          example: 4400
        tags:
          type: array
          items:
            type: string
          example:
          - java
          - programming
    Book Response:
      type: object
      properties:
        isbn:
          type: string
          example: 978-4621303252
        title:
          type: string
          example: Effective Java 第3版
        price:
          format: int32
          type: integer
          example: 4400
        tags:
          type: array
          items:
            type: string
          example:
          - java
          - programming

openapi-definition/openapi.json

{
  "openapi" : "3.0.3",
  "info" : {
    "title" : "OpenAPI Definition Example",
    "version" : "0.0.1"
  },
  "servers" : [ {
    "url" : "http://localhost:8080",
    "description" : "Auto generated value"
  }, {
    "url" : "http://0.0.0.0:8080",
    "description" : "Auto generated value"
  } ],
  "tags" : [ {
    "name" : "book",
    "description" : "Book operations"
  } ],
  "paths" : {
    "/books" : {
      "get" : {
        "tags" : [ "book" ],
        "summary" : "List books",
        "operationId" : "listBooks",
        "responses" : {
          "200" : {
            "description" : "OK",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/Book Response"
                  }
                }
              }
            }
          }
        }
      },
      "post" : {
        "tags" : [ "book" ],
        "summary" : "Create book",
        "operationId" : "createBook",
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/Book Create Request"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "OK",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "object"
                }
              }
            }
          }
        }
      }
    },
    "/books/{isbn}" : {
      "get" : {
        "tags" : [ "book" ],
        "summary" : "Get book by isbn",
        "operationId" : "getBook",
        "parameters" : [ {
          "name" : "isbn",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "OK",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Book Response"
                }
              }
            }
          }
        }
      },
      "delete" : {
        "tags" : [ "book" ],
        "summary" : "Delete book by isbn",
        "operationId" : "deleteBook",
        "parameters" : [ {
          "name" : "isbn",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "OK",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components" : {
    "schemas" : {
      "Book Create Request" : {
        "required" : [ "isbn", "title", "price" ],
        "type" : "object",
        "properties" : {
          "isbn" : {
            "type" : "string",
            "example" : "978-4621303252"
          },
          "title" : {
            "type" : "string",
            "example" : "Effective Java 第3版"
          },
          "price" : {
            "format" : "int32",
            "type" : "integer",
            "example" : 4400
          },
          "tags" : {
            "type" : "array",
            "items" : {
              "type" : "string"
            },
            "example" : [ "java", "programming" ]
          }
        }
      },
      "Book Response" : {
        "type" : "object",
        "properties" : {
          "isbn" : {
            "type" : "string",
            "example" : "978-4621303252"
          },
          "title" : {
            "type" : "string",
            "example" : "Effective Java 第3版"
          },
          "price" : {
            "format" : "int32",
            "type" : "integer",
            "example" : 4400
          },
          "tags" : {
            "type" : "array",
            "items" : {
              "type" : "string"
            },
            "example" : [ "java", "programming" ]
          }
        }
      }
    }
  }
}

出力されていますね。

これで、まずは目的は達成できました。

とはいえ、ビルド時にしか使わないのにapplication.propertiesに書いておくのはちょっと抵抗があったので、いったんコメントアウト。

src/main/resources/application.properties
quarkus.smallrye-openapi.info-title=OpenAPI Definition Example
quarkus.smallrye-openapi.info-version=0.0.1

#quarkus.smallrye-openapi.store-schema-directory=openapi-definition

これでもOKです。

$ mvn package -Dquarkus.smallrye-openapi.store-schema-directory=openapi-definition

なんなら、quarkus:devでも有効です。

$ mvn compile quarkus:dev -Dquarkus.smallrye-openapi.store-schema-directory=openapi-definition

quarkus:devの場合、最初の起動時に1度OpenAPIの定義ファイルが出力され、その後はアプリケーションの再ロードの度にファイルが更新されて
いきます。

あとは、環境変数でも試してみましょう。

$ QUARKUS_SMALLRYE_OPENAPI_STORE_SCHEMA_DIRECTORY=openapi-definition mvn package

こちらもOKでした。

これで、今回確認したいことはできましたね。

まとめ

QuarkusとSmallRye OpenAPIで、ビルド時にOpenAPIの定義ファイルを出力してみました。

ドキュメントのプロパティを眺めていて、求めていたものがあったので使ってみたら、割とあっさり使えたので良かったですね。

ちなみに、SmallRye OpenAPIのMavenプラグイン(またはGradleプラグイン)を使っても同じようなことができそうなので、そのうち
試してみてもいいかなと思いました。