# 機能設計書 16-バリデーション

## 概要

本ドキュメントは、Decap CMSにおけるエントリフィールドのバリデーション機能の設計を記述する。必須フィールド検証・パス検証・カスタムバリデーションエラーへの対応を含む入力検証処理の仕様を網羅する。

### 本機能の処理概要

バリデーション機能は、エントリの各フィールドに対する入力検証を行い、不正な値が保存されることを防ぐ機能である。バリデーションエラーはRedux状態の`fieldsErrors`に格納され、UIに表示される。

**業務上の目的・背景**：CMSに保存されるコンテンツの品質を保証するため、入力段階でデータの妥当性を検証する必要がある。必須フィールドの未入力、パス形式の不正、カスタムルールへの違反を検出し、ユーザーに修正を促す。

**機能の利用シーン**：エントリ編集画面およびエントリ新規作成画面でフィールド値を変更した際に即座に検証が実行される。また、保存操作時にもfieldsErrorsの存在チェックが行われ、エラーがある場合は保存がブロックされる。

**主要な処理内容**：
1. `changeDraftFieldValidation`: フィールドごとのバリデーションエラーをRedux状態に設定
2. `clearFieldErrors`: 特定フィールドのバリデーションエラーをクリア
3. `validateMetaField`: メタフィールド（path）の特殊バリデーション（パス形式検証・重複チェック）
4. 保存時バリデーション: `persistEntry`/`persistUnpublishedEntry`でfieldsErrorsの存在をチェックし、PRESENCE型エラーがあればトースト通知
5. バリデーションエラー型: PRESENCE（必須）、PATTERN（パターン）、RANGE（範囲）、CUSTOM（カスタム）

**関連システム・外部連携**：各ウィジェットコンポーネントが独自のバリデーションルールを実装し、changeDraftFieldValidationアクションを通じてエラーを報告する。

**権限による制御**：バリデーションはフィールドレベルで定義され、権限による分岐はない。全ユーザーに同一のバリデーションルールが適用される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | フィールド入力値の検証処理。changeDraftFieldValidationアクションによるバリデーションエラー管理 |
| 7 | エントリ新規作成画面 | 主画面 | 新規エントリのフィールド入力値検証。必須フィールドチェックとカスタムバリデーション |

## 機能種別

バリデーション - 入力データの妥当性検証

## 入力仕様

### 入力パラメータ（changeDraftFieldValidation）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| uniquefieldId | string | Yes | フィールドの一意識別子 | - |
| errors | Array<{type, parentIds, message}> | Yes | バリデーションエラーの配列 | 空配列の場合はエラーをクリア |

### 入力パラメータ（validateMetaField）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| state | State | Yes | Redux状態全体 | - |
| collection | Collection | Yes | 対象コレクション | - |
| field | EntryField | Yes | 検証対象フィールド | meta=true かつ name='path' |
| value | string / undefined | Yes | 検証する値 | - |
| t | function | Yes | 翻訳関数 | - |

### 入力データソース

各ウィジェットコンポーネントからのフィールド値変更イベント。ウィジェットが独自にバリデーションルールを評価し、結果をchangeDraftFieldValidationアクションで報告する。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| fieldsErrors | Map<string, Array> | フィールドID別のバリデーションエラーマップ |
| error | {type, message} / false | validateMetaFieldの検証結果 |

### バリデーションエラー型

| エラー型 | 定数名 | 説明 |
|---------|--------|------|
| PRESENCE | ValidationErrorTypes.PRESENCE | 必須フィールドの未入力エラー |
| PATTERN | ValidationErrorTypes.PATTERN | パターン（正規表現）不一致エラー |
| RANGE | ValidationErrorTypes.RANGE | 範囲外の値エラー |
| CUSTOM | ValidationErrorTypes.CUSTOM | カスタムバリデーションエラー |

### 出力先

Redux Store（`entryDraft.fieldsErrors`）

## 処理フロー

### 処理シーケンス

```
■ フィールドバリデーション
1. ウィジェットコンポーネントがフィールド値の変更を検知
   └─ ウィジェット独自のバリデーションルールを評価
2. changeDraftFieldValidationアクションをディスパッチ
   └─ errorsが空の場合: fieldsErrorsから該当フィールドのエラーを削除
   └─ errorsが存在する場合: fieldsErrorsに該当フィールドのエラーを設定
3. UIがfieldsErrorsを参照してエラーメッセージを表示

■ メタフィールドバリデーション（path）
1. パスフィールドの値が変更される
2. validateMetaField関数が呼び出される
3. 値が空の場合: invalidPathエラー
4. getProcessSegmentでサニタイズし、サニタイズ後と異なる場合: invalidPathエラー
5. 既存エントリと同じパスの場合: pathExistsエラー
6. 正常の場合: error: false

■ 保存時バリデーション
1. persistEntry / persistUnpublishedEntryが呼ばれる
2. fieldsErrorsが空でない場合は保存をブロック
3. PRESENCE型エラーがある場合はui.toast.missingRequiredFieldトーストを表示
4. Promise.reject()で保存処理を中断
```

### フローチャート

```mermaid
flowchart TD
    A[フィールド値変更] --> B[ウィジェットバリデーション]
    B --> C{エラーあり?}
    C -->|Yes| D[changeDraftFieldValidation dispatch]
    D --> E[fieldsErrorsにエラー設定]
    C -->|No| F[changeDraftFieldValidation dispatch - 空配列]
    F --> G[fieldsErrorsからエラー削除]
    E --> H[UIにエラー表示]
    G --> I[UIからエラー非表示]

    J[保存ボタン押下] --> K{fieldsErrorsが空?}
    K -->|Yes| L[保存処理実行]
    K -->|No| M{PRESENCE型エラーあり?}
    M -->|Yes| N[missingRequiredFieldトースト]
    M -->|No| O[保存ブロック]
    N --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-16-01 | 即時バリデーション | フィールド値の変更時に即座にバリデーションが実行される | フィールド値変更時 |
| BR-16-02 | 保存ブロック | fieldsErrorsが空でない場合、保存処理はブロックされる | persistEntry時 |
| BR-16-03 | エラークリア | errorsが空配列の場合、該当フィールドのエラーがfieldsErrorsから削除される | changeDraftFieldValidation時 |
| BR-16-04 | パスサニタイズ検証 | metaフィールドのpathはgetProcessSegmentでサニタイズされ、元の値と異なればinvalidPath | validateMetaField時 |
| BR-16-05 | パス重複チェック | 同一コレクション内に同一パスのエントリが既に存在する場合pathExistsエラー | validateMetaField時 |
| BR-16-06 | 自己参照除外 | パス重複チェック時、現在編集中のエントリ自身は除外される | validateMetaField時 |

### 計算ロジック

- パスサニタイズ: `value.split('/').map(getProcessSegment(state.config.slug)).join('/')`
- 既存エントリチェック: `selectEntryByPath(state.entries, collection.get('name'), customPath)`
- 自己参照除外: `existingEntryPath && existingEntryPath !== draftPath`

## データベース操作仕様

### 操作別データベース影響一覧

本機能ではデータベース操作は発生しない。Redux Storeへの状態更新のみである。

### テーブル別操作詳細

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| PRESENCE | 必須エラー | 必須フィールドが未入力 | フィールド横にエラーメッセージ表示、保存時トースト通知 |
| PATTERN | パターンエラー | パターン（正規表現）不一致 | フィールド横にエラーメッセージ表示 |
| RANGE | 範囲エラー | 数値等が許容範囲外 | フィールド横にエラーメッセージ表示 |
| CUSTOM | カスタムエラー | パス不正、パス重複等 | フィールド横にカスタムメッセージ表示 |

### リトライ仕様

リトライは不要。ユーザーがフィールド値を修正すると自動的にバリデーションが再実行される。

## トランザクション仕様

該当なし。Redux状態の更新はアトミックに行われる。

## パフォーマンス要件

- バリデーションはフィールド値変更のたびに実行されるため、軽量であること
- validateMetaFieldはRedux状態の検索（selectEntryByPath）を含むため、大量エントリがある場合の性能に注意

## セキュリティ考慮事項

- クライアントサイドバリデーションのみであり、サーバーサイドでの再検証は行われない
- パス検証はサニタイズ処理を含み、不正なパスの入力を防ぐ

## 備考

- バリデーションエラーの表示はウィジェットコンポーネント側で行われる
- パターンバリデーションはフィールド定義のpatternプロパティで設定される

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | validationErrorTypes.js | `packages/decap-cms-core/src/constants/validationErrorTypes.js` | PRESENCE、PATTERN、RANGE、CUSTOMの4つのエラー型定数（1-6行目） |
| 1-2 | entryDraft.js | `packages/decap-cms-core/src/reducers/entryDraft.js` | initialStateのfieldsErrors: Map()（40行目）の構造 |

**読解のコツ**: fieldsErrorsはMap<string, Array<{type, parentIds, message}>>形式で、キーがフィールドのuniqueId、値がエラーオブジェクトの配列である。

#### Step 2: アクションクリエータを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entries.ts (action) | `packages/decap-cms-core/src/actions/entries.ts` | changeDraftFieldValidation（425-433行目）、clearFieldErrors（435-440行目）、validateMetaField（1022-1055行目） |

**主要処理フロー**:
1. **425-433行目**: `changeDraftFieldValidation` - uniquefieldIdとerrorsをペイロードにDRAFT_VALIDATION_ERRORSをディスパッチ
2. **435-440行目**: `clearFieldErrors` - uniqueFieldIdを指定してDRAFT_CLEAR_ERRORSをディスパッチ
3. **1022-1055行目**: `validateMetaField` - meta+pathフィールドの特殊検証。値の空チェック、サニタイズ後比較、既存パス重複チェック

#### Step 3: リデューサーの状態管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | entryDraft.js | `packages/decap-cms-core/src/reducers/entryDraft.js` | DRAFT_VALIDATION_ERRORSケース（124-129行目）、DRAFT_CLEAR_ERRORSケース（131-134行目） |

**主要処理フロー**:
- **124-129行目**: errors.length === 0の場合はdeleteIn、それ以外はsetInでエラーを設定
- **131-134行目**: uniqueFieldIdを指定してdeleteIn

#### Step 4: 保存時バリデーションチェックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | entries.ts (action) | `packages/decap-cms-core/src/actions/entries.ts` | persistEntry関数（893-977行目）のfieldsErrorsチェック部分（897-919行目） |

**主要処理フロー**:
- **897行目**: `entryDraft.get('fieldsErrors')` でエラーマップ取得
- **901行目**: `!fieldsErrors.isEmpty()` でエラー存在チェック
- **902-903行目**: PRESENCE型エラーの存在チェック
- **906-916行目**: missingRequiredFieldトースト通知
- **918行目**: `Promise.reject()` で保存中断

### プログラム呼び出し階層図

```
ウィジェットコンポーネント
    │
    ├─ changeDraftFieldValidation(uniquefieldId, errors)  [actions/entries.ts:425-433]
    │      └─ entryDraftReducer → DRAFT_VALIDATION_ERRORS  [reducers/entryDraft.js:124-129]
    │
    ├─ clearFieldErrors(uniqueFieldId)  [actions/entries.ts:435-440]
    │      └─ entryDraftReducer → DRAFT_CLEAR_ERRORS  [reducers/entryDraft.js:131-134]
    │
    └─ validateMetaField(state, collection, field, value, t)  [actions/entries.ts:1022-1055]
           ├─ getProcessSegment(state.config.slug)
           ├─ selectCustomPath(collection, entryDraft)
           └─ selectEntryByPath(state.entries, collection, customPath)

persistEntry(collection)  [actions/entries.ts:893-977]
    └─ fieldsErrors.isEmpty() チェック
           ├─ true: 保存処理継続
           └─ false: トースト通知 + Promise.reject()
```

### データフロー図

```
[入力]                        [処理]                           [出力]

フィールド値変更        ───▶  ウィジェットバリデーション  ───▶  バリデーションエラー配列
                               │
                               ▼
                        changeDraftFieldValidation    ───▶  entryDraft.fieldsErrors
                               │                              (Map<fieldId, errors>)
                               ▼
保存ボタン押下          ───▶  persistEntry                    fieldsErrors存在チェック
                        ───▶  fieldsErrors.isEmpty()?         ├─ 空: 保存実行
                                                              └─ 非空: 保存ブロック + 通知
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| entries.ts (action) | `packages/decap-cms-core/src/actions/entries.ts` | ソース | バリデーション関連アクションクリエータ |
| entryDraft.js | `packages/decap-cms-core/src/reducers/entryDraft.js` | ソース | fieldsErrorsの状態管理 |
| validationErrorTypes.js | `packages/decap-cms-core/src/constants/validationErrorTypes.js` | ソース | バリデーションエラー型定数 |
| formatters.ts | `packages/decap-cms-core/src/lib/formatters.ts` | ソース | getProcessSegment（パスサニタイズ） |
