diff --git a/README.md b/README.md index eb9e75f..0bf7ba1 100644 --- a/README.md +++ b/README.md @@ -164,10 +164,19 @@ Swedish personnummer): `address`, `color`, `company`, `date`, `email`, `ip`, `person`, `phone`, `price`, `sentence`, `ssn`, `time`, `url`, `username`, `version`, `word`. -`data/misc` carries locale-neutral categories: `uuid` (a proper random v4), -`mac`, and `creditcard` (per-network numbers ending in a valid `{luhn()}` digit). -A time-ordered v7 UUID can't be expressed as data, so it's the `{uuid()}` builtin -instead (see **Functions**). +`data/misc` carries locale-neutral categories — universal data that isn't tied to +a language or region: + +- ids & networking: `uuid` (a proper random v4), `mac`, `creditcard` (per-network + numbers ending in a valid `{luhn()}` digit) +- reference codes: `currency` (ISO 4217), `country` (ISO 3166), `language` (ISO + 639), `timezone` (IANA) +- web & systems: `mimetype`, `httpstatus`, `useragent` +- misc: `coordinate` (a lat/long point), `emoji`, `car` + +Many carry dotted sub-fields — `currency.symbol`, `country.alpha2`, +`mimetype.ext`, `httpstatus.code`, `car.maker`. A time-ordered v7 UUID can't be +expressed as data, so it's the `{uuid()}` builtin instead (see **Functions**). ## Data format diff --git a/data/misc/car.json b/data/misc/car.json new file mode 100644 index 0000000..208e638 --- /dev/null +++ b/data/misc/car.json @@ -0,0 +1,10 @@ +[ + { "format": "{maker} {model}", "maker": ["BMW"], "model": ["3 Series", "5 Series", "X3", "X5"] }, + { "format": "{maker} {model}", "maker": ["Ford"], "model": ["Fiesta", "Focus", "Mustang", "Explorer"] }, + { "format": "{maker} {model}", "maker": ["Honda"], "model": ["Civic", "Accord", "CR-V", "Jazz"] }, + { "format": "{maker} {model}", "maker": ["Mercedes-Benz"], "model": ["A-Class", "C-Class", "E-Class", "GLC"] }, + { "format": "{maker} {model}", "maker": ["Tesla"], "model": ["Model 3", "Model S", "Model X", "Model Y"] }, + { "format": "{maker} {model}", "maker": ["Toyota"], "model": ["Corolla", "Camry", "RAV4", "Yaris"] }, + { "format": "{maker} {model}", "maker": ["Volkswagen"], "model": ["Golf", "Passat", "Polo", "Tiguan"] }, + { "format": "{maker} {model}", "maker": ["Volvo"], "model": ["XC40", "XC60", "XC90", "V60"] } +] diff --git a/data/misc/coordinate.json b/data/misc/coordinate.json new file mode 100644 index 0000000..b00f222 --- /dev/null +++ b/data/misc/coordinate.json @@ -0,0 +1,7 @@ +[ + { + "format": "{lat}, {lon}", + "lat": { "format": "{float(-90,90,6)}" }, + "lon": { "format": "{float(-180,180,6)}" } + } +] diff --git a/data/misc/country.json b/data/misc/country.json new file mode 100644 index 0000000..cf6ae99 --- /dev/null +++ b/data/misc/country.json @@ -0,0 +1,22 @@ +[ + { "format": "{name}", "name": ["Australia"], "alpha2": ["AU"], "alpha3": ["AUS"] }, + { "format": "{name}", "name": ["Brazil"], "alpha2": ["BR"], "alpha3": ["BRA"] }, + { "format": "{name}", "name": ["Canada"], "alpha2": ["CA"], "alpha3": ["CAN"] }, + { "format": "{name}", "name": ["China"], "alpha2": ["CN"], "alpha3": ["CHN"] }, + { "format": "{name}", "name": ["Denmark"], "alpha2": ["DK"], "alpha3": ["DNK"] }, + { "format": "{name}", "name": ["Finland"], "alpha2": ["FI"], "alpha3": ["FIN"] }, + { "format": "{name}", "name": ["France"], "alpha2": ["FR"], "alpha3": ["FRA"] }, + { "format": "{name}", "name": ["Germany"], "alpha2": ["DE"], "alpha3": ["DEU"] }, + { "format": "{name}", "name": ["India"], "alpha2": ["IN"], "alpha3": ["IND"] }, + { "format": "{name}", "name": ["Italy"], "alpha2": ["IT"], "alpha3": ["ITA"] }, + { "format": "{name}", "name": ["Japan"], "alpha2": ["JP"], "alpha3": ["JPN"] }, + { "format": "{name}", "name": ["Mexico"], "alpha2": ["MX"], "alpha3": ["MEX"] }, + { "format": "{name}", "name": ["Netherlands"], "alpha2": ["NL"], "alpha3": ["NLD"] }, + { "format": "{name}", "name": ["Norway"], "alpha2": ["NO"], "alpha3": ["NOR"] }, + { "format": "{name}", "name": ["Poland"], "alpha2": ["PL"], "alpha3": ["POL"] }, + { "format": "{name}", "name": ["Spain"], "alpha2": ["ES"], "alpha3": ["ESP"] }, + { "format": "{name}", "name": ["Sweden"], "alpha2": ["SE"], "alpha3": ["SWE"] }, + { "format": "{name}", "name": ["Switzerland"], "alpha2": ["CH"], "alpha3": ["CHE"] }, + { "format": "{name}", "name": ["United Kingdom"], "alpha2": ["GB"], "alpha3": ["GBR"] }, + { "format": "{name}", "name": ["United States"], "alpha2": ["US"], "alpha3": ["USA"] } +] diff --git a/data/misc/currency.json b/data/misc/currency.json new file mode 100644 index 0000000..7e79b8a --- /dev/null +++ b/data/misc/currency.json @@ -0,0 +1,18 @@ +[ + { "format": "{code}", "code": ["AUD"], "name": ["Australian Dollar"], "symbol": ["$"] }, + { "format": "{code}", "code": ["BRL"], "name": ["Brazilian Real"], "symbol": ["R$"] }, + { "format": "{code}", "code": ["CAD"], "name": ["Canadian Dollar"], "symbol": ["$"] }, + { "format": "{code}", "code": ["CHF"], "name": ["Swiss Franc"], "symbol": ["CHF"] }, + { "format": "{code}", "code": ["CNY"], "name": ["Chinese Yuan"], "symbol": ["¥"] }, + { "format": "{code}", "code": ["DKK"], "name": ["Danish Krone"], "symbol": ["kr"] }, + { "format": "{code}", "code": ["EUR"], "name": ["Euro"], "symbol": ["€"] }, + { "format": "{code}", "code": ["GBP"], "name": ["Pound Sterling"], "symbol": ["£"] }, + { "format": "{code}", "code": ["INR"], "name": ["Indian Rupee"], "symbol": ["₹"] }, + { "format": "{code}", "code": ["JPY"], "name": ["Japanese Yen"], "symbol": ["¥"] }, + { "format": "{code}", "code": ["MXN"], "name": ["Mexican Peso"], "symbol": ["$"] }, + { "format": "{code}", "code": ["NOK"], "name": ["Norwegian Krone"], "symbol": ["kr"] }, + { "format": "{code}", "code": ["PLN"], "name": ["Polish Zloty"], "symbol": ["zł"] }, + { "format": "{code}", "code": ["SEK"], "name": ["Swedish Krona"], "symbol": ["kr"] }, + { "format": "{code}", "code": ["USD"], "name": ["US Dollar"], "symbol": ["$"] }, + { "format": "{code}", "code": ["ZAR"], "name": ["South African Rand"], "symbol": ["R"] } +] diff --git a/data/misc/emoji.json b/data/misc/emoji.json new file mode 100644 index 0000000..193f29a --- /dev/null +++ b/data/misc/emoji.json @@ -0,0 +1,6 @@ +[ + "😀", "😂", "😍", "🤔", "😎", "😭", "😡", "👍", "👎", "🙏", + "👏", "🙌", "💪", "🔥", "✨", "🎉", "❤️", "💔", "💯", "👀", + "🚀", "⭐", "🌈", "☀️", "🌙", "⚡", "❄️", "🍕", "🍔", "🍺", + "☕", "🎂", "🐶", "🐱", "🦊", "🐢", "🦄", "🌸", "🌍", "💡" +] diff --git a/data/misc/httpstatus.json b/data/misc/httpstatus.json new file mode 100644 index 0000000..b27e5ad --- /dev/null +++ b/data/misc/httpstatus.json @@ -0,0 +1,18 @@ +[ + { "format": "{code} {reason}", "code": ["200"], "reason": ["OK"] }, + { "format": "{code} {reason}", "code": ["201"], "reason": ["Created"] }, + { "format": "{code} {reason}", "code": ["204"], "reason": ["No Content"] }, + { "format": "{code} {reason}", "code": ["301"], "reason": ["Moved Permanently"] }, + { "format": "{code} {reason}", "code": ["302"], "reason": ["Found"] }, + { "format": "{code} {reason}", "code": ["304"], "reason": ["Not Modified"] }, + { "format": "{code} {reason}", "code": ["400"], "reason": ["Bad Request"] }, + { "format": "{code} {reason}", "code": ["401"], "reason": ["Unauthorized"] }, + { "format": "{code} {reason}", "code": ["403"], "reason": ["Forbidden"] }, + { "format": "{code} {reason}", "code": ["404"], "reason": ["Not Found"] }, + { "format": "{code} {reason}", "code": ["409"], "reason": ["Conflict"] }, + { "format": "{code} {reason}", "code": ["422"], "reason": ["Unprocessable Entity"] }, + { "format": "{code} {reason}", "code": ["429"], "reason": ["Too Many Requests"] }, + { "format": "{code} {reason}", "code": ["500"], "reason": ["Internal Server Error"] }, + { "format": "{code} {reason}", "code": ["502"], "reason": ["Bad Gateway"] }, + { "format": "{code} {reason}", "code": ["503"], "reason": ["Service Unavailable"] } +] diff --git a/data/misc/language.json b/data/misc/language.json new file mode 100644 index 0000000..566825b --- /dev/null +++ b/data/misc/language.json @@ -0,0 +1,18 @@ +[ + { "format": "{name}", "name": ["Arabic"], "code": ["ar"] }, + { "format": "{name}", "name": ["Chinese"], "code": ["zh"] }, + { "format": "{name}", "name": ["Danish"], "code": ["da"] }, + { "format": "{name}", "name": ["Dutch"], "code": ["nl"] }, + { "format": "{name}", "name": ["English"], "code": ["en"] }, + { "format": "{name}", "name": ["Finnish"], "code": ["fi"] }, + { "format": "{name}", "name": ["French"], "code": ["fr"] }, + { "format": "{name}", "name": ["German"], "code": ["de"] }, + { "format": "{name}", "name": ["Italian"], "code": ["it"] }, + { "format": "{name}", "name": ["Japanese"], "code": ["ja"] }, + { "format": "{name}", "name": ["Norwegian"], "code": ["no"] }, + { "format": "{name}", "name": ["Polish"], "code": ["pl"] }, + { "format": "{name}", "name": ["Portuguese"], "code": ["pt"] }, + { "format": "{name}", "name": ["Russian"], "code": ["ru"] }, + { "format": "{name}", "name": ["Spanish"], "code": ["es"] }, + { "format": "{name}", "name": ["Swedish"], "code": ["sv"] } +] diff --git a/data/misc/mimetype.json b/data/misc/mimetype.json new file mode 100644 index 0000000..e7ffda2 --- /dev/null +++ b/data/misc/mimetype.json @@ -0,0 +1,17 @@ +[ + { "format": "{type}", "type": ["application/gzip"], "ext": [".gz"] }, + { "format": "{type}", "type": ["application/json"], "ext": [".json"] }, + { "format": "{type}", "type": ["application/pdf"], "ext": [".pdf"] }, + { "format": "{type}", "type": ["application/xml"], "ext": [".xml"] }, + { "format": "{type}", "type": ["application/zip"], "ext": [".zip"] }, + { "format": "{type}", "type": ["audio/mpeg"], "ext": [".mp3"] }, + { "format": "{type}", "type": ["image/gif"], "ext": [".gif"] }, + { "format": "{type}", "type": ["image/jpeg"], "ext": [".jpg"] }, + { "format": "{type}", "type": ["image/png"], "ext": [".png"] }, + { "format": "{type}", "type": ["image/svg+xml"], "ext": [".svg"] }, + { "format": "{type}", "type": ["image/webp"], "ext": [".webp"] }, + { "format": "{type}", "type": ["text/csv"], "ext": [".csv"] }, + { "format": "{type}", "type": ["text/html"], "ext": [".html"] }, + { "format": "{type}", "type": ["text/plain"], "ext": [".txt"] }, + { "format": "{type}", "type": ["video/mp4"], "ext": [".mp4"] } +] diff --git a/data/misc/timezone.json b/data/misc/timezone.json new file mode 100644 index 0000000..62d6cb5 --- /dev/null +++ b/data/misc/timezone.json @@ -0,0 +1,29 @@ +[ + "UTC", + "Africa/Cairo", + "Africa/Johannesburg", + "America/Chicago", + "America/Denver", + "America/Los_Angeles", + "America/Mexico_City", + "America/New_York", + "America/Sao_Paulo", + "America/Toronto", + "Asia/Dubai", + "Asia/Hong_Kong", + "Asia/Kolkata", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Tokyo", + "Australia/Sydney", + "Europe/Amsterdam", + "Europe/Berlin", + "Europe/London", + "Europe/Madrid", + "Europe/Moscow", + "Europe/Oslo", + "Europe/Paris", + "Europe/Stockholm", + "Europe/Warsaw", + "Pacific/Auckland" +] diff --git a/data/misc/useragent.json b/data/misc/useragent.json new file mode 100644 index 0000000..f42bf9f --- /dev/null +++ b/data/misc/useragent.json @@ -0,0 +1,10 @@ +[ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0" +] diff --git a/data_test.go b/data_test.go index 53a5ba9..9c0e09d 100644 --- a/data_test.go +++ b/data_test.go @@ -2,6 +2,7 @@ package fakes import ( "regexp" + "strconv" "strings" "testing" "time" @@ -84,6 +85,48 @@ func TestShippedMiscCategories(t *testing.T) { } } +// TestShippedMiscReferenceData covers the locale-neutral reference categories: +// codes follow their standard shape, dotted sub-paths resolve, emoji is a real +// glyph, and a coordinate parses to a point within geographic bounds. +func TestShippedMiscReferenceData(t *testing.T) { + f := newFakes(t, "data/misc", WithSeed(1)) + re := map[string]*regexp.Regexp{ + "currency": regexp.MustCompile(`^[A-Z]{3}$`), + "currency.name": regexp.MustCompile(`\p{L}`), + "currency.symbol": regexp.MustCompile(`^\S+$`), + "country": regexp.MustCompile(`\p{L}`), + "country.alpha2": regexp.MustCompile(`^[A-Z]{2}$`), + "country.alpha3": regexp.MustCompile(`^[A-Z]{3}$`), + "language": regexp.MustCompile(`\p{L}`), + "language.code": regexp.MustCompile(`^[a-z]{2}$`), + "timezone": regexp.MustCompile(`^([A-Za-z_]+(/[A-Za-z_]+)+|UTC)$`), + "mimetype": regexp.MustCompile(`^[a-z]+/[a-z0-9.+-]+$`), + "mimetype.ext": regexp.MustCompile(`^\.[a-z0-9]+$`), + "httpstatus": regexp.MustCompile(`^[1-5]\d{2} \S.*$`), + "httpstatus.code": regexp.MustCompile(`^[1-5]\d{2}$`), + "useragent": regexp.MustCompile(`^Mozilla/5\.0 .+`), + "car": regexp.MustCompile(`^\S.* \S`), + "car.maker": regexp.MustCompile(`^\S`), + } + for i := 0; i < 100; i++ { + for p, rx := range re { + if v := fake(t, f, p); !rx.MatchString(v) { + t.Fatalf("misc %s = %q, want %s", p, v, rx) + } + } + if e := fake(t, f, "emoji"); e == "" || []rune(e)[0] < 128 { + t.Fatalf("emoji %q is not a non-ASCII glyph", e) + } + c := fake(t, f, "coordinate") + parts := strings.SplitN(c, ", ", 2) + lat, err1 := strconv.ParseFloat(parts[0], 64) + lon, err2 := strconv.ParseFloat(parts[len(parts)-1], 64) + if len(parts) != 2 || err1 != nil || err2 != nil || lat < -90 || lat > 90 || lon < -180 || lon > 180 { + t.Fatalf("coordinate %q is not a 'lat, lon' point within bounds", c) + } + } +} + // TestSwedishPersonnummer checks the two rules the shape regex can't: the date // is a real calendar date (so month-length variants never emit e.g. Apr 31 or // Feb 30) and the trailing digit is a valid Luhn checksum over the other nine.