package validator import ( "errors" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" validatorlib "github.com/go-playground/validator/v10" ) // FieldError merepresentasikan 1 field DTO yang gagal validasi. // Field mengikuti nama di tag `json` DTO (bukan nama struct Go), supaya konsisten // dengan nama yang dikirim client di body request. type FieldError struct { Field string `json:"field"` Message string `json:"message"` } // RegisterJSONTagNameFunc membuat validator Gin melaporkan error pakai nama tag `json` // dari DTO (misal "email", "password"), bukan nama struct Go (misal "Email", "Password"). // WAJIB dipanggil sekali saat startup aplikasi (lihat internal/router/router.go), // sebelum ada request yang divalidasi. func RegisterJSONTagNameFunc() { v, ok := binding.Validator.Engine().(*validatorlib.Validate) if !ok { return } v.RegisterTagNameFunc(func(field reflect.StructField) string { name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0] if name == "-" || name == "" { return field.Name } return name }) } func TranslateError(err error) []FieldError { var validationErrs validatorlib.ValidationErrors if !errors.As(err, &validationErrs) { return []FieldError{{ Field: "body", Message: "format request tidak valid, pastikan body berupa JSON yang benar", }} } fieldErrors := make([]FieldError, 0, len(validationErrs)) for _, fe := range validationErrs { fieldErrors = append(fieldErrors, FieldError{ Field: fe.Field(), Message: buildMessage(fe), }) } return fieldErrors } // buildMessage menerjemahkan tag validasi (required, email, min, dst) jadi kalimat // Bahasa Indonesia yang jelas. Tambahkan case baru di sini kalau ada tag validasi baru // yang dipakai di DTO tapi belum ada terjemahannya. func buildMessage(fe validatorlib.FieldError) string { field := fe.Field() switch fe.Tag() { case "required": return fmt.Sprintf("%s wajib diisi", field) case "email": return fmt.Sprintf("%s harus berupa alamat email yang valid", field) case "min": if fe.Kind() == reflect.String { return fmt.Sprintf("%s minimal %s karakter", field, fe.Param()) } return fmt.Sprintf("%s minimal bernilai %s", field, fe.Param()) case "max": if fe.Kind() == reflect.String { return fmt.Sprintf("%s maksimal %s karakter", field, fe.Param()) } return fmt.Sprintf("%s maksimal bernilai %s", field, fe.Param()) case "gte": return fmt.Sprintf("%s minimal bernilai %s", field, fe.Param()) case "lte": return fmt.Sprintf("%s maksimal bernilai %s", field, fe.Param()) case "oneof": return fmt.Sprintf("%s harus salah satu dari: %s", field, fe.Param()) case "len": return fmt.Sprintf("%s harus tepat %s karakter", field, fe.Param()) case "numeric": return fmt.Sprintf("%s harus berupa angka", field) case "alphanum": return fmt.Sprintf("%s hanya boleh berisi huruf dan angka", field) default: return fmt.Sprintf("%s tidak valid", field) } }