go-faker/generator/generator.go
selamanapps 4ff98a40d4 feat: initial go-faker - Ethiopian fake data generator
- High-performance batch data generation (~150k records/sec)
- Seeded randomness for reproducible load tests
- Rich Ethiopian data: names, phones (+251), cities, regions
- Four data types: person, address, product, analytics
- Three modes: CLI, Go library, HTTP API
- Project skill available in .skills/go-faker/SKILL.md
2026-05-02 02:59:43 +03:00

316 lines
No EOL
8.4 KiB
Go

package generator
import (
"crypto/md5"
"fmt"
"math/rand"
"time"
"go-faker/data"
"go-faker/types"
)
type Faker struct {
rng *rand.Rand
}
func New(seed string) *Faker {
s := hashSeed(seed)
src := rand.NewSource(s)
return &Faker{rng: rand.New(src)}
}
func hashSeed(seed string) int64 {
h := md5.Sum([]byte(seed))
var result int64
for i := 0; i < 8; i++ {
result = result<<8 | int64(h[i])
}
return result
}
func (f *Faker) generateUUID() string {
b := make([]byte, 16)
f.rng.Read(b)
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
func (f *Faker) generateSKU() string {
return fmt.Sprintf("SKU-%08d", f.rng.Intn(99999999))
}
func (f *Faker) generatePhone() string {
prefix := data.PhonePrefixes[f.rng.Intn(len(data.PhonePrefixes))]
number := fmt.Sprintf("%07d", f.rng.Intn(9999999))
return prefix + number
}
func (f *Faker) generateEmail(firstName, lastName string) string {
domains := []string{"gmail.com", "yahoo.com", "outlook.com", "hotmail.com", "icloud.com", "telecom.net.et", "ethiotelecom.et"}
domain := domains[f.rng.Intn(len(domains))]
separator := []string{".", "_", ""}
sep := separator[f.rng.Intn(len(separator))]
variants := []string{
fmt.Sprintf("%s%s%s@%s", firstName, sep, lastName, domain),
fmt.Sprintf("%s.%s@%s", firstName, lastName, domain),
fmt.Sprintf("%s%d@%s", firstName, f.rng.Intn(999), domain),
fmt.Sprintf("%s_%s%d@%s", firstName, lastName, f.rng.Intn(99), domain),
}
return variants[f.rng.Intn(len(variants))]
}
func (f *Faker) generateAge() int {
return f.rng.Intn(72) + 18
}
func (f *Faker) generateDateOfBirth() string {
now := time.Now()
maxAge := 72
minAge := 18
maxBirthDate := now.AddDate(-minAge, 0, 0)
minBirthDate := now.AddDate(-maxAge, 0, 0)
unixMin := minBirthDate.Unix()
unixMax := maxBirthDate.Unix()
unixBirth := unixMin + f.rng.Int63n(unixMax-unixMin)
t := time.Unix(unixBirth, 0)
return t.Format("2006-01-02")
}
func (f *Faker) generatePastDate(maxDaysAgo int) string {
now := time.Now()
daysAgo := f.rng.Intn(maxDaysAgo)
past := now.AddDate(0, 0, -daysAgo)
return past.Format("2006-01-02")
}
func (f *Faker) generateFutureDate(maxDaysAhead int) string {
now := time.Now()
daysAhead := f.rng.Intn(maxDaysAhead)
future := now.AddDate(0, 0, daysAhead)
return future.Format("2006-01-02")
}
func (f *Faker) generatePrice() float64 {
return float64(f.rng.Intn(9999999))/100.0 + 0.50
}
func (f *Faker) generateWeight() float64 {
return float64(f.rng.Intn(1000))/10.0 + 0.1
}
func (f *Faker) generateStock() int {
return f.rng.Intn(1000)
}
func (f *Faker) generateCoordinates() (float64, float64) {
lat := float64(f.rng.Intn(900000))/10000.0 + 3.0
lon := float64(f.rng.Intn(900000))/10000.0 + 33.0
return lat, lon
}
func (f *Faker) generateIPAddress() string {
return fmt.Sprintf("10.%d.%d.%d", f.rng.Intn(256), f.rng.Intn(256), f.rng.Intn(256))
}
func (f *Faker) generateSessionID() string {
return fmt.Sprintf("sess-%s", f.generateUUID())
}
func (f *Faker) generatePassword() string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
password := make([]byte, 12)
for i := range password {
password[i] = charset[f.rng.Intn(len(charset))]
}
return string(password)
}
func (f *Faker) generatePostalCode() string {
return fmt.Sprintf("%04d", f.rng.Intn(9999))
}
func (f *Faker) generateHouseNumber() string {
return fmt.Sprintf("House-%d", f.rng.Intn(999)+1)
}
func (f *Faker) generateAmount() float64 {
return float64(f.rng.Intn(100000))/100.0 + 0.01
}
func (f *Faker) pickString(arr []string) string {
return arr[f.rng.Intn(len(arr))]
}
func (f *Faker) GeneratePerson(fields []string) map[string]any {
firstName := f.pickString(data.EthiopianFirstNames)
lastName := f.pickString(data.EthiopianLastNames)
result := make(map[string]any)
for _, field := range fields {
switch field {
case "first_name":
result[field] = firstName
case "last_name":
result[field] = lastName
case "full_name":
result[field] = firstName + " " + lastName
case "phone":
result[field] = f.generatePhone()
case "email":
result[field] = f.generateEmail(firstName, lastName)
case "gender":
result[field] = f.pickString(data.Genders)
case "age":
result[field] = f.generateAge()
case "language":
result[field] = f.pickString(data.Languages)
case "city":
result[field] = f.pickString(data.EthiopianCities)
case "region":
result[field] = f.pickString(data.EthiopianRegions)
case "nationality":
result[field] = f.pickString(data.Nationalities)
case "occupation":
result[field] = f.pickString(data.Occupations)
case "company":
result[field] = f.pickString(data.Companies)
case "blood_type":
result[field] = f.pickString(data.BloodTypes)
case "date_of_birth":
result[field] = f.generateDateOfBirth()
case "username":
result[field] = fmt.Sprintf("%s%s%d", firstName, lastName, f.rng.Intn(99))
case "password":
result[field] = f.generatePassword()
}
}
return result
}
func (f *Faker) GenerateAddress(fields []string) map[string]any {
result := make(map[string]any)
city := f.pickString(data.EthiopianCities)
for _, field := range fields {
switch field {
case "street":
result[field] = f.pickString(data.EthiopianStreets)
case "city":
result[field] = city
case "region":
result[field] = f.pickString(data.EthiopianRegions)
case "sub_city":
if subCities, ok := data.EthiopianSubCities[city]; ok && len(subCities) > 0 {
result[field] = f.pickString(subCities)
} else {
result[field] = city + " Sub City"
}
case "postal_code":
result[field] = f.generatePostalCode()
case "zone":
result[field] = "Zone " + fmt.Sprintf("%d", f.rng.Intn(20)+1)
case "woreda":
result[field] = f.pickString(data.EthiopianWoredas)
case "kebele":
result[field] = fmt.Sprintf("Kebele %d", f.rng.Intn(15)+1)
case "house_number":
result[field] = f.generateHouseNumber()
case "latitude", "longitude":
lat, lon := f.generateCoordinates()
result["latitude"] = lat
result["longitude"] = lon
}
}
return result
}
func (f *Faker) GenerateProduct(fields []string) map[string]any {
result := make(map[string]any)
for _, field := range fields {
switch field {
case "name":
result[field] = f.pickString(data.ProductNames)
case "category":
result[field] = f.pickString(data.ProductCategories)
case "price":
result[field] = f.generatePrice()
case "sku":
result[field] = f.generateSKU()
case "brand":
result[field] = f.pickString(data.Brands)
case "description":
result[field] = "High quality " + f.pickString(data.ProductCategories)
case "weight":
result[field] = f.generateWeight()
case "stock":
result[field] = f.generateStock()
case "expiry_date":
result[field] = f.generateFutureDate(365*3)
case "manufactured_date":
result[field] = f.generatePastDate(365*2)
}
}
return result
}
func (f *Faker) GenerateAnalytics(fields []string) map[string]any {
result := make(map[string]any)
for _, field := range fields {
switch field {
case "user_id":
result[field] = "user-" + fmt.Sprintf("%d", f.rng.Intn(999999))
case "event":
result[field] = f.pickString(data.EventTypes)
case "timestamp":
now := time.Now()
secondsAgo := f.rng.Int63n(86400 * 30)
t := time.Unix(now.Unix()-secondsAgo, 0)
result[field] = t.Format(time.RFC3339)
case "amount":
result[field] = f.generateAmount()
case "currency":
result[field] = "ETB"
case "session_id":
result[field] = f.generateSessionID()
case "page_url":
result[field] = fmt.Sprintf("https://example.com/page-%d", f.rng.Intn(100))
case "ip_address":
result[field] = f.generateIPAddress()
case "device_type":
result[field] = f.pickString(data.DeviceTypes)
case "browser":
result[field] = f.pickString(data.Browsers)
case "os":
result[field] = f.pickString(data.OperatingSystems)
case "country":
result[field] = f.pickString(data.Countries)
case "referrer":
result[field] = f.pickString(data.Referrers)
}
}
return result
}
func (f *Faker) GenerateBatch(count int, dataType types.DataType, fields []string) []map[string]any {
results := make([]map[string]any, count)
for i := 0; i < count; i++ {
switch dataType {
case types.TypePerson:
results[i] = f.GeneratePerson(fields)
case types.TypeAddress:
results[i] = f.GenerateAddress(fields)
case types.TypeProduct:
results[i] = f.GenerateProduct(fields)
case types.TypeAnalytics:
results[i] = f.GenerateAnalytics(fields)
}
}
return results
}