Documentation
¶
Overview ¶
Package goppelganger is a Schema-First Faker: it reads a JSON Schema and generates random data that satisfies every constraint in the contract.
Example (Errors) ¶
Every error wraps a sentinel re-exported by this package; match it with errors.Is instead of comparing messages.
package main
import (
"errors"
"fmt"
"github.com/ReBase-Dev-Studio/goppelganger"
)
var userSchema = []byte(`{
"type": "object",
"required": ["id", "email", "role", "tags"],
"properties": {
"id": {"type": "string", "format": "uuid"},
"email": {"type": "string", "format": "email"},
"role": {"enum": ["admin", "editor", "viewer"]},
"age": {"type": ["integer", "null"], "minimum": 18, "maximum": 99},
"tags": {"type": "array", "minItems": 1, "maxItems": 3, "items": {"type": "string", "pattern": "^[a-z]{3,8}$"}}
}
}`)
func main() {
_, err := goppelganger.Generate([]byte(`{"type": "integer", "minimum": 10, "maximum": 1}`))
fmt.Println(errors.Is(err, goppelganger.ErrInvalidConstraint))
_, err = goppelganger.Generate([]byte(`{"type": "string"}`))
fmt.Println(errors.Is(err, goppelganger.ErrRootMustBeObject))
_, err = goppelganger.New(goppelganger.Options{Locale: "xx_XX"}).Generate(userSchema)
fmt.Println(errors.Is(err, goppelganger.ErrUnsupportedLocale))
}
Output: true true true
Index ¶
Examples ¶
Constants ¶
const DefaultLocale = "en_US"
DefaultLocale is used when Options.Locale is empty.
Variables ¶
var ( ErrUnknownType = parser.ErrUnknownType ErrMissingType = parser.ErrMissingType ErrInvalidConstraint = parser.ErrInvalidConstraint ErrInvalidPattern = parser.ErrInvalidPattern ErrInvalidRef = parser.ErrInvalidRef ErrSizeLimitExceeded = parser.ErrSizeLimitExceeded ErrUnsatisfiable = generator.ErrUnsatisfiable ErrUnsupportedLocale = generator.ErrUnsupportedLocale )
Re-exported sentinel errors so callers only need to import this package.
var ErrRootMustBeObject = errors.New("goppelganger: schema root must be of type object")
ErrRootMustBeObject is returned by Generate when the schema root is not of type "object". Use GenerateValue for non-object roots.
Functions ¶
func Generate ¶
Generate uses a shared, randomly seeded Faker.
Example ¶
The package-level functions use a randomly seeded Faker, so only the constrained parts of the output are predictable.
package main
import (
"fmt"
"log"
"github.com/ReBase-Dev-Studio/goppelganger"
)
func main() {
order, err := goppelganger.Generate([]byte(`{
"type": "object",
"required": ["id", "status"],
"properties": {
"id": {"type": "string", "format": "uuid"},
"status": {"const": "paid"}
}
}`))
if err != nil {
log.Fatal(err)
}
fmt.Println(order["status"], len(order["id"].(string)))
}
Output: paid 36
func GenerateSchema ¶
GenerateSchema uses a shared, randomly seeded Faker.
func GenerateSchemaValue ¶
GenerateSchemaValue uses a shared, randomly seeded Faker.
func GenerateValue ¶
GenerateValue uses a shared, randomly seeded Faker.
Types ¶
type Faker ¶
type Faker struct {
// contains filtered or unexported fields
}
Faker generates schema-compliant data. It is safe for concurrent use.
func New ¶
New returns a Faker configured with opts. An unsupported locale does not fail here; it is reported as ErrUnsupportedLocale by every Generate call.
Example ¶
A seeded Faker returns the same sequence of values for the same sequence of calls, which makes generated fixtures reproducible in tests.
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/ReBase-Dev-Studio/goppelganger"
)
var userSchema = []byte(`{
"type": "object",
"required": ["id", "email", "role", "tags"],
"properties": {
"id": {"type": "string", "format": "uuid"},
"email": {"type": "string", "format": "email"},
"role": {"enum": ["admin", "editor", "viewer"]},
"age": {"type": ["integer", "null"], "minimum": 18, "maximum": 99},
"tags": {"type": "array", "minItems": 1, "maxItems": 3, "items": {"type": "string", "pattern": "^[a-z]{3,8}$"}}
}
}`)
func seededFaker(seed int64) *goppelganger.Faker {
return goppelganger.New(goppelganger.Options{Seed: &seed})
}
func printJSON(value any) {
encoded, err := json.MarshalIndent(value, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(encoded))
}
func main() {
user, err := seededFaker(42).Generate(userSchema)
if err != nil {
log.Fatal(err)
}
printJSON(user)
}
Output: { "email": "winter.banner616@mountain.example.org", "id": "c1be0cb7-e57d-4160-b718-3a6e539fecae", "role": "admin", "tags": [ "hfj", "dztbzyna" ] }
func (*Faker) Generate ¶
Generate parses schema and returns a random object that satisfies it. It is Compile followed by GenerateSchema; compile once to reuse a schema.
func (*Faker) GenerateSchema ¶
GenerateSchema returns a random object that satisfies schema. The root must be of type object (directly or through $ref); a nullable root yields a nil map when null is drawn.
Example ¶
Recursive schemas expand once per branch; deeper levels are cut off (an optional property is omitted, an array becomes empty).
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/ReBase-Dev-Studio/goppelganger"
)
func seededFaker(seed int64) *goppelganger.Faker {
return goppelganger.New(goppelganger.Options{Seed: &seed})
}
func printJSON(value any) {
encoded, err := json.MarshalIndent(value, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(encoded))
}
func main() {
category := goppelganger.MustCompile([]byte(`{
"$ref": "#/$defs/category",
"$defs": {
"category": {
"type": "object",
"required": ["name", "children"],
"properties": {
"name": {"type": "string", "pattern": "^[A-Z][a-z]{3,6}$"},
"children": {"type": "array", "maxItems": 2, "items": {"$ref": "#/$defs/category"}}
}
}
}
}`))
tree, err := seededFaker(5).GenerateSchema(category)
if err != nil {
log.Fatal(err)
}
printJSON(tree)
}
Output: { "children": [ { "children": [], "name": "Djlw" }, { "children": [], "name": "Lsvfj" } ], "name": "Popt" }
func (*Faker) GenerateSchemaValue ¶
GenerateSchemaValue returns a random value of any JSON type that satisfies schema.
func (*Faker) GenerateValue ¶
GenerateValue parses schema and returns a random value of any JSON type. It is Compile followed by GenerateSchemaValue.
Example ¶
GenerateValue accepts any root type, not only objects.
package main
import (
"fmt"
"log"
"github.com/ReBase-Dev-Studio/goppelganger"
)
func seededFaker(seed int64) *goppelganger.Faker {
return goppelganger.New(goppelganger.Options{Seed: &seed})
}
func main() {
addresses, err := seededFaker(7).GenerateValue([]byte(`{
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"type": "string", "format": "ipv4"}
}`))
if err != nil {
log.Fatal(err)
}
fmt.Println(addresses)
}
Output: [146.181.107.237 184.47.140.171]
type Options ¶
type Options struct {
// Seed makes generation deterministic. A nil Seed draws a random seed
// from crypto/rand; any non-nil value (including 0) is used literally.
Seed *int64
// Locale selects the word lists used by semantic generators.
// Empty means DefaultLocale.
Locale string
}
Options configures a Faker instance.
type Schema ¶
type Schema struct {
// contains filtered or unexported fields
}
Schema is a parsed JSON Schema, ready to generate from many times. It is immutable and safe for concurrent use by any number of Fakers. Build it with Compile or MustCompile; a nil *Schema panics.
func Compile ¶
Compile parses schema once. Any root type is accepted: GenerateSchema reports ErrRootMustBeObject, GenerateSchemaValue takes every root.
Example ¶
Compile parses a schema once; the result is immutable and can be shared by any number of Fakers and goroutines.
package main
import (
"fmt"
"log"
"github.com/ReBase-Dev-Studio/goppelganger"
)
func seededFaker(seed int64) *goppelganger.Faker {
return goppelganger.New(goppelganger.Options{Seed: &seed})
}
func main() {
counter, err := goppelganger.Compile([]byte(`{"type": "integer", "minimum": 1, "maximum": 6}`))
if err != nil {
log.Fatal(err)
}
faker := seededFaker(3)
rolls := make([]any, 0, 5)
for range 5 {
roll, err := faker.GenerateSchemaValue(counter)
if err != nil {
log.Fatal(err)
}
rolls = append(rolls, roll)
}
fmt.Println(rolls...)
}
Output: 1 1 4 6 2
func MustCompile ¶
MustCompile is like Compile but panics with the wrapped error, for schemas known to be valid such as package-level variables.
Example ¶
package main
import (
"fmt"
"log"
"github.com/ReBase-Dev-Studio/goppelganger"
)
var userSchema = []byte(`{
"type": "object",
"required": ["id", "email", "role", "tags"],
"properties": {
"id": {"type": "string", "format": "uuid"},
"email": {"type": "string", "format": "email"},
"role": {"enum": ["admin", "editor", "viewer"]},
"age": {"type": ["integer", "null"], "minimum": 18, "maximum": 99},
"tags": {"type": "array", "minItems": 1, "maxItems": 3, "items": {"type": "string", "pattern": "^[a-z]{3,8}$"}}
}
}`)
// compiledUser is parsed once at package initialization; MustCompile panics
// on an invalid schema, so mistakes surface at startup.
var compiledUser = goppelganger.MustCompile(userSchema)
func seededFaker(seed int64) *goppelganger.Faker {
return goppelganger.New(goppelganger.Options{Seed: &seed})
}
func main() {
faker := seededFaker(1)
for range 2 {
user, err := faker.GenerateSchema(compiledUser)
if err != nil {
log.Fatal(err)
}
fmt.Println(user["role"], user["tags"])
}
}
Output: editor [zcq] viewer [xjfmaz ayluds ezbcv]
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
mockserver
command
Command mockserver is a stub REST API whose responses are generated from JSON Schemas, so a client can be built before the real backend exists.
|
Command mockserver is a stub REST API whose responses are generated from JSON Schemas, so a client can be built before the real backend exists. |
|
Package generator walks a parser AST and produces random data that satisfies every constraint expressed in the schema.
|
Package generator walks a parser AST and produces random data that satisfies every constraint expressed in the schema. |
|
internal
|
|
|
numeric
Package numeric holds integer and float range math shared by the parser (to reject unsatisfiable schemas) and the generator (to pick values).
|
Package numeric holds integer and float range math shared by the parser (to reject unsatisfiable schemas) and the generator (to pick values). |
|
Package parser converts raw JSON Schema documents into a strongly typed abstract syntax tree that the generator package can walk.
|
Package parser converts raw JSON Schema documents into a strongly typed abstract syntax tree that the generator package can walk. |