zodでアップロード画像に対して必須バリデーションをかける方法

zodでフォームから送信された画像に対してバリデーションをかけたかったが、デフォルトでは画像専用のバリデーションの仕組みが用意されていない。

画像のバリデーションをzodで実装したのでメモとして残しておく。

必須のバリデーションをかける

まずは完成系。

const scheme = z.object({ uploadImage: z.custom<File>( (data) => { return data instanceof File }, { message: '画像は必須です。' }, ), }) // ★1 function validateUploadedImageSample(formData: FormData) { const validated = scheme.safeParse({ uploadImage: formData.get('uploadImage'), }) if (!validated.success) { return { status: 'error', errors: validated.error.flatten().fieldErrors, } } }

画像データは <input type="file" name="uploadImage" /> 経由で送信されてくる前提で、
フォームからの送信データは**★1の引数に指定しているformData**内に入って くる想定。

バリデーションの方針として、取得した画像データはFile型になるので、zod側では入力データの型がFile型かどうかをチェックすることで画像の必須チェックを行います。

zodで特定の型かどうかをチェックするために**z.custom< T > **を使用します。

このz.customは入力値を受け取るコールバック関数を受け取ることができます。
その関数内で入力値の型判定を行って、正しくない型であればfalseを返すことでバリデーションエラーとして判定できます。

この際に undefinednullも弾いてくれることになるので 必須チェック もできます。

型が問題なければtrueを返します。trueを返すことでFile型として扱ってくれるので、後続の処理でファイルサイズのバリデーションをかけたい場合などでも型補完が効きます。

ファイルサイズのチェック

const MAX_FILE_MEGA_BYTE_SIZE = 5 const scheme = z.object({ uploadImage: z.custom<File>( (data) => { return data instanceof File }, { message: '画像は必須です。' }, ) .refine(validateFileSize, { message: `ファイルサイズが${MAX_FILE_MEGA_BYTE_SIZE}MBを超えています。${MAX_FILE_MEGA_BYTE_SIZE}MB以内のファイルを選択してください。`, }), }) function validateFileSize(file: File): boolean { return toMBFromByte(file.size) <= MAX_FILE_MEGA_BYTE_SIZE } type MegaByte = number function toMBFromByte(byte: number): MegaByte { return byte / (1024 * 1024) }

mineTypeのチェック

z.object({ uploadImage: z .custom<File>( (data) => { return data instanceof File }, { message: '画像は必須です。' }, ) .refine(validateMineType, { message: 'エラーメッセージ', }), }) function validateMineType(file: File): boolean { return includes(uploadableMineTypes, file.type) } const uploadableMineTypes = ['image/gif', 'image/jpeg', 'image/png'] as const function includes<Array extends ReadonlyArray<unknown>>( array: Array, value: unknown, ): value is Array[number] { return array.includes(value) }