LCOV - code coverage report
Current view: top level - adapter/validation/rules - mapper.go Coverage Total Hit
Test: coverage.lcov Lines: 0.0 % 105 0
Test Date: 2026-04-14 06:42:22 Functions: - 0 0

            Line data    Source code
       1              : // Package rules は、カスタムバリデーションルールとそのエラー変換ユーティリティを提供します。
       2              : // ここでは validator.ValidationErrors を i18n 対応のエラーマップ形式に変換します。
       3              : package rules
       4              : 
       5              : import (
       6              :         "log/slog"
       7              :         "strings"
       8              : 
       9              :         "github.com/go-playground/validator/v10"
      10              : 
      11              :         "resume/internal/shared/util"
      12              : )
      13              : 
      14              : // ErrorItem は、1 件のバリデーションエラー項目を表す構造体です。
      15              : // 各エラーの翻訳キー(Code)と、埋め込みパラメータ(Params)を保持します。
      16              : type ErrorItem struct {
      17              :         Code   string         `json:"code"`
      18              :         Params map[string]any `json:"params"`
      19              : }
      20              : 
      21              : // normalizeRuleTagLowerCamel は、validator のルールタグを lowerCamel に正規化します。
      22              : // - snake/kebab/Pascal/camel/UPPER いずれの入力でも受ける
      23              : // - go-playground/validator の一部タグ表記("startswith"/"endswith")も camel に寄せる
      24            0 : func normalizeRuleTagLowerCamel(tag string) string {
      25            0 :         t := strings.TrimSpace(tag)
      26            0 :         // まず kebab を snake に
      27            0 :         t = strings.ReplaceAll(t, "-", "_")
      28            0 : 
      29            0 :         // validator の素のタグが "startswith"/"endswith" のため補正
      30            0 :         switch t {
      31            0 :         case "startswith":
      32            0 :                 t = "starts_with"
      33            0 :         case "endswith":
      34            0 :                 t = "ends_with"
      35              :         }
      36              : 
      37              :         // 何が来ても lowerCamel に統一
      38            0 :         return util.ToCamel(t)
      39              : }
      40              : 
      41              : // region KeyStore
      42              : 
      43              : // MessageKeyStore は「キーが辞書に存在するかだけ」を見るためのインターフェース。
      44              : type MessageKeyStore interface {
      45              :         HasKey(key string) bool
      46              : }
      47              : 
      48              : var msgKeyStore MessageKeyStore
      49              : 
      50              : // SetMessageKeyStore は、バリデーションメッセージ用のキー存在チェックに使用する
      51              : // MessageKeyStore 実装をグローバルに設定します。
      52            0 : func SetMessageKeyStore(s MessageKeyStore) {
      53            0 :         msgKeyStore = s
      54            0 : }
      55              : 
      56            0 : func hasValidationKey(key string) bool {
      57            0 :         if msgKeyStore == nil {
      58            0 :                 slog.Debug("hasValidationKey: msgKeyStore is nil", "key", key)
      59            0 :                 return false
      60            0 :         }
      61            0 :         ok := msgKeyStore.HasKey(key)
      62            0 :         slog.Debug("hasValidationKey", "key", key, "ok", ok)
      63            0 :         return ok
      64              : }
      65              : 
      66              : // endregion
      67              : 
      68              : // region Key Builders
      69              : 
      70              : // rulesKey は、通常のバリデーションルール用のメッセージキーを生成します。
      71            0 : func rulesKey(tagLowerCamel string) string { return "validation.rules." + tagLowerCamel }
      72              : 
      73              : // customKey は、カスタムバリデーションルール用のメッセージキーを生成します。
      74            0 : func customKey(tagLowerCamel string) string { return "validation.custom." + tagLowerCamel }
      75              : 
      76              : // fieldSpecificKey は、フィールド固有のバリデーションメッセージキーを生成します。
      77            0 : func fieldSpecificKey(scope, fieldKeyLowerCamel, tagLowerCamel string) string {
      78            0 :         return "validation.fieldspecific." + scope + "." + fieldKeyLowerCamel + "." + tagLowerCamel
      79            0 : }
      80              : 
      81              : // endregion
      82              : 
      83              : // MapValidationErrors は validator.ValidationErrors を i18n 対応の
      84              : // map[string][]ErrorItem 形式に変換します。
      85              : // scope にはドメインスコープ(例: "domain.address")を指定します。
      86              : func MapValidationErrors(
      87              :         verrs validator.ValidationErrors,
      88              :         scope string, // 例: "domain.address"
      89            0 : ) map[string][]ErrorItem {
      90            0 : 
      91            0 :         out := map[string][]ErrorItem{}
      92            0 : 
      93            0 :         for _, fe := range verrs {
      94            0 :                 fieldLowerCamel := util.ToCamel(fe.Field()) // ex) address_line1 -> addressLine1
      95            0 :                 base := scope + "." + fieldLowerCamel       // ex) domain.address.addressLine1
      96            0 :                 //legacyKey := "validation.fields." + base    // 旧互換
      97            0 :                 labelKey := base + ".label" // 推奨: domain.*.label(存在判定しないでキーをそのまま渡す)
      98            0 :                 //fieldParam := labelKey                                // デフォは labelKey
      99            0 :                 tagLowerCamel := normalizeRuleTagLowerCamel(fe.Tag()) // ex) startswith -> startsWith
     100            0 : 
     101            0 :                 // ★ sort_key は oneof として扱う(コードとテンプレ両方を oneof に統一)
     102            0 :                 if tagLowerCamel == "sortKey" {
     103            0 :                         tagLowerCamel = "oneof"
     104            0 :                 }
     105              : 
     106            0 :                 tagSnake := util.ToSnake(tagLowerCamel)
     107            0 : 
     108            0 :                 // 直接キーを渡す(フロント側で解決)。未定義時はそのままキー表示になるが、Humanizeより整合的。
     109            0 :                 //fieldParam := labelKey
     110            0 : 
     111            0 :                 // パラメータ(辞書側で {field}, {param}, {min}, {max}, {len}, {value} などを利用)
     112            0 :                 p := map[string]any{"field": labelKey}
     113            0 : 
     114            0 :                 if param := strings.TrimSpace(fe.Param()); param != "" {
     115            0 :                         p["param"] = param
     116            0 : 
     117            0 :                         // ★ sort_key = identities の時、allowedSortKeys["identities"] を values に展開する
     118            0 :                         if tagLowerCamel == "oneof" { // sort_key → oneof に統一されている
     119            0 :                                 if keys, ok := SortKeyAllowed[param]; ok && len(keys) > 0 {
     120            0 :                                         // "created_at provider uid email_at_signup"
     121            0 :                                         joined := strings.Join(keys, " ")
     122            0 :                                         p["values"] = joined // ★ フロントが欲しかった最終形
     123            0 :                                         p["oneof"] = joined  // 従来 param の代わりに oneof をより正確に
     124            0 :                                 } else {
     125            0 :                                         // フォールバック(パラメータ名だけ)
     126            0 :                                         p["values"] = param
     127            0 :                                         p["oneof"] = param
     128            0 :                                 }
     129              :                         }
     130              : 
     131            0 :                         switch tagLowerCamel {
     132            0 :                         case "min":
     133            0 :                                 p["min"] = param
     134            0 :                         case "max":
     135            0 :                                 p["max"] = param
     136            0 :                         case "len":
     137            0 :                                 p["len"] = param
     138            0 :                         case "oneof":
     139            0 :                                 p["oneof"] = param
     140            0 :                         case "gte", "lte", "gt", "lt", "gtfield", "gtefield", "ltfield", "ltefield":
     141            0 :                                 p["value"] = param
     142              :                                 // ToDo: gt,gte,lt,lteの場合は、paramにドメインキーも付与して辞書に差し込みたいかも。(フロントと接続後検証してチケット化)
     143              :                         }
     144              :                 }
     145              : 
     146              :                 // 3種類の候補キーを生成
     147            0 :                 fsKey := fieldSpecificKey(scope, fieldLowerCamel, tagLowerCamel)
     148            0 :                 cKey := customKey(tagLowerCamel)
     149            0 :                 rKey := rulesKey(tagLowerCamel)
     150            0 :                 //key := rulesKey(tagLowerCamel)
     151            0 : 
     152            0 :                 // --- 検索用(辞書内に存在するかを見る)キー: snake ベース ---
     153            0 :                 //     ※ builder は文字列を連結しているだけなので、最後の引数に snake を渡しても問題なし。
     154            0 :                 fsKeyLookup := fieldSpecificKey(scope, fieldLowerCamel, tagSnake)
     155            0 :                 cKeyLookup := customKey(tagSnake)
     156            0 :                 // rKeyLookup := rulesKey(tagSnake) // 今回は rules は存在チェックしなくてもOKなら未使用でよい
     157            0 : 
     158            0 :                 // 優先度:フィールド固有 > custom > rules
     159            0 :                 key := rKey
     160            0 :                 if hasValidationKey(cKeyLookup) {
     161            0 :                         key = cKey
     162            0 :                 }
     163            0 :                 if hasValidationKey(fsKeyLookup) {
     164            0 :                         key = fsKey
     165            0 :                 }
     166              : 
     167            0 :                 out[base] = append(
     168            0 :                         out[base],
     169            0 :                         ErrorItem{
     170            0 :                                 Code:   key,
     171            0 :                                 Params: p,
     172            0 :                         },
     173            0 :                 )
     174              :         }
     175            0 :         return out
     176              : }
        

Generated by: LCOV version 2.3.1-1