All files / src/validation addressSchema.ts

0% Statements 0/82
0% Branches 0/1
0% Functions 0/1
0% Lines 0/82

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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126                                                                                                                                                                                                                                                           
import { z } from 'zod';
import { toTypedSchema } from '@vee-validate/zod';
import {
  optionalEmptyToUndef,
  stringMax,
  stringMaxOptional,
  jpPostalRe
} from '@/libs/vee/rules';
import { makeMsg, type TFunc } from '@/libs/vee/messages';
import { emptyToNull} from '@/utils'
 
export type AddressForm = {
  purposeId: number | null;       // ← 必須キー + null許可
  countryCode: string | null;     // ← 必須キー + null許可
  administrativeArea?: string;
  locality?: string;
  dependentLocality?: string;
  postalCode?: string;
  addressLine1: string;
  addressLine2?: string;
  addressLine3?: string;
  latitude?: number | null;       // ← スキーマに合わせて null も許可
  longitude?: number | null;      // ← 同上
}
 
export function makeAddressSchema(t: TFunc, L: Record<string, string>) {
  const m = makeMsg(t, L);
 
  const base = z.object({
    purposeId: z.preprocess(
      emptyToNull,
      z.number()
      // z.number({
      //   required_error: m.req('purposeId'),
      //   invalid_type_error: m.req('purposeId')
      // })
        .int()
        .positive(m.positive('purposeId'))
        .nullable()
        .refine(v => v !== null, {
          message: m.req('purposeId')
        })
    ),
 
    countryCode: z.preprocess(
      emptyToNull,
      z.string()
        .length(2, t('validation.rules.len.string', { field: L.countryCode, len: 2 }))
        .transform(s => s.toUpperCase())
        .nullable()
        .refine(v => v !== null, {
          message: m.req('countryCode'),
        })
    ),
 
    administrativeArea: optionalEmptyToUndef()
      .or(stringMax(128, m.max('administrativeArea', 128))),
 
    locality:          stringMaxOptional(128, m.max('locality', 128)),
 
    dependentLocality: stringMaxOptional(128, m.max('dependentLocality', 128)),
 
    postalCode:        optionalEmptyToUndef()
      .or(stringMax(16, m.max('postalCode', 16))),
 
    addressLine1: z
      .string()
      .trim()
      .nonempty(m.req('addressLine1'))
      .min(1, m.min('addressLine1', 1))
      .max(160, m.max('addressLine1', 160)),
 
    addressLine2: stringMaxOptional(160, m.max('addressLine2', 160)),
 
    addressLine3: stringMaxOptional(160, m.max('addressLine3', 160)),
 
    latitude:  z
      .coerce
      .number()                 // ← オプション渡さない
      .nullable()
      .optional()
      // 文字列→数値化されるが、"abc" などは NaN になる。NaN を弾いてメッセージを出す
      .refine(v => v == null || !Number.isNaN(v), {
        message: m.typeNum('latitude'),
      })
      // 範囲チェックも必要なら追記
      .refine(v => v == null || (v >= -90 && v <= 90), {
        message: m.range('latitude', -90, 90),
      }),
 
    longitude: z
      .coerce
      .number()
      .nullable()
      .optional()
      // 文字列→数値化されるが、"abc" などは NaN になる。NaN を弾いてメッセージを出す
      .refine(v => v == null || !Number.isNaN(v), {
        message: m.typeNum('longitude')
      })
      // 範囲チェックも必要なら追記
      .refine(v => v == null || (v >= -180 && v <= 180), {
        message: m.range('longitude', -180, 180),
      }),
 
  })
    // 緯度経度はセットで
    .refine((data) => {
      const latOk = typeof data.latitude  === 'number';
      const lngOk = typeof data.longitude === 'number';
      return (latOk && lngOk) || (!latOk && !lngOk);
    }, { path: ['latitude'], message: m.coordPair() })
    // JP の時の追加ルール
    .superRefine((data, ctx) => {
      if (data.countryCode?.toUpperCase() === 'JP') {
        if (!data.administrativeArea) {
          ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['administrativeArea'], message: m.req('administrativeArea') });
        }
        if (data.postalCode && !jpPostalRe.test(data.postalCode)) {
          ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['postalCode'], message: m.jpPostal('postalCode') });
        }
      }
    });
 
  return toTypedSchema<AddressForm>(base);
}