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:
commit
4ff98a40d4
10 changed files with 1081 additions and 0 deletions
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal 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
175
README.md
Normal 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
100
api/handlers.go
Normal 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
43
api/server.go
Normal 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
81
cmd/gofaker/main.go
Normal 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
163
data/ethiopian.go
Normal 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
50
faker.go
Normal 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
316
generator/generator.go
Normal 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
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module go-faker
|
||||||
|
|
||||||
|
go 1.22.0
|
||||||
120
types/types.go
Normal file
120
types/types.go
Normal 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"`
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue