Zenn
🐟

Orvalのメンテナーとして2024年の活動と2025年やりたいこと

2025/01/02に公開2

2024年はorvalのmaintainerとしての活動が非常に充実した年となりました。
この記事では一年での活動内容、2025年に向けた取り組みについて書きます。

サマリ

  • orvalのmaintainerになりました
  • orvalを作成者の個人リポジトリからorganizationへ移行しました
  • 2名からGitHub Sponsorsを受け取りました
  • 直接的な貢献としては、107件のPR作成、109件のissueをcloseしました
  • 機能追加や改善に関する議論に参加しました
  • すべてのPRとissueを把握しキャッチアップしました
  • 依存ライブラリ、サンプルアプリ、ドキュメントのメンテナンスを継続しています

主な成果

自分自身が多くの時間を使い改善した対応をいくつかピックアップしました。

1. 自動生成されるMSWのmosk、handler定義を上書きできるようにした

orvalではOpenAPIからモック定義やモックハンドラーを自動生成する事ができます。
しかし、モック定義の値や、モックハンドラーのレスポンスを上書きできなかった為、テストコードで使用する際など扱いが難しいケースがありました。
改善後は、以下のようにモック定義やモックハンドラーのレスポンスを上書きできるようになりました。

mock定義の場合

OpenAPIから以下のようなmock定義が自動生成されます。

export const getShowPetByIdResponseMock = (
  overrideResponse: Partial<Pet> = {},
): Pet => ({
  id: faker.number.int({ min: undefined, max: undefined }),
  name: faker.string.alpha(20),
});

mockの値はランダムに生成された値が定義される為コントロールできず、テストで使用する場合などで扱いづらいケースがありました。
改善後は引数で上書きしたいフィールドと値を指定することで値の上書きが可能になりました。
例えば、nameだけを固定で別の値に変更したい場合は以下のように記述できます。

import { getShowPetByIdResponseMock } from 'pets.msw';

const pet = getShowPetByIdResponseMock({ name: 'override' });

console.log(pet);
// => { id: 7272122785202176, ​name: "override" }

mockハンドラーの場合

レスポンスをモックするハンドラーの定義も自動生成されます。
しかし、レスポンスの値もmock定義同様にランダムな値となり上書きできず扱いにくいケースがありました。
改善後は、以下のようにハンドラーの引数にオブジェクトを渡すことでモックするレスポンスを上書きできます。

import { Pet } from './gen/model/pet';
import { getShowPetByIdMockHandler } from 'petstore.msw';

const pet: Pet = { id: 1, name: 'test'};

const showPetByIdMockHandler = getShowPetByIdMockHandler(pet);

console.log(showPetByIdMockHandler);
// => Object { info: {…}, isUsed: false, resolver: async getShowPetByIdMockHandler(), resolverGenerator: undefined, resolverGeneratorResult: undefined, options: {} }

詳細: https://orval.dev/guides/msw

2. Fetch Clientの追加

fetch clientを自動生成されるクライアントに追加しました。

https://github.com/orval-labs/orval/issues/1387

これにより、fetch API単体でのHTTPクライアントの生成が可能になりました。

https://github.com/orval-labs/orval/blob/master/samples/next-app-with-fetch/app/gen/pets/pets.ts

また、React QuerySWRのHTTPクライアントとして、これまでのAxiosだけでなく、Fetch APIも選択可能になりました。

https://github.com/orval-labs/orval/blob/master/samples/react-app-with-swr/fetch-client/src/api/endpoints/swaggerPetstore.ts

Axiosへの依存を避ける事ができる事から、サーバーサイドフレームワークや、エッジコンピューティングランタイムでの動作も可能になりました。

詳細:

3. Honoの生成を追加

OpenAPIからHonoを使ったサーバーサイドのソースコードを生成する機能を追加しました。

  • handlerの雛形
  • context定義
  • ルーティング定義
  • zodスキーマ
  • バリデータ

を自動生成します。
これによりOpenAPIからHonoとfetch APIクライアントを自動生成しREST APIでも堅牢なスキーマ駆動開発が可能になりました。

詳細:

4. MSWのバグ修正

OpenAPI定義でallOf, oneOf, anyOf同士の組合わせ、ネスト、refと組合わさなど複雑なスキーマの場合にモックが正しく生成されない問題があり一部を修正しました。
まだ全てのエッジケースや複雑なケースに対応しきれていませんが、多くのケースでカバーできるようになりました。

5. その他

  • Turborepoの設定を見直しビルド時間を改善
  • フォーマッターにbiomeをサポート

思い出

  • チームメンバーが優しい
    • メンテナーになったばかりの頃、自分が1週間ほど私用で活動できないこと伝えると、
      「自分のために時間を使う事はとても大事なことだから心配しないで」と言って貰えてとても嬉しかったです。
    • 1年を通して学びましたが他のコントリビューターは日常的に1週間程休んでリフレッシュしていました。
  • 時差があるチーム開発の慣れ
    • 他のメンテナーが活動するのが日本時間の21時頃なので夜になると自分のスマホが鳴り始めます。
      • レビューや議論はその時に対応できるのですが、リリースする時間は寝ているため朝起きたら大体終わっており、リアルに共有できない寂しさがあります。
    • 朝の9時頃までは向こうも活動しているため日本のビジネスタイムの前後でのOSS活動がことができています。
  • テキストコミュニケーションが上手い
    • メンテナーである自分はコントリビュートの背景や対応内容を全て理解する必要がありますが必ずしも初見で全て理解できる訳ではありません。
    • それは取り込むべきなのか、もっと良い方法があるのか判断するために深く理解する必要があるのですが
    • 技術的な関心事に加えて、私は英語が流暢ではないので誤解や認識のすれ違いも発生します
      • 私自身が技術的な知見が不足しているケースもあります
      • 英文については、翻訳ツールを使ってもニュアンスが分からない場合や、言い回しやスラングについて調べることもあります
    • そういった場合でも、コントリビューターは丁寧にコミュニケーションをとって頂けるため最終的には理解できています
    • その過程から、テキストコミュニケーションの方法や議論の進め方、提案の構造は非常に学びになっています

今後の展望

行いたい改善案や追加機能もいくつかあり例えば、

  • fetch clientの最適化
  • Hono周辺の機能追加、改善
  • zodのスキーマ出力をオプションでコントロール
  • mswのインポートを全てまとめるindex.tsを生成できるようにする

などをコツコツ対応していきたいと考えています。
合わせて、ユーザの増加に合わせて増えているissueの議論やPRレビューもしっかりと対応していきます。

Discussion

伊藤伊藤

普段からOrvalを使わせていただいています。
特にZodスキーマ出力をオプションでコントロールする機能に関しては、ちょうど欲しいなと感じていたところだったので非常にありがたいです。
これからも陰ながら応援させていただきます!

soartec-labsoartec-lab

ありがとうございます!こうしてコメント頂けるのは非常に励みになります!
今も以下の様にclientの設定とzodの設定を分けて書くことで両方を出力することも可能ですので、もしまだ試していなかったら、こちらも試してみてください。

module.exports = {
  petstore: {
    input: {
      target: './petstore.yaml',
    },
    output: {
      mode: 'tags-split',
      client: 'swr',
      target: 'src/gen/endpoints',
      schemas: 'src/gen/models',
      mock: true,
    },
  },
  petstoreZod: {
    input: {
      target: './petstore.yaml',
    },
    output: {
      mode: 'tags-split',
      client: 'zod',
      target: 'src/gen/endpoints',
      fileExtension: '.zod.ts',
    },
  },
};

詳細: https://orval.dev/guides/client-with-zod

ただ、上記だとジェネレーターを2回動かしているので時間が掛かるのと、設定ファイルが冗長なので以下の様に1つの設定にまとめたいなと思っています 👍

module.exports = {
  petstore: {
    input: {
      target: './petstore.yaml',
    },
    output: {
        mode: 'tags-split',
      client: 'swr',
      target: 'src/gen/endpoints',
      schemas: 'src/gen/models',
      mock: true,
+     zod: true
    },
  },
- petstoreZod: {
-   input: {
-     target: './petstore.yaml',
-   },
-   output: {
-     mode: 'tags-split',
-     client: 'zod',
-     target: 'src/gen/endpoints',
-     fileExtension: '.zod.ts',
-   },
- },
};
ログインするとコメントできます