🎹

OpenAPIで異なるレスポンスを返せるoneOfが便利

2024/06/24に公開

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-typescripttypescript-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

IndividualUserCorporateUser という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