Skip to content

[Feature/51] :: standardize country specific struct naming convention#52

Merged
wellfernandes merged 30 commits intodevelopfrom
feature/51-standardize-country-specific-struct-naming-convention
Mar 21, 2026
Merged

[Feature/51] :: standardize country specific struct naming convention#52
wellfernandes merged 30 commits intodevelopfrom
feature/51-standardize-country-specific-struct-naming-convention

Conversation

@wellfernandes
Copy link
Copy Markdown
Member

@wellfernandes wellfernandes commented Mar 21, 2026

  • complete refactoring of the domains
  • added custom translation system
  • record of all translation lists
  • fixed usage examples
  • instance of mocker immutable
  • updated docs

Summary by CodeRabbit

  • New Features

    • Added support for more mock entities (Company, CPF/CNPJ, Certificates, National ID/RG, Voter Registration) and explicit Portuguese (ptbr) localization.
  • Documentation

    • Expanded README and localized docs with new examples, updated usage (Mocker-based), example execution, testing instructions, and wording/typo fixes.
  • Refactor

    • APIs and Mocker redesigned to use per-instance language/formatting and injectable randomness; generators now produce entities on demand.
    • Standardized error message prefixes for clearer context.

@wellfernandes wellfernandes self-assigned this Mar 21, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2ccb7112-b482-4143-96b4-0881ba4cac4c

📥 Commits

Reviewing files that changed from the base of the PR and between 0fbbe3f and 076b2be.

📒 Files selected for processing (2)
  • pkg/mocai/entities/cnpj/generator.go
  • pkg/mocai/entities/company/countries/brazilian_company.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/mocai/entities/company/countries/brazilian_company.go

📝 Walkthrough

Walkthrough

Refactors mocai from global state to dependency-injected instance usage: introduces NewMocker(lang,isFormatted,rnd) returning a configured Mocker that lazily generates entities; generators and translations now accept explicit lang and RandSource; many error messages standardized and several struct fields/return signatures changed.

Changes

Cohort / File(s) Summary
Mocker core & API
pkg/mocai/mocker.go, examples/mocker/main.go
New NewMocker(lang,isFormatted,rnd) *Mocker; removed eager pre-built entities; added instance methods (NewPerson, NewGender, NewCompany, NewVoteRegistration, NewNationalID, NewAddress, NewCPF, NewCertificate) and GetRand(). Example updated to use mocai.NewMocker("ptbr", true, nil) and instance calls.
Translations refactor & data
pkg/mocai/translations/translations.go, pkg/mocai/translations/pt_br.go, docs/localization/pt/README-PT.md
Removed global language state/APIs; added per-call Get(lang,key), GetList, GetRandom, RegisterList, and RandSource interface. ptbr data migrated to list registrations and expanded keys.
Certificates
pkg/mocai/entities/certificate/generator.go, pkg/mocai/entities/certificate/countries/brazilian_certificates.go, pkg/mocai/entities/certificate/countries/errors.go
Replaced GenerateBrazilianCertificates with NewBrazilCertificatesCustom(lang,isFormatted,rnd); NewCertificate(lang,isFormatted,rnd) added; BaseCertificate fields renamed (CertificateTypeType, CertificateNumberNumber); certificates now pointers; errors wrapped/translations used.
Person, CPF, Gender, Company, Address, Phone, VoteReg, NationalID
pkg/mocai/entities/person/..., pkg/mocai/entities/cpf/..., pkg/mocai/entities/gender/..., pkg/mocai/entities/company/..., pkg/mocai/entities/address/..., pkg/mocai/entities/phone/..., pkg/mocai/entities/voteregistration/..., pkg/mocai/entities/nationalid/...
Many generators changed to accept (lang string, isFormatted bool, rnd translations.RandSource) (or (lang, rnd)), removed receiver-based generation in favor of package helpers, switched to translations.GetList/GetRandom, injected RNG usage with defaultRandSource() fallback, and standardized/wrapped errors with translated messages. Field renames (e.g., CompanyNameName) and some exported function/field renames occurred.
CNPJ & low-level RNG changes
pkg/mocai/entities/cnpj/generator.go, pkg/mocai/entities/cnpj/errors.go
GenerateCNPJ now accepts an rnd source; ErrInvalidCNPJ message prefixed ("cnpj: ...")—adds nil-fallback RNG behavior.
Error message standardization
pkg/mocai/entities/*/errors.go (multiple)
Many exported sentinel error messages prefixed with entity context ("person:", "phone:", "gender:", "national id:", "certificate:", etc.).
Docs & README
README.md, docs/localization/pt/README-PT.md
README broadened (additional entities listed), updated usage to Mocker API, ptbr notes, isFormatted explanation, examples path changed, added "Running Tests", and header rename LicenceLicense.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer/Client
    participant Mocker as Mocker Instance
    participant Trans as Translations
    participant RNG as RandSource
    participant Entity as Entity Generator (Person/CPF/...)

    Dev->>Mocker: call NewMocker(lang,isFormatted,rnd)
    Note right of Mocker: stores config (lang, formatted, rnd)
    Dev->>Mocker: m.NewPerson()
    Mocker->>Trans: GetList(lang,"first_names"/"last_names")
    Mocker->>RNG: Intn(...)
    Mocker->>Entity: generatePerson(lang,isFormatted,rnd)
    Entity->>Trans: Get/format translations (e.g., gender, messages)
    Entity->>RNG: Intn(...) for digits/age/selection
    Entity-->>Mocker: *Person / error (wrapped with Trans.Get message)
    Mocker-->>Dev: returns *Person / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • rluders
  • DODOSUBI
  • BertBR

Poem

🐰 A little rabbit hops through code so spry,
I tuck lang and rand where globals used to lie.
Lazy mocks now wake upon each call,
Lists and translations dance down the hall.
Hooray — lightweight hops, no eager stall!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title references standardizing country-specific struct naming convention, but the changeset encompasses a much broader refactoring including translations, mocker architecture redesign, error messaging, API signature changes, and randomness injection across all entity generators. Clarify the title to better reflect the comprehensive nature of the changes, such as '[Feature/51] :: refactor mocker API and translation system' or provide additional context about the scope.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/51-standardize-country-specific-struct-naming-convention

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can get early access to new features in CodeRabbit.

Enable the early_access setting to enable early access features such as new models, tools, and more.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (3)
pkg/mocai/entities/gender/gender.go (1)

46-48: Consider centralizing defaultRandSource helper.

This defaultRandSource function is duplicated in phone/generator.go and likely other entity generators. To avoid drift and simplify maintenance, consider moving it to a shared location (e.g., translations package or a common package).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/entities/gender/gender.go` around lines 46 - 48, The helper
defaultRandSource (returns translations.RandSource) is duplicated across
generators (e.g., gender.go and phone/generator.go); move a single
implementation into a shared package (suggest translations or a new common
package), export it (e.g., NewDefaultRandSource or DefaultRandSource) and
replace/remove the local copies in gender.go and phone/generator.go, updating
imports and callers to use the centralized symbol to prevent drift and simplify
maintenance.
pkg/mocai/entities/phone/generator.go (1)

40-42: Duplicate defaultRandSource helper.

This is identical to the implementation in gender/gender.go. As noted earlier, consider centralizing this to avoid duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/entities/phone/generator.go` around lines 40 - 42, The function
defaultRandSource in generator.go is a duplicate of the same helper in the
gender package; remove this duplicate and instead use a single shared helper
(e.g., move the implementation into the translations package as
translations.DefaultRandSource or a new util package) and update callers to call
that shared symbol; ensure the symbol name and signature match existing callers
so you only change the implementation location (keep the function name
defaultRandSource or provide translations.DefaultRandSource and replace
references to defaultRandSource with the new shared symbol across generator.go
and gender code).
pkg/mocai/translations/translations.go (1)

18-30: Don't expose the registry's backing slices.

RegisterList stores caller-owned slices directly and GetList returns them directly. Any caller that mutates the returned slice can corrupt global translation data for later lookups, and that happens outside mu.

♻️ Suggested hardening
 func RegisterList(lang string, messages map[string][]string) {
 	if lang == "" || messages == nil {
 		return
 	}
 	mu.Lock()
 	defer mu.Unlock()
 	if registryList[lang] == nil {
 		registryList[lang] = make(map[string][]string)
 	}
 	for key, values := range messages {
-		registryList[lang][key] = values
+		registryList[lang][key] = append([]string(nil), values...)
 	}
 }
@@
 func GetList(lang, key string) []string {
 	if lang == "" || key == "" {
 		return nil
 	}
 	mu.RLock()
 	defer mu.RUnlock()
 	if val, ok := registryList[lang][key]; ok {
-		return val
+		return append([]string(nil), val...)
 	}
 	return nil
 }

Also applies to: 61-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/translations/translations.go` around lines 18 - 30, RegisterList
currently stores caller-owned slices directly into the shared registry and
GetList returns those same slices, allowing external mutation to corrupt global
state; change RegisterList to copy each incoming slice before assigning it into
registryList (copy values into a new []string) and ensure GetList returns a copy
of the stored slice(s) (or copies the map and each slice) so callers never get
direct references to the registry's backing arrays; refer to RegisterList,
GetList, registryList and mu when updating the logic and preserve mu locking
semantics while performing the copies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/mocai/entities/address/generator.go`:
- Around line 35-39: The generator currently fetches states and UFs
independently which can create invalid state↔UF combos; change the logic to
perform a single paired lookup from the translations map that represents
state→uf (use the existing state→UF mapping in translations/pt_br.go) and then
select one random pair to populate both the State and UF fields. Replace the
separate translations.GetList(lang, "address_state") and
translations.GetList(lang, "address_uf") calls with a single map/list lookup
(e.g., a stateToUf map or paired list) and update the code paths that set State
and UF (including the usage around lines referenced 63-64) to consume the chosen
pair so the two values remain consistent.
- Around line 34-35: generateAddress currently dereferences rnd (type
translations.RandSource) without checking for nil, causing NewAddress("ptbr",
nil) to panic; add a nil check at the top of generateAddress and if rnd == nil
assign the same default/fallback RandSource used by the other generators (e.g.,
rnd = translations.DefaultRand() or the package's default RandSource
initializer) before any calls to rnd.Intn or other methods so subsequent code
using rnd (and similar fixes in the other generator functions referenced around
lines 60-69) will not panic.

In `@pkg/mocai/entities/certificate/countries/brazilian_certificates.go`:
- Around line 86-90: The helpers for birth/marriage/death are dropping the
caller-provided language because generateCertificateCustom sets a hard default
and callers don't forward the variadic lang; update the helper functions (the
birth/marriage/death certificate generators that call generateCertificateCustom)
to forward their lang... variadic argument into generateCertificateCustom so the
chosen language reaches the translation layer, and normalize the internal
default in generateCertificateCustom from "pt_br" to "ptbr" to match the rest of
the codebase (examples/mocker uses "ptbr"). Ensure all other calls in this file
(including the calls around lines 142-171) also pass through the lang parameter
unchanged.

In `@pkg/mocai/entities/certificate/generator.go`:
- Around line 18-23: The wrapper generateCertificate(lang string, formatted
bool, rnd translations.RandSource) accepts a language but downstream code still
hardcodes pt_br; update countries.NewBrazilCertificatesCustom and the
Brazil-specific helper functions in pkg/mocai/entities/certificate/countries
(including generateCertificateCustom, the birth/marriage/death helper functions)
to accept and forward the lang parameter instead of defaulting to "pt_br", and
ensure NewCertificate (or equivalent constructor used in those helpers) is
called with the passed lang so translations are correctly used when
generateCertificate is invoked with non-default languages.

In `@pkg/mocai/entities/company/countries/brazilian_company.go`:
- Around line 17-30: GenerateBrazilianCompany advertises an injected random
source (rnd) but still calls cnpj.GenerateCNPJ(formatted) which ignores rnd;
update GenerateBrazilianCompany so CNPJ generation uses the same rnd (either by
calling a cnpj function that accepts a RandSource if available, e.g.,
cnpj.GenerateCNPJWithRand(rnd, formatted), or implement a small helper in this
package that generates/format CNPJ digits using rnd.Intn and the same
checksum/format logic) and replace the cnpj.GenerateCNPJ call with that
rnd-aware generator so the entire payload is reproducible via rnd.

In `@pkg/mocai/entities/nationalid/countries/brazilian_national_id.go`:
- Around line 18-23: The default language key in NewRGCustom is incorrect
("pt_br") causing untranslated metadata; update the default to the registered
key "ptbr" and ensure any fallback logic that passes the language to
generateBrazilianNationalIDCustom uses "ptbr" when len(lang)==0 or lang[0]==""
so callers that omit lang receive Brazilian Portuguese translations.

In `@pkg/mocai/entities/nationalid/generator.go`:
- Around line 17-20: The function generateNationalID currently declares an error
return but always returns nil; update its signature to remove the error return
so it returns only (*NationalID) and update the return statement to return the
value directly; locate generateNationalID and the construction using
countries.NewRGCustom and change any callers if necessary to handle the
single-return value from generateNationalID/NationalID (no error).

In `@pkg/mocai/entities/phone/generator.go`:
- Around line 30-33: The condition checking number == "" is unreachable because
number is produced by fmt.Sprintf("9%08d", ...), so remove the dead check and
only validate the meaningful condition (areaCode == ""); alternatively, if you
want defensive validation, replace number == "" with a format/length check
(e.g., len(number) != 9 or a regex) before returning ErrGeneratingPhone and the
translations.Get(lang, "error_generating_phone") error; update the condition in
the phone generation function where variables number and areaCode are used (and
the error return that references ErrGeneratingPhone and translations.Get)
accordingly.

In `@pkg/mocai/mocker.go`:
- Around line 19-23: The Mocker struct holds a shared translations.RandSource
(rnd) and exposes it via GetRand(), making Mocker mutable and unsafe for
concurrent reuse; remove direct exposure and either wrap rnd in a mutex-backed
implementation of translations.RandSource or allocate a fresh RandSource per
operation and stop returning it from GetRand(); update callers that currently
pass m.rnd into generators (look for uses in methods named GetRand, NewMocker,
and generator constructors) to accept a thread-safe wrapper or a newly created
source per call so the shared math/rand state is never concurrently accessed or
externally advanced.

In `@pkg/mocai/translations/pt_br.go`:
- Line 70: The translation key "invalid_vital_records_service number" has a
space that breaks the snake_case convention; update the key to
"invalid_vital_records_service_number" (preserving the Portuguese value "número
de serviço de registros vitais inválido") so lookups using
invalid_vital_records_service_number work correctly in the code paths that
reference that key.
- Around line 31-44: RegisterList for "ptbr" is missing the "phone_area_code"
entry, so calls to GetList("ptbr","phone_area_code") return nil; add
"phone_area_code" to the RegisterList map in pt_br.go (the same place
Register("ptbr","phone_area_code", ...) is set) and populate it with the slice
source used for single-value Register (or the underlying slice from phone_mocks
if available) so registryList contains that key; ensure you reference
RegisterList("ptbr", ...) and the existing Register("ptbr","phone_area_code",
...) to keep values consistent with GetList and registryList behavior.

In `@pkg/mocai/translations/translations.go`:
- Around line 18-31: RegisterList split left PT-BR list-backed keys registered
via Register() unreachable by GetList, causing empty slices; fix by adding a
compatibility path so GetList (or RegisterList) considers both registryList and
the older registry map: update GetList to merge entries from registryList[lang]
and registry[lang] (or, alternatively, move the pt_br registrations into
RegisterList) so keys like company_name, person_first_name_*, person_last_name,
phone_area_code are returned; reference the functions RegisterList, GetList,
Register and the maps registryList and registry when making the change.

---

Nitpick comments:
In `@pkg/mocai/entities/gender/gender.go`:
- Around line 46-48: The helper defaultRandSource (returns
translations.RandSource) is duplicated across generators (e.g., gender.go and
phone/generator.go); move a single implementation into a shared package (suggest
translations or a new common package), export it (e.g., NewDefaultRandSource or
DefaultRandSource) and replace/remove the local copies in gender.go and
phone/generator.go, updating imports and callers to use the centralized symbol
to prevent drift and simplify maintenance.

In `@pkg/mocai/entities/phone/generator.go`:
- Around line 40-42: The function defaultRandSource in generator.go is a
duplicate of the same helper in the gender package; remove this duplicate and
instead use a single shared helper (e.g., move the implementation into the
translations package as translations.DefaultRandSource or a new util package)
and update callers to call that shared symbol; ensure the symbol name and
signature match existing callers so you only change the implementation location
(keep the function name defaultRandSource or provide
translations.DefaultRandSource and replace references to defaultRandSource with
the new shared symbol across generator.go and gender code).

In `@pkg/mocai/translations/translations.go`:
- Around line 18-30: RegisterList currently stores caller-owned slices directly
into the shared registry and GetList returns those same slices, allowing
external mutation to corrupt global state; change RegisterList to copy each
incoming slice before assigning it into registryList (copy values into a new
[]string) and ensure GetList returns a copy of the stored slice(s) (or copies
the map and each slice) so callers never get direct references to the registry's
backing arrays; refer to RegisterList, GetList, registryList and mu when
updating the logic and preserve mu locking semantics while performing the
copies.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df89a63b-74b0-48fb-80e5-1aa39322e81c

📥 Commits

Reviewing files that changed from the base of the PR and between bb343f1 and 0fbbe3f.

📒 Files selected for processing (29)
  • README.md
  • docs/localization/pt/README-PT.md
  • examples/mocker/main.go
  • pkg/mocai/entities/address/generator.go
  • pkg/mocai/entities/certificate/countries/brazilian_certificates.go
  • pkg/mocai/entities/certificate/countries/errors.go
  • pkg/mocai/entities/certificate/generator.go
  • pkg/mocai/entities/cnpj/errors.go
  • pkg/mocai/entities/company/countries/brazilian_company.go
  • pkg/mocai/entities/company/countries/errors.go
  • pkg/mocai/entities/company/countries/generator_test.go
  • pkg/mocai/entities/company/generator.go
  • pkg/mocai/entities/cpf/errors.go
  • pkg/mocai/entities/cpf/generator.go
  • pkg/mocai/entities/cpf/validator.go
  • pkg/mocai/entities/gender/errors.go
  • pkg/mocai/entities/gender/gender.go
  • pkg/mocai/entities/nationalid/countries/brazilian_national_id.go
  • pkg/mocai/entities/nationalid/countries/errors.go
  • pkg/mocai/entities/nationalid/generator.go
  • pkg/mocai/entities/person/errors.go
  • pkg/mocai/entities/person/generator.go
  • pkg/mocai/entities/phone/errors.go
  • pkg/mocai/entities/phone/generator.go
  • pkg/mocai/entities/voteregistration/countries/brazilian_vote_registration.go
  • pkg/mocai/entities/voteregistration/generator.go
  • pkg/mocai/mocker.go
  • pkg/mocai/translations/pt_br.go
  • pkg/mocai/translations/translations.go

Comment on lines +34 to +35
func generateAddress(lang string, rnd translations.RandSource) (*Address, error) {
streets := translations.GetList(lang, "address_street")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Handle nil RandSource before the first Intn call.

generateAddress dereferences rnd unconditionally, so NewAddress("ptbr", nil) panics. The new API surface also allows nil, so this needs the same fallback the other generators already have.

🛠️ Suggested fix
 import (
 	"fmt"
+	"math/rand"
 	"strings"
+	"time"
 
 	"github.com/brazzcore/mocai/pkg/mocai/translations"
 )
 
 func generateAddress(lang string, rnd translations.RandSource) (*Address, error) {
+	if rnd == nil {
+		rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
+	}
 	streets := translations.GetList(lang, "address_street")

Also applies to: 60-69

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/entities/address/generator.go` around lines 34 - 35,
generateAddress currently dereferences rnd (type translations.RandSource)
without checking for nil, causing NewAddress("ptbr", nil) to panic; add a nil
check at the top of generateAddress and if rnd == nil assign the same
default/fallback RandSource used by the other generators (e.g., rnd =
translations.DefaultRand() or the package's default RandSource initializer)
before any calls to rnd.Intn or other methods so subsequent code using rnd (and
similar fixes in the other generator functions referenced around lines 60-69)
will not panic.

Comment on lines +35 to +39
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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep State and UF generated as a pair.

Selecting state and uf from independent lists can emit impossible combinations. The source data already has a state→UF relationship in pkg/mocai/translations/pt_br.go, so this should come from one paired lookup instead of two unrelated random picks.

Also applies to: 63-64

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/entities/address/generator.go` around lines 35 - 39, The generator
currently fetches states and UFs independently which can create invalid state↔UF
combos; change the logic to perform a single paired lookup from the translations
map that represents state→uf (use the existing state→UF mapping in
translations/pt_br.go) and then select one random pair to populate both the
State and UF fields. Replace the separate translations.GetList(lang,
"address_state") and translations.GetList(lang, "address_uf") calls with a
single map/list lookup (e.g., a stateToUf map or paired list) and update the
code paths that set State and UF (including the usage around lines referenced
63-64) to consume the chosen pair so the two values remain consistent.

Comment on lines +86 to +90
func generateCertificateCustom(rnd translations.RandSource, formatted bool, certificateType int, lang ...string) (*BaseCertificate, error) {
l := "pt_br"
if len(lang) > 0 && lang[0] != "" {
l = lang[0]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

lang is currently dropped before the certificate helpers reach the translation layer.

The birth/marriage/death helpers call generateCertificateCustom without forwarding lang, so that function always falls back to its internal default. That default is pt_br, while the rest of the PR uses ptbr (for example examples/mocker/main.go:15), so localized error paths from these helpers fall back to raw keys even when callers pass a language.

🔧 Suggested fix
-func generateCertificateCustom(rnd translations.RandSource, formatted bool, certificateType int, lang ...string) (*BaseCertificate, error) {
-	l := "pt_br"
-	if len(lang) > 0 && lang[0] != "" {
-		l = lang[0]
-	}
+func generateCertificateCustom(rnd translations.RandSource, formatted bool, certificateType int, lang string) (*BaseCertificate, error) {
+	if lang == "" {
+		lang = "ptbr"
+	}
@@
-base, err := generateCertificateCustom(rnd, formatted, brazilianBirthCertificateType)
+base, err := generateCertificateCustom(rnd, formatted, brazilianBirthCertificateType, lang)
@@
-base, err := generateCertificateCustom(rnd, formatted, brazilianMarriageCertificateType)
+base, err := generateCertificateCustom(rnd, formatted, brazilianMarriageCertificateType, lang)
@@
-base, err := generateCertificateCustom(rnd, formatted, brazilianDeathCertificateType)
+base, err := generateCertificateCustom(rnd, formatted, brazilianDeathCertificateType, lang)

Also applies to: 142-171

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/entities/certificate/countries/brazilian_certificates.go` around
lines 86 - 90, The helpers for birth/marriage/death are dropping the
caller-provided language because generateCertificateCustom sets a hard default
and callers don't forward the variadic lang; update the helper functions (the
birth/marriage/death certificate generators that call generateCertificateCustom)
to forward their lang... variadic argument into generateCertificateCustom so the
chosen language reaches the translation layer, and normalize the internal
default in generateCertificateCustom from "pt_br" to "ptbr" to match the rest of
the codebase (examples/mocker uses "ptbr"). Ensure all other calls in this file
(including the calls around lines 142-171) also pass through the lang parameter
unchanged.

Comment on lines +18 to +23
func generateCertificate(lang string, formatted bool, rnd translations.RandSource) (*Certificate, error) {
createdBrazilianCertificates, err := countries.NewBrazilCertificatesCustom(lang, formatted, rnd)
if err != nil {
return nil, err
}
return &Certificate{BrazilianCertificates: createdBrazilianCertificates}, nil
return &Certificate{Brazil: createdBrazilianCertificates}, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The new lang parameter is not fully honored yet.

This wrapper advertises custom-language generation, but the supplied context for pkg/mocai/entities/certificate/countries/brazilian_certificates.go still defaults generateCertificateCustom to pt_br, and the birth/marriage/death helpers there do not forward lang. NewCertificate("ptbr", ...) can therefore still miss translations even though Line 19 accepts the caller’s language.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/entities/certificate/generator.go` around lines 18 - 23, The
wrapper generateCertificate(lang string, formatted bool, rnd
translations.RandSource) accepts a language but downstream code still hardcodes
pt_br; update countries.NewBrazilCertificatesCustom and the Brazil-specific
helper functions in pkg/mocai/entities/certificate/countries (including
generateCertificateCustom, the birth/marriage/death helper functions) to accept
and forward the lang parameter instead of defaulting to "pt_br", and ensure
NewCertificate (or equivalent constructor used in those helpers) is called with
the passed lang so translations are correctly used when generateCertificate is
invoked with non-default languages.

Comment on lines +30 to 33
number := fmt.Sprintf("9%08d", rnd.Intn(100000000))
if areaCode == "" || number == "" {
return nil, fmt.Errorf("%s: missing required data (areaCode: %s, number: %s)",
ErrGeneratingPhone, areaCode, number)
return nil, fmt.Errorf("%w, %s", ErrGeneratingPhone, translations.Get(lang, "error_generating_phone"))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unreachable condition: number can never be empty.

The number == "" check on line 31 is always false because fmt.Sprintf("9%08d", ...) always produces a 9-character string. Consider removing this dead branch or, if validating against unexpected formats, checking for a more meaningful condition.

♻️ Proposed simplification
 	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"))
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/entities/phone/generator.go` around lines 30 - 33, The condition
checking number == "" is unreachable because number is produced by
fmt.Sprintf("9%08d", ...), so remove the dead check and only validate the
meaningful condition (areaCode == ""); alternatively, if you want defensive
validation, replace number == "" with a format/length check (e.g., len(number)
!= 9 or a regex) before returning ErrGeneratingPhone and the
translations.Get(lang, "error_generating_phone") error; update the condition in
the phone generation function where variables number and areaCode are used (and
the error return that references ErrGeneratingPhone and translations.Get)
accordingly.

Comment on lines 19 to 23
type Mocker struct {
Address *address.Address
Certificate *certificate.Certificate
Company *company.Company
Gender *gender.Gender
NationalID *nationalid.NationalID
Person *person.Person
Phone *phone.Phone
VoteRegistration *voteregistration.VoteRegistration

formatted bool
lang string
formatted bool
rnd translations.RandSource
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Is Go's math/rand.Rand safe for concurrent use by multiple goroutines, and are only the package-level math/rand functions concurrency-safe?

💡 Result:

No, Go's math/rand.Rand is not safe for concurrent use by multiple goroutines. It must be used by a single goroutine at a time; sharing among multiple goroutines requires synchronization such as a mutex. Yes, the package-level math/rand functions (top-level functions like Int, Float64, etc.) are safe for concurrent use by multiple goroutines, as they use a synchronized default Source.

Citations:


🏁 Script executed:

cd pkg/mocai && head -100 mocker.go | cat -n

Repository: brazzcore/mocai

Length of output: 3776


The shared RNG makes Mocker mutable and unsafe to reuse concurrently.

This instance keeps one RandSource, passes it into every generator, and then exposes it again via GetRand(). When the default is *rand.Rand, a single mocker used from multiple goroutines will race inside math/rand, and external callers can advance the sequence through GetRand(), which undercuts the "immutable instance" contract. Consider wrapping the source in a mutex-backed RandSource or creating a fresh source per call, and avoid returning it directly.

Also applies to: 25-39, 87-89

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/mocker.go` around lines 19 - 23, The Mocker struct holds a shared
translations.RandSource (rnd) and exposes it via GetRand(), making Mocker
mutable and unsafe for concurrent reuse; remove direct exposure and either wrap
rnd in a mutex-backed implementation of translations.RandSource or allocate a
fresh RandSource per operation and stop returning it from GetRand(); update
callers that currently pass m.rnd into generators (look for uses in methods
named GetRand, NewMocker, and generator constructors) to accept a thread-safe
wrapper or a newly created source per call so the shared math/rand state is
never concurrently accessed or externally advanced.

Comment on lines +31 to +44
// registers complete lists for use in mocks
RegisterList("ptbr", map[string][]string{
"address_street": address_mocks.Streets,
"address_city": address_mocks.Cities,
"address_state": address_mocks.States,
"address_uf": func() []string {
ufs := make([]string, 0, len(address_mocks.UFs))
for _, uf := range address_mocks.UFs {
ufs = append(ufs, uf)
}
return ufs
}(),
"address_zip": address_mocks.ZIPCodes,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing phone_area_code registration in RegisterList.

The phone_area_code key is registered only via Register() at line 63 as a single random string, but not via RegisterList(). Based on the GetList() implementation in translations.go (lines 62-72), any code calling GetList("ptbr", "phone_area_code") will receive nil since the key doesn't exist in registryList.

Add phone_area_code to the list registrations:

Proposed fix
 	// registers complete lists for use in mocks
 	RegisterList("ptbr", map[string][]string{
 		"address_street": address_mocks.Streets,
 		"address_city":   address_mocks.Cities,
 		"address_state":  address_mocks.States,
 		"address_uf": func() []string {
 			ufs := make([]string, 0, len(address_mocks.UFs))
 			for _, uf := range address_mocks.UFs {
 				ufs = append(ufs, uf)
 			}
 			return ufs
 		}(),
 		"address_zip": address_mocks.ZIPCodes,
+		"phone_area_code": phone_mocks.AreaCodes,
 	})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// registers complete lists for use in mocks
RegisterList("ptbr", map[string][]string{
"address_street": address_mocks.Streets,
"address_city": address_mocks.Cities,
"address_state": address_mocks.States,
"address_uf": func() []string {
ufs := make([]string, 0, len(address_mocks.UFs))
for _, uf := range address_mocks.UFs {
ufs = append(ufs, uf)
}
return ufs
}(),
"address_zip": address_mocks.ZIPCodes,
})
// registers complete lists for use in mocks
RegisterList("ptbr", map[string][]string{
"address_street": address_mocks.Streets,
"address_city": address_mocks.Cities,
"address_state": address_mocks.States,
"address_uf": func() []string {
ufs := make([]string, 0, len(address_mocks.UFs))
for _, uf := range address_mocks.UFs {
ufs = append(ufs, uf)
}
return ufs
}(),
"address_zip": address_mocks.ZIPCodes,
"phone_area_code": phone_mocks.AreaCodes,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/translations/pt_br.go` around lines 31 - 44, RegisterList for
"ptbr" is missing the "phone_area_code" entry, so calls to
GetList("ptbr","phone_area_code") return nil; add "phone_area_code" to the
RegisterList map in pt_br.go (the same place Register("ptbr","phone_area_code",
...) is set) and populate it with the slice source used for single-value
Register (or the underlying slice from phone_mocks if available) so registryList
contains that key; ensure you reference RegisterList("ptbr", ...) and the
existing Register("ptbr","phone_area_code", ...) to keep values consistent with
GetList and registryList behavior.

"invalid_certificate": "certidão inválida",
"invalid_vital_records_office_number": "número do cartório de registros civis inválido",
"invalid_archive_number": "número do arquivo inválido",
"invalid_vital_records_service number": "número de serviço de registros vitais inválido",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: inconsistent key naming with space instead of underscore.

The key "invalid_vital_records_service number" contains a space before "number" instead of an underscore. This breaks the snake_case naming convention used by all other keys.

Proposed fix
-		"invalid_vital_records_service number": "número de serviço de registros vitais inválido",
+		"invalid_vital_records_service_number": "número de serviço de registros vitais inválido",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"invalid_vital_records_service number": "número de serviço de registros vitais inválido",
"invalid_vital_records_service_number": "número de serviço de registros vitais inválido",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/translations/pt_br.go` at line 70, The translation key
"invalid_vital_records_service number" has a space that breaks the snake_case
convention; update the key to "invalid_vital_records_service_number" (preserving
the Portuguese value "número de serviço de registros vitais inválido") so
lookups using invalid_vital_records_service_number work correctly in the code
paths that reference that key.

Comment on lines +18 to 31
// RegisterList records lists of translations
func RegisterList(lang string, messages map[string][]string) {
if lang == "" || messages == nil {
return
}

mu.Lock()
defer mu.Unlock()

if registry[lang] == nil {
registry[lang] = make(map[string]string)
if registryList[lang] == nil {
registryList[lang] = make(map[string][]string)
}
for key, value := range messages {
registry[lang][key] = value
for key, values := range messages {
registryList[lang][key] = values
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

This split breaks the current PT-BR list-backed datasets.

GetList only reads registryList, but the current pkg/mocai/translations/pt_br.go:51-87 still registers keys like company_name, person_first_name_*, person_last_name, and phone_area_code through Register(). That leaves every GetList("ptbr", ...) caller with an empty slice and causes generators like company, person, and phone to fail immediately. Either migrate those registrations in the same change or add a compatibility path here.

Also applies to: 61-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/mocai/translations/translations.go` around lines 18 - 31, RegisterList
split left PT-BR list-backed keys registered via Register() unreachable by
GetList, causing empty slices; fix by adding a compatibility path so GetList (or
RegisterList) considers both registryList and the older registry map: update
GetList to merge entries from registryList[lang] and registry[lang] (or,
alternatively, move the pt_br registrations into RegisterList) so keys like
company_name, person_first_name_*, person_last_name, phone_area_code are
returned; reference the functions RegisterList, GetList, Register and the maps
registryList and registry when making the change.

@wellfernandes wellfernandes merged commit d2bc7b9 into develop Mar 21, 2026
2 checks passed
@wellfernandes wellfernandes deleted the feature/51-standardize-country-specific-struct-naming-convention branch March 21, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants