TypeScriptで柔軟なバリデーションクラスを実装する

フォーム入力値のバリデーションを簡単に行うための `Validation` クラスをTypeScriptで実装しました。  
このクラスは共通のバリデーションルールをサポートし、カスタムルールも定義できます。

---

## ✅ **バリデーション仕様**
### **1. 共通バリデーション**
- `required`: 必須入力
- `maxLength`: 最大文字数
- `minLength`: 最小文字数
- `rangeLength`: 文字数の範囲
- `min` / `max`: 数値の範囲
- `between`: 数値の最小値～最大値
- `pattern`: 正規表現チェック
- `equalsTo`: 他の入力値と一致

### **2. 特定の入力タイプ向けバリデーション**
- `email`: メールアドレス形式
- `date`: `YYYY/MM/DD` の日付形式
- `alphaNumeric`: 英数字のみ
- `alpha`: 英字のみ
- `numeric`: 数字のみ
- `checkboxMin` / `checkboxMax`: チェックボックスの選択数制限

### **3. カスタムバリデーション**
以下のように定義可能：
```typescript
const customValidators = {
  startsWithA: (value: string) => {
    return value.startsWith("A")
      ? { valid: true }
      : { valid: false, message: "Value must start with 'A'.\n'A' で始まる必要があります。" };
  },
};
```

---
## ✅ **TypeScript `Validation` クラス**

```typescript
class Validation {
  private customValidators: { [key: string]: (value: any, ruleValue: any) => { valid: boolean; message?: string } };
  private errorMessages: { [key: string]: string } = {
    required: "[{field}] is required.\n[{field}] は必須です。",
    maxLength: "[{field}] must be at most {max} characters.\n[{field}] の最大文字数は {max} です。",
    minLength: "[{field}] must be at least {min} characters.\n[{field}] の最小文字数は {min} です。",
    rangeLength: "[{field}] must be between {min} and {max} characters.\n[{field}] の文字数は {min}～{max} です。",
    min: "[{field}] must be at least {min}.\n[{field}] の最小値は {min} です。",
    max: "[{field}] must be at most {max}.\n[{field}] の最大値は {max} です。",
    between: "[{field}] must be between {min} and {max}.\n[{field}] は {min}～{max} の範囲である必要があります。",
    pattern: "[{field}] format is invalid.\n[{field}] の形式が正しくありません。",
    equalsTo: "[{field}] must match {targetField}.\n[{field}] は {targetField} と一致する必要があります。",
    email: "[{field}] must be a valid email address.\n[{field}] は有効なメールアドレスである必要があります。",
    date: "[{field}] must be a valid date (YYYY/MM/DD).\n[{field}] は YYYY/MM/DD 形式の日付である必要があります。",
    alphaNumeric: "[{field}] must contain only letters and numbers.\n[{field}] は英数字のみ使用できます。",
    alpha: "[{field}] must contain only letters.\n[{field}] は英字のみ使用できます。",
    numeric: "[{field}] must contain only numbers.\n[{field}] は数字のみ使用できます。",
    checkboxMin: "[{field}] must select at least {min} options.\n[{field}] は最低 {min} 個選択してください。",
    checkboxMax: "[{field}] must select at most {max} options.\n[{field}] は最大 {max} 個まで選択できます。",
  };

  constructor(customValidators: { [key: string]: (value: any, ruleValue: any) => { valid: boolean; message?: string } } = {}) {
    this.customValidators = customValidators;
  }

  /**
   * 指定されたフィールドの値をバリデーションする
   * @param {string} fieldName - 検証するフィールド名
   * @param {string | Array} value - 入力値（チェックボックスの場合は配列）
   * @param {Object} rules - バリデーションルールのオブジェクト
   * @return {Object} - `valid` (boolean), `errors` (エラーメッセージの配列)
   */
  validate(fieldName: string, value: any, rules: { [key: string]: any }) {
    const errors: string[] = [];

    for (const rule in rules) {
      const ruleValue = rules[rule];
      let valid = true;
      let errorMessage = this.errorMessages[rule]?.replace("{field}", fieldName);

      switch (rule) {
        case "required":
          valid = value !== undefined && value !== null && value !== "";
          break;
        case "maxLength":
          valid = typeof value === "string" && value.length <= ruleValue;
          errorMessage = errorMessage.replace("{max}", ruleValue);
          break;
        case "minLength":
          valid = typeof value === "string" && value.length >= ruleValue;
          errorMessage = errorMessage.replace("{min}", ruleValue);
          break;
        case "rangeLength":
          valid = typeof value === "string" && value.length >= ruleValue[0] && value.length <= ruleValue[1];
          errorMessage = errorMessage.replace("{min}", ruleValue[0]).replace("{max}", ruleValue[1]);
          break;
        case "min":
          valid = parseFloat(value) >= ruleValue;
          errorMessage = errorMessage.replace("{min}", ruleValue);
          break;
        case "max":
          valid = parseFloat(value) <= ruleValue;
          errorMessage = errorMessage.replace("{max}", ruleValue);
          break;
        case "between":
          valid = parseFloat(value) >= ruleValue[0] && parseFloat(value) <= ruleValue[1];
          errorMessage = errorMessage.replace("{min}", ruleValue[0]).replace("{max}", ruleValue[1]);
          break;
        case "pattern":
          valid = new RegExp(ruleValue).test(value);
          break;
        case "equalsTo":
          valid = value === ruleValue;
          errorMessage = errorMessage.replace("{targetField}", ruleValue);
          break;
        case "email":
          valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
          break;
        case "date":
          valid = /^\d{4}\/\d{2}\/\d{2}$/.test(value);
          break;
        case "alphaNumeric":
          valid = /^[a-zA-Z0-9]+$/.test(value);
          break;
        case "alpha":
          valid = /^[a-zA-Z]+$/.test(value);
          break;
        case "numeric":
          valid = /^[0-9]+$/.test(value);
          break;
        case "checkboxMin":
          valid = Array.isArray(value) && value.length >= ruleValue;
          errorMessage = errorMessage.replace("{min}", ruleValue);
          break;
        case "checkboxMax":
          valid = Array.isArray(value) && value.length <= ruleValue;
          errorMessage = errorMessage.replace("{max}", ruleValue);
          break;
        default:
          if (this.customValidators[rule]) {
            const result = this.customValidators[rule](value, ruleValue);
            valid = result.valid;
            if (!valid && result.message) errorMessage = result.message;
          }
      }

      if (!valid && errorMessage) errors.push(errorMessage);
    }

    return { valid: errors.length === 0, errors };
  }

  /**
   * 複数のフィールドをまとめてバリデーションする
   * @param {Object} fields - フィールドごとの入力値とルールのオブジェクト
   * @return {Object} - `valid` (boolean), `results` (各フィールドのバリデーション結果)
   */
  validateAll(fields: { [key: string]: { value: any; rules: { [key: string]: any } } }) {
    let allValid = true;
    const results: { [key: string]: { valid: boolean; errors: string[] } } = {};

    for (const field in fields) {
      const { value, rules } = fields[field];
      const result = this.validate(field, value, rules);
      results[field] = result;
      if (!results[field].valid) allValid = false;
    }

    return { valid: allValid, results };
  }
}

```

---
## ✅ **使い方**

```typescript
const validator = new Validation({
  startsWithA: (value) => {
    return value.startsWith("A")
      ? { valid: true }
      : { valid: false, message: "Value must start with 'A'.\n'A' で始まる必要があります。" };
  },
});

const result = validator.validate("username", "Abe", { required: true, minLength: 3 });
console.log(result);

const allResult = validator.validateAll({
  email: { value: "test@example.com", rules: { required: true, email: true } },
});
console.log(allResult);
```
---
## 🚀 **まとめ**

この Validation クラスを使用すれば、簡単にフォームバリデーションが実装できます。
また、カスタムバリデーションを追加することで、柔軟に対応可能です。
ぜひ、プロジェクトに取り入れてみてください！