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
This commit is contained in:
selamanapps 2026-05-02 02:59:10 +03:00
commit 4ff98a40d4
10 changed files with 1081 additions and 0 deletions

30
.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Binaries
*.exe
*.exe~
*.dll
*.so
*.dylib
# Build output
gofaker
bin/
# Test binary
*.test
# Output of go coverage
*.out
# Dependency directories
vendor/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db

175
README.md Normal file
View file

@ -0,0 +1,175 @@
# Go-Faker
High-performance Ethiopian fake data generator for load testing and development.
## Features
- **Seeded randomness** - Reproducible datasets across runs
- **Batch generation** - Generate thousands of records in a single call
- **Rich Ethiopian data** - Names, cities, regions, phone numbers (+251), and more
- **Two modes** - Use as a Go library or HTTP API server
- **Fast** - ~150k records/second throughput
## Quick Start
### CLI
```bash
# Generate 10 person records
./gofaker -count 10 -type person
# Generate 100 product records with specific fields
./gofaker -count 100 -type product -fields name,price,sku
# Use a seed for reproducible data
./gofaker -count 50 -type person -seed my-test-seed
```
### API Server
```bash
# Start server
./gofaker -server -addr :8080
# Generate via HTTP
curl -X POST http://localhost:8080/generate \
-H "Content-Type: application/json" \
-d '{"seed":"test","count":10,"type":"person","fields":["first_name","phone","email"]}'
```
### Library
```go
import "go-faker"
f := faker.New("my-seed")
// Generate single record
person := f.GeneratePerson([]string{"first_name", "phone", "email"})
// Generate batch
persons := f.GenerateBatch(1000, "person", []string{"first_name", "email", "city"})
```
## Data Types
### person
| Field | Description |
|-------|-------------|
| first_name | Ethiopian first name |
| last_name | Ethiopian last name |
| full_name | First + last name |
| phone | Ethiopian mobile (+2519x...) |
| email | Generated email address |
| gender | Male/Female |
| age | 18-90 |
| language | Ethiopian language |
| city | Ethiopian city |
| region | Ethiopian region |
| nationality | Ethiopian/nearby |
| occupation | Job title |
| company | Ethiopian company |
| blood_type | Blood type |
| date_of_birth | YYYY-MM-DD |
| username | Generated username |
| password | Secure random password |
### address
| Field | Description |
|-------|-------------|
| street | Street name |
| city | City |
| region | Region |
| sub_city | Sub-city (Addis Ababa) |
| postal_code | 4-digit code |
| zone | Zone number |
| woreda | Woreda |
| kebele | Kebele number |
| house_number | House identifier |
| latitude | GPS coordinate |
| longitude | GPS coordinate |
### product
| Field | Description |
|-------|-------------|
| name | Product name |
| category | Product category |
| price | Price in ETB |
| sku | Stock keeping unit |
| brand | Brand name |
| description | Product description |
| weight | Weight in kg |
| stock | Stock quantity |
| expiry_date | Future date |
| manufactured_date | Past date |
### analytics
| Field | Description |
|-------|-------------|
| user_id | user-XXXXX |
| event | Event type |
| timestamp | RFC3339 timestamp |
| amount | Transaction amount |
| currency | ETB |
| session_id | Session identifier |
| page_url | Page URL |
| ip_address | IP address (10.x.x.x) |
| device_type | Device type |
| browser | Browser name |
| os | Operating system |
| country | Country |
| referrer | Referrer URL |
## API Reference
### POST /generate
Generate fake data.
**Request:**
```json
{
"seed": "optional-seed-string",
"count": 100,
"type": "person",
"fields": ["first_name", "email", "phone"]
}
```
**Response:**
```json
{
"data": [
{"first_name": "Tadesse", "email": "Tadesse@email.com", "phone": "+251911234567"}
],
"meta": {
"count": 100,
"seed": "optional-seed-string",
"generated_at": "2026-05-02T10:00:00Z"
},
"format": "json"
}
```
### GET /fields
List all valid fields per data type.
### GET /health
Health check endpoint.
## Performance
| Records | Time | Rate |
|---------|------|------|
| 10,000 | 0.4s | ~25k/s |
| 100,000 | 0.7s | ~143k/s |
## License
MIT

100
api/handlers.go Normal file
View file

@ -0,0 +1,100 @@
package api
import (
"encoding/json"
"net/http"
"time"
"go-faker/generator"
"go-faker/types"
)
type Handler struct {
faker *generator.Faker
}
func NewHandler(seed string) *Handler {
return &Handler{
faker: generator.New(seed),
}
}
func (h *Handler) Generate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req types.GenerateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body: "+err.Error(), http.StatusBadRequest)
return
}
defer r.Body.Close()
if req.Count <= 0 || req.Count > 100000 {
http.Error(w, "Count must be between 1 and 100000", http.StatusBadRequest)
return
}
if req.Seed == "" {
req.Seed = time.Now().UTC().Format(time.RFC3339Nano)
}
faker := generator.New(req.Seed)
validFields, ok := types.ValidFields[req.Type]
if !ok {
http.Error(w, "Invalid data type", http.StatusBadRequest)
return
}
if len(req.Fields) == 0 {
req.Fields = validFields
}
fieldSet := make(map[string]bool)
for _, f := range validFields {
fieldSet[f] = true
}
for _, f := range req.Fields {
if !fieldSet[f] {
http.Error(w, "Invalid field: "+f, http.StatusBadRequest)
return
}
}
data := faker.GenerateBatch(req.Count, req.Type, req.Fields)
response := types.GenerateResponse{
Data: data,
Format: "json",
Meta: types.ResponseMeta{
Count: req.Count,
Seed: req.Seed,
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
},
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
json.NewEncoder(w).Encode(response)
}
func (h *Handler) Health(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
func (h *Handler) Fields(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
json.NewEncoder(w).Encode(types.ValidFields)
}
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/generate", h.Generate)
mux.HandleFunc("/fields", h.Fields)
mux.HandleFunc("/health", h.Health)
}

43
api/server.go Normal file
View file

@ -0,0 +1,43 @@
package api
import (
"log"
"net/http"
"os"
"os/signal"
"syscall"
)
type Server struct {
addr string
mux *http.ServeMux
}
func NewServer(addr string) *Server {
mux := http.NewServeMux()
return &Server{addr: addr, mux: mux}
}
func (s *Server) Start() error {
handler := NewHandler("default-seed")
handler.RegisterRoutes(s.mux)
server := &http.Server{
Addr: s.addr,
Handler: s.mux,
}
go func() {
log.Printf("Go-Faker API server starting on %s", s.addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
return server.Close()
}

81
cmd/gofaker/main.go Normal file
View file

@ -0,0 +1,81 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"go-faker/api"
"go-faker/generator"
"go-faker/types"
)
var (
serverMode bool
addr string
seed string
count int
dataType string
fieldsStr string
)
func init() {
flag.BoolVar(&serverMode, "server", false, "Run as HTTP API server")
flag.StringVar(&addr, "addr", ":8080", "Server address")
flag.StringVar(&seed, "seed", "default", "Random seed for reproducible data")
flag.IntVar(&count, "count", 10, "Number of records to generate")
flag.StringVar(&dataType, "type", "person", "Data type: person, address, product, analytics")
flag.StringVar(&fieldsStr, "fields", "", "Comma-separated fields (empty = all)")
}
func main() {
flag.Parse()
if serverMode {
server := api.NewServer(addr)
if err := server.Start(); err != nil {
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
os.Exit(1)
}
return
}
dt := types.DataType(dataType)
validFields, ok := types.ValidFields[dt]
if !ok {
fmt.Fprintf(os.Stderr, "Invalid type: %s. Valid types: person, address, product, analytics\n", dataType)
os.Exit(1)
}
var fields []string
if fieldsStr == "" {
fields = validFields
} else {
fields = splitFields(fieldsStr)
}
faker := generator.New(seed)
results := faker.GenerateBatch(count, dt, fields)
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(results); err != nil {
fmt.Fprintf(os.Stderr, "JSON encode error: %v\n", err)
os.Exit(1)
}
}
func splitFields(s string) []string {
var result []string
start := 0
for i := 0; i <= len(s); i++ {
if i == len(s) || s[i] == ',' {
if start < i {
result = append(result, s[start:i])
}
start = i + 1
}
}
return result
}

163
data/ethiopian.go Normal file
View file

@ -0,0 +1,163 @@
package data
var EthiopianFirstNames = []string{
"Abebe", "Abera", "Abel", "Abraham", "Abrham", "Adane", "Admas", "Alem", "Alemayehu",
"Alex", "Almaz", "Amare", "Amir", "Anna", "Asrat", "Aster", "Ayana", "Bekele", "Belete",
"Bethlehem", "Birtukan", "Bogale", "Chala", "Chaltu", "Daniel", "Dagmawi", "Dawit", "Delina",
"Deribe", "Diriba", "Duncan", "Eleni", "Elias", "Ermias", "Esete", "Frehiwot", "Gabriel",
"Gashaw", "Genet", "Getachew", "Girma", "Gitanjali", "Gobena", "Gordon", "Haben", "Hailemariam",
"Haimanot", "Hana", "Helen", "Henok", "Hirut", "Hiwot", "Jamal", "Jerome", "Jimma", "Kaleab",
"Kaleb", "Kassa", "Kebede", "Kifle", "Kiros", "Kumneger", "Lalem", "Lidya", "Lily", "Mahlet",
"Makda", "Malaku", "Mamo", "Manahil", "Mariam", "Markos", "Marta", "Mekdes",
"Mekonnen", "Melaku", "Melat", "Melese", "Mengistu", "Meron", "Michele", "Mihret",
"Mikael", "Mulu", "Nathanael", "Negash", "Nega", "Netsanet", "Newer", "Nigus", "Nikol",
"Paul", "Philip", "Rahel", "Samrawit", "Samuel", "Sara", "Selam", "Sisay", "Solomon", "Sofia",
"Tadesse", "Tamrat", "Tania", "Tarik", "Tatiana", "Tebeje", "Tefera", "Tenagne", "Tenna",
"Tigist", "Tirgo", "Tobias", "Tsigereda", "Voke", "Wallelign", "Wolde", "Woldegeorgis",
"Yared", "Yasmin", "Yecats", "Yohannes", "Zelalem", "Zeleke", "Zewditu", "Zinaye", "Zinash",
}
var EthiopianLastNames = []string{
"Abebe", "Abera", "Abitew", "Admas", "Alemayehu", "Alemu", "Asnakech", "Bekele", "Belete",
"Berhanu", "Birhanu", "Birhane", "Bogale", "Chala", "Cheneke", "Dale", "Daniel", "Dembel",
"Deressa", "Desalegn", "Dessalegn", "Dibaba", "Diriba", "Dubale", "Dulo", "Eshete", "Fekadu",
"Feleke", "Fikadu", "Fikremariam", "Fre", "G/Medhin", "Gashaw", "Gebre", "Gebrekidan",
"Gebremariam", "Gebremedhin", "Gemechu", "Getachew", "Girma", "Gobena", "Guta", "Hailu",
"Haimanot", "Hall", "Jamal", "Jember", "Kassa", "Kassaye", "Kebede", "Kumssa", "Lema",
"Lemma", "Mekonnen", "Melaku", "Melkamu", "Mengistu", "Mersha", "Michele", "Mekdes",
"Molla", "Mulat", "Negeri", "Nega", "Olana", "Oumer", "Ramos", "Saba", "Seifu", "Sisay",
"Solomon", "Tadesse", "Tafesse", "Taye", "Tefera", "Teklu", "Temesgen", "Tessema", "Tigabe",
"Tigist", "Tola", "Wakjira", "Wolde", "Woldegeorgis", "Woldesemayat", "Yohannes", "Zenebe",
"Zewde", "Zewdie", "Zinabu",
}
var EthiopianCities = []string{
"Addis Ababa", "Dire Dawa", "Harar", "Adama", "Hawassa", "Mekelle", "Gondar", "Bahir Dar",
"Semera", "Asosa", " Gambela", "Jijiga", "Akordat", "Axum", "Lalibela", "Bahr Dar",
"Jimma", "Woliso", "Bishoftu", "Adwa", "Debre Markos", "Debre Tabor", "Dessie", "Dolo Ado",
"East Wollega", "Gambela", "Gojjam", "Gondar", "Harar", "Hosaena", "Jijiga", "Jimma",
"Kaffa", "Metu", "Nekemte", "Arba Minch", "Shashamane", "Wolaita Sodo", "Dilla", "Mizan Teferi",
"Sawla", "Kelem Welega", "West Wellega", "East Wellega", "Kaffa", "Gedeo", "Wolita",
"Alaba", "Konso", "Dawro", "Bench", "Sheka", "Mareko",
}
var EthiopianRegions = []string{
"Addis Ababa", "Afar", "Amhara", "Benishangul-Gumuz", "Central Somalia", "Dire Dawa",
"Gambela", "Harari", "Oromia", "SNNPR", "Somalia", "Tigray",
}
var EthiopianSubCities = map[string][]string{
"Addis Ababa": {"Akaki", "Bole", "Gullele", "Kirkos", "Kolfe", "Lideta", "Lemmi", "Yeka", "Nifassilk", "Addis Ketema"},
"Dire Dawa": {"Dire Dawa", "Gildessa", "Mekonnen"},
"Harar": {"Harar", "Jigil", "Shah",
"Aboker", "Sinje"},
}
var EthiopianWoredas = []string{
"Adama", "Agarfa", "Akaki", "Alaba", "Alamata", "Areka", "Asossa", "Awasa", "Axum",
" Bahir Dar", "Bale", "Bonga", "Burayu", "Chiro", "Dabra", "Dalocha", "Dangila", "Deder",
"Dembel", "Dessie", "Dilla", "Dolo Ado", "Dubti", "East Wollega", "Endasilasie", "Fentale",
"Gimbi", "Gondar", "Grawa", "Hagere Selam", "Harar", "Hosaena", "Jijiga", "Jimma",
"Karrayu", "Kofele", "Konga", "Lalibela", "Liben", "Mekelle", "Mekonnen", "Mizan",
"Mojo", "Negele", "Nekemte", "Otona", "Sawla", "Semera", "Shashamane", "Shire", "Sodo",
"Sululta", "Tepi", "Welenchiti", "Woliso", "Wondo", "Yirgalem", "Zeway",
}
var EthiopianStreets = []string{
" Bole Road", "Africa Avenue", "Cameroon Street", "Nigeria Street", "Kenya Street",
"Egypt Street", "Congo Street", "Sudan Avenue", "Guinea Street", "Tanzania Road",
"Uganda Street", "Rwanda Avenue", "Ghana Street", "Sierra Leone Road", "Liberia Street",
"Morocco Avenue", "Algeria Street", "Tunisia Road", "Libya Street", "Mauritania Avenue",
"Ethiopia Street", "National Palace Road", "Church Road", "Market Street", "Station Road",
"Railway Street", "Airport Road", "University Avenue", "Hospital Road", "School Street",
"Police Station Road", "Fire Brigade Street", "Post Office Avenue", "Telecom Street",
"Industrial Road", "Factory Street", "Workshop Road", "Garage Avenue", "Hotel Street",
}
var PhonePrefixes = []string{
"+25191", "+25192", "+25193", "+25194", "+25195", "+25196", "+25197", "+25198", "+25199",
"+25170", "+25171", "+25172", "+25173", "+25174", "+25175", "+25176", "+25177", "+25178", "+25179",
"+25111", "+25112", "+25113", "+25114", "+25115", "+25116", "+25117", "+25118", "+25119",
}
var BloodTypes = []string{"A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"}
var Genders = []string{"Male", "Female"}
var Languages = []string{
"Amharic", "Oromiffa", "Tigrinya", "Somali", "Arabic", "Wolayta", "Affar", "Hadiyya",
"Gedeo", "Kambaata", "Konso", "Burji", "Daasenech", "Mursi", "Hamer", "Nyangatom",
"Kara", "Maale", "Dinka", "Nuwer", "Jie", "Toposa", "Anyuak", "Shilluk", "Mundang",
"Koma", "Opta", "Shekkacho", "Maji", "Bear", "Weyto", "Kusto", "Mesmes", "Kewama", "Gumuz",
"Berta", "Hadendowa", "Blin", "Kunama", "Nara", "Rashaida",
}
var Occupations = []string{
"Software Engineer", "Doctor", "Teacher", "Nurse", "Accountant", "Engineer", "Architect",
"Lawyer", "Journalist", "Police Officer", "Soldier", "Farmer", "Trader", "Business Owner",
"Government Employee", "Banker", "Electrician", "Plumber", "Carpenter", "Driver",
"Chef", "Waiter", "Shop Keeper", "Tailor", "Hairdresser", "Mechanic", "Welder",
"Pharmacist", "Dentist", "Veterinarian", "Pilot", "Flight Attendant", "Hotel Manager",
"Tour Guide", "Taxi Driver", "Motorcycle Rider", "Street Vendor", "Laborer", "Security Guard",
"Secretary", "Receptionist", "Data Entry Clerk", "HR Manager", "Marketing Manager",
"Sales Representative", "Consultant", "Researcher", "Professor", "Student",
}
var Companies = []string{
"Ethio Telecom", "Ethiopian Airlines", "Commercial Bank of Ethiopia", "Dashen Bank",
"Awash Bank", "United Bank", "Bank of Abyssinia", "Nib International Bank",
"Zemen Bank", "Addis Bank", "Oromia International Bank", "Bunna International Bank",
"Construction and Business Bank", "Development Bank of Ethiopia", "Ethiopian Investment Holdings",
"Ethiopian Electric Power", "Ethiopian Petroleum Supply Enterprise", "Ethiopian Mining Corporation",
"East African Holding", "Midroc Ethiopia", "Lydford Mining", "Kality Metal & Engineering",
"Al-Madani Group", "Beshale Group", "Julphar Ethiopia", "Halal Pharmaceutical",
"Africa Educational Services", "Yegna Gebeya", "Shoa Agro Industry", "Ahadu Bank",
"HiLCoFe", "Fasika Shopping Center", "Berecha Supermarket", "Kale Heywet Chemist",
"Addis continental institute", "Ripple Addis", "Afro-Dollar Group", "Summit Consulting",
}
var Nationalities = []string{"Ethiopian", "Eritrean", "Kenyan", "Ugandan", "Somali", "Sudanese", "Djibouti"}
var ProductCategories = []string{
"Electronics", "Clothing", "Food & Beverages", "Home & Garden", "Sports & Outdoors",
"Toys & Games", "Books & Stationery", "Health & Beauty", "Automotive", "Industrial",
"Agricultural", "Construction", "Medical", "Office Supplies", "Pet Supplies",
}
var ProductNames = []string{
"Injera Pot", "Coffee Roaster", "Berbere Spice Set", "Tej Honey Wine", "Coffee Table",
"Wooden Stool", "Cotton Scarf", "Silk Shawl", "Leather Sandals", "Brass Jewelry Box",
"Adder Compass", "Coffee Bean Grinder", "Ceramic Cup Set", "Woven Basket", "Palm Leaf Fan",
"Spice Mortar", "Clay Oven", "Bamboo Mat", "Recycled Glass Vase", "Metal Tribal Art",
}
var Brands = []string{
"Addis Brand", "EthioCraft", "Oromia Style", "Tigray Treasures", "Amhara Arts",
"ESM", "Zemen Essentials", "Bole Fashion", "Harar Heritage", "Gondar Gold",
"National", "Panasonic", "Samsung", "LG", "Hisense", "Nokia", "Tecno", "Infinix",
"Titan", "Casio", "Local Made", "Handicraft Co",
}
var EventTypes = []string{
"page_view", "click", "purchase", "add_to_cart", "remove_from_cart", "signup",
"login", "logout", "search", "filter", "sort", "share", "download", "upload",
"form_submit", "video_play", "video_pause", "video_complete", "scroll_depth",
"session_start", "session_end", "error", "notification", "message_sent", "message_received",
}
var DeviceTypes = []string{"Desktop", "Mobile", "Tablet", "Smart TV", "Smart Watch"}
var Browsers = []string{"Chrome", "Firefox", "Safari", "Edge", "Opera", "Samsung Internet", "UC Browser"}
var OperatingSystems = []string{"Windows", "macOS", "Linux", "Android", "iOS", "Chrome OS", "Ubuntu"}
var Countries = []string{
"Ethiopia", "Kenya", "Uganda", "Tanzania", "Rwanda", "Burundi", "South Sudan", "Sudan",
"Eritrea", "Djibouti", "Somalia", "Egypt", "Libya", "Algeria", "Morocco", "Tunisia",
}
var Referrers = []string{
"google.com", "facebook.com", "twitter.com", "instagram.com", "linkedin.com",
"youtube.com", "bing.com", "yahoo.com", "direct", "bookmark", "email_campaign",
"referral_link", "partner_site", "affiliate_network",
}

50
faker.go Normal file
View file

@ -0,0 +1,50 @@
package faker
import (
"go-faker/generator"
"go-faker/types"
)
type Faker struct {
inner *generator.Faker
}
func New(seed string) *Faker {
return &Faker{inner: generator.New(seed)}
}
func (f *Faker) GeneratePerson(fields []string) map[string]any {
return f.inner.GeneratePerson(fields)
}
func (f *Faker) GenerateAddress(fields []string) map[string]any {
return f.inner.GenerateAddress(fields)
}
func (f *Faker) GenerateProduct(fields []string) map[string]any {
return f.inner.GenerateProduct(fields)
}
func (f *Faker) GenerateAnalytics(fields []string) map[string]any {
return f.inner.GenerateAnalytics(fields)
}
func (f *Faker) GenerateBatch(count int, dataType types.DataType, fields []string) []map[string]any {
return f.inner.GenerateBatch(count, dataType, fields)
}
func (f *Faker) GeneratePersonStruct(count int, fields []string) []map[string]any {
return f.inner.GenerateBatch(count, types.TypePerson, fields)
}
func (f *Faker) GenerateAddressStruct(count int, fields []string) []map[string]any {
return f.inner.GenerateBatch(count, types.TypeAddress, fields)
}
func (f *Faker) GenerateProductStruct(count int, fields []string) []map[string]any {
return f.inner.GenerateBatch(count, types.TypeProduct, fields)
}
func (f *Faker) GenerateAnalyticsStruct(count int, fields []string) []map[string]any {
return f.inner.GenerateBatch(count, types.TypeAnalytics, fields)
}

316
generator/generator.go Normal file
View file

@ -0,0 +1,316 @@
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
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module go-faker
go 1.22.0

120
types/types.go Normal file
View file

@ -0,0 +1,120 @@
package types
import "time"
type GenerateRequest struct {
Seed string `json:"seed"`
Count int `json:"count"`
Type DataType `json:"type"`
Fields []string `json:"fields"`
}
type GenerateResponse struct {
Data []map[string]any `json:"data"`
Meta ResponseMeta `json:"meta"`
Format string `json:"format"`
}
type ResponseMeta struct {
Count int `json:"count"`
Seed string `json:"seed"`
GeneratedAt string `json:"generated_at"`
}
type DataType string
const (
TypePerson DataType = "person"
TypeAddress DataType = "address"
TypeProduct DataType = "product"
TypeAnalytics DataType = "analytics"
)
var ValidFields = map[DataType][]string{
TypePerson: {
"first_name", "last_name", "full_name",
"phone", "email", "gender", "age",
"language", "city", "region", "nationality",
"occupation", "company", "blood_type",
"date_of_birth", "username", "password",
},
TypeAddress: {
"street", "city", "region", "sub_city",
"postal_code", "zone", "woreda", "kebele",
"house_number", "latitude", "longitude",
},
TypeProduct: {
"name", "category", "price", "sku",
"brand", "description", "weight", "stock",
"expiry_date", "manufactured_date",
},
TypeAnalytics: {
"user_id", "event", "timestamp", "amount",
"currency", "session_id", "page_url",
"ip_address", "device_type", "browser",
"os", "country", "referrer",
},
}
type Person struct {
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
FullName string `json:"full_name,omitempty"`
Phone string `json:"phone,omitempty"`
Email string `json:"email,omitempty"`
Gender string `json:"gender,omitempty"`
Age int `json:"age,omitempty"`
Language string `json:"language,omitempty"`
City string `json:"city,omitempty"`
Region string `json:"region,omitempty"`
Nationality string `json:"nationality,omitempty"`
Occupation string `json:"occupation,omitempty"`
Company string `json:"company,omitempty"`
BloodType string `json:"blood_type,omitempty"`
DateOfBirth string `json:"date_of_birth,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
type Address struct {
Street string `json:"street,omitempty"`
City string `json:"city,omitempty"`
Region string `json:"region,omitempty"`
SubCity string `json:"sub_city,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
Zone string `json:"zone,omitempty"`
Woreda string `json:"woreda,omitempty"`
Kebele string `json:"kebele,omitempty"`
HouseNumber string `json:"house_number,omitempty"`
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
}
type Product struct {
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
Price float64 `json:"price,omitempty"`
SKU string `json:"sku,omitempty"`
Brand string `json:"brand,omitempty"`
Description string `json:"description,omitempty"`
Weight float64 `json:"weight,omitempty"`
Stock int `json:"stock,omitempty"`
ExpiryDate string `json:"expiry_date,omitempty"`
ManufacturedDate string `json:"manufactured_date,omitempty"`
}
type Analytics struct {
UserID string `json:"user_id,omitempty"`
Event string `json:"event,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
Amount float64 `json:"amount,omitempty"`
Currency string `json:"currency,omitempty"`
SessionID string `json:"session_id,omitempty"`
PageURL string `json:"page_url,omitempty"`
IPAddress string `json:"ip_address,omitempty"`
DeviceType string `json:"device_type,omitempty"`
Browser string `json:"browser,omitempty"`
OS string `json:"os,omitempty"`
Country string `json:"country,omitempty"`
Referrer string `json:"referrer,omitempty"`
}