ZodとGraphQLは対応づけがむずかしい

ushironoko
·
  • GraphQL Inputは同じ関心事で1つのtypeにまとめるのが良いプラクティス

    • 例えば住所

    • city,town,postalcodeなどはAddressというtypeにまとめる

      • こうすることで、cityはあるのにpostalcodeはない、のような矛盾を防げる

      • ProductionReadyGraphQL参照

  • 住所入力フォームが、それ以外(例えば氏名、年齢、電話番号等)の情報とセットで送られてくるとする

    • `input { address: AddressInput name: String! age: Number! phone: String! }`

      • この場合、フロントエンド上のオブジェクト構造ではこうなる

      • `{ address?: { postalCode: string; city: string; town: string; }; name: string; age: number; phone: string; }`

    • これをフォームに対応付けると、各要素のname属性はどうなるか

      • `address.city` のようにドット繋ぎになる?

      • `addressCity` のようにキャメルケースでまとめる?

      • どちらのパターンも、GraphQL SchemaからZod Schemaを生成した場合苦労する

  • Zod Schemaはネストされた構造を `ZodLazy` で表現する

    • `{ address: z.lazy(() => AddressInput()) }`

    • `city` にバリデーションをかけるには、`city` に突き当たるまで再帰的に `shape` で取り出したのちに `pick` する必要がある

    • つまり `address.city` をsplitして `shape[currentPath]` みたいに降りていく

    • しかし、`z.lazy` された要素は `shape` で取り出すことはできない

      • 実行されるまで `shape` が定まらないため

      • 同様に、`nullish()` された場合も `ZodOptional` となり `shape` を参照できない

    • `addressCity` 形式の場合、そもそもパースすること自体難しい

    • `addressCity: z.string()` のようなスキーマを別途手書きすることになる

      • 自動生成の意味がない

  • 結局、GraphQL Inputのネストした構造自体がフォームバリデーションと相性が悪いのでは?

  • blur等で特定の要素のみがきた場合も、古いフォームオブジェクトにマージして全体をバリデーションする等すれば可能かもしれない

    • pickが難しいという話なので、全体構造を保っていれば良い

    • だとしても、ZodSchemaのネスト構造をGraphQLErrorのextensionsが持つpathパターン(address.city)にマッピングしなおす({ "address.city": { errors: [{ message: "..." }] } })ことは必要になり、つらい