All files / src/libs/vee fieldCatalog.ts

0% Statements 0/43
100% Branches 1/1
100% Functions 1/1
0% Lines 0/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77                                                                                                                                                         
// src/libs/vee/fieldCatalog.ts
export type TFunc = (key: string, params?: Record<string, any>) => string
 
export type FieldCatalogOptions<Name extends string> = {
  /** 例: 'domain.address' */
  baseKey: string
  /** 例: ['purposeId','countryCode',...] as const */
  names: readonly Name[]
  /** vue-i18n の t */
  t: TFunc
  /** validation.rules.* の既定枝 */
  rulesDefaultBranch?: 'string' | 'number'
}
 
export type ResolveField<Name extends string> = (backendPath: string) => { name: Name }
 
/** 422: code+params → メッセージ(未定義は invalid フォールバック) */
export function buildMsgFromCode(
  t: TFunc,
  rulesDefault: 'string' | 'number' = 'string',
) {
  return function msgFromCode(code?: string, params: any = {}) {
    if (!code) return t('validation.invalid')
 
    const p = params || {}
    const fieldLabel =
      typeof p.fieldKey === 'string' ? t(p.fieldKey) :
        typeof p.field    === 'string' ? t(p.field)    : undefined
 
    // rules.* は .string/.number を補完
    let key = String(code)
    if (key.startsWith('validation.rules.') && !/\.string$|\.number$/.test(key)) {
      key += `.${rulesDefault}`
    }
 
    const len = p.len ?? p.param
    const max = p.max ?? p.param
    const min = p.min ?? p.param
 
    const msg = t(key, { field: fieldLabel, len, max, min })
    if (msg && msg !== key) return msg
 
    // 旧式フォールバック
    if (p?.tag === 'max' && (p?.param || p?.max)) {
      return t('validation.rules.max.string', { field: fieldLabel ?? '', max: p.param || p.max })
    }
    if (p?.tag === 'len' && (p?.param || p?.len)) {
      return t('validation.rules.len.string', { field: fieldLabel ?? '', len: p.param || p.len })
    }
    return t('validation.invalid')
  }
}
 
/** フィールド・カタログ(camel専用)。i18nキー, L/PH, resolveField を提供 */
export function makeFieldCatalog<Name extends string>(opts: FieldCatalogOptions<Name>) {
  const { baseKey, names, t, rulesDefaultBranch = 'string' } = opts
 
  const labelKeyOf = (name: Name) => `${baseKey}.${name}.label`
  const phKeyOf    = (name: Name) => `${baseKey}.${name}.placeholder`
 
  const L  = Object.fromEntries(names.map(n => [n, t(labelKeyOf(n))])) as Record<Name, string>
  const PH = Object.fromEntries(names.map(n => [n, t(phKeyOf(n))]))   as Record<Name, string>
 
  // details のキーは `${baseKey}.${camel}` で来る前提
  const backendFieldMap = Object.fromEntries(names.map(n => [`${baseKey}.${n}`, n])) as Record<string, Name>
 
  const resolveField: ResolveField<Name> = (backendPath: string) => {
    if (backendFieldMap[backendPath]) return { name: backendFieldMap[backendPath] }
    const last = backendPath?.split('.')?.pop() as Name
    return { name: last }
  }
 
  const msgFromCode = buildMsgFromCode(t, rulesDefaultBranch)
 
  return { L, PH, labelKeyOf, phKeyOf, resolveField, msgFromCode }
}