Mock Data Generator — Go source
Generate deterministic fake data (names, emails, numbers, dates, booleans, UUIDs, pick-from-list) from a schema and a seed. Reproducible output.
This is the Go implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Package mockdata is the Go twin of CosmoDev's src/lib/mockData.ts (dual
// source: the web lib is TypeScript, the CLI lib is Go — kept in lock-step).
// Pure + deterministic, never panics. The table-driven tests in
// mock-data-generator_test.go share vectors with src/lib/mockData.test.ts so
// the two implementations are held to the same contract.
//
// Generation mirrors the TS lib exactly: a seeded mulberry32 PRNG drives every
// field, consumed in schema order, so identical (schema, count, seed) produces
// identical output. No math/rand, no time.Now. Invalid options fall back to
// defaults/zero-values mirroring the TS lib rather than panicking.
package mockdata
import (
"fmt"
"math"
"strings"
"time"
)
// FieldType enumerates the kinds of value a field can generate. It is the Go
// equivalent of the TS FieldType union; the underlying strings must match.
type FieldType string
const (
FieldTypeFirstName FieldType = "firstName"
FieldTypeLastName FieldType = "lastName"
FieldTypeFullName FieldType = "fullName"
FieldTypeEmail FieldType = "email"
FieldTypeUsername FieldType = "username"
FieldTypeInteger FieldType = "integer"
FieldTypeNumber FieldType = "number"
FieldTypeBoolean FieldType = "boolean"
FieldTypeUUID FieldType = "uuid"
FieldTypeDate FieldType = "date"
FieldTypePick FieldType = "pick"
FieldTypeString FieldType = "string"
FieldTypeIndex FieldType = "index"
)
// FieldSpec describes one column of generated data. Optional fields use
// pointers/empties so an omitted value is distinguishable from a specified
// zero, mirroring the TS lib's ?? / || / isFinite defaults:
// - Min/Max: nil → per-type default (integer 0/100, number 0/1, username 1/99)
// - From/To: empty string → default date range ("2000-01-01"/"2025-12-31")
// - Options: nil/empty → pick returns null
// - Length: nil → default 8
type FieldSpec struct {
Name string
Type FieldType
Min *float64
Max *float64
From string
To string
Options []string
Length *int
}
// firstNames / lastNames mirror the FIRST_NAMES / LAST_NAMES pools in the TS lib.
var (
firstNames = []string{
"Ava", "Liam", "Noah", "Emma", "Olivia", "Aiden", "Sophia", "Mason", "Isabella",
"Lucas", "Mia", "Ethan", "Amelia", "Leo", "Harper", "Ezra", "Ella", "Owen",
"Luna", "Finn", "Zoe", "Jude", "Nora", "Kai", "Ruby", "Theo", "Ivy", "Max",
}
lastNames = []string{
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
"Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson",
"Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Perez", "Thompson",
"White", "Harris", "Sanchez", "Clark", "Ramirez",
}
)
const lowercaseLetters = "abcdefghijklmnopqrstuvwxyz"
// Mulberry32 returns a seeded PRNG yielding floats in [0, 1). It is the Go
// twin of mulberry32() in src/lib/mockData.ts: the arithmetic is carried out
// in uint32 so every bit matches the JS >>> 0 / Math.imul reference.
func Mulberry32(seed int) func() float64 {
a := uint32(seed) // == seed >>> 0
return func() float64 {
a += 0x6d2b79f5 // == (a + 0x6d2b79f5) | 0
t := (a ^ (a >> 15)) * (1 | a) // == Math.imul(a ^ (a>>>15), 1|a)
t = (t + (t^(t>>7))*(61|t)) ^ t // == (t + Math.imul(t ^ (t>>>7), 61|t)) ^ t
return float64(t^(t>>14)) / 4294967296.0
}
}
// isFinite mirrors JS Number.isFinite: false for NaN and ±Inf.
func isFinite(x float64) bool { return !math.IsNaN(x) && !math.IsInf(x, 0) }
// clampInt mirrors clampInt() in the TS lib: floor finite bounds (defaulting
// non-finite min→0, max→100) and return them ordered low/high so an inverted
// range is silently swapped.
func clampInt(minVal, maxVal float64) (lo, hi int) {
lo = 0
if isFinite(minVal) {
lo = int(math.Floor(minVal))
}
hi = 100
if isFinite(maxVal) {
hi = int(math.Floor(maxVal))
}
if lo > hi {
lo, hi = hi, lo
}
return lo, hi
}
// uuidFromRng generates a v4-style UUID from a PRNG, matching uuidFromRng():
// 32 hex digits then one extra draw for the variant nibble, version pinned.
func uuidFromRng(rng func() float64) string {
const hexDigits = "0123456789abcdef"
out := make([]byte, 32)
for i := 0; i < 32; i++ {
out[i] = hexDigits[int(rng()*16)]
}
out[12] = '4'
out[16] = "89ab"[int(rng()*4)]
return fmt.Sprintf("%s-%s-%s-%s-%s", out[0:8], out[8:12], out[12:16], out[16:20], out[20:32])
}
// sanitizeFieldName strips every rune that is not [A-Za-z0-9_$], falling back
// to "field<index>" when nothing remains — mirroring sanitizeFieldName().
func sanitizeFieldName(name string, idx int) string {
var b strings.Builder
for _, r := range name {
switch {
case r >= 'A' && r <= 'Z',
r >= 'a' && r <= 'z',
r >= '0' && r <= '9',
r == '_', r == '$':
b.WriteRune(r)
}
}
if b.Len() == 0 {
return fmt.Sprintf("field%d", idx)
}
return b.String()
}
// parseDateMS parses an ISO date ("YYYY-MM-DD") to UTC milliseconds, falling
// back to the given default string on failure so the generator never panics.
// time.Parse of a date-only layout yields UTC midnight, matching Date.parse.
func parseDateMS(s, fallback string) int64 {
t, err := time.Parse("2006-01-02", s)
if err != nil {
t, err = time.Parse("2006-01-02", fallback)
if err != nil {
return 0
}
}
return t.UnixMilli()
}
// GenerateMock produces count rows of deterministic mock data from a schema.
// It is the Go twin of generateMock() in src/lib/mockData.ts and must agree
// with it on every shared test vector.
func GenerateMock(schema []FieldSpec, count, seed int) []map[string]interface{} {
rng := Mulberry32(seed)
n := count
if n < 0 {
n = 0
}
rows := make([]map[string]interface{}, 0, n)
for i := 0; i < n; i++ {
row := map[string]interface{}{}
for f, spec := range schema {
key := sanitizeFieldName(spec.Name, f)
row[key] = generateField(spec, i, rng)
}
rows = append(rows, row)
}
return rows
}
// generateField produces one value for a field, mirroring generateField(). The
// default case returns nil so an unknown type yields JSON null, never a panic.
func generateField(spec FieldSpec, index int, rng func() float64) interface{} {
switch spec.Type {
case FieldTypeIndex:
return index
case FieldTypeFirstName:
return firstNames[int(rng()*float64(len(firstNames)))]
case FieldTypeLastName:
return lastNames[int(rng()*float64(len(lastNames)))]
case FieldTypeFullName:
first := firstNames[int(rng()*float64(len(firstNames)))]
last := lastNames[int(rng()*float64(len(lastNames)))]
return first + " " + last
case FieldTypeUsername:
first := strings.ToLower(firstNames[int(rng()*float64(len(firstNames)))])
lo, hi := usernameBounds(spec)
return first + fmt.Sprintf("%d", lo+int(rng()*float64(hi-lo+1)))
case FieldTypeEmail:
first := strings.ToLower(firstNames[int(rng()*float64(len(firstNames)))])
last := strings.ToLower(lastNames[int(rng()*float64(len(lastNames)))])
return first + "." + last + "@example.com"
case FieldTypeInteger:
lo, hi := integerBounds(spec)
return lo + int(rng()*float64(hi-lo+1))
case FieldTypeNumber:
lo, hi := numberBounds(spec)
if lo > hi {
lo, hi = hi, lo
}
return math.Round((lo+rng()*(hi-lo))*10000) / 10000
case FieldTypeBoolean:
return rng() < 0.5
case FieldTypeUUID:
return uuidFromRng(rng)
case FieldTypeDate:
from, to := spec.From, spec.To
if from == "" {
from = "2000-01-01"
}
if to == "" {
to = "2025-12-31"
}
lo := parseDateMS(from, "2000-01-01")
hi := parseDateMS(to, "2025-12-31")
if lo > hi {
lo, hi = hi, lo
}
ms := lo + int64(rng()*float64(hi-lo))
return time.UnixMilli(ms).UTC().Format("2006-01-02")
case FieldTypePick:
if len(spec.Options) == 0 {
return nil
}
return spec.Options[int(rng()*float64(len(spec.Options)))]
case FieldTypeString:
length := 8
if spec.Length != nil {
length = *spec.Length
}
if length < 1 {
length = 1
}
var b strings.Builder
for i := 0; i < length; i++ {
b.WriteByte(lowercaseLetters[int(rng()*float64(len(lowercaseLetters)))])
}
return b.String()
default:
return nil
}
}
// usernameBounds resolves spec.Min/Max with the TS defaults (1/99), then clamps
// via clampInt so non-finite values fall back to 0/100 — matching the TS lib's
// ?? (which does not catch NaN) feeding into clampInt (which does).
func usernameBounds(spec FieldSpec) (int, int) {
minVal, maxVal := 1.0, 99.0
if spec.Min != nil {
minVal = *spec.Min
}
if spec.Max != nil {
maxVal = *spec.Max
}
return clampInt(minVal, maxVal)
}
// integerBounds resolves spec.Min/Max with the TS defaults (0/100), then clamps.
func integerBounds(spec FieldSpec) (int, int) {
minVal, maxVal := 0.0, 100.0
if spec.Min != nil {
minVal = *spec.Min
}
if spec.Max != nil {
maxVal = *spec.Max
}
return clampInt(minVal, maxVal)
}
// numberBounds resolves spec.Min/Max with the TS defaults (0/1), keeping them
// as floats and treating non-finite values as absent — mirroring the number
// branch's Number.isFinite gate.
func numberBounds(spec FieldSpec) (float64, float64) {
minVal, maxVal := 0.0, 1.0
if spec.Min != nil && isFinite(*spec.Min) {
minVal = *spec.Min
}
if spec.Max != nil && isFinite(*spec.Max) {
maxVal = *spec.Max
}
return minVal, maxVal
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →