Closed13

openapi-zod-clientが整数型のenumをZod公式の方針通りに変換してくれなかったりしたので諸々直してもらった

macropygiamacropygia

openapi-zod-client はOpenAPI 3.0+のYAML/JSONから Zod というか Zodios の定義を生成してくれるCLIツール。Playground もある。この記事で扱うバージョンは 1.4.5

これに手元のOASを通すと整数型のenumが――

YAML
- in: query
  name: foo
  schema:
    type: integer
    enum:
      - 1
      - 2

こうなってしまう。

TS
{
  name: "foo",
  type: "Query",
  schema: z.union([z.literal("1"), z.literal("2")]).int()
}

ついでに小数だとこうなる。

YAML
- in: query
  name: bar
  schema:
    type: number
    enum:
      - 1.234
      - 2.345
TS
{
  name: "bar",
  type: "Query",
  schema: z.union([z.literal("1.234"), z.literal("2.345")])
}
macropygiamacropygia

まず schemaenum がぶら下がっていたら int() は付与しないことにする。

openApiToZod.ts
if (schema.type === "integer" && !schema.enum) {
    validations.push("int()");
}

変換してみる。

YAML
- in: query
  name: foo
  schema:
    type: integer
    enum:
      - 1
      - 2
TS
{
  name: "foo",
  type: "Query",
  schema: z.union([z.literal("1"), z.literal("2")])
}
macropygiamacropygia

次に typeinteger または number だったらそのまま z.literal() に入れることにする。
null の扱いは元々あったロジックを踏襲しておく。

openApiToZod.ts
  if (schema.enum) {
      if (schema.type === "string") {
          // 略
      }

+   if (schema.type === "integer" || schema.type === "number") {
+       return code.assign(
+           `z.union([${schema.enum
+               // eslint-disable-next-line sonarjs/no-nested-template-literals
+               .map((value) => `z.literal(${value === null ? "null" : `${value}`})`)
+               .join(", ")}])`
+       );
+   }

      return code.assign(
          // 略
      );
  }

変換してみる。

YAML
- in: query
  name: foo
  schema:
    type: integer
    enum:
      - 1
      - 2
- in: query
  name: bar
  schema:
    type: number
    enum:
      - 1.234
      - 234.5
{
  name: "foo",
  type: "Query",
  schema: z.union([z.literal("1"), z.literal("2")])
},
{
  name: "bar",
  type: "Query",
  schema: z.union([z.literal("1.234"), z.literal("234.5")])
}
macropygiamacropygia

テストをかけたらVitestの toMatchInlineSnapshot が引っかかったので integernumber がそれぞれテストされるように変更。

openApiToZod.test.ts
expect(getSchemaAsZodString({ type: "integer", enum: [1, 2, 3, null] })).toMatchInlineSnapshot();
expect(getSchemaAsZodString({ type: "number", enum: [1.2, 3.4, 56.789, null] })).toMatchInlineSnapshot();

もう1回実行して内容を確認。

macropygiamacropygia

本題から外れるけど、ちょっと実装精度が怪しくて現時点での実戦投入は厳しそう。

  • patternに実体参照の制御文字を記述すると復号されたまま戻らない
  • minLengthとmaxLengthを両方指定するとminLengthの固定長として処理されてしまう
  • allOfに$refが3個以上あるとうまく連結されない
macropygiamacropygia

#49で盛り上がった結果、自分の用途には十分な状態まで仕上がってしまった。
これで6000余行(絶賛増加中)のYAMLを変換してやれば、自前のフォームコンポーネントを使ったreact-hook-formとZodの繋ぎ込み作業を効率化できる……はず。

このスクラップは2022/12/14にクローズされました