OpenAPIで異なるレスポンスを返せるoneOfが便利
OpenAPI初心者の @zaru です、こんにちは。この記事ではOpenAPIのoneOfという便利な機能を紹介します。ぼくはOpenAPI歴数ヶ月ですが、今まで知らなかったのを悔やむレベルです。
oneOfとは
簡単に言うと、同じEndpointでも異なるフォーマットのレスポンスが返せる・あるいはリクエストデータを受け付けることができる機能です。別の言い方をすると XOR 排他的論理和 です。
例えば、ユーザ情報を取得するEndpointで、対象リソースが「個人ユーザ」もしくは「法人ユーザ」の2通りどちらかが返ってくるとします。「個人ユーザ」と「法人ユーザ」で返すデータフォーマットが異なるときに oneOf
で「個人ユーザ」「法人ユーザ」どちらか一つが返ってくることを定義することができます。
oneOfを使うことで、以下のようにどちらかのフォーマットでレスポンスを受け取れます。
// 個人ユーザの場合
{
"type": "Individual",
"nickname": string
}
// 法人ユーザの場合
{
"type": "Corporate",
"company_name": string
}
oneOfを使ったコード例
実際にOpenAPIの定義と、TypeScriptのコード例を紹介します。TypeScriptでは openapi-typescript
と typescript-fetch
ライブラリを利用します。
OpenAPIの定義例
openapi: 3.0.1
paths:
/user:
get:
summary: oneOf sample
responses:
'200':
description: User information retrieved successfully
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/IndividualUser'
- $ref: '#/components/schemas/CorporateUser'
components:
schemas:
CorporateUser:
type: object
properties:
type:
type: string
enum:
- Corporate
company_name:
type: string
required:
- type
- company_name
IndividualUser:
type: object
properties:
type:
type: string
enum:
- Individual
nickname:
type: string
required:
- type
- nickname
IndividualUser
と CorporateUser
という2つのスキーマを定義し、 oneOf
で2つのスキーマを指定しています。スキーマを使わずとも直接プロパティを指定しても問題ありませんが、指定したほうがTypeScriptで型情報を明示的に呼び出しやすいのでオススメします。
TypeScriptのコード例
import createClient from "openapi-fetch";
import type { components, paths } from "./sample.openapi";
const client = createClient<paths>({
baseUrl: "http://127.0.0.1:3658/", // モックサーバ
});
async function useSchema() {
const { data, error } = await client.GET("/user", {});
if (error) {
console.error(error);
return;
}
if (data) {
if (isIndividual(data)) {
console.log(data.nickname);
} else if (isCorporate(data)) {
console.log(data.company_name);
}
} else {
console.log("data is undefined");
}
}
// 以降、型ガード
function isIndividual(
data:
| components["schemas"]["IndividualUser"]
| components["schemas"]["CorporateUser"],
) {
return data.type === "Individual";
}
function isCorporate(
data:
| components["schemas"]["IndividualUser"]
| components["schemas"]["CorporateUser"],
) {
return data.type === "Corporate";
}
共通の type
というプロパティをもたせ、それぞれ文字列で "Individual"
"Corporate"
が入るように指定されているので、条件分岐(型ガード)で判定することで、個人法人どちらのユーザか見分けることができます。
oneOf
を使えば表現力が強化されつつも使い勝手が良いので、ぜひ使ってみてください。
Discussion