{"id":47260029,"url":"https://github.com/ozgen/openapi-emulator","last_synced_at":"2026-03-14T22:50:35.168Z","repository":{"id":335192383,"uuid":"1144244588","full_name":"ozgen/openapi-emulator","owner":"ozgen","description":"A small HTTP emulator that returns predefined JSON responses based on an OpenAPI / Swagger specification.","archived":false,"fork":false,"pushed_at":"2026-03-08T22:04:13.000Z","size":188,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-09T03:23:10.537Z","etag":null,"topics":["api","emulator","go","http","kin-openapi","mock","mock-server","openapi3","service-emulation","swagger2"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ozgen.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":null,"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-01-28T13:22:32.000Z","updated_at":"2026-03-08T22:04:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"916046d2-13cb-4d45-bd50-200d9860e64f","html_url":"https://github.com/ozgen/openapi-emulator","commit_stats":null,"previous_names":["ozgen/openapi-sample-emulator","ozgen/gvm-openapi-emulator","ozgen/openapi-emulator"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ozgen/openapi-emulator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozgen%2Fopenapi-emulator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozgen%2Fopenapi-emulator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozgen%2Fopenapi-emulator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozgen%2Fopenapi-emulator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ozgen","download_url":"https://codeload.github.com/ozgen/openapi-emulator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozgen%2Fopenapi-emulator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30520758,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-14T19:51:21.629Z","status":"ssl_error","status_checked_at":"2026-03-14T19:51:12.959Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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","emulator","go","http","kin-openapi","mock","mock-server","openapi3","service-emulation","swagger2"],"created_at":"2026-03-14T22:50:30.806Z","updated_at":"2026-03-14T22:50:35.162Z","avatar_url":"https://github.com/ozgen.png","language":"Go","readme":"# openapi-emulator\n\n`openapi-emulator` is a lightweight HTTP emulator that serves predefined responses based on an OpenAPI / Swagger specification.\n\nIt is designed for **local development**, **integration testing**, and **CI environments** where deterministic and predictable API behavior is required.\n\n---\n\n## Documentation\n\n* [Environment Variables](./docs/ENVIRONMENT_VARIABLES.md) – Configuration options\n\n---\n\n## How to use\n\nClone the repository and run locally:\n\n```bash\ngit clone https://github.com/ozgen/openapi-emulator.git\ncd openapi-emulator\nmake run\n```\n\n### Using Makefile\n\nThe repository includes a `Makefile` with helpful targets:\n\n```bash\nmake build         # Build the binary into ./bin/emulator\nmake run           # Build and run the emulator (uses SPEC_PATH / SAMPLES_DIR defaults)\nmake test          # Run all tests\nmake cover         # Run tests with coverage and generate reports (coverage.html)\n\nmake format        # Format code with goimports + gofumpt + gofmt\nmake lint          # Run golangci-lint\nmake tidy          # Run go mod tidy\n\nmake docker-build  # Build Docker image (gvm-openapi-emulator:local)\nmake docker-run    # Run Docker image with example volume mount\nmake compose-up    # Start via docker compose\nmake compose-down  # Stop docker compose\nmake clean         # Remove build and coverage artifacts\n```\n\n---\n\n## What it does\n\n* Reads an OpenAPI / Swagger specification\n* Matches incoming requests by HTTP method and path\n* Resolves responses from JSON sample files (folder-based or legacy flat)\n* Supports **stateful APIs** using explicit `scenario.json` definitions\n* Supports **step-based** and **time-based** state progression\n* Optionally falls back to examples defined in the OpenAPI spec\n* Can enforce basic request validation (e.g. required request body)\n\n---\n\n## Why use it\n\nThis tool is useful when:\n\n* You need **deterministic, repeatable responses**\n* You want to test integrations without running real services\n* Your API spec has limited or missing examples\n* CI tests must be stable and predictable\n* You want to simulate **long-running or stateful APIs**\n  (e.g. scans, jobs, tasks, workflows)\n\n---\n\n## How responses are resolved\n\nFor each request, the emulator resolves responses in the following order:\n\n1. **Scenario-based responses** (`scenario.json`, if present)\n2. **Folder-based sample files**\n3. **Legacy flat sample files** (optional)\n4. **OpenAPI response examples** (if enabled)\n5. Otherwise, an error response is returned\n\nThe resolution behavior is controlled via `LAYOUT_MODE`.\n\n---\n\n## Folder-based sample layout (recommended)\n\nThe recommended layout mirrors the API path structure:\n\n```\nSAMPLES_DIR/\n  api/\n    v1/\n      items/\n        GET.json\n        POST.json\n        {id}/\n          GET.json\n```\n\n### Naming rules\n\n```\n\u003cpath\u003e/\u003cMETHOD\u003e[.\u003cstate\u003e].json\n```\n\nExamples:\n\n* `GET /api/v1/items` - `api/v1/items/GET.json`\n* `POST /scans` - `scans/POST.json`\n* `GET /scans/{id}` - `scans/{id}/GET.json`\n\nPath parameters remain as `{id}`.\n\n---\n\n## Stateful APIs with `scenario.json`\n\nStateful behavior is defined **explicitly per endpoint** using a `scenario.json` file placed in that endpoint’s folder.\n\n### Example folder\n\n```\nscans/{id}/status/\n  scenario.json\n  GET.requested.json\n  GET.running.1.json\n  GET.running.2.json\n  GET.running.3.json\n  GET.running.4.json\n  GET.succeeded.json\n```\n\n---\n\n## Step-based scenarios (recommended)\n\nEach matching request advances the state by one step.\n\n### Example `scenario.json`\n\n```json\n{\n  \"version\": 1,\n  \"mode\": \"step\",\n  \"key\": { \"pathParam\": \"id\" },\n  \"sequence\": [\n    { \"state\": \"requested\", \"file\": \"GET.requested.json\" },\n    { \"state\": \"running.1\", \"file\": \"GET.running.1.json\" },\n    { \"state\": \"running.2\", \"file\": \"GET.running.2.json\" },\n    { \"state\": \"running.3\", \"file\": \"GET.running.3.json\" },\n    { \"state\": \"running.4\", \"file\": \"GET.running.4.json\" },\n    { \"state\": \"succeeded\", \"file\": \"GET.succeeded.json\" }\n  ],\n  \"behavior\": {\n    \"advanceOn\": [{ \"method\": \"GET\" }],\n    \"resetOn\": [{ \"method\": \"DELETE\", \"path\": \"/scans/{id}\" }],\n    \"repeatLast\": true\n  }\n}\n```\n\n**Behavior:**\n\n* First `GET` - `requested`\n* Each subsequent `GET` advances the state\n* After the last step, the state remains `succeeded` (`repeatLast: true`)\n* `DELETE /scans/{id}` resets the scenario for that `id`\n\nThis mode is **deterministic and CI-friendly**.\n\n### Looping step scenarios (optional)\n\nIf you want the sequence to repeat from the beginning:\n\n```json\n\"behavior\": {\n  \"advanceOn\": [{ \"method\": \"GET\" }],\n  \"repeatLast\": false,\n  \"loop\": true\n}\n```\n\n---\n\n## Time-based scenarios (optional)\n\nState progression is based on **elapsed seconds** since the scenario starts.\n\n### Example `scenario.json`\n\n```json\n{\n  \"version\": 1,\n  \"mode\": \"time\",\n  \"key\": { \"pathParam\": \"id\" },\n  \"timeline\": [\n    { \"afterSec\": 0, \"state\": \"requested\", \"file\": \"GET.requested.json\" },\n    { \"afterSec\": 2, \"state\": \"running.1\", \"file\": \"GET.running.1.json\" },\n    { \"afterSec\": 7, \"state\": \"succeeded\", \"file\": \"GET.succeeded.json\" }\n  ],\n  \"behavior\": {\n    \"startOn\": [{ \"method\": \"GET\" }],\n    \"resetOn\": [{ \"method\": \"DELETE\", \"path\": \"/scans/{id}\" }],\n    \"repeatLast\": true\n  }\n}\n```\n\n**Notes:**\n\n* `afterSec` means “effective from this second onward”.\n* With `repeatLast: true`, once the last milestone is reached it stays there.\n* `startOn` controls when the timer starts. If omitted, the timer starts on first access.\n\n### Looping time scenarios (important)\n\nIf you enable looping:\n\n```json\n\"behavior\": { \"loop\": true }\n```\n\nMake sure the final state is observable for more than an instant.\n\n**Recommended pattern:** add a “hold” window at the end:\n\n```json\n\"timeline\": [\n  { \"afterSec\": 0, \"state\": \"requested\", \"file\": \"GET.requested.json\" },\n  { \"afterSec\": 2, \"state\": \"running.1\", \"file\": \"GET.running.1.json\" },\n  { \"afterSec\": 7, \"state\": \"succeeded\", \"file\": \"GET.succeeded.json\" },\n  { \"afterSec\": 9, \"state\": \"succeeded\", \"file\": \"GET.succeeded.json\" }\n]\n```\n\nThis keeps `succeeded` active for 2 seconds before the loop restarts, which is easier to observe in polling clients.\n\nTime-based mode is useful for demos or UI testing, but may be less suitable for CI due to timing.\n\n---\n\n## Legacy flat sample files (optional)\n\nFor backward compatibility, flat files are still supported:\n\n```\nMETHOD__path_with_slashes_replaced_by_underscores.json\n```\n\nExamples:\n\n* `GET /api/v1/items` - `GET__api_v1_items.json`\n* `GET /scans/{id}/results` - `GET__scans_{id}_results.json`\n\nEnable via:\n\n```bash\nLAYOUT_MODE=flat\n# or\nLAYOUT_MODE=auto\n```\n\n---\n\n## Layout modes\n\n```bash\nLAYOUT_MODE=auto     # default: scenario -\u003e folders -\u003e flat\nLAYOUT_MODE=folders  # only folder-based layout\nLAYOUT_MODE=flat     # only legacy flat files\n```\n\n---\n\n## Validation\n\nOptional request validation can be enabled:\n\n```bash\nVALIDATION_MODE=required\n```\n\nCurrently supported:\n\n* Required request body\n\nIf the API spec marks a request body as required, requests with an empty body are rejected with **HTTP 400**.\n\nSupported specs:\n\n* OpenAPI 3.x – `requestBody.required: true`\n* Swagger 2.0 – `in: body` with `required: true`\n  (via Swagger 2 to OpenAPI 3 conversion using\n  [https://github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi))\n\n---\n\n## When not to use it\n\nThis tool is **not intended** to:\n\n* Generate random or synthetic data\n* Fully validate request schemas\n* Replace contract-testing tools\n\n---\n\n## License\n\nCopyright (C) 2009-2026 [Greenbone AG](https://www.greenbone.net/)\n\nLicensed under the [GNU Affero General Public License v3.0 or later](LICENSE).\n---\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fozgen%2Fopenapi-emulator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fozgen%2Fopenapi-emulator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fozgen%2Fopenapi-emulator/lists"}