はじめに
1ヶ月後にモンスターハンターになる、ノベルワークスのりょうちん(ryotech34)です。
今回は、TypeScript向けに設計されたスキーマ宣言やバリデーションを助けてくれるライブラリである、zodを使用して条件分岐バリデーションを実装した際の備忘録です。
ドメインモデルとzodを組み合わせた実装方法として、下記の記事を参考にさせていただきました。ぜひ確認してみてください。
対象読者
- zodに触れているもしくはこれから触ろうと思っている方
話さないこと
- zodの基本的な記法
本題
テーマ
toB向けのサービスがあり、組織単位でベーシック、プロ、エンタープライズという3つのプランで契約できるとします。
そこでは、プランごとに毎月使用できるクレジットが制限されており、クレジットが制限値を超えると当該月では使用できないようにするという想定です。
やりたいこと
今回はプランが変更された場合、プランのタイプに合わせてスキーマまるごと変更を行い、不整合があれば型にしたがってエラーを吐かせたいというものです。
変更箇所は、プランを示すtype
と現在のクレジットを示すcurrentCredit
の2つです。
このcurrentCredit
の上限値を、条件分岐で変更したいというのが今回の主旨になります。
zodで上限値を設定する
zodで上限値を設定するには、主に.lte
と.refine
が使えます。
.lte
を使用した場合
.max
は引数に(上限値, メッセージ)を取るもので、シンプルに実装できます。
const planSchema = z.object({
type: z.literal("basic"),
currentCredit: NonNegativeIntSchema
.lte(100, "Monthly limit exceeded because of basic plan"),
})
z.number()
において.gte
は.min
で、.lte
は.max
でも実装できる。
z.number().gt(5);
z.number().gte(5); // alias .min(5)
z.number().lt(5);
z.number().lte(5); // alias .max(5)
.refine
を使用した場合
.refine
は引数に(バリデート関数, パラメータ)を取るもので、バリデート関数がfalse
の場合にパラメータを返すというエラーハンドリングが柔軟に実装できるのが特徴です。
const planSchema = z.object({
type: z.literal("basic"),
currentCredit: NonNegativeIntSchema
.refine((value) => value <= 100, {
message: "Monthly limit exceeded because of basic plan",
}),
})
今回は、
- 複雑なエラーハンドリングが出来る
-
.lte
は記事としてはシンプルすぎる
以上のことから.refine
を採用したと仮定して続きます。
.superRefine
.refine
よりも複雑なエラーハンドリングができる.superRefine
というものもあります。.refine
を含め、以下の記事にわかりやすくまとめられていたので確認してみてください。
zodでスキーマを条件分岐で定義する
条件分岐で定義する方法として、.discriminatedUnion
があります。
引数に(キー、条件分岐リスト)をすることで、リスト内のキーを参照して条件分岐を実装することができます。
今回はtype
をキーにして実装したと仮定します。
export const PlanSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("basic"),
currentCredit: NonNegativeIntSchema
.refine((value) => value <= 100, {
message: "Monthly limit exceeded because of basic plan",
}),
}),
z.object({
type: z.literal("pro"),
currentCredit: NonNegativeIntSchema
.refine((value) => value <= 200, {
message: "Monthly limit exceeded because of pro plan",
}),
}),
z.object({
type: z.literal("enterprise"),
currentCredit: NonNegativeIntSchema
.refine((value) => value <= 300, {
message: "Monthly limit exceeded because of enterprise plan",
}),
}),
]);
currentCredit
が月上限値を超えた場合、エラーを吐くようになります。
-
currentCredit
がそれぞれのプランで上限値をもつ - 型チェックをzodを使用した関数で統一できる
- コードがシンプルになる
ようになり、型安全性とメンテナンス性が向上したと思います。
最後に
今回はzodを活用して、条件分岐で変わるバリデーションをスキーマに移してみました。最初こそ新しいzodの概念に揉まれて苦労したものの、拡張性があり便利だなと感じています。
まだまだ触り始めのため、もっと開拓してガッチガチのコーディングが出来るようになりたいです。
皆さんの役に立てば嬉しいです👾