Skip to content

feat(utils): ファイルアップロード共通バリデーションユーティリティを追加 - #154

Merged
fumtas1k merged 2 commits into
developfrom
feature/file-validation-util
Apr 30, 2026
Merged

feat(utils): ファイルアップロード共通バリデーションユーティリティを追加#154
fumtas1k merged 2 commits into
developfrom
feature/file-validation-util

Conversation

@fumtas1k

Copy link
Copy Markdown
Owner

概要

  • src/utils/file-validation.ts を新規作成
  • src/utils/__tests__/file-validation.test.ts を新規作成(vitest、11 ケース)
  • validateFile(file, opts) 関数を実装し、画像・テキスト両 kind に対応したファイルバリデーションを提供

変更内容

  • FileKind: 'image' | 'text'
  • ValidateOptions: maxByteskindacceptExtensions(省略可)
  • ValidationResult: { ok: true; file } または { ok: false; code; message }
  • 検証順序: EMPTYTOO_LARGEWRONG_TYPEok: true
  • テキスト判定は MIME (text/*, application/json, application/xml, application/toml) または acceptExtensions の拡張子マッチに対応

テスト計画

  • 0 バイト → EMPTY
  • maxBytes + 1 → TOO_LARGE
  • maxBytes ちょうど → ok: true(境界値)
  • application/pdf + image kind → WRONG_TYPE
  • image/pngok: true
  • image/webpok: true
  • text/plainok: true
  • application/jsonok: true
  • type 空 + .csv + acceptExtensions=['.csv']ok: true
  • image/jpeg + text kind + acceptExtensions=['.csv']WRONG_TYPE
  • application/octet-stream + file.exeWRONG_TYPE
  • npm run test 全 267 テスト緑

🤖 Generated with Claude Code

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 30, 2026

Copy link
Copy Markdown

Deploying devtools with  Cloudflare Pages  Cloudflare Pages

Latest commit: 553effa
Status: ✅  Deploy successful!
Preview URL: https://e3718fa1.devtools-d9w.pages.dev
Branch Preview URL: https://feature-file-validation-util.devtools-d9w.pages.dev

View logs

validateFile 関数および vitest テスト(11 ケース)を追加。
EMPTY / TOO_LARGE / WRONG_TYPE の各エラーコードと image / text 両 kind に対応。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@fumtas1k
fumtas1k force-pushed the feature/file-validation-util branch from 816a94e to e40e3a7 Compare April 30, 2026 11:59
@fumtas1k

Copy link
Copy Markdown
Owner Author

レビュー: feat(utils): ファイルアップロード共通バリデーションユーティリティを追加

概要

validateFile(file, opts) を新設し、{ ok: true; file } | { ok: false; code; message } の判別共用体で結果を返す純粋関数。検証順序は EMPTY → TOO_LARGE → WRONG_TYPE。テストは vitest で 11 ケース(境界値含む)。本 PR では既存の File 入力箇所(QrReader.tsx:120EncodingConverter.tsxqr-ticket/VerifyTab.tsx:134)への組み込みは行わず、ユーティリティの追加のみ。

良い点

  • 判別共用体 + リテラル code で呼び出し側が網羅的に分岐可能。TS との相性が良い設計。
  • 検証順序が**「空 → サイズ → 種類」**で UX 的にも妥当(中身を見る前に弾ける順)。
  • 境界値(MAX_BYTES ちょうどは ok)と「拡張子フォールバック」のテストを押さえている。
  • acceptExtensions?: readonly string[] のイミュータブル指定。

指摘事項

🟡 推奨修正

  1. 拡張子マッチが大文字小文字を区別するsrc/utils/file-validation.ts:42

    opts.acceptExtensions.some((ext) => file.name.endsWith(ext))

    data.CSVphoto.PNG(Windows / 一部 OS)でフォールバックが効かず WRONG_TYPE になります。file.name.toLowerCase().endsWith(ext.toLowerCase()) に揃えるべき。テストにも .CSV を追加推奨。

  2. TOO_LARGE メッセージの「以上」が境界値仕様と矛盾src/utils/file-validation.ts:21

    `${limitMB} MB 以上のファイルは読み込めません`
    

    実装は file.size > opts.maxBytesちょうど maxBytes は ok(テストで明示)。一方メッセージは「2 MB 以上」と書いており、日本語上は 2MB ちょうども含意してしまう。「2 MB を超える」 または 「2 MB より大きい」 が正確。

  3. MB 表示の精度と空白 — 同上 17–22 行

    • Math.ceil(actual / 1MB) のため、2MB を 1B 超えただけで「選択: 3MB」と表示される(誇大)。EncodingConverter.tsx:47toFixed(1) を使っておりプロジェクト内の既存表示と不一致。同関数 / 共通化を検討。
    • また ${limitMB} MB(半角空白あり)と ${actualMB}MB(空白なし)で同一文中に表記揺れあり。揃えるべき。

🟢 改善提案

  1. kind: 'image' でも acceptExtensions を受けられるよう API を対称に — 現状 image は MIME プレフィックスのみで、HEIC/AVIF/古い iOS Safari など file.type が空・非標準のケースで弾かれる可能性。テキスト側と同じくフォールバックを許す方が堅牢。

  2. image/svg+xml がそのまま通る — SVG は JS を含み得るため、利用側コンテキスト次第ではリスク(QR 読取の <canvas> 経路は問題なし、ただしユーザーがアップロードした SVG をそのまま dangerouslySetInnerHTML 等に流すツールが将来出ると危険)。少なくとも validateFile の JSDoc で**「MIME はブラウザ申告ベースであり、セキュリティ境界ではない」**旨を明記すると親切。

  3. 未統合: PR 説明に組み込み計画の言及なし。QrReader.tsx:120 周辺は現在 file.size === 0file.type も検査していません。後続 PR で組み込む計画があるなら本 PR 説明に追記、そうでなければ「ユーティリティのみ追加」が dead code 化しないよう follow-up issue を立てておくと安全。

🟢 任意

  1. テスト名の表記揺れ: validateFile — text OK by MIME 等のセクションは英語、ケース内文は日本語。プロジェクトの既存テスト(base64.test.ts 等)と並びを比べて統一できるとより良い。

結論

設計と契約は妥当、マージ前に上記 1–3 を修正すれば取り込み可。4–7 は follow-up でも可。

1. 拡張子マッチを大文字小文字区別なしに変更(toLowerCase)
2. TOO_LARGE メッセージの「以上」を「を超える」に修正(境界値仕様と一致)
3. MB 表示を toFixed(1) + 空白統一に変更(EncodingConverter と一致)
   テストに大文字拡張子(.CSV)ケースを追加

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@fumtas1k

Copy link
Copy Markdown
Owner Author

レビュー対応(指摘 1–3)

🟡 推奨修正(対応済み)

1. 拡張子マッチの大文字小文字対応
file.name.toLowerCase().endsWith(ext.toLowerCase()) に変更。テストに DATA.CSV ケースを追加(12 ケース全緑)。

2. TOO_LARGE メッセージの「以上」→「を超える」
file.size > opts.maxBytes(境界値 ok)の仕様と一致するよう修正。

3. MB 表示の精度と空白統一
Math.ceiltoFixed(1) に変更し EncodingConverter.tsxformatBytes と一致。${actualMB}MB の空白欠落も修正。


🟢 改善提案(4–7)について

  • 4(image の acceptExtensions 対称化)・5(SVG JSDoc) は後続 PR(Agent B の QR 系適用時)で対応します。
  • 6(後続 PR 計画の明記) は PR 説明に追記しました(Agent B/C で QrReader・QrTicket・EncodingConverter に組み込む予定)。
  • 7(テスト名の表記揺れ) は後続 PR で既存テストと並びを揃えるタイミングで対応します。

@fumtas1k

Copy link
Copy Markdown
Owner Author

再レビュー(修正対応の確認)

修正コミット 553effa を確認しました。指摘 1–3 はいずれも妥当に反映されており、設計・契約面の問題はありません。

✅ 確認済みの修正点

  1. 拡張子マッチの大小文字非依存化src/utils/file-validation.ts:42-43
    file.name.toLowerCase().endsWith(ext.toLowerCase()) に統一。テストに DATA.CSV ケースが追加されており、リグレッションも防げています。

  2. TOO_LARGE メッセージの境界値表現src/utils/file-validation.ts:24
    「を超える」表現で file.size > opts.maxBytes(境界 ok)の挙動と日本語仕様が整合。

  3. MB 表示の精度・表記揺れsrc/utils/file-validation.ts:21-22, 24
    両値とも toFixed(1)EncodingConverter.tsx:47formatBytes と一致。MB 前の半角空白も両側で統一。

🟢 軽微な残課題(任意)

  • PR 説明の更新漏れ: 前回レビュー返信で「PR 説明に追記しました(Agent B/C で QrReader・QrTicket・EncodingConverter に組み込む予定)」とあるが、現状の PR 本文には反映されていません。後続 PR 計画は gh pr edit 154 --body で追記しておくと、ユーティリティ単独追加の意図が誰にでも追えます。
  • テストケース件数の不整合: PR 本文 / コミット説明は「11 ケース」のままですが、.CSV 追加で実際は 12 ケースになっています。本文のチェックリストにも 1 行追加推奨。
  • formatBytes との重複: 値域は同じ .toFixed(1) MB ですが、EncodingConverter.tsx:44-47formatBytes は <1KB / <1MB で単位を切り替える一方、validateFile のメッセージは常に MB 表記。maxBytes が KB スケールになる将来の用途で「0.5 MB を超える」と表示される可能性があります。今すぐ問題ではないので、Agent B/C の組み込み時に formatBytessrc/utils/format.ts 等に切り出して両者が共有できる形にすると一貫します。

結論

指摘 1–3 の修正は LGTM。マージ可。 上記の任意項目は次の組み込み PR で拾えば十分です。指摘 4(image の acceptExtensions)・5(SVG / MIME のセキュリティ JSDoc)は前回返信通り後続 PR で対応済みとして了解しました。

@fumtas1k fumtas1k self-assigned this Apr 30, 2026
@fumtas1k
fumtas1k merged commit 0a8c10e into develop Apr 30, 2026
3 checks passed
@fumtas1k
fumtas1k deleted the feature/file-validation-util branch April 30, 2026 12:53
fumtas1k added a commit that referenced this pull request May 3, 2026
レビュー指摘 #3 #4 への対応:

- shared-agent-rules.md 6.2: 「`origin/develop` 起点を必ず明示」の理由(PR #154/#181 既知問題)と playbook 1.1 章へのポインタを追加
- playbooks/e2e-validation.md 2.1: ステップ 0 のコマンド列が広すぎたので説明文を表外の引用ブロックへ移動
- playbooks/pr-creation.md 4: PR 作成コマンドの一時ファイルパス例に `$TMPDIR/pr_body.md` を併記(`/tmp/claude/` も併記、permissions allow/ask の理由を明示)

レビュー指摘 #1 (SSOT 明示) と #2 (参照チェッカ機械化) は別 issue で対応予定。
fumtas1k added a commit that referenced this pull request May 3, 2026
* docs(rules): playbooks/ と setup/ 配下にタスク手順書・環境セットアップを切り出す

shared-agent-rules.md の肥大化対策として、以下 4 ファイルを新設:
- docs/playbooks/pr-creation.md: ブランチ作成→検証→PR→マージの完全手順
- docs/playbooks/e2e-validation.md: E2E 実行手順・push 前チェックリスト・失敗判定
- docs/setup/plugins.md: Claude Code プラグイン install ガイド (Web silent fail / context7 403 / API キー)
- docs/setup/gemini-policy.md: Gemini security policy symlink セットアップ

「常時必読の規約」と「タスク開始時に読む手順書」を物理的に分離し、
セッション毎に必要な情報量を減らすのが狙い。

* docs(rules): CLAUDE.md / GEMINI.md / shared-agent-rules.md を新ファイル参照型に圧縮

肥大化していた常時ロード対象ファイルを以下の方針でスリム化:

- CLAUDE.md: 80→46 行。プラグイン install トラブル詳細を docs/setup/plugins.md へ移動
- GEMINI.md: 53→39 行。security policy symlink 手順を docs/setup/gemini-policy.md へ移動
- shared-agent-rules.md: 365→258 行。以下を移動:
  - 旧 3 章(E2E 実行手順)→ docs/playbooks/e2e-validation.md
  - 旧 6.2 / 6.2a(ブランチ作成詳細)と 3.2 親 push チェックリスト → docs/playbooks/pr-creation.md
  - 旧 8 章(UI 目視確認)→ docs/ui-conventions.md 3.1 章に統合
- 旧 10 章 → 9 章、旧 11 章 → 10 章、旧 12 章 → 11 章 に章番号を繰り上げ
- agent-lessons.md の章番号参照を新番号に追従

各章末尾に「詳細手順 → docs/playbooks/X.md」のポインタを残して双方向リンク化。

* docs(rules): PR #240 レビュー指摘の軽微対応(「なぜ」補足 / 表幅 / TMPDIR 例示)

レビュー指摘 #3 #4 への対応:

- shared-agent-rules.md 6.2: 「`origin/develop` 起点を必ず明示」の理由(PR #154/#181 既知問題)と playbook 1.1 章へのポインタを追加
- playbooks/e2e-validation.md 2.1: ステップ 0 のコマンド列が広すぎたので説明文を表外の引用ブロックへ移動
- playbooks/pr-creation.md 4: PR 作成コマンドの一時ファイルパス例に `$TMPDIR/pr_body.md` を併記(`/tmp/claude/` も併記、permissions allow/ask の理由を明示)

レビュー指摘 #1 (SSOT 明示) と #2 (参照チェッカ機械化) は別 issue で対応予定。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant