第20章:入力チェックは“実行時”だよ🧪🫥(TypeScript)
この章のゴールはシンプル👇 **「外から来たデータ(フォーム/URL/JSON/APIレスポンス)を、境界でちゃんと検証して、ドメインエラーとして返せる」**ようになることだよ😊💪
20-1. なんで“実行時チェック”が必要なの?🫥➡️🧪
TypeScriptの型は、コンパイル後に消える(= 実行時には守ってくれない)からだよ😳💦 つまり…
![実行時チェック:未知のデータを通す検問[(./picture/err_model_ts_study_020_border_control.png)
fetch()で受け取ったJSON- フォーム入力
URLSearchParamsの値- 外部APIのレスポンス
これ全部、実行時には 「何が入ってるか分からない箱(unknown)」 みたいなもの📦🫥 だから 境界(入口)で validation/parse が必要になるよ🚪✨
20-2. “validate” と “parse” を分けて考えよう🍳✨
ここ、設計が一気にラクになるポイント💡
✅ validate(検証)
- 「形・型・制約が合ってる?」をチェックするだけ👀
- 合ってなければ **入力エラー(ドメイン)**として返す🎁
✅ parse(変換+検証)
- 文字列
"123"→ 数値123にする、空白トリムする、デフォルト値を入れる…など - “使える形に整えてから” 型付けされた値を返す🧼✨
実務だと「parse = 入口での正規化」って感覚がすごく強いよ😊 (第15章の“正規化”の入力版って感じ!)
20-3. 入力チェック設計の鉄板ルール3つ🔩✨
ルール①:境界の入力はまず unknown として扱う🫥
「信用しない」から始めるのが安全🥹🛡️
ルール②:入力エラーは “例外” じゃなく “ドメインエラー” にする💗
入力ミスはユーザーが直せる“想定内の失敗”だよね🙂 だから Resultで返すのが相性◎🎁
ルール③:エラーメッセージは「表示用」と「ログ用」を分ける🧾🔒
- 表示:やさしい言葉💬🌸
- ログ:開発者が調査できる情報🔎🧑💻
20-4. まずは王道:スキーマバリデーション(Zod例)🧪🧩
フォーム/JSONの検証で超よく使われる形だよ🙂
Zodは safeParse で「例外にしない検証」ができて、さらに flatten() で formErrors / fieldErrors に整形できるのが強い✨ (Zod)
20-4-1. 例:会員登録入力を“入口で”パースする🎀
import { z } from "zod";
// 1) 入力スキーマ(入口の期待値)
const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
age: z.coerce.number().int().min(18).max(120), // "20" みたいな文字列も数値化したい例
});
// 2) 型はスキーマから作る(重複を避ける)
type RegisterInput = z.infer<typeof RegisterSchema>;
// 3) 入力エラー(ドメイン)
type InvalidInputError = {
kind: "InvalidInput";
formErrors: string[];
fieldErrors: Record<string, string[]>;
};
// 4) Result(超ミニ版)
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
const err = <E>(error: E): Err<E> => ({ ok: false, error });
// 5) 入口の正規化関数(parse)
export function parseRegisterInput(raw: unknown): Result<RegisterInput, InvalidInputError> {
const r = RegisterSchema.safeParse(raw);
if (!r.success) {
const flat = r.error.flatten(); // formErrors / fieldErrors に分かれる
return err({
kind: "InvalidInput",
formErrors: flat.formErrors,
fieldErrors: flat.fieldErrors,
});
}
return ok(r.data);
}
flatten() で フォーム全体のエラー(formErrors) と 項目別(fieldErrors) に分かれるのがめっちゃ便利だよ🫶✨(次の第21章に直結!) (Zod)
20-5. もう1つの有力:Valibot(軽量・モジュール型)🪶✨
Valibotは 型安全&モジュール指向を押してるスキーマライブラリで、safeParse が用意されてるよ🙂 (Valibot)
「必要な機能だけ取り込む」思想なので、用途によってはかなり相性いい🧩✨
import * as v from "valibot";
const RegisterSchema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
age: v.pipe(v.number(), v.minValue(18), v.maxValue(120)),
});
export function parseRegisterInput(raw: unknown) {
const r = v.safeParse(RegisterSchema, raw); // 例外にしない
if (!r.success) {
// ここで「自分のInvalidInputError形式」に変換して返す
}
return r.output;
}
safeParse が公式APIとして用意されてるのがポイントだよ😊 (Valibot)
20-6. API/契約が絡むなら:JSON Schema(Ajv)+TypeBox も強い🌐📜
「APIの契約」「OpenAPI」「JSON Schemaでの共有」をやるなら、この系統が便利✨
- Ajv:JSON Schema validator(TypeScript向けの型サポート解説もある) (Ajv)
- TypeBox:TypeScriptっぽく書いて JSON Schema を生成できる (GitHub)
“スキーマを中心に契約を回す” 系の設計で活躍しがちだよ🙂
20-7. 「入力エラー」を“ドメインエラー”として美しく扱うコツ🎀🧠
✅ 入口関数はこう分けると超運用しやすい
parseXxx(raw: unknown):入口の正規化(Resultで返す)usecase(input: Xxx):中では「型がある前提」で進める
こうすると、ユースケース内に「型チェック地獄」が侵入しない🚫😵💫 境界で止めるのが勝ち✌️✨
20-8. ミニ演習📝✨(15〜30分)
お題:検索条件(クエリ)を検証して “入力エラー” で返そう🔎
入力(例):
q:検索文字列(1〜50文字)page:1以上sort:"new"|"popular"のどっちか
やること👇
- スキーマを作る🧩
parseSearchQuery(raw: unknown)を作る🚪- 失敗したら
InvalidInputで返す🎁 - 成功したら “型付き” で返す✨
追加ミッション(強い)💪:
境界値テストケースを10個作る(空文字、長すぎ、page=0、sort=hoge…など)🧪📋
この「境界値リスト作り」、第20章の一番おいしい筋トレだよ😆🔥
20-9. AI活用🤖✨(そのままコピペOK)
① 境界値を網羅したい📋
「q/page/sort の入力について、ありえる境界値・異常値を30個、理由つきで出して」
② エラー文言をやさしくしたい💬🌸
「この fieldErrors を、ユーザーが直しやすい日本語に言い換えて(短く・丁寧に・責めない)」
③ スキーマ設計レビューしてほしい👀
「このスキーマ設計の穴(通ってしまう変な入力)を指摘して、改善案を出して」
20-10. 今どきTypeScript事情(軽くメモ)🧠✨
- TypeScript 5.9系が現行安定版として提供されていて、
import deferなどの新しめ仕様も追ってるよ (Microsoft for Developers) - さらに次の大きな流れとして TypeScript 6/7(ネイティブ移行の話)が公式に進捗共有されてるよ (Microsoft for Developers)
この章の結論(入力チェック)は、TypeScriptのバージョンが進んでも ずっと重要なまま🥹✨
まとめ🎁✨(この章で持ち帰ること)
- 外部入力はまず
unknown🫥 - 境界で parse/validate して、成功だけを中に通す🚪
- 入力ミスは例外じゃなく ドメインエラー(Result) 💗
fieldErrors/formErrors形式に落とすと、次章(フォームUX)で爆伸びする🫶 (Zod)
次は第21章で、この fieldErrors を使って “どこが悪いか一瞬で分かるUX” を作っていくよ🫶📝✨
(第20章で作った InvalidInputError が、そのまま武器になる!⚔️😊)