File size: 4,640 Bytes
48471f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158bb31
48471f7
 
158bb31
48471f7
 
 
 
158bb31
 
 
 
 
 
 
48471f7
 
 
 
 
158bb31
48471f7
 
 
 
 
158bb31
48471f7
 
 
158bb31
48471f7
 
 
 
158bb31
 
 
48471f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158bb31
 
 
48471f7
 
 
 
 
 
 
 
 
 
 
 
158bb31
48471f7
158bb31
48471f7
 
158bb31
 
 
 
 
48471f7
 
158bb31
 
 
 
 
48471f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158bb31
 
 
 
 
 
 
48471f7
158bb31
48471f7
 
 
 
158bb31
 
 
 
 
 
48471f7
 
 
 
 
 
 
158bb31
 
 
 
 
 
48471f7
158bb31
 
48471f7
 
 
 
 
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package validation

import (
	"errors"
	"fmt"
	"strings"

	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/id"
	ut "github.com/go-playground/universal-translator"
	v10 "github.com/go-playground/validator/v10"
	entranslations "github.com/go-playground/validator/v10/translations/en"
	idtranslations "github.com/go-playground/validator/v10/translations/id"
)

// Constants for supported locales
const (
	LocaleID = "id"
	LocaleEN = "en"
)

// ErrorMessage represents a validation error message
type ErrorMessage struct {
	Field   string `json:"field"`
	Message string `json:"-"`
}

type validator struct {
	validate   *v10.Validate
	translator ut.Translator
}

// validatorInstance adalah instance global dari validator.
var validatorInstance *validator

// New creates a new validation instance with the specified locale
// dan menginisialisasi instance global validatorInstance.
func New(locale string) error {
	v := &validator{}
	parsedLocale := parseLocale(locale)

	uni := ut.New(en.New(), id.New(), en.New())
	translator, found := uni.GetTranslator(parsedLocale)
	if !found {
		return fmt.Errorf("translator not found for locale: %s", parsedLocale)
	}

	validate := v10.New()

	if err := setupValidations(validate); err != nil {
		return fmt.Errorf("failed to setup validations: %w", err)
	}

	if err := setupTranslations(validate, translator, parsedLocale); err != nil {
		return fmt.Errorf("failed to setup translations for locale %s: %w", parsedLocale, err)
	}

	v.validate = validate
	v.translator = translator

	validatorInstance = v // Inisialisasi instance global
	return nil
}

func parseLocale(locale string) string {
	switch strings.ToLower(locale) {
	case "id":
		return LocaleID
	case "en":
		return LocaleEN
	default:
		return LocaleID // Default to Indonesian
	}
}

// setupValidations configures custom validation rules.
func setupValidations(validate *v10.Validate) error {
	rules := NewValidatorRules(&InMemoryOptionSource{})
	if err := rules.RegisterAllCustomRules(validate); err != nil {
		return err
	}
	return nil
}

// setupTranslations configures translations for validation messages.
func setupTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
	// Register default translations based on locale
	if err := registerDefaultTranslations(validate, translator, locale); err != nil {
		return fmt.Errorf("failed to register default translations for locale %s: %w", locale, err)
	}

	// Register custom password validation translation
	err := validate.RegisterTranslation("password", translator,
		func(ut ut.Translator) error {
			return ut.Add("password", "harus mengandung minimal 8 karakter, huruf besar, huruf kecil, dan angka.", true)
		},
		func(ut ut.Translator, fe v10.FieldError) string {
			translated, err := ut.T(fe.Tag(), fe.Field())
			if err != nil {
				return fe.Field() + " is invalid"
			}
			return translated
		},
	)
	if err != nil {
		return fmt.Errorf("failed to register password translation: %w", err)
	}

	return nil
}

// registerDefaultTranslations sets up default translations for the specified locale.
func registerDefaultTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
	switch locale {
	case LocaleID:
		return idtranslations.RegisterDefaultTranslations(validate, translator)
	case LocaleEN:
		return entranslations.RegisterDefaultTranslations(validate, translator)
	default:
		// Fallback to English if the locale is not supported
		return entranslations.RegisterDefaultTranslations(validate, translator)
	}
}

// Validate validates a struct using the global validator instance
// and returns a slice of ErrorMessage.
func Validate(s any) []ErrorMessage {
	if validatorInstance == nil {
		return []ErrorMessage{{Field: "", Message: "Validator belum diinisialisasi. Panggil validation.New() terlebih dahulu."}}
	}
	err := validatorInstance.validate.Struct(s)
	if err != nil {
		return TranslateError(err)
	}
	return nil
}

// TranslateError takes a validation error and translates it using the global translator.
func TranslateError(err error) []ErrorMessage {
	if validatorInstance == nil {
		return nil
	}

	var validationErrors v10.ValidationErrors
	if !errors.As(err, &validationErrors) {
		return nil
	}

	errorMessages := make([]ErrorMessage, 0, len(validationErrors))
	for _, e := range validationErrors {
		fieldLabel := e.Field()
		msg, err := validatorInstance.translator.T(e.Tag(), fieldLabel)
		if err != nil {
			msg = fieldLabel + " is Invalid"
		}

		errorMessages = append(errorMessages, ErrorMessage{
			Field:   e.Tag(),
			Message: msg,
		})
	}

	return errorMessages
}