mock-plane

module
v0.0.0-...-197210c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2026 License: MIT

README

mock-server

CI Go Report Card codecov

Turn an OpenAPI 3.1 spec into editable, relationally-consistent mock data — served over HTTP as a drop-in mock, or dumped as static fixtures.

Most mock tools generate each endpoint in isolation, so GET /orders returns a userId that doesn't exist in GET /users. mock-server infers the relations between your schemas and materializes one consistent dataset: every foreign key points at a real row, and reads expand those ids into the nested objects your schema declares.

OpenAPI 3.1 spec  ─▶  entity model + inferred relations  ─▶  consistent seed data  ─▶  HTTP (two planes)

Written in Go: a single static binary, no runtime dependencies.

Quick start

# build the binary (Go ≥ 1.26)
go build -o mock ./cmd/mock
# …or install it onto your PATH
go install github.com/thiszona/mock-plane/cmd/mock@latest

# run the bundled demo (Shop API: users → orders → items → products)
./mock demo
#   mock-plane:    http://127.0.0.1:<port>
#   control-plane: http://127.0.0.1:<port>/__mock

# inspect the relation graph inferred from a spec
./mock graph ./openapi.yaml

# dump deterministic fixtures as JSON
./mock dump ./openapi.yaml --count 10 --out fixtures.json

# run a live mock of your own spec
./mock serve ./openapi.yaml --port 8080

No Go toolchain? Use the Docker image.

Two planes

A single server exposes two clearly separated surfaces:

Mock-plane — /<entity> · /<entity>/<id>

A spec-faithful drop-in: raw bodies that match your OpenAPI schemas, with no wrapper. Foreign-key ids are expanded into nested objects on read, exactly as the schema declares them.

curl http://127.0.0.1:8080/orders
# [ { "id": "...", "userId": "d4a6…",            ← scalar FK stays a string (schema says string)
#     "status": "paid", "total": 42.5,
#     "items": [ { "id": "...", "quantity": 2,
#                  "product": { "id": "...", "name": "…",
#                               "category": { "id": "...", "name": "…" } } } ] } ]
Method Path Result
GET /<entity> bare array, FK-expanded · ?limit=&offset= slice · X-Total-Count header
GET /<entity>/<id> bare object, FK-expanded · 404 if missing
POST /<entity> create · 201 + Location · 409 on duplicate id · 422 on dangling FK
PUT / PATCH /<entity>/<id> update (shallow merge) · 404 if missing
DELETE /<entity>/<id> 204 · 404 if missing

Routes are entity-driven: every schema gets full CRUD even if the spec declares few paths. Path stems are matched tolerantly (/users, /addresses, /categories all resolve).

Control-plane — /__mock/*

The tool's own API for authoring and introspection, wrapped in a consistent envelope { success, data, error, meta }.

Method Path Purpose
GET /__mock/health liveness + entity count
GET /__mock/model inferred entities + relations (incl. abstain edges)
GET /__mock/routes synthesized route table
GET /__mock/collections entity names + row counts
GET/POST /__mock/collections/<entity> list (paginated, raw rows) / create
GET/PATCH/DELETE /__mock/collections/<entity>/<id> fetch / update / delete a raw row
POST /__mock/reset restore the original materialized seed
GET/POST/DELETE /__mock/injection read / set / clear fault injection
POST /__mock/scenario/save·/load snapshot the store / hydrate a snapshot

The control-plane shows raw rows (foreign-key ids, not expanded) — the edit-plane view for authoring.

Multi-project

One server can host many specs at once. Pass two or more specs to serve and each becomes a project mounted at its id at the root/<id>/… — behind a single port:

mock serve examples/demo-spec.yaml examples/tracker-spec.yaml --port 8080
#   http://127.0.0.1:8080/demo-spec/...     (Shop API)
#   http://127.0.0.1:8080/tracker-spec/...  (Tracker API)
#   projects-api: http://127.0.0.1:8080/__mock/projects

Each project keeps its own two planes/<id>/<entity> (mock) and /<id>/__mock/* (control). The only reserved root is the control prefix /__mock; project ids are [a-z0-9-], so they can never collide with it. A registry API manages projects at runtime:

Method Path Purpose
GET /__mock/projects list hosted projects
POST /__mock/projects?id=&name= add a project (body = an OpenAPI spec) → 201 + Location: /<id>
GET /__mock/projects/<id> one project's descriptor
DELETE /__mock/projects/<id> remove a project

A single spec keeps the flat routes (/<entity>, /__mock/*). (Durable, multi-team-tenant hosting is a separate plane, not part of this local CLI.)

Relational inference

Relations between schemas are detected by a heuristic, in descending priority: explicit x-relation$ref → single-ref anyOf/oneOf…Id/…Sid/…Gid suffix → bare entity name. Ambiguous edges (e.g. a polymorphic anyOf of two entities) abstain rather than guess — they show up as UNRESOLVED in mock graph and can be promoted with an x-relation hint.

$ mock graph examples/demo-spec.yaml
Shop API  (5 entities, 5 relations, 0 unresolved)
├─ User
├─ Order
│  ├─ items → OrderItem (many)
│  └─ userId → User (one)
├─ OrderItem
│  ├─ orderId → Order (one)
│  └─ product → Product (one)
├─ Product
│  └─ category → Category (one)
└─ Category

Add --json for a machine-readable graph.

For a richer example, examples/tracker-spec.yaml (a project-tracker SaaS) exercises deep chains, x-relation role hints (assignee/reporter/lead all → User), a self-reference (Issue.parentId → Issue), a many-to-many (Issue.labels → Label), and a polymorphic edge the engine abstains on:

$ mock graph examples/tracker-spec.yaml
Tracker API  (8 entities, 16 relations, 1 unresolved)
…
├─ Issue
│  ├─ assigneeId → User (one)
│  ├─ labels → Label (many)
│  ├─ linkedTo → ? UNRESOLVED [candidates: Issue, Milestone]
│  ├─ parentId → Issue (one)
…

CLI

mock dump  <spec> [--count N] [--out FILE]            Print materialized fixtures (JSON)
mock graph <spec> [--json]                            Print the inferred relation graph
mock serve <spec...> [--port N] [--host H] [--count N] Run the mock server (1 spec, or N → multi-project)
mock demo          [--port N] [--host H] [--count N]  Run the server on the bundled demo spec
mock version                                          Print the version
mock completion <shell>                               Shell completion (bash/zsh/fish/powershell)
mock help                                             Show help (also: any command --help)

Built on spf13/cobra, so every command has its own --help and shell completion is generated for free.

<spec> is a path to an OpenAPI 3.1 YAML/JSON file, or - to read stdin. Data is deterministic — the same spec and --count always produce the same rows. --host defaults to 127.0.0.1; use 0.0.0.0 to accept external connections (e.g. in Docker).

Fault injection (serve / demo)

Fake slow or failing APIs to test your client's resilience. Configure at boot or live via POST /__mock/injection.

--latency <ms>                 global added latency (non-negative integer)
--error-rate <0..1>            global probability a request is failed
--error-status <400..599>      status for injected errors (default 503)
--seed <int>                   seed the error-rate RNG (reproducible)
--latency-for 'KEY=ms'         per-endpoint latency (repeatable)
--error-rate-for 'KEY=0..1'    per-endpoint error rate (repeatable)

KEY is METHOD:/Entity or METHOD:/Entity/:id (e.g. GET:/User); copy from GET /__mock/routes. Injected responses carry the header X-Mock-Injected: error.

Scenario snapshots (serve / demo)

Save, share, and replay store state. Snapshots are validated on load (format version + spec fingerprint + FK integrity) and rejected atomically — a bad snapshot never half-loads.

--scenario save|load           save state on shutdown, or load before serving
--scenario-file FILE           snapshot file path (single-spec --scenario)
--scenario-dir DIR             per-project snapshot directory (multi-spec --scenario)

Single spec → one snapshot file. Two or more specs (multi-project) → a directory holding one {project-id}.json per project; load restores each project from its matching file and skips any that's absent. Mixing --scenario-file with multiple specs (or --scenario-dir with one) is a usage error.

# save all projects on shutdown, reload them next run
mock serve api-a.yaml api-b.yaml --scenario save --scenario-dir ./snap
mock serve api-a.yaml api-b.yaml --scenario load --scenario-dir ./snap

Also live: POST /__mock/scenario/{save,load} (per project under /{id}/__mock/... in multi-project mode).

Docker

# bundled demo on :8080
docker build -t mock-server .
docker run -p 8080:8080 mock-server

# your own spec
docker run -p 8080:8080 -v "$PWD/openapi.yaml:/spec.yaml" \
  mock-server serve /spec.yaml --port 8080 --host 0.0.0.0

Published images: ghcr.io/thiszona/mock-plane (multi-arch: amd64 + arm64).

Layout

Package Role
internal/engine pure core — spec → entity model → relation inference → materialized, FK-consistent in-memory store. No FS, no HTTP.
internal/server go-chi/chi two-plane HTTP server over a store.
cmd/mock the mock command — dump, graph, serve, demo.

OpenAPI parsing uses pb33f/libopenapi for real 3.1 support. The engine is deliberately pure so it can back a CLI, a server, a UI, or a hosted service behind the same StateStore seam.

Development

go build ./...
go vet ./...
go test -race -cover ./...

go run ./cmd/mock demo   # run locally

Requires Go ≥ 1.26. CI builds, vets, and tests with the race detector, and verifies the Docker image builds.

License

MIT

Directories

Path Synopsis
cmd
mock command
Command mock turns an OpenAPI 3.1 spec into editable, relationally-consistent mock data: dump fixtures, print the inferred relation graph, or serve a two-plane mock API (raw mock plane + enveloped control plane).
Command mock turns an OpenAPI 3.1 spec into editable, relationally-consistent mock data: dump fixtures, print the inferred relation graph, or serve a two-plane mock API (raw mock plane + enveloped control plane).
internal
engine
Package engine is the pure mock-data core: it lifts an OpenAPI 3.1 document's component schemas into an entity model, infers the relation graph between them, and materializes relationally-consistent seed data into an in-memory store.
Package engine is the pure mock-data core: it lifts an OpenAPI 3.1 document's component schemas into an entity model, infers the relation graph between them, and materializes relationally-consistent seed data into an in-memory store.
server
Package server is the two-plane mock HTTP layer over an engine StateStore: a raw, spec-faithful mock plane and an enveloped control plane under a prefix.
Package server is the two-plane mock HTTP layer over an engine StateStore: a raw, spec-faithful mock plane and an enveloped control plane under a prefix.
store/postgres
Package postgres is the durable, hosted implementation of server.ProjectStore: the project registry (ids, names, spec bytes) lives in Postgres so a redeployed binary can replay its projects on boot.
Package postgres is the durable, hosted implementation of server.ProjectStore: the project registry (ids, names, spec bytes) lives in Postgres so a redeployed binary can replay its projects on boot.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL