Files
2026-04-14 01:21:54 +00:00

301 lines
10 KiB
Go

package errors
import (
"encoding/json"
"fmt"
"strings"
"sync"
)
// MessageStore stores localized messages
type MessageStore struct {
messages map[string]map[string]string // language -> code -> message
mu sync.RWMutex
}
var (
messageStore = &MessageStore{
messages: make(map[string]map[string]string),
}
)
func init() {
// Initialize default messages
initializeDefaultMessages()
}
// initializeDefaultMessages sets up default error messages
func initializeDefaultMessages() {
// English messages
englishMessages := map[string]string{
ErrCodeValidationFailed: "Validation failed",
ErrCodeInvalidInput: "Invalid input provided",
ErrCodeMissingField: "Required field is missing",
ErrCodeInvalidFormat: "Invalid format",
ErrCodeInvalidLength: "Invalid length",
ErrCodeInvalidRange: "Value out of range",
ErrCodeNotFound: "Resource not found",
ErrCodeUserNotFound: "User not found",
ErrCodeResourceNotFound: "Resource not found",
ErrCodeDataNotFound: "Data not found",
ErrCodeUnauthorized: "Unauthorized access",
ErrCodeInvalidToken: "Invalid token provided",
ErrCodeTokenExpired: "Token has expired",
ErrCodeInvalidCredentials: "Invalid credentials",
ErrCodeForbidden: "Access forbidden",
ErrCodeInsufficientRights: "Insufficient permissions",
ErrCodeAccessDenied: "Access denied",
ErrCodeConflict: "Conflict occurred",
ErrCodeDuplicateEntry: "Duplicate entry",
ErrCodeResourceLocked: "Resource is locked",
ErrCodeConcurrentUpdate: "Concurrent update detected",
ErrCodeRateLimitExceeded: "Rate limit exceeded",
ErrCodeTooManyRequests: "Too many requests",
ErrCodeQuotaExceeded: "Quota exceeded",
ErrCodeInternalError: "Internal server error",
ErrCodeUnexpectedError: "An unexpected error occurred",
ErrCodeServiceUnavailable: "Service is unavailable",
ErrCodeConfigurationError: "Configuration error",
ErrCodeExternalError: "External service error",
ErrCodeThirdPartyError: "Third party service error",
ErrCodeAPIError: "API error occurred",
ErrCodeBusinessRule: "Business rule violation",
ErrCodeInsufficientBalance: "Insufficient balance",
ErrCodeAccountSuspended: "Account suspended",
ErrCodeTimeout: "Request timeout",
ErrCodeRequestTimeout: "Request timeout",
ErrCodeConnectionTimeout: "Connection timeout",
ErrCodeNetworkError: "Network error",
ErrCodeConnectionFailed: "Connection failed",
ErrCodeConnectionLost: "Connection lost",
ErrCodeDatabaseError: "Database error",
ErrCodeQueryFailed: "Query execution failed",
ErrCodeTransactionFailed: "Transaction failed",
}
// Indonesian messages
indonesianMessages := map[string]string{
ErrCodeValidationFailed: "Validasi gagal",
ErrCodeInvalidInput: "Input tidak valid",
ErrCodeMissingField: "Field wajib tidak ada",
ErrCodeInvalidFormat: "Format tidak valid",
ErrCodeInvalidLength: "Panjang tidak valid",
ErrCodeInvalidRange: "Nilai di luar jangkauan",
ErrCodeNotFound: "Sumber daya tidak ditemukan",
ErrCodeUserNotFound: "Pengguna tidak ditemukan",
ErrCodeResourceNotFound: "Sumber daya tidak ditemukan",
ErrCodeDataNotFound: "Data tidak ditemukan",
ErrCodeUnauthorized: "Akses tidak sah",
ErrCodeInvalidToken: "Token tidak valid",
ErrCodeTokenExpired: "Token telah kadaluarsa",
ErrCodeInvalidCredentials: "Kredensial tidak valid",
ErrCodeForbidden: "Akses dilarang",
ErrCodeInsufficientRights: "Izin tidak mencukupi",
ErrCodeAccessDenied: "Akses ditolak",
ErrCodeConflict: "Terjadi konflik",
ErrCodeDuplicateEntry: "Entri duplikat",
ErrCodeResourceLocked: "Sumber daya terkunci",
ErrCodeConcurrentUpdate: "Pembaruan bersamaan terdeteksi",
ErrCodeRateLimitExceeded: "Batas laju terlampaui",
ErrCodeTooManyRequests: "Terlalu banyak permintaan",
ErrCodeQuotaExceeded: "Kuota terlampaui",
ErrCodeInternalError: "Kesalahan server internal",
ErrCodeUnexpectedError: "Terjadi kesalahan tak terduga",
ErrCodeServiceUnavailable: "Layanan tidak tersedia",
ErrCodeConfigurationError: "Kesalahan konfigurasi",
ErrCodeExternalError: "Kesalahan layanan eksternal",
ErrCodeThirdPartyError: "Kesalahan layanan pihak ketiga",
ErrCodeAPIError: "Terjadi kesalahan API",
ErrCodeBusinessRule: "Pelanggaran aturan bisnis",
ErrCodeInsufficientBalance: "Saldo tidak mencukupi",
ErrCodeAccountSuspended: "Akun ditangguhkan",
ErrCodeTimeout: "Permintaan timeout",
ErrCodeRequestTimeout: "Permintaan timeout",
ErrCodeConnectionTimeout: "Koneksi timeout",
ErrCodeNetworkError: "Kesalahan jaringan",
ErrCodeConnectionFailed: "Koneksi gagal",
ErrCodeConnectionLost: "Koneksi terputus",
ErrCodeDatabaseError: "Kesalahan database",
ErrCodeQueryFailed: "Eksekusi query gagal",
ErrCodeTransactionFailed: "Transaksi gagal",
}
// Add messages to store
SetMessages("en", englishMessages)
SetMessages("id", indonesianMessages)
}
// SetMessages sets messages for a language
func SetMessages(language string, messages map[string]string) {
messageStore.mu.Lock()
defer messageStore.mu.Unlock()
if messageStore.messages[language] == nil {
messageStore.messages[language] = make(map[string]string)
}
for code, message := range messages {
messageStore.messages[language][code] = message
}
}
// SetMessage sets a single message for a language
func SetMessage(language, code, message string) {
messageStore.mu.Lock()
defer messageStore.mu.Unlock()
if messageStore.messages[language] == nil {
messageStore.messages[language] = make(map[string]string)
}
messageStore.messages[language][code] = message
}
// GetLocalizedMessage returns localized message for error code
func GetLocalizedMessage(code, language, defaultMessage string) string {
messageStore.mu.RLock()
defer messageStore.mu.RUnlock()
// Try specific language
if langMessages, exists := messageStore.messages[language]; exists {
if message, exists := langMessages[code]; exists {
return message
}
}
// Try English as fallback
if langMessages, exists := messageStore.messages["en"]; exists {
if message, exists := langMessages[code]; exists {
return message
}
}
// Return default message
return defaultMessage
}
// GetSupportedLanguages returns all supported languages
func GetSupportedLanguages() []string {
messageStore.mu.RLock()
defer messageStore.mu.RUnlock()
languages := make([]string, 0, len(messageStore.messages))
for lang := range messageStore.messages {
languages = append(languages, lang)
}
return languages
}
// LoadMessagesFromJSON loads messages from JSON file
func LoadMessagesFromJSON(language, jsonData string) error {
var messages map[string]string
if err := json.Unmarshal([]byte(jsonData), &messages); err != nil {
return fmt.Errorf("failed to parse JSON messages: %w", err)
}
SetMessages(language, messages)
return nil
}
// FormatMessage formats message with parameters
func FormatMessage(template string, params map[string]interface{}) string {
result := template
for key, value := range params {
placeholder := fmt.Sprintf("{{%s}}", key)
result = strings.ReplaceAll(result, placeholder, fmt.Sprintf("%v", value))
}
return result
}
// GetLocalizedMessageWithParams returns localized message with parameters
func GetLocalizedMessageWithParams(code, language, defaultMessage string, params map[string]interface{}) string {
message := GetLocalizedMessage(code, language, defaultMessage)
if params != nil {
message = FormatMessage(message, params)
}
return message
}
// MessageBuilder builds localized messages
type MessageBuilder struct {
language string
code string
params map[string]interface{}
}
// NewMessageBuilder creates a new message builder.
// Parameter 'lang' dianjurkan untuk dikirim berdasarkan context HTTP request
// misalnya dari header Accept-Language dari klien per request.
func NewMessageBuilder(lang string) *MessageBuilder {
if lang == "" {
lang = "en" // Fallback language default
}
return &MessageBuilder{
language: lang,
params: make(map[string]interface{}),
}
}
// Language sets language
func (mb *MessageBuilder) Language(lang string) *MessageBuilder {
mb.language = lang
return mb
}
// Code sets error code
func (mb *MessageBuilder) Code(code string) *MessageBuilder {
mb.code = code
return mb
}
// Param adds parameter
func (mb *MessageBuilder) Param(key string, value interface{}) *MessageBuilder {
mb.params[key] = value
return mb
}
// Params adds multiple parameters
func (mb *MessageBuilder) Params(params map[string]interface{}) *MessageBuilder {
for k, v := range params {
mb.params[k] = v
}
return mb
}
// Build builds the final message
func (mb *MessageBuilder) Build(defaultMessage string) string {
return GetLocalizedMessageWithParams(mb.code, mb.language, defaultMessage, mb.params)
}
// ValidationMessages provides common validation messages
type ValidationMessages struct {
Required string
Invalid string
TooShort string
TooLong string
InvalidEmail string
InvalidPhone string
}
// GetValidationMessages returns validation messages for language
func GetValidationMessages(language string) ValidationMessages {
return ValidationMessages{
Required: GetLocalizedMessage("VALIDATION_REQUIRED", language, "This field is required"),
Invalid: GetLocalizedMessage("VALIDATION_INVALID", language, "Invalid value"),
TooShort: GetLocalizedMessage("VALIDATION_TOO_SHORT", language, "Value is too short"),
TooLong: GetLocalizedMessage("VALIDATION_TOO_LONG", language, "Value is too long"),
InvalidEmail: GetLocalizedMessage("VALIDATION_INVALID_EMAIL", language, "Invalid email format"),
InvalidPhone: GetLocalizedMessage("VALIDATION_INVALID_PHONE", language, "Invalid phone format"),
}
}
// AddValidationMessages adds validation messages for language
func AddValidationMessages(language string, messages ValidationMessages) {
SetMessage(language, "VALIDATION_REQUIRED", messages.Required)
SetMessage(language, "VALIDATION_INVALID", messages.Invalid)
SetMessage(language, "VALIDATION_TOO_SHORT", messages.TooShort)
SetMessage(language, "VALIDATION_TOO_LONG", messages.TooLong)
SetMessage(language, "VALIDATION_INVALID_EMAIL", messages.InvalidEmail)
SetMessage(language, "VALIDATION_INVALID_PHONE", messages.InvalidPhone)
}