OpenAPI + OrvalでrequestをカスタマイズしたSWRフックを生成する

リクエストをカスタマイズしつつOpen APIの定義ファイルからSWRフックを生成する方法をメモがてら記載する。

リクエストをカスタマイズしたい需要としては、APIエンドポイントに認証など追加の手続きが必要な処理が存在する場合に、その手続きを実施するためのカスタマイズを行うなどである。

# Orvalの選定理由
Open APIからコードを生成するツールは多々あり、選定が難しい。最終的に残った候補のみを記載する。
- Orval
    - 設定ファイルが単純かつ、ドキュメントがしっかり書かれている。カスタマイズもある程度柔軟に可能。生成されるコードが比較的シンプルでわかりやすい。
- swagger-typescript-api
    - ejsを利用したテンプレートを利用し柔軟にカスタマイズできるため複雑なケースには有用だが、ejsは読みにくく開発難度が高い。(別プロジェクトで導入実績があるため、Orvalで実現できない場合の代替候補として考えていた。)
- Redux RTK Query
    - 柔軟なカスタマイズを提供しており有力候補であったが、Reduxが依存関係に必要なのが今回のユースケースに合わなかった。(導入先のプロジェクトにはすでに古いバージョンのReduxが入っており、複数のバージョンを入れたくなかった。また、古いバージョンのReduxは廃止する予定であったため混乱を防止するためにRTK Queryを選択できなかった。)

# Orvalの設定
orvalの設定は `orval.config.ts` に設定する。typescriptで書くことができる。今回は、例としてSwagger公式のpetstoreの定義ファイルを利用する。

```typescript forval.config.ts
import { defineConfig } from "orval"

export default defineConfig({
  petstore: {
    input: {
      target: "https://petstore.swagger.io/v2/swagger.json",
    },
    output: {
      mode: "single",
      target: "src/api/index.ts",
      client: "swr",
      override: {
        mutator: {
          path: "src/api/lib/mutator.ts",
          name: "useMutator",
        }
      }
    },
  },
})
```

Orvalの細かなカスタマイズは `override` 内に記述する。[公式ドキュメント](https://orval.dev/reference/configuration/output#mutator)
今回はリクエスト処理を上書きするパラメータ `mutator` を設定する。 `mutator` はファイルを指定することによってリクエスト関数を丸ごと置き換えることができる。

# mutatorの設定
`axios` をベースに認証処理を行う `axios` のインスタンスを作成する。Open ID Connectやその他認証規格に準拠した実装であれば、それらのaxios用アダプタも利用も可能と考えられる。今回は、認証方法が特殊であるため自前で実装している（詳細は割愛）。

```typescript src/api/lib/mutator.ts
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
  maxAttempts?: number;
  skipAuth?: boolean;
}

const mutator = axios.create({
  baseURL: BASE_URL,
})

mutator.interceptors.request.use((config: CustomAxiosRequestConfig) => {
  if (config.skipAuth) {
    return config
  }
  /* 認証の処理 */
  return config
})

mutator.interceptors.response.use(
  response => {
    return response
  },
  async (error: AxiosCustomError) => {
    /* 認証が失敗した時のリトライ処理 */
  }
)

export const useMutator = <T = any>(config: CustomAxiosRequestConfig) => {
  return mutator.request<T>(config)
}
export type ErrorType<T = any> = AxiosCustomError<T>
```

# コードの生成
設定に問題がなければすぐにコードが生成される
```bash
$ yarn orval

🍻 Start orval v6.16.0 - A swagger client generator for typescript
🎉 petstore - Your OpenAPI spec has been converted into ready to use orval!
✨  Done in 0.93s.
```

# 生成されたコード

例えば `getPetById` は以下のように生成される。 `useMutator` が利用されている以外は通常のOrvalが生成するコードと同様である。

```typescript src/api/index.ts
/**
 * Returns a single pet
 * @summary Find pet by ID
 */
export const getPetById = (
    petId: number,
 ) => {
      return useMutator<Pet>(
      {url: `/pet/${petId}`, method: 'get'
    },
      );
    }
  

export const getGetPetByIdKey = (petId: number,) => [`/pet/${petId}`] as const;

    
export type GetPetByIdQueryResult = NonNullable<Awaited<ReturnType<typeof getPetById>>>
export type GetPetByIdQueryError = ErrorType<void>

/**
 * @summary Find pet by ID
 */
export const useGetPetById = <TError = ErrorType<void>>(
 petId: number, options?: { swr?:SWRConfiguration<Awaited<ReturnType<typeof getPetById>>, TError> & { swrKey?: Key, enabled?: boolean },  }

  ) => {

  const {swr: swrOptions} = options ?? {}

  const isEnabled = swrOptions?.enabled !== false && !!(petId)
    const swrKey = swrOptions?.swrKey ?? (() => isEnabled ? getGetPetByIdKey(petId) : null);
  const swrFn = () => getPetById(petId, );

  const query = useSwr<Awaited<ReturnType<typeof swrFn>>, TError>(swrKey, swrFn, swrOptions)

  return {
    swrKey,
    ...query
  }
}
```

`override` の設定では他にも色々なオプションが用意されているため、用途に応じて色々とカスタマイズできそうだ。