{"id":50205312,"url":"https://github.com/lfdubiela/mockwave","last_synced_at":"2026-06-04T00:01:37.692Z","repository":{"id":359836815,"uuid":"1247628069","full_name":"lfdubiela/mockwave","owner":"lfdubiela","description":"HTTP mock server with Goja scripting, MCP integration for AI-assisted rule generation, and a real-time admin UI. Local dev or deployed.","archived":false,"fork":false,"pushed_at":"2026-06-01T22:35:15.000Z","size":7992,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T00:24:45.649Z","etag":null,"topics":["api-testing","developer-tools","goja","golang","http","local-dev","mcp","mock-server"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lfdubiela.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-23T15:12:52.000Z","updated_at":"2026-06-02T00:06:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lfdubiela/mockwave","commit_stats":null,"previous_names":["lfdubiela/mockwave"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/lfdubiela/mockwave","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfdubiela%2Fmockwave","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfdubiela%2Fmockwave/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfdubiela%2Fmockwave/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfdubiela%2Fmockwave/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lfdubiela","download_url":"https://codeload.github.com/lfdubiela/mockwave/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfdubiela%2Fmockwave/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33884734,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-testing","developer-tools","goja","golang","http","local-dev","mcp","mock-server"],"created_at":"2026-05-26T01:04:28.379Z","updated_at":"2026-06-04T00:01:37.669Z","avatar_url":"https://github.com/lfdubiela.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mockwave\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Go](https://img.shields.io/badge/Go-1.21+-00ADD8.svg)](https://golang.org)\n\n**Mockwave** is an open-source, multi-protocol mock server. Define rules and simulations in JSON, manage them through the browser UI, or let an AI assistant do it — Mockwave responds to HTTP, GraphQL, SOAP, and gRPC requests with weighted traffic splitting, dynamic JavaScript responses, real-time metrics, and a built-in MCP server for Claude Code integration.\n\n---\n\n## Features\n\n- **Multi-protocol** — HTTP REST, GraphQL, SOAP, gRPC (reflection-free, descriptor-based)\n- **Traffic splitting** — weighted buckets per rule (e.g., 90% mock / 10% forward to real service)\n- **Dynamic scripting** — per-simulation JavaScript (goja) for computed responses\n- **Real-time admin UI** — browser dashboard at `localhost:9090` for rule/simulation CRUD, live metrics, and unmatched request capture\n- **Multiple store backends** — JSON file, DynamoDB, MongoDB, Azure Cosmos DB (MongoDB API)\n- **Hot reload** — update rules without restarting via the admin API\n- **AI integration (MCP)** — `mockwave mcp` exposes a Model Context Protocol server so Claude Code can create rules, manage simulations, and auto-generate mocks from any OpenAPI 2.0/3.0 spec\n- **Embeddable library** — public `store.DataStore`, `observability.Logger/Tracer/MetricsRecorder` interfaces; bring your own backends\n\n---\n\n## Quick Start\n\n### Homebrew (macOS / Linux)\n\n```bash\nbrew tap lfdubiela/mockwave\nbrew install mockwave\n\n# Verify installation\nmockwave version\n```\n\n```bash\n# Create a minimal config\ncat \u003e config.json \u003c\u003c'EOF'\n{\n  \"rules\": [\n    {\n      \"id\": \"hello\",\n      \"name\": \"Hello World\",\n      \"match\": { \"method\": \"GET\", \"path\": \"/hello\" },\n      \"buckets\": [{ \"weight\": 100, \"action\": \"simulate\", \"simulation_id\": \"hello-sim\" }]\n    }\n  ],\n  \"simulations\": [\n    {\n      \"id\": \"hello-sim\",\n      \"protocol\": \"http\",\n      \"response\": { \"status\": 200, \"body\": { \"message\": \"Hello from Mockwave!\" } }\n    }\n  ]\n}\nEOF\n\n# Start on default ports (mock :8080, admin :9090)\nmockwave start -f config.json\n\n# Custom ports\nmockwave start -f config.json --port 3000 --admin-port 3001\n```\n\n```bash\n# Test it\ncurl http://localhost:8080/hello\n# {\"message\":\"Hello from Mockwave!\"}\n\n# Open the admin UI\nopen http://localhost:9090\n```\n\n```bash\n# Upgrade\nbrew upgrade mockwave\n```\n\n### MCP (Claude Code integration)\n\nWith Mockwave running, add to `~/.claude/mcp.json` (create if it doesn't exist):\n\n```json\n{\n  \"mcpServers\": {\n    \"mockwave-local\": {\n      \"command\": \"mockwave\",\n      \"args\": [\"mcp\", \"--admin-url\", \"http://localhost:9090\"]\n    }\n  }\n}\n```\n\nThen ask Claude Code to do the work:\n\n```\n\"Generate mocks from https://petstore3.swagger.io/api/v3/openapi.json\"\n\"Create a mock for POST /checkout that returns 201 with an order ID\"\n\"What requests are hitting mockwave but not matching any rule?\"\n```\n\n### Binary\n\n```bash\n# Download the latest release binary (replace OS/ARCH as needed)\ncurl -Lo mockwave https://github.com/lfdubiela/mockwave/releases/download/v0.2.0/mockwave-linux-amd64\nchmod +x mockwave\n\n# Start the server\n./mockwave start -f config.json\n```\n\n### Docker\n\n```bash\ndocker run -p 8080:8080 -p 9090:9090 \\\n  -v $(pwd)/config.json:/config.json \\\n  ghcr.io/lfdubiela/mockwave:v0.1.0 \\\n  start -f /config.json\n```\n\n---\n\n## CLI Reference\n\n```\nmockwave [command]\n\nCommands:\n  start     Start the mock server\n  validate  Validate a config file without starting the server\n  version   Print version\n  mcp       Start MCP server for AI assistant integration (Claude Code, etc.)\n\nFlags (start):\n  -f, --config string            Path to JSON config file (required for --store=json)\n      --port int                 Mock server port (default 8080)\n      --admin-port int           Admin UI/API port (default 9090)\n      --protocols string         Comma-separated: http,graphql,soap,grpc (default \"http\")\n      --grpc-port int            gRPC server port (default 50051)\n      --grpc-proto string        Path to compiled .pb descriptor for gRPC proto conversion\n\n  # Store backend\n      --store string             Storage backend: json|dynamodb|mongo|cosmos (default \"json\")\n\n  # DynamoDB\n      --dynamo-rules-table string  DynamoDB table for rules (default \"mockwave-rules\")\n      --dynamo-sims-table string   DynamoDB table for simulations (default \"mockwave-simulations\")\n      --dynamo-region string       AWS region (default \"us-east-1\")\n      --dynamo-endpoint string     Custom endpoint, e.g. http://localhost:8000\n\n  # MongoDB\n      --mongo-uri string          MongoDB connection URI (default \"mongodb://localhost:27017\")\n      --mongo-db string           MongoDB database name (default \"mockwave\")\n\n  # Cosmos DB\n      --cosmos-uri string         Cosmos DB connection string (MongoDB API)\n      --cosmos-db string          Cosmos DB database name (default \"mockwave\")\n```\n\n### Examples\n\n```bash\n# HTTP + GraphQL on the same port\nmockwave start -f config.json --protocols http,graphql\n\n# All protocols\nmockwave start -f config.json --protocols http,graphql,soap,grpc --grpc-proto service.pb\n\n# DynamoDB backend (uses default AWS credential chain)\nmockwave start --store dynamodb --dynamo-region eu-west-1\n\n# Local DynamoDB (e.g. DynamoDB Local)\nmockwave start --store dynamodb --dynamo-endpoint http://localhost:8000\n\n# MongoDB backend\nmockwave start --store mongo --mongo-uri mongodb://user:pass@host:27017/mydb\n\n# Validate a config file\nmockwave validate config.json\n```\n\n---\n\n## Config File Format\n\nThe JSON config file has two top-level arrays: `rules` and `simulations`.\n\n```json\n{\n  \"rules\": [ ...Rule... ],\n  \"simulations\": [ ...Simulation... ]\n}\n```\n\n### Rule\n\n```json\n{\n  \"id\": \"string (required, unique)\",\n  \"name\": \"string (display label)\",\n  \"match\": {\n    \"protocol\": \"http | graphql | soap | grpc\",\n    \"method\":   \"GET | POST | PUT | DELETE | PATCH | ...\",\n    \"path\":     \"/users/* (glob supported)\",\n    \"headers\":  { \"X-Tenant\": \"acme\" },\n    \"query\":    { \"version\": \"2\" },\n    \"body\":     { \"$.type\": \"order\" }\n  },\n  \"buckets\": [\n    {\n      \"weight\":        100,\n      \"action\":        \"simulate | forward\",\n      \"simulation_id\": \"sim-id (required when action=simulate)\"\n    }\n  ],\n  \"forward_url\": \"https://real-api.example.com (required when any bucket has action=forward)\"\n}\n```\n\n**Path globs:** `*` matches a single segment, `**` matches any number of segments.\n- `/users/*` matches `/users/123` but not `/users/123/orders`\n- `/api/**` matches any path under `/api/`\n\n**Traffic splitting:** weights are relative. `[{weight:90,…}, {weight:10,…}]` routes 90% to the first bucket and 10% to the second. Weights do not need to sum to 100.\n\n**Forwarding:** set `action: \"forward\"` and provide `forward_url`. The request is proxied to `forward_url + original_path` with original headers and body.\n\n### Simulation\n\n```json\n{\n  \"id\":       \"string (required, unique)\",\n  \"protocol\": \"http | graphql | soap | grpc\",\n\n  \"response\": {\n    \"status\":   200,\n    \"headers\":  { \"Content-Type\": \"application/json\" },\n    \"body\":     { \"any\": \"JSON value\" },\n    \"delay_ms\": 150\n  },\n\n  \"script\": \"// optional JS — return value overrides response.body\\nreturn { computed: request.path };\",\n\n  \"soap_envelope\": \"\u003csoap:Envelope\u003e...\u003c/soap:Envelope\u003e\",\n\n  \"grpc_message\": \"{ \\\"userId\\\": \\\"123\\\" }\",\n  \"grpc_status\":  0\n}\n```\n\n---\n\n## Protocols\n\n### HTTP REST\n\nEnabled by default. Routes by `method` + `path`. Response body supports Go template variables:\n\n```json\n{\n  \"id\": \"user-get\",\n  \"protocol\": \"http\",\n  \"response\": {\n    \"status\": 200,\n    \"body\": { \"id\": \"{{.PathParam \\\"id\\\"}}\", \"name\": \"Alice\" }\n  }\n}\n```\n\n### GraphQL\n\nEnable with `--protocols http,graphql`. Mockwave parses the `operationName` from the request body and matches against `match.path` (treated as operation name prefix/glob).\n\n```json\n{\n  \"id\": \"gql-user\",\n  \"match\": { \"protocol\": \"graphql\", \"path\": \"GetUser\" },\n  \"buckets\": [{ \"weight\": 100, \"action\": \"simulate\", \"simulation_id\": \"gql-user-sim\" }]\n}\n```\n\n### SOAP\n\nEnable with `--protocols http,soap`. Mockwave reads the SOAP action from the `SOAPAction` header and routes accordingly. Set `soap_envelope` in the simulation to return a raw XML envelope.\n\n```json\n{\n  \"id\": \"soap-create-order\",\n  \"match\": { \"protocol\": \"soap\", \"path\": \"CreateOrder\" },\n  \"buckets\": [{ \"weight\": 100, \"action\": \"simulate\", \"simulation_id\": \"create-order-sim\" }]\n}\n```\n\n```json\n{\n  \"id\": \"create-order-sim\",\n  \"protocol\": \"soap\",\n  \"soap_envelope\": \"\u003csoap:Envelope xmlns:soap=\\\"http://schemas.xmlsoap.org/soap/envelope/\\\"\u003e\u003csoap:Body\u003e\u003cCreateOrderResponse\u003e\u003corderId\u003e42\u003c/orderId\u003e\u003c/CreateOrderResponse\u003e\u003c/soap:Body\u003e\u003c/soap:Envelope\u003e\"\n}\n```\n\n### gRPC\n\nEnable with `--protocols http,grpc`. Requires a compiled protobuf descriptor:\n\n```bash\n# Compile your .proto to a descriptor\nprotoc --descriptor_set_out=service.pb --include_imports service.proto\n\n# Start with the descriptor\nmockwave start -f config.json --protocols http,grpc --grpc-proto service.pb\n```\n\nSet `grpc_message` (JSON representation of the proto response) and `grpc_status` (gRPC status code, 0 = OK) in the simulation:\n\n```json\n{\n  \"id\": \"get-user-sim\",\n  \"protocol\": \"grpc\",\n  \"grpc_message\": \"{ \\\"userId\\\": \\\"abc\\\", \\\"name\\\": \\\"Alice\\\" }\",\n  \"grpc_status\": 0\n}\n```\n\n---\n\n## Store Backends\n\n| Backend | Flag | Notes |\n|---------|------|-------|\n| JSON file | `--store json -f config.json` | Default. File is read on start; hot-reloaded via admin API. |\n| DynamoDB | `--store dynamodb` | Uses AWS default credential chain. Tables must exist with PK `id` (String). |\n| MongoDB | `--store mongo` | Tested with MongoDB 6+. |\n| Cosmos DB | `--store cosmos` | Uses MongoDB wire protocol. `ssl=true` and `retryWrites=false` applied automatically. |\n\n### DynamoDB Setup\n\nCreate two tables (substitute region/table names as needed):\n\n```bash\naws dynamodb create-table --table-name mockwave-rules \\\n  --attribute-definitions AttributeName=id,AttributeType=S \\\n  --key-schema AttributeName=id,KeyType=HASH \\\n  --billing-mode PAY_PER_REQUEST\n\naws dynamodb create-table --table-name mockwave-simulations \\\n  --attribute-definitions AttributeName=id,AttributeType=S \\\n  --key-schema AttributeName=id,KeyType=HASH \\\n  --billing-mode PAY_PER_REQUEST\n```\n\n---\n\n## Admin UI\n\nOpen `http://localhost:9090` in a browser. The UI is served from the admin port and requires no external dependencies.\n\n| Tab | What it does |\n|-----|--------------|\n| **Rules** | List, create, edit, and delete rules |\n| **Simulations** | List, add (JSON editor), and delete simulations |\n| **Metrics** | Live request counters and per-rule hit rates (SSE, updates every second) |\n| **Unmatched** | Requests that matched no rule — click \"Create Rule\" to pre-fill the rule form |\n\n---\n\n## Admin REST API\n\nAll endpoints are on the admin port (default `:9090`).\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/api/rules` | List all rules |\n| `POST` | `/api/rules` | Create a rule |\n| `GET` | `/api/rules/:id` | Get a rule |\n| `PUT` | `/api/rules/:id` | Update a rule |\n| `DELETE` | `/api/rules/:id` | Delete a rule |\n| `GET` | `/api/simulations` | List all simulations |\n| `POST` | `/api/simulations` | Create a simulation |\n| `GET` | `/api/simulations/:id` | Get a simulation |\n| `PUT` | `/api/simulations/:id` | Update a simulation |\n| `DELETE` | `/api/simulations/:id` | Delete a simulation |\n| `GET` | `/api/openapi.json` | OpenAPI 3.0 spec (JSON) |\n| `GET` | `/api/metrics` | Current metrics snapshot (JSON) |\n| `GET` | `/api/metrics/stream` | SSE stream — one event per second |\n| `GET` | `/api/unmatched` | List captured unmatched requests |\n| `DELETE` | `/api/unmatched` | Clear unmatched request buffer |\n| `POST` | `/api/reload` | Trigger hot-reload from store |\n| `GET` | `/api/health` | `{\"status\":\"ok\"}` |\n\n### Metrics snapshot shape\n\n```json\n{\n  \"at\": \"2026-05-24T12:00:00Z\",\n  \"total_requests\": 1042,\n  \"misses\": 13,\n  \"rules\": [\n    {\n      \"rule_id\":   \"hello\",\n      \"rule_name\": \"Hello World\",\n      \"hits\":      1029,\n      \"p95_ms\":    42.3\n    }\n  ]\n}\n```\n\n---\n\n## AI Integration (MCP)\n\n`mockwave mcp` exposes a [Model Context Protocol](https://modelcontextprotocol.io) server that lets Claude Code (and other MCP-compatible AI assistants) create, inspect, and delete rules and simulations on any Mockwave instance — local or remote.\n\n### How it works\n\n```\nClaude Code\n    │  stdio (stdin/stdout)\n    ▼\nmockwave mcp (local process, spawned by Claude Code)\n    │  HTTP\n    ▼\nMockwave Admin API (:9090) — localhost OR remote (EKS, sandbox, etc.)\n```\n\n`mockwave mcp` runs locally and bridges MCP tool calls to HTTP requests against `--admin-url`. The admin URL can point anywhere — a local dev instance or a shared sandbox running in the cloud.\n\n### Setup\n\nEnsure `mockwave` is on your `$PATH` (e.g. via `brew install mockwave`), then add to `~/.claude/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"mockwave-local\": {\n      \"command\": \"mockwave\",\n      \"args\": [\"mcp\", \"--admin-url\", \"http://localhost:9090\"]\n    }\n  }\n}\n```\n\nMultiple instances are supported — Claude Code namespaces the tools automatically:\n\n```json\n{\n  \"mcpServers\": {\n    \"mockwave-local\": {\n      \"command\": \"mockwave\",\n      \"args\": [\"mcp\", \"--admin-url\", \"http://localhost:9090\"]\n    },\n    \"mockwave-sandbox\": {\n      \"command\": \"mockwave\",\n      \"args\": [\"mcp\", \"--admin-url\", \"https://mockwave.sandbox.example.com\"]\n    }\n  }\n}\n```\n\n\u003e **Security:** The Mockwave admin API has no authentication. When pointing `--admin-url` at a remote instance, ensure the admin port is protected by a firewall or reverse proxy.\n\n### Available tools\n\n| Tool | Description |\n|------|-------------|\n| `list_rules` | List all rules |\n| `get_rule` | Get a rule by ID |\n| `create_rule` | Create a new rule |\n| `update_rule` | Replace a rule |\n| `delete_rule` | Delete a rule |\n| `list_simulations` | List all simulations |\n| `get_simulation` | Get a simulation by ID |\n| `create_simulation` | Create a new simulation |\n| `update_simulation` | Replace a simulation |\n| `delete_simulation` | Delete a simulation |\n| `generate_from_openapi` | Auto-generate rules + simulations from an OpenAPI 2.0/3.0 spec (URL or file) |\n| `get_metrics` | Current metrics snapshot |\n| `list_unmatched` | List requests that matched no rule |\n| `clear_unmatched` | Clear the unmatched buffer |\n| `reload` | Trigger hot-reload from store |\n| `health` | Check admin API reachability |\n\n### Examples\n\n**Create a rule from a natural language description:**\n```\nYou: \"Create a mock for GET /orders that returns 200 with an empty orders array\"\n\nClaude calls create_simulation → POST /api/simulations\nClaude calls create_rule       → POST /api/rules\nClaude: \"Done. GET http://localhost:8080/orders now returns {\"orders\":[]}\"\n```\n\n**Generate mocks from an existing OpenAPI spec:**\n```\nYou: \"Generate mocks from https://petstore3.swagger.io/api/v3/openapi.json\"\n\nClaude calls generate_from_openapi with the URL\nClaude: \"Created 18 rules and 18 simulations covering all Petstore endpoints.\n         GET /pet/{petId} → 200 {\"id\":1,\"name\":\"doggie\",\"status\":\"available\"}\n         POST /pet        → 201 {\"id\":1,\"name\":\"doggie\"}\n         DELETE /pet/{petId} → 200\n         ...\"\n```\n\n**Inspect what's being hit and fix gaps:**\n```\nYou: \"What requests are hitting mockwave but not matching any rule?\"\n\nClaude calls list_unmatched\nClaude: \"3 unmatched requests found:\n         POST /api/v2/checkout  ← no rule\n         GET  /api/v2/cart/99   ← no rule\n         Want me to create mocks for these?\"\n```\n\n**Dynamic script mock via MCP:**\n```\nYou: \"Create a mock for GET /users/:id that echoes the ID back in the response\"\n\nClaude calls create_simulation with script:\n  const id = request.path.split('/').pop();\n  return { body: { id: id, name: \"User \" + id } };\nClaude calls create_rule → POST /api/rules\nClaude: \"Done. GET /users/42 now returns {\"id\":\"42\",\"name\":\"User 42\"}\"\n```\n\n### Tip — add to CLAUDE.md\n\nDrop this in your project's `CLAUDE.md` to make Claude aware of Mockwave automatically:\n\n```markdown\n## Mocking\nMockwave is running at http://localhost:8080 (admin: http://localhost:9090).\nUse the `mockwave-local` MCP tools to create or update mocks instead of hardcoding responses.\nWhen a test hits an unmocked endpoint, call `list_unmatched` and create the missing rule.\n```\n\n---\n\n## JavaScript Scripting\n\nSet `\"script\"` on any simulation to run JavaScript (via [goja](https://github.com/dop251/goja)) on every matched request. The script must return an object with at least a `body` key (and optionally `status`, `headers`, `delay_ms`) to override the response.\n\n```json\n{\n  \"id\": \"dynamic-user\",\n  \"protocol\": \"http\",\n  \"response\": { \"status\": 200 },\n  \"script\": \"const id = request.path.split('/').pop(); return { body: { id: id, ts: Date.now() } };\"\n}\n```\n\nThe return object can override any part of the response:\n\n```js\nreturn {\n  status: 201,\n  headers: { \"X-Custom\": \"value\" },\n  body: { id: request.path.split('/').pop(), ts: Date.now() },\n  delay_ms: 100\n};\n```\n\n### Extracting path parameters\n\n`request.path` is the raw path string. Use standard JS string methods to extract segments:\n\n```js\n// GET /users/42  →  id = \"42\"\nconst id = request.path.split('/').pop();\nreturn { body: { id: id } };\n```\n\n```js\n// GET /org/acme/users/42  →  segments = [\"org\",\"acme\",\"users\",\"42\"]\nconst parts = request.path.split('/').filter(Boolean);\nconst org  = parts[1]; // \"acme\"\nconst id   = parts[3]; // \"42\"\nreturn { body: { org: org, userId: id } };\n```\n\n```js\n// named segment via regex: /users/:id/orders/:orderId\nconst match = request.path.match(/\\/users\\/([^/]+)\\/orders\\/([^/]+)/);\nconst userId  = match ? match[1] : null;\nconst orderId = match ? match[2] : null;\nreturn { body: { userId: userId, orderId: orderId } };\n```\n\n```js\n// numeric ID anywhere in path\nconst match = request.path.match(/\\/(\\d+)/);\nconst id = match ? parseInt(match[1], 10) : null;\nreturn {\n  status: id ? 200 : 404,\n  body: id ? { id: id } : { error: \"not found\" }\n};\n```\n\n```js\n// reflect full path + method back (useful for debugging)\nreturn {\n  body: {\n    method: request.method,\n    path:   request.path,\n    parts:  request.path.split('/').filter(Boolean)\n  }\n};\n```\n\n### Using request headers\n\n```js\n// bearer token presence check\nconst auth = request.headers[\"authorization\"] || \"\";\nconst token = auth.replace(\"Bearer \", \"\");\nreturn {\n  status: token ? 200 : 401,\n  body: token ? { token: token } : { error: \"unauthorized\" }\n};\n```\n\n```js\n// tenant routing via custom header\nconst tenant = request.headers[\"x-tenant-id\"] || \"default\";\nconst id = request.path.split('/').pop();\nreturn { body: { tenant: tenant, id: id, source: \"mock\" } };\n```\n\n```js\n// echo all headers back (debugging)\nreturn { body: { headers: request.headers } };\n```\n\n### Using request body\n\n```js\n// body is already parsed when Content-Type is application/json\nconst name = request.body \u0026\u0026 request.body.name;\nreturn { body: { message: \"Hello, \" + (name || \"stranger\") } };\n```\n\n```js\n// validate required fields, return 422 if missing\nconst b = request.body || {};\nif (!b.email || !b.name) {\n  return { status: 422, body: { error: \"email and name are required\" } };\n}\nreturn { status: 201, body: { id: Math.floor(Math.random() * 10000), email: b.email } };\n```\n\n### Combining path + headers + body\n\n```js\n// POST /accounts/:accountId/transfers\nconst accountId = request.path.split('/').filter(Boolean)[1];\nconst requestId = request.headers[\"x-request-id\"] || \"none\";\nconst amount    = request.body \u0026\u0026 request.body.amount;\nreturn {\n  status: 202,\n  headers: { \"x-request-id\": requestId },\n  body: {\n    transferId: \"txn-\" + Date.now(),\n    from:       accountId,\n    amount:     amount,\n    status:     \"pending\"\n  }\n};\n```\n\nAvailable in the script context:\n\n| Variable | Type | Description |\n|----------|------|-------------|\n| `request.method` | string | HTTP method (`\"GET\"`, `\"POST\"`, …) |\n| `request.path` | string | Full path string (e.g. `\"/orders/12345\"`) |\n| `request.headers` | object | Request headers (lowercase keys) |\n| `request.body` | object\\|null | Parsed JSON body, or `null` |\n| `response.status` | number | Current response status (modifiable) |\n| `response.body` | object | Current response body (modifiable) |\n\n---\n\n## Extending Mockwave\n\nMockwave exposes public Go interfaces for storage and observability. Implement any of them and pass to `server.New`:\n\n| Interface | Controls |\n|-----------|----------|\n| `store.DataStore` | Where rules and simulations are stored |\n| `observability.Logger` | Structured logging (zerolog, zap, …) |\n| `observability.Tracer` | Distributed tracing (OpenTelemetry, …) |\n| `observability.MetricsRecorder` | Request metrics (Prometheus, …) |\n\nAll fields in `server.Config` default to Noop implementations when nil — safe to omit anything you don't need.\n\n**→ [Extension guide: DataStore + observability interfaces, full examples](docs/extending.md)**\n\n---\n\n## Docker\n\n```bash\n# Run with a local config file\ndocker run -p 8080:8080 -p 9090:9090 \\\n  -v $(pwd)/config.json:/config.json \\\n  ghcr.io/lfdubiela/mockwave:v0.1.0 \\\n  start -f /config.json\n\n# All protocols\ndocker run -p 8080:8080 -p 9090:9090 -p 50051:50051 \\\n  -v $(pwd)/config.json:/config.json \\\n  -v $(pwd)/service.pb:/service.pb \\\n  ghcr.io/lfdubiela/mockwave:v0.1.0 \\\n  start -f /config.json --protocols http,graphql,soap,grpc --grpc-proto /service.pb\n\n# DynamoDB backend (IAM role or env vars)\ndocker run -p 8080:8080 -p 9090:9090 \\\n  -e AWS_ACCESS_KEY_ID=... \\\n  -e AWS_SECRET_ACCESS_KEY=... \\\n  -e AWS_REGION=us-east-1 \\\n  ghcr.io/lfdubiela/mockwave:v0.1.0 \\\n  start --store dynamodb\n```\n\n### Building locally\n\n```bash\ndocker build -t mockwave:local .\ndocker run -p 8080:8080 -p 9090:9090 \\\n  -v $(pwd)/config.json:/config.json \\\n  mockwave:local start -f /config.json\n```\n\n---\n\n## Building from Source\n\nRequirements: Go 1.21+\n\n```bash\ngit clone https://github.com/lfdubiela/mockwave.git\ncd mockwave\n\n# Build binary\nmake build          # outputs ./mockwave\n\n# Run tests\nmake test\n\n# Check coverage (must be ≥80%)\nmake coverage\n```\n\n---\n\n## Contributing\n\nContributions are welcome. Please open an issue before submitting a large PR.\n\n1. Fork the repo\n2. Create a feature branch (`git checkout -b feat/my-feature`)\n3. Write tests first (TDD)\n4. Ensure `make test` and `make coverage` pass\n5. Submit a pull request\n\n---\n\n## License\n\nMockwave is released under the [MIT License](LICENSE). Free to use, modify, and distribute — commercially or otherwise — with attribution.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flfdubiela%2Fmockwave","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flfdubiela%2Fmockwave","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flfdubiela%2Fmockwave/lists"}