Zod で独自のスキーマバリデーションを実現するには、refine というメソッドを使います。また、さらに高度な使い方ができる superRefine が提供されているため、使い方を復習してみようと思います。
refine とは
refine
は refinement
のエイリアスとして提供されており、素材などを変換する (Zod でいうスキーマの型を再定義) という意味で使われます。Zod は数多くのスキーマを提供していますが、独自で定義したい場面も少なくありません。
import z from 'zod';
// Zod は整数だけでなく、自然数のようなスキーマも提供している。
const numberSchema = z.number().positive();
console.log(numberSchema.safeParse(12)); // { success: true, data: 12 }
// 偶数のスキーマは独自で定義する。
const evenNumberSchema = z.number().refine((num) => num % 2 === 0);
evenNumberSchema.parse(5); // 例外発生
より高度な使い方
refine
の第 2 引数を使うことで、エラーハンドリングの振る舞いを細かく調整できます。React Hook Form と組み合わせたフォームバリデーションでは、エラーが発生したフィールドを特定するといった用途で用います。
import z from 'zod';
export function validatePassword(credential: {
userId: string;
password: string;
}) {
const credentialSchema = z
.object({
userId: z.string(),
password: z.string(),
})
.refine(({ password }) => password.length > 8, {
message: 'パスワードは 8 文字以上に設定してください。',
path: ['password'],
// ^^^^ フィールドを特定する値を path の配列内で指定する。
});
credentialSchema.parse(credential);
}
検証に失敗すると、Zod は次の様なエラーを吐きます。そのため、パスワードの入力フィールドにフォーカスを当てるといった実装ができるわけです。
ZodError {
issues: [{
'code': 'custom',
'path': [ 'password' ],
'message': 'パスワードは 8 文字以上に設定してください。'
}]
}
superRefine
実のところ、refine
は superRefine
のシンタックスシュガーです。よって、より複雑な検証には superRefine
が必要になります。
ここではインターフェースの確認にとどめますが、ZodIssue をカスタマイズしたり、検証を途中で止めたりできる様です。上記のコードを superRefine
で書き換えてみましょう。
userId: z.string(),
password: z.string(),
})
- .refine(({ password }) => password.length > 8, {
- message: 'password must be longer than 8 characters.',
- path: ['password'],
+ .superRefine(({ password }, context) => {
+ if (password.length <= 8) {
+ context.addIssue({
+ code: 'custom',
+ message: 'password must be longer than 8 characters',
+ path: ['password'],
+ });
+ }
});
credentialSchema.parse(credential);
見ての通り ZodIssue を定義して、ZodError を自力で組み立てていきます。そのため、refine
より柔軟なカスタマイズができるというわけです。
おわりに
今回は Zod の refine
周りを復習しました。フォームバリデーションや API との繋ぎ込みなど、用途は広いのでしっかりと押さえておきたいところです。