- 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
316 lines
No EOL
8.4 KiB
Go
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
|
|
} |