From a4a59c0903deae8a954bb214e2f8d4c4bec70679 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 10:47:45 -0300 Subject: [PATCH 01/31] fix(address): fixed generate valid state/uf pairs --- pkg/mocai/entities/address/generator.go | 38 +++++++++++++++++++------ pkg/mocai/translations/translations.go | 10 +++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/pkg/mocai/entities/address/generator.go b/pkg/mocai/entities/address/generator.go index 6e64ab4..0b3b577 100644 --- a/pkg/mocai/entities/address/generator.go +++ b/pkg/mocai/entities/address/generator.go @@ -32,17 +32,19 @@ func NewAddress(lang string, rnd translations.RandSource) (*Address, error) { } func generateAddress(lang string, rnd translations.RandSource) (*Address, error) { - streets := translations.GetList(lang, "address_street") - cities := translations.GetList(lang, "address_city") - states := translations.GetList(lang, "address_state") - ufs := translations.GetList(lang, "address_uf") - zips := translations.GetList(lang, "address_zip") + supportedLang := lang + if translations.GetUFMap(lang) == nil { + supportedLang = "ptbr" + } + streets := translations.GetList(supportedLang, "address_street") + cities := translations.GetList(supportedLang, "address_city") + states := translations.GetList(supportedLang, "address_state") + zips := translations.GetList(supportedLang, "address_zip") slicesToCheck := []SlicesToCheck{ {streets, ErrNoStreets}, {cities, ErrNoCities}, {states, ErrNoStates}, - {ufs, ErrNoUFs}, {zips, ErrNoZips}, } @@ -57,11 +59,29 @@ func generateAddress(lang string, rnd translations.RandSource) (*Address, error) } } - // performs a random selection using a custom random source + // select the index for state and UF in a paired manner + stateIdx := rnd.Intn(len(states)) + state := states[stateIdx] + + var uf string + + ufMap := translations.GetUFMap(supportedLang) + if ufMap != nil { + uf = ufMap[state] + } + if uf == "" { + ufs := translations.GetList(supportedLang, "address_uf") + if len(ufs) > stateIdx { + uf = ufs[stateIdx] + } else if len(ufs) > 0 { + uf = ufs[rnd.Intn(len(ufs))] + } else { + uf = "" + } + } + street := streets[rnd.Intn(len(streets))] city := cities[rnd.Intn(len(cities))] - state := states[rnd.Intn(len(states))] - uf := ufs[rnd.Intn(len(ufs))] zip := zips[rnd.Intn(len(zips))] return &Address{ diff --git a/pkg/mocai/translations/translations.go b/pkg/mocai/translations/translations.go index 7f6f8b3..f9cf5f1 100644 --- a/pkg/mocai/translations/translations.go +++ b/pkg/mocai/translations/translations.go @@ -2,8 +2,18 @@ package translations import ( "sync" + + address_ptbr "github.com/brazzcore/mocai/pkg/mocai/entities/address/mocks/ptbr" ) +// GetUFMap Returns the mapping of states for the specified language, if available +func GetUFMap(lang string) map[string]string { + if lang == "ptbr" { + return address_ptbr.UFs + } + return nil +} + // registryList stores lists of translations by language and keyword var ( registryList = make(map[string]map[string][]string) From be0f3d2e33df58b2a9819f023ad5468eb18d236b Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:07:05 -0300 Subject: [PATCH 02/31] docs(certificate): clarify that 'lang' is unused for Brazilian certs numbers --- .../entities/certificate/countries/brazilian_certificates.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go index ccc4fdc..f798350 100644 --- a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go +++ b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go @@ -140,6 +140,8 @@ func generateCertificateCustom(rnd translations.RandSource, formatted bool, cert } func generateBirthCertificateCustom(lang string, formatted bool, rnd translations.RandSource) (*BirthCertificate, error) { + // Note: The 'lang' parameter is currently unused because certificate numbers do not depend on language. + // It will be relevant only if textual data (e.g., names, descriptions) is added in the future. base, err := generateCertificateCustom(rnd, formatted, brazilianBirthCertificateType) if err != nil { return nil, err From 7932dac4ff51d13aac2027571aa96dcec155cf51 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:18:08 -0300 Subject: [PATCH 03/31] fix(translations): register full list of phone area codes for ptbr --- pkg/mocai/translations/pt_br.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/mocai/translations/pt_br.go b/pkg/mocai/translations/pt_br.go index 2ab2466..42a4442 100644 --- a/pkg/mocai/translations/pt_br.go +++ b/pkg/mocai/translations/pt_br.go @@ -40,8 +40,10 @@ func init() { } return ufs }(), - "address_zip": address_mocks.ZIPCodes, + "address_zip": address_mocks.ZIPCodes, + "phone_area_code": phone_mocks.AreaCodes, }) + // choose a random state state := address_mocks.States[rand.Intn(len(address_mocks.States))] @@ -60,8 +62,9 @@ func init() { "address_state": state, "address_uf": uf, "address_zip": address_mocks.ZIPCodes[rand.Intn(len(address_mocks.ZIPCodes))], - "phone_area_code": phone_mocks.AreaCodes[rand.Intn(len(phone_mocks.AreaCodes))], - "company_name": company_mocks.CompanyNames[rand.Intn(len(company_mocks.CompanyNames))], + // single random area code for legacy API (keep for compatibility) + "phone_area_code": phone_mocks.AreaCodes[rand.Intn(len(phone_mocks.AreaCodes))], + "company_name": company_mocks.CompanyNames[rand.Intn(len(company_mocks.CompanyNames))], // errors "invalid_certificate": "certidão inválida", From 135eeee860136fbdfd92acc8395e4a223ced53de Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:19:25 -0300 Subject: [PATCH 04/31] refactor(phone): adjusted phone data generation --- pkg/mocai/entities/phone/generator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/mocai/entities/phone/generator.go b/pkg/mocai/entities/phone/generator.go index b55be32..13cf2e0 100644 --- a/pkg/mocai/entities/phone/generator.go +++ b/pkg/mocai/entities/phone/generator.go @@ -26,9 +26,10 @@ func generatePhone(lang string, rnd translations.RandSource) (*Phone, error) { if rnd == nil { rnd = defaultRandSource() } + areaCode := areaCodes[rnd.Intn(len(areaCodes))] number := fmt.Sprintf("9%08d", rnd.Intn(100000000)) - if areaCode == "" || number == "" { + if areaCode == "" { return nil, fmt.Errorf("%w, %s", ErrGeneratingPhone, translations.Get(lang, "error_generating_phone")) } return &Phone{ From c139505ead2832d3d06efa09da5ce4c91bea33bd Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:21:02 -0300 Subject: [PATCH 05/31] feat(mocker): added phone data generation to mocker --- pkg/mocai/mocker.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/mocai/mocker.go b/pkg/mocai/mocker.go index 6ee10e3..a8f832b 100644 --- a/pkg/mocai/mocker.go +++ b/pkg/mocai/mocker.go @@ -11,6 +11,7 @@ import ( "github.com/brazzcore/mocai/pkg/mocai/entities/gender" "github.com/brazzcore/mocai/pkg/mocai/entities/nationalid" "github.com/brazzcore/mocai/pkg/mocai/entities/person" + "github.com/brazzcore/mocai/pkg/mocai/entities/phone" "github.com/brazzcore/mocai/pkg/mocai/entities/voteregistration" "github.com/brazzcore/mocai/pkg/mocai/translations" ) @@ -79,6 +80,11 @@ func (m *Mocker) NewCertificate() (*certificate.Certificate, error) { return certificate.NewCertificate(m.lang, m.formatted, m.rnd) } +// NewPhone generates a mock phone using a custom language and random source +func (m *Mocker) NewPhone() (*phone.Phone, error) { + return phone.NewPhone(m.lang, m.rnd) +} + // GetLanguage returns the language used in this instance func (m *Mocker) GetLanguage() string { return m.lang From 5d3a919cc975370273ecd286a175f4ff584671a0 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:26:39 -0300 Subject: [PATCH 06/31] feat(translations): add safe rand source for thread-safe random number generation --- pkg/mocai/translations/safe_rand.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 pkg/mocai/translations/safe_rand.go diff --git a/pkg/mocai/translations/safe_rand.go b/pkg/mocai/translations/safe_rand.go new file mode 100644 index 0000000..330ec8a --- /dev/null +++ b/pkg/mocai/translations/safe_rand.go @@ -0,0 +1,19 @@ +package translations + +import "sync" + +// SafeRandSource wraps a RandSource with a mutex for concurrent safety +type SafeRandSource struct { + rnd RandSource + mu sync.Mutex +} + +func NewSafeRandSource(rnd RandSource) *SafeRandSource { + return &SafeRandSource{rnd: rnd} +} + +func (s *SafeRandSource) Intn(n int) int { + s.mu.Lock() + defer s.mu.Unlock() + return s.rnd.Intn(n) +} From c6aa7ceb7e47aacf714e1dfd706cef417aa654dd Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:27:27 -0300 Subject: [PATCH 07/31] refactor(mocker): use safe rand source for concurrency safety and remove get rand --- pkg/mocai/mocker.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/mocai/mocker.go b/pkg/mocai/mocker.go index a8f832b..ef4d269 100644 --- a/pkg/mocai/mocker.go +++ b/pkg/mocai/mocker.go @@ -24,14 +24,16 @@ type Mocker struct { } func defaultRandSource() translations.RandSource { - return rand.New(rand.NewSource(time.Now().UnixNano())) + return translations.NewSafeRandSource(rand.New(rand.NewSource(time.Now().UnixNano()))) } // NewMocker creates a new Mocker instance with customizable language, formatting, and random source func NewMocker(lang string, isFormatted bool, rnd translations.RandSource) *Mocker { if rnd == nil { - // fallback to rand.New(rand.NewSource(time.Now().UnixNano())) rnd = defaultRandSource() + } else { + // Always wrap in SafeRandSource for concurrency safety + rnd = translations.NewSafeRandSource(rnd) } return &Mocker{ lang: lang, @@ -89,8 +91,3 @@ func (m *Mocker) NewPhone() (*phone.Phone, error) { func (m *Mocker) GetLanguage() string { return m.lang } - -// GetRand returns the randomness source used -func (m *Mocker) GetRand() translations.RandSource { - return m.rnd -} From 719e5a4a9d41c18e344c4c3b3d5e97e1e3c6e1c3 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:36:34 -0300 Subject: [PATCH 08/31] fix(errors): show only localized error messages to user --- .../certificate/countries/brazilian_certificates.go | 10 +++++----- .../entities/company/countries/brazilian_company.go | 6 +++--- pkg/mocai/entities/cpf/generator.go | 2 +- pkg/mocai/entities/gender/gender.go | 2 +- .../nationalid/countries/brazilian_national_id.go | 2 +- pkg/mocai/entities/person/generator.go | 6 +++--- pkg/mocai/entities/phone/generator.go | 4 ++-- .../countries/brazilian_vote_registration.go | 6 +++--- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go index f798350..325b639 100644 --- a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go +++ b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go @@ -93,14 +93,14 @@ func generateCertificateCustom(rnd translations.RandSource, formatted bool, cert } vitalRecordsOffice := rnd.Intn(899999-100000+1) + 100000 if vitalRecordsOffice < 0 { - return nil, fmt.Errorf("%w: %s", ErrInvalidVitalRecordsOffice, translations.Get(l, "invalid_vital_records_office_number")) + return nil, fmt.Errorf("%s", translations.Get(l, "invalid_vital_records_office_number")) } archiveCode := 1 serviceType := 55 birthYear := rnd.Intn(time.Now().Year()-2010+1) + 2010 bookNumber := rnd.Intn(89999-10000+1) + 10000 if bookNumber < 0 { - return nil, fmt.Errorf("%w, %s", ErrInvalidBookNumber, translations.Get(l, "invalid_book_number")) + return nil, fmt.Errorf("%s", translations.Get(l, "invalid_book_number")) } pageNumber := rnd.Intn(899-100+1) + 100 if pageNumber < 0 { @@ -108,12 +108,12 @@ func generateCertificateCustom(rnd translations.RandSource, formatted bool, cert } termNumber := rnd.Intn(8999999-1000000+1) + 1000000 if termNumber < 0 { - return nil, fmt.Errorf("%w, %s", ErrInvalidTermNumber, translations.Get(l, "invalid_term_number")) + return nil, fmt.Errorf("%s", translations.Get(l, "invalid_term_number")) } numberWithoutCheckDigits := fmt.Sprintf("%06d%02d%02d%04d%d%05d%03d%07d", vitalRecordsOffice, archiveCode, serviceType, birthYear, certificateType, bookNumber, pageNumber, termNumber) if len(numberWithoutCheckDigits) != 30 { - return nil, fmt.Errorf("%w, %s", ErrInvalidNumberWithoutCheckDigits, translations.Get(l, "invalid_number_without_check_digits")) + return nil, fmt.Errorf("%s", translations.Get(l, "invalid_number_without_check_digits")) } checkDigits := calculateCheckDigits(numberWithoutCheckDigits) certificateNumber := fmt.Sprintf("%s%02s", numberWithoutCheckDigits, checkDigits) @@ -122,7 +122,7 @@ func generateCertificateCustom(rnd translations.RandSource, formatted bool, cert vitalRecordsOffice, archiveCode, serviceType, birthYear, certificateType, bookNumber, pageNumber, termNumber, checkDigits) } if certificateNumber == "" { - return nil, fmt.Errorf("%w: %s", ErrInvalidCertificate, translations.Get(l, "invalid_certificate")) + return nil, fmt.Errorf("%s", translations.Get(l, "invalid_certificate")) } createdBaseCertificate := &BaseCertificate{ VitalRecordsOffice: vitalRecordsOffice, diff --git a/pkg/mocai/entities/company/countries/brazilian_company.go b/pkg/mocai/entities/company/countries/brazilian_company.go index a1ba120..023e04b 100644 --- a/pkg/mocai/entities/company/countries/brazilian_company.go +++ b/pkg/mocai/entities/company/countries/brazilian_company.go @@ -18,7 +18,7 @@ type BrazilianCompany struct { func GenerateBrazilianCompany(lang string, formatted bool, rnd translations.RandSource) (BrazilianCompany, error) { companyNames := translations.GetList(lang, "company_name") if len(companyNames) == 0 { - return BrazilianCompany{}, fmt.Errorf("%w, %s", ErrNoCompanyNamesAvailable, translations.Get(lang, "no_company_names_available")) + return BrazilianCompany{}, fmt.Errorf("%s", translations.Get(lang, "no_company_names_available")) } if rnd == nil { rnd = defaultRandSource() @@ -30,10 +30,10 @@ func GenerateBrazilianCompany(lang string, formatted bool, rnd translations.Rand return BrazilianCompany{}, err } if companyName == "" { - return BrazilianCompany{}, fmt.Errorf("%w, %s", ErrGeneratingBrazilianCompany, translations.Get(lang, "error_generating_brazilian_company")) + return BrazilianCompany{}, fmt.Errorf("%s", translations.Get(lang, "error_generating_brazilian_company")) } if cnpjVal == "" { - return BrazilianCompany{}, fmt.Errorf("%w, %s", ErrGeneratingCNPJ, translations.Get(lang, "invalid_cnpj")) + return BrazilianCompany{}, fmt.Errorf("%s", translations.Get(lang, "invalid_cnpj")) } createdCompany := BrazilianCompany{ Name: companyName, diff --git a/pkg/mocai/entities/cpf/generator.go b/pkg/mocai/entities/cpf/generator.go index a71b850..201b5c6 100644 --- a/pkg/mocai/entities/cpf/generator.go +++ b/pkg/mocai/entities/cpf/generator.go @@ -35,7 +35,7 @@ func NewCPF(lang string, isFormatted bool, rnd translations.RandSource) (*CPF, e cpfNumber := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(digits)), ""), "[]") if isFormatted { if len(cpfNumber) != 11 { - return nil, fmt.Errorf("%w, %s", ErrInvalidCPF, translations.Get(lang, "invalid_cpf")) + return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_cpf")) } return &CPF{Number: cpfNumber[:3] + "." + cpfNumber[3:6] + "." + cpfNumber[6:9] + "-" + cpfNumber[9:]}, nil } diff --git a/pkg/mocai/entities/gender/gender.go b/pkg/mocai/entities/gender/gender.go index 281ad86..eda86cc 100644 --- a/pkg/mocai/entities/gender/gender.go +++ b/pkg/mocai/entities/gender/gender.go @@ -34,7 +34,7 @@ func NewGender(lang string, rnd translations.RandSource) (*Gender, error) { func generateRandomGender(lang string, rnd translations.RandSource) (*Gender, error) { genders := translations.GetList(lang, "gender") if len(genders) == 0 { - return nil, fmt.Errorf("%w, %s", ErrNoGenders, translations.Get(lang, "no_data_available_for_genders")) + return nil, fmt.Errorf("%s", translations.Get(lang, "no_data_available_for_genders")) } if rnd == nil { rnd = defaultRandSource() diff --git a/pkg/mocai/entities/nationalid/countries/brazilian_national_id.go b/pkg/mocai/entities/nationalid/countries/brazilian_national_id.go index cd08a85..71bfb6d 100644 --- a/pkg/mocai/entities/nationalid/countries/brazilian_national_id.go +++ b/pkg/mocai/entities/nationalid/countries/brazilian_national_id.go @@ -54,7 +54,7 @@ func calculateSPRGDigitCustom(rnd translations.RandSource, lang string) (string, for i := range 8 { val, err := strconv.Atoi(string(baseStr[i])) if err != nil { - return "", fmt.Errorf("%w, %s", ErrToConvertDigit, translations.Get(lang, "error_converting_digit")) + return "", fmt.Errorf("%s", translations.Get(lang, "error_converting_digit")) } d[i] = val } diff --git a/pkg/mocai/entities/person/generator.go b/pkg/mocai/entities/person/generator.go index a9da752..d6d85ac 100644 --- a/pkg/mocai/entities/person/generator.go +++ b/pkg/mocai/entities/person/generator.go @@ -31,10 +31,10 @@ func generatePerson(lang string, isFormatted bool, rnd translations.RandSource) lastNames := translations.GetList(lang, "person_last_name") if len(firstNamesMale) == 0 || len(firstNamesFemale) == 0 { - return nil, fmt.Errorf("%w, %s", ErrNoFirstNames, translations.Get(lang, "no_data_available_for_first_names")) + return nil, fmt.Errorf("%s", translations.Get(lang, "no_data_available_for_first_names")) } if len(lastNames) == 0 { - return nil, fmt.Errorf("%w, %s", ErrNoLastNames, translations.Get(lang, "no_data_available_for_last_names")) + return nil, fmt.Errorf("%s", translations.Get(lang, "no_data_available_for_last_names")) } if rnd == nil { @@ -56,7 +56,7 @@ func generatePerson(lang string, isFormatted bool, rnd translations.RandSource) } if firstNameMale == "" || firstNameFemale == "" || lastName == "" { - return nil, fmt.Errorf("%w, %s", ErrGeneratingPerson, translations.Get(lang, "error_generating_person")) + return nil, fmt.Errorf("%s", translations.Get(lang, "error_generating_person")) } return &Person{ diff --git a/pkg/mocai/entities/phone/generator.go b/pkg/mocai/entities/phone/generator.go index 13cf2e0..7f1042e 100644 --- a/pkg/mocai/entities/phone/generator.go +++ b/pkg/mocai/entities/phone/generator.go @@ -21,7 +21,7 @@ func NewPhone(lang string, rnd translations.RandSource) (*Phone, error) { func generatePhone(lang string, rnd translations.RandSource) (*Phone, error) { areaCodes := translations.GetList(lang, "phone_area_code") if len(areaCodes) == 0 { - return nil, fmt.Errorf("%w, %s", ErrNoAreaCodes, translations.Get(lang, "no_data_available_for_area_codes")) + return nil, fmt.Errorf("%s", translations.Get(lang, "no_data_available_for_area_codes")) } if rnd == nil { rnd = defaultRandSource() @@ -30,7 +30,7 @@ func generatePhone(lang string, rnd translations.RandSource) (*Phone, error) { areaCode := areaCodes[rnd.Intn(len(areaCodes))] number := fmt.Sprintf("9%08d", rnd.Intn(100000000)) if areaCode == "" { - return nil, fmt.Errorf("%w, %s", ErrGeneratingPhone, translations.Get(lang, "error_generating_phone")) + return nil, fmt.Errorf("%s", translations.Get(lang, "error_generating_phone")) } return &Phone{ AreaCode: areaCode, diff --git a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go index 6e157aa..fe09bd6 100644 --- a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go +++ b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go @@ -145,15 +145,15 @@ func NewBrazilianVoteRegistrationCustom(lang string, isFormatted bool, rnd trans stateCodeStr := fmt.Sprintf("%02d", stateCode) checkDigit1, err := calculateCheckDigit1(sequenceNumberStr) if err != nil { - return nil, fmt.Errorf("%w, %s", ErrInvalidCheckDigit1, translations.Get(lang, "invalid_check_digit_1")) + return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_check_digit_1")) } checkDigit2, err := calculateCheckDigit2(stateCodeStr, checkDigit1, stateCode) if err != nil { - return nil, fmt.Errorf("%w, %s", ErrInvalidCheckDigit2, translations.Get(lang, "invalid_check_digit_2")) + return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_check_digit_2")) } number := sequenceNumberStr + stateCodeStr + checkDigit1 + checkDigit2 if number == "" || len(number) != 12 { - return nil, fmt.Errorf("%w, %s", ErrInvalidVoteRegistration, translations.Get(lang, "invalid_vote_registration")) + return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_vote_registration")) } if isFormatted { number = fmt.Sprintf("%s %s %s", number[:4], number[4:8], number[8:]) From b9408fedbc51245bbb7cda4279b3e95fe9883d11 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:42:31 -0300 Subject: [PATCH 09/31] docs: updated main readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 7f0359f..47fd49a 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ func main() { // Generate a mock address address, err := mocker.NewAddress() if err != nil { + // All error messages are always localized and user-friendly log.Fatal(err) } fmt.Printf("Address: %s, %d - %s, %s (%s) - %s\n", address.Street, address.Number, address.City, address.State, address.UF, address.ZIP) @@ -76,6 +77,7 @@ func main() { // Generate a mock person person, err := mocker.NewPerson() if err != nil { + // All error messages are always localized and user-friendly log.Fatal(err) } fmt.Printf("Person: %s %s, Gender: %s, Age: %d, CPF: %s\n", person.FirstNameMale, person.LastName, person.Gender.Identity, person.Age, person.CPF.Number) @@ -83,9 +85,15 @@ func main() { // Generate a mock company company, err := mocker.NewCompany() if err != nil { + // All error messages are always localized and user-friendly log.Fatal(err) } fmt.Printf("Company: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ) +### Error Messages & Localization + +All error messages returned by Mocai are always localized and user-friendly. When an error occurs (e.g., invalid data, unsupported language, or generation failure), the error message will be presented in the language configured for the `Mocker` instance. This ensures that both developers and end-users receive clear, context-appropriate feedback. + +You do not need to perform any extra steps for error localization — Mocai handles this automatically for all supported languages. } ``` From 60d8ba782091254deed0368b55a4e0d9433db7e8 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 11:43:00 -0300 Subject: [PATCH 10/31] docs: updated ptbr readme --- docs/localization/pt/README-PT.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/localization/pt/README-PT.md b/docs/localization/pt/README-PT.md index 6fd052d..230e150 100644 --- a/docs/localization/pt/README-PT.md +++ b/docs/localization/pt/README-PT.md @@ -67,6 +67,7 @@ func main() { // Gera um endereço fictício address, err := mocker.NewAddress() if err != nil { + // Todas as mensagens de erro são sempre localizadas e amigáveis log.Fatal(err) } fmt.Printf("Endereço: %s, %d - %s, %s (%s) - %s\n", address.Street, address.Number, address.City, address.State, address.UF, address.ZIP) @@ -74,6 +75,7 @@ func main() { // Gera uma pessoa fictícia person, err := mocker.NewPerson() if err != nil { + // Todas as mensagens de erro são sempre localizadas e amigáveis log.Fatal(err) } fmt.Printf("Pessoa: %s %s, Gênero: %s, Idade: %d, CPF: %s\n", person.FirstNameMale, person.LastName, person.Gender.Identity, person.Age, person.CPF.Number) @@ -81,9 +83,15 @@ func main() { // Gera uma empresa fictícia company, err := mocker.NewCompany() if err != nil { + // Todas as mensagens de erro são sempre localizadas e amigáveis log.Fatal(err) } fmt.Printf("Empresa: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ) +### Mensagens de Erro e Localização + +Todas as mensagens de erro retornadas pelo Mocai são sempre localizadas e amigáveis ao usuário. Quando ocorre um erro (ex: dados inválidos, idioma não suportado ou falha na geração), a mensagem será apresentada no idioma configurado para a instância do `Mocker`. Isso garante que tanto desenvolvedores quanto usuários finais recebam feedback claro e apropriado ao contexto. + +Não é necessário realizar nenhuma etapa extra para a localização das mensagens de erro — o Mocai faz isso automaticamente para todos os idiomas suportados. } ``` From d8a173527cd6f45c2f1c6b68bdb672267a807f76 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 12:00:00 -0300 Subject: [PATCH 11/31] fix: fixed invalid page number error in certificate generator for consistent error handling --- .../entities/certificate/countries/brazilian_certificates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go index 325b639..a0c873a 100644 --- a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go +++ b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go @@ -104,7 +104,7 @@ func generateCertificateCustom(rnd translations.RandSource, formatted bool, cert } pageNumber := rnd.Intn(899-100+1) + 100 if pageNumber < 0 { - return nil, ErrInvalidPageNumber + return nil, fmt.Errorf("%s", translations.Get(l, "invalid_page_number")) } termNumber := rnd.Intn(8999999-1000000+1) + 1000000 if termNumber < 0 { From aa15d6183d2df443bab675959276521b568792c1 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 12:45:03 -0300 Subject: [PATCH 12/31] docs: updated main readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 47fd49a..a346365 100644 --- a/README.md +++ b/README.md @@ -89,12 +89,14 @@ func main() { log.Fatal(err) } fmt.Printf("Company: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ) +} +``` + ### Error Messages & Localization All error messages returned by Mocai are always localized and user-friendly. When an error occurs (e.g., invalid data, unsupported language, or generation failure), the error message will be presented in the language configured for the `Mocker` instance. This ensures that both developers and end-users receive clear, context-appropriate feedback. You do not need to perform any extra steps for error localization — Mocai handles this automatically for all supported languages. -} ``` > **Note:** Each call to a method like `NewPerson()` or `NewAddress()` generates a new mock with random data. The `Mocker` instance is immutable regarding its configuration (language, formatting, random source). From 774d7faf6ebb14d2a6b9d0efe558976deed117c1 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 12:45:22 -0300 Subject: [PATCH 13/31] docs: updated ptbr readme --- docs/localization/pt/README-PT.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/localization/pt/README-PT.md b/docs/localization/pt/README-PT.md index 230e150..b864974 100644 --- a/docs/localization/pt/README-PT.md +++ b/docs/localization/pt/README-PT.md @@ -87,12 +87,14 @@ func main() { log.Fatal(err) } fmt.Printf("Empresa: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ) +} +``` + ### Mensagens de Erro e Localização Todas as mensagens de erro retornadas pelo Mocai são sempre localizadas e amigáveis ao usuário. Quando ocorre um erro (ex: dados inválidos, idioma não suportado ou falha na geração), a mensagem será apresentada no idioma configurado para a instância do `Mocker`. Isso garante que tanto desenvolvedores quanto usuários finais recebam feedback claro e apropriado ao contexto. Não é necessário realizar nenhuma etapa extra para a localização das mensagens de erro — o Mocai faz isso automaticamente para todos os idiomas suportados. -} ``` > **Nota:** Cada chamada de método como `NewPerson()` ou `NewAddress()` gera um novo mock com dados aleatórios. A instância do `Mocker` é imutável quanto à configuração (idioma, formatação, fonte de aleatoriedade). From 5229eead088e93bf63bcf69ae653139922d5797a Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 13:03:48 -0300 Subject: [PATCH 14/31] remove: removed all error sentinel files no longer in use --- .../entities/certificate/countries/errors.go | 18 ------------------ pkg/mocai/entities/cnpj/errors.go | 7 ------- pkg/mocai/entities/company/countries/errors.go | 12 ------------ pkg/mocai/entities/cpf/errors.go | 7 ------- pkg/mocai/entities/gender/errors.go | 11 ----------- .../entities/nationalid/countries/errors.go | 7 ------- pkg/mocai/entities/person/errors.go | 12 ------------ pkg/mocai/entities/phone/errors.go | 11 ----------- pkg/mocai/translations/errors.go | 15 --------------- 9 files changed, 100 deletions(-) delete mode 100644 pkg/mocai/entities/certificate/countries/errors.go delete mode 100644 pkg/mocai/entities/cnpj/errors.go delete mode 100644 pkg/mocai/entities/company/countries/errors.go delete mode 100644 pkg/mocai/entities/cpf/errors.go delete mode 100644 pkg/mocai/entities/gender/errors.go delete mode 100644 pkg/mocai/entities/nationalid/countries/errors.go delete mode 100644 pkg/mocai/entities/person/errors.go delete mode 100644 pkg/mocai/entities/phone/errors.go delete mode 100644 pkg/mocai/translations/errors.go diff --git a/pkg/mocai/entities/certificate/countries/errors.go b/pkg/mocai/entities/certificate/countries/errors.go deleted file mode 100644 index 0ba2f1c..0000000 --- a/pkg/mocai/entities/certificate/countries/errors.go +++ /dev/null @@ -1,18 +0,0 @@ -package countries - -import "errors" - -// Errors for Certificate data generation failures. -// These errors represent specific failure scenarios and should be wrapped with additional -// context when returned. -var ( - ErrInvalidCertificate = errors.New("certificate: invalid certificate") - ErrInvalidVitalRecordsOffice = errors.New("certificate: invalid vital records office number") - ErrInvalidArchive = errors.New("certificate: invalid archive number") - ErrInvalidVitalRecordsService = errors.New("certificate: invalid vital records service number") - ErrInvalidBirthYear = errors.New("certificate: invalid birth year") - ErrInvalidBookNumber = errors.New("certificate: invalid book number") - ErrInvalidPageNumber = errors.New("certificate: invalid page number") - ErrInvalidTermNumber = errors.New("certificate: invalid term number") - ErrInvalidNumberWithoutCheckDigits = errors.New("certificate: invalid number without check digits") -) diff --git a/pkg/mocai/entities/cnpj/errors.go b/pkg/mocai/entities/cnpj/errors.go deleted file mode 100644 index 658cda0..0000000 --- a/pkg/mocai/entities/cnpj/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package cnpj - -import "errors" - -var ( - ErrInvalidCNPJ = errors.New("cnpj: invalid cnpj") -) diff --git a/pkg/mocai/entities/company/countries/errors.go b/pkg/mocai/entities/company/countries/errors.go deleted file mode 100644 index 770626f..0000000 --- a/pkg/mocai/entities/company/countries/errors.go +++ /dev/null @@ -1,12 +0,0 @@ -package countries - -import "errors" - -// Errors for Company data generation failures. -// These errors represent specific failure scenarios and should be wrapped with additional -// context when returned. -var ( - ErrGeneratingBrazilianCompany = errors.New("company: error generating brazilian company") - ErrGeneratingCNPJ = errors.New("company: error generating CNPJ") - ErrNoCompanyNamesAvailable = errors.New("company: no company names available") -) diff --git a/pkg/mocai/entities/cpf/errors.go b/pkg/mocai/entities/cpf/errors.go deleted file mode 100644 index cd3918a..0000000 --- a/pkg/mocai/entities/cpf/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package cpf - -import "errors" - -var ( - ErrInvalidCPF = errors.New("cpf: invalid cpf") -) diff --git a/pkg/mocai/entities/gender/errors.go b/pkg/mocai/entities/gender/errors.go deleted file mode 100644 index eb0e2c0..0000000 --- a/pkg/mocai/entities/gender/errors.go +++ /dev/null @@ -1,11 +0,0 @@ -package gender - -import "errors" - -// Package gender defines common errors used during gender generation and validation. -// These errors represent specific failure scenarios and should be wrapped with additional -// context when returned. -var ( - ErrNoGenders = errors.New("gender: no data available for genders") - ErrInvalidGender = errors.New("gender: invalid gender") -) diff --git a/pkg/mocai/entities/nationalid/countries/errors.go b/pkg/mocai/entities/nationalid/countries/errors.go deleted file mode 100644 index 8b01c99..0000000 --- a/pkg/mocai/entities/nationalid/countries/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package countries - -import "errors" - -var ( - ErrToConvertDigit = errors.New("national id: error converting digit") -) diff --git a/pkg/mocai/entities/person/errors.go b/pkg/mocai/entities/person/errors.go deleted file mode 100644 index 4349ad4..0000000 --- a/pkg/mocai/entities/person/errors.go +++ /dev/null @@ -1,12 +0,0 @@ -package person - -import "errors" - -// Error constants for person-related data generation failures. -// These errors represent specific failure scenarios and should be wrapped with additional -// context when returned. -var ( - ErrGeneratingPerson = errors.New("person: error generating person") - ErrNoFirstNames = errors.New("person: no data available for first names") - ErrNoLastNames = errors.New("person: no data available for last names") -) diff --git a/pkg/mocai/entities/phone/errors.go b/pkg/mocai/entities/phone/errors.go deleted file mode 100644 index c2d1967..0000000 --- a/pkg/mocai/entities/phone/errors.go +++ /dev/null @@ -1,11 +0,0 @@ -package phone - -import "errors" - -// Error constants used for phone number generation failures. -// These errors represent specific failure scenarios and should be wrapped with additional -// context when returned. -var ( - ErrGeneratingPhone = errors.New("phone: error generating phone") - ErrNoAreaCodes = errors.New("phone: no data available for area codes") -) diff --git a/pkg/mocai/translations/errors.go b/pkg/mocai/translations/errors.go deleted file mode 100644 index 0cd6b47..0000000 --- a/pkg/mocai/translations/errors.go +++ /dev/null @@ -1,15 +0,0 @@ -// Package translations provides standardized error messages and translations -// used across the mocai package. These constants ensure consistency and reusability -// throughout the application, especially in error handling and mock data generation. -// -// Example usage: -// -// if !isSupported(lang) { -// return nil, translations.ErrUnsupportedLanguage -// } -package translations - -import "errors" - -// ErrUnsupportedLanguage indicates that the requested language is not supported. -var ErrUnsupportedLanguage = errors.New("unsupported language") From 1c9892c070e462ca291c69dd54944625428fa6bb Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 13:06:21 -0300 Subject: [PATCH 15/31] refactor: removed unnecessary comments and other adjts --- pkg/mocai/entities/address/errors.go | 12 ++++-------- .../entities/voteregistration/countries/errors.go | 9 +++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pkg/mocai/entities/address/errors.go b/pkg/mocai/entities/address/errors.go index fee5523..d7851ab 100644 --- a/pkg/mocai/entities/address/errors.go +++ b/pkg/mocai/entities/address/errors.go @@ -2,13 +2,9 @@ package address import "errors" -// Package address defines common errors used during address generation and validation. -// These errors represent specific failure scenarios and should be wrapped with additional -// context when returned. var ( - ErrNoStreets = errors.New("no data available for streets") - ErrNoCities = errors.New("no data available for cities") - ErrNoStates = errors.New("no data available for states") - ErrNoUFs = errors.New("no data available for UFs") - ErrNoZips = errors.New("no data available for zips") + ErrNoStreets = errors.New("address: no streets available") + ErrNoCities = errors.New("address: no cities available") + ErrNoStates = errors.New("address: no states available") + ErrNoZips = errors.New("address: no zip codes available") ) diff --git a/pkg/mocai/entities/voteregistration/countries/errors.go b/pkg/mocai/entities/voteregistration/countries/errors.go index 4661fe5..0f4a50c 100644 --- a/pkg/mocai/entities/voteregistration/countries/errors.go +++ b/pkg/mocai/entities/voteregistration/countries/errors.go @@ -2,11 +2,8 @@ package countries import "errors" -// Errors for Vote Registration data generation failures. -// These errors represent specific failure scenarios and should be wrapped with additional -// context when returned. var ( - ErrInvalidVoteRegistration = errors.New("invalid vote registration") - ErrInvalidCheckDigit1 = errors.New("invalid check digit 1") - ErrInvalidCheckDigit2 = errors.New("invalid check digit 2") + ErrInvalidVoteRegistration = errors.New("vote registration: invalid vote registration") + ErrInvalidCheckDigit1 = errors.New("vote registration: invalid check digit 1") + ErrInvalidCheckDigit2 = errors.New("vote registration: invalid check digit 2") ) From 81f1d5fe4cfabca33fc97e716cd6cd0cd47228ec Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 13:17:48 -0300 Subject: [PATCH 16/31] docs: updated main readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a346365..46beff4 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,8 @@ func main() { } fmt.Printf("Company: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ) } -``` +``` ### Error Messages & Localization All error messages returned by Mocai are always localized and user-friendly. When an error occurs (e.g., invalid data, unsupported language, or generation failure), the error message will be presented in the language configured for the `Mocker` instance. This ensures that both developers and end-users receive clear, context-appropriate feedback. From 642963ac055687203d14169fdf4b9e5743be03ef Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 13:18:05 -0300 Subject: [PATCH 17/31] docs: updated ptbr readme --- docs/localization/pt/README-PT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/localization/pt/README-PT.md b/docs/localization/pt/README-PT.md index b864974..d14b224 100644 --- a/docs/localization/pt/README-PT.md +++ b/docs/localization/pt/README-PT.md @@ -88,8 +88,8 @@ func main() { } fmt.Printf("Empresa: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ) } -``` +``` ### Mensagens de Erro e Localização Todas as mensagens de erro retornadas pelo Mocai são sempre localizadas e amigáveis ao usuário. Quando ocorre um erro (ex: dados inválidos, idioma não suportado ou falha na geração), a mensagem será apresentada no idioma configurado para a instância do `Mocker`. Isso garante que tanto desenvolvedores quanto usuários finais recebam feedback claro e apropriado ao contexto. From 433609aab332d07f6a926ad0f89680cb8357c9e2 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 13:35:09 -0300 Subject: [PATCH 18/31] docs(readme): adjusted main readme --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 46beff4..d7a11a6 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ func main() { // Generate a mock address address, err := mocker.NewAddress() if err != nil { - // All error messages are always localized and user-friendly + // Error messages are localized where translation keys are available log.Fatal(err) } fmt.Printf("Address: %s, %d - %s, %s (%s) - %s\n", address.Street, address.Number, address.City, address.State, address.UF, address.ZIP) @@ -77,7 +77,7 @@ func main() { // Generate a mock person person, err := mocker.NewPerson() if err != nil { - // All error messages are always localized and user-friendly + // Error messages are localized where translation keys are available log.Fatal(err) } fmt.Printf("Person: %s %s, Gender: %s, Age: %d, CPF: %s\n", person.FirstNameMale, person.LastName, person.Gender.Identity, person.Age, person.CPF.Number) @@ -85,18 +85,19 @@ func main() { // Generate a mock company company, err := mocker.NewCompany() if err != nil { - // All error messages are always localized and user-friendly + // Error messages are localized where translation keys are available log.Fatal(err) } fmt.Printf("Company: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ) } ``` + ### Error Messages & Localization -All error messages returned by Mocai are always localized and user-friendly. When an error occurs (e.g., invalid data, unsupported language, or generation failure), the error message will be presented in the language configured for the `Mocker` instance. This ensures that both developers and end-users receive clear, context-appropriate feedback. +Error messages are localized where translation keys are available. When an error occurs (e.g., invalid data, unsupported language, or generation failure), the error message will be presented in the language configured for the `Mocker` instance, if a translation exists. In some cases, fallback or hardcoded errors may occur if translation coverage is incomplete. -You do not need to perform any extra steps for error localization — Mocai handles this automatically for all supported languages. +You do not need to perform any extra steps for error localization — Mocai handles this automatically for all supported languages where translation keys are present. ``` > **Note:** Each call to a method like `NewPerson()` or `NewAddress()` generates a new mock with random data. The `Mocker` instance is immutable regarding its configuration (language, formatting, random source). From 4ddc6bc41fc83bd7fa7ff40f0704bfef1f27aa82 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 13:35:25 -0300 Subject: [PATCH 19/31] docs(readme): adjusted ptbr readme --- docs/localization/pt/README-PT.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/localization/pt/README-PT.md b/docs/localization/pt/README-PT.md index d14b224..f16022d 100644 --- a/docs/localization/pt/README-PT.md +++ b/docs/localization/pt/README-PT.md @@ -67,7 +67,7 @@ func main() { // Gera um endereço fictício address, err := mocker.NewAddress() if err != nil { - // Todas as mensagens de erro são sempre localizadas e amigáveis + // As mensagens de erro são localizadas quando há chave de tradução disponível log.Fatal(err) } fmt.Printf("Endereço: %s, %d - %s, %s (%s) - %s\n", address.Street, address.Number, address.City, address.State, address.UF, address.ZIP) @@ -75,7 +75,7 @@ func main() { // Gera uma pessoa fictícia person, err := mocker.NewPerson() if err != nil { - // Todas as mensagens de erro são sempre localizadas e amigáveis + // As mensagens de erro são localizadas quando há chave de tradução disponível log.Fatal(err) } fmt.Printf("Pessoa: %s %s, Gênero: %s, Idade: %d, CPF: %s\n", person.FirstNameMale, person.LastName, person.Gender.Identity, person.Age, person.CPF.Number) @@ -83,18 +83,19 @@ func main() { // Gera uma empresa fictícia company, err := mocker.NewCompany() if err != nil { - // Todas as mensagens de erro são sempre localizadas e amigáveis + // As mensagens de erro são localizadas quando há chave de tradução disponível log.Fatal(err) } fmt.Printf("Empresa: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ) } ``` + ### Mensagens de Erro e Localização -Todas as mensagens de erro retornadas pelo Mocai são sempre localizadas e amigáveis ao usuário. Quando ocorre um erro (ex: dados inválidos, idioma não suportado ou falha na geração), a mensagem será apresentada no idioma configurado para a instância do `Mocker`. Isso garante que tanto desenvolvedores quanto usuários finais recebam feedback claro e apropriado ao contexto. +As mensagens de erro são localizadas quando há chave de tradução disponível. Quando ocorre um erro (ex: dados inválidos, idioma não suportado ou falha na geração), a mensagem será apresentada no idioma configurado para a instância do `Mocker`, se houver tradução. Em alguns casos, mensagens fixas podem aparecer caso a cobertura de traduções não seja completa. -Não é necessário realizar nenhuma etapa extra para a localização das mensagens de erro — o Mocai faz isso automaticamente para todos os idiomas suportados. +Não é necessário realizar nenhuma etapa extra para a localização das mensagens de erro — o Mocai faz isso automaticamente para todos os idiomas suportados onde há chave de tradução. ``` > **Nota:** Cada chamada de método como `NewPerson()` ou `NewAddress()` gera um novo mock com dados aleatórios. A instância do `Mocker` é imutável quanto à configuração (idioma, formatação, fonte de aleatoriedade). From 399d968b7801577309351276e1865afda70e550b Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 13:40:13 -0300 Subject: [PATCH 20/31] refactor(translations): remove unused single-value phone_area_code registration --- pkg/mocai/translations/pt_br.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/mocai/translations/pt_br.go b/pkg/mocai/translations/pt_br.go index 42a4442..1be3f77 100644 --- a/pkg/mocai/translations/pt_br.go +++ b/pkg/mocai/translations/pt_br.go @@ -62,9 +62,7 @@ func init() { "address_state": state, "address_uf": uf, "address_zip": address_mocks.ZIPCodes[rand.Intn(len(address_mocks.ZIPCodes))], - // single random area code for legacy API (keep for compatibility) - "phone_area_code": phone_mocks.AreaCodes[rand.Intn(len(phone_mocks.AreaCodes))], - "company_name": company_mocks.CompanyNames[rand.Intn(len(company_mocks.CompanyNames))], + "company_name": company_mocks.CompanyNames[rand.Intn(len(company_mocks.CompanyNames))], // errors "invalid_certificate": "certidão inválida", From bd0fb13f5d5fb33f6e0879acf92920135fb9fdc5 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 14:11:20 -0300 Subject: [PATCH 21/31] refactor(voteregistration): rename and document NewBrazilianVoteRegistration, unify logic and comments --- .../countries/brazilian_vote_registration.go | 68 +++++-------------- 1 file changed, 18 insertions(+), 50 deletions(-) diff --git a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go index fe09bd6..c74b612 100644 --- a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go +++ b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go @@ -20,55 +20,9 @@ type BrazilianVoteRegistration struct { Number string } -// NewBrazilianVoteRegistration generates a new Brazilian vote registration +// NewBrazilianVoteRegistration generates a new Brazilian vote registration with localized errors func NewBrazilianVoteRegistration(isFormatted bool) (*BrazilianVoteRegistration, error) { - bvr, err := (&BrazilianVoteRegistration{}).generateBrazilianVoteRegistration(isFormatted) - if err != nil { - return nil, err - } - return bvr, nil -} - -// GenerateBrazilianVoteRegistration generates a valid Brazilian vote registration number -func (b *BrazilianVoteRegistration) generateBrazilianVoteRegistration(formatted bool) (*BrazilianVoteRegistration, error) { - section := randomInt3Digits() - zone := randomInt3Digits() - - // Generate an 8 digit sequence number - sequenceNumber := randomInt(1, 99999999) - sequenceNumberStr := fmt.Sprintf("%08d", sequenceNumber) - - // Generate a random state code 01 to 28 - stateCode := randomInt(1, 28) - stateCodeStr := fmt.Sprintf("%02d", stateCode) - - // Calculate the first check digit - checkDigit1, err := calculateCheckDigit1(sequenceNumberStr) - if err != nil { - return nil, err - } - - // Calculate the second check digit - checkDigit2, err := calculateCheckDigit2(stateCodeStr, checkDigit1, stateCode) - if err != nil { - return nil, err - } - - // Combine everything to form the complete number - number := sequenceNumberStr + stateCodeStr + checkDigit1 + checkDigit2 - if number == "" || len(number) != 12 { - return nil, ErrInvalidVoteRegistration - } - - if formatted { - number = fmt.Sprintf("%s %s %s", number[:4], number[4:8], number[8:]) - } - - return &BrazilianVoteRegistration{ - Section: section, - Zone: zone, - Number: number, - }, nil + return NewBrazilianVoteRegistrationCustom("ptbr", isFormatted, nil) } // randomInt generates a random integer between min and max @@ -82,7 +36,7 @@ func randomInt3Digits() string { } // calculateCheckDigit1 calculates the first check digit. -// It depends on the sequence number. +// it depends on the sequence number. func calculateCheckDigit1(sequenceNumber string) (string, error) { sum := 0 weights := []int{2, 3, 4, 5, 6, 7, 8, 9} @@ -103,7 +57,7 @@ func calculateCheckDigit1(sequenceNumber string) (string, error) { } // calculateCheckDigit2 calculates the second check digit -// It depends on the state code and the first check digit +// it depends on the state code and the first check digit func calculateCheckDigit2(stateCode, checkDigit1 string, stateCodeInt int) (string, error) { sum := 0 weights := []int{7, 8} @@ -137,27 +91,41 @@ func NewBrazilianVoteRegistrationCustom(lang string, isFormatted bool, rnd trans if rnd == nil { rnd = rand.New(rand.NewSource(time.Now().UnixNano())) } + + // Generate a random 3-digit section and zone section := fmt.Sprintf("%03d", rnd.Intn(1000)) zone := fmt.Sprintf("%03d", rnd.Intn(1000)) + + // Generate an 8-digit sequence number sequenceNumber := rnd.Intn(99999999) + 1 sequenceNumberStr := fmt.Sprintf("%08d", sequenceNumber) + + // Generate a random state code 01 to 28 stateCode := rnd.Intn(28) + 1 stateCodeStr := fmt.Sprintf("%02d", stateCode) + + // Calculate the first check digit checkDigit1, err := calculateCheckDigit1(sequenceNumberStr) if err != nil { return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_check_digit_1")) } + + // Calculate the second check digit checkDigit2, err := calculateCheckDigit2(stateCodeStr, checkDigit1, stateCode) if err != nil { return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_check_digit_2")) } + + // Combine everything to form the complete number number := sequenceNumberStr + stateCodeStr + checkDigit1 + checkDigit2 if number == "" || len(number) != 12 { return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_vote_registration")) } + if isFormatted { number = fmt.Sprintf("%s %s %s", number[:4], number[4:8], number[8:]) } + return &BrazilianVoteRegistration{ Section: section, Zone: zone, From fa266cba4978cc30189e50d541334720b9fc0283 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 17:36:17 -0300 Subject: [PATCH 22/31] fix(certificate): fixed language code ptbr for error localization --- .../entities/certificate/countries/brazilian_certificates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go index a0c873a..cad6924 100644 --- a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go +++ b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go @@ -84,7 +84,7 @@ func generateBrazilianCertificatesCustom(lang string, formatted bool, rnd transl } func generateCertificateCustom(rnd translations.RandSource, formatted bool, certificateType int, lang ...string) (*BaseCertificate, error) { - l := "pt_br" + l := "ptbr" if len(lang) > 0 && lang[0] != "" { l = lang[0] } From 42549e9614d0de19b512ff55fe0ac7810af8702d Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 17:49:16 -0300 Subject: [PATCH 23/31] refactor(certificates): simplify method names --- .../countries/brazilian_certificates.go | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go index cad6924..bbeaf36 100644 --- a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go +++ b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go @@ -55,23 +55,23 @@ type BrazilianCertificates struct { } func NewBrazilCertificatesCustom(lang string, isFormatted bool, rnd translations.RandSource) (*BrazilianCertificates, error) { - cert, err := generateBrazilianCertificatesCustom(lang, isFormatted, rnd) + cert, err := generateBrazilianCertificates(lang, isFormatted, rnd) if err != nil { return nil, err } return cert, nil } -func generateBrazilianCertificatesCustom(lang string, formatted bool, rnd translations.RandSource) (*BrazilianCertificates, error) { - createdBirthCertificate, err := generateBirthCertificateCustom(lang, formatted, rnd) +func generateBrazilianCertificates(lang string, formatted bool, rnd translations.RandSource) (*BrazilianCertificates, error) { + createdBirthCertificate, err := generateBirthCertificate(lang, formatted, rnd) if err != nil { return nil, err } - createdMarriageCertificate, err := generateMarriageCertificateCustom(lang, formatted, rnd) + createdMarriageCertificate, err := generateMarriageCertificate(lang, formatted, rnd) if err != nil { return nil, err } - createdDeathCertificate, err := generateDeathCertificateCustom(lang, formatted, rnd) + createdDeathCertificate, err := generateDeathCertificate(lang, formatted, rnd) if err != nil { return nil, err } @@ -83,7 +83,7 @@ func generateBrazilianCertificatesCustom(lang string, formatted bool, rnd transl return createdBrazilianCertificates, nil } -func generateCertificateCustom(rnd translations.RandSource, formatted bool, certificateType int, lang ...string) (*BaseCertificate, error) { +func generateCertificate(rnd translations.RandSource, formatted bool, certificateType int, lang ...string) (*BaseCertificate, error) { l := "ptbr" if len(lang) > 0 && lang[0] != "" { l = lang[0] @@ -139,10 +139,8 @@ func generateCertificateCustom(rnd translations.RandSource, formatted bool, cert return createdBaseCertificate, nil } -func generateBirthCertificateCustom(lang string, formatted bool, rnd translations.RandSource) (*BirthCertificate, error) { - // Note: The 'lang' parameter is currently unused because certificate numbers do not depend on language. - // It will be relevant only if textual data (e.g., names, descriptions) is added in the future. - base, err := generateCertificateCustom(rnd, formatted, brazilianBirthCertificateType) +func generateBirthCertificate(lang string, formatted bool, rnd translations.RandSource) (*BirthCertificate, error) { + base, err := generateCertificate(rnd, formatted, brazilianBirthCertificateType, lang) if err != nil { return nil, err } @@ -152,8 +150,8 @@ func generateBirthCertificateCustom(lang string, formatted bool, rnd translation return createdBirthCertificate, nil } -func generateMarriageCertificateCustom(lang string, formatted bool, rnd translations.RandSource) (*MarriageCertificate, error) { - base, err := generateCertificateCustom(rnd, formatted, brazilianMarriageCertificateType) +func generateMarriageCertificate(lang string, formatted bool, rnd translations.RandSource) (*MarriageCertificate, error) { + base, err := generateCertificate(rnd, formatted, brazilianMarriageCertificateType, lang) if err != nil { return nil, err } @@ -163,8 +161,8 @@ func generateMarriageCertificateCustom(lang string, formatted bool, rnd translat return createdMarriageCertificate, nil } -func generateDeathCertificateCustom(lang string, formatted bool, rnd translations.RandSource) (*DeathCertificate, error) { - base, err := generateCertificateCustom(rnd, formatted, brazilianDeathCertificateType) +func generateDeathCertificate(lang string, formatted bool, rnd translations.RandSource) (*DeathCertificate, error) { + base, err := generateCertificate(rnd, formatted, brazilianDeathCertificateType, lang) if err != nil { return nil, err } From 1b554ddf7f94987c96d4f322960770b6e6c68aed Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 18:05:17 -0300 Subject: [PATCH 24/31] feat: added en-us translation for errors --- pkg/mocai/translations/en_us.go | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 pkg/mocai/translations/en_us.go diff --git a/pkg/mocai/translations/en_us.go b/pkg/mocai/translations/en_us.go new file mode 100644 index 0000000..c37d38d --- /dev/null +++ b/pkg/mocai/translations/en_us.go @@ -0,0 +1,44 @@ +package translations + +func init() { + Register("en_us", map[string]string{ + // certificate errors + "invalid_certificate": "invalid certificate", + "invalid_vital_records_office_number": "invalid vital records office number", + "invalid_archive_number": "invalid archive number", + "invalid_vital_records_service number": "invalid vital records service number", + "invalid_birth_year": "invalid birth year", + "invalid_book_number": "invalid book number", + "invalid_page_number": "invalid page number", + "invalid_term_number": "invalid term number", + "invalid_number_without_check_digits": "invalid number without check digits", + + // company errors + "error_generating_brazilian_company": "error generating brazilian company", + "invalid_cnpj": "invalid cnpj", + "no_company_names_available": "no company names available", + + // cpf errors + "invalid_cpf": "invalid cpf", + + // gender errors + "no_data_available_for_genders": "no data available for this gender", + + // national ID errors + "error_converting_digit": "error converting digit", + + // person errors + "error_generating_person": "error generating person", + "no_data_available_for_first_names": "no data available for first name", + "no_data_available_for_last_names": "no data available for last name", + + // phone errors + "error_generating_phone": "error generating phone number", + "no_data_available_for_area_codes": "no area code available", + + // voter Registration errors + "invalid_vote_registration": "invalid vote registration", + "invalid_check_digit_1": "invalid check digit 1", + "invalid_check_digit_2": "invalid check digit 2", + }) +} From d7ebaf98d72faecf5bcf81f8a36a9ee13df2438a Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 18:08:15 -0300 Subject: [PATCH 25/31] refactor: adjusted error fallback to use en-us --- pkg/mocai/translations/translations.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/mocai/translations/translations.go b/pkg/mocai/translations/translations.go index f9cf5f1..4c0ec45 100644 --- a/pkg/mocai/translations/translations.go +++ b/pkg/mocai/translations/translations.go @@ -65,6 +65,13 @@ func Get(lang, key string) string { if val, ok := registrySingle[lang][key]; ok { return val } + + // fallback: try en_us if not found in requested language + if lang != "en_us" { + if val, ok := registrySingle["en_us"][key]; ok { + return val + } + } return key } From 7bf9f5717b8a97ba812c619007188270fd7fe5ab Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 18:31:24 -0300 Subject: [PATCH 26/31] feat(safe-rand): added function for standard thread-safe source for global use --- pkg/mocai/translations/safe_rand.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/mocai/translations/safe_rand.go b/pkg/mocai/translations/safe_rand.go index 330ec8a..1b16369 100644 --- a/pkg/mocai/translations/safe_rand.go +++ b/pkg/mocai/translations/safe_rand.go @@ -1,6 +1,15 @@ package translations -import "sync" +import ( + "math/rand" + "sync" + "time" +) + +// DefaultRandSource returns a standard thread-safe randomness source for global use +func DefaultRandSource() RandSource { + return NewSafeRandSource(rand.New(rand.NewSource(time.Now().UnixNano()))) +} // SafeRandSource wraps a RandSource with a mutex for concurrent safety type SafeRandSource struct { From 8bd99b788db6a53ea35d5ef98a56c801dc8499ef Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 18:35:09 -0300 Subject: [PATCH 27/31] refactor(random): standardize use of thread-safe randomness source --- pkg/mocai/entities/address/generator.go | 4 ++++ .../certificate/countries/brazilian_certificates.go | 3 +-- .../entities/company/countries/brazilian_company.go | 7 +------ pkg/mocai/entities/cpf/generator.go | 4 +--- pkg/mocai/entities/gender/gender.go | 7 +------ .../nationalid/countries/brazilian_national_id.go | 3 +-- pkg/mocai/entities/person/generator.go | 3 +-- pkg/mocai/entities/phone/generator.go | 7 +------ .../countries/brazilian_vote_registration.go | 2 +- pkg/mocai/mocker.go | 12 +----------- 10 files changed, 13 insertions(+), 39 deletions(-) diff --git a/pkg/mocai/entities/address/generator.go b/pkg/mocai/entities/address/generator.go index 0b3b577..1ead929 100644 --- a/pkg/mocai/entities/address/generator.go +++ b/pkg/mocai/entities/address/generator.go @@ -36,6 +36,10 @@ func generateAddress(lang string, rnd translations.RandSource) (*Address, error) if translations.GetUFMap(lang) == nil { supportedLang = "ptbr" } + if rnd == nil { + rnd = translations.NewSafeRandSource(translations.DefaultRandSource()) + } + streets := translations.GetList(supportedLang, "address_street") cities := translations.GetList(supportedLang, "address_city") states := translations.GetList(supportedLang, "address_state") diff --git a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go index bbeaf36..5162047 100644 --- a/pkg/mocai/entities/certificate/countries/brazilian_certificates.go +++ b/pkg/mocai/entities/certificate/countries/brazilian_certificates.go @@ -2,7 +2,6 @@ package countries import ( "fmt" - "math/rand" "time" "github.com/brazzcore/mocai/pkg/mocai/translations" @@ -89,7 +88,7 @@ func generateCertificate(rnd translations.RandSource, formatted bool, certificat l = lang[0] } if rnd == nil { - rnd = rand.New(rand.NewSource(int64(rand.Int()))) + rnd = translations.DefaultRandSource() } vitalRecordsOffice := rnd.Intn(899999-100000+1) + 100000 if vitalRecordsOffice < 0 { diff --git a/pkg/mocai/entities/company/countries/brazilian_company.go b/pkg/mocai/entities/company/countries/brazilian_company.go index 023e04b..eef1a99 100644 --- a/pkg/mocai/entities/company/countries/brazilian_company.go +++ b/pkg/mocai/entities/company/countries/brazilian_company.go @@ -2,7 +2,6 @@ package countries import ( "fmt" - "math/rand" "github.com/brazzcore/mocai/pkg/mocai/entities/cnpj" "github.com/brazzcore/mocai/pkg/mocai/translations" @@ -21,7 +20,7 @@ func GenerateBrazilianCompany(lang string, formatted bool, rnd translations.Rand return BrazilianCompany{}, fmt.Errorf("%s", translations.Get(lang, "no_company_names_available")) } if rnd == nil { - rnd = defaultRandSource() + rnd = translations.DefaultRandSource() } companyName := companyNames[rnd.Intn(len(companyNames))] @@ -41,7 +40,3 @@ func GenerateBrazilianCompany(lang string, formatted bool, rnd translations.Rand } return createdCompany, nil } - -func defaultRandSource() translations.RandSource { - return rand.New(rand.NewSource(int64(rand.Int()))) -} diff --git a/pkg/mocai/entities/cpf/generator.go b/pkg/mocai/entities/cpf/generator.go index 201b5c6..78e8f07 100644 --- a/pkg/mocai/entities/cpf/generator.go +++ b/pkg/mocai/entities/cpf/generator.go @@ -2,9 +2,7 @@ package cpf import ( "fmt" - "math/rand" "strings" - "time" "github.com/brazzcore/mocai/pkg/mocai/translations" ) @@ -16,7 +14,7 @@ type CPF struct { // NewCPF generates a mock CPF using a custom language and random source func NewCPF(lang string, isFormatted bool, rnd translations.RandSource) (*CPF, error) { if rnd == nil { - rnd = rand.New(rand.NewSource(time.Now().UnixNano())) + rnd = translations.DefaultRandSource() } // generate the first 9 digits diff --git a/pkg/mocai/entities/gender/gender.go b/pkg/mocai/entities/gender/gender.go index eda86cc..cf3527f 100644 --- a/pkg/mocai/entities/gender/gender.go +++ b/pkg/mocai/entities/gender/gender.go @@ -2,7 +2,6 @@ package gender import ( "fmt" - "math/rand" "github.com/brazzcore/mocai/pkg/mocai/translations" ) @@ -37,12 +36,8 @@ func generateRandomGender(lang string, rnd translations.RandSource) (*Gender, er return nil, fmt.Errorf("%s", translations.Get(lang, "no_data_available_for_genders")) } if rnd == nil { - rnd = defaultRandSource() + rnd = translations.DefaultRandSource() } genderStr := genders[rnd.Intn(len(genders))] return &Gender{Identity: Identity(genderStr)}, nil } - -func defaultRandSource() translations.RandSource { - return rand.New(rand.NewSource(int64(rand.Int()))) -} diff --git a/pkg/mocai/entities/nationalid/countries/brazilian_national_id.go b/pkg/mocai/entities/nationalid/countries/brazilian_national_id.go index 71bfb6d..3d07fe7 100644 --- a/pkg/mocai/entities/nationalid/countries/brazilian_national_id.go +++ b/pkg/mocai/entities/nationalid/countries/brazilian_national_id.go @@ -2,7 +2,6 @@ package countries import ( "fmt" - "math/rand" "strconv" "github.com/brazzcore/mocai/pkg/mocai/translations" @@ -46,7 +45,7 @@ func generateBrazilianNationalIDCustom(formatted bool, rnd translations.RandSour func calculateSPRGDigitCustom(rnd translations.RandSource, lang string) (string, error) { if rnd == nil { - rnd = rand.New(rand.NewSource(int64(rand.Int()))) + rnd = translations.DefaultRandSource() } base := rnd.Intn(100000000) baseStr := fmt.Sprintf("%08d", base) diff --git a/pkg/mocai/entities/person/generator.go b/pkg/mocai/entities/person/generator.go index d6d85ac..292b8d1 100644 --- a/pkg/mocai/entities/person/generator.go +++ b/pkg/mocai/entities/person/generator.go @@ -2,7 +2,6 @@ package person import ( "fmt" - "math/rand" "github.com/brazzcore/mocai/pkg/mocai/entities/cpf" "github.com/brazzcore/mocai/pkg/mocai/entities/gender" @@ -38,7 +37,7 @@ func generatePerson(lang string, isFormatted bool, rnd translations.RandSource) } if rnd == nil { - rnd = rand.New(rand.NewSource(int64(rand.Int()))) + rnd = translations.DefaultRandSource() } firstNameMale := firstNamesMale[rnd.Intn(len(firstNamesMale))] diff --git a/pkg/mocai/entities/phone/generator.go b/pkg/mocai/entities/phone/generator.go index 7f1042e..d4be88e 100644 --- a/pkg/mocai/entities/phone/generator.go +++ b/pkg/mocai/entities/phone/generator.go @@ -2,7 +2,6 @@ package phone import ( "fmt" - "math/rand" "github.com/brazzcore/mocai/pkg/mocai/translations" ) @@ -24,7 +23,7 @@ func generatePhone(lang string, rnd translations.RandSource) (*Phone, error) { return nil, fmt.Errorf("%s", translations.Get(lang, "no_data_available_for_area_codes")) } if rnd == nil { - rnd = defaultRandSource() + rnd = translations.DefaultRandSource() } areaCode := areaCodes[rnd.Intn(len(areaCodes))] @@ -37,7 +36,3 @@ func generatePhone(lang string, rnd translations.RandSource) (*Phone, error) { Number: number, }, nil } - -func defaultRandSource() translations.RandSource { - return rand.New(rand.NewSource(int64(rand.Int()))) -} diff --git a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go index c74b612..c293ac2 100644 --- a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go +++ b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go @@ -89,7 +89,7 @@ func calculateCheckDigit2(stateCode, checkDigit1 string, stateCodeInt int) (stri func NewBrazilianVoteRegistrationCustom(lang string, isFormatted bool, rnd translations.RandSource) (*BrazilianVoteRegistration, error) { if rnd == nil { - rnd = rand.New(rand.NewSource(time.Now().UnixNano())) + rnd = translations.DefaultRandSource() } // Generate a random 3-digit section and zone diff --git a/pkg/mocai/mocker.go b/pkg/mocai/mocker.go index ef4d269..1180a5c 100644 --- a/pkg/mocai/mocker.go +++ b/pkg/mocai/mocker.go @@ -1,9 +1,6 @@ package mocai import ( - "math/rand" - "time" - "github.com/brazzcore/mocai/pkg/mocai/entities/address" "github.com/brazzcore/mocai/pkg/mocai/entities/certificate" "github.com/brazzcore/mocai/pkg/mocai/entities/company" @@ -23,17 +20,10 @@ type Mocker struct { rnd translations.RandSource } -func defaultRandSource() translations.RandSource { - return translations.NewSafeRandSource(rand.New(rand.NewSource(time.Now().UnixNano()))) -} - // NewMocker creates a new Mocker instance with customizable language, formatting, and random source func NewMocker(lang string, isFormatted bool, rnd translations.RandSource) *Mocker { if rnd == nil { - rnd = defaultRandSource() - } else { - // Always wrap in SafeRandSource for concurrency safety - rnd = translations.NewSafeRandSource(rnd) + rnd = translations.DefaultRandSource() } return &Mocker{ lang: lang, From 8eefd030edd144d183dcf65febbd9e33298cd3b3 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 18:51:09 -0300 Subject: [PATCH 28/31] fix(translations): return defensive copy in GetUFMap to prevent external mutation --- pkg/mocai/translations/translations.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/mocai/translations/translations.go b/pkg/mocai/translations/translations.go index 4c0ec45..584aeef 100644 --- a/pkg/mocai/translations/translations.go +++ b/pkg/mocai/translations/translations.go @@ -9,7 +9,12 @@ import ( // GetUFMap Returns the mapping of states for the specified language, if available func GetUFMap(lang string) map[string]string { if lang == "ptbr" { - return address_ptbr.UFs + // returns a defensive copy to avoid exposing the global map + copyMap := make(map[string]string, len(address_ptbr.UFs)) + for k, v := range address_ptbr.UFs { + copyMap[k] = v + } + return copyMap } return nil } From 72807801bc7f12b24d977d8ecbcc97c7c2431968 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 18:54:18 -0300 Subject: [PATCH 29/31] refactor(vote-registration): remove unused globalRand and helper funcs --- .../countries/brazilian_vote_registration.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go index c293ac2..93d7904 100644 --- a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go +++ b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go @@ -2,17 +2,11 @@ package countries import ( "fmt" - "math/rand" "strconv" - "time" "github.com/brazzcore/mocai/pkg/mocai/translations" ) -var ( - globalRand = rand.New(rand.NewSource(time.Now().UnixNano())) -) - // BrazilianVoteRegistration represents a Brazilian vote registration type BrazilianVoteRegistration struct { Section string @@ -25,16 +19,6 @@ func NewBrazilianVoteRegistration(isFormatted bool) (*BrazilianVoteRegistration, return NewBrazilianVoteRegistrationCustom("ptbr", isFormatted, nil) } -// randomInt generates a random integer between min and max -func randomInt(min, max int) int { - return globalRand.Intn(max-min+1) + min -} - -// randomInt3Digits generates a random 3 digit number -func randomInt3Digits() string { - return fmt.Sprintf("%03d", globalRand.Intn(1000)) -} - // calculateCheckDigit1 calculates the first check digit. // it depends on the sequence number. func calculateCheckDigit1(sequenceNumber string) (string, error) { From 0fc06df4de23e21b607d8d9a9063a310df6c8446 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 19:12:02 -0300 Subject: [PATCH 30/31] refactor: removed error sentinels for vote registration --- .../countries/brazilian_vote_registration.go | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go index 93d7904..dcd507b 100644 --- a/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go +++ b/pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go @@ -27,7 +27,7 @@ func calculateCheckDigit1(sequenceNumber string) (string, error) { for i := 0; i < len(sequenceNumber); i++ { digit, err := strconv.Atoi(string(sequenceNumber[i])) if err != nil { - return "", ErrInvalidCheckDigit1 + return "", fmt.Errorf("%s", translations.Get("en_us", "invalid_check_digit_1")) } sum += digit * weights[i] } @@ -48,13 +48,13 @@ func calculateCheckDigit2(stateCode, checkDigit1 string, stateCodeInt int) (stri for i := 0; i < len(stateCode); i++ { digit, err := strconv.Atoi(string(stateCode[i])) if err != nil { - return "", ErrInvalidCheckDigit2 + return "", fmt.Errorf("%s", translations.Get("en_us", "invalid_check_digit_2")) } sum += digit * weights[i] } checkDigit1Int, err := strconv.Atoi(checkDigit1) if err != nil { - return "", ErrInvalidCheckDigit2 + return "", fmt.Errorf("%s", translations.Get("en_us", "invalid_check_digit_2")) } sum += checkDigit1Int * 9 @@ -63,7 +63,7 @@ func calculateCheckDigit2(stateCode, checkDigit1 string, stateCodeInt int) (stri checkDigit2 = 0 } - // Special case for states 01 SP and 02 MG + // special case for states 01 SP and 02 MG if (stateCodeInt == 1 || stateCodeInt == 2) && checkDigit2 == 0 { checkDigit2 = 1 } @@ -72,35 +72,47 @@ func calculateCheckDigit2(stateCode, checkDigit1 string, stateCodeInt int) (stri } func NewBrazilianVoteRegistrationCustom(lang string, isFormatted bool, rnd translations.RandSource) (*BrazilianVoteRegistration, error) { + if lang == "" { + lang = "en_us" + } + + supported := false + if translations.Get(lang, "invalid_vote_registration") != "invalid_vote_registration" { + supported = true + } + if !supported { + lang = "en_us" + } + if rnd == nil { rnd = translations.DefaultRandSource() } - // Generate a random 3-digit section and zone + // generate a random 3-digit section and zone section := fmt.Sprintf("%03d", rnd.Intn(1000)) zone := fmt.Sprintf("%03d", rnd.Intn(1000)) - // Generate an 8-digit sequence number + // generate an 8-digit sequence number sequenceNumber := rnd.Intn(99999999) + 1 sequenceNumberStr := fmt.Sprintf("%08d", sequenceNumber) - // Generate a random state code 01 to 28 + // generate a random state code 01 to 28 stateCode := rnd.Intn(28) + 1 stateCodeStr := fmt.Sprintf("%02d", stateCode) - // Calculate the first check digit + // calculate the first check digit checkDigit1, err := calculateCheckDigit1(sequenceNumberStr) if err != nil { return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_check_digit_1")) } - // Calculate the second check digit + // calculate the second check digit checkDigit2, err := calculateCheckDigit2(stateCodeStr, checkDigit1, stateCode) if err != nil { return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_check_digit_2")) } - // Combine everything to form the complete number + // combine everything to form the complete number number := sequenceNumberStr + stateCodeStr + checkDigit1 + checkDigit2 if number == "" || len(number) != 12 { return nil, fmt.Errorf("%s", translations.Get(lang, "invalid_vote_registration")) From dd349dac4dfc37a7d08506219ef83ad1b4007e65 Mon Sep 17 00:00:00 2001 From: Welliton Fernandes Leal Date: Sun, 22 Mar 2026 19:12:47 -0300 Subject: [PATCH 31/31] refactor: removed unused errors file --- pkg/mocai/entities/voteregistration/countries/errors.go | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 pkg/mocai/entities/voteregistration/countries/errors.go diff --git a/pkg/mocai/entities/voteregistration/countries/errors.go b/pkg/mocai/entities/voteregistration/countries/errors.go deleted file mode 100644 index 0f4a50c..0000000 --- a/pkg/mocai/entities/voteregistration/countries/errors.go +++ /dev/null @@ -1,9 +0,0 @@ -package countries - -import "errors" - -var ( - ErrInvalidVoteRegistration = errors.New("vote registration: invalid vote registration") - ErrInvalidCheckDigit1 = errors.New("vote registration: invalid check digit 1") - ErrInvalidCheckDigit2 = errors.New("vote registration: invalid check digit 2") -)