CLOVER🍀

That was when it all began.

SmallRye OpenAPIのUIを使ってOpenAPIドキュメントを参照する

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

前にこういったMicroProfile OpenAPIを使ったエントリーを書きました。

WildFly 33とMicroProfile OpenAPI(SmallRye OpenAPI)でOpenAPIドキュメントを生成する - CLOVER🍀

この時は生成したOpenAPIドキュメントをSwagger Editorで確認していたのですが、SmallRye OpenAPIがUIも提供していることがわかったので
こちらを試してみることにしました。

このエントリーでは、差分だけを簡単にまとめます。

SmallRye OpenAPIのUI

SmallRye OpenAPIでは、UIも提供されています。

https://github.com/smallrye/smallrye-open-api/tree/3.10.0/ui

MicroProfile OpenAPIで生成したOpenAPIドキュメントを、Swagger UIで見ることができます。

こちらでビルドした結果を

https://github.com/smallrye/smallrye-open-api/tree/3.10.0/ui/open-api-ui-forms

こちらにJARとしてまとめて使うようです。

https://github.com/smallrye/smallrye-open-api/tree/3.10.0/ui/open-api-ui

こんな感じで、runtimeスコープとして依存関係に追加します。

        <dependency>
            <groupId>io.smallrye</groupId>
            <artifactId>smallrye-open-api-ui</artifactId>
            <version>3.10.0</version>
            <scope>runtime</scope>
        </dependency>

UIを参照するには/openapi-uiという固定のパスでアクセスする必要があり、OpenAPIドキュメントは/openapiでアクセスできる必要が
あります。

どういう仕掛けになっているかというと、smallrye-open-api-uiにはMETA-INF/resources/openapi-ui配下にUIのリソースが含まれていて、
こちらをJakarta Servletの仕様で参照します。

$ jar -tvf ~/.m2/repository/io/smallrye/smallrye-open-api-ui/3.10.0/smallrye-open-api-ui-3.10.0.jar
     0 Wed Feb 21 00:04:30 JST 2024 META-INF/
   580 Wed Feb 21 00:04:30 JST 2024 META-INF/MANIFEST.MF
     0 Wed Feb 21 00:04:30 JST 2024 io/
     0 Wed Feb 21 00:04:30 JST 2024 io/smallrye/
     0 Wed Feb 21 00:04:30 JST 2024 io/smallrye/openapi/
     0 Wed Feb 21 00:04:30 JST 2024 io/smallrye/openapi/ui/
     0 Wed Feb 21 00:04:30 JST 2024 META-INF/resources/
     0 Wed Feb 21 00:04:30 JST 2024 META-INF/resources/openapi-ui/
     0 Wed Feb 21 00:04:30 JST 2024 META-INF/resources/template/
     0 Wed Feb 21 00:04:30 JST 2024 template/
     0 Wed Feb 21 00:04:30 JST 2024 META-INF/maven/
     0 Wed Feb 21 00:04:30 JST 2024 META-INF/maven/io.smallrye/
     0 Wed Feb 21 00:04:30 JST 2024 META-INF/maven/io.smallrye/smallrye-open-api-ui/
 13898 Wed Feb 21 00:04:30 JST 2024 io/smallrye/openapi/ui/IndexHtmlCreator.class
  4659 Wed Feb 21 00:04:30 JST 2024 io/smallrye/openapi/ui/Option.class
  1389 Wed Feb 21 00:04:30 JST 2024 io/smallrye/openapi/ui/HttpMethod.class
  2079 Wed Feb 21 00:04:30 JST 2024 io/smallrye/openapi/ui/ThemeHref.class
   931 Wed Feb 21 00:04:30 JST 2024 io/smallrye/openapi/ui/ThemeHref$1.class
  1138 Wed Feb 21 00:04:30 JST 2024 io/smallrye/openapi/ui/DocExpansion.class
 33496 Tue Sep 05 03:21:24 JST 2023 META-INF/resources/openapi-ui/theme-feeling-blue.css
262590 Fri Feb 16 12:01:22 JST 2024 META-INF/resources/openapi-ui/swagger-ui.css.map
1402103 Fri Feb 16 12:01:20 JST 2024 META-INF/resources/openapi-ui/swagger-ui-bundle.js
  6630 Wed Feb 21 00:04:30 JST 2024 META-INF/resources/openapi-ui/logo.png
 34635 Tue Sep 05 03:21:24 JST 2023 META-INF/resources/openapi-ui/theme-material.css
  2715 Fri Feb 16 12:01:20 JST 2024 META-INF/resources/openapi-ui/oauth2-redirect.html
230809 Fri Feb 16 12:01:20 JST 2024 META-INF/resources/openapi-ui/swagger-ui-standalone-preset.js
 33492 Tue Sep 05 03:21:24 JST 2023 META-INF/resources/openapi-ui/theme-flattop.css
   356 Wed Feb 21 00:04:30 JST 2024 META-INF/resources/openapi-ui/style.css
1907201 Fri Feb 16 12:01:20 JST 2024 META-INF/resources/openapi-ui/swagger-ui-bundle.js.map
330453 Fri Feb 16 12:01:22 JST 2024 META-INF/resources/openapi-ui/swagger-ui-standalone-preset.js.map
  1360 Wed Feb 21 00:04:30 JST 2024 META-INF/resources/openapi-ui/index.html
 33490 Tue Sep 05 03:21:24 JST 2023 META-INF/resources/openapi-ui/theme-muted.css
  4158 Wed Feb 21 00:04:30 JST 2024 META-INF/resources/openapi-ui/favicon.ico
 36042 Tue Sep 05 03:21:24 JST 2023 META-INF/resources/openapi-ui/theme-monokai.css
 33471 Tue Sep 05 03:21:24 JST 2023 META-INF/resources/openapi-ui/theme-newspaper.css
 33135 Tue Sep 05 03:21:24 JST 2023 META-INF/resources/openapi-ui/theme-outline.css
152042 Fri Feb 16 12:01:20 JST 2024 META-INF/resources/openapi-ui/swagger-ui.css
  4595 Wed Feb 21 00:04:30 JST 2024 META-INF/resources/template/index.html
  4595 Wed Feb 21 00:04:30 JST 2024 template/index.html
  6192 Wed Feb 21 00:03:22 JST 2024 META-INF/maven/io.smallrye/smallrye-open-api-ui/pom.xml
    67 Wed Feb 21 00:04:30 JST 2024 META-INF/maven/io.smallrye/smallrye-open-api-ui/pom.properties
   299 Wed Feb 21 00:04:30 JST 2024 META-INF/INDEX.LIST

このあたりの話ですね。

The getResource and getResourceAsStream methods take a String with a leading "/" as an argument that gives the path of the resource relative to the root of the context or relative to the META-INF/resources directory of a JAR file inside the web application’s WEB-INF/lib directory. If there is a WEB-INF entry inside the META-INF/resources entry of a JAR file in WEB-INF/lib, then it and all child entries are available only as static resources.

Jakarta Servlet Specification / Servlet Context / Resources

そして、/openapiからOpenAPIドキュメントを取得してUIを生成するという流れになっています。

https://github.com/smallrye/smallrye-open-api/blob/3.10.0/ui/open-api-ui/src/main/webapp/index.html#L21

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.4 2024-07-16
OpenJDK Runtime Environment (build 21.0.4+7-Ubuntu-1ubuntu222.04)
OpenJDK 64-Bit Server VM (build 21.0.4+7-Ubuntu-1ubuntu222.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 21.0.4, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.15.0-122-generic", arch: "amd64", family: "unix"

前のエントリーの差分追記なので、WildFlyは33.0.2.Finalを使います。

SmallRye OpenAPIのUIを使う

では、SmallRye OpenAPIのUIを使ってみます。Maven依存関係にsmallrye-open-api-uiruntimeスコープで追加します。

        <dependency>
            <groupId>io.smallrye</groupId>
            <artifactId>smallrye-open-api-ui</artifactId>
            <version>3.10.0</version>
            <scope>runtime</scope>
        </dependency>

バージョンは、WildFly 33.0.2.Finalに含まれているSmallRye OpenAPIのバージョンに含まれていました。

で、http://localhost:8080/openapi-ui/にアクセスすると、Swagger UIを見ることができます。
※作成したソースコードは省略します

よさそうです。

最初からこれで確認すればよかったです…。

オマケ

最初、Webアプリケーションに含めたUIにアクセスできずにかなりハマりました…。

これなのですが、自分が@ApplicationPath/を指定していたのでJAX-RSのルーティングの方に奪われていたからですね。

@ApplicationPath("/")
public class RestApplication extends Application {
}

なので、割り当てるパスをずらしました…。

@ApplicationPath("/api")
public class RestApplication extends Application {
}

パスを/にしたままだと

@ApplicationPath("/")
public class RestApplication extends Application {
}

以下のようなContainerRequestFilterを作ればなんとかなりましたが、Content-Typeも指定していませんし、ちょっとイマイチですね…。

src/main/java/org/littlewings/wildfly/openapi/OpenApiUiRequestPassThroughFilter.java

package org.littlewings.wildfly.openapi;

import java.io.IOException;
import java.io.InputStream;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.servlet.ServletContext;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;

@PreMatching
@Provider
@ApplicationScoped
public class OpenApiUiRequestPassThroughFilter implements ContainerRequestFilter {
    @Inject
    private ServletContext servletContext;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String path = requestContext.getUriInfo().getPath();
        if (path.startsWith("/openapi-ui")) {
            if (path.endsWith("/")) {
                path = path + "index.html";
            }

            InputStream is = servletContext.getResourceAsStream(path);
            Response response = Response.ok(is).build();
            requestContext.abortWith(response);
        }
    }
}

この場合はServlet APIが必要ですね。

        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>

最後に、pom.xml全体を載せておきましょう。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>microprofile-openapi-ui-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.wildfly.bom</groupId>
                <artifactId>wildfly-ee-with-tools</artifactId>
                <version>33.0.2.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.wildfly.bom</groupId>
                <artifactId>wildfly-microprofile</artifactId>
                <version>33.0.2.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>5.11.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>jakarta.ws.rs</groupId>
            <artifactId>jakarta.ws.rs-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.enterprise</groupId>
            <artifactId>jakarta.enterprise.cdi-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.microprofile.openapi</groupId>
            <artifactId>microprofile-openapi-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.smallrye</groupId>
            <artifactId>smallrye-open-api-ui</artifactId>
            <version>3.10.0</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.26.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>5.5.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.4.0</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <version>5.0.1.Final</version>
                <executions>
                    <execution>
                        <id>package</id>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <overwrite-provisioned-server>true</overwrite-provisioned-server>
                    <discover-provisioning-info>
                        <version>33.0.2.Final</version>
                    </discover-provisioning-info>
                </configuration>
            </plugin>
            <plugin>
                <groupId>io.smallrye</groupId>
                <artifactId>smallrye-open-api-maven-plugin</artifactId>
                <version>3.10.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate-schema</goal>
                        </goals>
                        <phase>compile</phase>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

おわりに

SmallRye OpenAPIのUIを試してみました。

自分は余計なところでハマりましたが、ふつうはあっさりと導入できると思うので、MicroProfile OpenAPIを使っているところでUIがない場合は
こちらを使うとよいのかなと思います。