# API Genie > API Genie is a command-line toolkit for API-first development. `apigenie` lints, formats, diffs, bundles, converts and translates OpenAPI 3.x, Swagger 2.0, AsyncAPI, Protobuf and GraphQL files, and contract-tests live APIs; `apigenie-generator` generates clients, servers and documentation for TypeScript, Python, Go, Java, C# and Elixir; `apigenie-server` serves mocks and validating proxies for HTTP, WebSocket, MQTT, gRPC and GraphQL. --- # Lint and format API descriptions Source: https://apigenie.pl/docs/guides/lint-and-format-api-descriptions This guide sets up `apigenie lint` and `apigenie format` for a repository of API descriptions: a shared configuration, the first clean run, your own rules, and a check on every pull request. It uses an OpenAPI file, `openapi.yaml`; AsyncAPI, Swagger 2.0, Protobuf and GraphQL files work the same way. You need the CLI installed; the [Quickstart](/docs/quickstart) shows how. ## Run the linter Without configuration the linter applies the `recommended` ruleset: ```sh apigenie lint openapi.yaml ``` ```text 764:6 warning Unused shared component unused-shared-component 781:8 error Schema with integer type does not define proper format schema-format-defined 2 issues (1 error, 1 warning) 0 errors, 1 warning are potentially auto-fixable ``` Each line gives the position, the severity, the message and the rule. Every rule has a page in the [linter rules](/docs/linter) that says what it checks and why. Errors make the command exit with status 1; warnings and hints are reported but don't fail it. Let the linter fix what has a safe fix. The files keep their comments: ```sh apigenie lint --fix openapi.yaml ``` ## Write down your rules Put an `apigenie.yaml` at the root of the repository. Every command finds it in the current directory or a parent, up to the root of the Git repository, so the whole team and CI use the same rules. The first line lets editors that use the YAML language server complete and check the file. ```yaml # yaml-language-server: $schema=https://apigenie.pl/schemas/apigenie-config/latest.json version: '1.0' commands: lint: extends: [recommended] rules: info-defined: off paths-max-count: [error, 30] tags-unique: error ``` `extends` picks the rulesets to start from: `recommended`, `strict`, `all`, or a ruleset of the [catalog](/docs/linter/openapi) such as `privacy`. `rules` changes the severity of a rule (`error`, `warning`, `hint` or `off`), or its severity and options. `apigenie config` prints the settings in effect, and `apigenie config check` checks the file. When a part of a document has a good reason to break a rule, disable the rule there, as close to the problem as you can: ```yaml info: x-apigenie-lint-disable: - contact-defined ``` ## Add a rule of your own House rules that no ruleset has are written the way [Spectral](https://docs.stoplight.io/docs/spectral) rules are: a JSONPath expression selects the values, and a function checks each of them. ```yaml commands: lint: extends: [recommended] rules: operation-summary: description: Operations must have a summary message: "{{property}} is missing at {{path}}" severity: error given: "$.paths[*][get,put,post,delete,patch]" then: field: summary function: truthy ``` The core functions of Spectral are built in, and JavaScript functions written for Spectral mostly work unchanged; see [custom rules](/docs/reference/cli/lint#custom-rules). ## Format the files The formatter puts the files in one style without changing what they mean: indentation, line endings, the order of properties and the YAML styles of values. It writes the files in place: ```sh apigenie format openapi.yaml ``` Its settings go in the same `apigenie.yaml`. Start from the `recommended` preset and override what your team does differently: ```yaml commands: format: extends: [recommended] openapi: newline: crlf style_overrides: - fields: [description] string: literal ``` `--check` writes nothing and exits with status 1 when a file would change, which is what CI needs: ```sh apigenie format --check openapi.yaml ``` ## Check every pull request The [GitHub Action](/docs/ci/github-action) runs both commands and shows the issues as annotations on the lines of the pull request: ```yaml on: pull_request jobs: api: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mykulyak/api-genie-action@v0 with: command: lint files: api/**/*.yaml - uses: mykulyak/api-genie-action@v0 with: command: format files: api/**/*.yaml ``` On other CI systems, run the commands and read their exit statuses. For dashboards, `lint --formatter` writes SARIF (GitHub code scanning), JUnit, checkstyle, TAP, JSON or HTML; see [formatting results](/docs/reference/cli/lint#formatting-results). ## Next steps - [Linter rules](/docs/linter): every rule and ruleset of the five linters. - [lint](/docs/reference/cli/lint) and [format](/docs/reference/cli/format): every option and setting. - [Catch breaking changes in CI](/docs/guides/catch-breaking-changes-in-ci): the next check for the same pull requests. --- # Catch breaking changes in CI Source: https://apigenie.pl/docs/guides/catch-breaking-changes-in-ci A breaking change is one that makes a working client fail: a removed operation, a new required parameter, a response field that went away. This guide makes `apigenie diff` stop such changes in pull requests, shows how to remove things without breaking anyone, and builds a changelog from the history of the file. It uses an OpenAPI file, `openapi.yaml`; Swagger 2.0, AsyncAPI, Protobuf and GraphQL files are compared the same way, each by what its clients depend on. You need the CLI installed; the [Quickstart](/docs/quickstart) shows how. ## Compare two versions `apigenie diff` takes the version clients use today and the new one: ```sh git show main:openapi.yaml > main.yaml apigenie diff main.yaml openapi.yaml ``` Every change is rated **breaking**, **non-breaking** or **non-functional** (a description, an example). The command exits with status 1 when any change is breaking, so a script or a CI job can stop there. `--report` picks what it prints: `changelog` (the default), `status` (one word) or `detailed` (every property that changed); `--formatter` writes it as JSON, Markdown, HTML or a format editors understand. Both files must be of the same standard. Here is the report for two versions of a Protobuf file: ```text shop.proto 19 1 breaking Modified message 'Customer' 19 1 non-breaking Removed field 'fax' 25 3 breaking Modified field 'full_name' 25 3 breaking Modified field name ``` ## Check every pull request The [GitHub Action](/docs/ci/github-action) compares each file with its version at the base of the pull request and shows the changes as annotations: ```yaml on: pull_request jobs: api: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mykulyak/api-genie-action@v0 with: command: diff files: api/openapi.yaml ``` The step fails on breaking changes. Set `base` to compare with another branch or tag, such as the last release, and `fail-on: never` to report without failing. A failing step only blocks the merge once the check is required. In the repository's settings, under *Branches*, add a branch protection rule for the base branch (or a ruleset under *Rules*), turn on *Require status checks to pass before merging* and pick the `api` job. From then on a pull request with a breaking change waits until the change is withdrawn, or its severity is [reconsidered](#decide-what-breaks-your-clients). ## Decide what breaks your clients The `recommended` rules fit most APIs. When yours differ, change them in `apigenie.yaml`: each property of each kind of node takes a severity (`breaking`, `non_breaking`, `non_functional` or `none`), or one each for `add`, `remove` and `modify`. List `recommended` in `extends` to change only some of its rules: ```yaml version: '1.0' commands: diff: extends: [recommended] rules: operation: tags: none ``` The [diff configuration](/docs/reference/cli/diff#configuration) lists the node types and their properties, and how to set different rules for requests and responses. ## Retire an operation on a schedule Removing an operation is breaking, unless clients were told in advance. Deprecate it with a sunset date first, and ship that: deprecating is not breaking. ```yaml paths: /v1/orders: get: deprecated: true x-sunset: 2027-01-31 ``` Once the date has passed, the `recommended` rules treat the removal as non-breaking, and the same CI job lets it through. `--today` sets the date of the comparison, to try it out before then. For changes bigger than one operation, `apigenie evolve plan` does the planning. It takes the document clients use and the one you want, and writes an expand wave (the new shape next to the old one, deprecated, safe to ship now) and a contract wave (the target, to ship after the sunset): ```sh apigenie evolve plan main.yaml openapi.yaml --out evolve-out/ ``` `evolve` is [experimental](/docs/reference/cli/overview#experimental-commands); see [evolve](/docs/reference/cli/evolve) for its recipes. ## Set the version and keep a changelog `--update-version` writes the smallest `info.version` the changes allow, a new major version for breaking changes, minor for non-breaking and patch for non-functional ones, and keeps the rest of the file as it is: ```sh apigenie diff main.yaml openapi.yaml --update-version ``` `apigenie changelog` compares the committed versions of a file and describes what changed for clients, per commit or per release tag: ```sh apigenie changelog openapi.yaml --revisions tags --tag-pattern 'v*' --unreleased ``` ## Next steps - [diff](/docs/reference/cli/diff): every option, the rules of each standard and the JSON report. - [changelog](/docs/reference/cli/changelog): revisions, tags and output formats. - [Publish API documentation](/docs/guides/publish-api-documentation): the changelog of each API on its documentation site. --- # Generate a client and a server Source: https://apigenie.pl/docs/guides/generate-a-client-and-a-server This guide generates both sides of an API from its OpenAPI document, `openapi.yaml`: a TypeScript client for the Fetch API and an Express server. The same steps work for the other targets of [`apigenie-generator`](/docs/reference/generator), in Java, Go, Python, C# and Elixir, and for AsyncAPI, GraphQL and Protobuf documents. You need the CLI installed; the [Quickstart](/docs/quickstart) shows how. Lint the document first ([Lint and format API descriptions](/docs/guides/lint-and-format-api-descriptions)): a clean contract gives cleaner code. ## Generate the client ```sh apigenie-generator client fetch openapi.yaml -o client --package-name shipping-client ``` The output is an npm package with no runtime dependencies: requests go through the global `fetch()`, so the client runs in browsers and in Node.js 18 or later. Each operation is an `async` function named after its `operationId`, with its parameters and its result typed from the document: ```ts import { configure, ApiError, listShipments, getShipment } from "shipping-client"; configure({ baseUrl: "https://api.example.com/v2", auth: { apiKey: "ce_live_123" }, }); try { const page = await listShipments({ status: ["in_transit"], limit: 20 }); const shipment = await getShipment({ shipmentId: "shp_01J9Z" }); } catch (error) { if (error instanceof ApiError) { console.error(error.status, error.body); } } ``` To compile the client as part of your own project instead of as a package, set `layout: sources` for `client_fetch` in the configuration (see [Configure the generators](#configure-the-generators) below): the generator then writes only the sources. ## Generate the server ```sh apigenie-generator server express openapi.yaml -o server --package-name shipping-server ``` The generator writes typed handler interfaces and a router that decodes and validates every request. Your code implements the handlers in files of its own, so regenerating never overwrites it: ```ts import express from "express"; import { BASE_PATH, createRouter, HttpError, type ShipmentsHandlers } from "shipping-server"; const shipments: Partial = { async getShipment(request) { const shipment = await db.shipments.find(request.params.shipmentId); if (shipment === undefined) { throw new HttpError(404, "No such shipment"); } return { status: 200, body: shipment }; }, }; const app = express(); app.use(BASE_PATH, createRouter({ shipments })); app.listen(8080); ``` Operations are grouped by their first tag (`ShipmentsHandlers`), and the status, body and headers a handler returns are typed per status from the document. A change to the contract that the handlers don't follow is a compile error. ## Configure the generators Generator settings go in the `commands.generate` section of `apigenie.yaml`, one entry per target, so the command lines stay short and the settings are reviewed like code. Here the client's imports suit a bundler, and an overlay adjusts the document for the server: ```yaml version: '1.0' commands: generate: client_fetch: import_extension: '' server_express: overlay: - overlays/express.overlay.yaml ``` Each target's page lists its settings and the vendor extensions it reads, such as [client fetch](/docs/reference/generator/client-fetch) and [server express](/docs/reference/generator/server-express). Extensions that matter to one generator only can live in an [OpenAPI Overlay](/docs/reference/cli/overlay) of that generator, listed under `overlay` or given with `--overlay`, instead of in the API description. ## Regenerate in CI Generate into the repository and let CI check that the generated code matches the contract, so nobody edits it by hand and nobody forgets to regenerate: ```sh apigenie-generator client fetch openapi.yaml -o client --package-name shipping-client apigenie-generator server express openapi.yaml -o server --package-name shipping-server git diff --exit-code client server ``` Once the server runs, check that it keeps the contract with a validating proxy: [Mock and contract-test an API](/docs/guides/mock-and-contract-test-an-api). ## Next steps - [apigenie-generator](/docs/reference/generator): every target, by standard. - [Overlays](/docs/reference/generator#overlays): settings outside the API description. - [Generate code](/use-cases/code-generation): what the generator covers. --- # Mock and contract-test an API Source: https://apigenie.pl/docs/guides/mock-and-contract-test-an-api A contract is only useful while both sides keep it. This guide serves an API from its OpenAPI document, `openapi.yaml`, so that client teams can start before the server exists, and then checks the real server's traffic against the same document, locally and in CI. `apigenie-server` does the same for AsyncAPI documents (WebSocket, HTTP and MQTT), `.proto` files (gRPC) and GraphQL schemas; see the [server reference](/docs/reference/server). You need the CLI installed; the [Quickstart](/docs/quickstart) shows how. ## Serve a mock ```sh apigenie-server openapi.yaml ``` ```text Listening on http://127.0.0.1:4010/v1 (spec: openapi.yaml, 12 operations) Admin API: http://127.0.0.1:4010/__apigenie Dashboard: http://127.0.0.1:4010/__apigenie/ui ``` The mock serves the paths of the document under the path of its first server (`/v1` here), checks every request against the document, and answers with the document's examples. Operations without examples get a body generated from their schemas; `--dynamic` generates every answer. `--watch` reloads the mock when the document changes, so it follows the contract as you write it. Open the dashboard to see each request, what answered it and what was wrong with it. ## Ask for the answers you need A client has to handle more than the happy path. The `Prefer` header, or a query parameter, picks the answer of one request: ```sh curl http://127.0.0.1:4010/v1/orders -H 'Prefer: code=404' curl http://127.0.0.1:4010/v1/orders -H 'Prefer: example=empty' curl http://127.0.0.1:4010/v1/orders -H 'Prefer: delay=1500' ``` For answers that should hold for a while, such as an outage of one service, define scenarios in the document or in the configuration, and switch them on with `--scenario` or in the dashboard: ```yaml paths: /orders: get: x-apigenie-mock: scenarios: empty: {body: []} broken: {status: 500, drop: {probability: 0.5, mode: reset}} ``` `--latency`, `--jitter` and `--drop-rate` make every answer slow or unreliable, and `--seed` repeats the same run. ## Check the real server Once the server runs, put `apigenie-server` in front of it with `--proxy-to`. Every request goes to the real server, and the request and the server's response are both checked against the document: ```sh apigenie-server openapi.yaml --proxy-to http://localhost:8080 ``` Point your tests, your front end or a load test at the proxy instead of the server, and their traffic becomes a contract test. Each response carries the problems found in its exchange in the `X-Apigenie-Mock-Violations` headers, and the dashboard's Contract screen shows the coverage and the violations by check. To send the requests yourself rather than through your tests, `apigenie test` runs `.http` files, or one generated request per operation, against the server with the same checks: ```sh apigenie test --spec openapi.yaml --generate --base-url http://localhost:8080 ``` ## Fail CI when the contract breaks In CI, start the proxy, run the test suite through it, and stop it. On stopping it writes the report and exits with status 5 when the contract failed: ```sh apigenie-server openapi.yaml --proxy-to http://localhost:8080 --port 4010 \ --report contract.json --report-junit contract.xml --fail-on errors --min-coverage 80 & API_URL=http://localhost:4010 npm test curl -X POST http://localhost:4010/__apigenie/shutdown wait $! ``` `--fail-on errors` fails on any error-level violation, and `--min-coverage 80` when the tests leave more than a fifth of the document untested. Most CI systems show the JUnit report, one test case per operation, next to the other test results. ## Record once, replay in tests Integration tests that call other services are slow and flaky. `--record DIR` writes every exchange of a session to a directory, and `--replay DIR` answers from it later, so the tests run without the real services: ```sh apigenie-server openapi.yaml --proxy-to https://api.example.com --record sessions/checkout apigenie-server openapi.yaml --replay sessions/checkout ``` See [session recording](/docs/reference/server#session-recording) for what is recorded and how secrets are kept out of the files. ## Next steps - [Server reference](/docs/reference/server): stubs, scripts, the document store, several services and TLS. - [apigenie test](/docs/reference/cli/test): `.http` files, assertions and scripts. - [Mock and test](/use-cases/mock-and-test): what the server covers. --- # Publish API documentation Source: https://apigenie.pl/docs/guides/publish-api-documentation Documentation written by hand falls behind the API it describes. Built from the contract, it changes when the API does. This guide builds a documentation site from an OpenAPI document and an AsyncAPI one, adds translations and changelogs, publishes it, and shows how to put a reference into an existing page instead. Swagger 2.0, GraphQL and Protobuf files work the same way. You need the CLI and Node.js installed; the [Quickstart](/docs/quickstart) shows how. ## Build a site Use your own files, or download the Carpet Express [OpenAPI definition](/samples/carpet-express.openapi-31.yaml) as `openapi.yaml` and [AsyncAPI definition](/samples/carpet-express.asyncapi.yaml) as `events.asyncapi.yaml`. These describe a fictional service; their sample endpoints aren't live. ```sh apigenie-generator docs openapi.yaml events.asyncapi.yaml -o docs cd docs npm install npm run build ``` The first command writes a *docs bundle* into `docs/`: the resolved documents and a manifest, with an npm project that depends on the [doc components](/tools/doc-components). The build renders it into `docs/dist/`: a page per API, operation and schema, with navigation, search and Try it out. Every page is prerendered, so it reads without JavaScript and search engines see all of it. Serve `docs/dist/` from any static host. To host the site under a path of your domain, pass the path and the origin to the build, which also writes them into the sitemap: ```sh npm run build -- --base /docs/ --origin https://example.com ``` ## Describe the site in the configuration Instead of listing the files on the command line, list them in `apigenie.yaml`, with a title for the site and for each API: ```yaml version: '1.0' commands: generate: docs: title: Acme APIs documents: - file: api/openapi.yaml id: shop title: Shop API - file: api/events.asyncapi.yaml id: events ``` ```sh apigenie-generator docs -o docs ``` The `id` of a document is its path on the site, so links to it survive when the file moves. ## Add the changelog of each API With `--changelog`, each API gets a page of the changes its consumers care about, built from the git history of its file as [`apigenie changelog`](/docs/reference/cli/changelog) builds it. Configure the revisions to list releases instead of commits: ```yaml commands: generate: docs: changelog: revisions: tags tag_pattern: 'v*' unreleased: true ``` ## Translate the documentation `apigenie i18n` extracts the texts of a document for translators, and the generator builds a copy of the site per locale, with a locale switcher: ```sh apigenie i18n extract openapi.yaml --output i18n-en.json # translators turn i18n-en.json into i18n-pl.json, keeping the keys apigenie i18n check openapi.yaml --translations i18n-pl.json apigenie-generator docs openapi.yaml --i18n pl=i18n-pl.json -o docs ``` `i18n check` fails until every text is translated, which suits CI. When the document changes, `i18n sync` keeps the translations of the texts that didn't change and lists the new ones; see [i18n](/docs/reference/cli/i18n). ## Or put a reference into your own page When the API documentation belongs inside a portal or a product site, use the doc components directly. They are standard custom elements, so they work in plain HTML, a CMS or any framework: ```sh npm install @mykulyak/apigenie-docs ``` ```html ``` `` renders a whole reference. For a page of your own design, use its parts: ``, ``, `` and the others, inside an `` that loads the document once. The [doc components](/tools/doc-components) page shows them, and their look follows CSS custom properties and `::part()`. ## Next steps - [apigenie-generator docs](/docs/reference/generator/docs): every option and setting, and the format of the bundle. - [Doc components](/tools/doc-components): the elements and how to theme them. - [Publish API docs](/use-cases/api-docs): what the documentation covers. --- # Edit an API in forms Source: https://apigenie.pl/docs/guides/edit-an-api-in-forms Not everyone who shapes an API wants to write the YAML of its standard. The API Genie editor shows OpenAPI 3.0, OpenAPI 3.1 and AsyncAPI 3.0 documents as forms, and changes only the lines an edit has to, so a change made in a form reviews like one made by hand. This guide edits an API in the web editor, which runs in the browser with nothing to install. ## Open a folder To try the editor without using your own API, download the [Carpet Express OpenAPI 3.1 example](/samples/carpet-express.openapi-31.yaml) into a folder. Open [the web editor](/editor/) and choose *Open folder…* on the folder that holds your API files, such as a clone of the repository. Chrome and Edge save the changes in place; in other browsers, drop the folder on the page, and the changes are saved as downloads. The editor works on the folder on your computer. There is no account and no server, and nothing leaves your machine unless you send a request with Try it out. ## Find your way around - The **API tree** on the left shows the operations, channels, schemas and other parts of the API, with search, filters and a badge where a part has problems. - The **form** in the middle edits the selected part. What a form doesn't model is shown as raw YAML in its Raw section, never dropped. - The **Code** tab shows the text of the file, and follows the selection. - The **Preview** shows the documentation of the selected part as readers will see it, with Try it out for OpenAPI operations. References are followed across every file of the folder. Renaming or moving a schema, extracting a part into a component or inlining it updates every file that refers to it. ## Fix the problems as you go The editor checks the document as you type: the problems appear under the fields they belong to and in the tree. Besides the standard's own checks, it applies the [linter rules](/docs/linter) of the repository and your own Spectral rulesets, with a quick fix where a rule has a safe one: the rules `apigenie lint` applies in CI. ## Review the change before you save The **Changes** panel lists the API changes since the last save, each rated breaking, non-breaking or non-functional as [`apigenie diff`](/docs/reference/cli/diff) rates them, and shows the text diff. Save when the change is what you meant; the unsaved files are counted in the toolbar. Comments, key order, anchors and quoting stay as they were. Opening and saving a file without an edit gives back the same bytes, so the pull request shows only what you changed. ## Bring in older documents *Import* converts Swagger 2.0 documents, AsyncAPI 2.x documents and Postman collections, and shows the conversion as a diff before it is written. ## Other hosts The same editor runs on the desktop, with a Git panel and a mock server started from the toolbar, and in VS Code, where *Open with API Genie Editor* opens any OpenAPI or AsyncAPI file. Both ship with 1.0.0; [the editor's hosts](/docs/reference/editor/hosts) lists what each host offers. ## Next steps - [Editor](/tools/editor): what the editor does. - [Lint and format API descriptions](/docs/guides/lint-and-format-api-descriptions): the rules the editor applies. - [Catch breaking changes in CI](/docs/guides/catch-breaking-changes-in-ci): the check that follows the pull request. --- # Catalog the APIs of a repository Source: https://apigenie.pl/docs/guides/catalog-the-apis-of-a-repository A repository with more than a few APIs raises questions nobody can answer quickly: which APIs are there, which version is which, and who to ask about each. `apigenie-catalog` answers them from the repository itself, with nothing to host and nothing to upload. This guide lists the APIs of a repository, records their names and owners, and keeps the catalog current. You need the CLI installed; the [Quickstart](/docs/quickstart) shows how. `apigenie-catalog` is [experimental](/docs/reference/cli/overview#experimental-commands): release tags, pinned consumers and catalogs that span several repositories will add options. ## List the specifications Run it anywhere in the repository; no configuration is needed: ```sh apigenie-catalog list ``` ```text NAME FORMAT VERSION PATH COMMIT order-events AsyncAPI 3.0.0 2.0.0 apis/events.json 96175d8 2026-10-12 orders-api OpenAPI 3.1.0 1.2.0 apis/orders/openapi.yaml 4d0c2e1 2026-10-14 schema GraphQL - graphql/schema.graphql 96175d8 2026-10-12 acme-pricing-v1 Protobuf proto3 - proto/pricing.proto 96175d8 2026-10-12 ``` It looks at the files Git tracks, so build output and ignored files stay out, and finds OpenAPI, AsyncAPI and Swagger documents by their content, every `.proto` file, and GraphQL files with type definitions. Each row has a name derived from the specification (its title, its Protobuf package or its file name), its format and version, its path and its last commit. Give a directory to list part of the repository: `apigenie-catalog list apis/`. ## Give each API a name and an owner A derived name changes when the title or the file does, and an owner can't be derived at all. `apigenie-catalog init` writes a `catalog` section into `apigenie.yaml`, with one entry per specification: ```sh apigenie-catalog init ``` ```yaml catalog: specs: - name: order-events path: apis/events.json owner: null - name: orders-api path: apis/orders/openapi.yaml owner: null ``` Fill in the owners, and change a name where the derived one isn't right. From then on the name stays when the file moves: change the `path` and keep the `name`. Commit the file: a new owner is now a change that is reviewed like any other. Leave files out of the catalog, such as test fixtures, with patterns under `catalog.exclude`; see [the catalog section](/docs/reference/cli/configuration#catalog) of the configuration. ## Keep it current Run `init` again after adding specifications. New ones are added, the entries you edited stay as they are, and the entries whose files are gone are reported; `--prune` removes them: ```text $ apigenie-catalog init Updated /home/me/specs/apigenie.yaml: 2 added, 4 kept, 1 not found added apis/billing.yaml added apis/orders-v2.yaml not found legacy/swagger.yaml Remove the entries of the files that are gone with --prune. ``` To catch a specification added without an entry, let CI run `init --dry-run`, which prints the file instead of writing it, and compare the result with the committed `apigenie.yaml`. ## Use the catalog in your tools `--json` prints the catalog with a [published schema](https://apigenie.pl/schemas/apigenie-catalog-report.json): the name, title, format, version, path and owner of each specification, its last commit, and whether the catalog section lists it. Scripts can lint or diff every API of the repository from it, or feed a dashboard of owners: ```sh apigenie-catalog list --json > catalog.json ``` ## Next steps - [apigenie-catalog](/docs/reference/catalog): every option and the JSON report. - [changelog](/docs/reference/cli/changelog): the history of each specification from git. - [Lint and format API descriptions](/docs/guides/lint-and-format-api-descriptions): the same rules for every API the catalog lists. --- # Work with AI agents Source: https://apigenie.pl/docs/guides/work-with-ai-agents A coding agent edits an API description the way it edits code: quickly, and without the context a reviewer has. The checks that protect a repository from a hurried person protect it from an agent too, provided the agent can run them and read their answers. This guide puts `apigenie lint`, `apigenie format` and `apigenie diff` in an agent's loop, tells the agent about them, and keeps the same checks in CI for the pull request the agent opens. You need the CLI installed where the agent runs; the [Quickstart](/docs/quickstart) shows how. The commands below work with OpenAPI, Swagger 2.0, AsyncAPI, Protobuf and GraphQL files. ## What an agent needs from a tool - **An exit status that means one thing.** The four binaries share [one table](/docs/reference/cli/overview#exit-status): 0 is success, 1 is findings (lint errors, breaking changes, files to reformat), 2 is a usage or input error. The 1.0 compatibility promise freezes it for `apigenie`, `apigenie-generator` and `apigenie-server`; the catalog is experimental. - **Output it can parse.** `lint`, `diff`, `format`, `test`, `apigenie-catalog list` and the server's contract report print JSON on request, with a `formatVersion` where the shape may grow. - **Settings in a file it can check.** `apigenie.yaml` is validated against [its JSON Schema](/docs/reference/cli/configuration) before any command reads it, so a wrong key is an error with a line, not a silently ignored setting. - **Documentation it can read.** Guides, reference pages and linter rules have Markdown copies, indexed in [`llms.txt`](/llms.txt) and collected in [`llms-full.txt`](/llms-full.txt). ## Tell the agent about the commands Most agents read an instructions file at the root of the repository (`AGENTS.md`, `CLAUDE.md` or the equivalent of your tool). Three lines are enough: ```markdown ## API descriptions The files under `apis/` are OpenAPI and AsyncAPI documents. After changing one: 1. `apigenie lint --fix`, then fix what is left; the command exits with 1 while errors remain. 2. `apigenie format `. 3. `apigenie diff `: a status of 1 means a breaking change. Say so in the pull request, and do not remove the breaking change to make the check pass without asking. ``` The last sentence matters: an agent told only to make the check pass will make it pass. ## The loop Each step prints something the agent can act on, and exits with a status it can test. ```sh apigenie lint apis/orders.yaml --fix --formatter json > lint.json; echo $? ``` ```text 1 ``` With `--formatter json`, each remaining issue has its file, line, column, severity, rule and message, and each rule links its page under [the linter rules](/docs/linter), which says why the rule exists and what a fix looks like. `--fix` has already applied the safe fixes and kept the comments of the file. ```sh apigenie format apis/orders.yaml ``` The formatter gives the same file for the same input, whoever ran it, so a formatted file is a small diff: only the lines the agent meant to change. The first formatting pass can change many lines; commit it on its own, before the API changes, so reviewers can tell the two apart. ```sh git show main:apis/orders.yaml > /tmp/orders.main.yaml apigenie diff /tmp/orders.main.yaml apis/orders.yaml --report status --formatter json ``` ```json { "formatVersion": 1, "status": "non-breaking" } ``` `--report status` is the one-word answer; `--report changelog` lists every change with its rating, and `--report detailed` every property that changed, for the pull request's description. ## Keep the same checks in CI The agent's checks are advisory until CI runs them on the pull request. The [GitHub Action](/docs/ci/github-action) lints the changed files and compares each with the base of the pull request, and shows the issues on the changed lines, where the reviewer and the agent both see them. [Catch breaking changes in CI](/docs/guides/catch-breaking-changes-in-ci) sets it up. ## Check the code the agent generates When an agent writes the server too, put `apigenie-server` in front of it as a validating proxy: every response is checked against the contract, and the run fails on a violation: ```sh apigenie-server apis/orders.yaml --proxy-to http://localhost:8080 --port 4010 \ --report contract.json --fail-on errors & API_URL=http://localhost:4010 npm test curl -X POST http://localhost:4010/__apigenie/shutdown wait $! ``` [Mock and contract-test an API](/docs/guides/mock-and-contract-test-an-api) explains the report and the coverage gate. ## Give an agent the API as tools `apigenie-generator server mcp` writes an MCP server with a tool per operation of an OpenAPI document, so an agent can call the API through the Model Context Protocol instead of writing HTTP by hand. It is [experimental](/docs/reference/cli/overview#experimental-commands): MCP and its SDK are still moving, and the tool names and result shapes can change with them. ```sh apigenie-generator server mcp apis/orders.yaml -o orders-mcp ``` ## Next steps - [Lint and format API descriptions](/docs/guides/lint-and-format-api-descriptions): the rulesets and your own rules. - [Exit statuses](/docs/reference/cli/overview#exit-status) and [the compatibility promise](/docs/reference/cli/compatibility): what a script can rely on. - [`llms.txt`](/llms.txt): the documentation, for the agent to read. --- # Overview Source: https://apigenie.pl/docs/reference/cli/overview API Genie CLI streamlines Web API development by providing a command-line interface for managing, testing and generating artifacts from OpenAPI, Swagger, AsyncAPI, Protobuf and GraphQL descriptions. From 1.0.0 the commands, options, configuration keys, exit statuses and JSON outputs follow semantic versioning; [Compatibility](/docs/reference/cli/compatibility) says what that covers. ## Key functionality | Subcommand | Description | | ---------- | ----------- | | [bundle](/docs/reference/cli/bundle) | Combines a schema or a Protobuf file and the files it uses into one file | | [changelog](/docs/reference/cli/changelog) | Builds the changelog of a spec from its Git history | | [convert](/docs/reference/cli/convert) | Converts Swagger 2.0 specs to OpenAPI 3.2, WSDL 1.1 to OpenAPI 3.1, and JSON Schema 2019-09/2020-12 to draft-07, draft-04 or the OpenAPI 3.0 dialect | | [diff](/docs/reference/cli/diff) | Compares OpenAPI, Swagger or AsyncAPI schemas, or Protobuf or GraphQL files, and detects breaking changes | | [evolve](/docs/reference/cli/evolve) | Plans safe API changes: an expand wave and a contract wave for a breaking OpenAPI change (experimental) | | [format](/docs/reference/cli/format) | Formats schemas to your team's standards | | [i18n](/docs/reference/cli/i18n) | Extracts texts for translation and applies translations | | [lint](/docs/reference/cli/lint) | Validates schemas for best practices | | [overlay](/docs/reference/cli/overlay) | Applies OpenAPI Overlays to schemas | | [test](/docs/reference/cli/test) | Runs requests against a live API and checks them against its contract | | [credits](/docs/reference/cli/credits), [license](/docs/reference/cli/license) | Show the licenses of API Genie and its dependencies | Commands marked *experimental* are outside the [1.0 compatibility promise](/docs/reference/cli/compatibility); see [Experimental commands](#experimental-commands). ## The four binaries API Genie installs four executables side by side, always of the same version: | Binary | What it does | | ------ | ------------ | | `apigenie` | Lints, formats, compares, bundles, converts and tests API documents (the commands above) | | [`apigenie-generator`](/docs/reference/generator) | Generates clients, servers, documentation, data and validation schemas from OpenAPI, Swagger, AsyncAPI and JSON Schema documents | | [`apigenie-server`](/docs/reference/server) | Serves a document as a mock server, or proxies a live API and checks the traffic against the contract | | [`apigenie-catalog`](/docs/reference/catalog) | Lists the API specifications of a repository and keeps their names and owners in `apigenie.yaml` (experimental) | The `--help` of each one ends with a line naming the others. `apigenie --version` prints the version on its first line and, below it, the versions of `apigenie-generator`, `apigenie-server` and `apigenie-catalog` when they are installed next to it; scripts that compare the version should read the first line: ```sh apigenie --version | head -n 1 ``` Code generation was `apigenie generate` before 0.9.0: `apigenie generate ...` and `apigenie mock ...` now exit with status 2 and print the command to run instead, e.g. `run: apigenie-generator client fetch api.yaml -o client`, and so does `apigenie catalog ...`, which points to `apigenie-catalog`. ## Exit status The four binaries share one table of exit statuses. The [1.0 compatibility promise](/docs/reference/cli/compatibility) freezes it: scripts and CI jobs can rely on these numbers. | Status | Meaning | | ------ | ------- | | 0 | Success | | 1 | Findings: `lint` found errors, `diff` found breaking changes, `format --check` found files to reformat, `i18n apply --strict` found unknown keys, `overlay --validate` found problems in an overlay | | 2 | A usage, input or unexpected error: bad or unknown options, an unknown command, a file that can't be read or isn't a valid document, a bad configuration file, or an internal error | | 3 | `apigenie-server`: the document can't be served | | 4 | `apigenie-server`: the address can't be used (the port is taken, ...) | | 5 | The contract or replay report failed: `apigenie-server` with `--fail-on` or `--min-coverage`, or with `--replay` when a recorded exchange was never replayed or a request matched none; `apigenie test` when a request or the contract gate failed | `apigenie-server` also exits with 130 when a second Ctrl+C stops it at once, as shells do for a process interrupted by a signal. An unknown command or subcommand prints the help of the command above it on the standard error. In 0.9 and earlier, errors exited with 255 or 1 depending on the command; see the Upgrading section of the changelog. ## Experimental commands These commands are outside the [1.0 compatibility promise](/docs/reference/cli/compatibility): their options and output can still change. Their `--help` and the first line they print (on the standard error, so that their output stays usable) say "(experimental, outside the 1.0 compatibility promise)". | Command | Why | | ------- | --- | | [`apigenie-catalog`](/docs/reference/catalog) | The first part of a catalog kept in git; release tags, pinned consumers and multi-repository catalogs will add settings | | [`apigenie convert`](/docs/reference/cli/convert) | Three sources (`--to openapi32` for Swagger 2.0, `--to openapi31` for WSDL 1.1, `--to draft7|draft4|openapi30` for JSON Schema); more sources and targets may change its options | | [`apigenie evolve plan`](/docs/reference/cli/evolve) | The first version of the planner: its recipes and `plan.yaml` will grow, and `evolve status` and `evolve next` will join it | | [`apigenie-generator schema zod`](/docs/reference/generator/schema-zod) | The generated schemas aren't yet checked against a live API (conformance Level 3) | | [`apigenie-generator server mcp`](/docs/reference/generator/server-mcp) | MCP and its SDK are still moving; tool names and result shapes can change with them | ## Continuous integration The [GitHub Action](/docs/ci/github-action) installs the CLI on GitHub Actions runners and lints, checks the formatting of, or diffs your files on every pull request. ## Supported file formats Input to most CLI commands are one or more OpenAPI schemas. All commands recognize schemas in JSON or YAML formats. ## Configuration files Apart from OpenAPI schemas, most CLI commands also accept input from configuration file. The file contains configuration for commands, based on best practices and guidelines in your organization. `apigenie`, `apigenie-generator`, `apigenie-server` and `apigenie-catalog` find the file themselves: `apigenie.yaml` (or `.yml`, `.json`, or the same names with a leading dot) in the current directory or a parent directory, up to the root of the Git repository. `--config FILE` uses another file instead, and `--no-config` none. The file is checked against its JSON Schema before any command reads it, so a misspelled setting is an error that names its line and JSON pointer. [`apigenie config`](/docs/reference/cli/config) prints the settings in effect and checks a file. See the [configuration](/docs/reference/cli/configuration) section for detailed description of this file. ### Editor support The JSON Schema of the file is published at `https://apigenie.pl/schemas/apigenie-config/latest.json`, and the schema of each release at `https://apigenie.pl/schemas/apigenie-config/.json`. Editors that use the YAML language server (VS Code with the Red Hat YAML extension, Neovim, JetBrains IDEs, Zed) complete and check the file when it starts with: ```yaml # yaml-language-server: $schema=https://apigenie.pl/schemas/apigenie-config/latest.json version: '1.0' commands: lint: extends: [recommended] ``` A standalone mock server file (`apigenie-server.yaml`) has its own schema, `https://apigenie.pl/schemas/apigenie-server-config/latest.json`. The API Genie extension for VS Code associates both schemas with `apigenie.yaml`, `apigenie-server.yaml` and their variants on its own. To pin the schema to the version you run, use `apigenie config schema --out apigenie.schema.json` and point the comment at that file. ## Telemetry API Genie CLI can record anonymous usage data locally to help debug the tool and identify areas for improvement. Local telemetry logging is disabled by default. The telemetry data is limited to technical details about how you use the CLI, such as: - Version of the CLI - Commands used - Performance metrics for internal tasks (e.g., time taken to parse YAML files) - Errors encountered by users (anonymized) **No personal information is collected**. This includes usernames, API keys, or any data specific to your API interactions. The local log can help with: - Identifying common usage patterns and workflows - Debugging and resolving technical issues - Prioritizing feature development based on user needs To opt in to a local telemetry log, set `APIGENIE_TELEMETRY_FILE` to the desired file path before running `apigenie`. Relative paths are resolved against the current directory. The file is created or truncated on each invocation; its parent directory must already exist. When the variable is unset or empty, no file telemetry is initialized and the CLI does not create or truncate `.apigenie.telemetry`. Telemetry is stored locally, not sent to a remote service. --- # Configuration Source: https://apigenie.pl/docs/reference/cli/configuration Most of API Genie CLI commands use configuration files. This file stores default parameters for API Genie CLI commands specific to your organization. ## Structure Configuration can be stored in JSON or YAML files. Below is a sample illustrating its general structure: ```yaml # yaml-language-server: $schema=https://apigenie.pl/schemas/apigenie-config/latest.json version: '1.0' openapi: custom_formats: - name: x-id type: integer catalog: ... commands: format: ... lint: ... diff: ... generate: client_csharp: ... client_elixir: ... client_fetch: ... client_go: ... client_java: ... client_kafkajs: ... client_python: ... client_ws: ... data: ... docs: ... schema_zod: ... server_absinthe: ... server_aspnet: ... server_express: ... server_fastapi: ... server_faststream: ... server_go: ... server_kafkajs: ... server_mcp: ... server_orpc: ... server_phoenix: ... server_spring: ... asyncapi: ... server_ws: ... ... ``` ## Where the file is found When no `--config` is given, `apigenie`, `apigenie-generator`, `apigenie-server` and `apigenie-catalog` use the first of `apigenie.yaml`, `apigenie.yml`, `apigenie.json`, `.apigenie.yaml`, `.apigenie.yml` and `.apigenie.json` found in the current directory or one of its parents, stopping at the root of the Git repository (the directory holding `.git`). `apigenie-server` looks for `apigenie-server.yaml`, then `apigenie-mock.yaml` (and their variants) in the current directory first. `--config FILE` uses FILE instead of the file found this way: the two are never merged. `--no-config` uses no file at all, so the commands run with their defaults. ## Validation Every file is checked against the JSON Schema of the configuration (draft-7) before any command reads it, whichever command loads it. A setting the schema doesn't know, a value of the wrong type or one outside its allowed values is an error that names the line, the column and the JSON pointer of the value, with a suggestion for a misspelled name: ```text apigenie.yaml:12:7: /commands/lint/rulez: unknown property 'rulez'; did you mean 'rules'? ``` `version` must be the string `'1.0'`: quote it in YAML, where `1.0` is a number. Sections of commands without settings (`bundle`, `convert`, `overlay`, `i18n`, `test`) are errors too; `changelog` uses the `diff` section. Use [`apigenie config check`](/docs/reference/cli/config) to check a file, and `apigenie config schema` to print the schema for an editor (see [editor support](/docs/reference/cli/overview#editor-support)). ### openapi This section holds settings which are related to OpenAPI schemas, and do not depend on specific CLI command. ### openapi.custom_formats API Genie knows the formats of the [OpenAPI format registry](https://spec.openapis.org/registry/format/). This section declares the formats your schemas use beyond it, so that they are accepted as they are. If present, this section must be an array of objects. Each object must contain the `name` and `type` properties. `name` specifies the format name, and must be unique within the specification. `type` specifies the JSON Schema type to which the format can be applied (`string`, `integer`, `number`, `boolean`, `object` or `array`). ```yaml openapi: custom_formats: - {name: currency-code, type: string} # the registry's decimal is a string or a number; here it is always a string - {name: decimal, type: string} ``` A format of the registry listed here takes the type given and is no longer checked. `apigenie-server` uses these formats when it validates requests and responses, and the linters' format rules accept them. The [Formats](/docs/reference/cli/formats) page lists the registry's formats and what API Genie does with each. ### catalog The specifications of the repository's API catalog, for [`apigenie-catalog`](/docs/reference/catalog), which writes it with `apigenie-catalog init`. Every setting is optional: specifications found in the repository but not listed here are still in the catalog, with derived names. ```yaml catalog: exclude: - "**/examples/**" specs: - name: orders-api path: apis/orders/openapi.yaml owner: team-commerce ``` | Setting | Description | | ------- | ----------- | | `exclude` | Glob patterns of files left out of the catalog, relative to the directory of the configuration file: `*` matches within a directory, `**` across directories. Files ignored by git are always left out. | | `specs` | One entry per specification file. | | `specs[].path` | Required. The file, relative to the directory of the configuration file, with `/` separators. | | `specs[].name` | The name in the catalog, which stays when the file moves: lower-case letters, digits and dashes. Derived from `info.title`, the Protobuf package or the file name when left out. | | `specs[].owner` | The team or person owning the specification, or `null`. | ### commands This section contains settings for CLI commands. Each command has a dedicated entry in this section, those name mimicks the name of the command. ### commands.format Configuration used by the `format` command. OpenAPI, Swagger 2.0, AsyncAPI, Protobuf and GraphQL files are formatted with its `openapi`, `swagger`, `asyncapi`, `protobuf` and `graphql` sections. For more details, see [format configuration](/docs/reference/cli/format#configuration) section. ### commands.lint Configuration used by the `lint` command. OpenAPI documents are linted with its top-level settings, Swagger 2.0, AsyncAPI, Protobuf and GraphQL files with its `swagger`, `asyncapi`, `protobuf` and `graphql` sections. For more details, see [lint configuration](/docs/reference/cli/lint#configuration) section. ### commands.diff Configuration used by the `diff` and `changelog` commands. AsyncAPI documents are compared with its `asyncapi` section, Protobuf files with its `protobuf` section, Swagger documents with its `swagger` section, and GraphQL files with its `graphql` section. For more details, see [diff configuration](/docs/reference/cli/diff#configuration) section. ### commands.mock Configuration of the mock server, `apigenie-server`: the document, the address, responses, validation, security, network simulation, scenarios, AsyncAPI channels, gRPC services (`grpc`: proto paths, stub files, limits, reflection, health and the proxy), the validating proxy (`proxy`, whose `target` may be `https://` or `wss://`), TLS (`tls`: the certificate and key to serve HTTPS with, `client_ca` to require client certificates, the authorities trusted for upstreams, `insecure`, and `client_cert` and `client_key` to present to them), scripts, the admin API and the log. The same keys at the top level of a file of its own (`apigenie-server.yaml`, or `apigenie-mock.yaml`) work too. For more details, see [server configuration](/docs/reference/server#configuration). ### commands.generate Entries in this section store configuration for specific code generators of [`apigenie-generator`](/docs/reference/generator), which reads the file found in the repository or given with `--config`. Every section of a target of OpenAPI or AsyncAPI documents (not `docs`, `server_absinthe` or `server_elixir_grpc`) also takes `overlay`: a list of [overlays](/docs/reference/generator#overlays) applied to the input before generating, with paths relative to the configuration file. The `--overlay` arguments apply after them. ```yaml version: '1.0' commands: generate: server_spring: overlay: - overlays/spring.overlay.yaml ``` | Key | Configuration details (link) | | ---------------------------------- | -------------------------------------------------------------------------------------------------------- | | `commands.generate.data` | [`apigenie-generator data` configuration](/docs/reference/generator/data#configuration) | | `commands.generate.client_csharp` | [`apigenie-generator client csharp` configuration](/docs/reference/generator/client-csharp#configuration) | | `commands.generate.client_csharp.asyncapi` | [`apigenie-generator client csharp` (AsyncAPI) configuration](/docs/reference/generator/client-csharp-asyncapi#configuration) | | `commands.generate.client_elixir` | [`apigenie-generator client elixir` configuration](/docs/reference/generator/client-elixir#configuration) | | `commands.generate.client_elixir.asyncapi` | [`apigenie-generator client elixir` (AsyncAPI) configuration](/docs/reference/generator/client-elixir-asyncapi#configuration) | | `commands.generate.client_elixir.protobuf` | [`apigenie-generator client elixir` (Protobuf) configuration](/docs/reference/generator/client-elixir-protobuf#configuration) | | `commands.generate.client_fetch` | [`apigenie-generator client fetch` configuration](/docs/reference/generator/client-fetch#configuration) | | `commands.generate.client_go` | [`apigenie-generator client go` configuration](/docs/reference/generator/client-go#configuration) | | `commands.generate.client_go.asyncapi` | [`apigenie-generator client go` (AsyncAPI) configuration](/docs/reference/generator/client-go-asyncapi#configuration) | | `commands.generate.client_java` | [`apigenie-generator client java` configuration](/docs/reference/generator/client-java#configuration) | | `commands.generate.client_java.asyncapi` | [`apigenie-generator client java` (AsyncAPI) configuration](/docs/reference/generator/client-java-asyncapi#configuration) | | `commands.generate.client_python` | [`apigenie-generator client python` configuration](/docs/reference/generator/client-python#configuration) | | `commands.generate.client_python.asyncapi` | [`apigenie-generator client python` (AsyncAPI) configuration](/docs/reference/generator/client-python-asyncapi#configuration) | | `commands.generate.server_express` | [`apigenie-generator server express` configuration](/docs/reference/generator/server-express#configuration) | | `commands.generate.docs` | [`apigenie-generator docs` configuration](/docs/reference/generator/docs#configuration) | | `commands.generate.server_fastapi` | [`apigenie-generator server fastapi` configuration](/docs/reference/generator/server-fastapi#configuration) | | `commands.generate.server_faststream` | [`apigenie-generator server faststream` configuration](/docs/reference/generator/server-faststream#configuration) | | `commands.generate.server_go` | [`apigenie-generator server go` configuration](/docs/reference/generator/server-go#configuration) | | `commands.generate.server_go.asyncapi` | [`apigenie-generator server go` (AsyncAPI) configuration](/docs/reference/generator/server-go-asyncapi#configuration) | | `commands.generate.server_mcp` | [`apigenie-generator server mcp` configuration](/docs/reference/generator/server-mcp#configuration) | | `commands.generate.server_orpc` | [`apigenie-generator server orpc` configuration](/docs/reference/generator/server-orpc#configuration) | | `commands.generate.server_phoenix` | [`apigenie-generator server phoenix` configuration](/docs/reference/generator/server-phoenix#configuration) | | `commands.generate.server_phoenix.asyncapi` | [`apigenie-generator server phoenix` (AsyncAPI) configuration](/docs/reference/generator/server-phoenix-asyncapi#configuration) | | `commands.generate.server_elixir_grpc` | [`apigenie-generator server elixir-grpc` configuration](/docs/reference/generator/server-elixir-grpc#configuration) | | `commands.generate.server_spring` | [`apigenie-generator server spring` configuration](/docs/reference/generator/server-spring#configuration) | | `commands.generate.server_spring.asyncapi` | [`apigenie-generator server spring` (AsyncAPI) configuration](/docs/reference/generator/server-spring-asyncapi#configuration) | | `commands.generate.server_absinthe` | [`apigenie-generator server absinthe` configuration](/docs/reference/generator/server-absinthe#configuration) | | `commands.generate.server_aspnet` | [`apigenie-generator server aspnet` configuration](/docs/reference/generator/server-aspnet#configuration) | | `commands.generate.server_aspnet.asyncapi` | [`apigenie-generator server aspnet` (AsyncAPI) configuration](/docs/reference/generator/server-aspnet-asyncapi#configuration) | | `commands.generate.client_kafkajs` | [`apigenie-generator client kafkajs` configuration](/docs/reference/generator/client-kafkajs#configuration) | | `commands.generate.server_kafkajs` | [`apigenie-generator server kafkajs` configuration](/docs/reference/generator/server-kafkajs#configuration) | | `commands.generate.client_ws` | [`apigenie-generator client ws` configuration](/docs/reference/generator/client-ws#configuration) | | `commands.generate.server_ws` | [`apigenie-generator server ws` configuration](/docs/reference/generator/server-ws#configuration) | | `commands.generate.schema_zod` | [`apigenie-generator schema zod` configuration](/docs/reference/generator/schema-zod#configuration) | --- # Formats Source: https://apigenie.pl/docs/reference/cli/formats API Genie follows the [OpenAPI format registry](https://spec.openapis.org/registry/format/), the list of `format` values the OpenAPI Initiative keeps, with the JSON types each applies to. The same list drives every part of API Genie (docs/adrs/166): - **Validation.** [`apigenie-server`](/docs/reference/server) checks the formats of requests and responses (mock and `--proxy-to`), as do the contract checks of `apigenie test`. A format applies to its JSON types only: `format: int32` checks numbers, and leaves a string to `type`. `validation.formats: false` turns format checks off. - **Lint.** [schema-format-type-mismatch](/docs/linter/openapi/rules/schema-format-type-mismatch) checks that a schema's `type` is one of its format's JSON types, [schema-format-deprecated](/docs/linter/openapi/rules/schema-format-deprecated) reports the deprecated formats with their replacement, and [schema-format-unknown](/docs/linter/openapi/rules/schema-format-unknown) (in the `strict` ruleset) reports formats that are neither in the registry nor declared. - **Code and data generation.** The generators map formats to the language's types (the table says which), and `apigenie-generator data` and the server's generated responses make values that pass the checks. - **Documentation.** The documentation components link a registry format to its registry page. Formats of your own are declared in [`openapi.custom_formats`](/docs/reference/cli/configuration#openapicustom_formats), with the JSON type they apply to; they are accepted as they are. An entry naming a registry format replaces that format's type and turns its check off. ## The registry | Format | JSON types | Validated | Generated code | Notes | | ------ | ---------- | --------- | -------------- | ----- | | [`base64url`](https://spec.openapis.org/registry/format/base64url.html) | string | yes | string | **Deprecated** from OpenAPI 3.1: use `contentEncoding: base64url`. Binary data encoded as a url-safe string as defined in RFC4648 | | [`binary`](https://spec.openapis.org/registry/format/binary.html) | string | no | bytes or a file | **Deprecated** from OpenAPI 3.1: use `contentMediaType`. any sequence of octets | | [`byte`](https://spec.openapis.org/registry/format/byte.html) | string | yes | bytes | **Deprecated** from OpenAPI 3.1: use `contentEncoding: base64`. base64 encoded data as defined in RFC4648 | | [`char`](https://spec.openapis.org/registry/format/char.html) | string | yes | string | A single character | | [`commonmark`](https://spec.openapis.org/registry/format/commonmark.html) | string | no | string | commonmark-formatted text | | [`date`](https://spec.openapis.org/registry/format/date.html) | string | yes | date | date as defined by full-date - RFC3339 | | [`date-time`](https://spec.openapis.org/registry/format/date-time.html) | string | yes | date-time with offset | date and time as defined by date-time - RFC3339 | | [`date-time-local`](https://spec.openapis.org/registry/format/date-time-local.html) | string | yes | local date-time (Java `LocalDateTime`, C# `DateTime`, Python `NaiveDatetime`) | RFC3339 date-time without the timezone component | | [`decimal`](https://spec.openapis.org/registry/format/decimal.html) | string, number | yes | decimal (Java `BigDecimal`, C# `decimal`, Python `Decimal`) | A fixed point decimal number of unspecified precision and range | | [`decimal128`](https://spec.openapis.org/registry/format/decimal128.html) | string, number | yes | decimal (Java `BigDecimal`, C# `decimal`, Python `Decimal`) | A decimal floating-point number with 34 significant decimal digits | | [`double`](https://spec.openapis.org/registry/format/double.html) | number | yes | double-precision float | double precision floating point number | | [`double-int`](https://spec.openapis.org/registry/format/double-int.html) | number | yes | 64-bit integer | an integer that can be stored in an IEEE 754 double-precision number without loss of precision | | [`duration`](https://spec.openapis.org/registry/format/duration.html) | string | yes | duration (Python `timedelta`) | duration as defined by duration - RFC3339 | | [`email`](https://spec.openapis.org/registry/format/email.html) | string | yes | string (FastAPI `EmailStr`) | An email address as defined as Mailbox in RFC5321 | | [`float`](https://spec.openapis.org/registry/format/float.html) | number | yes | single-precision float | single precision floating point number | | [`hostname`](https://spec.openapis.org/registry/format/hostname.html) | string | yes | string | A host name as defined by RFC1123 | | [`html`](https://spec.openapis.org/registry/format/html.html) | string | no | string | HTML-formatted text | | [`http-date`](https://spec.openapis.org/registry/format/http-date.html) | string | yes | string | date and time as defined by HTTP-date - RFC7231 | | [`idn-email`](https://spec.openapis.org/registry/format/idn-email.html) | string | yes | string | An email address as defined as Mailbox in RFC6531 | | [`idn-hostname`](https://spec.openapis.org/registry/format/idn-hostname.html) | string | yes | string | An internationalized host name as defined by RFC5890 | | [`int16`](https://spec.openapis.org/registry/format/int16.html) | number | yes | integer (C# `short`, Go `int16`) | signed 16-bit integer | | [`int32`](https://spec.openapis.org/registry/format/int32.html) | number | yes | integer | signed 32-bit integer | | [`int64`](https://spec.openapis.org/registry/format/int64.html) | number, string | yes | 64-bit integer | signed 64-bit integer | | [`int8`](https://spec.openapis.org/registry/format/int8.html) | number | yes | integer (C# `sbyte`, Go `int8`) | signed 8-bit integer | | [`ipv4`](https://spec.openapis.org/registry/format/ipv4.html) | string | yes | string (FastAPI `IPv4Address`) | An IPv4 address as defined as dotted-quad by RFC2673 | | [`ipv4-cidr`](https://spec.openapis.org/registry/format/ipv4-cidr.html) | string | yes | string | An IPv4 address in CIDR notation | | [`ipv6`](https://spec.openapis.org/registry/format/ipv6.html) | string | yes | string (FastAPI `IPv6Address`) | An IPv6 address as defined by RFC4673 | | [`ipv6-cidr`](https://spec.openapis.org/registry/format/ipv6-cidr.html) | string | yes | string | An IPv6 address in CIDR-style notation | | [`iri`](https://spec.openapis.org/registry/format/iri.html) | string | yes | string | A Internationalized Resource Identifier as defined in RFC3987 | | [`iri-reference`](https://spec.openapis.org/registry/format/iri-reference.html) | string | yes | string | A Internationalized Resource Identifier as defined in RFC3987 | | [`json-pointer`](https://spec.openapis.org/registry/format/json-pointer.html) | string | yes | string | A JSON string representation of a JSON Pointer as defined in RFC6901 | | [`language`](https://spec.openapis.org/registry/format/language.html) | string | yes | string | language tag as defined in RFC5646 | | [`media-range`](https://spec.openapis.org/registry/format/media-range.html) | string | yes | string | A media type as defined by the media-range ABNF production in RFC9110. | | [`password`](https://spec.openapis.org/registry/format/password.html) | string | no | string | a string that hints to obscure the value. | | [`regex`](https://spec.openapis.org/registry/format/regex.html) | string | yes | string | A regular expression as defined in ECMA-262 | | [`relative-json-pointer`](https://spec.openapis.org/registry/format/relative-json-pointer.html) | string | yes | string | A JSON string representation of a relative JSON Pointer as defined in draft RFC 01 | | [`sf-binary`](https://spec.openapis.org/registry/format/sf-binary.html) | string | yes | string | structured fields byte sequence as defined in RFC8941 | | [`sf-boolean`](https://spec.openapis.org/registry/format/sf-boolean.html) | string | yes | string | structured fields boolean as defined in RFC8941 | | [`sf-decimal`](https://spec.openapis.org/registry/format/sf-decimal.html) | number | yes | string | structured fields decimal as defined in RFC8941 | | [`sf-integer`](https://spec.openapis.org/registry/format/sf-integer.html) | number | yes | 64-bit integer | structured fields integer as defined in RFC8941 | | [`sf-string`](https://spec.openapis.org/registry/format/sf-string.html) | string | yes | string | structured fields string as defined in RFC8941 | | [`sf-token`](https://spec.openapis.org/registry/format/sf-token.html) | string | yes | string | structured fields token as defined in RFC8941 | | [`time`](https://spec.openapis.org/registry/format/time.html) | string | yes | time | time as defined by full-time - RFC3339 | | [`time-local`](https://spec.openapis.org/registry/format/time-local.html) | string | yes | local time (Java `LocalTime`, C# `TimeOnly`, Python `time`) | RFC3339 time without the timezone component | | [`uint16`](https://spec.openapis.org/registry/format/uint16.html) | number | yes | integer (C# `ushort`, Go `uint16`) | unsigned 16-bit integer | | [`uint32`](https://spec.openapis.org/registry/format/uint32.html) | number | yes | integer (Java `Long`, C# `uint`) | unsigned 32-bit integer | | [`uint64`](https://spec.openapis.org/registry/format/uint64.html) | number, string | yes | integer (Java `BigInteger`, C# `ulong`) | unsigned 64-bit integer | | [`uint8`](https://spec.openapis.org/registry/format/uint8.html) | number | yes | integer (C# `byte`, Go `uint8`) | unsigned 8-bit integer | | [`unixtime`](https://spec.openapis.org/registry/format/unixtime.html) | number, string | yes | 64-bit integer (epoch seconds) | seconds since Jan 1st 1970 - IEEE1003.1-2024/POSIX.1-2024 | | [`uri`](https://spec.openapis.org/registry/format/uri.html) | string | yes | URI (Java, C#) | A Uniform Resource Identifier as defined in RFC3986 | | [`uri-reference`](https://spec.openapis.org/registry/format/uri-reference.html) | string | yes | string | A URI reference as defined in RFC3986 | | [`uri-template`](https://spec.openapis.org/registry/format/uri-template.html) | string | yes | string | A URI Template as defined in RFC6570 | | [`uuid`](https://spec.openapis.org/registry/format/uuid.html) | string | yes | UUID | A Universally Unique IDentifier as defined in RFC4122 | "Generated code" names the type most generators use; the TypeScript and Zod generators keep JSON's `number` and `string`, adding range checks for the integer formats. commonmark, html, password and binary have no syntax to check. The registry is vendored into API Genie and refreshed with `scripts/update-format-registry`; a format added to the registry after an API Genie release is unknown to it until the next one, and can be declared in `openapi.custom_formats` meanwhile. --- # faker Source: https://apigenie.pl/docs/reference/cli/faker API Genie makes realistic values (names, emails, addresses, IBANs, dates...) with its own faker library. Its providers are named like those of [faker.js](https://fakerjs.dev/api/) (`person.firstName`, `internet.email`) and are used by: - the `x-faker` (or `x-apigenie-mock-faker`) extension of schemas, in [`apigenie-server`](/docs/reference/server#scenarios-and-extensions) and [`data`](/docs/reference/generator/data); - `@mock(faker:, args:)` and `graphql.fields..faker` of the GraphQL mock; - `fake(name, ...arguments)` of mock scripts and `{{fake:name}}` of gRPC templates; - smart names: string properties called like `email`, `first_name` or `city`. ## Arguments A provider takes arguments by position or by name, and checks them: an argument it doesn't take, of the wrong type, out of its bounds or not one of its choices is an error naming the provider and the parameter (`faker: number.int: parameter min: expects an integer, not a string`). ```yaml properties: owner: {type: string, x-faker: person.fullName} age: {type: integer, x-faker: {number.int: [18, 90]}} code: {type: string, x-faker: {string.alpha: {length: 6, casing: upper}}} email: type: string x-faker: {provider: internet.email, args: [Ada, Lovelace], options: {provider: example.org}} ``` Arguments are numbers, strings and booleans. Whole numbers given as decimals (JavaScript and YAML give `6.0`) are integers. ## Records Some providers make related values together, as a record (an object): `person.profile` (a person's sex, first and last names of that sex, full name, a username made of the names, an email of that username and an adult's birth date), `location.address` (an address of one country, with its code and a time zone of it; city, state and postal code are of the country but not of each other), `finance.card` (an issuer's number, verification value length and expiration), `finance.currency` and `commerce.product`. A record is an object in JSON responses and data, and its JSON text where the schema wants a string. ## Countries Names exist for 61 countries; addresses and phone numbers for 15 of them (US, PL, FR, DE, IT, RU, IN, FI, ES, CZ, UA, DK, BR, AU and EE). Providers taking a `country` use the USA's data when not given one, except IBANs, BICs and national ids, which are then of a random country that has them. Usernames and emails are ASCII: names in Latin, Cyrillic or Greek letters are transliterated (`Łukasz` is `Lukasz`, `Иван` is `Ivan`); names in other scripts give usernames of American names. ## Validity IBANs have valid ISO 13616 check digits, routing numbers a valid ABA checksum, card numbers and IMEIs a valid Luhn check digit, and EAN and ISBN codes their check digits. National ids (`person.ssn`) are valid for Poland (PESEL), the USA, Spain (NIE) and the United Kingdom (National Insurance number), and only format-shaped for Germany, France, Italy and India. Bitcoin, Litecoin and Ethereum addresses and VINs are format-shaped. ## Reproducibility `--seed` repeats a run. Faker draws its values with algorithms of its own, so a seed gives the same faker values on every platform; the other choices of a mock (numbers, list lengths, which optional properties) repeat on the same platform. Relative dates (`date.past`, `date.recent`, birth dates) count from a reference time and repeat when it does too. Values may change between versions of API Genie. ## Providers ### airline | Provider | Returns | Description | | --- | --- | --- | | `airline.aircraftTypeName` | string | An aircraft type: regional, narrowbody or widebody. | | `airline.airline` | string | The name of an airline. | | `airline.airplane` | string | The name of an airplane model. | | `airline.airport` | string | The name of an airport. | | `airline.flightNumber` | string | A flight number. `length` (integer, 1 to 10, default `4`): the number of digits. `addLeadingZeros` (boolean, default `false`): pad with zeros to the length. | | `airline.recordLocator` | string | A six-character booking reference. `allowNumbers` (boolean, default `false`): digits next to letters. | | `airline.seat` | string | A seat, e.g. 23C, of an aircraft of the type. `aircraftType` (one of `regional`, `narrowbody`, `widebody`, default `narrowbody`): regional, narrowbody or widebody. | ### animal | Provider | Returns | Description | | --- | --- | --- | | `animal.bear` | string | A bear species. | | `animal.bird` | string | A bird species. | | `animal.cat` | string | A cat breed. | | `animal.cetacean` | string | A cetacean species. | | `animal.cow` | string | A cattle breed. | | `animal.crocodilia` | string | A crocodilian species. | | `animal.dog` | string | A dog breed. | | `animal.fish` | string | A fish species. | | `animal.horse` | string | A horse breed. | | `animal.insect` | string | An insect species. | | `animal.lion` | string | A lion subspecies. | | `animal.rabbit` | string | A rabbit breed. | | `animal.rodent` | string | A rodent species. | | `animal.snake` | string | A snake species. | | `animal.type` | string | A kind of animal. | ### book | Provider | Returns | Description | | --- | --- | --- | | `book.author` | string | The name of an author. | | `book.format` | string | A book format, e.g. hardcover. | | `book.genre` | string | A literary genre. | | `book.isbn` | string | An ISBN-13 with its check digit. | | `book.page` | integer | A page number. | | `book.publisher` | string | The name of a publisher. | | `book.releaseYear` | integer | The year of a release. | | `book.series` | string | The name of a book series. | | `book.title` | string | The title of a book. | | `book.translator` | string | The name of a translator. | ### color | Provider | Returns | Description | | --- | --- | --- | | `color.cmyk` | string | A CMYK color. | | `color.hex` | string | A hexadecimal color, e.g. #a3c2f0. `casing` (one of `lower`, `upper`, default `lower`): lower or upper. `prefix` (one of `0x`, `#`, nothing, default `#`): 0x, # or nothing. `includeAlpha` (boolean, default `false`): add an alpha channel. Also `internet.color`. | | `color.hsb` | string | An HSB color. | | `color.hsl` | string | An HSL color. `includeAlpha` (boolean, default `false`): add an alpha channel. | | `color.hsv` | string | An HSV color. | | `color.lab` | string | A CIELAB color. | | `color.lch` | string | An LCH color. `includeAlpha` (boolean, default `false`): add an alpha channel. | | `color.name` | string | The name of a color. | | `color.rgb` | string | An RGB color. `includeAlpha` (boolean, default `false`): add an alpha channel. | | `color.yuv` | string | A YUV color. | ### commerce | Provider | Returns | Description | | --- | --- | --- | | `commerce.department` | string | A store department. | | `commerce.discountAmount` | number | A discount amount. | | `commerce.discountCode` | string | A discount code. | | `commerce.discountPercentage` | number | A discount percentage. | | `commerce.discountType` | string | A kind of discount. | | `commerce.ean13` | string | An EAN-13 barcode with its check digit. | | `commerce.ean8` | string | An EAN-8 barcode with its check digit. | | `commerce.isbn10` | string | An ISBN-10 with its check digit. | | `commerce.isbn13` | string | An ISBN-13 with its check digit. | | `commerce.paymentProvider` | string | A payment provider. | | `commerce.paymentType` | string | A payment method. | | `commerce.price` | string | A price with two decimals, as text. `min` (number, default `0`): the lowest price. `max` (number, default `1000`): the highest price. | | `commerce.product` | record | A product whose name, description, department, price and SKU belong together, a record. | | `commerce.productAdjective` | string | An adjective for a product. | | `commerce.productCategory` | string | A product category. | | `commerce.productDescription` | string | A product description. | | `commerce.productFullName` | string | A product name with an adjective and a material. | | `commerce.productId` | string | A product identifier. | | `commerce.productMaterial` | string | A material of a product. | | `commerce.productName` | string | The name of a product. | | `commerce.productRating` | number | A product rating. | | `commerce.productReview` | string | A product review. | | `commerce.sku` | string | A stock keeping unit of digits. `length` (integer, 1 to 64, default `4`): the number of digits. | ### company | Provider | Returns | Description | | --- | --- | --- | | `company.buzzAdjective` | string | A business buzzword adjective. | | `company.buzzNoun` | string | A business buzzword noun. | | `company.buzzPhrase` | string | A business buzzword phrase. Also `company.bs`. | | `company.buzzVerb` | string | A business buzzword verb. | | `company.catchPhrase` | string | A company catch phrase. | | `company.catchPhraseAdjective` | string | An adjective of a catch phrase. | | `company.catchPhraseDescriptor` | string | A descriptor of a catch phrase. | | `company.catchPhraseNoun` | string | A noun of a catch phrase. | | `company.industry` | string | An industry. | | `company.name` | string | The name of a company. Also `company.companyName`. | | `company.type` | string | A legal form of a company. | ### computer | Provider | Returns | Description | | --- | --- | --- | | `computer.cpuManufacturer` | string | A CPU manufacturer. | | `computer.cpuModel` | string | A CPU model. | | `computer.cpuType` | string | A CPU type. | | `computer.gpuManufacturer` | string | A GPU manufacturer. | | `computer.gpuModel` | string | A GPU model. | | `computer.gpuType` | string | A GPU type. | | `computer.manufacturer` | string | A computer manufacturer. | | `computer.model` | string | A computer model. | | `computer.type` | string | A kind of computer. | ### crypto | Provider | Returns | Description | | --- | --- | --- | | `crypto.md5` | string | The MD5 digest of the data, or of a random word. `data` (string): the data to hash. | | `crypto.sha256` | string | The SHA-256 digest of the data, or of a random word. `data` (string): the data to hash. | ### database | Provider | Returns | Description | | --- | --- | --- | | `database.collation` | string | A database collation. | | `database.columnName` | string | A column name. | | `database.columnType` | string | A column type. | | `database.engine` | string | A database engine. | | `database.mongodbObjectId` | string | A MongoDB ObjectId. | ### datatype | Provider | Returns | Description | | --- | --- | --- | | `datatype.boolean` | boolean | true with the probability. `probability` (number, 0 to 1, default `0.5`): the probability of true, 0 to 1. | ### date | Provider | Returns | Description | | --- | --- | --- | | `date.between` | string | A moment between two RFC 3339 dates or date-times, both included. `from` (string, required): the first moment, e.g. 2024-01-01 or 2024-01-01T10:00:00Z. `to` (string, required): the last moment. `format` (one of `iso`, `timestamp`, `date`, `time`, default `iso`): iso (an RFC 3339 date-time), timestamp (Unix seconds), date or time. | | `date.birthdate` | string | The birth moment of someone of an age on the reference date. `minAge` (integer, 0 to 150, default `18`): the lowest age in years. `maxAge` (integer, 0 to 150, default `80`): the highest age in years. `format` (one of `iso`, `timestamp`, `date`, `time`, default `iso`): iso (an RFC 3339 date-time), timestamp (Unix seconds), date or time. | | `date.birthdateByYear` | string | A birth moment within the years. `minYear` (integer, 0 to 9999, default `1920`): the first year. `maxYear` (integer, 0 to 9999, default `2000`): the last year. `format` (one of `iso`, `timestamp`, `date`, `time`, default `iso`): iso (an RFC 3339 date-time), timestamp (Unix seconds), date or time. | | `date.future` | string | A moment within calendar years after the reference time. `years` (integer, 1 to 1000, default `1`): how many years ahead. `format` (one of `iso`, `timestamp`, `date`, `time`, default `iso`): iso (an RFC 3339 date-time), timestamp (Unix seconds), date or time. | | `date.month` | string | The name of a month. `abbreviated` (boolean, default `false`): Jan rather than January. | | `date.past` | string | A moment within calendar years before the reference time. `years` (integer, 1 to 1000, default `1`): how many years back. `format` (one of `iso`, `timestamp`, `date`, `time`, default `iso`): iso (an RFC 3339 date-time), timestamp (Unix seconds), date or time. | | `date.recent` | string | A moment within days before the reference time. `days` (integer, 1 to 365000, default `3`): how many days back. `format` (one of `iso`, `timestamp`, `date`, `time`, default `iso`): iso (an RFC 3339 date-time), timestamp (Unix seconds), date or time. | | `date.soon` | string | A moment within days after the reference time. `days` (integer, 1 to 365000, default `3`): how many days ahead. `format` (one of `iso`, `timestamp`, `date`, `time`, default `iso`): iso (an RFC 3339 date-time), timestamp (Unix seconds), date or time. | | `date.weekday` | string | The name of a day of the week. `abbreviated` (boolean, default `false`): Mon rather than Monday. | ### finance | Provider | Returns | Description | | --- | --- | --- | | `finance.accountNumber` | string | A bank account number of digits. `length` (integer, 1 to 64, default `8`): the number of digits. Also `finance.account`. | | `finance.accountType` | string | A kind of bank account. | | `finance.amount` | string | An amount with two decimals, as text. `min` (number, default `0`): the lowest amount. `max` (number, default `1000`): the highest amount. `symbol` (string): a currency symbol to put first. | | `finance.bic` | string | A BIC (SWIFT code) of a bank of the country. `country` (one of `PL`, `US`, `GB`, `DE`, `RO`, `FR`, `IN`, `IT`, `NL`, `ES`): the ISO 3166-1 code of the country; the context's, or any, when not given. | | `finance.bitcoinAddress` | string | A Bitcoin address (format-shaped, no checksum). | | `finance.card` | record | A payment card whose number, verification value and expiration belong to one issuer, a record. `issuer` (one of `amex`, `discover`, `mastercard`, `visa`): amex, discover, mastercard or visa, any when not given. | | `finance.creditCardCvv` | string | A three-digit card verification value. | | `finance.creditCardExpirationDate` | string | An expiration date MM/YY within three years of the reference time. | | `finance.creditCardNumber` | string | A card number with a valid Luhn check digit. `issuer` (one of `amex`, `discover`, `mastercard`, `visa`): amex, discover, mastercard or visa, any when not given. | | `finance.currency` | record | A currency's name, ISO 4217 code and symbol, a record. | | `finance.currencyCode` | string | An ISO 4217 currency code. | | `finance.currencyName` | string | The name of a currency. | | `finance.currencySymbol` | string | The symbol of a currency, empty for some. | | `finance.ethereumAddress` | string | An Ethereum address (format-shaped, no checksum). | | `finance.iban` | string | An IBAN with valid ISO 13616 check digits. `country` (one of 27 codes): the ISO 3166-1 code of the country; the context's, or any, when not given. | | `finance.litecoinAddress` | string | A Litecoin address (format-shaped, no checksum). | | `finance.pin` | string | A PIN of digits. `length` (integer, 1 to 64, default `4`): the number of digits. | | `finance.routingNumber` | string | An ABA routing number with a valid checksum. | ### food | Provider | Returns | Description | | --- | --- | --- | | `food.alcoholicBeverage` | string | An alcoholic beverage. | | `food.category` | string | A food category. | | `food.dish` | string | The name of a dish. | | `food.fruit` | string | A fruit. | | `food.grain` | string | A grain. | | `food.meat` | string | A meat. | | `food.milkProduct` | string | A milk product. | | `food.nonAlcoholicBeverage` | string | A non-alcoholic beverage. | | `food.nut` | string | A nut. | | `food.oil` | string | A cooking oil. | | `food.seafood` | string | A seafood. | | `food.seed` | string | An edible seed. | | `food.sugarProduct` | string | A sugar product. | | `food.vegetable` | string | A vegetable. | ### git | Provider | Returns | Description | | --- | --- | --- | | `git.branch` | string | A branch name. `maxIssueNumber` (integer, 1 to 1000000000, default `100`): the highest issue number in a name. | | `git.commitDate` | string | A commit date as git prints it, in the local time of its offset. `maxYearsAgo` (integer, 1 to 1000, default `15`): how many years back. | | `git.commitEntry` | string | A commit as git log prints it. | | `git.commitMessage` | string | A commit message. | | `git.commitSha` | string | A commit hash of hexadecimal digits. `length` (integer, 1 to 40, default `40`): the number of digits. | ### hacker | Provider | Returns | Description | | --- | --- | --- | | `hacker.abbreviation` | string | A technical abbreviation. | | `hacker.adjective` | string | A technical adjective. | | `hacker.ingverb` | string | A technical verb ending in -ing. | | `hacker.noun` | string | A technical noun. | | `hacker.phrase` | string | A technical phrase. | | `hacker.verb` | string | A technical verb. | ### helpers | Provider | Returns | Description | | --- | --- | --- | | `helpers.fromPattern` | string | A string from a pattern of a subset of regular expressions. `pattern` (string, required): # a digit, ? a letter, * either, [A-Z0-9] a class, {n} or {min,max} a repetition, \ a literal character. Also `helpers.fromRegExp`. | | `helpers.replaceSymbols` | string | A pattern with its symbols replaced by random characters. `pattern` (string, required): # becomes a digit, ? a letter, * either. | ### image | Provider | Returns | Description | | --- | --- | --- | | `image.avatar` | string | The URL of a GitHub avatar. Also `image.avatarGitHub`, `internet.avatar`. | | `image.dimensions` | string | Image dimensions, e.g. 640x480. | | `image.type` | string | An image file type. | | `image.url` | string | The URL of a placeholder image. `width` (integer, 1 to 10000, default `640`): the width in pixels. `height` (integer, 1 to 10000, default `480`): the height in pixels. `category` (one of `animals`, `business`, `cats`, `city`, `food`, `nightlife`, `fashion`, `people`, `nature`, `sports`, `technics`, `transport`): a picture category, any when not given. Also `image.imageUrl`. | ### internet | Provider | Returns | Description | | --- | --- | --- | | `internet.domainName` | string | A domain name. | | `internet.domainSuffix` | string | A top-level domain. | | `internet.domainWord` | string | A word of a domain name. | | `internet.email` | string | An email address, from the names when given. `firstName` (string): the first name to use, a random one when not given. `lastName` (string): the last name to use, a random one when not given. `provider` (string): the host after @, a free mail provider when not given. | | `internet.emailProvider` | string | The domain of a free email provider. | | `internet.emoji` | string | An emoji. `type` (one of `smiley`, `body`, `person`, `nature`, `food`, `travel`, `activity`, `object`, `symbol`, `flag`): smiley, body, person, nature, food, travel, activity, object, symbol or flag; any when not given. | | `internet.exampleEmail` | string | An email address at an example domain. `firstName` (string): the first name to use, a random one when not given. `lastName` (string): the last name to use, a random one when not given. | | `internet.httpMethod` | string | An HTTP method. | | `internet.httpRequestHeader` | string | The name of an HTTP request header. | | `internet.httpResponseHeader` | string | The name of an HTTP response header. | | `internet.httpStatusCode` | integer | An HTTP status code. `type` (one of `informational`, `success`, `redirection`, `clientError`, `serverError`): informational, success, redirection, clientError or serverError; any when not given. | | `internet.ipv4` | string | An IPv4 address. `class` (one of `a`, `b`, `c`, default `c`): the address class a, b or c. Also `internet.ip`. | | `internet.ipv6` | string | An IPv6 address. | | `internet.mac` | string | A MAC address. `separator` (string, default `:`): between the bytes. | | `internet.mimeType` | string | A media type. | | `internet.password` | string | A password of letters, digits and symbols. `length` (integer, 1 to 256, default `15`): the number of characters. | | `internet.port` | integer | A port number. | | `internet.protocol` | string | http or https. | | `internet.url` | string | A URL. `protocol` (one of `http`, `https`, default `https`): http or https. | | `internet.username` | string | A username, from the names when given. `firstName` (string): the first name to use, a random one when not given. `lastName` (string): the last name to use, a random one when not given. Also `internet.userName`. | ### location | Provider | Returns | Description | | --- | --- | --- | | `location.address` | record | An address whose parts are of one country, with its code and a time zone, a record. `country` (one of 15 codes): the ISO 3166-1 code of the country of the address, the context's (or US) when not given. | | `location.buildingNumber` | string | A building number. `country` (one of 15 codes): the ISO 3166-1 code of the country of the address, the context's (or US) when not given. Also `address.buildingNumber`. | | `location.city` | string | A city. `country` (one of 15 codes): the ISO 3166-1 code of the country of the address, the context's (or US) when not given. Also `address.city`. | | `location.country` | string | The name of a country. Also `address.country`. | | `location.countryCode` | string | An ISO 3166-1 country code. Also `address.countryCode`. | | `location.county` | string | A county; FakerError for countries without counties. `country` (one of 15 codes, default `ES`): the ISO 3166-1 code of a country with counties: EE or ES. Also `address.county`. | | `location.direction` | string | A compass direction. | | `location.latitude` | string | A latitude, as text. `precision` (integer, 0 to 7, default `4`): the number of decimals. Also `address.latitude`. | | `location.longitude` | string | A longitude, as text. `precision` (integer, 0 to 7, default `4`): the number of decimals. Also `address.longitude`. | | `location.secondaryAddress` | string | An apartment or suite. `country` (one of 15 codes): the ISO 3166-1 code of the country of the address, the context's (or US) when not given. Also `address.secondaryAddress`. | | `location.state` | string | A state or region. `country` (one of 15 codes): the ISO 3166-1 code of the country of the address, the context's (or US) when not given. Also `address.state`. | | `location.street` | string | A street name. `country` (one of 15 codes): the ISO 3166-1 code of the country of the address, the context's (or US) when not given. Also `address.streetName`, `location.streetName`. | | `location.streetAddress` | string | A street with a building number. `country` (one of 15 codes): the ISO 3166-1 code of the country of the address, the context's (or US) when not given. Also `address.streetAddress`. | | `location.timeZone` | string | An IANA time zone. Also `address.timeZone`. | | `location.zipCode` | string | A postal code. `country` (one of 15 codes): the ISO 3166-1 code of the country of the address, the context's (or US) when not given. Also `address.zipCode`. | ### lorem | Provider | Returns | Description | | --- | --- | --- | | `lorem.paragraph` | string | A paragraph of placeholder text. `minSentences` (integer, 0 to 100, default `2`): the fewest sentences. `maxSentences` (integer, 0 to 100, default `6`): the most sentences. | | `lorem.paragraphs` | string | Paragraphs of placeholder text. `min` (integer, 0 to 100, default `2`): the fewest paragraphs. `max` (integer, 0 to 100, default `4`): the most paragraphs. | | `lorem.sentence` | string | A sentence of placeholder text. `minWords` (integer, 0 to 100, default `3`): the fewest words. `maxWords` (integer, 0 to 100, default `10`): the most words. | | `lorem.sentences` | string | Sentences of placeholder text. `min` (integer, 0 to 100, default `2`): the fewest sentences. `max` (integer, 0 to 100, default `6`): the most sentences. | | `lorem.slug` | string | Words joined by hyphens. `wordCount` (integer, 0 to 100, default `3`): the number of words. | | `lorem.word` | string | A word of placeholder text. Also `random.word`. | | `lorem.words` | string | Words of placeholder text. `count` (integer, 0 to 1000, default `3`): the number of words. Also `random.words`. | ### medicine | Provider | Returns | Description | | --- | --- | --- | | `medicine.condition` | string | A medical condition. | | `medicine.medicalTest` | string | A medical test. | | `medicine.specialty` | string | A medical specialty. | ### movie | Provider | Returns | Description | | --- | --- | --- | | `movie.actor` | string | The name of an actor. | | `movie.actress` | string | The name of an actress. | | `movie.director` | string | The name of a director. | | `movie.genre` | string | A film genre. | | `movie.title` | string | The title of a film. | | `movie.tvShow` | string | The title of a TV show. | ### music | Provider | Returns | Description | | --- | --- | --- | | `music.artist` | string | The name of a musical artist. | | `music.genre` | string | A musical genre. | | `music.songName` | string | The name of a song. | ### number | Provider | Returns | Description | | --- | --- | --- | | `number.float` | number | A number from min (included) to max (excluded). `min` (number, default `0`): the lowest number. `max` (number, default `1000`): the highest number. Also `datatype.float`. | | `number.hex` | string | A hexadecimal number. `min` (integer, 0 to 2147483647, default `0`): the lowest number. `max` (integer, 0 to 2147483647, default `15`): the highest number. | | `number.int` | integer | An integer from min to max, both included. `min` (integer, -9007199254740991 to 9007199254740991, default `0`): the lowest integer. `max` (integer, -9007199254740991 to 9007199254740991, default `1000`): the highest integer. Also `datatype.number`, `random.number`. | ### person | Provider | Returns | Description | | --- | --- | --- | | `person.bio` | string | A short biography. | | `person.chineseZodiac` | string | A Chinese zodiac sign. | | `person.firstName` | string | A first name. `sex` (one of `male`, `female`): male or female, either when not given. `country` (one of 61 codes): the ISO 3166-1 code of the country whose names to use, the context's when not given. Also `name.firstName`. | | `person.fullName` | string | A full name. `sex` (one of `male`, `female`): male or female, either when not given. `country` (one of 61 codes): the ISO 3166-1 code of the country whose names to use, the context's when not given. Also `name.findName`, `name.fullName`. | | `person.gender` | string | A gender identity. Also `name.gender`. | | `person.hobby` | string | A hobby. | | `person.jobArea` | string | An area of work. | | `person.jobDescriptor` | string | A descriptor of a job title. | | `person.jobTitle` | string | A job title. Also `name.jobTitle`. | | `person.jobType` | string | A kind of job. | | `person.language` | string | A language. | | `person.lastName` | string | A last name. `sex` (one of `male`, `female`): male or female, either when not given. `country` (one of 61 codes): the ISO 3166-1 code of the country whose names to use, the context's when not given. Also `name.lastName`. | | `person.middleName` | string | A middle name. `sex` (one of `male`, `female`): male or female, either when not given. Also `name.middleName`. | | `person.nationality` | string | A nationality. | | `person.prefix` | string | A name prefix, e.g. Mrs.. `sex` (one of `male`, `female`): male or female, either when not given. Also `name.prefix`. | | `person.profile` | record | A person whose sex, names, username, email and birth date belong together, a record. `sex` (one of `male`, `female`): male or female, either when not given. `country` (one of 61 codes): the ISO 3166-1 code of the country whose names to use, the context's when not given. | | `person.sex` | string | Male or Female. | | `person.ssn` | string | A national identification number: valid for PL, US, ES and GB, format-shaped for DE, FR, IT and IN. `country` (one of `PL`, `US`, `GB`, `DE`, `FR`, `IT`, `ES`, `IN`): the ISO 3166-1 code of the country; the context's, or any, when not given. | | `person.suffix` | string | A name suffix, e.g. Jr.. Also `name.suffix`. | | `person.zodiacSign` | string | A western zodiac sign. | ### phone | Provider | Returns | Description | | --- | --- | --- | | `phone.imei` | string | An IMEI with a valid Luhn check digit. | | `phone.manufacturer` | string | A phone manufacturer. | | `phone.modelName` | string | A phone model. | | `phone.number` | string | A phone number. `format` (string): a pattern whose # become digits, e.g. ###-###-####; a random format when not given. Also `phone.phoneNumber`. | | `phone.platform` | string | A mobile platform. | ### science | Provider | Returns | Description | | --- | --- | --- | | `science.chemicalElement` | string | The name of a chemical element. | | `science.currentUnit` | string | A unit of electric current. | | `science.distanceUnit` | string | A unit of distance. | | `science.massUnit` | string | A unit of mass. | | `science.temperatureUnit` | string | A unit of temperature. | | `science.timeUnit` | string | A unit of time. | | `science.unit` | string | A unit of measurement. | ### sport | Provider | Returns | Description | | --- | --- | --- | | `sport.athlete` | string | The name of an athlete. `sex` (one of `male`, `female`): male or female, either when not given. | | `sport.event` | string | A sport event. | | `sport.sport` | string | A sport. | | `sport.team` | string | A soccer team. | ### string | Provider | Returns | Description | | --- | --- | --- | | `string.alpha` | string | Letters. `length` (integer, 0 to 10000, default `1`): the number of characters. `casing` (one of `mixed`, `lower`, `upper`, default `mixed`): mixed, lower or upper. `exclude` (string): characters not to use. Also `random.alpha`. | | `string.alphanumeric` | string | Letters and digits. `length` (integer, 0 to 10000, default `1`): the number of characters. `casing` (one of `mixed`, `lower`, `upper`, default `mixed`): mixed, lower or upper. `exclude` (string): characters not to use. Also `random.alphaNumeric`, `string.alphaNumeric`. | | `string.binary` | string | Binary digits. `length` (integer, 0 to 10000, default `1`): the number of characters. | | `string.fromCharacters` | string | Characters picked from a set. `characters` (string, required): the characters to pick from. `length` (integer, 0 to 10000, default `1`): the number of characters. | | `string.hexadecimal` | string | Hexadecimal digits. `length` (integer, 0 to 10000, default `1`): the number of characters. `casing` (one of `lower`, `upper`, default `lower`): lower or upper. `prefix` (one of `0x`, `#`, nothing, default `0x`): 0x, # or nothing. Also `datatype.hexadecimal`. | | `string.numeric` | string | Decimal digits. `length` (integer, 0 to 10000, default `1`): the number of characters. `allowLeadingZeros` (boolean, default `true`): whether the first digit may be 0. Also `random.numeric`. | | `string.octal` | string | Octal digits. `length` (integer, 0 to 10000, default `1`): the number of characters. | | `string.sample` | string | Printable ASCII characters. `length` (integer, 0 to 10000, default `10`): the number of characters. | | `string.uuid` | string | A version 4 UUID. Also `datatype.uuid`, `random.uuid`. | ### system | Provider | Returns | Description | | --- | --- | --- | | `system.commonFileExt` | string | A common file extension. | | `system.commonFileName` | string | A file name with a common extension. `extension` (string): the extension, a common one when not given. | | `system.commonFileType` | string | A common file type. | | `system.cron` | string | A cron expression. | | `system.directoryPath` | string | A directory path. | | `system.fileExt` | string | A file extension of a media type. `type` (one of `video`, `audio`, `image`, `text`, `application`): video, audio, image, text or application; any when not given. | | `system.fileName` | string | A file name. | | `system.filePath` | string | A file path. | | `system.fileType` | string | A file type. | | `system.mimeType` | string | A media type. | | `system.networkInterface` | string | A network interface name. | | `system.semver` | string | A semantic version. | ### vehicle | Provider | Returns | Description | | --- | --- | --- | | `vehicle.bicycle` | string | A kind of bicycle. | | `vehicle.color` | string | A vehicle color. | | `vehicle.fuel` | string | A fuel type. | | `vehicle.manufacturer` | string | A vehicle manufacturer. | | `vehicle.model` | string | A vehicle model. | | `vehicle.type` | string | A kind of vehicle. | | `vehicle.vehicle` | string | A vehicle manufacturer and model. | | `vehicle.vin` | string | A vehicle identification number (format-shaped, no check digit). | | `vehicle.vrm` | string | A UK vehicle registration mark. | ### videoGame | Provider | Returns | Description | | --- | --- | --- | | `videoGame.genre` | string | A video game genre. | | `videoGame.platform` | string | A gaming platform. | | `videoGame.studioName` | string | A game studio. | | `videoGame.title` | string | The title of a video game. | ### weather | Provider | Returns | Description | | --- | --- | --- | | `weather.cloudCover` | integer | Cloud cover, percent. | | `weather.description` | string | A description of the weather. | | `weather.humidity` | integer | Relative humidity, percent. | | `weather.pressure` | number | Air pressure, hPa or inHg. `units` (one of `metric`, `imperial`, default `metric`): metric or imperial. | | `weather.temperature` | number | A temperature, Celsius or Fahrenheit. `units` (one of `metric`, `imperial`, default `metric`): metric or imperial. | | `weather.uvIndex` | integer | A UV index. | | `weather.visibility` | number | Visibility, km or miles. `units` (one of `metric`, `imperial`, default `metric`): metric or imperial. | | `weather.windSpeed` | number | Wind speed, km/h or mph. `units` (one of `metric`, `imperial`, default `metric`): metric or imperial. | ### word | Provider | Returns | Description | | --- | --- | --- | | `word.adjective` | string | An English adjective. `length` (integer, 1 to 100): the length of the word, any when not given. | | `word.adverb` | string | An English adverb. `length` (integer, 1 to 100): the length of the word, any when not given. | | `word.conjunction` | string | An English conjunction. `length` (integer, 1 to 100): the length of the word, any when not given. | | `word.interjection` | string | An English interjection. `length` (integer, 1 to 100): the length of the word, any when not given. | | `word.noun` | string | An English noun. `length` (integer, 1 to 100): the length of the word, any when not given. | | `word.preposition` | string | An English preposition. `length` (integer, 1 to 100): the length of the word, any when not given. | | `word.sample` | string | An English word. `length` (integer, 1 to 100): the length of the word, any when not given. | | `word.verb` | string | An English verb. `length` (integer, 1 to 100): the length of the word, any when not given. | | `word.words` | string | English words. `count` (integer, 0 to 1000, default `1`): the number of words. | --- # Compatibility Source: https://apigenie.pl/docs/reference/cli/compatibility Scripts, CI jobs, Dockerfiles and dashboards call API Genie and read what it writes. This page says what they can rely on from one release to the next. From 1.0.0, `apigenie`, `apigenie-generator` and `apigenie-server` follow [semantic versioning](https://semver.org/spec/v2.0.0.html) for everything listed under [What the promise covers](#what-the-promise-covers): - a **patch** release (1.0.1) fixes bugs and changes nothing listed there; - a **minor** release (1.1.0) adds commands, options, settings and members, and may [deprecate](#deprecation) something, but keeps it working; - only a **major** release (2.0.0) removes or changes what is listed there. The fourth binary, [`apigenie-catalog`](/docs/reference/catalog), is experimental as a whole (see [What it doesn't cover](#what-it-doesnt-cover)) until it becomes stable. Versions below 1.0.0 make no such promise: the [changelog](https://github.com/mykulyak/api-genie/blob/main/CHANGELOG.md) lists what each one broke under "Upgrading". ## What the promise covers - **Command names and subcommands** of the three binaries: the commands of [`apigenie`](/docs/reference/cli/overview), the targets of [`apigenie-generator`](/docs/reference/generator) (`client fetch`, `server spring`, ...), and [`apigenie-server`](/docs/reference/server). - **Option names and their meaning**: an option keeps its name, its short form, the values it accepts and what it does. A new option, or a new accepted value, is a minor change. - **Configuration keys and their schema**: the keys of [`apigenie.yaml`](/docs/reference/cli/configuration) and `apigenie-server.yaml`, their types and defaults, as the JSON Schema of each release (`https://apigenie.pl/schemas/apigenie-config/.json`) describes them. A file that is valid for 1.x stays valid for every later 1.y. - **Environment variables**: `APIGENIE_TELEMETRY_FILE`, the `APIGENIE_SERVER_*` variables of [`apigenie-server`](/docs/reference/server) (`HOST`, `PORT`, `TLS_CERT`, `TLS_KEY`, `CA_CERT`, `INSECURE`, `PROXY_TO`, `REPORT`, `REPORT_JUNIT`, `RECORD_DIR`, `RECORD_HAR`, `STUBS`, `PROTO_PATH`, `MQTT_PORT`), and `APIGENIE_MQTT_USERNAME` and `APIGENIE_MQTT_PASSWORD`. - **Exit statuses**: the [table](/docs/reference/cli/overview#exit-status) shared by the three binaries. - **The `formatVersion` of each JSON report**: see [Versioned JSON outputs](#versioned-json-outputs) below. - **The docs bundle contract**: the layout of the bundle and its manifest `apigenie-docs.json` ([`apigenie-generator docs`](/docs/reference/generator/docs)), whose `apigenieDocs` and `formatVersion` say which `@mykulyak/apigenie-docs` versions can build it. - **The admin API of `apigenie-server`**: the paths and methods under `/__apigenie` listed in [Admin API](/docs/reference/server#admin-api), and the JSON they answer with. New paths and new members are minor changes. - **The `x-apigenie-*` extensions the tools read**: `x-apigenie-lint-disable` ([lint](/docs/reference/cli/lint)), `x-apigenie-mock` and `x-apigenie-mock-faker` ([server](/docs/reference/server#scenarios-and-extensions)), and the `codegen` members of `x-apigenie` (`name`, `action-name`, `controller-name`, `disabled`) that the generators read. ## What it doesn't cover - **The inside of generated code.** A generator keeps the options on its page and the shape of what it generates for your API: the names of the types, functions and routes. The generated files' layout, formatting, private helpers and comments, and the versions of the libraries they depend on, can change in a minor release, when a generator is fixed or improved. Commit generated code, or pin the API Genie version that generates it. - **Log and message text**: warnings, errors, progress lines and the text formatters (`stylish`, `compact`, `unix`, `visualstudio`, `html`, and the text and Markdown output of `diff` and `changelog`). Read the JSON reports, SARIF, checkstyle, TAP or JUnit XML instead. - **The wording of `--help`.** - **Experimental commands**: they say "(experimental, outside the 1.0 compatibility promise)" in their `--help` and on the first line they print, and the CLI overview lists them under [Experimental commands](/docs/reference/cli/overview#experimental-commands). Their options and output can change, or they can go away, in a minor release. When one becomes stable, the promise covers it from that release on. - **What a release fixes**: when API Genie accepted a document it shouldn't have, or reported a finding it shouldn't have, a minor or patch release can correct it, even though a script sees a different result. New lint rules in the built-in rulesets arrive in minor releases. ## Deprecation Something covered by the promise is removed or changed in two steps: 1. A **minor release announces it**: the changelog lists it under "Deprecated" with what to use instead, its reference page says so, and using it prints a one-line warning on the standard error (the standard output stays as it was). 2. It is **removed no earlier than the next minor release**, and only in a major release once 1.0 is out. The changelog lists the removal under "Removed" and "Upgrading". The rename of `apigenie-mock` to `apigenie-server` follows these rules: the old names print a notice and keep working until 1.1 (see [Deprecated names](/docs/reference/server#deprecated-names)). ## Versions `apigenie`, `apigenie-generator`, `apigenie-server` and `apigenie-catalog` are built, released and installed together, always with the same version. `apigenie --version` prints its version on the first line and the versions of the others below it, so a mismatch shows. The [Docker images](/downloads), the npm package and the Homebrew formula carry that version too. The **VS Code extension**, the **desktop editor** and the **docs package** (`@mykulyak/apigenie-docs`) are released on their own, each with its own version and changelog. This page covers the four binaries; the extension and the editor follow semantic versioning of their own version. ## Versioned JSON outputs Every JSON report of the CLI and the server has a top-level `formatVersion` member, an integer that started at 1, and a JSON Schema published on this site: | Output | Schema | | ------ | ------ | | [`apigenie lint --formatter json`](/docs/reference/cli/lint) | [apigenie-lint-report.json](https://apigenie.pl/schemas/apigenie-lint-report.json) | | [`apigenie diff --formatter json`](/docs/reference/cli/diff) (`status`, `changelog` and `detailed` reports) | [apigenie-diff-report.json](https://apigenie.pl/schemas/apigenie-diff-report.json) | | [`apigenie changelog --formatter json`](/docs/reference/cli/changelog) | [apigenie-changelog-report.json](https://apigenie.pl/schemas/apigenie-changelog-report.json) | | [`apigenie-catalog list --json`](/docs/reference/catalog) | [apigenie-catalog-report.json](https://apigenie.pl/schemas/apigenie-catalog-report.json) | | [`apigenie test --report`](/docs/reference/cli/test) | [apigenie-test-report.json](https://apigenie.pl/schemas/apigenie-test-report.json) | | [`apigenie-server --report`, `GET /__apigenie/contract`, `GET /__apigenie/contract.json`](/docs/reference/server) | [apigenie-contract-report.json](https://apigenie.pl/schemas/apigenie-contract-report.json) | | [`apigenie-docs.json`](/docs/reference/generator/docs), the manifest of a docs bundle | [apigenie-docs-manifest.json](https://apigenie.pl/schemas/apigenie-docs-manifest.json) | The `apigenie test --formatter json` stream carries `formatVersion` in its `summary` line. The contract and test reports also have `version`, the former name of `formatVersion`; it is deprecated and will be removed in the next release. **Adding fields keeps the version; removing or changing one bumps it.** A new member, or a new value of a member documented as open (such as a new change type), keeps `formatVersion`, so readers should ignore members they don't know. Removing a member, renaming it, or changing its type or meaning increments `formatVersion`, and the schema of the new version is published next to the old one. After 1.0, a new `formatVersion` is a breaking change and waits for a major release. ### External formats SARIF, JUnit XML, checkstyle and TAP are defined elsewhere and keep their own versions: SARIF output says `"version": "2.1.0"`, and the others follow the formats their tools read. ## Reporting a break If a minor or patch release breaks something this page covers, it's a bug. Open an issue at [github.com/mykulyak/api-genie/issues](https://github.com/mykulyak/api-genie/issues) with the two versions (`apigenie --version`), the command line, and what it did before and after. The fix is a patch release that brings the old behaviour back. If something you rely on isn't listed here, ask for it in an issue too. --- # GitHub Action Source: https://apigenie.pl/docs/ci/github-action The API Genie GitHub Action installs the CLI on GitHub Actions runners and lints, checks the formatting of, or diffs OpenAPI, Swagger 2.0, AsyncAPI, Protobuf and GraphQL files. Issues and changes are shown as annotations on the files of a pull request, and a report is added to the job summary. The action is published on the GitHub Marketplace as `mykulyak/api-genie-action`; its source and its releases are in [mykulyak/api-genie-action](https://github.com/mykulyak/api-genie-action). ## Usage ```yaml on: pull_request jobs: api: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mykulyak/api-genie-action@v0 with: command: lint files: api/**/*.yaml config: apigenie.yaml - uses: mykulyak/api-genie-action@v0 with: command: format files: api/**/*.yaml - uses: mykulyak/api-genie-action@v0 with: command: diff files: api/openapi.yaml ``` - `lint` fails the step when an issue of severity `error` is reported (see [lint](/docs/reference/cli/lint#exit-status)). - `format` runs `apigenie format --check`: nothing is written, and the step fails when a file is not formatted. - `diff` compares each file with its version at the base commit of the pull request, or the commit before a push, and fails the step on breaking changes. Set `base` to compare with another commit, branch or tag, or `base-file` to compare two files of the repository. - Without `command`, the action only installs `apigenie` and puts it on `PATH`, for the steps that follow. ## Inputs | Input | Description | | ----- | ----------- | | `version` | CLI version: `latest` (default), `X.Y.Z` or `cli-X.Y.Z`. 0.9.0 is the oldest supported version. | | `command` | `lint`, `format` or `diff`. | | `files` | Files, one per line or separated by spaces. Glob patterns such as `api/**/*.yaml` are expanded. | | `config` | The [configuration file](/docs/reference/cli/configuration). | | `formatter` | Formatter of `lint` and `diff`. Only the default, `unix`, becomes annotations. | | `report` | Report of `diff`: `status`, `changelog` (default) or `detailed`. | | `fail-on` | `error` (default), `warning` (lint warnings fail the step too) or `never`. | | `base`, `base-file` | What `diff` compares with. | | `args` | More arguments for the command, separated by spaces. | | `annotations`, `summary` | Turn the annotations or the job summary off with `false`. | ## Outputs | Output | Description | | ------ | ----------- | | `status` | `lint`: `clean`, `warnings` or `errors`. `format`: `clean` or `changed`. `diff`: `none`, `non-breaking` or `breaking`. | | `exit-code` | The exit status of the CLI. | | `error-count`, `warning-count` | The numbers of lint errors and warnings. | | `changed-files` | The files that are not formatted. | | `version`, `path` | The installed CLI and its location. | ## Runners The action runs on Linux x64, macOS arm64 and Windows x64 runners. It downloads the CLI from the GitHub release, checks it against the release's checksums, and reuses it in later steps of the same job. --- # Commands Source: https://apigenie.pl/docs/reference/cli `apigenie` is the API Genie command-line interface. Each command works on one or more API descriptions: OpenAPI, Swagger 2.0, AsyncAPI, Protobuf or GraphQL files. ## Usage ```sh apigenie COMMAND [OPTIONS] [FILES] ``` ## Commands | Name | Summary | | ----------------------------------------------------- | ------------------------------------------------------ | | [apigenie bundle](/docs/reference/cli/bundle) | bundles an OpenAPI, Swagger or AsyncAPI spec, or a Protobuf file, into one file | | [apigenie changelog](/docs/reference/cli/changelog) | builds the changelog of an OpenAPI, Swagger or AsyncAPI spec, or of a Protobuf or GraphQL file, from Git history | | [apigenie config](/docs/reference/cli/config) | prints the effective configuration, checks a configuration file and prints its JSON Schema | | [apigenie convert](/docs/reference/cli/convert) | converts a Swagger 2.0 spec to OpenAPI 3.2, a WSDL 1.1 one to OpenAPI 3.1, or a JSON Schema to an older dialect | | [apigenie credits](/docs/reference/cli/credits) | shows credits for 3rd party dependencies | | [apigenie diff](/docs/reference/cli/diff) | compares two versions of an OpenAPI, Swagger or AsyncAPI spec, or of a Protobuf or GraphQL file | | [apigenie evolve](/docs/reference/cli/evolve) | plans safe API changes: splits a breaking OpenAPI change into an expand wave and a contract wave (experimental) | | [apigenie format](/docs/reference/cli/format) | formats OpenAPI, Swagger, AsyncAPI, Protobuf and GraphQL files | | [apigenie i18n](/docs/reference/cli/i18n) | extracts texts for translation from OpenAPI, Swagger and AsyncAPI specs and Protobuf and GraphQL files, and applies translations to all of them | | [apigenie license](/docs/reference/cli/license) | show licenses for API Genie and 3rd party dependencies | | [apigenie lint](/docs/reference/cli/lint) | lints OpenAPI, Swagger, AsyncAPI and Protobuf files | | [apigenie overlay](/docs/reference/cli/overlay) | applies OpenAPI Overlays to an OpenAPI spec | | [apigenie test](/docs/reference/cli/test) | runs .http files (HTTP, gRPC, WebSocket, MQTT) and generated requests against a live API and validates them against its OpenAPI or AsyncAPI spec or .proto file | Code is generated with the separate `apigenie-generator` executable: see [apigenie-generator](/docs/reference/generator). Mock servers are started with the separate `apigenie-server` executable: see [mock server](/docs/reference/server). The API catalog of a repository is listed and kept with the separate `apigenie-catalog` executable: see [catalog](/docs/reference/catalog). --- # bundle Source: https://apigenie.pl/docs/reference/cli/bundle Bundles an OpenAPI, Swagger or AsyncAPI schema and the files it references, or a Protobuf (`.proto`) file and the files it imports, into a single file. ## Usage ```sh apigenie bundle [OPTIONS] --output OUTPUT INPUT ``` ## Description The command recognizes Protobuf files by the `.proto` extension. Other files are read as JSON or YAML, and recognized as OpenAPI, Swagger or AsyncAPI schemas by the `openapi`, `swagger` or `asyncapi` property of the document root. Unlike `format`, `overlay` and `lint --fix`, `bundle` does not keep the YAML comments of OpenAPI, Swagger and AsyncAPI inputs: the output is a new document built from several files. In YAML output, strings that YAML 1.1 parsers would read as booleans or numbers, such as `yes`, `off`, `0755` or `1:30`, are written in double quotes. ### OpenAPI External references (`$ref` to other local files) are copied into the `components` object of the output, and the references are rewritten to point there. Remote references (URLs) are not supported. The output format, JSON or YAML, follows the extension of the output file. ### Swagger Swagger 2.0 documents are bundled like OpenAPI ones: schemas, parameters and responses referenced in other local files are copied into the `definitions`, `parameters` and `responses` objects of the output, and the references are rewritten to point there. A path item that is a reference to another file takes the content of that file. Remote references (URLs) are not supported. The output format, JSON or YAML, follows the extension of the output file. ### AsyncAPI AsyncAPI 3.x documents are bundled with the local files they reference. Root servers, channels and operations kept in other files take the content of those files; other referenced objects become components. Schemas in other formats, such as Avro, are copied as they are, and references by URL are kept. The output format, JSON or YAML, follows the extension of the output file. ### Protobuf The input file and the files it imports, directly or indirectly, are combined into one proto3 file, which can be compiled without the imported files: - All declarations of the input file are kept, in their order, with their comments. The output is written in the layout of the `format` command. - Of the imported files, only the messages, enums and extensions that the input file uses, directly or through other imported declarations, are copied. They follow the declarations of the input file. Imported services, unused declarations and the file options of imported files (such as `go_package` or `java_package`) are left out. - The output has the package of the input file. Declarations from files with another package are renamed with a prefix made from their package: top-level messages and enums get the package in PascalCase, and top-level extensions get it in snake case. Declarations nested in them keep their names. When a name is already taken, a numeric suffix is added (`_2`, `_3`, and so on). Type names and option names that refer to renamed declarations are rewritten. Declarations from files with the same package keep their names. - The well-known types (`google/protobuf/*.proto`) are not bundled: their imports are kept, and the imports needed by the copied declarations are added. For example, with `shop.proto`: ```proto syntax = "proto3"; package shop.v1; import "common/money.proto"; message Order { common.v1.Money total = 1; } ``` and `common/money.proto`: ```proto syntax = "proto3"; package common.v1; message Money { string currency = 1; int64 units = 2; } ``` `apigenie bundle shop.proto --output bundle.proto` writes: ```proto syntax = "proto3"; package shop.v1; message Order { CommonV1Money total = 1; } message CommonV1Money { string currency = 1; int64 units = 2; } ``` Renaming changes the full names of the moved declarations, e.g. `common.v1.Money` becomes `shop.v1.CommonV1Money`, and an option such as `(google.api.http)` becomes `(google_api_http)`. Field numbers and the wire format do not change, but tools that depend on full names, such as JSON encoding of `Any` values or code generators matching option names, see the new names. Type names inside string option values are not rewritten. Imports are searched in the proto paths given with `--proto-path`, like `protoc` does, never relative to the importing file. Without `--proto-path`, the directory of the input file is searched. Nothing is written when there are errors. The command reports, with the file, line and column: - imports that are not found or cannot be read, including proto2 files and files with editions, which are not supported - import cycles - type names and option names that are not defined, or not visible from the file that uses them - enum values that clash after the merge; enum values are in the scope of the package, so they cannot be renamed ## Options | Option | Description | | ------------------------ | ------------------------------------------------- | | `-o`, `--output OUTPUT` | Path to the bundled file. Required. | | `-I`, `--proto-path DIR` | Directory in which Protobuf imports are searched. | ### --output OUTPUT Path of the bundled file. ### --proto-path DIR Directory in which Protobuf imports are searched. The option can be repeated; the directories are searched in the given order. Write it as `-I DIR` or `--proto-path=DIR`. Defaults to the directory of the input file. ```sh apigenie bundle api/orders.proto -I shared -I api --output bundle.proto ``` --- # changelog Source: https://apigenie.pl/docs/reference/cli/changelog Builds the changelog of an OpenAPI, Swagger, AsyncAPI, Protobuf or GraphQL file from its Git history. ## Usage ```sh apigenie changelog [OPTIONS] FILE ``` ## Description This command compares the consecutive versions of an OpenAPI, Swagger, AsyncAPI, Protobuf or GraphQL file stored in a Git repository, and describes what changed for API consumers: added, removed, deprecated and changed operations, servers and metadata, for AsyncAPI the channels, for Protobuf the methods, messages, enums and services, and for GraphQL the fields of the root operation types and the types and directives of the schema. Each change is rated as breaking, non-breaking or non-functional, with the same rules as the `diff` command. The command runs `git`, which must be installed. The file must be in a git repository, and only its committed versions are compared. ### Revisions By default, the changelog has an entry for every commit that changes the file or one of the files it references. Commits that change neither, and commits whose changes don't affect the API (e.g. reformatting), are left out. With `--revisions tags`, the changelog has an entry for every release tag instead, comparing each release with the previous one. ```sh # every commit apigenie changelog api/openapi.yaml # every release tagged v1.0.0, v1.1.0, ..., and the changes made since the last one apigenie changelog api/openapi.yaml --revisions tags --tag-pattern 'v*' --unreleased ``` Only the first parent of merge commits is followed, so a merged branch appears as a single entry: its merge commit. The first version of the file starts the changelog, and lists the operations the API had then. With `--from`, the changelog starts after the given revision instead, e.g. the last release. `--to` sets the last revision, which is `HEAD` by default. ```sh apigenie changelog api/openapi.yaml --from v2.0.0 ``` ### Files split into many documents References to other files (e.g. `$ref: 'schemas/pet.yaml'`) are followed in each version, so a change of a referenced file is a change of the API. By default, the commits changing the file or the files it references in the last revision are compared. `--path` selects the commits changing other paths instead, e.g. files that were referenced in older versions only: ```sh apigenie changelog api/openapi.yaml --path api ``` References to URLs and to files outside the repository aren't supported. ### Changes of operations With `--by operation`, the changes are grouped by operation, with the history of each one, newest first. `--operation` selects the operations to show, by method and path, by path or by operation id, and can be repeated: ```sh apigenie changelog api/openapi.yaml --by operation --operation 'GET /pets' --operation createPet ``` Operations are identified by their method and path, so moving an operation to another path shows as removing it and adding a new one. ### Swagger files Swagger 2.0 files are recognized by their `swagger` field in the last revision. Their changelog lists the added, removed, deprecated and changed operations, by method and path like OpenAPI ones, with the rules of the `commands.diff.swagger` section of the configuration. Changes of the host, the base path, the schemes, the media types and the security of the whole API are metadata. A change of a path item other than of its operations, e.g. of the parameters its operations share, is shown under each of its operations. Changes of definitions, shared parameters and responses are shown under the operations using them. Files the specification references are bundled in each version, as for OpenAPI files. ```sh apigenie changelog api/swagger.yaml --by operation --operation 'GET /pets' ``` ### AsyncAPI files AsyncAPI 3.x files are recognized by their `asyncapi` field in the last revision. Their changelog lists the added, removed and changed operations, channels and servers, and the changed metadata, with the rules of the `commands.diff.asyncapi` section of the configuration. An operation is shown by its action and the address of its channel, followed by its id, e.g. `send user/signedup` (sendUserSignedUp). Messages belong to channels, so a change of a message, e.g. of its payload, is a change of its channel, and also of the operations using the channel. Operations are identified by their id: renaming one shows as removing it and adding a new one. `--operation` selects operations by id, by action and address (e.g. `'send user/signedup'`), or by the address of their channel: ```sh apigenie changelog api/asyncapi.yaml --by operation --operation 'receive user/signedup' ``` ### Protobuf files Protobuf (proto3) files are recognized by their `.proto` extension. Their changelog lists the added, removed and changed methods of the services, named by their service and their own name (e.g. `Customers.GetCustomer`), then the messages, enums, services and extended messages declared at the top level of the file, with the rules of the `commands.diff.protobuf` section of the configuration. A change of the package is shown like a version change (e.g. `shop.v1 → shop.v2`), and changes of the imports, the file options and the comment of the syntax statement as metadata. The file is compared on its own, as by `diff`: the files it imports aren't read, and the types they declare are compared by their names. So by default, only the commits changing the file are compared. A method is identified by the names of its service and its own: renaming either shows as removing the method and adding a new one. A change of a service other than of its methods, e.g. of its options, is listed under the service. `--operation` selects methods by their full name, by their own name or by the name of their service: ```sh apigenie changelog api/shop.proto --by operation --operation Customers.GetCustomer ``` See [Protobuf files](/docs/reference/cli/diff#protobuf-files) of the `diff` command for how changes are rated. ### GraphQL files GraphQL schema files are recognized by their `.graphql`, `.graphqls` or `.gql` extension. Their operations are the fields of the root operation types, named by their type and their own name (e.g. `Query.books` or `Mutation.addBook`). The changelog lists the added, removed and changed operations, then the scalars, object types, interfaces, unions, enums, input objects and directive definitions of the schema, with the rules of the `commands.diff.graphql` section of the configuration. Changes of the root operation types, of the description of the schema and of its directives are shown as metadata. A schema has no version, so none is shown. The file is compared on its own, as by `diff`: types defined in other files are compared by their names. So by default, only the commits changing the file are compared. The root operation types are those named by the schema definition, or the types named `Query`, `Mutation` and `Subscription`. An operation is identified by the names of its type and its field: renaming either shows as removing the operation and adding a new one. A change of a root operation type other than of its fields, e.g. of its description, is listed under the object types. `--operation` selects operations by their full name, by their field name, by the name of their type, or by their operation type (`query`, `mutation` or `subscription`): ```sh apigenie changelog api/schema.graphql --by operation --operation Query.books ``` See [GraphQL files](/docs/reference/cli/diff#graphql-files) of the `diff` command for how changes are rated. ### Invalid versions Versions that can't be read, e.g. because of a YAML, Protobuf or GraphQL syntax error, are skipped with a warning, and the next version is compared with the last valid one. ## Options | Option | Description | | ------------------------------ | -------------------------------------------------------------------------------------------------------- | | `--revisions commits\|tags` | Compares every commit that changes the file (default), or every release tag. | | `--tag-pattern PATTERN` | Glob pattern of the release tags, e.g. `v*`. All tags by default. | | `--tag-sort version\|date` | Orders the release tags by version numbers in their names (default), or by date. | | `--unreleased` | With `--revisions tags`, adds an entry with the changes made after the last release. | | `--from REVISION` | The revision the changelog starts after, e.g. a tag. | | `--to REVISION` | The last revision, `HEAD` by default. | | `--path PATH` | With `--revisions commits`, compares the commits changing these paths. Can be repeated. By default, the file and the files it references; for Protobuf and GraphQL, the file only. | | `--by revision\|operation` | Groups the changes by revision (default), or by operation (for Protobuf, by method; for GraphQL, by field of a root operation type). | | `--operation OPERATION` | Shows the changes of an operation only: `'GET /pets'`, a path or an operation id (OpenAPI and Swagger); for AsyncAPI, `'send ADDRESS'`, a channel address or an operation id; for Protobuf, `Service.Method`, a method name or a service name; for GraphQL, `Query.books`, a field name, a root operation type name, or `query`, `mutation` or `subscription`. Can be repeated. | | `--formatter FORMATTER` | `stylish` (default), `markdown`, `html` or `json`. | | `--config FILE` | Configuration file; its `diff` settings (`diff.swagger` for Swagger, `diff.asyncapi` for AsyncAPI, `diff.protobuf` for Protobuf, `diff.graphql` for GraphQL) decide the severity of the changes. | | `--git PATH` | Path to the git program. | | `-o`, `--output FILE` | Writes the changelog to a file instead of printing it. | ## Output The `markdown` formatter prints a changelog that can be published as is: ```markdown ## v2.0.0 (2026-01-05) Release 2.0 (a25ac8d, Jane Doe) **Contains breaking changes.** Version: 1.1.0 → 2.0.0 ### Removed - `GET /pets/{id}` (getPet) **breaking** ### Deprecated - `GET /pets` (listPets) **breaking** ``` The `json` formatter prints the full data, including every change of the modified operations, for further processing: an object with `formatVersion` (the version of the format, which changes only when a member is removed or changes meaning, see [Compatibility](/docs/reference/cli/compatibility)), `spec`, `warnings`, and `entries` (one per revision, newest first) or, with `--by operation`, `operations` (`methods` for Protobuf) with the history of each one. Its JSON Schema is [apigenie-changelog-report.json](https://apigenie.pl/schemas/apigenie-changelog-report.json). ```json5 { "formatVersion": 1, "spec": "petstore.yaml", "entries": [ { "revision": { "id": "a25ac8d…", "shortId": "a25ac8d", "label": "v2.0.0", "tag": "v2.0.0", "author": "Jane Doe", "date": "2026-01-05", "subject": "Release 2.0" }, "initial": false, "version": "2.0.0", "severity": "breaking", … metadata, servers, operations … } ], "warnings": [] } ``` ## Exit status The command exits with 0 when it prints the changelog, even if it has breaking changes, and with 2 when git can't be run, the file isn't in a Git repository, a revision doesn't exist, or no revision changes the file. See the [exit statuses](/docs/reference/cli/overview#exit-status) shared by the three binaries. --- # config Source: https://apigenie.pl/docs/reference/cli/config Prints the effective configuration, checks a configuration file, and prints the JSON Schema of the configuration file. ## Usage ```sh apigenie config [--config FILE | --no-config] [--json] [--origin] [--for SECTION] apigenie config check [FILE] apigenie config schema [--for SECTION | --standalone-server] [--out FILE] ``` ## Description The commands of `apigenie`, `apigenie-generator`, `apigenie-server` and `apigenie-catalog` read their settings from the [configuration file](/docs/reference/cli/configuration): `apigenie.yaml` in the current directory or a parent up to the root of the repository, or the file given with `--config`. Every file is checked against a JSON Schema (draft-7) when it is loaded. The schema is made of one part per section, so it covers the settings of all four executables. ### apigenie config Prints the settings the commands use: the values of the file, those set by environment variables, and the defaults of the settings the file leaves out. Sections without any setting are left out. | Option | Description | | ----------------- | ----------- | | `--config FILE` | Uses FILE instead of the file found in the repository. | | `--no-config` | Uses no file: prints the defaults. | | `--json` | Prints JSON instead of YAML. | | `--origin` | Annotates each value with where it comes from. | | `--for SECTION` | Prints one section only: `catalog`, `lint`, `format`, `diff`, `generate`, `generate.` (for example `generate.client_go`) or `mock`. | With `--origin`, the YAML output has a comment after each value: ```yaml commands: lint: extends: - recommended # apigenie.yaml (repository file) mock: server: host: 127.0.0.1 # default port: 8080 # environment APIGENIE_SERVER_PORT ``` The origins are: | Origin | Meaning | | ------ | ------- | | ` (repository file)` | the file found in the current directory or a parent | | `--config ` | the file given with `--config` | | `environment ` | an environment variable of `apigenie-server` (`APIGENIE_SERVER_PORT` and the others listed in [mock server](/docs/reference/server)), which wins over the file | | `default` | the value used when the file leaves the setting out | With `--json --origin`, the output is an object with the configuration under `config` and the origin of each value, keyed by its JSON pointer, under `origins`. Below a section with `extends` (such as `extends: [recommended]` in `lint`, `format` or `diff`), only the values of the file are shown: the presets set the others. Settings given on the command line of a command (such as `--port` of `apigenie-server`) are not part of the configuration and are not shown. ### apigenie config check Validates FILE, or the file found in the repository, against the schema. It prints `: ok` and exits with 0 when the file is valid. Otherwise it prints each error with its position and the JSON pointer of the value, and exits with 2: ```text $ apigenie config check apigenie.yaml apigenie.yaml:4:11: /commands/fromat: unknown property 'fromat'; did you mean 'format'? apigenie.yaml:9:15: /commands/format/openapi/indent: expected type integer, found string apigenie.yaml:12:20: /commands/generate/server_express/validation: "zod" is not one of "ajv", "generated", "none" ``` Every unknown setting is reported at once. Of the other errors, the first one found is reported. ### apigenie config schema Prints the JSON Schema of the configuration file, of one section with `--for`, or of a standalone `apigenie-server` file (`apigenie-server.yaml`, whose root holds the settings of `commands.mock`) with `--standalone-server`. `--out FILE` writes it to FILE. The schema of each release is also published on the website, at `https://apigenie.pl/schemas/apigenie-config/.json` and `https://apigenie.pl/schemas/apigenie-server-config/.json`, with `latest.json` for the last release. See [editor support](/docs/reference/cli/overview#editor-support). ## Exit status | Status | Meaning | | ------ | ------- | | 0 | Success; `config check`: the file is valid. | | 2 | `config check` found errors in the file, the configuration file can't be loaded, or a usage error: an unknown section, no file to check. | These are the statuses of every command ([exit status](/docs/reference/cli/overview#exit-status)). ## See also - [Configuration](/docs/reference/cli/configuration): the sections of the file. - [Mock server configuration](/docs/reference/server#configuration). --- # convert Source: https://apigenie.pl/docs/reference/cli/convert Convert a Swagger 2.0 document to OpenAPI 3.2, a WSDL 1.1 document to OpenAPI 3.1, or a JSON Schema document of a newer draft to draft-07, draft-04 or the Schema Object dialect of OpenAPI 3.0. > **(experimental, outside the 1.0 compatibility promise)** `convert` has three sources, Swagger > 2.0, WSDL 1.1 and JSON Schema, and its options and output can still change. It says so in its > `--help` and on the first line it prints, on the standard error. See the > [experimental commands](/docs/reference/cli/overview#experimental-commands). ## Usage ```sh apigenie convert --to openapi32 [...OPTIONS] SWAGGER_FILE apigenie convert --to openapi31 [...OPTIONS] WSDL_FILE apigenie convert --to draft7|draft4|openapi30 [...OPTIONS] SCHEMA_FILE ``` ## Description The command reads a Swagger 2.0 document, a WSDL 1.1 document or a JSON Schema document and writes the converted document, to the standard output or to the file given with `--output`. A Swagger document becomes OpenAPI 3.2 (`--to openapi32`), a WSDL document OpenAPI 3.1 (`--to openapi31`), and a JSON Schema document a draft-07 (`--to draft7`), draft-04 (`--to draft4`) or OpenAPI 3.0 schema (`--to openapi30`). The source is the one `--from` names; without `--from`, an input that starts with `<` (an XML document) is WSDL, a document with a `swagger` member Swagger 2.0, and any other input a JSON Schema. ## Swagger 2.0 input The command reads a Swagger 2.0 document (`swagger: "2.0"`, JSON or YAML) and writes the equivalent OpenAPI 3.2 document. OpenAPI 3.x documents are not accepted as input: they are read as Swagger documents and rejected (`Missing required property 'swagger'`). The document is checked first. When it isn't a valid Swagger 2.0 document, the command lists the problems with their lines and columns and writes nothing. The conversion changes the structure of the document, not only its references: - `body` and `formData` parameters become request bodies, with a media type per entry of `consumes` (file uploads as `multipart/form-data`). Global body parameters become `components.requestBodies`. - Other parameters get a `schema` built from their keywords, and `collectionFormat` becomes `style` and `explode`. - Response schemas, headers and examples move into `content`, with a media type per entry of `produces`. - `host`, `basePath` and `schemes` become `servers`: one URL per scheme, `https` when no scheme is given, and a relative URL from `basePath` alone when there is no `host`. - `definitions`, `parameters`, `responses` and `securityDefinitions` move into `components`, and references follow them (`#/definitions/Pet` becomes `#/components/schemas/Pet`). - `basic` security schemes become `http` schemes with `scheme: basic`; the `flow`, URLs and scopes of `oauth2` schemes move into `flows` (`accessCode` becomes `authorizationCode`, `application` becomes `clientCredentials`). - Schemas switch dialect: `type: file` becomes a binary string, discriminators become objects, boolean `exclusiveMinimum` and `exclusiveMaximum` become numeric bounds, and `x-nullable: true` adds `null` to the type. - Specification extensions (`x-...`) and everything else without a counterpart to convert are copied as they are. Some parts of a Swagger 2.0 document have no exact counterpart in OpenAPI 3.2 and are approximated or dropped: a `collectionFormat` of `tsv` (or one other than `csv` in a path or a header, or other than `multi` in a form field), `allowEmptyValue` of a form field, an operation's own `schemes`, form parameters next to a body parameter, and `x-nullable` without a `type`. A reference to another document is copied unchanged, so that document has to be converted too; bundle the document first with [`apigenie bundle`](/docs/reference/cli/bundle) to convert it as a whole. The `convert` command doesn't report these approximations yet; [`apigenie-server`](/docs/reference/server#swagger-20-documents) and [`apigenie test`](/docs/reference/cli/test) do (see below). The output keeps the order of the input's keys. Comments of a YAML input are not kept. ### Swagger 2.0 in apigenie-server and apigenie test `apigenie-server`, its validating proxy (`--proxy-to`), and `apigenie test --spec` take Swagger 2.0 documents directly: they convert them to OpenAPI 3.2 in memory, as `convert --to openapi32` does, each time the document is read, and write no file. Locations stay those of the Swagger file: the lines of warnings, the lines and JSON pointers of `x-apigenie-mock` problems, and the `pointer` of each operation in the contract reports. What the conversion can only approximate is told once at startup as `info:` lines with the Swagger file's line and JSON pointer (`{"event":"info",...}` with `apigenie test --formatter json`). See [Swagger 2.0 documents](/docs/reference/server#swagger-20-documents). The other commands that need an OpenAPI document, such as the [generators](/docs/reference/generator), read only OpenAPI 3.x: convert Swagger 2.0 documents first. ## JSON Schema input The command reads a JSON Schema document written against draft 2019-09 or 2020-12 (JSON or YAML) and writes the same schema in an older dialect: draft-07 (`--to draft7`), draft-04 (`--to draft4`), or the Schema Object of OpenAPI 3.0 (`--to openapi30`), to paste into `components.schemas` of an OpenAPI 3.0 document. A draft-07 document goes through the same pass on its way to draft-04: the rewrite is driven by the keywords the document uses, not by its `$schema`. The document is not validated first. ### Composition: allOf and unevaluatedProperties The older dialects have no `unevaluatedProperties`, and `additionalProperties: false` next to an `allOf` would reject every property the members declare. So a schema that extends others with `allOf` and closes itself with `unevaluatedProperties` becomes one flat object schema: ```yaml # 2020-12 $defs: Base: {type: object, properties: {id: {type: string}}, required: [id]} type: object allOf: - $ref: '#/$defs/Base' - properties: {name: {type: string}} unevaluatedProperties: false ``` ```yaml # draft-07 $schema: http://json-schema.org/draft-07/schema# definitions: Base: {type: object, properties: {id: {type: string}}, required: [id]} type: object properties: id: {type: string} name: {type: string} required: [id] additionalProperties: false ``` The members are the `allOf` elements, the schemas that local `$ref` members point to (chains of references and nested `allOf` followed, each schema merged once), and the schema itself (a `$ref` with siblings counts as a member too). Their `properties`, `required`, `patternProperties`, `propertyNames`, `minProperties`, `maxProperties` and `dependentRequired` are merged; a member's `additionalProperties` schema becomes the result's, since it evaluates the extra properties; otherwise `unevaluatedProperties` does. The members' titles and descriptions are not carried over, and the definitions stay for the other schemas that reference them. A property two members define differently is kept as an `allOf` of both definitions, and reported. A member that can't be merged keeps the `allOf` as it is, drops `unevaluatedProperties` (the result then accepts more than the source, which is the lesser loss) and says why on the standard error: `additionalProperties: false` or `unevaluatedProperties` of its own, `anyOf`, `oneOf`, `not`, `if`, `dependentSchemas`, `enum`, `const`, a `type` other than `object`, a `discriminator`, a reference to another document or to an anchor, or a cycle. ### The other keywords | Source keyword | draft-07 | draft-04 | OpenAPI 3.0 | | --- | --- | --- | --- | | `$schema` | the draft-07 URI | the draft-04 URI | dropped | | `$defs` and `#/$defs/` references | `definitions` | `definitions` | `definitions`, reported: move them to `components.schemas` | | `$id` | kept | `id` | dropped | | `unevaluatedProperties` alone | `additionalProperties` | same | same | | `unevaluatedItems` after a tuple | `additionalItems` | same | dropped | | `prefixItems` + `items` | tuple `items` + `additionalItems` | same | `items` accepting any element schema, `maxItems` for a closed tuple | | `dependentRequired`, `dependentSchemas` | `dependencies` | same | dropped | | `$dynamicRef`, `$recursiveRef` | `$ref` | same | same | | `const` | kept | `enum` | `enum` | | numeric `exclusiveMinimum` / `exclusiveMaximum` | kept | `minimum` / `maximum` + boolean | same | | `true` / `false` schemas | kept | `{}` / `{not: {}}` | same | | `contains`, `propertyNames`, `if` / `then` / `else` | kept | dropped | dropped | | `examples` | kept | dropped | the first as `example` | | `contentEncoding: base64`, `contentMediaType` | kept | dropped | `format: byte` / `binary` | | `type: [T, 'null']`, a `{type: 'null'}` alternative | kept | kept | `type: T`, `nullable: true` | | `type` with several names | kept | kept | `anyOf` of single-type schemas | | `patternProperties` | kept | kept | `^name$` patterns become `properties`, others dropped | | `$ref` with siblings | kept | kept | wrapped in `allOf` | `$anchor`, `$dynamicAnchor`, `$vocabulary`, `minContains`, `maxContains` and `contentSchema` have no counterpart in any target. Everything dropped or approximated is reported on the standard error, one line per keyword with its JSON pointer in the source, so that the document on the standard output stays usable. Specification extensions (`x-...`), annotations and the keywords the target has are copied as they are, in the order of the input. ## WSDL 1.1 input The command reads a WSDL 1.1 document whose SOAP 1.1 or SOAP 1.2 bindings are document/literal over HTTP, with the XML Schemas of its types, and writes an OpenAPI 3.1 document. The result lints without errors with the `recommended` ruleset (see [Linting](#linting-the-result)). It serves in [`apigenie-server`](/docs/reference/server), which validates SOAP requests and answers SOAP envelopes. [`diff`](/docs/reference/cli/diff) and the [generators](/docs/reference/generator) read it like any other OpenAPI document. The WSDL documents it imports (`wsdl:import`) and the schemas (`xs:import` and `xs:include` with a `schemaLocation`) are read from files, relative to the document that names them. Remote documents (`http:` and `https:`) are not fetched: download them and point the `location` or `schemaLocation` at the copies. A schema that isn't fetched is reported, and the elements, types and attributes of its namespace then accept any value. ### Operations Each operation of a SOAP binding becomes a `POST` operation: - **Path:** the path of the port's `soap:address`, followed by the operation's name. The operations of a port share one address in SOAP, but an OpenAPI path has one `POST`, so each operation gets a path of its own. For `https://api.example.com/soap/greeter`, the operation `Greet` is `POST /soap/greeter/Greet`. The address's origin is the server. A binding that no port uses gets `/{binding}/{operation}` and no server. - **Extensions:** `x-soap-address` is the port's address, and `x-soap-action` the operation's SOAPAction. - **Tag and operationId:** the tag is the port type. The operationId is the operation's name, prefixed with the port type (or the port) when several operations have that name. - **Description:** the operation's `wsdl:documentation`, or a sentence naming the operation when it has none. - **SOAP 1.1 and 1.2:** a binding of each version for one port type at one address makes one operation, with a `text/xml` body (the SOAP 1.1 envelope) and an `application/soap+xml` body (the SOAP 1.2 envelope). `apigenie-server` answers in the media type of the request. ### Bodies and responses Request and response bodies are SOAP envelopes. Their schemas carry the [`xml` objects](/docs/reference/server#xml-bodies) that map the envelope's XML to the schema and back: - **Envelope:** the root is `soap:Envelope`. Its `Body` holds an element per body part of the message, and its `Header` the `soap:header` parts. The Header also accepts header blocks that the WSDL doesn't declare, such as WS-Addressing's. - **Responses:** the output message is the `200` response. A one-way operation answers `202` without content. - **Faults:** the `500` response is a SOAP Fault, with the faults of the operation in its `detail` (SOAP 1.1) or `Detail` (SOAP 1.2). The global elements and types the messages use become schemas in `components`: - **Elements and types:** an element and a type of the same name are `X` and `XType`. - **Content models:** `xs:sequence` and `xs:all` become objects with `additionalProperties: false`. `minOccurs` gives `required`, and a repeated element becomes an array whose items repeat in the parent element. A required `xs:choice` of required elements becomes `oneOf` of its alternatives. - **Attributes and text:** an attribute is a property with `xml: {attribute: true}`. The text of an element with simple content is a `value` property with `xml: {x-nodeType: text}`. - **Facets and values:** `nillable` adds `null` to the type, `default` becomes `default`, and `fixed` becomes `const`. Enumerations, lengths, bounds and patterns become their JSON Schema keywords. - **Built-in types:** each gets a type and a format; an `xs:int` is `{type: integer, format: int32}`, an `xs:dateTime` is `{type: string, format: date-time}`. - **Namespaces:** every `xml` object with a namespace has a prefix, the one the WSDL declares when it is free. Elements are qualified as `elementFormDefault` (or `form`) says. - **Descriptions:** `xs:documentation` becomes the description. ### Limits What has no exact counterpart is approximated, and told on the standard error as a warning with its file, line and column. Warnings with the same text are told once, followed by "(and N more like this)". - `xs:any`, `xs:anyAttribute` and `xs:anyType` accept any content: the object gets `additionalProperties: true` and `x-apigenie-lint-disable: [schema-no-additional-properties]`. - Substitution groups aren't followed: only the head element is accepted where it is used. Abstract elements and types are accepted as they are declared. - A type extending another one copies its content. An element of a derived type (`xsi:type`) isn't accepted in place of its base. - Mixed content: the text between child elements isn't described. - A list type becomes a string of space-separated values. - A repeated `xs:sequence` or `xs:choice` becomes an array per element; the order across elements is lost. - `xs:decimal` becomes a `double`, and integer types without bounds (`xs:integer`, `xs:nonNegativeInteger`...) an `int64`. - `totalDigits` is dropped, and so is `fractionDigits` other than 0. Patterns using `\i`, `\c`, `\p{Is...}` or character class subtraction are dropped. - `xs:redefine` and `xs:override` are read as `xs:include`. - A message part with a `type` instead of an `element` becomes an element in no namespace. - HTTP and MIME bindings are left out. So are operations in rpc style or with encoded use, next to document/literal ones. - WS-Policy, WS-Addressing, WS-Security and other WS-* extensions aren't read, and neither are MIME attachments (MTOM, SwA). - WSDL has no version: `info.version` is `1.0.0`. These documents are refused, with exit status 2: - documents that aren't well-formed XML, and WSDL 2.0 documents; - documents with a missing local import, or with a reference to something that isn't defined; - documents without a document/literal SOAP binding over HTTP: rpc/encoded and rpc/literal services, and SOAP over other transports. The message names the binding and its style and use. The importer converts WSDL to OpenAPI only: it doesn't generate SOAP clients or servers, and it doesn't write WSDL. ### Linting the result The converted document has no errors with the `recommended` ruleset. Some warnings stay, because they concern what WSDL doesn't describe or names WSDL sets: - security and rate limits: `operation-security-defined` and `operation-rate-limiting-defined`; - the length of strings: `schema-constraints`; - a binding without an address: `servers-defined`; - large services: `paths-max-count`; - the address's case: `paths-well-formed`; - date element names: `schema-datetime-suffix`. Two more come from XML Schema constructs: - `schema-discriminator-defined`: the alternatives of an `xs:choice` are told apart by their element, not by a property; - `schema-no-optional-nullable-properties`: an optional nillable element can be absent or `xsi:nil`, which differ in XML. ## Options ### --from The source: `swagger2` (Swagger 2.0), `wsdl` (WSDL 1.1) or `jsonschema` (a JSON Schema document). Without it, an XML input is WSDL, a document with a `swagger` member Swagger 2.0, and any other input a JSON Schema. ### --to Required. The target: `openapi32` (OpenAPI 3.2) for Swagger 2.0, `openapi31` (OpenAPI 3.1) for WSDL, and `draft7`, `draft4` or `openapi30` (the Schema Object of OpenAPI 3.0) for a JSON Schema. Another pair exits with status 2. ### --format Serialization of the output: `json` or `yaml`. Defaults to the format of the input, and to YAML for WSDL. ### -o, --output Path of the converted document. Without it, the document is written to the standard output. ## Exit status | Status | Meaning | | ------ | ------- | | 0 | The document was converted | | 2 | Invalid arguments (a missing `--to`, an unknown target or format, a target the source doesn't convert to), the input file can't be read, it isn't a valid Swagger 2.0 document, or the WSDL document can't be converted (the problems are listed on the standard error) | ## Examples Convert a YAML document and print the result: ```sh apigenie convert --to openapi32 petstore.yaml ``` Convert to a JSON file: ```sh apigenie convert --to openapi32 --format json -o petstore.openapi.json petstore.yaml ``` Downgrade a 2020-12 schema to draft-07, keeping the notes apart from the result: ```sh apigenie convert --to draft7 -o order.draft7.json order.schema.json 2> notes.txt ``` Convert a WSDL document, lint the result and serve it: ```sh apigenie convert --to openapi31 -o greeter.yaml greeter.wsdl apigenie lint greeter.yaml apigenie-server greeter.yaml ``` For this Swagger 2.0 input: ```yaml swagger: "2.0" info: {title: Pets, version: "1.0"} host: api.example.com basePath: /v1 schemes: [https, http] consumes: [application/json] produces: [application/json] securityDefinitions: basic: {type: basic} paths: /pets: post: operationId: addPet parameters: - {name: body, in: body, required: true, schema: {$ref: '#/definitions/Pet'}} responses: "201": {description: Created, schema: {$ref: '#/definitions/Pet'}} definitions: Pet: type: object properties: name: {type: string, x-nullable: true} photo: {type: file} ``` the command writes: ```yaml openapi: 3.2.0 info: title: Pets version: "1.0" servers: - url: https://api.example.com/v1 - url: http://api.example.com/v1 paths: /pets: post: operationId: addPet responses: "201": description: Created content: application/json: schema: $ref: "#/components/schemas/Pet" requestBody: content: application/json: schema: $ref: "#/components/schemas/Pet" required: true components: schemas: Pet: type: object properties: name: type: - string - "null" photo: type: string format: binary securitySchemes: basic: type: http scheme: basic ``` For this WSDL input: ```xml Greets someone. ``` the command writes (the 500 response and the shared `Soap11Header` and `Soap11Fault` components left out): ```yaml openapi: 3.1.0 info: title: GreeterService summary: The SOAP service GreeterService, converted from WSDL description: The operations of the SOAP service GreeterService. Each operation takes and returns a SOAP envelope; its path is the address of the service followed by the name of the operation. version: 1.0.0 x-wsdl-target-namespace: urn:example:greeter servers: - url: https://api.example.com tags: - name: Greeter description: The operations of the port type Greeter. paths: /soap/greeter/Greet: post: tags: - Greeter description: Greets someone. operationId: Greet requestBody: required: true content: text/xml: schema: type: object properties: Header: $ref: "#/components/schemas/Soap11Header" Body: type: object xml: namespace: http://schemas.xmlsoap.org/soap/envelope/ prefix: soap properties: Greet: $ref: "#/components/schemas/Greet" xml: name: Greet namespace: urn:example:greeter prefix: g required: - Greet additionalProperties: false required: - Body additionalProperties: false xml: name: Envelope namespace: http://schemas.xmlsoap.org/soap/envelope/ prefix: soap responses: "200": description: The response of Greet. content: text/xml: schema: type: object properties: Header: $ref: "#/components/schemas/Soap11Header" Body: type: object xml: namespace: http://schemas.xmlsoap.org/soap/envelope/ prefix: soap properties: GreetResponse: $ref: "#/components/schemas/GreetResponse" xml: name: GreetResponse namespace: urn:example:greeter prefix: g required: - GreetResponse additionalProperties: false required: - Body additionalProperties: false xml: name: Envelope namespace: http://schemas.xmlsoap.org/soap/envelope/ prefix: soap x-soap-action: urn:example:greeter/Greet x-soap-address: https://api.example.com/soap/greeter components: schemas: Greet: type: object properties: name: type: string xml: namespace: urn:example:greeter prefix: g times: type: integer format: int32 xml: namespace: urn:example:greeter prefix: g required: - name additionalProperties: false xml: name: Greet namespace: urn:example:greeter prefix: g GreetResponse: type: object properties: greeting: type: array items: type: string xml: namespace: urn:example:greeter prefix: g minItems: 1 required: - greeting additionalProperties: false xml: name: GreetResponse namespace: urn:example:greeter prefix: g ``` `apigenie-server greeter.yaml` then answers this request with a `GreetResponse` envelope, and a request without `name` with a 422 problem (`body/Body/Greet: required property 'name' is missing`): ```sh curl -X POST http://127.0.0.1:4010/soap/greeter/Greet -H 'Content-Type: text/xml' \ --data ' Ada ' ``` --- # credits Source: https://apigenie.pl/docs/reference/cli/credits Displays credits for 3rd party libraries used by API Genie. ## Usage ```sh apigenie credits ``` ## Description API Genie uses several open-source libraries. This command fullfills their license requirements by displaying a list of these libraries along with links to their respective project pages and brief descriptions. ## See Also The [license](/docs/reference/cli/license) command displays licenses of API Genie itself and 3rd party libraries it uses. --- # diff Source: https://apigenie.pl/docs/reference/cli/diff Compares two versions of an OpenAPI, Swagger 2.0 or AsyncAPI schema, of a Protobuf (`.proto`) file, or of a GraphQL schema, and reports the changes and their severity. ## Usage ```sh apigenie diff [OPTIONS] SOURCE_FILE DEST_FILE ``` ## Options | Option | Description | | ------ | ----------- | | `--config` | Path to the configuration file. | | `--report` | What to print: `status`, `changelog` (default) or `detailed`. | | `--formatter` | `html`, `json`, `markdown`, `stylish` (default), `unix` or `visualstudio`. | | `--update-version` | Updates `info.version` of the destination file to the smallest version compatible with the changes (a new major version for breaking changes, minor for non-breaking and patch for non-functional ones), keeping the rest of the file intact. | | `--today` | The date of the comparison (`YYYY-MM-DD`) for the `sunset_passed` condition of the OpenAPI rules: a deprecated element whose `x-sunset` date is on or before it may go. Defaults to the current date; it has no effect on the other formats. | The command exits with status 1 when there are breaking changes, 0 otherwise, and 2 when a file can't be read or compared or the configuration is invalid (the [exit statuses](/docs/reference/cli/overview#exit-status) shared by the three binaries). With `--update-version` it prints nothing and exits with 0. Protobuf and GraphQL files have no version, so `--update-version` is rejected for them. Both files must be OpenAPI 3.x documents, both Swagger 2.0 documents, both AsyncAPI 3.x documents, both Protobuf files or both GraphQL files. ## Swagger documents A file is treated as Swagger when its root object has a `swagger` field. For Swagger documents the differ compares the host, the base path, the schemes, the consumed and produced media types, paths, operations, parameters, responses and their headers and examples, schemas, security definitions, security requirements and tags. Parameters are matched by their location and name, e.g. `query:limit`, so a parameter moved from the query to a header is a removed and an added one. An operation has at most one body parameter, whose name is only documentation: it is matched as `body`. Security requirements are alternatives, matched by the names of their schemes, so their order doesn't matter. References to definitions, parameters and responses are followed, and their changes are reported where they are used as well as where they are defined. As for OpenAPI documents, the severity of a change to a schema depends on the direction of the data: parameters, including body parameters, and consumed media types get the `request` rules, responses and produced media types the `response` ones. Definitions get the stricter of the two, and read-only properties don't matter in requests. The changelog report lists the changed metadata (including the host, the base path, the schemes, the media types and the security of the whole API), the added, removed and modified operations, with the changes of each modified one, and the paths whose own parameters changed. ## AsyncAPI documents A file is treated as AsyncAPI when its root object has an `asyncapi` field. For AsyncAPI documents the differ compares servers, channels, operations, messages with their payloads and headers, security schemes, bindings and components. Bindings of HTTP, WebSockets, Kafka, AMQP and MQTT are compared property by property, those of other protocols as whole values; schemas in formats other than AsyncAPI and JSON Schema, e.g. Avro, are compared as whole values too. The severity of a change to a message depends on who sends it. The messages of `receive` operations are sent by other parties to the application, those of `send` operations by the application. A reply flows the other way than the messages of its operation. For example, a new required payload property breaks the senders of a received message, while the receivers of a sent message can ignore it. Messages used in both directions, or by no operation, get the stricter severity. The changelog report lists the changed metadata, and the added, removed and modified servers, channels and operations, with the changes of each modified one. Changes of components are shown where the components are used. ## Protobuf files Files with the `.proto` extension are compared as Protobuf (proto3) files; both files must be Protobuf files. The differ compares the package, imports and options of the files, and their messages, enums, services and extend blocks, down to fields, oneofs, enum values, methods, reserved numbers and names, options and comments. Declarations are matched the way the programs that use them find them: - messages, enums, services, oneofs and methods by their names; - fields and enum values by their numbers, which identify them in the encoded data. A renamed field is a modified field; a field that got another number is a removed and an added one; - extend blocks by the type they extend; - reserved numbers as sets, so `reserved 1, 2, 3;` equals `reserved 1 to 3;`. Type names are resolved before they are compared, so `Address`, `User.Address` and `.shop.v1.User.Address` are the same type when they name the same message, and renaming the package is one change rather than a change of every type. The files are compared on their own: the files they import are not read, and the types those declare are compared by their names. To compare imported declarations as well, [bundle](/docs/reference/cli/bundle) both versions first. With the `recommended` rules, a change is breaking when it breaks the programs exchanging encoded messages (the binary or the JSON encoding) or the code compiled against the generated classes: removing or renaming a declaration, changing the type, the label or the oneof of a field, the types or the streaming of a method, the package, or an option that decides the names of the generated code (e.g. `go_package`). Adding declarations is non-breaking, and comments are non-functional. Removing a field or an enum value is non-breaking when the destination file reserves its number, which is how Protobuf files retire them. Changes are reported at their positions in the destination file; what has been removed is reported at the closest enclosing declaration that is still there. The changelog report lists the changed metadata (package, imports, file options), and the added, removed and modified messages, enums, services and extensions, with the changes of each modified one: ```text shop.proto 19 1 breaking Modified message 'Customer' 19 1 non-breaking Removed field 'fax' 25 3 breaking Modified field 'full_name' 25 3 breaking Modified field name 36 3 non-breaking Modified message 'Address' 39 5 non-breaking Added field 'country' ``` ## GraphQL files Files with the `.graphql`, `.graphqls` or `.gql` extension are compared as GraphQL schemas; both files must be GraphQL files. The differ compares their type systems: the root operation types, the types of every kind with their fields, arguments, input fields, default values, interfaces, union members and enum values, the directive definitions, the directives applied to all of these, and descriptions. Operations and fragments are ignored. Type extensions (`extend type`, `extend schema`, ...) are merged into what they extend, so moving a field from a type into an extension, or reordering definitions, is not a change. Everything is matched by its name. When a schema has no `schema` definition, the types named `Query`, `Mutation` and `Subscription` are its root operation types, so adding a `schema` definition that names them changes nothing. The files are compared on their own: types defined elsewhere are compared by their names. With the `recommended` rules, a change is breaking when operations that were valid become invalid, or when clients can no longer read their results, as graphql-js's `findBreakingChanges()` decides it: - removing a type, a field, an argument, an input field, an enum value, a union member, an implemented interface, a directive or one of its locations, or changing the kind of a type or a root operation type; - adding a required argument or input field: one with a non-null type and no default value; - changing a type in an incompatible way. The type of a field may become stricter (`String` to `String!`, `[String]` to `[String!]!`); the type of an argument or input field may become looser (`String!` to `String`). Named types must stay the same, and lists must stay lists; - removing the default value of a non-null argument or input field, which operations that omitted it relied on; - making a directive non-repeatable, or adding `@oneOf` to an input type. Additions are non-breaking, as are changed default values and applied directives such as `@deprecated`; descriptions are non-functional. Changes are reported at their positions in the destination file; what has been removed is reported at the closest enclosing definition that is still there. The changelog report lists the changed metadata (root operation types, the description and the directives of the schema), and the added, removed and modified scalars, objects, interfaces, unions, enums, input objects and directives, with the changes of each modified one: ```text shop.graphql 36 1 breaking Modified object 'Customer' 44 3 non-breaking Added field 'phone' 36 1 breaking Removed field 'fax' 38 3 non-breaking Modified field 'name' 38 3 non-breaking Modified field type ``` ## JSON output With `--formatter json` every report is an object whose `formatVersion` member is the version of the format. It changes only when a member is removed or changes meaning (see [Compatibility](/docs/reference/cli/compatibility)). The JSON Schema of the three reports is [apigenie-diff-report.json](https://apigenie.pl/schemas/apigenie-diff-report.json). - `--report status`: `{ "formatVersion": 1, "status": "breaking" }`. - `--report changelog`: `formatVersion`, `metadata` (which top-level fields changed) and groups of added, modified and removed items named after the elements of the format, e.g. `added`, `modified` and `removed` operations for OpenAPI or `channels`, `operations` and `servers` for AsyncAPI. - `--report detailed`: `formatVersion` and `changes`, every change in document order: ```json5 { "formatVersion": 1, "changes": [ { "type": "key_removed", "severity": "breaking", "target": "paths", "config_key": "paths", "key": "/pets/{id}", "line": 9, "column": 3 } ] } ``` API Genie 0.9 and earlier wrote the reports without `formatVersion`, and the detailed one as the bare `changes` array. ## Configuration The differ is configured in the `commands.diff` block of the [configuration file](/docs/reference/cli/configuration) given with `--config`. The block itself configures the differ for OpenAPI documents; its `swagger`, `asyncapi`, `protobuf` and `graphql` sections configure the other formats. Without `--config`, or without a section for a format, the format is compared with its `recommended` rules. A block or section starts only from the rulesets in its `extends`, so list `recommended` there to change only some of the recommended rules. ### diff The configuration of the differ for OpenAPI documents. It accepts these keys: | Key | Description | | --- | ----------- | | `extends` | Rulesets to start from: `recommended`. | | `rules` | Severities of the changes of the properties of each node type. | | `request`, `response` | Overlays of `rules` for the data sent by API consumers (parameters, request bodies) and by API providers (responses). Inside callbacks and webhooks the directions are swapped. A component schema takes the direction of the places it is used in (following the references between schemas, in either document); one used both ways, or nowhere, gets the stricter severity. Only node types and properties, no `defaults` or `extensions`. | | `overrides` | A list of `where` (`location`: one pattern or a list of them, and optionally `direction`: `request` or `response`) and `rules` applied to the matching locations. | The rules set a severity (`breaking`, `non_breaking`, `non_functional`, `none` to ignore the change, or `nested` to take the highest severity of the changes inside) for the properties of each node type: | Node type | Properties | | --------- | ---------- | | `openapi` | `openapi`, `info`, `jsonSchemaDialect`, `servers`, `paths`, `webhooks`, `components`, `security`, `tags`, `externalDocs` | | `info` | `title`, `summary`, `description`, `termsOfService`, `contact`, `license`, `version` | | `server` | `url`, `description`, `variables` | | `serverVariable` | `enum`, `default`, `description` | | `components` | `schemas`, `responses`, `parameters`, `examples`, `requestBodies`, `headers`, `securitySchemes`, `links`, `callbacks`, `pathItems` | | `pathItem` | `summary`, `description`, `parameters`, `operations`, `servers` | | `operation` | `tags`, `summary`, `operationId`, `description`, `externalDocs`, `deprecated`, `parameters`, `requestBody`, `responses`, `callbacks`, `security`, `servers` | | `parameter` | `name`, `in`, `description`, `required`, `deprecated`, `allowEmptyValue`, `style`, `explode`, `allowReserved`, `schema`, `content`, `example`, `examples` | | `header` | the properties of `parameter`, except `in` and `style` | | `requestBody` | `description`, `content`, `required` | | `response` | `description`, `headers`, `content`, `links` | | `mediaType` | `schema`, `encoding`, `example`, `examples` | | `encoding` | `contentType`, `headers`, `style`, `explode`, `allowReserved` | | `link` | `description`, `server`, `operationRef`, `operationId`, `parameters`, `requestBody` | | `example` | `summary`, `description`, `value`, `externalValue` | | `reference` | `$ref`, `summary`, `description` | | `schema` | `id`, `dialect`, `type`, `properties`, `required`, `readOnly`, `writeOnly`, `enum`, `discriminator`, `xml`, and the other JSON Schema keywords | | `discriminator` | `propertyName`, `mapping` | | `securityScheme` | `name`, `description`, `type`, `in`, `scheme`, `bearerFormat`, `flows`, `openIdConnectUrl` | | `oauthFlows` | `implicit`, `password`, `clientCredentials`, `authorizationCode` | | `oauthFlow` | `authorizationUrl`, `tokenUrl`, `refreshUrl`, `scopes` | | `paths`, `responses`, `callback`, `securityRequirement` | none: the node type itself is set to the rules of its items, e.g. `paths: { remove_item: breaking }` | | `contact`, `license`, `tag`, `externalDocumentation`, `xml` | their fields | A property is set to a severity, or to separate severities for `add`, `remove` and `modify`; lists and maps take `add_item`, `remove_item`, `modify_item`, `add_first_item` and `remove_last_item`. Properties that every node has, e.g. `parameter.name` or `schema.readOnly`, can only be modified, so they take a severity or `modify`. A severity can depend on a condition: `{ if: CONDITION, then: SEVERITY, else: SEVERITY }`, where the condition is `required` (the added or removed item is required), `deprecated` (it is deprecated), `sunset_passed` (it is deprecated and its `x-sunset` date is on or before the date of the comparison, `--today`), or `true` / `false` (the new value of a boolean property). The `extensions` property of a node type configures its `x-` extensions by name, with `*` for the extensions that are not listed, and `defaults.extensions` applies to all node types. `sunset_passed` is how the `recommended` rules let a deprecated element go once its window has closed: the `remove_item` of operations, path items (every operation deprecated and sunset), parameters, component schemas and parameters, response properties and their `required` items, and request enum values is non-breaking when the condition holds and breaking otherwise. An `x-sunset` is an RFC 3339 date (`2027-01-06`) or date-time next to `deprecated: true`; a property may carry them where it is used, as siblings of its `$ref`, or where it is defined; an enum value is deprecated when the schema's `x-deprecated-enum` array lists it, and the schema's `x-sunset` says when it may go. Deprecating itself (`deprecated: true` on an operation, a parameter, a header or a schema) is non-breaking: it announces the removal, and the removal is the breaking change. [`apigenie evolve plan`](/docs/reference/cli/evolve) writes documents that follow this convention. ```yaml version: '1.0' commands: diff: extends: [recommended] rules: defaults: extensions: non_functional pathItem: operations: # deprecated operations may be removed remove_item: if: deprecated then: non_breaking else: breaking response: schema: properties: # clients reject unknown properties add_item: breaking overrides: - where: location: /paths/~1internal~1*/** rules: operation: parameters: non_functional ``` The locations of `overrides` are JSON pointers into the document, where `*` matches any part of a segment and `**` any number of segments, e.g. `/paths/~1pets/get/parameters/query:limit`, `/paths/**/properties/name` or `/components/schemas/Pet/**`. The Swagger, AsyncAPI, Protobuf and GraphQL sections accept the same severities, rule forms and location patterns. ### diff.protobuf The configuration of the differ for Protobuf files. It accepts `extends` (`recommended`), `rules` and `overrides`; there are no `request` and `response` overlays. The OpenAPI settings of the `diff` block do not apply to Protobuf files; without this block, Protobuf files are compared with the `recommended` rules. The rules set a severity (`breaking`, `non_breaking`, `non_functional`, `none` to ignore the change, or `nested` to take the highest severity of the changes inside) for the properties of each node type: | Node type | Properties | | --------- | ---------- | | `file` | `package`, `imports`, `messages`, `enums`, `services`, `extends`, `comment` | | `import` | `kind` (`public` or `weak`) | | `message` | `fields`, `oneofs`, `messages`, `enums`, `extends`, `reservedNumbers`, `reservedNames`, `comment` | | `field` | `name`, `type`, `label`, `oneof`, `comment` | | `oneof` | `comment` | | `enum` | `values`, `reservedNumbers`, `reservedNames`, `comment` | | `enumValue` | `name`, `comment` | | `service` | `methods`, `comment` | | `method` | `inputType`, `outputType`, `clientStreaming`, `serverStreaming`, `comment` | | `extend` | `fields`, `comment` | Map fields and the fields of oneofs are configured as `field`. The `comment` of a declaration is its leading and trailing comment, that of a file the comment of its `syntax` statement. A property is set to a severity, or to separate severities for `add`, `remove` and `modify` (lists: `add_item`, `remove_item`, `modify_item`, `add_first_item`, `remove_last_item`). Properties that every declaration has, e.g. the `name`, `type` and `label` of a field or the types of a method, take only a severity or `modify`. A severity can depend on a condition: | Condition | Holds when | | --------- | ---------- | | `reserved` | the number of the removed field or enum value is reserved in the destination file | | `deprecated` | the added, removed or modified field or enum value has `deprecated = true` | | `wire_compatible` | the old and the new type (or label) of a field are encoded the same way, e.g. `int32` and `int64`, `string` and `bytes`, or a field with and without `optional` | | `true`, `false` | the new value of a boolean option is `true` or `false` (the old value for a removed option) | Options are configured by their names as written in the file, in the `options` property of `file`, `message`, `field`, `oneof`, `enum`, `enumValue`, `service` and `method`; `*` stands for the options that are not listed, and `defaults.options` applies to all node types. ```yaml version: '1.0' commands: diff: protobuf: extends: [recommended] rules: defaults: options: non_functional field: # only the encoded data matters: names and compatible types may change name: non_breaking type: modify: if: wire_compatible then: non_breaking else: breaking options: json_name: non_breaking "(validate.rules)": breaking message: fields: # removing a field is breaking even when its number is reserved remove_item: breaking overrides: - where: location: /messages/Internal*/** rules: message: fields: none ``` The locations of `overrides` follow the structure of the file, with declarations by their names, e.g. `/messages/User/fields/email/type`, `/messages/User/messages/Address/**`, `/enums/Status/values/*` or `/services/Users/methods/Get`. ### diff.swagger The configuration of the differ for Swagger 2.0 documents. It accepts the same keys as the `diff` block itself: `extends`, `rules`, the `request` and `response` overlays and `overrides`. The properties are those of the Swagger node types: | Node type | Properties | | --------- | ---------- | | `swagger` | `swagger`, `info`, `host`, `basePath`, `schemes`, `consumes`, `produces`, `paths`, `definitions`, `parameters`, `responses`, `securityDefinitions`, `security`, `tags`, `externalDocs` | | `info` | `title`, `description`, `termsOfService`, `contact`, `license`, `version` | | `pathItem` | `$ref`, `parameters`, `operations` | | `operation` | `tags`, `summary`, `description`, `externalDocs`, `operationId`, `consumes`, `produces`, `parameters`, `responses`, `schemes`, `deprecated`, `security` | | `parameter` | `name`, `in`, `description`, `required`, `schema`, `type`, `format`, `allowEmptyValue`, `items`, `collectionFormat`, and the validation keywords | | `response` | `description`, `schema`, `headers`, `examples` | | `header`, `items` | `type`, `format`, `items`, `collectionFormat`, the validation keywords, and `description` for headers | | `schema` | `title`, `description`, `type`, `format`, the validation keywords, `items`, `allOf`, `properties`, `required`, `maxProperties`, `minProperties`, `additionalProperties`, `discriminator`, `readOnly`, `xml`, `externalDocs`, `example` | | `securityScheme` | `type`, `description`, `name`, `in`, `flow`, `authorizationUrl`, `tokenUrl`, `scopes` | | `securityRequirement` | `scopes` | | `contact`, `license`, `tag`, `externalDocumentation`, `xml` | their fields | The validation keywords are `default`, `multipleOf`, `maximum`, `exclusiveMaximum`, `minimum`, `exclusiveMinimum`, `maxLength`, `minLength`, `pattern`, `maxItems`, `minItems`, `uniqueItems` and `enum`. For `additionalProperties`, `add` applies when additional properties become allowed, `remove` when they become forbidden, and `modify` to changes of their schema. The OpenAPI settings of the `diff` block do not apply to Swagger documents; without this block, Swagger documents are compared with the `recommended` rules. ```yaml version: '1.0' commands: diff: extends: [recommended] swagger: extends: [recommended] rules: swagger: host: non_breaking pathItem: operations: # deprecated operations may be removed remove_item: if: deprecated then: non_breaking else: breaking overrides: - where: location: /paths/~1internal~1*/** rules: operation: parameters: non_functional ``` The locations of `overrides` follow the structure of the document, with parameters by their keys and security requirements by the names of their schemes, e.g. `/paths/~1pets/get/parameters/query:limit/maximum`, `/paths/~1pets/post/parameters/body/schema/properties/name` or `/definitions/Pet/**`. ### diff.graphql The configuration of the differ for GraphQL schemas. It accepts `extends` (`recommended`), `rules` and `overrides`, like `diff.protobuf`. The OpenAPI settings of the `diff` block do not apply to GraphQL files; without this block, GraphQL files are compared with the `recommended` rules. The node types and their properties are: | Node type | Properties | | --------- | ---------- | | `schema` | `query`, `mutation`, `subscription`, `types`, `directiveDefinitions`, `description` | | `type` | `kind` (of types of all kinds) | | `scalar` | `description` | | `object` | `interfaces`, `fields`, `description` | | `interface` | `interfaces`, `fields`, `description` | | `union` | `members`, `description` | | `enum` | `values`, `description` | | `enumValue` | `description` | | `inputObject` | `fields`, `description` | | `field` | `type`, `arguments`, `description` | | `argument` | `type`, `defaultValue`, `description` | | `inputField` | `type`, `defaultValue`, `description` | | `directive` | `arguments`, `repeatable`, `locations`, `description` | The arguments of directive definitions are configured as `argument`. A severity can depend on a condition: | Condition | Holds when | | --------- | ---------- | | `required` | the added or removed argument or input field has a non-null type and no default value; for a default value, the type of the argument or input field is non-null | | `compatible` | the old and the new type of a field, argument or input field are compatible (see above) | | `deprecated` | the added or removed field, argument, input field or enum value has `@deprecated` | | `true`, `false` | the new value of `repeatable`, or of the only argument of an applied directive when it is a boolean (the old value for a removed directive) | Applied directives are configured by their names, without `@`, in the `directives` property of `schema`, `scalar`, `object`, `interface`, `union`, `enum`, `enumValue`, `inputObject`, `field`, `argument` and `inputField`; `*` stands for the directives that are not listed, and `defaults.directives` applies to all node types. Directives are compared with their arguments, whatever the order of the arguments and the form of the strings; the uses of a repeatable directive are compared together. ```yaml version: '1.0' commands: diff: graphql: extends: [recommended] rules: defaults: directives: non_functional field: directives: # authorization changes matter to clients auth: breaking enum: values: # deprecated values can be removed remove_item: if: deprecated then: non_breaking else: breaking overrides: - where: location: /types/Internal*/** rules: object: fields: none ``` The locations of `overrides` follow the structure of the schema, with definitions by their names, e.g. `/types/User/fields/email/type`, `/types/Query/fields/users/arguments/first/defaultValue`, `/types/Status/values/*`, `/directiveDefinitions/auth/locations/*` or `/query`. ### diff.asyncapi The configuration of the differ for AsyncAPI documents. It accepts the same keys as the `diff` block itself: `extends`, `rules`, `overrides`, and the `receive` and `send` overlays in place of `request` and `response` (also as the `direction` of an override). The properties are those of the AsyncAPI node types, e.g. `channel.address` or `message.payload`. The OpenAPI settings of the `diff` block do not apply to AsyncAPI documents; without this block, AsyncAPI documents are compared with the `recommended` rules. ```yaml version: '1.0' commands: diff: extends: [recommended] asyncapi: extends: [recommended] rules: channel: address: non_breaking send: schema: properties: # receivers reject unknown properties add_item: breaking overrides: - where: location: /channels/internal/** rules: message: payload: non_functional ``` --- # evolve Source: https://apigenie.pl/docs/reference/cli/evolve Plans safe API changes: `evolve plan` splits the breaking changes between two OpenAPI documents into an expand wave (deprecate, add next to) and a contract wave (remove), writes the wave documents and the plan, and proves the plan with `diff`. > **(experimental, outside the 1.0 compatibility promise)** `evolve plan` is the first version of > the planner: its recipes, the layout of `plan.yaml` and the options can still change, and > `evolve status` and `evolve next` will join it. It says so in its `--help` and on the first line > it prints, on the standard error. See the > [experimental commands](/docs/reference/cli/overview#experimental-commands). ## Usage ```sh apigenie evolve plan [OPTIONS] OLD_FILE NEW_FILE ``` ## Description `diff` says that a change breaks consumers. `evolve plan` says how to ship the same change without breaking anyone, the way database migrations do it: first **expand** (add the new shape next to the old one and deprecate the old one with a sunset date), let consumers move during a **window**, then **contract** (remove what was deprecated). The command takes the document consumers use today (`OLD_FILE`) and the document the change arrives at (`NEW_FILE`, usually the one in the pull request), and writes three files to the directory of `--out` (`evolve-out/` by default): | File | What it is | When to ship it | | ---- | ---------- | --------------- | | `1-expand.yaml` | The target document with every old element kept next to its replacement, `deprecated: true` and `x-sunset: `; non-breaking against `OLD_FILE` | now | | `2-contract.yaml` | The target document | on or after the sunset date | | `plan.yaml` | The plan: the waves, the units, the residue, the server notes | | The wave files take the format of `NEW_FILE` (`1-expand.json` for a JSON document) and keep its comments and layout. The sunset date is `--today` plus `--window` (90 days by default). ### Recipes Each breaking change of the diff becomes a **unit** with a recipe: | Change | Recipe | Expand wave | Contract wave | | ------ | ------ | ----------- | ------------- | | An operation or a whole path is removed | `remove-operation` | the operation stays, deprecated with its sunset date | removed | | An operation moved to another path (a hint says where, see below) | `move-path` | the old operation stays, deprecated | removed | | A response property is removed | `remove-response-property` | the property stays, deprecated (and required when it was) | removed | | A response property is renamed | `rename-response-property` | the old property stays, deprecated, next to the new one | removed | | A request property is renamed | `rename-request-property` | the old property stays, deprecated; neither name is required | the old one removed, the new one required when it was in `NEW_FILE` | | A request parameter is renamed | `rename-request-parameter` | the old parameter stays, deprecated and optional; the new one is optional | the old one removed, the new one required when it was | | A request parameter is removed | `remove-request-parameter` | the parameter stays, deprecated | removed | | A request property becomes required (new or existing) | `require-request-property` | present but optional | required | | A request parameter becomes required | `require-request-parameter` | optional | required | | A request enum value is removed | `remove-request-enum-value` | the value stays, listed in `x-deprecated-enum`, with `x-sunset` on the schema | removed | A rename is detected when an object loses one property (or an operation one parameter) and gains one whose schema is the same, after following `$ref`s and ignoring descriptions, titles, examples and extensions. Removed and added members that were not paired are listed as **rename candidates**; a hint decides them. A component schema is reported where it is used and under `components`; one unit covers all of them, and a schema used in requests and responses gets the request recipe, the stricter one. Changes the differ rates non-breaking need no recipe: they are in both waves as they are in `NEW_FILE`. Breaking changes no recipe makes safe are **residue**: a changed type or format, a response property no longer required, a new security requirement, a replaced server, a changed status code or media type. The plan lists them with a reason, the expand wave keeps the old shape there, the contract wave applies the change, and the command exits with status 1: these need a major version or a new resource. ### Server notes A document can say that both `fullName` and `name` exist; only the implementation can emit both or accept both. Each unit that needs it carries a **server note** (`server` in `plan.yaml`, the "Server checklist" of the report): emit both names until the sunset, accept both, apply a default when a soon-required property is missing, accept a deprecated enum value until the sunset. A plan whose server does not follow its notes is a lie the documents can't catch. ### Hints `--hints FILE` names what the diff can't see, as YAML or JSON: ```yaml renames: - from: "#/components/schemas/Customer/properties/fullName" to: "#/components/schemas/Customer/properties/name" - from: "GET /v1/parcels/{id}" to: "GET /v1/shipments/{id}" window: 90d reason: Unify naming with the shipments API ``` `renames` pairs a removed element with an added one: operations as `METHOD /path`, properties and parameters by their location, written the way the `overrides.where.location` of the [diff configuration](/docs/reference/cli/diff#configuration) is (`/components/schemas/Customer/properties/fullName`, `/paths/~1pets/get/parameters/query:page`; a leading `#` is accepted). A hint that names no removed and added element of the diff is an error. `window` applies when `--window` is not given, and `reason` is written into every unit. ### Verification Before anything is written, the plan is checked with the differ and the same configuration: 1. `diff OLD_FILE 1-expand.yaml` has no breaking change. 2. `diff 1-expand.yaml 2-contract.yaml --today ` has breaking changes only where a unit's contract step owns them (a `required` list it changes) or where the residue is; the scheduled removals are non-breaking thanks to the [`sunset_passed` condition](/docs/reference/cli/diff#configuration). 3. `2-contract.yaml` equals `NEW_FILE`, ignoring `x-sunset`, `x-deprecated-enum` and `x-apigenie-*` extensions. When a check fails, the report names the change and the unit, nothing is written, and the command exits with status 2. The same differ suggests the versions of the waves: a minor for the expand wave (a patch when it only adds extensions), a minor for a contract wave that only removes scheduled elements, a major for one that requires something or carries residue. ### plan.yaml ```yaml apigenie-evolve: 1 from: {path: api.v1.yaml, version: 1.4.0} to: {path: api.v2.yaml, version: 1.4.0} today: 2026-10-13 window: 90d waves: - id: expand file: 1-expand.yaml version: 1.5.0 units: [remove-operation-quoteLegacy, rename-response-property-Parcel-fullName] - id: contract file: 2-contract.yaml version: 1.6.0 notBefore: 2027-01-11 units: [remove-operation-quoteLegacy, rename-response-property-Parcel-fullName] units: remove-operation-quoteLegacy: recipe: remove-operation from: GET /quotes/legacy sunset: 2027-01-11 rename-response-property-Parcel-fullName: recipe: rename-response-property from: /components/schemas/Parcel/properties/fullName to: /components/schemas/Parcel/properties/name detected: true sunset: 2027-01-11 server: emit both fullName and name until 2027-01-11 residue: [] ``` A unit's `from` is `METHOD /path` for an operation and the element's location otherwise; `to` names the new element of a rename or a move, `value` the removed enum value, `detected` whether the planner found a rename itself (`true`) or a hint supplied it (`false`), `reason` comes from the hints, `sites` lists the places a component schema was reported at. Each residue item has its `location`, the `change` as `diff` describes it, and `why`. ## Options | Option | Description | | ------ | ----------- | | `--hints FILE` | The hints file: renames, the window, the reason. | | `--window WINDOW` | How long consumers have before the contract wave: days (`90d`) or weeks (`12w`). Defaults to the hints file's window, then to `90d`. | | `--today DATE` | The date the plan is made at (`YYYY-MM-DD`); the sunset dates are this plus the window. Defaults to the current date. | | `--config FILE`, `--no-config` | The configuration file whose `commands.diff` block rates the changes, as for [`diff`](/docs/reference/cli/diff#configuration). | | `--out DIR` | Where the plan and the wave documents go. Defaults to `evolve-out`. | | `--format FORMAT` | The report: `text` (default) or `json`. | ## JSON output With `--format json` the report is an object whose `formatVersion` member is the version of the format, changed only when a member is removed or changes meaning (see [Compatibility](/docs/reference/cli/compatibility)); its JSON Schema is [apigenie-evolve-report.json](https://apigenie.pl/schemas/apigenie-evolve-report.json). ```json5 { "formatVersion": 1, "result": "planned", // or "residue", or "failed" "plan": { "apigenie-evolve": 1, "...": "the same object as plan.yaml" }, "checks": [ { "id": "expand-non-breaking", "status": "ok" }, { "id": "contract-scheduled-only", "status": "ok" }, { "id": "contract-equals-target", "status": "ok" } ], "serverNotes": [{ "unit": "rename-response-property-Parcel-fullName", "note": "emit both fullName and name until 2027-01-11" }], "files": ["evolve-out/plan.yaml", "evolve-out/1-expand.yaml", "evolve-out/2-contract.yaml"] } ``` ## Exit status | Status | Meaning | | ------ | ------- | | 0 | Every breaking change was placed in a wave; the files were written | | 1 | The files were written, but some changes are residue (listed in the report) | | 2 | Invalid options, a file that can't be read or isn't an OpenAPI 3.x document, a hint that names nothing, an invalid configuration, or a failed check (nothing written) | ## Examples Plan the change of a pull request, then let `diff` confirm the waves: ```sh apigenie evolve plan api.main.yaml api.pr.yaml --out evolve-out apigenie diff api.main.yaml evolve-out/1-expand.yaml apigenie diff evolve-out/1-expand.yaml evolve-out/2-contract.yaml --today 2027-01-11 ``` With hints for a moved endpoint, a two-month window and a JSON report for a pipeline: ```sh apigenie evolve plan api.main.yaml api.pr.yaml --hints evolve.yaml --window 8w --format json ``` Serve the expand wave to old consumers before shipping it: ```sh apigenie-server evolve-out/1-expand.yaml ``` --- # format Source: https://apigenie.pl/docs/reference/cli/format Formats JSON and YAML files containing OpenAPI, Swagger 2.0 or AsyncAPI schemas, Protobuf (`.proto`) files and GraphQL (`.graphql`, `.graphqls`, `.gql`) files. ## Usage ```sh apigenie format [OPTIONS] FILES ``` ## Description This command formats JSON and YAML files containing OpenAPI, Swagger 2.0 or AsyncAPI 3.x schemas. The formatter recognizes the specification by the `openapi`, `swagger` or `asyncapi` string property of the document root (see [Swagger configuration](#swagger-configuration)). Files that contain none of them are reported as errors and left untouched. Files with the `.proto` extension are formatted as proto3 files (see [Protobuf configuration](#protobuf-configuration)), and files with the `.graphql`, `.graphqls` or `.gql` extension as GraphQL documents (see [GraphQL configuration](#graphql-configuration)). This formatting helps to: - maintain a consistent style for your OpenAPI schemas - enhance readability of the schemas - facilitate collaboration on schemas among multiple developers API Genie formatter works with all supported file formats. However, it is important to note that it's not a generic JSON/YAML parser. The formatter can process partially invalid schemas. It fails only when it encounters JSON or YAML syntax error. This is different from commands like `lint` or `diff`, which need valid OpenAPI schemas. Currently, the formatter can change the following aspects of input schemas: - Indentation size, line endings, the final line break and the width at which long YAML values are folded - The YAML styles of strings, keys, sequences and mappings, globally or for chosen fields - Order of properties for objects defined by the OpenAPI specification, at every depth of OpenAPI 3.0, 3.1 and 3.2 documents, and of the names in maps such as components, properties or media types It never changes what a document means: formatting the output again gives the same output, and it holds the same values as the input. ### Comments The formatter keeps comments: the YAML comments of OpenAPI, Swagger and AsyncAPI documents (at the top and the end of the file, above keys, at the end of lines, inside sequences, between entries and after the last entry), the comments of Protobuf files and the `#` comments of GraphQL documents. A comment directly above a key belongs to that key and moves with it when properties are sorted; a comment at the top of the file that a blank line separates from the first key stays at the top. JSON has no comments, so a YAML document written as JSON loses them. `--strip-comments` drops the comments. With the `best-fit` string and key styles, strings that YAML 1.1 parsers (such as PyYAML, SnakeYAML or go-yaml v2) would read as booleans, numbers or merge keys are written in double quotes: `yes`, `No`, `on`, `OFF`, `y`, `n`, `0755`, `0b101`, `1_000`, `1:30`, `<<` and the like. The `original` style keeps the quoting of the source. ### Specifying property order Formatter command can be configured to list properties of OpenAPI objects in specific order. This greatly helps to improve the readability of OpenAPI schemas. Below are supported modes of ordering properties of schema objects: - **original** - list properties in the same order as in input file. This is the default behaviour. - **sorted** - sort properties alphabetically - **custom** - users can define property order in the configuration file Each mode can be useful in specific situations. For example, **original** mode is useful when modifying existing schemas to avoid excessive changes. **Sorted** mode is a natural order for elements like path items or shared schemas. Finally, **custom** order allows users to define a specific order for properties within schema objects, which can be beneficial for request/response bodies and operations where placing `summary` and `description` properties at the beginning improves readability. Property mode can be configured - Globally (default) for all schema objects - Per schema object type **Example** - Preserve original property order for all schema objects ```yaml commands: format: openapi: property_order: default: original ``` **Example** - Sort all path items within `Paths` objects alphabetically ```yaml commands: format: openapi: property_order: paths: sorted ``` **Example** - Specify custom property order for `Operation` objects ```yaml commands: format: openapi: property_order: operation: # place summary and description at the beginning, to help # understand operation's purpose - summary - description - tags - operationId - externalDocs - deprecated - parameters - requestBody - responses - callbacks - security - servers ``` Properties not explicitly listed in the configuration for a specific schema object type will be placed after the listed ones, in the same order as in the input file. **Example** If the configuration for the `Contact` object looks like: ```yaml commands: format: openapi: property_order: contact: - name - url - email ``` and the actual contact data before formatting looks like: ```yaml openapi: 3.1.0 info: contact: x-team: awesome name: You email: you@example.com url: your.site x-audience: internal x-stability: stable ... ``` formatting it will change it to: ```yaml openapi: 3.1.0 info: contact: name: You url: your.site email: you@example.com x-team: awesome x-audience: internal x-stability: stable ... ``` ## Options Without options, formatter formats input file(-s) using default configuration. The resulting content is written back into input files, without changing their format. ```sh apigenie format input1.yaml input2.json ``` ### --config FILE Optional path to the configuration file. If not specified, the default configuration is used. For details, see the [configuration](#configuration) section. ```sh apigenie format --config api-style-guide.yaml input1.yaml input2.json ``` ### --check Checks that the files are formatted, without writing anything. The path of every file that formatting would change is printed on its own line, and the command exits with 1 when there is at least one. A file whose output format differs from its own (see [Swagger configuration](#swagger-configuration)) always counts as a change. Files with syntax errors and unsupported documents are reported as without `--check`. Cannot be combined with `--output`. ```sh apigenie format --check api/*.yaml ``` ### --strip-comments Drops the comments of the files instead of keeping them: YAML comments of OpenAPI, Swagger and AsyncAPI documents, `//` and `/* */` comments of Protobuf files and `#` comments of GraphQL documents. GraphQL descriptions are part of the schema and are kept. A `# prettier-ignore` comment is dropped too, so the definition after it is formatted. ```sh apigenie format --strip-comments api/openapi.yaml ``` ### --openapi-fragment KIND Formats the files as fragments of OpenAPI documents, such as a schema kept in its own file and referenced with `$ref`. `KIND` is the object at the root of the files: `schema`, `pathItem`, `operation`, `parameter`, `requestBody`, `response`, `header`, `example`, `link`, `securityScheme` or `components`. The objects inside the fragment are recognized as in a whole document. Protobuf and GraphQL files are formatted as usual. ```sh apigenie format --openapi-fragment schema schemas/*.yaml ``` ### --output OUTPUT Optional path to the output file. If not specified, resulting content is written to the input file. If this option is given, formatter processes only a single file at a time. Note that the output format is determined by this file's extension. So this can be used to convert between JSON and YAML representations. A `.proto` or GraphQL input is written in its own format, whatever the output extension. ```sh apigenie format input.yaml --output output.json ``` ## Exit status The command exits with 0 when every file was formatted or, with `--check`, is already formatted, and with 1 when, with `--check`, a file is not formatted. It exits with 2 when a file has syntax errors or is not a supported document (the other files are still formatted or checked), or when the options can't be used together. See the [exit statuses](/docs/reference/cli/overview#exit-status) shared by the three binaries. ## Configuration Formatter stores its configuration for OpenAPI documents under the `#/commands/format/openapi` key, and for AsyncAPI documents under the `#/commands/format/asyncapi` key in the configuration file (see [AsyncAPI configuration](#asyncapi-configuration)). Swagger 2.0 documents use the `#/commands/format/swagger` key (see [Swagger configuration](#swagger-configuration)). Protobuf files use the `#/commands/format/protobuf` key (see [Protobuf configuration](#protobuf-configuration)), and GraphQL files the `#/commands/format/graphql` key (see [GraphQL configuration](#graphql-configuration)). All sections are optional. See the [global configuration](/docs/reference/cli/configuration) section to understand how this file is structured. A configuration replaces the defaults as a whole: the presets named by `extends` are applied first, in order, then the keys of the `openapi` section. Unknown keys, unknown object names and invalid values are errors, reported with their line and column in the configuration file; nothing is formatted then. **Example** - Recommended settings, with CRLF line endings and descriptions written as literal blocks ```yaml commands: format: extends: - recommended openapi: newline: crlf style_overrides: - fields: [description] string: literal ``` ### format The output format: `yaml`, `json` or `original`. Without it, or with `original`, every file keeps its own format. With another format, formatting `api.json` in place writes `api.yaml` next to it. ### indent The number of spaces of one level of indentation, from 2 to 9. The default is 2. ### force_array_indent Indents the elements of YAML block sequences under their key (`true`), instead of writing them at the level of the key (`false`, the default). ### style The styles of YAML values: `string` (`original`, `best-fit`, `plain`, `single-quoted`, `double-quoted`, `literal`, `folded`), `array` and `object` (`original`, `best-fit`, `block`, `flow`) and `key` (`original`, `best-fit`, `plain`, `single-quoted`, `double-quoted`). The default is `original` for all of them, which keeps the styles of the input. ### Layout | Key | Values | Default | Effect | | ----------------- | -------------- | ---------- | -------------------------------------------------------------------------------------------- | | `line_width` | 0 to 100000 | 0 | Folds long YAML scalars at this width. 0 never folds them. | | `newline` | `lf`, `crlf` | `lf` | The line endings of the output, YAML and JSON. | | `final_newline` | `true`, `false`| the writer's | Whether the output ends with a line break. Without it, YAML and indented JSON end with one. | | `document_marker` | `true`, `false`| `false` | Starts YAML documents with `---`. | ### style_overrides A list of rules giving the values of some fields their own YAML styles. A rule selects values by `fields` (the names of their fields), by `objects` (the [kinds](#property_order) of the objects holding them), or by both, and sets one or more of `string`, `array` and `object`, with the values of [style](#style). A rule without `objects` applies to the fields of OpenAPI objects only, never to those inside example values or extensions. When several rules match a value, the later ones win. **Example** - Literal descriptions, and flow sequences for the tags of operations and the required properties of schemas ```yaml commands: format: openapi: style_overrides: - fields: [description] string: literal - fields: [tags] objects: [operation] array: flow - fields: [required] objects: [schema] array: flow ``` ### property_order Keys of the `property_order` section are `default`, which sets the order of every object without an order of its own (`sorted` or `original`, the default), or one of the names below. Each key accepts `sorted`, `original` or an array of property names in the desired order. Properties that an array does not list are placed after the listed ones, in their order in the input file; this also holds for fields of later OpenAPI versions and for extensions. The last column shows the order used by the `recommended` configuration. | Key | Object | Recommended order | | ----------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `openapi` | OpenAPI (document root) | openapi, $self, info, jsonSchemaDialect, servers, paths, webhooks, components, security, tags, externalDocs | | `info` | Info | title, summary, description, version, termsOfService, contact, license | | `contact` | Contact | name, url, email | | `license` | License | name, identifier, url | | `server` | Server | url, name, description, variables | | `serverVariable` | Server Variable | description, default, enum | | `paths` | Paths (the path templates) | sorted | | `pathItem` | Path Item (in paths, webhooks, callbacks and components) | $ref, summary, description, parameters, get, post, put, patch, delete, query, options, head, trace, additionalOperations, servers | | `operation` | Operation (also `query` and `additionalOperations`) | tags, summary, operationId, description, externalDocs, deprecated, parameters, requestBody, responses, callbacks, security, servers | | `components` | Components | schemas, responses, parameters, requestBodies, headers, mediaTypes, pathItems, securitySchemes, links, callbacks, examples | | `externalDocumentation` | External Documentation (also of schemas) | description, url | | `parameter` | Parameter | name, in, description, required, deprecated, style, allowEmptyValue, explode, allowReserved, schema, content, example, examples | | `requestBody` | Request Body | description, required, content | | `mediaType` | Media Type | description, schema, itemSchema, encoding, prefixEncoding, itemEncoding, example, examples | | `encoding` | Encoding | contentType, style, headers, explode, allowReserved, encoding, prefixEncoding, itemEncoding | | `responses` | Responses (the status codes of an operation) | sorted | | `response` | Response | summary, description, headers, content, links | | `example` | Example | summary, description, dataValue, serializedValue, value, externalValue | | `link` | Link | description, server, operationRef, operationId, parameters, requestBody | | `header` | Header | name, description, required, deprecated, allowEmptyValue, style, explode, allowReserved, schema, content, example, examples | | `tag` | Tag | name, summary, description, parent, kind, externalDocs | | `reference` | Reference (see below) | $ref, summary, description | | `schema` | Schema, at any depth | $schema, $id, $anchor, $dynamicAnchor, $ref, $dynamicRef, title, description, $comment, type, format, nullable, const, enum, numeric, string, array and object keywords, allOf, oneOf, anyOf, not, if, then, else, default, examples, example, readOnly, writeOnly, deprecated, discriminator, xml, externalDocs, $defs, definitions | | `discriminator` | Discriminator | propertyName, mapping, defaultMapping | | `xml` | XML | nodeType, name, namespace, prefix, attribute, wrapped | | `securityScheme` | Security Scheme | name, description, type, in, scheme, bearerFormat, flows, openIdConnectUrl, oauth2MetadataUrl, deprecated | | `oauthFlows` | OAuth Flows | implicit, password, clientCredentials, authorizationCode, deviceAuthorization | | `oauthFlow` | OAuth Flow | authorizationUrl, deviceAuthorizationUrl, tokenUrl, refreshUrl, scopes | | `componentNames` | The names in each map of Components | default | | `webhooks` | The names of webhooks | default | | `callbacks` | The names in the `callbacks` map of an operation | default | | `callback` | Callback (its runtime expressions) | default | | `content` | The media types of a `content` map | default | | `headers` | The names of a `headers` map | default | | `properties` | The names in `properties`, `patternProperties`, `$defs` and `dependentSchemas` | default | | `scopes` | OAuth scopes | default | | `extension` | Everything inside the value of an `x-` field | default | | `exampleValue` | Everything inside example payloads, `default`, `const` and `enum` values | default | The keys of named maps and raw values are independent of the object keys: for example, `componentNames: sorted` with `properties: original` and a fixed `schema` order sorts the schemas of the components by name, keeps the properties of each schema in their order and puts the keywords of each schema in the fixed order. Arrays are never sorted. **Example** - Sorted component names, everything else as in the input ```yaml commands: format: openapi: property_order: componentNames: sorted ``` ### OpenAPI versions and object recognition The formatter recognizes the objects of OpenAPI 3.0, 3.1 and 3.2 by their place in the document, from the root down, so names chosen by the author (paths, component names, schema properties) are never mistaken for fields: - Schemas are recognized at every depth: under `properties`, `patternProperties`, `additionalProperties`, `items` (a schema, or the array of schemas of 3.0), `prefixItems`, `allOf`, `anyOf`, `oneOf`, `not`, `if`, `then`, `else`, `$defs`, `dependentSchemas`, `contains`, `propertyNames`, `unevaluatedItems`, `unevaluatedProperties` and `contentSchema`, and under the `itemSchema` of a 3.2 media type. Boolean schemas are left as they are. - Path items and operations are recognized in `paths`, `webhooks`, the `callbacks` of operations at any depth and in components, and so are 3.2 `query` operations and `additionalOperations`. - Headers, media types, encodings (also 3.2 `prefixEncoding` and `itemEncoding`) and examples are recognized wherever the specification allows them, including the content and examples of headers. - Example payloads, `default`, `const` and `enum` values and the values of `x-` fields are data: they are never formatted as OpenAPI objects, and their keys follow the `exampleValue` and `extension` orders. An object in a place that allows a Reference Object (a parameter, request body, media type, response, header, example, link, callback, security scheme or path item) follows the `reference` order when it has a `$ref` field. A path item is a Reference Object only when it has no other fields than `$ref`, `summary` and `description`: next to its operations, `$ref` is merged with them. A schema with `$ref` is always a schema, because `$ref` is a schema keyword since 3.1; the `schema` order of the `recommended` configuration puts `$ref` first. A document that is valid YAML or JSON is formatted even when it is not a valid OpenAPI document; values in unexpected places keep the default order. YAML anchors and aliases are not kept: each alias is written as a copy of its anchor's value, in YAML as in JSON. ## AsyncAPI configuration AsyncAPI documents are formatted with settings stored under the `#/commands/format/asyncapi` key. The section accepts the same keys as the OpenAPI one: `format`, `indent`, `force_array_indent`, `style` and `property_order`. **Example** - Recommended settings for both specifications, with a custom property order for AsyncAPI `Operation` objects ```yaml commands: format: extends: - recommended asyncapi: indent: 2 property_order: channels: sorted operation: - action - channel - summary - description - messages - reply ``` Keys of the `property_order` section are `default` or one of the following names of [AsyncAPI 3 objects](https://www.asyncapi.com/docs/reference/specification/v3.0.0). Each key accepts `sorted`, `original` or an array of property names in the desired order. The last column shows the order used by the `recommended` configuration. | Key | Object | Recommended order | | ----------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `asyncapi` | AsyncAPI (document root) | asyncapi, id, info, servers, defaultContentType, channels, operations, components | | `info` | Info | title, version, description, termsOfService, contact, license, tags, externalDocs | | `contact` | Contact | name, url, email | | `license` | License | name, url | | `servers` | Servers | original | | `server` | Server | host, protocol, protocolVersion, pathname, title, summary, description, variables, security, tags, externalDocs, bindings | | `serverVariable` | Server Variable | enum, default, description, examples | | `channels` | Channels | sorted | | `channel` | Channel | address, title, summary, description, servers, parameters, messages, tags, externalDocs, bindings | | `parameter` | Parameter | enum, default, description, examples, location | | `operations` | Operations | original | | `operation` | Operation | action, channel, title, summary, description, security, tags, externalDocs, bindings, traits, messages, reply | | `operationTrait` | Operation Trait | title, summary, description, security, tags, externalDocs, bindings | | `operationReply` | Operation Reply | address, channel, messages | | `operationReplyAddress` | Operation Reply Address | description, location | | `message` | Message | name, title, summary, description, contentType, correlationId, headers, payload, tags, externalDocs, bindings, examples, traits | | `messageTrait` | Message Trait | name, title, summary, description, contentType, correlationId, headers, tags, externalDocs, bindings, examples | | `messageExample` | Message Example | name, summary, headers, payload | | `correlationId` | Correlation ID | description, location | | `tag` | Tag | name, description, externalDocs | | `externalDocumentation` | External Documentation | description, url | | `components` | Components | schemas, servers, channels, operations, messages, securitySchemes, serverVariables, parameters, correlationIds, replies, replyAddresses, externalDocs, tags, operationTraits, messageTraits, serverBindings, channelBindings, operationBindings, messageBindings | | `reference` | Reference | $ref | | `schema` | Schema (component, message payload/headers) | title, description, type, format, const, enum, default, examples, minimum, exclusiveMinimum, maximum, exclusiveMaximum, multipleOf, minLength, maxLength, pattern, items, contains, minItems, maxItems, uniqueItems, required, properties, ... | | `securityScheme` | Security Scheme | type, description, name, in, scheme, bearerFormat, flows, openIdConnectUrl, scopes | | `oauthFlows` | OAuth Flows | implicit, password, clientCredentials, authorizationCode | | `oauthFlow` | OAuth Flow | authorizationUrl, tokenUrl, refreshUrl, availableScopes | | `bindings` | Server, Channel, Operation, Message Bindings | sorted | Properties that are not listed in a fixed order are placed after the listed ones, in the same order as in the input file. ## Swagger configuration Swagger 2.0 documents are recognized by the `swagger` string property of the document root, and formatted with settings stored under the `#/commands/format/swagger` key. The section accepts the same keys as the OpenAPI one: `format`, `indent`, `force_array_indent`, `style` and `property_order`. In YAML, the version must be quoted (`swagger: "2.0"`): an unquoted `2.0` is a number, and such documents are reported as unsupported. Every file keeps its own format unless `format` asks for another one: with `format: yaml`, formatting `swagger.json` in place writes `swagger.yaml` next to it. **Example** - Recommended settings, with sorted definitions and a custom property order for `Operation` objects ```yaml commands: format: extends: - recommended swagger: format: json indent: 2 property_order: definitions: sorted operation: - operationId - summary - tags - parameters - responses ``` Keys of the `property_order` section are `default` or one of the following names of [Swagger 2.0 objects](https://swagger.io/specification/v2/). Each key accepts `sorted`, `original` or an array of property names in the desired order. The last column shows the order used by the `recommended` configuration. | Key | Object | Recommended order | | ----------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `swagger` | Swagger (document root) | swagger, info, host, basePath, schemes, consumes, produces, paths, definitions, parameters, responses, securityDefinitions, security, tags, externalDocs | | `info` | Info | title, description, version, termsOfService, contact, license | | `contact` | Contact | name, url, email | | `license` | License | name, url | | `paths` | Paths | sorted | | `pathItem` | Path Item | $ref, parameters, get, post, put, patch, delete, options, head | | `operation` | Operation | tags, summary, operationId, description, externalDocs, deprecated, schemes, consumes, produces, parameters, responses, security | | `externalDocumentation` | External Documentation | description, url | | `parameter` | Parameter (in path items, operations and `parameters`) | $ref, name, in, description, required, allowEmptyValue, schema, type, format, items, collectionFormat, default, enum, minimum, exclusiveMinimum, maximum, exclusiveMaximum, multipleOf, minLength, maxLength, pattern, minItems, maxItems, uniqueItems | | `items` | Items (of parameters and headers) | type, format, items, collectionFormat, default, enum, minimum, exclusiveMinimum, maximum, exclusiveMaximum, multipleOf, minLength, maxLength, pattern, minItems, maxItems, uniqueItems | | `responses` | Responses (of an operation) | sorted | | `response` | Response (in operations and `responses`) | $ref, description, schema, headers, examples | | `header` | Header | description, type, format, items, collectionFormat, default, enum, minimum, ..., uniqueItems | | `schema` | Schema, including nested ones | $ref, title, description, type, format, enum, default, minimum, exclusiveMinimum, maximum, exclusiveMaximum, multipleOf, minLength, maxLength, pattern, items, minItems, maxItems, uniqueItems, required, properties, additionalProperties, ... | | `xml` | XML | name, namespace, prefix, attribute, wrapped | | `definitions` | Definitions (`definitions`) | original | | `parametersDefinitions` | Parameters Definitions (`parameters` of the root) | original | | `responsesDefinitions` | Responses Definitions (`responses` of the root) | original | | `securityDefinitions` | Security Definitions | original | | `securityScheme` | Security Scheme | type, description, name, in, flow, authorizationUrl, tokenUrl, scopes | | `tag` | Tag | name, description, externalDocs | Schemas are recognized at any depth: in `definitions`, in body parameters and responses, and inside `properties`, `items`, `additionalProperties` and `allOf`. Values that are not Swagger objects, such as extensions (`x-*`), `example`, `examples`, `default`, `enum` and `scopes`, are ordered by the `default` setting only. Properties that are not listed in a fixed order are placed after the listed ones, in the same order as in the input file. ## Protobuf configuration Files with the `.proto` extension are formatted as proto3 files, with settings stored under the `#/commands/format/protobuf` key. The formatter: - writes every declaration in a canonical layout: one statement per line, normalized spacing, blank lines between top-level declarations and around nested messages, enums and extends - indents nested declarations by the configured number of spaces - optionally sorts imports, options and declarations No comment is lost. Comments are written as `//` line comments and stay with their declarations when declarations are sorted. Comments that protoc ignores, such as those inside a declaration (`int32 /* type */ id = 1;`), are written as `/* */` comments just before the end of their declaration (`int32 id = 1 /* type */;`). Files that are syntactically correct but break proto3 rules, for example with duplicate field numbers, are formatted. Files with syntax errors are reported with the position of each error and left untouched. Editions and proto2 files are not supported. | Key | Values | Recommended | Description | | ------------------- | --------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `indent` | 1 to 8 | 2 | Number of spaces for one level of nesting. | | `sort.imports` | `original`, `sorted` | `sorted` | Sorts imports by path. | | `sort.options` | `original`, `sorted` | `sorted` | Sorts option statements and the options in brackets (`[deprecated = true, json_name = "x"]`) by name. | | `sort.declarations` | `original`, `grouped`, `sorted` | `original` | `grouped` puts top-level declarations in this order: package, imports, options, services, messages, enums, extends. `sorted` also sorts services, messages, enums and extends by name within their groups. | | `sort.fields` | `original`, `number`, `name` | `original` | Sorts the fields of messages, oneofs and extends. A oneof is placed by its lowest field number (or its name), and its fields are sorted too. | | `sort.enum_values` | `original`, `number`, `name` | `original` | Sorts enum values. A first value of zero stays first, as proto3 requires. | | `sort.nested_types` | `original`, `sorted` | `original` | Sorts the messages, enums and extends nested in a message by name. | | `sort.methods` | `original`, `sorted` | `original` | Sorts the methods of services by name. | Without a configuration file, the recommended settings are used. With `extends: [original]`, nothing is sorted. Each kind of declaration is sorted among the positions that declarations of that kind take in its body, so sorting fields, for example, does not move options, reserved statements or nested messages. Only the `grouped` and `sorted` values of `sort.declarations` move top-level declarations between kinds. Changing the order of fields, enum values or methods does not change the wire format, but it changes the order of declarations in generated code and descriptors. For this reason, the recommended configuration sorts only imports and options. **Example** - Recommended settings with 4-space indentation, and fields sorted by number ```yaml commands: format: extends: - recommended protobuf: indent: 4 sort: declarations: grouped fields: number enum_values: number ``` ```sh apigenie format api/store/v1/*.proto --config api-style-guide.yaml ``` ## GraphQL configuration Files with the `.graphql`, `.graphqls` or `.gql` extension are formatted as GraphQL documents, both schemas (type system definitions) and operations, with settings stored under the `#/commands/format/graphql` key. The layout is that of [Prettier](https://prettier.io): a file formatted by Prettier with the same line width and indentation is left unchanged. The formatter: - writes selection sets, fields, enum values and input fields one per line - keeps argument lists, variable definitions, directives, list and object values, `implements` lists and union members on one line if they fit in the line width, and puts one item per line otherwise - keeps single blank lines between definitions and between members; several blank lines become one, and no blank lines are added - writes block strings (`"""`) on lines of their own, and trims the spaces around the text of a single line block string - keeps shorthand queries (`{ ... }`) and the `query` keyword as written - optionally sorts definitions and the members of types No comment is lost. Comments stay before or after the node they are written next to, and move with it when it is sorted. A node with a `# prettier-ignore` comment before it is written as it is in the source. Documents that are syntactically correct but break validation rules, for example with fields of unknown types, are formatted. Files with syntax errors are reported with the position of each error and left untouched. The output differs from that of Prettier in a few cases, where Prettier loses comments or writes documents that cannot be read back: - control characters in quoted strings, other than tabs, are written as escapes (`"\r"`, `"\u0001"`) instead of as they are - two comments after nodes that end on the same line are written on two lines; Prettier writes them on one line, which makes the second one part of the first - a comment on its own line between a description and a definition, or after the last variable or argument before `)`, stays there; Prettier moves it into the middle of a line, and moves it again on the next run Like Prettier, the formatter drops a blank line that follows a comment at the end of a member, and attaches comments written in the middle of a definition, such as between a field name and its type, to the nearest node, from where they can move again on the next run. | Key | Values | Recommended | Description | | -------------------------- | ------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `indent` | 1 to 8 | 2 | Number of spaces for one level of nesting. | | `line_width` | 20 to 1000 | 80 | Line length that groups must fit in to stay on one line. East Asian wide characters count as two columns. | | `sort.definitions` | `original`, `grouped`, `sorted` | `original` | `grouped` puts definitions in this order: schema, directives, scalars, enums, interfaces, object types, input types, unions, operations, fragments. `sorted` also sorts them by name within their groups, with extensions after the definitions they extend. | | `sort.fields` | `original`, `sorted` | `original` | Sorts the fields of object types, interfaces and input types, and of their extensions, by name. | | `sort.enum_values` | `original`, `sorted` | `original` | Sorts enum values by name. | | `sort.arguments` | `original`, `sorted` | `original` | Sorts the argument definitions of fields and directives by name. | | `sort.union_members` | `original`, `sorted` | `original` | Sorts the member types of unions by name. | | `sort.interfaces` | `original`, `sorted` | `original` | Sorts the interfaces that types implement by name. | | `sort.directive_locations` | `original`, `sorted` | `original` | Sorts the locations of directive definitions by name. | Without a configuration file, the recommended settings are used. Neither the recommended nor the `original` configuration sorts anything, because the order of definitions and fields shows in introspection results, generated code and documentation. Selections, arguments and variables of operations are never sorted, as selections define the order of the response. **Example** - 4-space indentation, a line width of 100 characters, and definitions and fields sorted by name ```yaml commands: format: extends: - recommended graphql: indent: 4 line_width: 100 sort: definitions: sorted fields: sorted ``` ```sh apigenie format schema/*.graphql --config api-style-guide.yaml ``` --- # i18n Source: https://apigenie.pl/docs/reference/cli/i18n Extracts texts for localization from OpenAPI, Swagger 2.0 and AsyncAPI specifications and Protobuf and GraphQL files, checks and updates their translations, and applies them. All subcommands work on all five: `i18n extract` writes the texts as a key-value file, `i18n check` tells whether a translation file is complete, `i18n sync` updates one after the document changed, and `i18n apply` puts the translated values back. ## Usage ```sh apigenie i18n extract [OPTIONS] FILE apigenie i18n check --translations TRANSLATIONS [OPTIONS] FILE apigenie i18n sync --previous PREVIOUS --translations TRANSLATIONS [OPTIONS] FILE apigenie i18n apply --translations TRANSLATIONS [--translations TRANSLATIONS ...] [OPTIONS] FILE ``` In version 0.9.0 `i18n` became a command with subcommands: `apigenie i18n FILE --output DEST` is now `apigenie i18n extract FILE --output DEST`. A typical workflow: 1. `apigenie i18n extract openapi.yaml --output i18n-en.json` writes the texts of the spec. 2. Translators turn `i18n-en.json` into `i18n-pl.json`, changing the values and keeping the keys. 3. `apigenie i18n check openapi.yaml --translations i18n-pl.json` fails, in CI for instance, until every text is translated and nothing else is. 4. `apigenie i18n apply openapi.yaml --translations i18n-pl.json --output openapi.pl.yaml` writes the Polish spec. 5. When the spec changes, `apigenie i18n sync openapi.yaml --previous i18n-en.json --translations i18n-pl.json --output i18n-pl.json` keeps the translations of the unchanged texts and lists the new texts, the changed ones (their translations may be stale) and the removed ones; then `i18n extract` writes the new `i18n-en.json`. The translation files can also be given to [`apigenie-generator docs`](/docs/reference/generator/docs) with `--i18n pl=i18n-pl.json`, which writes a translated copy of each document into the documentation site, with a locale switcher, without writing a translated spec. | Input | Recognized by | Keys | Texts | | --- | --- | --- | --- | | OpenAPI 3.0, 3.1, 3.2 | the `openapi` property (JSON or YAML) | JSON pointers | titles, summaries, descriptions, names | | Swagger 2.0 | the `swagger` property | JSON pointers | titles, summaries, descriptions, names | | AsyncAPI 3.x | the `asyncapi` property | JSON pointers | titles, summaries, descriptions, names | | Protobuf | the `.proto` extension | fully qualified names | leading comments | | GraphQL | the `.graphql`, `.graphqls` or `.gql` extension | schema coordinates and names | descriptions | ## i18n extract This command extracts texts suitable for translations, from an OpenAPI, Swagger 2.0 or AsyncAPI 3.x document, or from a Protobuf or GraphQL file. Files with the `.proto` extension are read as Protobuf files (see [Protobuf files](#protobuf-files)), and files with the `.graphql`, `.graphqls` or `.gql` extension as GraphQL files (see [GraphQL files](#graphql-files)); the kind of any other document is detected by its `openapi`, `swagger` or `asyncapi` property. Extracted texts are written in key-value files. For OpenAPI, Swagger and AsyncAPI, keys are JSON pointers to locations in the input file, and values are the strings the keys point to. For Protobuf and GraphQL files, keys are the full names of the documented declarations (see below). Example: ```sh apigenie i18n extract --output i18n-en.json openapi-in.yaml ``` may produce the following output: ```yaml /info/title: My API /info/contact/name: My Team ``` The command extracts the following properties of OpenAPI documents: | Object | Properties | | ------ | ---------- | | `Info` | `title`, `summary` (3.1+), `description` | | `Contact` | `name` | | `License` | `name` | | `Server` | `name` (3.2), `description` | | `ServerVariable` | `description` | | `PathItem` | `summary`, `description` | | `Schema` | `title`, `description` | | `Response` | `description` | | `Parameter` | `description` | | `Example` | `summary`, `description` | | `RequestBody` | `description` | | `Header` | `description` | | `SecurityScheme` | `description` | | `OAuthFlow` | the description of each scope, keyed `.../scopes/` | | `Link` | `description` | | `Operation` | `summary`, `description` | | `ExternalDocumentation` | `description` | | `Tag` | `summary` (3.2), `description` | | Reference Object | `summary`, `description` (3.1+): they override those of the referenced object at the place of the `$ref` only, so they are keyed there | Schemas are extracted wherever they are, including under `$defs`, `dependentSchemas` and `additionalItems` (`definitions` is not an OpenAPI 3.1 keyword, and is skipped). Identifiers and values that are part of the API's meaning are never extracted: names of tags, parameters, properties and scopes, `operationId`, URLs, enum values, defaults, examples, `xml.name` and `$comment`. the following properties of Swagger 2.0 documents, as for OpenAPI: | Object | Properties | | ------ | ---------- | | `Info` | `title`, `description` | | `Contact` | `name` | | `License` | `name` | | `Operation` | `summary`, `description` | | `Parameter` | `description` | | `Response` | `description` | | `Header` | `description` | | `Schema` | `title`, `description` | | `SecurityScheme` | `description` | | `ExternalDocumentation` | `description` | | `Tag` | `description` | and the following properties of AsyncAPI documents: | Object | Properties | | ------ | ---------- | | `Info` | `title`, `description` | | `Contact` | `name` | | `License` | `name` | | `Server` | `title`, `summary`, `description` | | `ServerVariable` | `description` | | `Channel` | `title`, `summary`, `description` | | `Parameter` | `description` | | `Operation` | `title`, `summary`, `description` | | `OperationTrait` | `title`, `summary`, `description` | | `OperationReplyAddress` | `description` | | `Message` | `title`, `summary`, `description` | | `MessageTrait` | `title`, `summary`, `description` | | `MessageExample` | `summary` | | `CorrelationId` | `description` | | `Schema` | `title`, `description` | | `SecurityScheme` | `description` | | `ExternalDocumentation` | `description` | | `Tag` | `description` | Texts are extracted where they are defined. An object used through a reference is extracted once, under `components` for instance, and objects kept in other files are not extracted; bundle the document first (`apigenie bundle`) to extract them too. Schemas in formats other than AsyncAPI and JSON Schema, such as Avro, and specification extensions are skipped. ### Protobuf files The texts of a Protobuf file are the leading comments of its declarations: the comment block right above a declaration, as `protoc` attaches it. Keys are fully qualified names instead of JSON pointers: | Declaration | Key | | ----------- | --- | | The file (the comment of its `syntax` statement) | the package, e.g. `acme.store.v1` | | Message, enum, service | `acme.store.v1.Order` | | Field, map field, oneof | `acme.store.v1.Order.id` | | Field of a oneof | the key of a field of the message, `acme.store.v1.Order.card` | | Enum value | under its enum, `acme.store.v1.Order.Status.STATUS_OPEN` | | Method | `acme.store.v1.OrderService.CreateOrder` | | Extension field | in the scope of its `extend` block, `acme.store.v1.max_length` | For example, the file ```proto syntax = "proto3"; package acme.greeter.v1; // Greets people. service Greeter { // Returns a greeting for one person. rpc SayHello(HelloRequest) returns (HelloReply); } ``` gives ```json { "acme.greeter.v1.Greeter": "Greets people.", "acme.greeter.v1.Greeter.SayHello": "Returns a greeting for one person." } ``` Comment markers are removed, as are one space at the start of each line, the whitespace at the end of each line, and empty lines at the start and the end of a comment. Line breaks inside a comment are kept. In a file without a package, the keys start with the top-level names and the file comment has the empty key. Trailing comments, detached comments (separated from the declaration by an empty line), and comments on the package, imports, options, `extend` blocks and `reserved` statements are not extracted. Imported files are not read. ### GraphQL files The command extracts the descriptions of GraphQL definitions, keyed by their full names: | Definition | Key | Example | | ---------- | --- | ------- | | Schema | `schema` | `schema` | | Scalar, object, interface, union, enum and input object type | type name | `Book` | | Field of an object or interface type | `Type.field` | `Book.title` | | Argument of a field | `Type.field(argument:)` | `Query.search(text:)` | | Field of an input object type | `Input.field` | `BorrowInput.days` | | Enum value | `Enum.VALUE` | `Genre.POETRY` | | Directive | `@directive` | `@key` | | Argument of a directive | `@directive(argument:)` | `@key(fields:)` | | Operation | operation type and name | `query FindBooks` | | Variable of an operation | `operation($variable:)` | `query FindBooks($text:)` | | Fragment | `fragment Name` | `fragment BookSummary` | Type system keys are the schema coordinates of the GraphQL specification. Fields, arguments and enum values of type extensions (`extend type`) are named after the extended type. An anonymous operation is keyed by its operation type only, e.g. `query`. Empty descriptions are skipped, and the document must be valid: duplicate names are reported as errors. For example, ```sh apigenie i18n extract --output i18n-en.yaml schema.graphql ``` may produce: ```yaml Query: The entry points of the library API Query.search(text:): The words to look for schema: The library service ``` ### Options #### -o, --output DEST Name of the file with extracted texts. Format of the file is determined by its extension: `.json`, or `.yaml` (`.yml`). Without it nothing is written: the file is only read and its problems reported. ## i18n apply This command replaces the texts of a document by their translations and writes the translated document. A translation file has the format `i18n extract` writes, in JSON or YAML: an object whose keys are those `i18n extract` gives for the document and whose values are the translated texts. Applying the texts `i18n extract` gives, untranslated, changes nothing. ```sh apigenie i18n apply --translations i18n-pl.json openapi.yaml --output openapi.pl.yaml ``` - A key that addresses nothing in the document (a key of an older version of the document, a mistyped one) is reported as a warning, and the other keys are applied. With `--strict`, it is an error, and nothing is written. - The translation files can be repeated: they are applied in order, so the translations of later files override those of earlier ones. - Comments are kept, unless `--strip-comments` is given. - A file that can't be read or parsed, or a translation that can't be applied (see below), is an error, and nothing is written. ### OpenAPI, Swagger and AsyncAPI specifications The keys are JSON pointers into the document (JSON or YAML). With `i18n-pl.json` ```json { "/info/title": "Moje API", "/paths/~1pets/get/summary": "Lista zwierząt" } ``` the title of the API and the summary of `GET /pets` are translated. Everything else is kept as it is: other values, the order of properties, and the YAML comments of the document, those of the translated texts included. - Only the texts `i18n extract` extracts are translated. A key pointing anywhere else, to an `operationId`, a URL, an object or a value that is not a string, addresses nothing: it is reported as a warning (an error with `--strict`), and the value is kept. The document must be a valid specification, so that its texts can be extracted. - Keys that are not JSON pointers, such as those of Protobuf and GraphQL files, and translations that are not strings, are errors. - References are not followed: a text used through a `$ref` is translated where it is defined, as `i18n extract` extracts it there. ### Protobuf files The keys are the fully qualified names `i18n extract` gives, and each translation replaces the leading comment of its declaration (the comment of the `syntax` statement for the package key): ```sh apigenie i18n apply --translations i18n-de.json greeter.proto --output greeter.de.proto ``` The comment is rewritten in place, in its style: `//` lines stay `//` lines with their indentation, and a `/* */` comment stays one, on one line if it was and the translation fits on one, as a `/*`, ` * line`, ` */` block otherwise. A translation containing `*/` is written as `//` lines, and an empty translation removes the comment. Nothing else changes: layout, trailing and detached comments, and the comments without a translation are kept byte for byte. A key naming a declaration without a leading comment addresses nothing. ### GraphQL files The keys are the schema coordinates and names `i18n extract` gives, and each translation replaces the description of its definition: ```json { "Query": "Die Einstiegspunkte der Bibliotheks-API", "Query.search(text:)": "Die zu suchenden Wörter" } ``` The description is rewritten in place, in its form: a block string (`"""..."""`) stays one, on one line if it was and the translation fits on one, on lines of their own at the indentation of the original otherwise; a quoted string (`"..."`) stays quoted, with escapes. A translation a block string can't hold (control characters, a common indentation of all lines) is quoted. Everything else is kept byte for byte, `#` comments included. A key naming a definition without a description addresses nothing. ### Options #### --translations TRANSLATIONS Path to a translation file, in JSON or YAML. Required. It can be repeated: the files are applied in order, so the translations of later files override those of earlier ones. #### -o, --output OUTPUT Path to the translated document. Defaults to the standard output. #### --format FORMAT Format of a translated specification, `json` or `yaml`. Defaults to the format of the input. Not available for Protobuf and GraphQL files, which are written in their own format. #### --strict Fails, without writing anything, when a key of a translation file addresses nothing in the document. #### --strip-comments Drops the comments of the document instead of keeping them: the YAML comments of a specification, the `#` comments of a GraphQL file (descriptions are kept, they are part of the schema). Not available for Protobuf files, whose translated texts are comments. ## i18n check This command compares a translation file with the texts of a document, and fails unless every text has a translation and every key of the file is a text. It is meant for CI: run it before publishing a translation. ```sh apigenie i18n check openapi.yaml --translations i18n-pl.json ``` `FILE` is a spec, a Protobuf or GraphQL file, whose texts are extracted as `i18n extract` does, or a file `i18n extract` wrote. The command reports, as errors: - the texts without a translation, in the order of the texts; - the texts whose translation is empty, unless `--allow-empty` is given; - the keys that are not texts of the document: a key of an older version of the document, a mistyped one, or one pointing to a value that is not meant to be translated, such as an `operationId`; - the keys that are not JSON pointers (for specs), and the translations that are not strings. It then writes, on the standard output, how many texts are translated: ```text i18n-pl.json: 74 of 76 texts translated (97.4%). ``` A text whose source changed after it was translated still counts as translated: `i18n sync` finds those. ### Options #### --translations TRANSLATIONS Path to the translation file, in JSON or YAML. Required. #### --allow-empty Counts empty translations (`""`) as translated, for texts meant to be empty in the language. #### --format FORMAT `text` (the default) or `json`. With `json`, nothing is written to the standard error, and the standard output is an object: ```json { "complete": false, "translated": 74, "total": 76, "coverage": 0.9736842105263158, "missing": ["/info/description", "/paths/~1pets/get/summary"], "empty": [], "unknown": [], "invalid": [] } ``` ## i18n sync This command updates a translation file after the document changed. Keys are locations, not texts: when a description changes, its key stays the same, and the old translation would still look complete. `i18n sync` compares the texts the translations were made from (`--previous`) with the current ones (`FILE`), and: - keeps the translations of the unchanged texts; - reports the texts whose source changed, as warnings: review their translations, which may be stale. With `--drop-changed`, their translations are dropped, so that `i18n check` reports them as missing; - reports the translations of removed texts, and keeps them unless `--prune` is given; - counts the new texts, which have no translation yet. ```sh apigenie i18n sync openapi.yaml --previous i18n-en.json --translations i18n-pl.json -o i18n-pl.json ``` `FILE` and `PREVIOUS` are specs, Protobuf or GraphQL files, or files `i18n extract` wrote: keeping the `i18n extract` output of the version the translators worked on is enough. Texts are matched by key, so a text that moved, such as a renamed path, is removed at its old key and added at its new one. The output follows the order of the current texts, then the kept translations of removed texts. Keys that are not JSON pointers, and values that are not strings, are copied at the end. ### Options #### --previous PREVIOUS The texts the translations were made from. Required. #### --translations TRANSLATIONS Path to the translation file to update, in JSON or YAML. Required. #### -o, --output OUTPUT Path to the updated translation file; its format follows its extension. With it, the command writes a summary (`3 new texts, 2 changed, 1 removed, 70 unchanged.`) on the standard output. Defaults to the standard output, in the format of the translation file. #### --prune Drops the translations of the texts the document no longer has. #### --drop-changed Drops the translations of the texts that changed, instead of keeping them for review. ## Exit status The subcommands exit with 0 on success, warnings about unknown keys and changed texts included. They exit with 1 when `i18n check` finds the translation file incomplete, or when, with `--strict`, a key of `i18n apply` addresses nothing. They exit with 2 when the input or a translation file can't be read or parsed, a translation can't be applied, an option isn't available for the input (`--format` for Protobuf and GraphQL files, `--strip-comments` for Protobuf files), or `i18n` is run without a subcommand. See the [exit statuses](/docs/reference/cli/overview#exit-status) shared by the three binaries. ## See also - [bundle](/docs/reference/cli/bundle), to put the texts of referenced files into the document before extracting them - [apigenie-generator docs](/docs/reference/generator/docs), which takes the translation files with `--i18n` --- # license Source: https://apigenie.pl/docs/reference/cli/license Displays license information of API Genie and 3rd party libraries it uses. ## Usage ```sh apigenie license [OPTIONS] ``` ## Description API Genie uses several open-source libraries. This command displays the license agreements for these libraries, ensuring compliance with their terms. It also can display the license agreement of API Genie itself. ## Options When invoked without options, this command displays the license agreement of API Genie itself. ### --list Lists names of 3rd party libraries used. This option is mutually exclusive with the `--library` option. ### --library NAME Displays license for specific library. `NAME` must be one of names printed when using the `--list` option. **Example**: ```sh apigenie license --library LibBF ``` ## See Also The [credits](/docs/reference/cli/credits) command displays descriptions of open-source libraries used, along with links to their websites. --- # lint Source: https://apigenie.pl/docs/reference/cli/lint Detects stylistic and structural issues in OpenAPI, Swagger 2.0 and AsyncAPI documents, Protobuf (`.proto`) files and GraphQL schemas, improving their quality. ## Usage ```sh apigenie lint [OPTIONS] FILES ``` ## Description The OpenAPI linter helps developers and teams create high-quality, consistent, and well-structured schemas. It achieves this by validating schemas against a set of configurable **lint rules**. ### Lint Rules Each rule in the linter focuses on a specific aspect of an OpenAPI schema. Here are some examples: - `operation-description-defined`: Ensures that all operations have meaningful descriptions. - `operation-success-response`: Ensures that all operations define a success response (e.g. `201 Created`). - `paths-max-count` and `paths-max-depth`: Limit API complexity by checking the number of path items and depth (e.g. a number of segments) or URLs. Each rule can have a **severity level** assigned, indicating its importance. Possible values for severity are `error`, `warning` and `hint` (also written `info`). All of them show up in the linter output, but only errors prevent the linter check from succeeding; hints are suggestions, which don't count as warnings either. A rule may report a hint where its advice can't be followed yet, such as [schema-format-deprecated](/docs/linter/openapi/rules/schema-format-deprecated) in OpenAPI 3.0 documents. Some rules may accept options that adjust how they evaluate the schema. The API Genie linter currently offers 53 built-in rules for OpenAPI, with over 20 additional rules under active development. ### AsyncAPI documents The linter also checks AsyncAPI 3.x documents; a file is treated as AsyncAPI when its root object has an `asyncapi` field. AsyncAPI documents have their own set of 31 built-in rules, ported from the OpenAPI ones where they make sense (for example `contact-defined`, `license-defined`, `operation-tags-defined` or `unused-shared-component`), plus AsyncAPI-specific ones such as `channel-parameters-defined`, `channel-addresses-unique` and `server-hosts`. They come in the same `recommended`, `strict` and `all` rulesets, and support custom rules, JavaScript functions, `x-apigenie-lint-disable` and `--fix` just like OpenAPI documents. OpenAPI and AsyncAPI files can be linted in a single run; see [lint.asyncapi](#lintasyncapi) for their configuration. ### Swagger 2.0 documents The linter also checks Swagger 2.0 documents; a file is treated as Swagger when its root object has a `swagger` field. Swagger documents have their own set of 46 built-in rules, ported from the OpenAPI ones where they make sense (for example `operation-id-unique`, `parameters-unique`, `path-parameters-defined` or `unused-shared-component`), plus Swagger-specific ones: `host-defined`, `schemes-secure`, `media-types-defined` and `swagger-validation`, which reports the violations of the Swagger 2.0 specification that span several objects (undefined security schemes, unresolved references, several body parameters and so on). They come in the same `recommended`, `strict` and `all` rulesets, and support custom rules (whose `formats` is `oas2`), JavaScript functions, `x-apigenie-lint-disable` and `--fix` just like OpenAPI documents. See [lint.swagger](#lintswagger) for their configuration. ### GraphQL documents The linter also checks GraphQL schemas and operations: files with the `.graphql`, `.graphqls` or `.gql` extension. GraphQL documents have their own set of 87 built-in rules: the type system and operation rules of the GraphQL specification, naming conventions, descriptions, deprecations, schema design (root types, mutation inputs and payloads, unreachable types, identifiers), the Relay connection conventions, ordering and spelling. Operations are also checked against a schema: unknown fields, types and arguments, required arguments, deprecated fields and selection depth. They come in the `recommended`, `strict`, `all` and `relay` rulesets. A `# apigenie-lint-disable rule-a, rule-b` comment disables rules for a definition and everything nested in it. `--fix` does not change GraphQL files. See [lint.graphql](#lintgraphql) for their configuration. ### Rulesets To simplify configuration and provide more flexibility, rules can be combined into **rulesets**. A ruleset is a named group of rules with assigned severity levels and options. ### Disabling certain rules There might be situations where you want to temporarily disable a rule for a specific part of a schema. This might be due to a legacy issue, ongoing development, or a specific API decision. To do this, annotate parts of your OpenAPI schema with the `x-apigenie-lint-disable` extension. The value of this extension must be a list of rule names you want to disable. Rules can be disabled on any schema object, including `OpenAPI` object itself. Disabling a rule on specific schema object applies to that object and its descendants within the schema hierarchy. For example, in the snippet below, the [contact-defined](/docs/linter/openapi/rules/contact-defined) rule is disabled on the `Info` schema object. However, it will also be enforced on the nested `Contact` object because `Contact` is a descendant of `Info` within the schema structure. ```yaml info: x-apigenie-lint-disable: - contact-defined ``` A couple of final remarks: - Disable rules as close to the problematic point in the schema as possible. - Disabling linter rules should be an exception, not the rule. ### Custom rules Besides the built-in rules, you can define your own rules in the `lint` section of the configuration file. A custom rule is data, in the format used by [Spectral](https://docs.stoplight.io/docs/spectral): it selects parts of the document with a JSONPath expression and checks each of them with a **function**. ```yaml version: "1.0" commands: lint: extends: [recommended] rules: operation-summary: description: Operations must have a summary message: "{{property}} is missing at {{path}}" severity: error given: "$.paths[*][get,put,post,delete,patch]" then: field: summary function: truthy ``` A rule definition accepts these properties: | Property | Description | | ------------- | ----------- | | `given` | JSONPath expression, or a list of them, selecting the values to check. Required. | | `then` | What to check: an object or a list of objects with `function`, optional `functionOptions` and optional `field`. Required. | | `field` | Narrows each selected value: a member path (`summary`, `a.b[0]`), `@key` to check member names, or a JSONPath expression relative to the value (`$.properties[*]`). | | `message` | Message template; see below. Defaults to `description`, then to the function's message. | | `description` | Human-readable description of the rule. | | `severity` | `error`, `warn` (default), `info`, `hint` or `off`. `info` and `hint` are reported as hints. | | `resolved` | Whether the rule sees the document with local `$ref`s followed. Defaults to `true`. | | `formats` | Restricts the rule to document formats: `oas2` (Swagger 2.0 documents), `oas3`, `oas3_0`, `oas3_1`, `oas3_2`, or, for AsyncAPI documents, `aas3` and `aas3_0`, `aas3_1`, ... | Messages may use `{{error}}` (the function's message), `{{description}}`, `{{path}}` (JSON pointer of the problem), `{{property}}` (the last path segment) and `{{value}}`. Issues are reported where the offending value is defined, even when it is reached through a `$ref`. Custom rules honour `x-apigenie-lint-disable` like built-in ones. A custom rule can be switched off or have its severity changed later in the configuration (`my-rule: off`), but it cannot reuse the name of a built-in rule. #### JSONPath `given` and `field` expressions follow [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535) with the extensions Spectral rulesets commonly use: - member unions without quotes: `$.paths[*][get,put]`; - `~` at the end selects member names: `$.paths[*]~`; - filters written as JavaScript expressions: `$..[?(@.type === 'object' && !@.properties)]`, with `@property`, `@parent`, `@parentProperty`, `@path` and `$`, and the string and array methods `match`, `test`, `startsWith`, `endsWith`, `includes`, `indexOf`, `toLowerCase`, `toUpperCase`, `trim` and `length`. Script expressions such as `[(@.length-1)]` and Spectral aliases (`#Name`) are not supported. #### Core functions | Function | Options | Checks that the value... | | -------- | ------- | ------------------------ | | `truthy`, `falsy` | none | is (not) truthy in the JavaScript sense | | `defined`, `undefined` | none | exists / does not exist | | `enumeration` | `values` | is one of the listed values | | `length` | `min`, `max` | has a length (string, array, object keys, number) within bounds | | `pattern` | `match`, `notMatch` | (does not) match a JavaScript regular expression; `/re/flags` is accepted | | `casing` | `type` (`flat`, `camel`, `pascal`, `kebab`, `cobol`, `snake`, `macro`), `disallowDigits`, `separator.char`, `separator.allowLeading` | follows a naming convention | | `alphabetical` | `keyedBy` | lists keys or items in alphabetical order | | `or`, `xor` | `properties` | has at least one / exactly one of the listed members | | `schema` | `schema`, `dialect`, `allErrors` | is valid against a JSON Schema (draft 4 to 2020-12) | | `typedEnum` | none | has `enum` values matching its `type` | | `unreferencedReusableObject` | `reusableObjectsLocation` | has only members referenced somewhere in the document | #### JavaScript functions When the core functions are not enough, write a function in JavaScript. List it under `functions`; API Genie loads `.js` from the `functions` directory next to the configuration file (change it with `functionsDir`). ```yaml commands: lint: functions: [operationIdPrefix] rules: operation-id-prefix: given: "$.paths[*][*].operationId" then: function: operationIdPrefix functionOptions: { prefix: api } ``` A function is an ES module whose default export receives the value, the options and a context, and returns a list of problems. The contract is the one of Spectral custom functions, so most of them work unchanged: ```js // functions/operationIdPrefix.js import { createRulesetFunction } from '@stoplight/spectral-core'; import { casing } from '@stoplight/spectral-functions'; export default createRulesetFunction( { input: { type: 'string' }, options: { type: 'object', properties: { prefix: { type: 'string' } }, required: ['prefix'], }, }, (input, options, context) => { if (!input.startsWith(options.prefix)) { return [{ message: `must start with "${options.prefix}"` }]; } return casing(input, { type: 'camel' }, context); }, ); ``` - `createRulesetFunction` validates `functionOptions` when the configuration is loaded and skips values that do not match `input`. - A result may carry a `path` (an array of keys and indexes) to point at a more specific location than `context.path`. - `context` provides `path`, `rule` (`name`, `severity`, `resolved`), `document` (`formats`, `data`) and `documentInventory` (`resolved`, `unresolved`). - Functions may be `async`. - A function that throws is reported as an issue of its rule. Functions run in a sandbox: they can import other modules from the functions directory and the two built-in `@stoplight` modules, but have no access to the file system, network, processes or timers. Each call must finish within 5 seconds. Custom JavaScript functions are not available in the WebAssembly build. ### Autofixing Some issues found by the `apigenie lint` command can be automatically fixed. To do this, run the command with the `--fix` option. The fixed files keep their comments, unless `--strip-comments` is given. ### Formatting results Apart from rules, API Genie linter allows to customize the way it displays results. It ships with several built-in **formatters**, described below, in subsections. Formatters differ in the way they present data, but all of them sort issues by: 1. The line in the input OpenAPI schema file where they were found 2. Severity level: warnings are displayed before errors. **Note**: If you used [ESLint](https://eslint.org) you may notice that the list of formatters API Genie provides is close to that of ESLint. This is by no coincidence, as both tools aim to be usable in many different environments. #### checkstyle Outputs results in the [Checkstyle](https://checkstyle.sourceforge.io) format. ```xml ``` #### compact Mimicks the default output of JSHint. ``` /apigenie/data/openapi.yaml: line 2, col 2, Error - Info summary is missing or empty. (info-defined) /apigenie/data/openapi.yaml: line 12, col 4, Warning - Tag 'Articles' has no meaningful description defined. (tag-description-defined) /apigenie/data/openapi.yaml: line 13, col 4, Warning - Tag 'Comments' has no meaningful description defined. (tag-description-defined) /apigenie/data/openapi.yaml: line 14, col 4, Warning - Tag 'Favorites' has no meaningful description defined. (tag-description-defined) /apigenie/data/openapi.yaml: line 15, col 4, Warning - Tag 'Profile' has no meaningful description defined. (tag-description-defined) ``` #### html Prints results as a single HTML file. Issues are displayed in a table, every issue takes one table row. ```html

API Genie Report

  • Total: 224
  • Errors: 32
  • Warnings: 192
/apigenie/data/openapi.yaml
2:2 Error Info summary is missing or empty info-defined
12:4 Warning Tag 'Articles' has no meaningful description defined tag-description-defined
``` #### json Outputs lint results in JSON file. This format is useful if you want to process linter result programmatically. `results` has one item per file with issues; `formatVersion` is the version of the format, which changes only when a member is removed or changes meaning (see [Compatibility](/docs/reference/cli/compatibility)). Its JSON Schema is [apigenie-lint-report.json](https://apigenie.pl/schemas/apigenie-lint-report.json). ```json5 { "formatVersion": 1, "results": [ { "filePath": "/apigenie/data/openapi.yaml", "messages": [ { "column": 2, "line": 2, "message": "Info summary is missing or empty", "ruleId": "info-defined", "severity": "error" }, { "column": 4, "line": 12, "message": "Tag 'Articles' has no meaningful description defined", "ruleId": "tag-description-defined", "severity": "warning" }, ... more issues ... ] } ] } ``` API Genie 0.9 and earlier wrote the bare `results` array. #### junit Outputs results to format compatible with the [JUnit Jenkins plugin](https://plugins.jenkins.io/junit/). ```xml ``` #### sarif Outputs results in [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html), the format of GitHub code scanning and other static analysis dashboards. Files below the current directory are given relative to it (`%SRCROOT%`), so that code scanning finds them in the repository. A result whose JSON pointer is known has it as a logical location and in a fingerprint, which lets code scanning keep tracking the issue when lines move. ```json5 { "$schema": "https://json.schemastore.org/sarif-2.1.0.json", "version": "2.1.0", "runs": [ { "tool": { "driver": { "name": "apigenie", "informationUri": "https://apigenie.pl", "rules": [ { "id": "info-defined", "helpUri": "https://apigenie.pl/docs/linter/openapi/rules/info-defined" }, ... more rules ... ] } }, "originalUriBaseIds": { "%SRCROOT%": { "uri": "file:///apigenie/" } }, "results": [ { "ruleId": "info-defined", "ruleIndex": 0, "level": "error", "message": { "text": "Info summary is missing or empty" }, "locations": [ { "physicalLocation": { "artifactLocation": { "uri": "data/openapi.yaml", "uriBaseId": "%SRCROOT%" }, "region": { "startLine": 2, "startColumn": 2 } }, "logicalLocations": [ { "fullyQualifiedName": "#/info", "kind": "member" } ] } ], "partialFingerprints": { "apigenieLocation/v1": "info-defined|data/openapi.yaml|#/info" } }, ... more issues ... ] } ] } ``` #### stylish This is the default formatter. It presents result as table, with values being highligted in different colors. ```sh 764:6 warning Unused shared component unused-shared-component 769:12 warning Object schema does not define constraints restricting its size schema-constraints 769:12 error Object schema does not disable additional properties schema-no-additional-properties 777:6 warning Unused shared component unused-shared-component 781:8 error Schema with integer type does not define proper format schema-format-defined 781:8 warning Integer schema does not define constraints restricting its range schema-constraints 785:6 warning Unused shared component unused-shared-component 789:8 error Schema with integer type does not define proper format schema-format-defined 789:8 warning Integer schema does not define constraints restricting its range schema-constraints 795:6 warning Unused shared component unused-shared-component 224 issues (32 errors, 192 warnings) 0 errors, 6 warnings are potentially auto-fixable ``` #### tap Outputs results to the [Test Anything Protocol (TAP)](https://testanything.org) specification format. ```yaml TAP version 14 1..1 not ok - /apigenie/data/openapi.yaml --- errorCount: 32 issues: - column: 2 line: 2 message: Info summary is missing or empty ruleId: info-defined severity: error - column: 4 line: 12 message: Tag 'Articles' has no meaningful description defined ruleId: tag-description-defined severity: warning - column: 4 line: 13 message: Tag 'Comments' has no meaningful description defined ruleId: tag-description-defined severity: warning ``` #### unix Outputs results to a format similar to many commands in UNIX-like systems. If you use `grep` or `awk`, this format may be best for you. ``` /apigenie/data/openapi.yaml:2:2: Info summary is missing or empty. [Error/info-defined] /apigenie/data/openapi.yaml:12:4: Tag 'Articles' has no meaningful description defined. [Warning/tag-description-defined] /apigenie/data/openapi.yaml:13:4: Tag 'Comments' has no meaningful description defined. [Warning/tag-description-defined] /apigenie/data/openapi.yaml:14:4: Tag 'Favorites' has no meaningful description defined. [Warning/tag-description-defined] /apigenie/data/openapi.yaml:15:4: Tag 'Profile' has no meaningful description defined. [Warning/tag-description-defined] /apigenie/data/openapi.yaml:16:4: Tag 'Tags' has no meaningful description defined. [Warning/tag-description-defined] ``` #### visualstudio Outputs results to format compatible with the integrated terminal of the Visual Studio IDE. ``` /apigenie/data/openapi.yaml(2,2): error info-defined : Info summary is missing or empty. /apigenie/data/openapi.yaml(12,4): warning tag-description-defined : Tag 'Articles' has no meaningful description defined. /apigenie/data/openapi.yaml(13,4): warning tag-description-defined : Tag 'Comments' has no meaningful description defined. /apigenie/data/openapi.yaml(14,4): warning tag-description-defined : Tag 'Favorites' has no meaningful description defined. /apigenie/data/openapi.yaml(15,4): warning tag-description-defined : Tag 'Profile' has no meaningful description defined. /apigenie/data/openapi.yaml(16,4): warning tag-description-defined : Tag 'Tags' has no meaningful description defined. ``` ## Options ### --config FILE Optional path to the configuration file. If not specified, the default configuration is used. For details, see the [configuration](#configuration) section. ### --fix If possible, automatically fixes problems it finds. Modified file is written under the same name as the original. The exit status reflects the issues that remain after the fixes. The YAML comments of the fixed OpenAPI, Swagger and AsyncAPI documents are kept with the values they belong to; those of values a fix removes go with them. Protobuf files are fixed by editing their text, which keeps everything outside the edited declarations. ### --strip-comments With `--fix`, drops the YAML comments of the OpenAPI, Swagger and AsyncAPI documents it rewrites, instead of keeping them. Files without fixes are not rewritten. ### --formatter NAME Sets the formatter to display the linter result. Supported formatter names are: - `checkstyle` - `compact` - `html` - `json` - `junit` - `sarif` - `stylish` - the default formatter - `tap` - `unix` - `visualstudio` ### --graphql-schema FILE A GraphQL schema file that the operations and fragments of the linted GraphQL files are checked against. Can be repeated for a schema split across files. When given, it replaces the files of the [schema](#lintgraphql) option of the configuration; relative paths resolve against the current directory. With `--watch`, the schema files are watched too. ```sh apigenie lint --graphql-schema schema.graphql queries/*.graphql ``` ## Exit status The command exits with 0 when no issue of severity `error` is reported, even if warnings or hints are, and with 1 when at least one is. With `--fix`, only the issues that remain after the fixes count. It exits with 2 when the configuration or an input file can't be read or parsed, or an input is not a document of its specification. With `--watch` the command keeps running and never exits on issues. See the [exit statuses](/docs/reference/cli/overview#exit-status) shared by the three binaries. ## Configuration The linter stores its configuration under the `#/commands/linter` key in the configuration file. See the [global configuration](/docs/reference/cli/configuration) section to understand how this file is structured. The linter configuration allows to apply rulesets, enable or disable certain rules, modify rule severity levels, or modify rules' options. **Example**: a minimal confiration illustrating all possible variants of configuring rules. ```yaml version: '1.0' commands: lint: extends: - recommended # ... more rulesets to extend from ... rules: info-defined: off paths-max-count: - error - 30 schema-datetime-suffix: - error - At tags-unique: error # ... more rules ... ``` If the configuration file is not specified, linter uses the default configuration: ```yaml version: '1.0' commands: lint: extends: - recommended ``` ### lint.extends This block lists rulesets whose configuration will be used and combined to create the final set of rules, rule severity levels and options. The value of this block, if present, must be an array of [supported ruleset names](/docs/linter/openapi). Linter will read all rulesets' configurations, in the order they are listed. If two rulesets contain the same rule, linter uses the configuration from the ruleset that was specified later in the list. ### lint.rules These are overrides to the rules' configurations from rulesets. If present, value of this block must be a map. Keys of the map must be valid rule names, and values can be either strings or two-element arrays. For valid rule names see the [rule index](/docs/linter/openapi) page. ### lint.rules.RULE_ID Such blocks configure a rule named `RULE_ID`. Each block may be a string or a two-element array. - **String**: If it a string, and must be one of: - `off` - to disable rule - `warning` - to set rule severity to warning - `error` - to set rule severity to error - **Array**: If it is an array, it must contain two elements: - The first element must be a valid rule severity (either `warning` or `error`). - The second element is rule-specific value, containing rule's options. See the documentation of each rule to find what options it accepts. **Examples** - Disables the `info-defined` rule: ```yaml version: '1.0' commands: lint: rules: info-defined: off ``` - configure the [`paths-max-count`](/docs/linter/openapi/rules/paths-max-count) rule to issue an error when the number of path items is larger than 30. If the number is below or equal to 30, the rule does not produce an error or warning. ```yaml version: '1.0' commands: lint: rules: paths-max-count: - error - 30 ``` ### lint.asyncapi The configuration of the linter for AsyncAPI documents. It accepts the same keys as the `lint` block itself (`extends`, `rules`, `functions` and `functionsDir`), with rule names of the AsyncAPI rules. The OpenAPI settings of the `lint` block do not apply to AsyncAPI documents; without this block, AsyncAPI documents are linted with the `recommended` ruleset. ```yaml version: '1.0' commands: lint: extends: [recommended] asyncapi: extends: [strict] rules: parameter-naming: [warning, camel] server-hosts: off ``` ### lint.swagger The configuration of the linter for Swagger 2.0 documents. It accepts the same keys as the `lint` block itself (`extends`, `rules`, `functions` and `functionsDir`), with rule names of the Swagger rules. The OpenAPI settings of the `lint` block do not apply to Swagger documents; without this block, Swagger documents are linted with the `recommended` ruleset. ```yaml version: '1.0' commands: lint: extends: [recommended] swagger: extends: [strict] rules: schemes-secure: [error, { upgradeInsecure: true }] swagger-validation: [error, { checks: [unresolved-reference, security-scheme-defined] }] paths-sorted: off ``` ### lint.graphql The configuration of the linter for GraphQL documents. It accepts `extends`, `rules`, `self_contained` (whether each document holds every type and fragment it references, so that references to missing ones are reported) and `schema` (a path, or a list of paths, of the schema files operations are checked against; relative paths resolve against the directory of the configuration file; [--graphql-schema](#graphql-schema-file) replaces them). Without `schema`, operations are checked against the type system of their own document. Without this block, GraphQL documents are linted with the `recommended` ruleset. ```yaml version: '1.0' commands: lint: graphql: extends: [recommended, relay] schema: schema.graphql rules: field-description-defined: warning selection-set-depth: [error, { maxDepth: 5 }] no-deprecated: off ``` ## See Also - [Rule index](/docs/linter/openapi) lists all available rules. - [Ruleset index](/docs/linter/openapi) lists all available rulesets. --- # overlay Source: https://apigenie.pl/docs/reference/cli/overlay Applies [OpenAPI Overlays](https://spec.openapis.org/overlay/latest.html) to an OpenAPI schema, or to any JSON or YAML document. ## Usage ```sh apigenie overlay [OPTIONS] --overlay OVERLAY [--overlay OVERLAY ...] INPUT apigenie overlay --validate --overlay OVERLAY [--overlay OVERLAY ...] ``` ## Description An overlay is a JSON or YAML document with an ordered list of actions. Each action selects nodes of the input with a JSONPath query (its `target`) and changes them. The overlays are applied in the order of the `--overlay` options, and the actions of an overlay in their order, each to the result of the previous one. The result is written to the standard output, or to the file of `--output`, in the format of the input unless `--format` says otherwise. YAML comments of the input and of the overlays are kept, unless `--strip-comments` is given. Strings that YAML 1.1 parsers would read as booleans or numbers, such as `yes`, `off`, `0755` or `1:30`, are written in double quotes. Overlays of versions 1.0, 1.1 and 1.2 of the specification are supported: `copy` actions need version 1.1 or later, `$self` and reusable actions (`components.actions`, referenced from the actions with `$ref`) version 1.2. The `extends` property is informative only: the input is always `INPUT`. To generate code from an overlaid document, there is no need for a separate step: `apigenie-generator` takes the overlays itself, with `--overlay`, and reports problems in the values they set at their lines in the overlay; see [overlays](/docs/reference/generator#overlays). For example, with `public.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Public API version: 1.0.0 actions: - target: $.info update: x-audience: public - target: $.servers[?@.description == 'Dev'] remove: true - target: $.paths.*.*.parameters[?@.name == 'debug'] remove: true ``` ```sh apigenie overlay --overlay public.overlay.yaml openapi.yaml -o public.yaml ``` ### Actions For every node the target selects: - with `remove: true`, the node is removed from its object or array; - otherwise the value of `update` (or, from version 1.1, a copy of the one node that the JSONPath query of `copy` selects) is merged into the node: - into an object, the value must be an object. Its members are added to the object, after the existing ones. When the object already has a member, a primitive value replaces a primitive value, an array is appended to an array, and an object is merged into an object the same way. Other combinations are errors; - to an array, an array is appended element by element, and any other value is appended as one element; - a primitive value (string, number, boolean or null) is replaced by the value, which must be a primitive value. An action without `update`, `copy` and `remove` changes nothing. The nodes selected by an action must all be objects, all arrays or all primitive values. An action that selects nothing changes nothing: the command warns about it on the standard error, and goes on. ### JSONPath Targets are JSONPath queries as defined by [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535): names (`.name` or `['name']`), wildcards (`*`), indexes and slices (`[0]`, `[-1]`, `[1:3]`), descendants (`..`), unions (`[0, 2]`) and filters (`[?@.in == 'query']`), with the comparisons, `&&`, `||`, `!` and the functions `length()`, `count()`, `match()`, `search()` and `value()`. As an extension, member names written with a dot may contain `-`, as in `$.paths.*.get.x-internal`. ## Options | Option | Description | | ----------------------- | ------------------------------------------------------ | | `--overlay OVERLAY` | Path to an overlay. Required, can be repeated. | | `-o`, `--output OUTPUT` | Path to the result. Defaults to the standard output. | | `--format FORMAT` | Format of the result, `json` or `yaml`. | | `--validate` | Only checks the overlays. | | `--strip-comments` | Drops the comments of the result. | ### --overlay OVERLAY Path to an overlay, in JSON or YAML. The option can be repeated: the overlays are applied in the given order. When an overlay is invalid, its issues are listed and nothing is written. ### --output OUTPUT Path of the file to write the result to. Without it, the result is written to the standard output. ### --format FORMAT Format of the result: `json` or `yaml`. Defaults to the format of the input. ### --strip-comments Drops the YAML comments of the input and of the overlays from the result, instead of keeping them. ### --validate Checks the overlays against the specification of their version, without applying them: unknown properties, values of the wrong type, invalid JSONPath queries, unresolved reusable action references and duplicate actions are reported with their line and column. `INPUT` is not needed. The command exits with a non-zero status when an overlay has issues. ```sh apigenie overlay --validate --overlay public.overlay.yaml ``` --- # test Source: https://apigenie.pl/docs/reference/cli/test Runs the requests of `.http` files and requests generated from an OpenAPI or AsyncAPI document, a GraphQL schema or a `.proto` file against a live server, checks their assertions and scripts, and validates every request, response and message against the document or the schema with the checks of the [validating proxy](/docs/reference/server). HTTP requests are checked against an OpenAPI document, [GraphQL requests](#graphql-requests) against a GraphQL schema, [`GRPC` requests](#grpc-requests) against a `.proto` file, and [WebSocket and MQTT requests](#websocket-and-mqtt-requests) and the requests of [HTTP channels](#requests-of-http-channels) against an AsyncAPI document. ## Usage ```sh apigenie test [OPTIONS] FILES... apigenie test --spec SPEC --generate [--base-url URL] [OPTIONS] [FILES...] apigenie test --spec schema.graphql [--generate --base-url URL] [OPTIONS] [FILES...] apigenie test --spec service.proto [--proto-path DIR ...] FILES... apigenie test --spec ASYNCAPI --generate [--base-url ws://HOST:PORT] [--mqtt-url mqtt://HOST:PORT] [OPTIONS] apigenie test import COLLECTION -o DIR [--environment FILE ...] [--spec SPEC] [--force] ``` ## Description The request files are written in the [httpYac](https://httpyac.github.io/) flavour of the `.http` format, which the JetBrains HTTP client and VS Code's REST Client read too. Directories are searched for `.http` and `.rest` files. ```http @host = http://localhost:4010/v1 ### List pets # @name list GET {{host}}/pets?limit=2 Accept: application/json ?? status == 200 ?? body $ isArray ### Read the first one GET {{host}}/pets/{{list.response.body.$[0].id}} {{ exports.petName = response.parsedBody.name; test("has a name", () => assert.ok(response.parsedBody.name)); }} ### Create a pet POST {{host}}/pets Content-Type: application/json {"id": 7, "name": "{{petName}}"} ?? status == 201 ``` ```sh apigenie test --spec openapi.yaml pets.http --report report.json --report-junit report.xml ``` ```text PASS GET http://localhost:4010/v1/pets?limit=2 -> 200 (9 ms) listPets PASS GET http://localhost:4010/v1/pets/746 -> 200 (4 ms) showPetById PASS POST http://localhost:4010/v1/pets -> 201 (2 ms) createPets Requests: 3 passed, 0 failed, 0 skipped Contract: 3 requests (0 undocumented), 0 errors, 0 warnings Coverage: 70.0% of 10 branches, 3 of 3 operations ``` ### Requests A file is split into regions by lines starting with `###` (the rest of the line is the region's title). A region has comments and directives (`#` or `//`), variable definitions, the request line (`METHOD URL [HTTP/1.1]`; a URL alone is a `GET`), headers up to the first blank line, and a body up to the next region, script or assertion. Lines starting with `?` or `&` right after the request line continue the URL. A body of `< ./file.json` is read from that file, `<@ ./file.json` with its variables substituted. Requests run in the order of the file, files in the order given. `http://` and `https://` URLs both work. For `https://` (and `grpcs://`, `wss://` and `mqtts://` alike), the server's certificate is checked against the system's root certificates and the URL's host name. `--ca-cert FILE` adds authorities, such as a test CA or a company's root, and `--insecure` skips the check. `SSL_CERT_FILE` replaces the system's roots. A certificate that is rejected fails the request with the reason, for example `no response: TLS with 10.0.0.5:443: certificate verification failed: The certificate is not correctly signed by the trusted CA`. A server behind mutual TLS asks for a client certificate: `--client-cert client.pem --client-key client.key` presents one to every `https://`, `grpcs://`, `wss://` and `mqtts://` server that asks (with `--insecure` too). The key must be PEM and not encrypted; a file that can't be read, or a key of another certificate, stops the run with exit status 2. Without one, such a server fails the request, for example `no response: TLS with api.example.com:443: the peer refused the connection: the server requires a client certificate`. TLS sessions aren't resumed (every connection does a full handshake), and certificates aren't checked for revocation (no CRL or OCSP). | Directive | Meaning | | --- | --- | | `# @name list` | Names the request, so that others can refer to its response | | `# @ref list` | Runs `list` first, unless it already ran | | `# @forceRef list` | Runs `list` first, every time | | `# @operation getPet` | Checks the request as the operation `getPet` of `--spec` (an AsyncAPI document's operation of an HTTP, WS or MQTT channel); for a GraphQL request, the operation to run (`operationName`) | | `# @operation off` | Doesn't check the request against `--spec`, nor count it in the coverage | | `# @channel pickupDesk` | WS and MQTT: checks the messages against that channel of the AsyncAPI document | | `# @timeout 8000` | WS and MQTT, GraphQL subscriptions, gRPC client and bidi streams: waits up to 8000 ms for messages (`--message-timeout`, 5000 by default) | | `# @expect 2` | WS and MQTT, GraphQL subscriptions: stops waiting after 2 messages; `0` doesn't wait | | `# @property content-type application/json` | MQTT 5: a property of the messages the region publishes (see [MQTT 5 properties](#mqtt-5-properties)) | | `# @disabled` | Skips the request | | `# @postman` | Enables the collection-import compatibility API for this file | | `# @title`, `# @description`, `# @tag`, `# @no-log`, `# @no-redirect`, `# @verbose`, ... | Accepted and ignored | Any other directive is a warning: the request still runs. ### GraphQL requests A `GRAPHQL` request has the GraphQL document as its body and, after a blank line, its variables as a JSON object, as in httpYac and the JetBrains HTTP client. It is sent as a `POST` of `{"query", "variables", "operationName"}` with `Content-Type: application/json`; `# @operation` gives the `operationName`. The query can also come from a file (`< ./book.graphql`), without inline variables then. ```http # @operation GetBook GRAPHQL {{host}}/graphql query GetBook($id: ID!) { book(id: $id) { title author { name } } } { "id": "1" } ``` A `POST` whose body starts with `query`, `mutation`, `subscription` or `fragment` is sent the same way, when it has no `Content-Type` or a JSON or `application/graphql` one; with another `Content-Type` (`text/plain`) it is sent as written. A `POST` of a JSON body that has a `query` (or a batch of them) and a `GET` with a `query` parameter are GraphQL requests as they are. Without `--spec`, GraphQL requests are only sent and asserted on. #### Subscriptions A `GRAPHQL` request (or a `POST` of a query) whose operation is a `subscription` goes over WebSocket with the graphql-ws protocol (the `graphql-transport-ws` subprotocol of Apollo Client, urql and GraphiQL). The URL's `http://` becomes `ws://` and `https://` becomes `wss://`; `ws://` and `wss://` URLs are taken as written. The headers go with the handshake and, as an object, as the payload of `connection_init`, where servers look for credentials. The events are the response, as for a [`WS` region](#websocket-and-mqtt-requests): - The body is the JSON array of the events (`{"data": ...}` each). - The status is 101. - Scripts get `response.messages`. The subscription ends when the server completes it, after `# @expect` events, or when `# @timeout` (`--message-timeout`, 5000 ms) has passed; then the client completes it. An `error` message from the server fails the request with its errors. ```http # @expect 3 GRAPHQL {{host}}/graphql Authorization: Bearer {{token}} subscription OnStory($topic: String!) { storyAdded(topic: $topic) { id title } } { "topic": "space" } ?? body $.length == 3 ?? body $[0].data.storyAdded.id == 1 ``` ```text PASS GRAPHQL http://localhost:4000/graphql -> 1 sent, 3 received (162 ms) OnStory ``` A server that doesn't offer `graphql-transport-ws` fails the request (`no connection: the server doesn't speak graphql-transport-ws (graphql-ws)`). The legacy `graphql-ws` subprotocol of `subscriptions-transport-ws` and server-sent events aren't spoken. ### Validation With `--spec`, each request is bound to an operation by its method and path, as the validating proxy does, then checked: parameters, body, security (credentials must be present), and the response's status, media type, headers and body. The checks, their severities and `--severity` are those of [contract testing](/docs/reference/server#contract-testing-validating-proxy). XML request and response bodies are mapped by the schema's `xml` objects and checked like JSON ones (see [XML bodies](/docs/reference/server#xml-bodies)). The coverage counts each operation, its request body and each declared response. A request sent with an [OAuth 2](#oauth-2) token is also checked against the operation's security requirements (or the document's), as warnings by default: | Check | Default | Meaning | | --- | --- | --- | | `request.authorization.oauth2-scheme` | `warn` | The operation has no `oauth2` or `openIdConnect` scheme (or no security at all) | | `request.authorization.oauth2-scope` | `warn` | A scope of `oauth2_scope` isn't declared for the operation, or no requirement has all its scopes in `oauth2_scope` | When several OAuth 2 schemes apply, the one whose flow's `tokenUrl` is the token endpoint is used. Without `oauth2_scope` the scopes aren't checked. A Swagger 2.0 specification is converted to OpenAPI 3.2 in memory, as `apigenie-server` does (see [Swagger 2.0 documents](/docs/reference/server#swagger-20-documents)); no file is written. What the conversion can only approximate is told once before the requests are sent, as `SPEC:LINE: info: POINTER: MESSAGE` lines on the standard error (`{"event":"info",...}` with `--formatter json`), and the report's operation pointers point into the Swagger file. With a GraphQL schema as `--spec` (`.graphql`, `.graphqls` or `.gql`), the GraphQL requests are checked by the [GraphQL validating proxy](/docs/reference/server#contract-testing-of-a-graphql-api)'s checks. The operation is validated against the schema before it is sent: an invalid one (a syntax error, an unknown field or argument, an unknown `operationName`, variables of the wrong type) fails the request, which isn't sent, with the errors the server would give (`invalid request: Cannot query field "nope" on type "Book".`), and the run goes on. The response is checked against the request's selection set: the shape of `data` and `errors`, the type of each value, `null` in non-null fields that no error's `path` explains (`null-in-non-null`), `__typename` of abstract types, and errors of operations the schema allows (`response.schema-drift`). Each event of a subscription is checked the same way, at `next[0]`, `next[1]`, ... (`next[1]/data/storyAdded/title: Story.title is an Int`). Deprecated fields selected are notes (`request.field.deprecated`). Requests that aren't GraphQL requests aren't checked. An answer with `errors` next to `data` is valid GraphQL: the operation ran, but fields failed. Each of those errors is a warning (`response.field-error`), so a server that answers every field with an error doesn't pass unnoticed. `--fail-on warnings` fails such requests, and `--severity response.field-error=error` makes them errors. The coverage counts the fields of the schema's types that requests selected, whether or not the answers had them, and each root field; `--min-coverage` is the share of fields. ```text PASS GRAPHQL http://localhost:4000/graphql -> 200 (7 ms) GetBook FAIL GRAPHQL http://localhost:4000/graphql Broken invalid request: Cannot query field "nope" on type "Book". Requests: 1 passed, 1 failed, 0 skipped Contract: 2 requests (0 to other paths), 1 errors, 0 warnings Coverage: 40.0% of 15 fields, 1 of 5 root fields not tested: Query.books, Query.node, Mutation.addBook, Mutation.deleteBook ``` With an AsyncAPI 3.x `--spec`, WS and MQTT requests are checked against the document's channels. The channel of a `WS` request is the one served at its URL's path; that of an `MQTT` request is the one of the topic it publishes to (or of its first subscription without wildcards), and each message received is matched by its own topic. `# @channel` names the channel when its address can't be matched (`address: null`, wildcard filters), `# @operation` checks the messages sent as that operation, and `# @operation off` checks nothing. A path or topic of no channel is a note, and its messages aren't checked. The messages sent are the client's: they are checked against the channel's `receive` operations. The messages received are the application's: they are checked against its `send` operations or, when they answer a request, against the reply's messages. When a message sent matched an operation with a `reply`, the first later message on the reply's channel that is one of the reply's messages is its reply: | Check | Default | Meaning | | --- | --- | --- | | `message.reply.missing` | `warn` | No reply came while the request waited | | `message.reply.uncorrelated` | `error` | The reply's `correlationId` (in the payload, or in a header over MQTT 5) isn't the request's, or is missing; over MQTT 5, the reply doesn't repeat the request's Correlation Data | | `message.properties.invalid` | `error` | MQTT 5: a message's properties don't match its MQTT bindings (see [MQTT 5 properties](#mqtt-5-properties)) | Violations are located in the message: `sent[0]/message/carrier`, `received[1]/message/requestId`, `sent[0]/message/property.contentType`. Over MQTT 5 a message's user properties are its headers: a `correlationId` or a reply address in `$message.header#/...` is read from them, and they are checked against the message's `headers` (`message.headers.invalid`). MQTT 3.1.1 has no message headers: such a `correlationId` or reply address can't be checked, and the request gets a note saying so. WS and MQTT requests aren't checked against an OpenAPI document. The coverage counts each message of each operation, those of HTTP channels included, as the [AsyncAPI validating proxy](/docs/reference/server#contract-testing-of-a-websocket-api) does. #### Requests of HTTP channels HTTP requests are checked against the AsyncAPI document's HTTP channels (the channels of `http` and `https` servers) with the checks of the [validating proxy](/docs/reference/server#contract-testing-of-http-channels), so both report the same violations: - The channel and operation are found by the request's path (under a server's `pathname` or not) and method: a `receive` operation's method is its HTTP binding's (`POST` by default); a `send` operation is asked for with `GET`, as the mock serves it. `# @operation` names the operation instead, whatever the method; a path that isn't its channel's is an error. - A `receive` operation's request: the body against the operation's messages (`body/...`), the query against the HTTP binding's `query` schema, and the header fields against the message's `headers` and its HTTP binding's `headers` (its traits' too). Its 2XX response: the body and header fields against the reply's messages (`response/...`). - A `send` operation's 2XX response with a body: against the operation's messages. Its header fields aren't checked: they are those of the webhook the application sends, which doesn't pass `apigenie test`. - Responses of other statuses, and the reply's `statusCode` binding, aren't checked. | Check | Default | Meaning | | --- | --- | --- | | `operation.query.invalid` | `error` | The query doesn't match the HTTP binding's `query` (`query.dryRun`) | | `message.headers.invalid` | `error` | The header fields the message's header schemas name don't match them (`header.X-Batch-Size`) | Query parameters and header fields are read as the type their schema says (`integer`, `number`, `boolean`, an `array` from repeated parameters); header fields the schemas don't name are left out. A path of no HTTP channel, or a method its channel doesn't take, is a note, and the request isn't checked. ```http ### A carrier's status batch POST {{http}}/http/carriers/dhl/events?dryRun=true Content-Type: application/json {"events":[{"trackingNumber":"CE1234567890","statusCode":"DLV","occurredAt":"2026-10-08T10:00:00Z"}]} ### The webhooks, asked for as the mock serves send operations GET {{http}}/http/ ``` ### WebSocket and MQTT requests A `WS` (or `WEBSOCKET`) region connects to a `ws://` or `wss://` URL, its headers going with the handshake; an `MQTT` region connects to an `mqtt://host:port` broker, or `mqtts://host:port` (also `ssl://`) over TLS (MQTT 5, or 3.1.1 when the broker refuses it). Without a port, `wss://` uses 443, `mqtt://` 1883 and `mqtts://` 8883. Over TLS the certificate is checked as for `https://`: `--ca-cert` and `--insecure` apply. The body is the message to send; lines `===` separate several messages, and `=== wait-for-server` waits for a message of the server before sending the next one, as in httpYac. Then the region waits for the server's messages until it has `# @expect` of them or `# @timeout` (`--message-timeout`, 5000 ms) has passed. The messages received are the response: its body is their JSON array, each message parsed when it is JSON. ```http ### Request a pickup: the reply carries the request's id # @expect 1 WS ws://localhost:4010/pickups {"type":"pickup.request","requestId":"6f1c2b9e-7f8a-4c35-9a51-2d1e8b7f0c11","carrier":"dhl", ...} ?? body $[0].type == pickup.confirmed ?? body $[0].requestId == 6f1c2b9e-7f8a-4c35-9a51-2d1e8b7f0c11 ### Report a courier's position # @expect 0 # @property content-type application/json # @property payload-format-indicator utf-8 MQTT mqtt://localhost:1883 publish: couriers/cou_4821/position qos: 1 {"courierId":"cou_4821","at":"2026-10-06T07:41:02Z","position":{"type":"Point","coordinates":[21.0067,52.2319]}} ### Receive a job offer # @channel courierJobOffers # @expect 1 MQTT mqtt://localhost:1883 subscribe: couriers/+/offers ?? body $.length == 1 ``` ```text PASS WS ws://localhost:4010/pickups -> 1 sent, 1 received (12 ms) requestPickup PASS MQTT mqtt://localhost:1883 -> 1 sent, 0 received (3 ms) receiveCourierPositions PASS MQTT mqtt://localhost:1883 -> 0 sent, 1 received (6 ms) offerPickupJob ``` The headers of an `MQTT` region are its settings: | Header | Meaning | | --- | --- | | `topic` | The topic messages are published to, also subscribed to (unless `subscribe` is given) | | `publish` | The topic messages are published to | | `subscribe` | The topic filters subscribed to before publishing, comma-separated (`couriers/+/offers, hubs/#`) | | `qos` | `0` (the default), `1` or `2`, for publishing and subscribing | | `retain` | `true` to publish retained messages | | `clientId`, `keepAlive`, `clean` | The connection's client id (a random one by default), keep-alive in seconds (60) and clean session (`true`) | | `username`, `password` | The CONNECT's user name and password; without them, `APIGENIE_MQTT_USERNAME` and `APIGENIE_MQTT_PASSWORD` | | `version` | `auto` (the default: MQTT 5, else 3.1.1 when the broker refuses it), `5` or `3.1.1` | The client's own messages coming back on a topic it subscribed to are left out. Retained messages of a subscription count as received. Settings are substituted like any header, so a password can stay out of the file: `password: {{$processEnv BROKER_PASSWORD}}`, or a variable of an environment file. A setting the region doesn't give comes from `APIGENIE_MQTT_USERNAME` or `APIGENIE_MQTT_PASSWORD`. MQTT 5 sends a password without a user name; MQTT 3.1.1 sends one only with a user name, so with `version: 3.1.1` a password alone is a warning and isn't sent. A broker that refuses them fails the request with `the broker refused the connection (reason code 0x86) (bad user name or password)` (MQTT 5), or `(return code 4) (bad user name or password)` (MQTT 3.1.1). Credentials never show in the output or the reports: wherever they would, they read `[redacted]`, and a URL's `user:password@` is shown as `***@`. The user name and password of the URL itself aren't sent. | In checks and scripts | Value | | --- | --- | | `?? body $.length == 2` | How many messages came (`.length` also gives the size of any array or string) | | `?? body $[0].type == pickup.confirmed` | A value of the first message | | `?? status == 101` | The handshake's status (WS); the CONNACK's return code, `0` (MQTT) | | `response.messages[i]` | In scripts: `topic` (the topic, or the WebSocket's path), `body` (parsed when JSON), `rawBody`, `binary` | #### MQTT 5 properties An `MQTT` region connects with MQTT 5. A broker that refuses it (reason code 0x84, a 3.1.1 broker's return code 1, or a connection closed before the CONNACK) gets a second connection with MQTT 3.1.1, and the request notes `the broker refused MQTT 5`. `version: 5` doesn't fall back; `version: 3.1.1` doesn't try MQTT 5. `# @property` gives the properties of every message the region publishes: | Directive | Property | | --- | --- | | `# @property content-type application/json` | Content Type | | `# @property response-topic clients/{{client}}/quotes` | Response Topic (no wildcards); the region also subscribes to it | | `# @property correlation-data {{$uuid}}` | Correlation Data | | `# @property payload-format-indicator utf-8` | Payload Format Indicator: `utf-8` or `1`, `bytes` or `0` | | `# @property message-expiry-interval 60` | Message Expiry Interval, in seconds | | `# @property user replyTo dispatch/d-1/replies` | A user property (repeat the line for more; the value may be empty) | Values are substituted like headers. An unknown property, a missing value or a bad value is a warning, and that property isn't sent. Over MQTT 3.1.1 none is sent, and the request notes it. Over MQTT 5 the messages sent and received are checked with their properties, against the message's and the operation's MQTT bindings (0.2.0), as `message.properties.invalid` (`error`): | Binding | Checked | | --- | --- | | message `payloadFormatIndicator` | The Payload Format Indicator is the same (absent counts as 0) | | message `contentType` | The Content Type is the same, ignoring case | | message `correlationData` | Correlation Data is present and matches the schema | | message `responseTopic` | A Response Topic is present, the string given or matching the schema | | operation `messageExpiryInterval` | A Message Expiry Interval is present and no longer | A request whose reply address is in a header (`$message.header#/replyTo`) is answered on the topic in that user property; one whose reply channel has `address: null` is answered on its Response Topic. Either way the region waits for the reply on that topic, checks it as the reply, and compares its correlation id (header or payload) and, when a binding declares `correlationData`, its Correlation Data with the request's. ```http ### Ask for a quote: the reply comes to the Response Topic # @expect 1 # @property content-type application/json # @property response-topic clients/7/quotes # @property correlation-data {{$uuid}} # @property user correlationId c-42 MQTT mqtt://localhost:1883 publish: quotes qos: 1 {"parcel":"P1"} ?? body $[0].price exists ``` A message whose bindings state properties counts in the coverage as checked (MQTT 5) or unchecked (MQTT 3.1.1): `summary.properties` and `operations[].properties` in the report, `MQTT 5 properties: checked on 2 messages, not on 0 (MQTT 3.1.1)` in the summary. ### Kafka requests A `KAFKA` region produces records to a Kafka cluster and reads what comes on its topics (ADR-170). The URL names a broker, `kafka://host:port` (port 9092 by default) or `kafkas://host:port` (9093, over TLS, verified as `https://` with `--ca-cert`, `--insecure` and `--client-cert`); several bootstrap servers are separated by commas. The cluster needs Kafka 2.3 or later. A cluster that creates topics on first use creates the region's topic, as Kafka's producers ask it to. ```http ### Place an order: the confirmation carries its correlation id # @expect 1 # @timeout 10000 # @property correlationId {{$uuid}} KAFKA kafka://localhost:9092 topic: shop.orders subscribe: shop.confirmations key: cust-1 {"sku": "A-1"} ?? body $[0].accepted == true ### Await a shipment the application produces # @channel shipments # @expect 1 KAFKA kafka://localhost:9092 subscribe: shop.shipments ``` | Header | Meaning | | --- | --- | | `topic` | The topic the records are produced to; also read, unless `subscribe` is given | | `subscribe` | The topics read, separated by commas | | `key` | The records' key | | `partition` | The records' partition; without it, the key's (murmur2, as Kafka's producers choose), else the next in turn | | `username`, `password` | SASL PLAIN credentials; without them `APIGENIE_KAFKA_USERNAME` and `APIGENIE_KAFKA_PASSWORD`. They are `[redacted]` in the output and reports | `# @property NAME VALUE` lines are the records' headers, the name as written (`# @property user NAME VALUE` too). The body's parts (`===`) are the records; `=== wait-for-server` waits for a record before the next part. Before the first record is produced, the region takes the latest offsets of the topics it reads, so a reply can't come too early; then it reads until it has `# @expect` records or `# @timeout` has passed. Its own records are left out. The records read are the response, as an MQTT region's messages are. With an AsyncAPI `--spec`, what is produced is checked against the receive operations of the topic's channel and what is read against its send operations, or as the reply to a request: the value, the headers against the message's `headers`, the key against the message's Kafka binding `key` (`message.key.invalid`) and the partition against the channel's Kafka binding `partitions` (`message.partition.invalid`). A channel's topic is its Kafka binding's `topic`, else its address. A reply is paired with its request by the correlation id in a header (`$message.header#/...`) or the payload, and a reply address in a header is followed, as for MQTT 5. Compressed records aren't read; they are reported as errors. ### AMQP requests An `AMQP` region publishes to an AMQP 0-9-1 broker and reads what comes to its queues (ADR-171). The URL is `amqp://[user:password@]host[:port][/vhost]` (port 5672, the virtual host percent-encoded: `/%2Fcarpet-express`), or `amqps://` (5671, over TLS, verified as `https://`). ```http ### File a declaration: the decision comes to the decisions queue # @expect 1 # @property delivery-mode 2 # @property correlationId {{$uuid}} AMQP amqp://localhost:5672/%2Fcarpet-express exchange: customs routing-key: customs.GB.filing subscribe: customs.decisions {"destinationCountry": "GB"} ### Await what the application publishes to the customs exchange # @expect 1 AMQP amqp://localhost:5672/%2Fcarpet-express bind: customs customs.*.filing ``` | Header | Meaning | | --- | --- | | `exchange` | The exchange the messages go to; without it, the default exchange (routing keys are queue names) | | `routing-key` | The messages' routing key | | `subscribe` | Queues consumed, separated by commas; a queue's messages go to the region, not to the application | | `bind` | `EXCHANGE KEY`: a queue of the region's own, bound to the exchange with that binding key (none for fanout); can be repeated | | `mandatory` | `false`: a message no queue takes isn't an error (`true` by default) | | `username`, `password` | PLAIN credentials; without them the URL's, then `APIGENIE_AMQP_USERNAME` and `APIGENIE_AMQP_PASSWORD`, then `guest`. They are `[redacted]` in the output and reports | `# @property NAME VALUE` lines with the name of a basic property set it: `content-type`, `content-encoding`, `delivery-mode`, `priority`, `correlation-id`, `reply-to`, `expiration` (milliseconds), `message-id`, `timestamp` (seconds since the epoch, or `now`), `type`, `user-id` and `app-id`. Other names, and `# @property user NAME VALUE`, are application headers. The region consumes its queues before it publishes, then reads until it has `# @expect` messages or `# @timeout` has passed; its own messages coming back are left out. RabbitMQ's direct reply-to works: `subscribe: amq.rabbitmq.reply-to` with `# @property reply-to amq.rabbitmq.reply-to`. With an AsyncAPI `--spec`, the channel is the one of the exchange and routing key (a queue channel's for the default exchange and its queue's name), and a message read from a queue is that queue's channel's. What is published is checked against the channel's receive operations and what is read against its send operations or as a reply: the body, the headers against the message's `headers`, and the properties against the AMQP bindings (`message.properties.invalid`). A reply is paired with its request by its correlation id in a header or a property: properties are seen by their AMQP and AsyncAPI names (`correlation_id` and `correlationId`, `reply_to` and `replyTo`), so `$message.header#/correlationId` finds either. ### gRPC requests With a `.proto` file as `--spec`, `GRPC` requests call the methods of its services, as httpYac writes them: ```http @host = localhost:50051 ### say hello GRPC {{host}}/acme.greeter.v1.Greeter/SayHello x-tenant: acme {"name": "Ann"} ?? status == 0 ?? body message == Hello Ann ?? header grpc-status == 0 ``` ```sh apigenie test --spec greeter.proto greeter.http ``` ```text PASS GRPC localhost:50051/acme.greeter.v1.Greeter/SayHello -> OK (6 ms) acme.greeter.v1.Greeter/SayHello Requests: 1 passed, 0 failed, 0 skipped Contract: 1 calls (0 undocumented), 0 errors, 0 warnings Coverage: 33.3% of 3 methods not tested: acme.greeter.v1.Greeter/SayHelloToAll, acme.greeter.v1.Greeter/Ping ``` - **The request line** is `GRPC HOST:PORT/package.Service/Method`, or a URL with `grpc://`, or with `grpcs://` for TLS. The method must be one of the file's: another is a failed request. - **Headers** are the call's metadata (names in lower case); `-bin` values are sent as written, base64-encoded. - **The body** is the request message in its proto3 JSON form (field names or their JSON names, 64-bit integers as numbers or strings, enum names, base64 bytes, the well-known types in their own forms); no body is `{}`. A body that isn't a valid message fails the request before anything is sent, naming the field: `the body isn't a valid acme.greeter.v1.HelloRequest: name: expected a string`. - **The `.proto` file is `--spec`**, with its imports looked up in `--proto-path` directories, then next to the file, then among the standard files (`google/protobuf/*`, `google/api/*`, `google/rpc/*`), as `apigenie-server --proto-path` does. httpYac's `proto < ./file.proto` line is read and ignored with a warning. - **Assertions and scripts** see the call as a response: `status` is the gRPC status code (`0` for OK, `5` for NOT_FOUND) and the status text its name, the headers are the response metadata and the trailers (`grpc-status`, `grpc-message` among them), and the body is the response message as JSON. A server-streaming or bidi-streaming call's body is a JSON array of its messages. - **Every kind of method.** Client- and bidi-streaming methods take a stream of messages, written as for [WS regions](#websocket-and-mqtt-requests): lines `===` separate them, and `=== wait-for-server` waits for a message of the server before the next one is sent (bidi methods only). After the last message the call is half-closed, and its end (the trailers) is awaited for `# @timeout` (`--message-timeout`, 5000 ms); a call that doesn't end in time is cancelled and fails. Unary and server-streaming methods take one message: more fail the request. - **HTTP/2**, cleartext with prior knowledge (h2c), or over TLS with ALPN `h2` for `grpcs://` (and `https://`) URLs, verified as `https://` requests are (`--ca-cert`, `--insecure`). `--timeout` bounds the connection and each wait for the server, and is sent as `grpc-timeout`. ```http ### a client stream: the messages, then one answer GRPC {{host}}/acme.chat.v1.Chat/Upload {"author": "Ann", "text": "hi"} === {"author": "Bob", "text": "hello"} ?? body lines == 2 ### a bidi stream that waits for each answer before it goes on GRPC {{host}}/acme.chat.v1.Chat/Talk {"author": "Ann", "text": "hi"} === wait-for-server {"author": "Ann", "text": "bye"} ?? body $.length == 2 ?? body $[1].text == re: bye ``` Each call is checked with the checks of [gRPC contract testing](/docs/reference/server#contract-testing-of-a-grpc-api): the request and response messages against their types (fields marked `[(google.api.field_behavior) = REQUIRED]` included), each message of a stream at `message[0]`, `message[1]`, ..., the status and its `google.rpc` details, the number of messages of a unary or client-streaming call. The coverage counts the file's methods. Without a `.proto` file as `--spec`, `GRPC` requests are skipped; HTTP requests in the same files are sent but not checked. ### Environments and variables `{{name}}` is replaced by the variable `name`. Variables come from, the later winning: 1. the environment files of the request file's directory or, when it has none, the working directory: `http-client.env.json` (its `$shared` object, then the object of `--env`), `http-client.private.env.json` (the same), `.env` and `.env.NAME`; 2. `--env-file` files (JSON like `http-client.env.json`, or `KEY=VALUE` lines); 3. `@name = value` lines outside of requests; 4. what scripts set (`exports`, `client.global.set`); 5. `@name = value` lines of the request's region; 6. `--var NAME=VALUE`. Responses of named requests can be used in the requests after them: | Reference | Value | | --- | --- | | `{{list.response.status}}` | The status | | `{{list.response.headers.Location}}` | A header | | `{{list.response.body.$[0].id}}`, `{{list.response.body./0/id}}` | A value of the JSON body (a path or a JSON pointer) | | `{{list.response.body}}` | The body | | `{{list.id}}` | httpYac's form: a value of the JSON body | Built-in variables: `{{$uuid}}` (and `$guid`, `$random.uuid`), `{{$timestamp}}`, `{{$isoTimestamp}}`, `{{$datetime iso8601}}`, `{{$datetime rfc1123}}`, `{{$randomInt MIN MAX}}`, `{{$processEnv NAME}}` and `{{$dotenv NAME}}`. A variable that isn't defined is a warning and stays as it is. `\{{` is a literal `{{`. `Authorization: Basic user:password` (or `Basic user password`) is sent base64-encoded, as httpYac and the JetBrains client do. ### OAuth 2 `Authorization: oauth2 [GRANT] [PREFIX]` gets a token from the token endpoint and sends `Authorization: Bearer TOKEN` instead, with httpYac's syntax and variables: ```http GET {{host}}/pets Authorization: oauth2 client_credentials ``` | Grant | Sent to the token endpoint | | --- | --- | | `client_credentials` (when no grant is written) | The client's credentials | | `password` | `PREFIX_username` and `PREFIX_password` too | | `refresh_token` | `PREFIX_refreshToken` | The token request is configured by variables named after the prefix (`oauth2` when no prefix is written; `Authorization: oauth2 password keycloak` reads `keycloak_tokenEndpoint` and so on). They come from environment files, `@` variables and `--var`, like any other variable: | Variable | Meaning | | --- | --- | | `oauth2_tokenEndpoint` | The token endpoint's URL (required) | | `oauth2_clientId` | The client's id (required) | | `oauth2_clientSecret` | The client's secret; none for a public client | | `oauth2_scope` | Space-separated scopes | | `oauth2_audience`, `oauth2_resource` | Sent as `audience` and `resource` when set | | `oauth2_username`, `oauth2_password` | The `password` grant's user | | `oauth2_refreshToken` | The `refresh_token` grant's refresh token | | `oauth2_useAuthorizationHeader` | `true` (the default): the client's id and secret go in a `Basic` `Authorization` header; `false`: in the form | | `oauth2_issuer` | `openid` only: the token endpoint is read from `ISSUER/.well-known/openid-configuration` when `oauth2_tokenEndpoint` isn't set | `Authorization: openid ...` is the same, with the OpenID Connect discovery of `oauth2_issuer`. The token is requested once per run for each configuration (the endpoint, the client, the scopes, the user) and requested again, with its refresh token when it has one, shortly before its `expires_in` runs out. The token endpoint is reached like any request: `https://` is verified as `--ca-cert` and `--insecure` say. A token endpoint that refuses fails the request, which isn't sent: ```text FAIL GET http://localhost:8080/pets no OAuth 2 token: the OAuth 2 token endpoint http://localhost:8080/token answered 401 (invalid_client: unknown client) ``` The `authorization_code` (or `code`), `implicit` and `device_code` grants need a browser or a person, and token exchange isn't supported: those requests are skipped with a warning. Tokens, refresh tokens, client secrets, passwords and the credentials of `Authorization` headers are shown as `[redacted]` wherever they would appear: in URLs, errors, assertion messages, script logs, warnings and the reports. ### Assertions A line `?? SUBJECT [ARGUMENT] PREDICATE [VALUE]` checks the response: | Subject | Value | | --- | --- | | `status` | The status | | `header NAME` | The header's value | | `body` | The body; `body PATH` a value of the JSON body (`$.items[0].id`, `items[0].id` or `/items/0/id`) | | `duration` | The time to the response, in milliseconds | | `js EXPRESSION` | The value of a JavaScript expression with the scripts' API | Predicates: `==`, `!=`, `>`, `>=`, `<`, `<=` (numbers when both sides are numbers), `startsWith`, `endsWith`, `includes` (or `contains`), `matches` (a regular expression, `/.../flags` too), `exists`, `isArray`, `isBoolean`, `isNumber`, `isString`, `isTrue` and `isFalse`. A `js` subject without a predicate must be true. Variables are substituted in the argument and the value. ### Scripts `{{` and `}}` on lines of their own enclose an httpYac script: before the request line it runs before the request is sent, after it once the response came. JetBrains handlers work too: `< {% ... %}` before the request line, `> {% ... %}` after it. Scripts run in a sandboxed JavaScript engine (QuickJS) with no file system or network access, five seconds each. Both flavours get the same API: | Name | What it is | | --- | --- | | `response.status`, `response.statusCode` | The status | | `response.headers` | Headers by lower-case name; `headers.valueOf(name)`, `headers.valuesOf(name)` | | `response.body` | The body, parsed when it is JSON; `response.parsedBody`, `response.rawBody` | | `response.contentType.mimeType`, `.charset` | The media type | | `response.duration` | Milliseconds | | `request.method`, `request.url`, `request.headers`, `request.body` | The request as written | | `request.variables.set(name, value)`, `.get(name)` | Variables of this request (pre-request scripts) | | `exports.name = value`, `client.global.set(name, value)` | Variables of the following requests | | `client.global.get(name)`, `request.environment.get(name)` | A variable | | `test(name, fn)`, `client.test(name, fn)` | A named check: it fails when `fn` throws (or its promise rejects) | | `test.status(code)`, `test.header(name, value)`, `test.headerContains(name, text)`, `test.hasResponseBody()`, `test.totalTime(ms)` | httpYac's shortcuts | | `assert(value, message)`, `client.assert(...)`, `assert.equal`, `assert.strictEqual`, `assert.deepStrictEqual`, `assert.notEqual`, `assert.fail` | Throws when the check fails | | `console.log`, `console.warn`, ..., `client.log` | Lines of the request's log (`--verbose`, the reports) | Variables are also local names of scripts when they are identifiers. A script that throws fails its request. Changes an ordinary pre-request script makes to `request` are not sent: set variables instead. Imported files opt into the header-addition API described below. ### Importing collections `apigenie test import` reads a Postman Collection Format v2.0 or v2.1 JSON export and writes request files that `apigenie test` can run. It uses an independently implemented reader, not Postman's JavaScript or the `postman-collection` SDK. Collection v1 is not supported: export as v2.1 from Postman. ```sh apigenie test import carpet-express.collection.json -o requests \ --environment mock.environment.json --spec carpet-express.openapi.yaml apigenie test requests --env mock --spec carpet-express.openapi.yaml \ --var baseUrl=http://localhost:4010/v2 ``` | Import option | Meaning | | --- | --- | | `COLLECTION` | Collection JSON file to import | | `-o DIR`, `--output DIR` | Required output directory | | `--environment FILE` | An exported environment JSON file; can be repeated | | `--spec SPEC` | Bind matching requests to operation IDs and write saved response examples as an OpenAPI overlay | | `--force` | Replace existing generated files; never delete unrelated files | The collection's root requests go into `.http`. A folder `Shipping` becomes `shipping.http`; its nested folder `Tracking` becomes `shipping/tracking.http`. Every request is a `###` region with a stable `# @name` slug. Collection and inherited folder variables are written as `@name = value` defaults at the top of each file. Slug collisions get deterministic suffixes. Collection and folder auth and pre-request/test scripts are inherited by descendant requests; explicit `noauth` disables inherited authentication. URLs and variables retain `{{name}}` references. Active headers and query parameters, raw, urlencoded, multipart form-data, GraphQL and file bodies are mapped. GraphQL becomes an HTTP POST with a JSON envelope, not the unsupported `GRAPHQL` pseudo-method and not GraphQL-specific contract validation. Available upload files are copied into the output with relative `< ./path` references; missing files are kept as references and reported as warnings. Relative source upload paths are resolved against the collection file's directory. Multipart file references are read as binary data when the request runs. Exported environments become named entries in `http-client.env.json`. Bearer, basic and API-key authentication becomes headers or query parameters with environment-variable references. Literal credentials and known credential defaults are stored only in the generated `http-client.private.env.json` (with the same `$shared` and environment entries), not in `http-client.env.json`, request files, overlays or the import summary; `apigenie test` reads both files. **The private environment file contains secrets: protect it and do not commit it to source control.** The importer cannot discover arbitrary secrets hidden in unrelated application data. OAuth 2 auth with the client credentials or password grant becomes `Authorization: oauth2 client_credentials` (or `password`) and the [OAuth 2 variables](#oauth-2): the access token URL, client id, scope, audience and client authentication (`header` or `body`) in `http-client.env.json`, the client secret, user name and password in `http-client.private.env.json`. Each distinct OAuth 2 configuration gets its own prefix (`oauth2`, `oauth2_2`, ...). A token stored in the collection isn't used: a new one is requested. The authorization code, PKCE and implicit grants need a browser: those requests are written disabled, their auth kept as a comment without its secrets, with a warning. Adding the token to the query and header prefixes other than `Bearer` are warnings; the token is sent in the `Authorization` header with the type the token endpoint gives. Imported files follow the ordinary `.http` variable precedence: file defaults override environment values, and `--var` overrides both. This differs from Postman's precedence. For marked files, environment discovery searches parent directories too, so nested request files can find the generated root environment. Script changes are in memory only, not saved back to the environment file. Files are run in the existing explicit-file or sorted-directory order; splitting folders into separate files does not preserve arbitrary cross-folder collection order. With `--spec`, requests that bind by method and path to an operation with an ID get `# @operation `. Saved responses become additive examples in `.overlay.json`; the specification is never edited. Apply the overlay explicitly: ```sh apigenie overlay carpet-express.openapi.yaml \ --overlay requests/carpet-express.overlay.json -o carpet-express.with-examples.yaml ``` Without a specification or a usable binding, examples are preserved as comments. Unresolved bindings or overlay targets produce warnings rather than guessed operation IDs. #### Imported script API Files carrying `# @postman` get a small compatibility shim in QuickJS. Unmarked files do not get a `pm` global. Pre-request blocks go before the request; test blocks go after it. Scripts containing their own standalone `}}` line are JSON-escaped inside the block so that multiline strings and JavaScript tokens remain intact. Simple `pm.test` callbacks that only assert a literal response status become `?? status == N` instead, so they do not require scripting support. | API | Supported subset | | --- | --- | | `pm.test(name, fn)` | Records a named check; a thrown assertion fails it | | `pm.expect(value)` | `to.equal`, `to.eql`, `to.be.true`, `to.be.false`, `to.have.property`, `to.have.status`, `to.include`, `to.be.oneOf`, `to.be.a` | | `pm.response` | `code`, `status`, `json()`, `text()`, case-insensitive `headers.get(name)`, `to.have.status(code)` | | `pm.environment` | `get`, `set`, `unset` | | `pm.collectionVariables` | `get`, `set` | | `pm.variables` | `get`, `set`; request-local changes | | `pm.globals` | `get`, `set` | | `pm.request.headers.add({key, value})` | Adds a header before sending a marked request | This is not the full SDK or Chai. `pm.sendRequest`, `postman.setNextRequest`, `pm.cookies` and `pm.visualizer` cause the **whole affected script block** to be kept as comments, with warnings that name the call and its source line. OAuth 2 auth with a grant that needs a browser is preserved as comments and the request is disabled. The summary lists unsupported features; they are never silently dropped. Other unknown script APIs fail explicitly at runtime. Scripts remain subject to the existing QuickJS time limit and lack of filesystem/network access. Imported `https://` URLs are sent over TLS like any other request (see `--ca-cert` and `--insecure`). Import returns 0 after a successful conversion, including one with reported warnings, 2 for bad command usage, and 3 for input or output failures. Existing generated filenames are refused unless `--force` is supplied. A directory literally named `import` can still be run with `apigenie test -- import`. ### Generated requests `--generate` sends one request per operation of `--spec`, checked as that operation: path parameters, required query, header and cookie parameters, and a body of the first media type (JSON first), each from the document's examples or generated from its schemas (`readOnly` properties left out). An XML media type gets an XML body, written as the schema's `xml` objects describe it. The URL is `--base-url` (or the document's first server) followed by the operation's path. Request files and `--generate` can be combined. With an AsyncAPI document, `--generate` sends one region per WebSocket or MQTT channel, bound with `# @channel` (and `# @operation` when the channel has one receive operation): WebSocket channels at `--base-url` (`ws://host:port`) followed by the channel's address, MQTT channels at `--mqtt-url`, publishing to the address and subscribing to it when the application sends there. Parameters of the address take their default, first enum value or example. The messages are those of the channel's receive operations, from their examples or generated from their schemas; MQTT regions get the `# @property` lines of the first one's bindings and headers (correlation data and response topics generated from their schemas, user properties from the first example's headers or generated); `# @expect` is the number of replies, plus one when the application sends on the channel. HTTP channels get one request per operation, bound with `# @operation`, at `--base-url` (a `ws://` URL is taken as `http://`) followed by the channel's address: a `receive` operation's with its HTTP binding's method and its first message (an example, else generated) as the body, a `send` operation's a `GET`. Channels without an address are left out with a warning, as are the channels of a protocol whose URL wasn't given. Kafka channels get one `KAFKA` region per operation at `--kafka-url`, bound with `# @operation`. A `receive` operation's region produces its first message (an example, else generated) to the channel's topic, with a key made from the message's Kafka binding and `# @property` headers from its headers; it reads the reply's topic for one record (`# @expect 1`) when the operation has a reply, and ends once the record is produced otherwise. A `send` operation's region only reads the topic, for one record the application produces. AMQP channels get one `AMQP` region per operation at `--amqp-url`, bound with `# @operation`. A `receive` operation's region publishes its first message to the channel's exchange and routing key (a queue channel's queue through the default exchange) with the properties its bindings say and its headers; it reads the reply's channel, its queue or a queue bound to its exchange, when the operation has a reply. A `send` operation's region reads the channel for one message the application publishes. With a `.proto` file, `--generate` sends one `GRPC` request per method of its services (the built-in reflection and health services aside) to `--base-url`, which is then required and is `HOST:PORT`, `grpc://HOST:PORT` or `grpcs://HOST:PORT` (TLS). The message is generated from the input type as the mock generates answers (`{}` for `google.protobuf.Empty`): REQUIRED fields set, `OUTPUT_ONLY` fields left out. Client- and bidi-streaming methods get a stream of two generated messages. With a GraphQL schema, `--generate` sends one `GRAPHQL` request per field of `Query`, `Mutation` and `Subscription` (deprecated fields left out), named after the field (`Query.book`) and bound to its operation (`QueryBook`). A subscription goes over graphql-ws with `# @expect 1`: the first event is enough. Required arguments get values made up from their types as `apigenie-server` makes up values (the scalar's or the argument's name: a UUID for `ID`, an email for `email`, a date for `DateTime`, an enum value that isn't deprecated, an input object with its required fields); optional ones are left out. The selection set has the leaf fields without required arguments, objects two levels down, and `__typename` with a fragment per type of an interface or a union. `--base-url` is required: it is the endpoint, with `/graphql` added when it has no path. ```graphql mutation MutationAddBook { addBook(input: {title: "Twenty Years After", genre: POETRY}) { id title genre pages author { id name email } } } ``` ### Reports `--report` writes the [contract report](/docs/reference/server#contract-testing-validating-proxy) of `apigenie-server` with `"report": "apigenie-test"`, plus `tests` (the counts and the run's gate) and `requests` (each request's file, line, URL, status, duration, operation, outcome, failures, assertions, violations and log; `grpc` and `grpcStatus` for `GRPC` requests). With a `.proto` file the contract members are those of the gRPC report (`"specification": "grpc"`, `methods`, `services`), with a GraphQL schema those of the GraphQL report (`"specification": "graphql"`, `rootFields`, `fields`, `uncoveredFields`, `operations`). Without `--spec`, only `report`, `formatVersion`, `version`, `startedAt`, `endedAt`, `tests` and `requests` are written. `formatVersion` is the version of the format, which changes only when a member is removed or changes meaning (see [Compatibility](/docs/reference/cli/compatibility)); `version` is its former name, kept for one release. The JSON Schema of the report is [apigenie-test-report.json](https://apigenie.pl/schemas/apigenie-test-report.json). `--report-junit` writes a `requests` suite (a test case per request: failed, or skipped for disabled requests) and, with `--spec`, the contract suite of `apigenie-server`. With an AsyncAPI document, the report is the AsyncAPI proxy's (`"specification": "asyncapi"`, channels, operations with their messages, violations); WS and MQTT requests also have `channel`, `address`, `messagesSent`, `messagesReceived` and `notes` (what wasn't checked, and why), and the coverage `--min-coverage` checks is the share of the operations' messages seen. ## Options | Option | Description | | --- | --- | | `FILES` | `.http` and `.rest` files, or directories to search for them | | `--spec SPEC` | The OpenAPI 3.0, 3.1 or 3.2 (or Swagger 2.0) document HTTP requests are validated against, the GraphQL schema (`.graphql`, `.graphqls`, `.gql`) of GraphQL requests, the `.proto` file of `GRPC` requests, or the AsyncAPI 3.x document WS, MQTT, KAFKA and AMQP requests and the requests of its HTTP channels are validated against | | `--proto-path DIR` | A directory in which the imports of a `.proto` `--spec` are looked up; can be repeated | | `--generate` | Also send one request per operation (OpenAPI), root field (GraphQL), method (`.proto`) or channel (AsyncAPI; one per operation of an HTTP, Kafka or AMQP channel) of `--spec` | | `--base-url URL` | The server's URL for `--generate` (the endpoint, required, for a GraphQL schema; `HOST:PORT` for a `.proto` file; `ws://` or `http://` for an AsyncAPI document's WebSocket and HTTP channels), also put before request URLs that start with `/` | | `--mqtt-url URL` | The MQTT broker (`mqtt://host:port`) of an AsyncAPI document's channels for `--generate` | | `--amqp-url URL` | The AMQP broker (`amqp://host:port/vhost`, `amqps://` over TLS) of an AsyncAPI document's AMQP channels for `--generate` | | `--kafka-url URL` | A broker of the Kafka cluster (`kafka://host:port`, `kafkas://` over TLS) of an AsyncAPI document's Kafka channels for `--generate` | | `--env NAME` | The environment of the environment files | | `--env-file FILE` | An environment file to read after the others; can be repeated | | `--var NAME=VALUE` | A variable above every other one; can be repeated | | `--header NAME:VALUE` | A header for every request that doesn't have it; can be repeated | | `--timeout MS` | How long to wait for a connection and for a response (30000) | | `--message-timeout MS` | How long WS, MQTT, KAFKA and AMQP requests, GraphQL subscriptions and gRPC client and bidi streams wait for messages (5000); `# @timeout` overrides it | | `--bail` | Stop at the first failed request | | `--ca-cert FILE` | A PEM file of certificate authorities trusted for `https://`, `grpcs://`, `wss://`, `mqtts://`, `kafkas://` and `amqps://` URLs, besides the system's; can be repeated | | `--insecure` | Don't verify the certificates of `https://`, `grpcs://`, `wss://`, `mqtts://`, `kafkas://` and `amqps://` servers; the traffic stays encrypted | | `--client-cert FILE` | A PEM client certificate (chain) presented to `https://`, `grpcs://`, `wss://`, `mqtts://`, `kafkas://` and `amqps://` servers that ask for one (mTLS); needs `--client-key` | | `--client-key FILE` | The unencrypted PEM private key of `--client-cert` | | `--report FILE` | Write the JSON report | | `--report-junit FILE` | Write the JUnit XML report | | `--fail-on none\|errors\|warnings` | The contract violations that fail the run (`errors`) | | `--min-coverage PERCENT` | Fail the run below this coverage of `--spec` (a GraphQL schema: the share of its fields; a `.proto` file: the share of its methods; an AsyncAPI document: the share of its operations' messages) | | `--severity CHECK=LEVEL` | Change a contract check's severity: `error`, `warn`, `info` or `off` (`ignore`); can be repeated. The checks are those of the OpenAPI, gRPC and AsyncAPI proxies or, with a GraphQL schema, of the GraphQL proxy; an unknown check is a bad option | | `--seed N` | Seed of the values `--generate` makes up | | `--formatter stylish\|json` | A line per request and a summary (`stylish`), or a JSON object per line | | `--quiet` | Show only failed requests and the summary | | `--verbose` | Also show the warnings and notes of the contract checks, and the scripts' log | ### --fail-on errors|warnings|none A failed request (an assertion, a script's test or error, no response, an unknown `@ref`) always fails the run. `--fail-on` says which contract violations do too: `errors` (the default), `warnings` (errors and warnings) or `none`. A request with such violations is shown as `FAIL`. ### --formatter json Each request is a line `{"event":"request",...}` with the members of the JSON report's `requests`; warnings are `{"event":"warning","file":...,"line":...,"message":...}`, notes about a Swagger 2.0 specification `{"event":"info",...}` with the same members; the last line is `{"event":"summary","formatVersion":1,"total":...,"passed":...,"failed":...,"skipped":...,"gate":{...}}` with the contract's `errors`, `warnings` and `coverage` when there is a `--spec`. Its `formatVersion` versions the whole stream, as `formatVersion` versions the report. ## httpYac compatibility **Supported** | Feature | Notes | | --- | --- | | `###` regions with titles, `#` and `//` comments | | | `GRPC` requests | Every method of the `.proto` file of `--spec` (streams of messages separated by `===`), over HTTP/2 (cleartext, or TLS for `grpcs://`); `proto <` lines are ignored with a warning | | Request line with or without method and HTTP version, multi-line query (`?`, `&`) | | | Headers, inline bodies, `< file` and `<@ file` bodies | `<@utf8 file`: the encoding is ignored | | `@name = value` variables | Outside of requests (file) or in a request's region | | `{{name}}`, `{{$uuid}}`, `{{$timestamp}}`, `{{$datetime}}`, `{{$randomInt}}`, `{{$processEnv}}`, `{{$dotenv}}` | See [Environments and variables](#environments-and-variables) | | `# @name`, `# @ref`, `# @forceRef`, `# @disabled` | Named responses are kept per file | | `{{ }}` scripts before and after the request | `exports`, `test`, `assert`, `console`, `request`, `response` | | JetBrains `< {% %}` and `> {% %}` handlers | `client.global`, `client.test`, `client.assert`, `client.log`, `request.variables` | | `??` assertions | `status`, `header`, `body`, `duration` and `js` subjects | | `WS` and `WEBSOCKET` requests | `ws://` and `wss://`; headers go with the handshake; text messages | | `MQTT` requests | `mqtt://` and `mqtts://`, MQTT 3.1.1; `topic`, `publish`, `subscribe`, `qos`, `retain`, `clientId`, `keepAlive`, `clean`, `username` and `password` headers | | `===` and `=== wait-for-server` between messages | | | `.env`, `.env.NAME`, `http-client.env.json`, `http-client.private.env.json` | `$shared` and the `--env` environment | | `Authorization: Basic user:password` | Encoded before sending | | `Authorization: oauth2` and `openid` with the `client_credentials`, `password` and `refresh_token` grants | See [OAuth 2](#oauth-2); `refresh_token` and OpenID Connect discovery are apigenie's additions | | `GRAPHQL` requests, `POST` requests of a query | Variables after a blank line; `# @operation` is the `operationName`; subscriptions over graphql-ws | **Not supported yet** | Feature | What happens | | --- | --- | | Server-sent events (`SSE`), AMQP | Not request lines | | `gql` fragment imports (`# @import ./fragments.gql`) | A warning; the request runs without them | | GraphQL subscriptions over the legacy `graphql-ws` subprotocol or SSE | The request fails: only `graphql-transport-ws` is spoken | | MQTT 5, client certificates | MQTT is 3.1.1; no certificate of the client is sent | | `# @keepStreaming`, binary WebSocket messages sent | A warning; messages are sent as text | | Redirects | Not followed: a 3xx response is checked as it is | | OAuth 2 and OpenID `authorization_code`, `implicit` and `device_code` grants, token exchange, `oauth2_keepAlive`, `oauth2_interceptRequest` | The request is skipped with a warning (the variables are ignored) | | AWS and digest authorization | The request is skipped with a warning | | `# @import`, `# @loop`, `# @sleep`, `# @jwt`, `# @keepStreaming`, other directives | A warning; the request runs | | `xpath` assertions, `sha256`/`md5` predicates | A warning; the assertion is ignored | | Offsets of `$datetime` and `$timestamp`, `$localDatetime` time zones | A warning; the current time in UTC is used | | `{{ expression }}` JavaScript inside a line, global `{{ }}` scripts outside of requests | Not evaluated (the latter with a warning) | | `require()`, npm packages, `client.exit()` in scripts | The script fails | | Response handler files (`> ./handler.js`) | A warning | | Changes a pre-request script makes to `request` | Not sent for ordinary files; marked imports support `pm.request.headers.add` | | `.httpyac.js`, `.httpyac.json`, `httpyac.config.js` | Not read | | Cookie jars | No cookies are kept between requests | ## Exit status | Code | Meaning | | --- | --- | | 0 | Every request passed and the contract gate held | | 2 | Bad options: no request files without `--generate`, `--generate` without `--spec`, or with a GraphQL schema or a `.proto` file but no `--base-url`, `--min-coverage` without `--spec`, an unknown `--severity` check; a request file, an environment file or the specification (an OpenAPI or AsyncAPI document, a GraphQL schema, or a `.proto` file with its imports) that can't be read or parsed; `--generate` without a URL it can use (no `--base-url` and no absolute server URL in the document, or a URL of the wrong scheme); a report that can't be written, or an unexpected error | | 5 | A request failed, or contract violations (`--fail-on`) or the coverage (`--min-coverage`) failed the run | ## See also - [apigenie-server](/docs/reference/server), whose validating proxy checks traffic something else drives, with the same checks and reports - [lint](/docs/reference/cli/lint), which checks the document itself --- # apigenie-generator Source: https://apigenie.pl/docs/reference/generator `apigenie-generator` generates code and data from an OpenAPI or AsyncAPI document, servers from a GraphQL schema, gRPC code from a Protobuf file set, documentation sites from OpenAPI, Swagger, AsyncAPI, GraphQL and Protobuf files, and validation schemas from JSON Schemas. It is a separate executable, installed next to `apigenie` by every package, the npm package and the Docker image (run it there with `docker run --entrypoint apigenie-generator mykulyak/apigenie ...`). Before 0.9.0 the generators were the `apigenie generate` command: `apigenie generate client fetch ...` is now `apigenie-generator client fetch ...`, with the same options and configuration. ## Usage ```sh apigenie-generator GENERATOR [TARGET] [...OPTIONS] INPUT_FILE apigenie-generator --version | --credits | --license [LIBRARY] | --license-list ``` `client go`, `client csharp`, `client java`, `client python`, `server go`, `server spring` and `server aspnet` read both kinds of documents: the generator is picked by the document, OpenAPI (3.0, 3.1, 3.2) or AsyncAPI (3.x). Swagger 2.0 documents can be converted first with [`convert --to openapi32`](/docs/reference/cli/convert). `server absinthe` reads a GraphQL schema (`.graphql`, `.graphqls` or `.gql`). Every code generator takes `-c, --config` (a configuration file, see [Configuration](/docs/reference/cli/configuration)), `-o, --output` (the output directory) and `--package-name` / `--package-version`; several take generator options as `-p name=value`. The pages below list the options of each target. ## Conformance checks The codegen conformance suite checks the generators at three levels: 1. **Snapshots and features.** Each generator's test suite generates from the shared fixtures, compares the output byte for byte with its snapshots, and checks each feature of the fixture against the target's manifest, which also lists the features the target doesn't support yet. 2. **Type checking and linting.** The generated code goes through the target language's type checker and linter, with pinned versions. 3. **Contract tests.** A generated client calls every operation of the fixture through the validating proxy, and a generated server receives generated requests and a hand-written request set; the traffic is validated against the fixture. Event-driven targets run against brokers. The fixtures model the fictional Carpet Express shipping service. Its [OpenAPI 3.1 definition](/samples/carpet-express.openapi-31.yaml) and [AsyncAPI 3.0 definition](/samples/carpet-express.asyncapi.yaml) are available as downloads. The suite generates from the OpenAPI 3.2 document; its 3.1 and 3.0 variants serve to look into a target whose 3.2 output fails early. Coverage varies by target, and a snapshot match is not a guarantee of complete standard support. Each target's reference documents its limitations, such as the [Fetch client's handling of servers and streaming responses](/docs/reference/generator/client-fetch#limitations). The experimental [Zod generator](/docs/reference/generator/schema-zod) has no Level 3 contract tests. ## Overlays Every target reading OpenAPI or AsyncAPI documents takes `--overlay FILE`: an [OpenAPI Overlay](/docs/reference/cli/overlay) (1.0 to 1.2) applied to the input before generating. Repeat it to apply several, in order. The vendor extensions a target reads (each target's page lists them) can then live in an overlay of that target instead of in the API description: ```sh apigenie-generator server spring openapi.yaml --overlay spring.overlay.yaml -o server ``` - The overlays listed under `commands.generate..overlay` in the [configuration file](/docs/reference/cli/configuration#commandsgenerate), relative to it, apply first, then the `--overlay` ones. - An AsyncAPI document that references other files is bundled before the overlays apply. - An action whose target selects nothing prints a warning with its position, e.g. `spring.overlay.yaml:12:5: action #3 ($.paths['/pickups'].get) selects no nodes.`. With `--strict` it is an error: nothing is generated and the exit status is 2. - Warnings and errors about a value an overlay set point at the overlay (`spring.overlay.yaml:9:9 Warning ...`); the others keep their line in the input. `docs`, which reads several inputs, and the targets of GraphQL schemas and `.proto` files don't; for `docs`, apply the overlays with [`apigenie overlay`](/docs/reference/cli/overlay) first. ## Exit status `apigenie-generator` exits with 0 when the code was generated and with 2 when the options, the configuration, an overlay or the input document can't be used (with `--strict`, also when an overlay action selects nothing), or no generator is named (`apigenie-generator` or `apigenie-generator client` alone print the help); see the [exit statuses](/docs/reference/cli/overview#exit-status) shared by the four binaries. `apigenie generate ...` exits with 2 and prints the `apigenie-generator` command to run instead. `schema zod` is [experimental](/docs/reference/generator/schema-zod). ## From OpenAPI ### Clients | Name | Summary | | ---- | ------- | | [client csharp](/docs/reference/generator/client-csharp) | C# client for `HttpClient` (.NET 8) | | [client elixir](/docs/reference/generator/client-elixir) | Elixir client on Req | | [client fetch](/docs/reference/generator/client-fetch) | TypeScript client for the Fetch API | | [client go](/docs/reference/generator/client-go) | Go client for `net/http` | | [client java](/docs/reference/generator/client-java) | Java client for Spring `RestClient`, `RestTemplate`, OpenFeign or HTTP interfaces | | [client python](/docs/reference/generator/client-python) | Python client on httpx and Pydantic | ### Servers | Name | Summary | | ---- | ------- | | [server aspnet](/docs/reference/generator/server-aspnet) | ASP.NET Core server (Minimal APIs) | | [server express](/docs/reference/generator/server-express) | TypeScript Express server | | [server fastapi](/docs/reference/generator/server-fastapi) | Python FastAPI server | | [server go](/docs/reference/generator/server-go) | Go `net/http` server | | [server mcp](/docs/reference/generator/server-mcp) | MCP server in TypeScript: a tool per operation (experimental) | | [server orpc](/docs/reference/generator/server-orpc) | TypeScript server on oRPC | | [server phoenix](/docs/reference/generator/server-phoenix) | Elixir Phoenix 1.7 server | | [server spring](/docs/reference/generator/server-spring) | Spring Boot server | ### Documentation and data | Name | Summary | | ---- | ------- | | [docs](/docs/reference/generator/docs) | a documentation site; also from Swagger 2.0, AsyncAPI, GraphQL and Protobuf files, several at once | | [data](/docs/reference/generator/data) | example data for a schema of the document | ## From AsyncAPI | Name | Summary | | ---- | ------- | | [client csharp](/docs/reference/generator/client-csharp-asyncapi) | C# Kafka client library | | [client elixir](/docs/reference/generator/client-elixir-asyncapi) | Elixir client library (WebSocket channels on Mint.WebSocket, Kafka on brod) | | [client go](/docs/reference/generator/client-go-asyncapi) | Go Kafka client package | | [client java](/docs/reference/generator/client-java-asyncapi) | Java Kafka client library on the Apache Kafka client | | [client kafkajs](/docs/reference/generator/client-kafkajs) | TypeScript Kafka client on KafkaJS | | [client python](/docs/reference/generator/client-python-asyncapi) | Python asyncio Kafka client on aiokafka | | [client ws](/docs/reference/generator/client-ws) | TypeScript WebSocket client | | [server aspnet](/docs/reference/generator/server-aspnet-asyncapi) | ASP.NET Core Kafka application | | [server faststream](/docs/reference/generator/server-faststream) | Python FastStream application (Kafka, RabbitMQ, MQTT) | | [server go](/docs/reference/generator/server-go-asyncapi) | Go Kafka application | | [server kafkajs](/docs/reference/generator/server-kafkajs) | TypeScript Kafka application on KafkaJS | | [server phoenix](/docs/reference/generator/server-phoenix-asyncapi) | Elixir Phoenix application (WebSocket channels, Kafka on Broadway and brod) | | [server spring](/docs/reference/generator/server-spring-asyncapi) | Spring Boot Kafka application | | [server ws](/docs/reference/generator/server-ws) | Node.js WebSocket server | ## From GraphQL | Name | Summary | | ---- | ------- | | [server absinthe](/docs/reference/generator/server-absinthe) | Elixir Absinthe server of the schema (`import_sdl`) | ## From Protobuf | Name | Summary | | ---- | ------- | | [client elixir](/docs/reference/generator/client-elixir-protobuf) | Elixir gRPC client on elixir-grpc | | [server elixir-grpc](/docs/reference/generator/server-elixir-grpc) | Elixir gRPC server on elixir-grpc | ## From JSON Schema | Name | Summary | | ---- | ------- | | [schema zod](/docs/reference/generator/schema-zod) | Zod schemas | --- # docs Source: https://apigenie.pl/docs/reference/generator/docs Generate a documentation site from OpenAPI, Swagger 2.0, AsyncAPI, GraphQL and Protobuf files, several at once. ## Usage ```sh apigenie-generator docs [...OPTIONS] INPUT... ``` ## Description The site is built in two steps: 1. `apigenie-generator docs` reads the inputs and writes a *docs bundle*: a manifest (`apigenie-docs.json`) and, per document and locale, the resolved document (OpenAPI and Swagger 2.0, with the files it references bundled in) or the documentation model (AsyncAPI, GraphQL and Protobuf), with the Git changelog and the example files when asked for. Next to the bundle it writes `package.json` and a README: an npm project that depends on the API Genie web components, `@mykulyak/apigenie-docs`. 2. `npm install`, then `npm run build` renders the bundle into `dist/`: one HTML page per API, operation and schema, prerendered so that it reads without JavaScript and hydrates with it (Try it out, search, navigation), with a sitemap, a locale switcher, a changelog page and the examples. Serve `dist/` from any static host. `npm run build -- --base /docs/ --origin https://example.com` hosts the site under a path and writes absolute URLs into the sitemap. The CLI never runs Node: the bundle is plain JSON, and `--bundle-only` writes it alone for a site built by other means, such as a page of your own holding an `` element. Several inputs of mixed families make one site, each document under its own path. An input that can't be read is reported with its positions and left out; the others are still written. Files of the output directory that the bundle doesn't write are left alone. ## Options | Option | Description | | --- | --- | | `-o`, `--output OUTPUT` | The output directory (required). | | `-c`, `--config CONFIG` | The configuration file: its `commands.generate.docs` section configures the site (below), its `diff` section the severities of the changelog. | | `--i18n LOCALE=FILE` | A translation file, as [`i18n extract`](/docs/reference/cli/i18n) writes it, for a locale. Can be repeated. Every file applies to every input, with the keys of the input's kind (JSON pointers for OpenAPI, Swagger and AsyncAPI, names for GraphQL and Protobuf); a file that translates nothing in an input is a warning. | | `--changelog` | Adds the changelog of each input from its Git history, as [`apigenie changelog`](/docs/reference/cli/changelog) computes it. An input outside a repository gets a warning and no changelog. | | `--package-name NAME` | The name of the generated npm package (`api-docs` by default). | | `--package-version VERSION` | Its version (`1.0.0` by default). | | `--bundle-only` | Writes the docs bundle only, without `package.json` and README. | ## Configuration All keys are optional and sit under `commands.generate.docs`. Paths are relative to the configuration file. ```yaml version: '1.0' commands: generate: docs: title: Acme APIs default_locale: en prefix_default_locale: false changelog: revisions: tags tag_pattern: 'v*' unreleased: true documents: - file: api/openapi.yaml id: shop title: Shop API - file: proto/orders.proto proto_paths: [proto] examples: - file: examples/create-order.json title: Create an order ``` | Key | Type | Default | Description | | --- | --- | --- | --- | | `title` | string | the first document's title | The title of the site. | | `default_locale` | string | `en` | The locale of the documents as written. `--i18n` files add the other locales. | | `prefix_default_locale` | bool | `false` | Whether the default locale's pages get the locale prefix too (`/en/...`). | | `changelog` | bool or object | `false` | `true` adds the changelog of every document (as `--changelog`); an object also selects the revisions: `revisions` (`commits` or `tags`), `tag_pattern`, `tag_sort` (`version` or `date`), `unreleased`, `from`, `to`, as the options of `apigenie changelog`. | | `documents` | list | | The inputs, besides those given on the command line: `file`, and optionally `id` (the document's directory in the bundle, by default the file's stem), `title`, `proto_paths` (the directories a .proto file's imports are searched in, before its own) and `examples` (paths, or `{file, title}`). | ## The docs bundle ``` apigenie-docs.json the manifest documents//document..json the document per locale documents//changelog.json with --changelog documents//examples/ example files package.json, README.md unless --bundle-only ``` The manifest holds `apigenieDocs: 1` and `formatVersion: 1` (the version of the format, under the name all JSON outputs of API Genie use; see [Compatibility](/docs/reference/cli/compatibility)), `title`, `generator`, `locales` (the default one first), `prefixDefaultLocale` and `documents`, each with `id`, `family` (`openapi`, `swagger`, `asyncapi`, `graphql` or `protobuf`), `title`, `version`, `source` (the input's file name), `files` (per locale), and `changelog` and `examples` when present. Paths are relative to the bundle. Its JSON Schema is [apigenie-docs-manifest.json](https://apigenie.pl/schemas/apigenie-docs-manifest.json). ## Examples Next to a GraphQL schema, the `.json`, `.yaml` and `.yml` files of its `_examples` directory (the examples [`apigenie test`](/docs/reference/cli/test) runs) are copied into the bundle and shown with the schema. Any document can list more under `examples` in the configuration. ## See also - [i18n](/docs/reference/cli/i18n), which extracts the translation files `--i18n` takes - [changelog](/docs/reference/cli/changelog), whose changelog `--changelog` writes into the bundle - [configuration](/docs/reference/cli/configuration) --- # data Source: https://apigenie.pl/docs/reference/generator/data Generate example data for a schema of an OpenAPI document. The command is experimental. ## Usage ```sh apigenie-generator data --pointer POINTER --output OUTPUT [...OPTIONS] INPUT_FILE ``` ## Description The command reads an OpenAPI 3.0, 3.1 or 3.2 document, takes the schema that `--pointer` points to, and writes a value that conforms to it. With `--number N` greater than 1 it writes a JSON array of `N` values. The output is JSON or YAML, chosen by the extension of the output file (`.json`, `.yaml` or `.yml`). Swagger 2.0 documents are not read; convert them first with [`convert --to openapi32`](/docs/reference/cli/convert). ```sh apigenie-generator data -p /components/schemas/Money -o money.json -n 2 openapi.yaml ``` ```json [ { "amount": "-841701990.0826", "currency": "ITG" }, { "amount": "-70238.2", "currency": "NOO" } ] ``` The pointer can name any schema of the document: a component (`/components/schemas/Pet`), a property (`/components/schemas/Pet/properties/tags`) or the schema of a parameter or a body. References inside the schema are followed when they are local (`#/...`). How values are made: - `enum` and `const` values are taken from the schema. Schema `example`, `examples` and `default` values are not used. - Numbers and integers stay within the schema's `minimum`, `maximum` and exclusive bounds. A bound the schema doesn't give comes from the configuration: -10 to 10 by default. - Strings with a `format` get a value of that format: `uuid`, `date`, `date-time`, `time`, `duration`, `email`, `idn-email`, `hostname`, `idn-hostname`, `ipv4`, `ipv6`, `uri`, `url`, `iri`, `uri-reference`, `iri-reference`, `uri-template`, `byte` and `password`. Strings with a `pattern` match the regular expression. Other strings are words of placeholder text, of a length within `minLength` and `maxLength`, or within the configured range (0 to 10) when the schema gives neither. - Arrays have between `minItems` and `maxItems` items, or a number within the configured range (0 to 10) when the schema gives neither. - Objects have all their required properties and a random selection of the optional ones, within `minProperties` and `maxProperties`. - `allOf` members are merged; `oneOf` and `anyOf` produce a value of one of the alternatives; a schema with several types (`type: [string, "null"]`) produces a value of one of them. - A schema with an `x-faker` extension gets its value from a faker provider (see "Extensions"), as in [`apigenie-server`](/docs/reference/server#scenarios-and-extensions). - Recursive schemas end: below a nesting depth of 16, objects only get their required properties and arrays their minimum number of items. Values are random: each run writes different data. A schema that can't produce a value (a `false` schema, an unresolved reference, a `minimum` greater than its `maximum`) stops the command with an error. ## Extensions | Extension | Effect | | --------- | ------ | | `x-faker` | the [faker provider](/docs/reference/cli/faker) that makes the value of a schema: a function name, `{name: [arguments]}`, or `{provider, args, options}` | | `x-apigenie-mock-faker` | the same as `x-faker`, which it takes precedence over | The extensions can live in an overlay for this target instead of in the document, e.g. `data.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Data fakers version: 1.0.0 actions: - target: $.components.schemas.User.properties.email update: x-faker: internet.email ``` ```sh apigenie-generator data -p /components/schemas/User openapi.yaml --overlay data.overlay.yaml -o user.json ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.data.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -p, --pointer Required. JSON pointer to the schema in the input document, e.g. `/components/schemas/User`. A pointer that doesn't point to a schema is an error (`Schema not found at '...'`). ### -o, --output Required. Path to the output file. Its extension, `.json`, `.yaml` or `.yml`, sets the format. ### -n, --number Number of values to generate, a positive integer; 1 by default. With a number greater than 1 the output is an array. ## Configuration The `commands.generate.data` section of the configuration file sets the ranges used where a schema doesn't give its own bounds. ```yaml version: '1.0' commands: generate: data: minimum: 0 maximum: 1000 minLength: 1 maxLength: 20 minItems: 1 maxItems: 3 ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `minimum` | number | `0` | Lower bound of numbers and integers whose schema has no lower bound. | | `maximum` | number | `10` | Upper bound of numbers and integers whose schema has no upper bound. | | `minLength` | integer | `0` | Minimum length of strings whose schema has neither `minLength` nor `maxLength`. | | `maxLength` | integer | `10` | Maximum length of strings whose schema has no `maxLength`. | | `minItems` | integer | `0` | Minimum number of items of arrays whose schema has neither `minItems` nor `maxItems`. | | `maxItems` | integer | `10` | Maximum number of items of arrays whose schema has no `maxItems`. | | `minProperties` | integer | `0` | Read and checked, but objects are only limited by their schema. | | `maxProperties` | integer | `10` | Read and checked, but objects are only limited by their schema. | Without a configuration file the numeric range is -10 to 10. When the `data` section is present, a key it leaves out takes the default of the table, so `minimum` becomes 0 unless it is given. A minimum greater than its maximum, or a negative count, is an error. --- # schema zod Source: https://apigenie.pl/docs/reference/generator/schema-zod Generate [Zod](https://zod.dev) (v4) schemas, with their TypeScript types, from JSON Schemas. > **(experimental, outside the 1.0 compatibility promise)** The generated schemas aren't yet > checked against a live API (conformance Level 3), and the options and the output can still > change. `schema zod` says so in its `--help` and on the first line it prints, on the standard > error. See the [experimental commands](/docs/reference/cli/overview#experimental-commands). ## Usage ```sh apigenie-generator schema zod [...OPTIONS] INPUT_FILE ``` ## Description The input is a JSON or YAML file: - a JSON Schema document (drafts 4, 6, 7, 2019-09 and 2020-12, 2020-12 when it has no `$schema`): its root schema, named after its `title` or the file, and its `$defs` (or `definitions`) are generated. A root that only holds definitions is left out; - an OpenAPI 3.x document: its `components.schemas`, with the OpenAPI 3.0 `nullable` keyword and boolean `exclusiveMinimum`/`exclusiveMaximum`; - a Swagger 2.0 document: its `definitions`, with the `x-nullable` extension; - an AsyncAPI document: its `components.schemas`. Schemas in other formats (Avro, ...) are skipped. Other schemas that a `$ref` points to, e.g. `#/$defs/User/properties/address` or a schema of another file, are named after their path (`UserAddressSchema`), so that each is written once. Every schema becomes a constant and a type: ```ts import * as z from "zod"; export const UserSchema = z.looseObject({ id: z.uuid(), email: z.email(), /** Display name */ name: z.string().min(1).max(80), age: z.int().gte(0).lt(150).optional(), }); export type User = z.infer; ``` Schemas are declared after the schemas they use. A schema using itself, or a schema declared later, from an object property gets a getter: ```ts export const CommentSchema = z.strictObject({ text: z.string(), get replies() { return z.array(CommentSchema).optional(); }, }); ``` Elsewhere, e.g. `items` of an array schema using the array itself, the reference is wrapped in `z.lazy()` and the constant is typed `z.ZodType`, so its inferred type is `unknown`. This is reported as a warning. ### How keywords are mapped | JSON Schema | Zod | | ----------- | --- | | `type` | `z.string()`, `z.number()`, `z.int()` (`z.int32()` for the `int32` format), `z.boolean()`, `z.null()`, `z.array()`, objects; several types make a `z.union()`, `null` with other types `.nullable()` | | no `type` | the type implied by the keywords, e.g. `minLength` makes a string; `z.unknown()` otherwise | | `enum`, `const` | `z.enum()` for strings, `z.literal()` otherwise | | `format` | `z.email()`, `z.url()` (`uri`, `iri`), `z.uuid()`, `z.iso.datetime({ offset: true })`, `z.iso.date()`, `z.iso.duration()`, `z.ipv4()`, `z.ipv6()`, `z.hostname()`, `z.base64()` (`byte`), `z.base64url()`; other formats are not validated | | `minLength`, `maxLength`, `pattern` | `.min()`, `.max()`, `.length()`, `.regex()` | | `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` | `.gte()`, `.lte()`, `.gt()`, `.lt()`, `.multipleOf()` | | `items`, `minItems`, `maxItems` | `z.array()`, `.min()`, `.max()` | | `prefixItems` (or an array of `items`) | `z.tuple()`, with optional items beyond `minItems` and a rest of `items` (`additionalItems`) | | `uniqueItems`, `contains` | refinements | | `properties`, `required` | the shape, with `.optional()` for properties that aren't required, or `.default()` with their `default` | | `additionalProperties` | none or `true`: `z.looseObject()` (unknown properties are kept); `false`: `z.strictObject()`; a schema: `.catchall()`, or `z.record()` without properties | | `patternProperties` | a refinement validating the matching properties | | `minProperties`, `maxProperties`, `propertyNames`, `dependentRequired`, `dependentSchemas` (`dependencies`) | refinements | | `allOf` | `z.intersection()` | | `anyOf`, `oneOf` | `z.union()`, or `z.discriminatedUnion()` when a required property with constant values tells the objects apart (e.g. the OpenAPI `discriminator`) | | `not`, `if`/`then`/`else` | refinements | | `$ref` | the referenced schema's constant | | `description` (or `title`), `deprecated` | JSDoc comments; `.describe()` with the `describe` option | Not supported, and reported as warnings: `unevaluatedProperties` and `unevaluatedItems`. `$dynamicRef` and `$recursiveRef` are resolved to their initial targets. In very large graphs of recursive schemas, TypeScript may give up inferring a type (`TS2589: Type instantiation is excessively deep` at `z.infer`); the schemas still validate. Approximations: `oneOf` is not exclusive, `uniqueItems` compares items by their JSON text, string lengths count UTF-16 code units, and `uuid` accepts the versions known to Zod. The types of properties are implied by their keywords also where JSON Schema accepts other types, e.g. `{ "minLength": 1 }` rejects numbers. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | base of the names of the constant and the type of a schema (`AnimalSchema`, `Animal`) | | `x-nullable` | `true` makes a schema accept `null`, as in Swagger 2.0 documents | The extensions can live in an overlay for this target instead of in the document, e.g. `zod.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Zod names version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-apigenie: codegen: name: Animal ``` ```sh apigenie-generator schema zod openapi.yaml --overlay zod.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.schema_zod.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the schemas are generated. ### --package-name Name of the generated package. Defaults to the title of the document or the name of the file. ### --package-version Version of the generated package. Defaults to the version of the API, or `0.0.0`. ## Configuration ```yaml version: '1.0' commands: generate: schema_zod: layout: package import_extension: .js schema_suffix: Schema type_suffix: '' types: true additional_properties: loose describe: false ``` | Option | Description | | ------ | ----------- | | `layout` | `package` (default): an npm package with `src/schemas.ts` and `src/index.ts`; `sources`: `schemas.ts` only | | `import_extension` | Extension of relative imports: `.js` (default, Node.js) or an empty string (bundlers) | | `schema_suffix` | Appended to the names of the schema constants (default `Schema`) | | `type_suffix` | Appended to the names of the types (default none) | | `types` | Whether `export type X = z.infer` is generated (default `true`) | | `additional_properties` | Objects without `additionalProperties`: `loose` (default) keeps unknown properties, `strip` removes them (`z.object()`) | | `describe` | Whether descriptions are attached with `.describe()` (default `false`) | --- # client csharp Source: https://apigenie.pl/docs/reference/generator/client-csharp Generate a C# client of an OpenAPI document, built on `HttpClient` and `System.Text.Json`, for .NET 8 (or 9 and 10). From an AsyncAPI document, the command generates a C# Kafka client library instead; see [apigenie-generator client csharp (AsyncAPI)](/docs/reference/generator/client-csharp-asyncapi). ## Usage ```sh apigenie-generator client csharp [...OPTIONS] INPUT_FILE ``` ## Description The input must be an OpenAPI 3.0, 3.1 or 3.2 document. The output is a class library: a `.csproj` targeting `net8.0` with nullable reference types, which references only `Microsoft.Extensions.Http` (for the registration with dependency injection). ```csharp services.AddPetStoreClient(options => { options.BaseUrl = new Uri("https://api.example.com/v1"); options.ApiKey = ApiCredential.Static(configuration["PetStore:ApiKey"]!); }); // inject PetStoreClient, or the client of a group, e.g. IPetsClient var pets = await client.Pets.ListPetsAsync(limit: 10, cancellationToken: token); try { var pet = await client.Pets.GetPetAsync(42); } catch (ApiException error) when (error.StatusCode == 404) { Console.WriteLine(error.Problem?.Detail); } ``` Without dependency injection, `PetStoreClient.CreateHttpClient(options)` makes an `HttpClient` that sends the credentials: `new PetStoreClient(PetStoreClient.CreateHttpClient(options), options)`. The generated code has: - **Clients.** One interface and one client per group of operations (`IPetsClient`, `PetsClient`), grouped by their first tag (see `grouping`), and `PetStoreClient` with a property per group. The clients take the `HttpClient` in their constructor, so `IHttpClientFactory` manages them; `AddPetStoreClient()` registers them as typed clients, with the options and the handlers sending the credentials. Operations marked `x-internal` are left out. - **Methods.** An `async` method per operation, named after its operation ID (`ListPetsAsync`, see `action_naming`): path parameters, the other required parameters and the body first, optional parameters (`null` leaves them out) and a `CancellationToken` last. Parameters are sent in their style (form, spaceDelimited, pipeDelimited, deepObject, simple, label, matrix), objects included; parameters described by `content` are sent as JSON. Deprecated operations are `[Obsolete]`. - **Bodies.** JSON (with the media type of the document, e.g. `application/merge-patch+json`), text, binary (`Stream`), JSON Lines uploads (`IAsyncEnumerable`), and forms and multipart bodies as `*Form` classes, whose files are `FilePart`s (`format: binary` or `contentMediaType`) sent with the content type of their `encoding`. - **Responses.** The method returns the body of the success response (`Task`), nothing (`Task`), or, when statuses have different bodies, a `*Result` with the status, the headers and a property per status. Responses with a status of 400 or more throw an `ApiException` with the status, the headers, the body as text and its RFC 9457 problem details (`Problem`). `text/event-stream` responses are `IAsyncEnumerable>`, typed by the `contentSchema` of the events' data (OpenAPI 3.2 `itemSchema`, or `x-itemSchema`), JSON Lines responses `IAsyncEnumerable`, both read as they arrive; binary responses are `BinaryResponse`s to dispose. Each other media type of a body or a response is a method of its own, named after it: `UpdatePetJsonPatchAsync`, `GetLabelPdfAsync`, `GetEventsEventStreamAsync`. - **Servers.** Requests go to `BaseUrl`, else to the base address of the `HttpClient`, else to the first server of the operation, of its path or of the API, with `ServerVariables` for their variables. - **Security.** A credential per security scheme in the options (`ApiCredential.Static`, `ApiCredential.FromProvider` for tokens that change, `ApiCredential.Basic`). A request carries the credentials of the first alternative of its operation's security requirements whose credentials are set; the `AuthorizationHandler` (HTTP schemes such as bearer and basic, OAuth 2.0, OpenID Connect) and `ApiKeyHandler` (headers, query parameters, cookies) delegating handlers add them. - **Models.** Records and enums, as `apigenie-generator server aspnet` writes them: `required`/`init` properties, DataAnnotations for constraints (exclusive bounds of OpenAPI 3.0 and 3.1 alike), nullable types for `nullable`, `null` types and `oneOf` with `null`. `allOf` is merged into one record, or is inheritance (`[JsonPolymorphic]`/`[JsonDerivedType]`) when the base has a discriminator. `oneOf` and `anyOf` with a discriminator are abstract records whose variants are told apart by the discriminator value (explicit mapping, or the `const` of the discriminator property, or the schema name), read and written by generated converters wherever the discriminator is in the object; values with an unknown discriminator are read as the default mapping (`defaultMapping`, or `x-defaultMapping`). Unions whose members have one C# type (e.g. strings of different formats) have that type. - **JSON.** A `System.Text.Json` source-generated `JsonSerializerContext` (`PetStoreJsonContext`) with every model and body type, so the clients read and write JSON without reflection. Use it to read error bodies: `JsonSerializer.Deserialize(error.Content!, PetStoreJsonContext.Default.Error)`. The project also has `README.md`, `Infrastructure/` (the runtime of the clients) and `Serialization/` (the JSON context and converters). The code builds with `-warnaserror` and the .NET analyzers. Not supported: callbacks and webhooks, OAuth 2.0 flows (the caller supplies access tokens), mutual TLS (configure the client certificate on the HTTP handler), XML bodies (sent and read as text), `prefixItems`, `patternProperties`, `if`/`then`/`else`, `dependentRequired`, `dependentSchemas`, `unevaluatedProperties`, `multipleOf`, retries (add a resilience handler with `configureHttpClient`) and pagination. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.controller-name` | on an operation or its path item: the group (client) of the operation, whatever `grouping` says | | `x-apigenie.codegen.action-name` | name of the method of an operation, when `action_naming` lists `extension` | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out, unless `skip_disabled` is `false` | | `x-internal` | `true` on an operation leaves it out | | `x-enum-varnames`, `x-enum-names` | names of the members of an enum, in PascalCase | | `x-enum-descriptions`, `x-enumDescriptions` | documentation comments of the members of an enum | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | | `x-itemSchema` | `itemSchema` of a media type in OpenAPI 3.0 and 3.1 documents: the type of the items of a streamed response | The extensions can live in an overlay for this target instead of in the document, e.g. `csharp.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: C# names version: 1.0.0 actions: - target: $.components.schemas.PetStatus update: x-enum-varnames: [Available, OnHold, Sold] - target: $.paths['/pets'] update: x-apigenie: codegen: controller-name: Animals ``` ```sh apigenie-generator client csharp openapi.yaml --overlay csharp.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_csharp.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Root namespace of the generated code, e.g. `Acme.PetStore.Client`; also the name of the project. The API-wide types are named after its last segment (or the one before when it is `Client`, `Clients` or `Sdk`): `PetStoreClient`, `PetStoreClientOptions`. Defaults to the title of the API followed by `.Client`. ### --package-version Version of the project. Defaults to the version of the API when it's a valid .NET version, or `1.0.0`. ### -p, --property NAME=VALUE Sets an option (see below), e.g. `-p layout=sources`; can be repeated. Options given on the command line take precedence over the configuration file. OpenAPI documents only: using it with an AsyncAPI document is an error. ## Configuration The options are read from `commands.generate.client_csharp`. Its `asyncapi` section configures the generation from AsyncAPI documents (see [apigenie-generator client csharp (AsyncAPI)](/docs/reference/generator/client-csharp-asyncapi#configuration)). ```yaml version: '1.0' commands: generate: client_csharp: namespace: Acme.PetStore.Client target_framework: net9.0 grouping: tag dependency_injection: true ``` | Option | Values | Description | | ------ | ------ | ----------- | | `layout` | `project`, `sources` | `project` (default): a class library (.csproj, README.md and the sources); `sources`: only the generated sources, for an existing project | | `namespace` | namespace | Root namespace of the generated code (also `--package-name`); derived from the API title by default, e.g. `PetStore.Client` | | `target_framework` | `net8.0`, `net9.0`, `net10.0` | Target framework of the project (default: `net8.0`) | | `grouping` | `tag`, `url` | Grouping of operations into clients: by their first tag (default), or by the first segment of their paths | | `action_naming` | `id`, `extension`, `url` | Sources of method names, in order of preference (default: `id`, `url`) | | `dependency_injection` | `true`, `false` | Generate the registration of the clients with `IServiceCollection` and `IHttpClientFactory` (default: `true`; it references Microsoft.Extensions.Http) | | `skip_deprecated` | `true`, `false` | Don't generate deprecated operations (default: `false`) | | `skip_disabled` | `true`, `false` | Don't generate operations disabled with the `x-apigenie` extension (default: `true`) | | `model_suffix` | text | Suffix of model names, e.g. `Dto` | | `version` | version | Version of the project (also `--package-version`); defaults to the version of the API | Unknown options are errors. --- # client elixir Source: https://apigenie.pl/docs/reference/generator/client-elixir Generate an Elixir client on [Req](https://hexdocs.pm/req) from an OpenAPI document. From an AsyncAPI document, the command generates a WebSocket and Kafka client library instead; see [apigenie-generator client elixir (AsyncAPI)](/docs/reference/generator/client-elixir-asyncapi). ## Usage ```sh apigenie-generator client elixir [...OPTIONS] INPUT_FILE ``` ## Description The generator writes a Mix project (Elixir 1.18 or later) depending on Req 0.5 or later and Jason. Its code passes `mix format --check-formatted`, `mix compile --warnings-as-errors`, `mix credo --strict` and `mix dialyzer`. | File | Contents | | ---- | -------- | | `lib/.ex` | the root module, with `new/1` creating a client | | `lib//api/.ex` | a module per tag (`Default` for operations without tags), with a function per operation | | `lib//models/*.ex` | a module per schema: structs, enums, unions and aliases, with `new/1`, `new!/1` and `to_map/1` | | `lib//client.ex`, `api_error.ex` | the client struct and the error of operations | | `lib//metadata.ex` | the servers and the security schemes of the document | | `lib//runtime/*.ex`, `models/decode.ex`, `models/encode.ex` | the encoding of parameters and bodies, and the decoding of responses | | `mix.exs`, `.formatter.exs`, `.gitignore`, `README.md` | the project | ```elixir client = CarpetExpress.new( base_url: "https://api.example.com/v2", auth: [bearer_jwt: System.fetch_env!("API_TOKEN")], req_options: [receive_timeout: 10_000, retry: :transient] ) {:ok, shipment} = CarpetExpress.Api.Shipments.get_shipment(client, shipment_id: "shp_123") shipment = CarpetExpress.Api.Shipments.get_shipment!(client, shipment_id: "shp_123") ``` ### Functions Every operation is a function named after its `operationId` in snake_case, in the module of its first tag: - `op(client, params \\ [], opts \\ [])`, or `op(client, body, params \\ [], opts \\ [])` when the operation has a request body; - `params` is a keyword list of the path, query, header and cookie parameters, keyed by their names in snake_case (`shipment_id: "..."`, `if_match: ...`); missing required parameters and unknown ones give an error before anything is sent; - `opts` are the options of the call: `:content_type` (the media type of the body when the operation accepts several), `:headers` and `:req_options` (options of `Req.request/2`, e.g. `retry: false` or `into: File.stream!("label.pdf")`). A `!` variant of each function returns the body itself and raises the error. ### Responses and errors A response with a 2XX status gives `{:ok, body}`: the body decoded into its model (`new/1` of its module), a list of them, a string for `text/*` media types, the bytes for other media types, or `nil` without content. Any other status gives `{:error, %ApiError{reason: :status}}`, with the `status`, the `headers` and the decoded `body` (a problem document, the error's model). The other reasons are `:transport` (the request failed), `:decode` (the body doesn't match its schema), `:params` and `:auth`. ### Parameters and bodies Parameters are encoded in their styles: `simple`, `label` and `matrix` path parameters, `form`, `spaceDelimited`, `pipeDelimited` and `deepObject` query parameters, headers, cookies, and parameters described with `content` as JSON. Models, enums (atoms), dates and times are encoded with their schemas. | Media type | Body | | ---------- | ---- | | JSON (`application/json`, `*+json`) | a model struct or a plain map | | `application/x-www-form-urlencoded` | a model struct or a map, its fields in the styles of their `encoding` | | `multipart/form-data` | a map or a keyword list of the parts: files as `{content, filename: ..., content_type: ...}` or the content alone, lists of files, values and JSON parts | | `text/*` | a string | | anything else | iodata, or an enumerable of chunks | ### Servers The base URL is the first server of the document, or the `:base_url` option of `new/1`. Operations with servers of their own are sent there, unless `operation_servers: false`. ### Security `new/1` takes the credentials by security scheme in `:auth`, keyed by the scheme names in snake_case (`CarpetExpress.Metadata.security_schemes/0` lists them). A credential is the value (an API key, a token, `{user, password}` for HTTP basic) or a supplier called before each request (`fn -> {:ok, token} end`, or `{module, function, args}`), e.g. to fetch and refresh OAuth2 tokens. Each request uses the first security requirement of its operation whose schemes all have a credential: API keys in headers, the query or cookies, HTTP bearer and basic, OAuth2 and OpenID Connect tokens as bearer tokens. Mutual TLS is set up with `req_options: [connect_options: [transport_opts: [certfile: ..., keyfile: ...]]]`. ### Types The models are the modules of [the Elixir models](/docs/reference/generator/client-elixir#types): a struct per object (with `allOf` flattened), atoms for string enums, a module dispatching on the discriminator for `oneOf` and `anyOf`, `Date`, `DateTime`, `NaiveDateTime` and `Time` for dates and times, binaries for `format: byte` (decoded from base64) and `format: binary`, `additional_properties` maps, and `| nil` for nullable values. ### Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the module of a component schema, unless `module_overrides` names it | | `x-apigenie.codegen.action-name` | name of the function of an operation without an `operationId` (with the default `action_naming`, which lists `id` first) | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out | | `x-internal` | `true` on an operation or its path item leaves the operation out, unless `internal_operations` is set | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | The extensions can live in an overlay for this target instead of in the document, e.g. `elixir.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Elixir names version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-apigenie: codegen: name: Animal - target: $.paths['/admin'] update: x-internal: true ``` ```sh apigenie-generator client elixir openapi.yaml --overlay elixir.overlay.yaml -o out ``` ### Limitations - `text/event-stream` and JSON Lines responses are read whole, as a string and as bytes. - The models don't validate `minimum`, `maximum`, `pattern` and the other constraints, and ignore `prefixItems`, `patternProperties` and conditional schemas. - `readOnly` and `writeOnly` properties are fields of the same struct. - Callbacks, webhooks and links aren't generated. ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_elixir.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. Not for a `.proto` input. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Name of the Mix application, converted to snake_case (`carpet-express` gives `carpet_express` and the root module `CarpetExpress`). Defaults to the name of the output directory. ### --package-version Version of the Mix project; `0.1.0` by default. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p dev_deps=false`. Options given on the command line take precedence over the configuration file. Lists are comma-separated (`-p include_tags=pets,store`) and mappings too (`-p module_overrides=pet-owner=Owner`). ## Configuration ```yaml version: '1.0' commands: generate: client_elixir: app: carpet_express namespace: CarpetExpress exclude_tags: [internal] ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `app` | string | name of the output directory | Name of the Mix application (snake_case, the directory under `lib/`); also set by `--package-name`. | | `namespace` | string | the application in PascalCase | Root module of the generated modules, e.g. `CarpetExpress`. | | `include_tags` | list of strings | | Generates only the operations with one of these tags. | | `exclude_tags` | list of strings | | Skips the operations with one of these tags. | | `skip_prune` | boolean | `false` | Keeps the component schemas that no generated operation uses. | | `internal_operations` | boolean | `false` | Also generates the operations marked `x-internal: true`. | | `dev_deps` | boolean | `true` | Lists credo and dialyxir as dependencies of `mix.exs` (`only: :dev`). | | `action_naming` | list of `id`, `extension`, `url` | `id, extension, url` | Where function names come from, in order of preference. | | `field_prefix` | string | `field_` | Prefix of struct fields whose property names give no valid name. | | `module_overrides` | map of strings | | Module names of component schemas, e.g. `pet-owner: Owner`. | --- # client fetch Source: https://apigenie.pl/docs/reference/generator/client-fetch Generate a TypeScript client for the Fetch API from an OpenAPI document. ## Usage ```sh apigenie-generator client fetch [...OPTIONS] INPUT_FILE ``` ## Description The generator writes TypeScript sources with no runtime dependencies: requests go through the global `fetch()` (or one you pass), so the client runs in browsers and in Node.js 18 or later. The input is an OpenAPI 3.0, 3.1 or 3.2 document. | File | Contents | | ---- | -------- | | `src/types.ts` | a type per component schema, the `...Params` interface of each operation, and the named types of inline bodies | | `src/operations.ts` | an `async` function per operation | | `src/metadata.ts` | `DEFAULT_BASE_URL`, the `AuthCredentials` interface and `SECURITY_SCHEMES` | | `src/runtime.ts` | `configure()`, `ApiError`, parameter serialization, authentication, body encoding | | `src/index.ts` | exports the types, the operations and the public part of the runtime | | `package.json`, `tsconfig.json` | an ES module package built with `tsc` into `dist/` (`layout: package` only) | With `layout: sources`, the five files are written directly to the output directory, to be compiled as part of your own project. A client of a shipping API, generated with `layout: sources` into `api/`: ```ts import { configure, ApiError, listShipments, getShipment, downloadLabelFile } from "./api/index.js"; configure({ baseUrl: "https://eu.api.carpetexpress.example/v2", auth: { apiKey: "ce_live_123", // sent in the X-Api-Key header oauth2: () => fetchAccessToken(), // called before every request that needs it }, }); try { const page = await listShipments({ status: ["in_transit", "delivered"], limit: 20 }); const shipment = await getShipment( { shipmentId: "shp_01J9Z", expand: ["labels"] }, { signal: AbortSignal.timeout(5000), headers: { "X-Request-Id": "req-1" } }, ); const label: Blob = await downloadLabelFile({ labelId: "lbl_1", extension: "pdf" }); } catch (error) { if (error instanceof ApiError) { console.error(error.status, error.body); // status 0: no response was received } } ``` ### Configuration at run time `configure(settings)` sets the client-wide settings; settings it doesn't give keep their values. Every operation function also takes an optional last argument, `RequestOptions`, with the same settings for that call only, and an `AbortSignal`. | Setting | Meaning | | ------- | ------- | | `baseUrl` | Base URL of the API. Defaults to `DEFAULT_BASE_URL`: the URL of the document's first server when it is absolute and has no variables, otherwise empty, and a call without a base URL throws an `ApiError`. | | `fetch` | The `fetch()` implementation; the global one by default. | | `headers` | Headers sent with every request. | | `auth` | Credentials, by security scheme name (`AuthCredentials`). | | `signal` | `RequestOptions` only: aborts the request. | ### Operations Every operation is an exported `async` function named after its `operationId` in camel case (`list_shipments` becomes `listShipments`), or after its method and path when it has none (`GET /pets/{id}` becomes `getPetsId`); see `action_naming`. The summary and the description become its JSDoc comment, and deprecated operations are marked `@deprecated`. ```ts export async function getShipment(params: GetShipmentParams, options?: RequestOptions): Promise export async function listShipments(params: ListShipmentsParams = {}, options?: RequestOptions): Promise export async function getAccount(options?: RequestOptions): Promise ``` - **Parameters** of all locations (path, query, header, cookie) are properties of one `...Params` object, under their names in the document (`params["If-Match"]`). A name used in two locations gets the location as a prefix for the second one (`queryId`). `Accept`, `Content-Type` and `Authorization` header parameters are left out. When no parameter is required, the object defaults to `{}`. - **Serialization** follows each parameter's `style` and `explode`: `simple`, `label` and `matrix` path parameters; `form`, `spaceDelimited`, `pipeDelimited` and `deepObject` query parameters. Values that are `undefined` or `null` are not sent; `Date` values are sent in ISO 8601 form. - **The request body** is the `body` property of the params (`requestBody` when a parameter is already named `body`). An operation with several request media types gets one: JSON first, then `application/x-www-form-urlencoded`, `multipart/form-data`, `text/*`, and other types. JSON bodies are typed by their schema and sent with `JSON.stringify`; form bodies are objects (or `URLSearchParams`), multipart bodies `FormData`, text bodies `string`, and other media types `Blob`. - **The result** is the body of the success response (`2XX`, or `default` when no success response is documented): its schema's type for JSON, `string` for `text/*`, `Blob` for other media types. Several success responses give a union (`Promise`), and a response without content adds `undefined`; an operation whose responses have no content returns `Promise`. The `Accept` header is set from the media type of the response. - **Security** is the operation's requirements, or the document's when the operation declares none. Before the request is sent, the first alternative whose credentials are all configured is applied; when none is, the request goes out without credentials. ### Types Component schemas become exported interfaces (plain objects) or type aliases, named in Pascal case (`user_profile` becomes `UserProfile`, `x-apigenie.codegen.name` overrides the name): | Schema | TypeScript | | ------ | ---------- | | object with properties | `interface`; optional properties are marked `?`, deprecated ones `@deprecated` | | `additionalProperties` | `Record`, or an index signature next to properties | | `enum`, `const` | union of literals (`"available" \| "sold"`, `1 \| 2 \| 3`) | | `oneOf`, `anyOf` | union (`Circle \| Square`) | | `allOf` | intersection (`Shape & { name?: string }`) | | `prefixItems` | tuple (`[number, number]`) | | `type: [T, "null"]`, OpenAPI 3.0 `nullable: true` | `T \| null` | | `format: binary` string | `Blob` | | other strings, `date-time` included | `string` | | `integer`, `number` | `number` | | `{}` | `unknown` | Recursive schemas refer to themselves (`children?: Tree[]`). The client doesn't validate values against their schemas: constraints such as `minimum`, `pattern` or `if`/`then`/`else` generate no code. ### Authentication `AuthCredentials` has an optional property per security scheme of the document. A credential is a string, or a function returning a string or a promise of one, called before every request that needs it (to refresh tokens). | Scheme | Sent as | | ------ | ------- | | `apiKey` | the header, query parameter or cookie the scheme names | | `http` with `scheme: basic` | `Authorization: Basic ...`, the credential being `user:password` | | `http` with `scheme: bearer` | `Authorization: Bearer ` | | other `http` schemes | `Authorization: ` | | `oauth2`, `openIdConnect` | `Authorization: Bearer ` | The client implements no OAuth 2.0 flow: it sends the access token you supply. ### Errors Every failure is an `ApiError` (a subclass of `Error`) with: | Property | Value | | -------- | ----- | | `status` | the HTTP status, or `0` when no response was received | | `body` | the response body: parsed JSON for JSON media types, otherwise text, `undefined` when empty | | `response` | the `Response`, when there was one | | `cause` | the underlying error, e.g. a network error or a `SyntaxError` of invalid JSON | A response with a status outside 200-299 throws, as do a network failure, a missing base URL or path parameter, and a success response whose JSON can't be parsed. ### Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the type of a component schema | | `x-apigenie.codegen.action-name` | name of the function of an operation, when `action_naming` lists `extension` | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | The extensions can live in an overlay for this target instead of in the document, e.g. `fetch.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: TypeScript names version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-apigenie: codegen: name: Animal ``` ```sh apigenie-generator client fetch openapi.yaml --overlay fetch.overlay.yaml -o out ``` ### Limitations From the [codegen conformance suite](/docs/reference/generator): - Operation-level and path-level `servers` are ignored: every request goes to the base URL. - `x-internal` is not honoured: such operations are part of the client. - A parameter described with `content` instead of `schema` is serialized with the `form` style, not as JSON. - One request media type is generated per operation, so an operation accepting both `application/merge-patch+json` and `application/json-patch+json` can send only the first. - Streaming responses (`text/event-stream`, `application/jsonl`) are read as one JSON value; `itemSchema` is not used. - Form-encoded bodies don't apply the `encoding` styles of their properties: an object property is sent as `[object Object]`. - Callbacks, webhooks and links aren't generated: they describe requests the server makes, or documentation. - Swagger 2.0 documents are not read; convert them with [`apigenie convert`](/docs/reference/cli/convert). Documents referencing other files must be bundled first with [`apigenie bundle`](/docs/reference/cli/bundle). ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_fetch.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Required. Directory where the client is generated. ### --package-name Name of the npm package in `package.json`. Defaults to the document's `info.title` in lower case with hyphens between words (`Carpet Express Shipping API` becomes `carpet-express-shipping-api`), or `api-client` when the title has no letters or digits. ### --package-version Version of the npm package in `package.json`. Defaults to the document's `info.version`, or `0.0.0` when it is empty. ## Configuration ```yaml version: '1.0' commands: generate: client_fetch: layout: sources import_extension: '' action_naming: [id, extension, url] ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `layout` | `package` or `sources` | `package` | `package` writes an npm package (`package.json`, `tsconfig.json`, sources in `src/`); `sources` writes only the sources, directly in the output directory. | | `import_extension` | `.js` or `''` | `.js` | Extension of relative imports between the generated files: `.js` for Node.js ES modules (`NodeNext` resolution), `''` for bundlers. | | `action_naming` | `id`, `extension`, `url`, or a list of them | `[id, url]` | Where function names come from, in order of preference: the `operationId`, the `x-apigenie.codegen.action-name` extension, or the method and path. | --- # client go Source: https://apigenie.pl/docs/reference/generator/client-go Generate a Go client for the `net/http` package of Go 1.22 from an OpenAPI document. From an AsyncAPI document, the command generates a Go Kafka client package instead; see [apigenie-generator client go (AsyncAPI)](/docs/reference/generator/client-go-asyncapi). ## Usage ```sh apigenie-generator client go [...OPTIONS] INPUT_FILE ``` ## Description The generator writes a Go package whose code uses the standard library only: its `go.mod` has no requirements. It shares the types, validation and parameter codecs with [`apigenie-generator server go`](/docs/reference/generator/server-go), so a client and a server generated from the same document read and write values the same way. | File | Contents | | ---- | -------- | | `types.gen.go` | the types of the component schemas the operations use, and of their inline schemas, with `Validate()` methods | | `client.gen.go` | `DefaultBaseURL`, `Servers`, `SecuritySchemes`, `Operations`, `NewClient`, an option per security scheme, and per operation its parameters, bodies, response type and methods | | `runtime_*.gen.go` | the `Client`, requests and errors, parameter encoding, streams, multipart bodies, validation, `Nullable[T]`, `Date` | | `go.mod` | with `layout: module` | ```go client, err := api.NewClient( api.WithBaseURL("https://api.example.com/v1"), // DefaultBaseURL otherwise api.WithAPIKey(os.Getenv("API_KEY")), api.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), ) if err != nil { return err } res, err := client.GetPet(ctx, 42, nil) var apiErr *api.APIError switch { case errors.As(err, &apiErr): log.Printf("status %d: %v", apiErr.StatusCode, apiErr.Problem) // RFC 9457 problem details case err != nil: return err // transport, encoding or decoding error default: fmt.Println(res.JSON200.Name) } ``` ### Methods Every operation is a method of `Client`, named after its `operationId`, whose first argument is a `context.Context`, followed by the path parameters, a pointer to a struct with the query, header and cookie parameters (`nil` when none is set), the body, and request editors changing the request before it is sent: ```go func (c *Client) CreatePet(ctx context.Context, params *CreatePetParams, body NewPet, reqEditors ...RequestEditorFn) (*CreatePetResponse, error) func (c *Client) CreatePetWithBody(ctx context.Context, params *CreatePetParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreatePetResponse, error) ``` - An operation with several request media types gets a method per media type (`UpdatePetWithApplicationMergePatchJSONBody`; the bare name sends `application/json`), and always a `...WithBody` method taking the media type and an `io.Reader`. - JSON bodies are values of their types, validated with `Validate()` before they are sent (`validate_requests: false` turns that off); `text/*` bodies are strings, other media types `io.Reader` values, and `application/jsonl` bodies slices of the items of their `itemSchema`. - `application/x-www-form-urlencoded` bodies are encoded field by field with the styles of their `encoding` (`deepObject`, delimited arrays, JSON fields). - Parameters are serialized in their style and `explode` mode: `simple`, `label` and `matrix` path parameters, `form`, `spaceDelimited`, `pipeDelimited` and `deepObject` query parameters, `allowReserved`, headers and cookies. Parameters described with `content` are sent as JSON. - Methods set the `Accept` header to the media types of the documented responses. - Operations marked `x-internal: true` (on the operation or its path) are left out, unless `internal_operations` is set. ### Responses A method returns a response struct with one field per documented status and media type, set for the response received, next to the raw `*http.Response`: ```go type GetPetResponse struct { HTTPResponse *http.Response Body []byte // the body, when it was read Headers200 *GetPet200Headers // typed response headers JSON200 *Pet // 200, application/json ApplicationPdf200 io.ReadCloser // 200, application/pdf: left open ApplicationProblemJSON404 *ProblemDetails // 404, application/problem+json ApplicationProblemJSONDefault *ProblemDetails } ``` - The field names join the media type (`JSON` for `application/json`, `Text` for `text/plain`, `EventStream`, `JSONL`, the media type otherwise) and the status (`200`, `2XX`, `Default`). - `application/problem+json` contents are `*ProblemDetails` (type, title, status, detail, instance and the other members in `Extensions`), whatever their schema. - Binary contents and streams are left open: close the field, or the response with `Close()`. Other bodies are read, kept in `Body`, and closed. - For a status of 400 or more, the method returns the response and an `*APIError` with the status, the problem details of an `application/problem+json` body, the body and the response. `errors.As(err, &problem)` also finds the `*ProblemDetails`. - An undocumented status or media type sets no field: `Body` holds the body. - Response headers that can't be parsed are left unset; their text stays in `HTTPResponse.Header`. ### Streams `text/event-stream` and `application/jsonl` contents are iterators over their items, typed by the `itemSchema` of the media type (OpenAPI 3.2; `x-itemSchema` in 3.0 and 3.1 documents): ```go res, err := client.GetTrackingEvents(ctx, id, nil) if err != nil { return err } defer res.Close() if events := res.EventStream200; events != nil { for events.Next() { event := events.Item() // the itemSchema type, filled from id, event, data and retry fmt.Println(event.Event, event.Data) } if err := events.Err(); err != nil { return err } } ``` - `JSONLStream[T]` reads one JSON value per line. `EventStream[T]` follows the server-sent events parsing rules of the HTML standard (`data` lines joined, comments, `id`, `retry`); `Event()` returns the event as received and `LastEventID()` the ID to resume from with `Last-Event-ID`. - Without `itemSchema`, server-sent events are `ServerSentEvent` values and JSON Lines items `json.RawMessage` values. - Go 1.22 has no range-over-func iterators, hence `Next()`, `Item()` and `Err()`. ### Multipart bodies `multipart/form-data` bodies of object schemas are structs with a field per part: - Binary parts (`format: binary`, or `contentMediaType` in 3.1 and 3.2) are `File` values (a name, a media type, a reader and extra part headers), arrays of them `[]File`. A file part is sent with the `contentType` of its encoding, or its `contentMediaType`, unless `File.ContentType` is set. Headers the encoding declares are set through `File.Header`. - Object parts, and parts whose encoding has a JSON `contentType`, are sent as JSON; other parts as text, a part per item of arrays. The body is built in memory and sent with its boundary. ### Servers `DefaultBaseURL` is the first server of the document with an absolute URL, the default values of its variables applied; `WithBaseURL` sets another one, and is required when there is none. Operations with servers of their own (on the operation or its path) are sent there, a relative server URL being resolved against the base URL; `WithOperationServers(false)` sends every request to the base URL instead, e.g. to a test server or a proxy. ### Security Every security scheme of the document gets an option setting its credentials, named `With` followed by the name of the scheme: | Scheme (name in the document) | Option | | ----------------------------- | ------ | | `apiKey` in a header, the query or a cookie (`apiKey`) | `WithAPIKey(key string)` | | `http` `bearer`, `oauth2`, `openIdConnect` (`oauth2`) | `WithOauth2(token string)`, sent as a bearer token | | `http` `basic` (`basicAuth`) | `WithBasicAuth(username, password string)` | A request gets the credentials of the first alternative of its operation's security requirements whose schemes all have credentials, so an API key in the query is only sent to the operations that accept it; an operation with `security: []` gets none. `WithSecurity(scheme, editor)` sets the credentials of any scheme with a `RequestEditorFn`; obtaining and refreshing OAuth2 tokens is up to the caller. `mutualTLS` needs a client certificate in the transport of the `http.Client` (`WithHTTPClient`). ### Types - Optional properties are pointers with `omitempty`; slices, maps and `any` stay values. `optional_fields: omitzero` uses values with `omitzero` (Go 1.24). - Optional properties that can be null are `Nullable[T]`, which tells absent, null and set values apart; required ones are pointers. `nullable_type: pointer` uses pointers for both. - Enums are defined types with constants named after the type and the value (`PetStatusAvailable`). - `oneOf` and `anyOf` schemas are structs with one pointer per variant. With a discriminator, the value goes to the variant its property selects: through the `mapping`, the name of the schema or the value the schema gives the property (`const`); other values go to the `defaultMapping` variant (`x-defaultMapping` in 3.0 and 3.1 documents), or are kept in `Raw`. Without a discriminator, the value goes to the first variant it decodes into. - `allOf` schemas are merged into one struct; an `anyOf` or `oneOf` whose members only list required properties (`anyOf: [{required: [email]}, {required: [phone]}]`) is checked by `Validate()`. - `date-time` strings are `time.Time`, `date` strings `Date`, `byte` and `binary` strings `[]byte`. ### Extensions | Extension | Effect | | --------- | ------ | | `x-go-name` | name of the type of a schema, or of the field of a property | | `x-go-type` | Go type of a schema, e.g. `decimal.Decimal`; with `x-go-type-import: {path: ...}` for its package | | `x-go-type-skip-optional-pointer` | the optional property isn't a pointer | | `x-go-json-ignore` | the field gets the tag `json:"-"` | | `x-omitempty` | `true` or `false` overrides the `omitempty` of the field | | `x-enum-varnames`, `x-enumNames` | names of the enum constants | | `x-enum-descriptions` | comments of the enum constants | | `x-apigenie.codegen.name` | name of the type of a component schema, when it has no `x-go-name` | | `x-apigenie.codegen.action-name` | name of the method of an operation without an `operationId` (with the default `action_naming`, which lists `id` first) | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out | | `x-internal` | `true` on an operation or its path item leaves the operation out, unless `internal_operations` is set | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | | `x-itemSchema` | `itemSchema` of a media type in OpenAPI 3.0 and 3.1 documents: the type of the items of a streamed response | The extensions can live in an overlay for this target instead of in the document, e.g. `go.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Go types version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-go-name: Animal - target: $.components.schemas.Pet.properties.price update: x-go-type: decimal.Decimal x-go-type-import: path: github.com/shopspring/decimal ``` ```sh apigenie-generator client go openapi.yaml --overlay go.overlay.yaml -o out ``` ### Limitations - Callbacks, webhooks and links aren't generated: they describe requests the server makes or documentation. - `Validate()` doesn't check `prefixItems`, `patternProperties`, `if`/`then`/`else`, `dependentRequired`, `dependentSchemas` and `unevaluatedProperties`; patterns using lookaround or backreferences aren't checked either. - Multipart bodies are built in memory; there are no retries or rate limiting beyond what the `http.Client` does. - Exported names of the runtime (`Client`, `File`, `Server`, `Operation`, `ProblemDetails`, `Date`, `Nullable`, ...) are reserved: schemas with these names get numbered types, e.g. `File2`. - The input must be an OpenAPI 3.0, 3.1 or 3.2 document; convert Swagger 2.0 documents with `apigenie convert`, and bundle documents referencing other files with `apigenie bundle`. ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_go.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the Go package. Defaults to the name of the output directory (`api` when it isn't a valid package name). ### --package-version Ignored: Go packages have no version. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p layout=module`. Options given on the command line take precedence over the configuration file. Lists are comma-separated (`-p include_tags=pets,store`) and mappings too (`-p type_mappings=uuid=github.com/google/uuid.UUID`). ## Configuration ```yaml version: '1.0' commands: generate: client_go: package: petstore layout: module module: github.com/acme/petstore type_mappings: uuid: github.com/google/uuid.UUID ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `package` | string | name of the output directory | Name of the Go package; also set by `--package-name`. | | `layout` | `package` or `module` | `package` | `module` also writes `go.mod`, making the output a module of its own. | | `module` | string | `example.com/` | Module path written to `go.mod`. | | `optional_fields` | `pointer` or `omitzero` | `pointer` | Optional properties as pointers with `omitempty`, or as values with `omitzero` (Go 1.24). | | `nullable_type` | `tristate` or `pointer` | `tristate` | Optional nullable properties as `Nullable[T]`, or as pointers. | | `include_tags` | list of strings | | Generates only the operations with one of these tags. | | `exclude_tags` | list of strings | | Skips the operations with one of these tags. | | `skip_prune` | boolean | `false` | Keeps the component schemas that no generated operation uses. | | `internal_operations` | boolean | `false` | Also generates the operations marked `x-internal: true`. | | `validate_requests` | boolean | `true` | Validates request bodies against their schemas before sending them. | | `initialisms` | list of strings | | Words written in upper case in identifiers, besides `ID`, `URL`, `HTTP`, `JSON` and the other initialisms of golint. | | `type_mappings` | map of strings | | Go types of string formats or `x-go-type` names, e.g. `uuid: github.com/google/uuid.UUID`. | | `action_naming` | list of `id`, `extension`, `url` | `id, extension, url` | Where method names come from, in order of preference. | --- # client java Source: https://apigenie.pl/docs/reference/generator/client-java Generate a Java client of an OpenAPI document: a Maven project using Spring's `RestClient` (the default) or `RestTemplate`, Spring Cloud OpenFeign interfaces, or Spring HTTP interfaces. From an AsyncAPI document, the command generates a Java Kafka client library instead; see [apigenie-generator client java (AsyncAPI)](/docs/reference/generator/client-java-asyncapi). ## Usage ```sh apigenie-generator client java [...OPTIONS] INPUT_FILE ``` ## Description The input must be an OpenAPI 3.0, 3.1 or 3.2 document. The generated project needs Java 17 and Maven; its dependencies come from Maven Central. ```java ApiClient client = new ApiClient(); client.setBearerToken("token"); PetsApi pets = new PetsApi(client); List page = pets.listPets(10, null); ResponseEntity response = pets.getPetWithHttpInfo(42L); try (Stream events = pets.watchPetsStream()) { events.limit(5).forEach(System.out::println); } ``` `--library` selects how the client calls the API: | Library | Output | | ------- | ------ | | `restclient` (default) | `ApiClient` on Spring's `RestClient`, one class per API | | `resttemplate` | the same classes, with `ApiClient` on Spring's `RestTemplate` | | `feign` | Spring Cloud OpenFeign: `@FeignClient` interfaces extending Spring MVC interfaces, request interceptors of the security schemes | | `http-interface` | Spring HTTP interfaces (`@HttpExchange`) and a configurator creating their proxies with a `RestClient` (or a `WebClient` with `reactive`) | The `feign` and `http-interface` libraries are the clients the `spring-cloud` and `spring-http-interface` libraries of `apigenie-generator server spring` generate, with the options of the client. The rest of this section describes the `restclient` and `resttemplate` libraries. Their API classes are grouped by the first tag of operations (`Api`); each operation has: - `op(...)`, returning the body of the response (`void` without content); - `opWithHttpInfo(...)`, returning the `ResponseEntity`, with the status and the headers; - `opStream(...)` when the response can be streamed as `text/event-stream`, `application/jsonl`, `application/x-ndjson` or `application/json-seq`: a `Stream` of the items, typed by the `itemSchema` of the media type (`x-itemSchema` before 3.2) or the `contentSchema` of the `data` of events, read as they arrive. Closing the stream (try-with-resources) closes the connection. - `opWith(...)` for each further request media type taking another Java type, e.g. `updateShipmentWithJsonPatch(...)` for `application/json-patch+json`. Required parameters come first. Parameters are serialized with their style (form, spaceDelimited, pipeDelimited, deepObject, simple); parameters described with `content` are sent as JSON. Bodies are JSON, JSON Lines (a `List` of items), form fields, multipart parts (Spring `Resource`s for files, JSON parts for objects, the content type of the `encoding`), text or binary (`Resource`). Binary responses are `Resource`s. Methods other than the eight classic ones (QUERY, `additionalOperations`) are sent as they are; the JDK `HttpClient` the client uses allows them. `ApiClient` holds: - the servers of the document (`setServerIndex`, `setServerVariables`) or a base path (`setBasePath`); operations with servers of their own are sent to them, unless `setOperationBasePath(operationId, url)` says otherwise; - one `Authentication` per security scheme, added to the operations requiring it: `setBearerToken` (HTTP bearer, OAuth 2 and OpenID Connect schemes, whose tokens are obtained elsewhere), `setApiKey` (headers, query parameters and cookies), `setUsername` and `setPassword` (HTTP basic); - the `ObjectMapper` (`createObjectMapper()`: Java time, `JsonNullable`, absent properties left out, unknown properties ignored), default headers and cookies. Responses with an error status raise an `ApiException` with the status, the headers and the body; `application/problem+json` responses raise a `ProblemDetailsException` with the RFC 9457 problem details, typed as the problem schema of the document. Models are the Jackson classes of `apigenie-generator server spring`, without Swagger annotations: - classes for objects, with the properties of `allOf` members (the properties of `anyOf` and `oneOf` alternatives are optional) - interfaces for `oneOf`: with a discriminator, `@JsonTypeInfo` and `@JsonSubTypes`, its `defaultMapping` (`x-defaultMapping` before 3.2) as `defaultImpl` and the `const` values of the members as the names of implicit mappings; without one, the members are told apart by a property with constant values (`useImplicitOneOfDiscriminator`) or by their properties (`useDeductionForOneOfInterfaces`) - enums, `JsonNullable` for nullable properties, Bean Validation annotations, `java.time` types - `readOnly` properties only read from responses and `writeOnly` ones only sent (`jsonAccessForReadWriteOnly`) `x-internal` operations and paths are left out. The project contains `pom.xml` (Spring Framework, Jackson and Jakarta versions from the Spring Boot BOM, without Spring Boot), `README.md`, `.openapi-generator-ignore` and, in the base package, `ApiClient`, `ApiException`, `ProblemDetailsException` (with a problem schema), `ServerConfiguration`, `ServerVariable`, `StreamDecoder` (with streaming responses), `RFC3339DateFormat`, `auth/` (`Authentication`, `HttpBearerAuth`, `HttpBasicAuth`, `ApiKeyAuth`), `api/` and `model/`. Not supported: callbacks and webhooks, OAuth 2 flows (tokens are set by hand), mutual TLS (set an `SSLContext` on the request factory of the `RestClient.Builder` or `RestTemplate` given to `ApiClient`), `prefixItems` (tuples are `List`), `patternProperties`, and constraints Bean Validation can't express (`multipleOf`, conditional schemas). The `feign` library leaves out operations of methods other than the classic ones. ## Extensions | Extension | Effect | | --------- | ------ | | `x-class-extra-annotation` | annotations of the class of a schema, e.g. `['@Entity']` | | `x-implements` | interfaces the class of a schema implements | | `x-field-extra-annotation` | annotations of the field of a property, or of the method parameter of a parameter (`feign`, `http-interface`) | | `x-extra-annotation` | annotations of the getter of a property | | `x-setter-extra-annotation` | annotations of the setter of a property | | `x-pattern-message`, `x-size-message`, `x-minimum-message`, `x-maximum-message` | `message` of the `@Pattern`, `@Size`, `@Min`/`@DecimalMin` and `@Max`/`@DecimalMax` annotations of a property | | `x-jackson-json-include-policy` | `@JsonInclude` policy of a property, e.g. `non_empty` (`none` for no annotation) | | `x-jackson-json-setter-nulls` | `@JsonSetter(nulls = ...)` of a property, e.g. `skip` (`none` for no annotation) | | `x-enum-varnames` | names of the constants of an enum | | `x-enum-descriptions` | Javadoc of the constants of an enum | | `x-parent` | `true` makes a schema the superclass of the schemas extending it with `allOf`, without a discriminator | | `x-discriminator-value` | the discriminator value of a subclass in `@JsonSubTypes` (default: the name of its schema) | | `x-jackson-default-impl` | `defaultImpl` of the `@JsonTypeInfo` of a class with a discriminator | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the `defaultImpl` of `@JsonTypeInfo`, without `x-jackson-default-impl` | | `x-itemSchema` | `itemSchema` of a media type in OpenAPI 3.0 and 3.1 documents: the type of the items of a streamed response | | `x-codegen-request-body-name` | on an operation or its request body: the name of the body parameter | | `x-apigenie.codegen.controller-name` | on an operation: the API class of the operation, instead of its tag or path | | `x-apigenie.codegen.action-name` | name of the method of an operation, instead of its `operationId` | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out, with `skipDisabled` | | `x-internal` | `true` on an operation or its path item leaves the operation out | | `x-operation-extra-annotation` | annotations of the method of an operation (`feign`) | | `x-version-param` | `true` on a header or query parameter adds it to the `headers` or `params` of the `@RequestMapping` (`feign`) | The extensions can live in an overlay for this target instead of in the document, e.g. `java.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Java annotations version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-class-extra-annotation: ['@com.acme.Audited'] x-implements: [java.io.Serializable] - target: $.components.schemas.Pet.properties.name update: x-pattern-message: Lower-case letters only ``` ```sh apigenie-generator client java openapi.yaml --overlay java.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_java.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Base package of the generated code, e.g. `com.acme.pets`; the `api`, `model` and `configuration` packages are derived from it unless set explicitly. Defaults to `org.openapitools.client`. ### --package-version Version of the Maven artifact. Defaults to the version of the API. ### --library `restclient` (default), `resttemplate`, `feign` or `http-interface`. Overrides the `library` of the configuration file. ### -p, --property An option of the "Configuration" section as `name=value`, e.g. `-p useBeanValidation=false`. Can be repeated; overrides the configuration file. ## Configuration ```yaml version: '1.0' commands: generate: client_java: library: restclient basePackage: com.acme.pets groupId: com.acme artifactId: pets-client useBeanValidation: true openApiNullable: true typeMappings: DateTime: java.time.Instant ``` The main options: | Option | Description | | ------ | ----------- | | `library` | `restclient` (default), `resttemplate`, `feign` or `http-interface` | | `basePackage`, `apiPackage`, `modelPackage` | Packages of the code (`org.openapitools.client`, `.api`, `.model` by default) | | `groupId`, `artifactId`, `artifactVersion` | Coordinates of the Maven artifact (`org.openapitools`, `openapi-java-client`, the version of the API) | | `useSpringBoot4` | The versions of Spring Boot 4 (Spring Framework 7) instead of Spring Boot 3.5 | | `useBeanValidation` | Bean Validation annotations on models (`true` by default) | | `openApiNullable` | `JsonNullable` for nullable properties (`true` by default) | | `useTags` | Group operations by tag (`true` by default) or by the first segment of their path | | `generateStreamMethods` | `Stream` methods for streaming responses (`true` by default) | | `allHttpMethods` | Operations of every HTTP method (`true` by default; `false` with `feign`) | | `contentMediaTypeAsBinary` | Strings with a non-JSON `contentMediaType` are binary `Resource`s (`true` by default) | | `useImplicitOneOfDiscriminator` | Dispatch `oneOf` schemas on a property with constant values (`true` by default) | | `jsonAccessForReadWriteOnly` | `@JsonProperty(access)` for `readOnly` and `writeOnly` properties (`true` by default) | | `typeMappings`, `importMappings`, `schemaMappings` | Java types of OpenAPI types and schemas | All options: | Option | Description | | ------ | ----------- | | `basePackage` | Base package for generated code | | `invokerPackage` | Alias of basePackage | | `apiPackage` | Package for generated API classes | | `modelPackage` | Package for generated models | | `configPackage` | Package for generated configuration classes | | `groupId` | groupId in the generated pom.xml | | `artifactId` | artifactId in the generated pom.xml | | `artifactVersion` | Artifact version in the generated pom.xml | | `artifactDescription` | Artifact description in the generated pom.xml | | `artifactUrl` | Artifact URL in the generated pom.xml | | `licenseName` | License name in the generated pom.xml | | `licenseUrl` | License URL in the generated pom.xml | | `developerName` | Developer name in the generated pom.xml | | `developerEmail` | Developer email in the generated pom.xml | | `developerOrganization` | Developer organization in the generated pom.xml | | `developerOrganizationUrl` | Developer organization URL in the generated pom.xml | | `scmConnection` | SCM connection in the generated pom.xml | | `scmDeveloperConnection` | SCM developer connection in the generated pom.xml | | `scmUrl` | SCM URL in the generated pom.xml | | `parentGroupId` | groupId of the parent pom | | `parentArtifactId` | artifactId of the parent pom | | `parentVersion` | Version of the parent pom | | `snapshotVersion` | Append -SNAPSHOT to the artifact version | | `title` | Server title, used in property names (defaults to info.title) | | `useSpringBoot3` | Generate code for Spring Boot 3 (jakarta); false means Spring Boot 2 | | `useSpringBoot4` | Generate code for Spring Boot 4 | | `useJackson3` | Use Jackson 3 (tools.jackson) instead of Jackson 2; requires useSpringBoot4 | | `optionalAcceptNullable` | Use Optional.ofNullable in fluent setters of Optional properties | | `useJspecify` | Use JSpecify (org.jspecify.annotations.Nullable) for null checks | | `hideGenerationTimestamp` | Omit the generation date from @Generated annotations | | `generationTimestamp` | Timestamp used in @Generated annotations | | `generatorVersion` | Generator version written into generated files | | `dateLibrary` | Date library: java8, java8-localdatetime, joda or legacy | | `documentationProvider` | springdoc, source or none | | `annotationLibrary` | swagger2 or none | | `interfaceOnly` | Generate only API interfaces, without controllers | | `skipDefaultInterface` | Don't generate default implementations of API methods | | `useTags` | Group operations by tag instead of the first path segment | | `requestMappingMode` | Where to put the class-level @RequestMapping: controller, api_interface or none | | `useResponseEntity` | Wrap return types in ResponseEntity | | `useOptional` | Use Optional for optional parameters | | `returnSuccessCode` | Return the success code instead of 501 Not Implemented | | `unhandledException` | Declare 'throws Exception' on API methods | | `async` | Return CompletableFuture from API methods | | `reactive` | Generate reactive (WebFlux) code | | `responseWrapper` | Wrapper of return types: CompletableFuture, Callable, Future, ListenableFuture, DeferredResult or a fully qualified class name | | `includeHttpRequestContext` | Add the HTTP request (ServerWebExchange / HttpServletRequest) to API methods | | `singleContentTypes` | Use a single content type per operation | | `sortParamsByRequiredFlag` | Put required parameters before optional ones | | `apiNameSuffix` | Suffix of API class names | | `skipDeprecated` | Don't generate code for deprecated operations | | `skipDisabled` | Don't generate code for operations disabled with x-apigenie | | `implicitHeaders` | Describe header parameters with @Parameters instead of method parameters | | `implicitHeadersRegex` | Regular expression of header parameters described with @Parameters | | `useHttpServiceProxyFactoryInterfacesConfigurator` | Use a HttpServiceProxyFactory in HttpInterfacesAbstractConfigurator (spring-http-interface) | | `clientRegistrationId` | @ClientRegistrationId of API interfaces (spring-http-interface, Spring Boot 4) | | `useFeignClientUrl` | Use the url attribute of @FeignClient (spring-cloud) | | `useFeignClientContextId` | Use the contextId attribute of @FeignClient (spring-cloud) | | `useBeanValidation` | Use Bean Validation annotations | | `performBeanValidation` | Use Hibernate Validator annotations | | `useSpringBuiltInValidation` | Rely on Spring's built-in validation (no @Validated) | | `additionalNotNullAnnotations` | Add @NotNull to required path and body parameters | | `openApiNullable` | Use JsonNullable for nullable properties | | `serializableModel` | Make models implement Serializable | | `useOneOfInterfaces` | Represent oneOf schemas as interfaces | | `useDeductionForOneOfInterfaces` | Deduce oneOf subtypes from their properties | | `generatedConstructorWithRequiredArgs` | Generate constructors with required arguments | | `generateConstructorWithAllArgs` | Generate constructors with all arguments | | `generateBuilders` | Generate builders for models | | `sortModelPropertiesByRequiredFlag` | Put required properties first in constructors | | `enumPropertyNaming` | Naming of enum constants: MACRO_CASE, original, legacy, camelCase, PascalCase, snake_case or UPPERCASE | | `useEnumCaseInsensitive` | Match enum values ignoring case | | `withXml` | Add XML annotations to models | | `useSealed` | Use sealed classes and interfaces (Java 17+) | | `containerDefaultToNull` | Initialize containers with null instead of empty ones | | `legacyDiscriminatorBehavior` | Use the legacy discriminator behavior | | `disableDiscriminatorJsonIgnoreProperties` | Don't ignore discriminator properties during serialization | | `modelNamePrefix` | Prefix of model names | | `camelCaseDollarSign` | Keep the case of the first letter after a leading $ in names | | `useEnumValueInterface` | Make enums implement the ValuedEnum interface | | `generateJsonIncludeAnnotations` | Add @JsonInclude annotations to model properties | | `optionalNonNullPropertyJsonInclude` | @JsonInclude policy of optional, non-nullable properties: NON_NULL, NON_EMPTY, NON_DEFAULT or NONE | | `generateJsonSetterNullsAnnotations` | Add @JsonSetter(nulls = ...) annotations to optional, non-nullable properties | | `optionalNonNullPropertyJsonSetterNulls` | @JsonSetter(nulls = ...) mode of optional, non-nullable properties: SKIP or FAIL | | `modelNameSuffix` | Suffix of model names | | `additionalModelTypeAnnotations` | Annotations added to model classes, separated by semicolons | | `additionalEnumTypeAnnotations` | Annotations added to enum classes, separated by semicolons | | `additionalOneOfTypeAnnotations` | Annotations added to oneOf interfaces, separated by semicolons | | `typeInfoDefaultImpls` | Classes used as defaultImpl of @JsonTypeInfo, by schema name (Schema=Class,...) | | `typeMappings` | Mappings of OpenAPI types to Java types (type=JavaType,...) | | `importMappings` | Mappings of Java types to imports (Type=package.Type,...) | | `schemaMappings` | Mappings of schemas to existing classes (Schema=package.Class,...) | | `modelNameMappings` | Mappings of schema names to model names (Schema=Model,...) | | `nameMappings` | Mappings of property names to Java names (name=javaName,...) | | `parameterNameMappings` | Mappings of parameter names to Java names (name=javaName,...) | | `enumNameMappings` | Mappings of enum values to constant names (value=NAME,...) | | `contentMediaTypeAsBinary` | Map strings with a non-JSON contentMediaType to binary types, like format: binary | | `useImplicitOneOfDiscriminator` | Dispatch oneOf schemas without a discriminator on a property whose value is constant in every member | | `jsonAccessForReadWriteOnly` | Read readOnly properties from JSON only and write writeOnly ones only (@JsonProperty access) | | `library` | Client library: restclient (Spring RestClient, the default), resttemplate, feign (Spring Cloud OpenFeign) or http-interface (Spring HTTP interfaces) | | `allHttpMethods` | Generate operations of every HTTP method (QUERY, COPY, ...), not only the eight classic ones | | `generateStreamMethods` | Generate `Stream` methods for text/event-stream and application/jsonl responses | Unknown options are errors. --- # client python Source: https://apigenie.pl/docs/reference/generator/client-python Generate a Python client of an OpenAPI document, built on [httpx](https://www.python-httpx.org) and [Pydantic](https://docs.pydantic.dev) 2. From an AsyncAPI document, the command generates an asyncio Kafka client package instead; see [apigenie-generator client python (AsyncAPI)](/docs/reference/generator/client-python-asyncapi). ## Usage ```sh apigenie-generator client python [...OPTIONS] INPUT_FILE ``` ## Description The input must be an OpenAPI 3.0, 3.1 or 3.2 document. The generated package needs Python 3.10 or later, `pydantic>=2.11` and `httpx` (or `httpx2`, see `http_library`). ```python from pet_store_client import Client, NotFoundError, models with Client(api_key="...") as client: pets = client.pets.list_pets(limit=10) pet = client.pets.create_pet(models.PetInput(name="Rex")) try: client.pets.get_pet(42) except NotFoundError as error: print(error.body) ``` `AsyncClient` has the same methods, as coroutines: ```python async with AsyncClient(api_key="...") as client: pets = await client.pets.list_pets() ``` The clients have: - one resource per group of operations, e.g. `client.pets`, with one method per operation. Operations are grouped by their first tag (see `group_by`); the methods of operations without a group belong to the client. Method names come from operation IDs, or from the method and the path (see `action_naming`). - arguments for the server (`base_url`, or `server` and `server_variables`), one argument per security scheme (API keys, HTTP schemes, bearer tokens, OAuth 2.0 tokens or an `OAuth2ClientCredentials`, which requests and refreshes tokens), `auth` (any `httpx.Auth`), `timeout`, `max_retries`, `headers`, `http_client` and `follow_redirects`. Each method takes the operation's required parameters as positional arguments, optional ones as keyword arguments (left out unless given; `UNSET` by default), the request body, and `request_options`, which override the timeout and retries and add headers and query parameters. Parameters are serialized with their style (form, spaceDelimited, pipeDelimited, deepObject, simple, label, matrix). Bodies are JSON, form, multipart (with `File` objects or bytes), text or binary. Requests use the credentials of the first alternative of the operation's security requirements for which the client has credentials. Methods return the parsed body of a successful response, chosen by status code (`200`, `2XX`, `default`): a model, a list, text, bytes, or `None`. Error statuses raise an `ApiStatusError` subclass (`BadRequestError`, `AuthenticationError`, `NotFoundError`, `RateLimitError`, `InternalServerError`, ...) whose `body` is parsed with the documented schema. Connection errors, timeouts, and 408, 409, 429 and 5xx responses are retried with exponential backoff, honoring `Retry-After`; timeouts and server errors only for idempotent methods. `client.pets.with_raw_response` returns responses with their status and headers, `client.pets.with_streaming_response` returns them before reading their bodies (e.g. to download a file in chunks). Operations answering with server-sent events (`text/event-stream`) return a `Stream` of events. Models are Pydantic classes in the `models` module. Fields have Python names (`pet_id` for `petId`) and are serialized with the names of the API. Requests send the fields that were set: a field left out is omitted, one set to `None` is sent as `null`. Unknown properties of responses are kept in `model_extra`. Schemas become: - classes for objects, with `allOf` flattened into one class - `Enum` classes for string and integer enums of component schemas, `Literal` types otherwise - tagged unions for `oneOf` and `anyOf` with a discriminator, unions otherwise - `PetInput` variants for requests when a schema has `readOnly` or `writeOnly` properties - `datetime.date`, `datetime.datetime`, `uuid.UUID`, `Decimal` and `Base64Bytes` for formats, `Annotated` constraints for `minimum`, `maxLength` and the like, and `pattern` checked with Python's `re` by `matching()` of `_models.py` (a pattern `re` can't compile, such as one using `\p{L}`, isn't checked) The generated package contains `pyproject.toml`, `README.md` and the package: `client.py`, `models.py`, `resources/` (one module per group), `_metadata.py` (servers and security schemes), the runtime (`_base_client.py`, `_types.py`, `_auth.py`, `_exceptions.py`, `_serialization.py`, `_response.py`, `_sse.py`, `_models.py`), `_http.py` and `py.typed`. The code passes `mypy --strict`. Not supported: callbacks and webhooks, XML bodies, `x-internal` (such operations are part of the client), parameters described with `content` (they are serialized with the form style), mutual TLS (configure it on an `http_client`), and pagination. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the model class of a component schema | | `x-enum-varnames`, `x-enumNames` | names of the members of an enum | | `x-apigenie.codegen.controller-name` | on an operation or its path item: the resource of the operation, when `group_by` lists `extension` | | `x-apigenie.codegen.action-name` | name of the method of an operation, when `action_naming` lists `extension` | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | The extensions can live in an overlay for this target instead of in the document, e.g. `python.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Python names version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-apigenie: codegen: name: Animal - target: $.components.schemas.PetStatus update: x-enum-varnames: [AVAILABLE, ON_HOLD, SOLD] ``` ```sh apigenie-generator client python openapi.yaml --overlay python.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_python.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the distribution, e.g. `pet-store-client`; the package is named after it (`pet_store_client`). Defaults to the title of the API followed by `-client`. ### --package-version Version of the package. Defaults to the version of the API. ## Configuration ```yaml version: '1.0' commands: generate: client_python: layout: package http_library: httpx group_by: tag action_naming: [id, url] raise_on_error: true enum_style: enum field_prefix: field_ class_overrides: pet-v2: Pet ``` | Option | Description | | ------ | ----------- | | `layout` | `package` (default): `pyproject.toml`, `README.md` and the package directory; `sources`: the modules only, directly in the output directory | | `http_library` | `httpx` (default) or `httpx2`, Pydantic's continuation of httpx with the same API | | `group_by` | How operations are grouped into resources: `tag` (default), `url`, `extension` (`x-apigenie` `codegen/controller-name`), or an array of them, tried in order | | `action_naming` | Sources of method names: `id`, `extension` (`x-apigenie` `codegen/action-name`), `url`, or an array of them, tried in order; `[id, url]` by default | | `raise_on_error` | `true` (default): documented error responses raise errors; `false`: methods return their parsed bodies | | `enum_style` | `enum` (default): `Enum` classes for enums of component schemas; `literal`: `Literal` types everywhere | | `field_prefix` | Prefix of field names that would be invalid or clash with Pydantic, e.g. `json` becomes `field_json`; `field_` by default | | `class_overrides` | Class names of component schemas, by schema name | Unknown options are errors. --- # server aspnet Source: https://apigenie.pl/docs/reference/generator/server-aspnet Generate an ASP.NET Core server in C#, built on Minimal APIs. From an AsyncAPI document, the command generates an ASP.NET Core Kafka application instead; see [apigenie-generator server aspnet (AsyncAPI)](/docs/reference/generator/server-aspnet-asyncapi). ## Usage ```sh apigenie-generator server aspnet [...OPTIONS] INPUT_FILE ``` ## Description The input must be an OpenAPI 3.0, 3.1 or 3.2 document. Convert Swagger 2.0 documents with [`apigenie convert`](/docs/reference/cli/convert), and bundle documents referencing other files with [`apigenie bundle`](/docs/reference/cli/bundle). The generated project targets .NET 8 (or 9 and 10, see `target_framework`) and uses only the shared framework, without NuGet packages: ``` PetStore.csproj Program.cs the host, written only when missing README.md ApiRegistration.cs MapPetStoreApi(): maps the endpoints of all operations Apis/IPetsApi.cs one interface per group of operations Apis/PetsApi.cs its implementation, written only when missing Endpoints/PetsEndpoints.cs routes, parameter binding, validation and metadata Models/Pet.cs records and enums of the schemas Infrastructure/*.cs helpers used by the generated code ``` Operations are grouped by their first tag (or by the first segment of their paths). Each group gets an interface whose methods return the responses of the operation as a union of typed results, so the compiler accepts only declared responses: ```csharp public interface IPetsApi { /// Find a pet /// GET /pets/{petId} Task, NotFound, GoneResult>> GetPet(long petId, CancellationToken cancellationToken); } ``` Implementations return `TypedResults.Ok(pet)`, `TypedResults.NotFound()` and the like. Statuses without a `TypedResults` method (403, 410, 429, 500, ...) have result types of their own, created with `ApiResults.Gone(error)`. All results describe their responses in the endpoint metadata, so OpenAPI documents produced by ASP.NET Core match the input. The endpoints bind parameters from the path, the query, headers and cookies, with the styles of the document (exploded or delimited arrays), JSON, text, binary, form and multipart bodies, and resolve the implementation from the services of the request: ```csharp var app = builder.Build(); app.MapPetStoreApi(); ``` Models are records with `required` properties, System.Text.Json attributes and DataAnnotations attributes for the constraints of the schemas (`minimum`, `maxLength`, `pattern`, `format: email`, `maxItems`, ...). An endpoint filter checks them, and the values of enums, before the implementation is called, and answers invalid requests with 400 validation problem details. - `oneOf` and `anyOf` with a discriminator become abstract records with `[JsonPolymorphic]` attributes, and their variants derive from them. On .NET 8, System.Text.Json reads the discriminator only as the first property of an object. - `allOf` referencing a schema with a discriminator becomes inheritance; other `allOf` schemas are merged into one record. - String enums become C# enums whose converters read and write the values of the document, e.g. `in-progress`. Integer enums keep their values. The `x-enum-varnames` extension names members. - Inline object and enum schemas get types named after their location, e.g. `PetOwner` for the `owner` property of `Pet`. Identical inline schemas share a type. - Schemas whose names would clash with types of .NET (`Task`, `File`, `Environment`, ...) get a `Model` suffix. Operations with security requirements call `RequireAuthorization()`; configure the authentication schemes in `Program.cs`, which lists the schemes of the document. Not supported: `oneOf` and `anyOf` without a discriminator whose members have different types (typed `object`; members of one type, e.g. strings of different formats, give that type), `not`, `if`/`then`/`else`, separate request and response variants of models with `readOnly` and `writeOnly` properties (such properties are optional), `prefixItems`, `patternProperties`, `multipleOf`, `dependentRequired`, `dependentSchemas`, `unevaluatedProperties`, object query parameters (`deepObject`, left out with a warning), parameters described by `content` (left out with a warning), the `deepObject` encoding and the constraints of form fields, response headers, more than one media type per response (JSON wins: no `text/event-stream`, JSON Lines or binary alternatives), callbacks and webhooks, operation and path servers (every route is below the path of the first server), and OAuth 2.0 scopes (operations only call `RequireAuthorization()` with the default policy). ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.controller-name` | on an operation or its path item: the group (API interface) of the operation, whatever `grouping` says | | `x-apigenie.codegen.action-name` | name of the method of an operation, when `action_naming` lists `extension` | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out, unless `skip_disabled` is `false` | | `x-internal` | `true` on an operation leaves it out | | `x-enum-varnames`, `x-enum-names` | names of the members of an enum, in PascalCase | | `x-enum-descriptions`, `x-enumDescriptions` | documentation comments of the members of an enum | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | The extensions can live in an overlay for this target instead of in the document, e.g. `aspnet.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: ASP.NET names version: 1.0.0 actions: - target: $.components.schemas.PetStatus update: x-enum-varnames: [Available, OnHold, Sold] - target: $.paths['/pets'] update: x-apigenie: codegen: controller-name: Animals ``` ```sh apigenie-generator server aspnet openapi.yaml --overlay aspnet.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_aspnet.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Root namespace of the generated code, e.g. `Acme.PetStore`; also the name of the project. Defaults to the title of the API. ### --package-version Version of the project. Defaults to the version of the API when it's a valid .NET version, or `1.0.0`. ### -p, --property NAME=VALUE Sets an option (see below), e.g. `-p layout=sources`; can be repeated. Options given on the command line take precedence over the configuration file. OpenAPI documents only: using it with an AsyncAPI document is an error. ## Configuration The options are read from `commands.generate.server_aspnet`. Its `asyncapi` section configures the generation from AsyncAPI documents (see [apigenie-generator server aspnet (AsyncAPI)](/docs/reference/generator/server-aspnet-asyncapi#configuration)). ```yaml version: '1.0' commands: generate: server_aspnet: namespace: Acme.PetStore target_framework: net10.0 grouping: url ``` | Option | Values | Description | | ------ | ------ | ----------- | | `layout` | `project`, `sources` | `project` (default): a complete project (.csproj, Program.cs, README.md); `sources`: only the generated sources, for an existing project | | `namespace` | namespace | Root namespace of the generated code (also `--package-name`); derived from the API title by default | | `target_framework` | `net8.0`, `net9.0`, `net10.0` | Target framework of the project (default: `net8.0`) | | `grouping` | `tag`, `url` | Grouping of operations into API interfaces: by their first tag (default), or by the first segment of their paths | | `action_naming` | `id`, `extension`, `url` | Sources of method names, in order of preference (default: `id`, `url`) | | `validation` | `filter`, `none` | `filter` (default): an endpoint filter validates parameters and bodies against their DataAnnotations; `none`: attributes only | | `authorization` | `require`, `none` | `require` (default): operations with security requirements call `RequireAuthorization()`; `none`: no authorization metadata | | `base_path` | `servers`, `none` | `servers` (default): routes are prefixed with the path of the first server URL; `none`: no prefix | | `program` | `true`, `false` | Generate Program.cs and stub implementations of the API interfaces (written only when missing; default: `true`) | | `skip_deprecated` | `true`, `false` | Don't generate deprecated operations (default: `false`) | | `skip_disabled` | `true`, `false` | Don't generate operations disabled with the `x-apigenie` extension (default: `true`) | | `model_suffix` | text | Suffix of model names, e.g. `Dto` | | `version` | version | Version of the project (also `--package-version`); defaults to the version of the API | Unknown options are errors. --- # server express Source: https://apigenie.pl/docs/reference/generator/server-express Generate an Express server stub in TypeScript from an OpenAPI document. ## Usage ```sh apigenie-generator server express [...OPTIONS] INPUT_FILE ``` ## Description The input must be an OpenAPI 3.0, 3.1 or 3.2 document; bundle documents referencing other files with `apigenie bundle` first. The generated code runs on Node.js 20 or later with Express 5. The generator writes typed handler interfaces and a router serving them. Your code implements the handlers in files of its own, so regenerating the code never overwrites it: ```ts import express from "express"; import { BASE_PATH, createRouter, HttpError, type PetsHandlers } from "pet-store-server"; const pets: Partial = { async listPets(request) { const pets = await db.pets.list({ limit: request.query.limit }); return { status: 200, body: pets, headers: { "X-Total-Count": pets.length } }; }, async showPetById(request) { const pet = await db.pets.find(request.params.petId); if (pet === undefined) { throw new HttpError(404, "No such pet"); } return { status: 200, body: pet }; }, }; const app = express(); app.use( BASE_PATH, createRouter( { pets }, { handleNotFound: true, security: { api_key: ({ credential }) => users.byApiKey(credential) }, }, ), ); app.listen(8080); ``` Operations are grouped into handler interfaces by their first tag (`PetsHandlers`); operations without tags belong to the `default` group. A handler is named after the `operationId` of its operation, or after its method and path. It receives: - the request: the decoded and validated path, query, header and cookie parameters (`params`, `query`, `headers`, `cookies`, by name) and the body with its declared media type (`contentType`), all typed from the document - the context: the Express request and response (`req`, `res`), the description of the operation and the principal returned by the security handlers It returns the response: a status, a body, headers and optionally a content type, typed per status from the document. A handler which answers on its own through `context.res` returns nothing. For every request, the router: 1. checks the security requirements of the operation: any of them, with all the schemes of a requirement, must be satisfied. A security handler receives the credential (the API key, the bearer token or the decoded `user:password` of basic authentication) and the required scopes, and returns the principal, or `undefined` to reject the request with 401 Unauthorized. It may throw an `HttpError`, e.g. 403 Forbidden for missing scopes. Schemes without handlers reject every request. 2. decodes the parameters in all OpenAPI styles (`simple`, `label`, `matrix`, `form`, `spaceDelimited`, `pipeDelimited` and `deepObject`, exploded or not) and converts them to the types of their schemas; parameters with `content` are parsed as JSON. Absent parameters get their default values. 3. reads the body: JSON, `application/x-www-form-urlencoded` (converted to the types of its schema), text and binary bodies are parsed; `multipart/form-data` bodies are left to middleware such as multer, whose `req.body` is used. A media type that isn't declared is answered with 415 Unsupported Media Type, a missing required body with 400. 4. validates the parameters and the body against their schemas (see `validation`) and answers invalid requests with 400 Bad Request. 5. calls the handler, or answers with 501 Not Implemented when there's none. 6. writes the response, optionally validating it first (`validateResponses`). Errors are answered with [problem details](https://www.rfc-editor.org/rfc/rfc9457) (`application/problem+json`). Validation errors list the failed checks: ```json { "type": "about:blank", "title": "Bad Request", "status": 400, "detail": "The request is invalid", "errors": [ { "in": "query", "name": "limit", "path": "", "keyword": "maximum", "message": "must be <= 50" }, { "in": "body", "path": "/name", "keyword": "required", "message": "must have required property 'name'" } ] } ``` Routes are registered so that literal path segments win over parameters (`/pets/mine` before `/pets/{petId}`). Deprecated operations are marked `@deprecated`, and operations disabled with the `x-apigenie` extension (`codegen.disabled`) are left out. Callbacks and webhooks are not generated, and operation and path servers are ignored: every route is served under `BASE_PATH`. The generated package contains: - `src/types.ts`: the types of the component schemas, and the request and response types of every operation - `src/handlers.ts`: the handler interfaces, `Handlers`, `SecurityHandlers` and `createRouter(handlers, options)` - `src/metadata.ts`: the operations (`OPERATIONS`) and security schemes described for the runtime, and the port (`DEFAULT_PORT`) and path (`BASE_PATH`) of the first server - `src/validation.ts` and, depending on `validation`, `src/schemas.ts` and `src/ajv_engine.ts`, or `src/validators.ts` - `src/runtime.ts`: the request handling code, which doesn't depend on the document - `src/document.ts`: the OpenAPI document, with `copy_document` - `src/index.ts`, `package.json`, `tsconfig.json` and `README.md` - `src/server.ts`: an example server with stub handlers, with `scaffold` `README.md` and `server.ts` are written only if they don't exist; the other files are overwritten. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the type of a component schema | | `x-apigenie.codegen.controller-name` | on an operation or its path item: the handler interface of the operation, when `grouping` lists `extension` | | `x-apigenie.codegen.action-name` | name of the handler of an operation, when `action_naming` lists `extension` | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out, unless `skip_disabled` is `false` | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | The extensions can live in an overlay for this target instead of in the document, e.g. `express.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: TypeScript names version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-apigenie: codegen: name: Animal - target: $.paths['/admin'] update: x-apigenie: codegen: disabled: true ``` ```sh apigenie-generator server express openapi.yaml --overlay express.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_express.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the generated package. Defaults to the title of the API followed by `-server`. ### --package-version Version of the generated package. Defaults to the version of the API. ## Configuration ```yaml version: '1.0' commands: generate: server_express: validation: ajv validate_responses: true scaffold: true ``` | Option | Description | | ------ | ----------- | | `layout` | `package` (default): an npm package with sources in `src/`; `sources`: the sources only | | `import_extension` | Extension of relative imports: `.js` (default, Node.js) or an empty string (bundlers) | | `validation` | `ajv` (default): JSON Schemas validated with [Ajv](https://ajv.js.org); `generated`: generated TypeScript validators, without dependencies; `none` | | `validate_responses` | Generate what's needed to validate responses, enabled by the `validateResponses` router option (default: false) | | `grouping` | How operations are grouped into handler interfaces: `tag` (default), `extension` (`x-apigenie` `codegen.controller-name`) or `url`, or a list of them, in order of preference | | `action_naming` | Sources of handler names: `id`, `extension` (`x-apigenie` `codegen.action-name`) or `url`, or a list of them (default: `[id, url]`) | | `skip_deprecated` | Leave deprecated operations out (default: false) | | `skip_disabled` | Leave out operations disabled with `x-apigenie` (default: true) | | `copy_document` | Include the document in the package, so that the router can serve it (default: false) | | `scaffold` | Write an example server, `src/server.ts`, unless it exists (default: false) | | `security` | Check the security requirements of operations (default: true) | Both validation modes report the same issues with the same messages, and both convert OAS 3.0 schemas (`nullable`, boolean `exclusiveMinimum` and `exclusiveMaximum`) and leave `readOnly` properties out of the required properties of requests and `writeOnly` ones out of responses. The generated validators don't check `if`/`then`/`else`, `dependentRequired`, `dependentSchemas`, `unevaluatedProperties`, `unevaluatedItems`, `contains`, `propertyNames`, `contentSchema` and `$dynamicRef`; the generator warns about them. Formats are checked as JSON Schema defines them, the generated validators knowing the common ones (`date-time`, `date`, `time`, `duration`, `email`, `hostname`, `ipv4`, `ipv6`, `uri`, `uri-reference`, `uuid`, `byte`, `int32` and `int64`). ## Router options | Option | Description | | ------ | ----------- | | `security` | Security handlers, by scheme name | | `validation` | A validation engine replacing the generated one, e.g. `noValidation` | | `validateResponses` | Validate the bodies returned by handlers; invalid ones are answered with 500 | | `handleNotFound` | Answer requests to unknown paths with 404 and unknown methods with 405 | | `notImplemented` | `"501"` (default) or `"next"`: pass requests to operations without handlers to the next middleware | | `exposeErrors` | Put the messages of unexpected errors into 500 responses | | `onError` | Called with unexpected errors, which are logged with `console.error` otherwise | | `bodyLimit` | Maximum size of request bodies, e.g. `"1mb"` | | `serveDocument` | Serve the document as JSON at this path, e.g. `"/openapi.json"` (needs `copy_document`) | | `caseSensitive`, `strict` | Options of the Express router | --- # server fastapi Source: https://apigenie.pl/docs/reference/generator/server-fastapi Generate a Python [FastAPI](https://fastapi.tiangolo.com) server from an OpenAPI document. ## Usage ```sh apigenie-generator server fastapi [...OPTIONS] INPUT_FILE ``` ## Description The generated project has the layout of the `python-fastapi` generator of [openapi-generator](https://openapi-generator.tech) and accepts its options, so a project generated by openapi-generator can be generated again by API Genie. The code itself is written for FastAPI 0.115+, pydantic 2.7+ and Python 3.10+: ``` pyproject.toml, requirements.txt, README.md, Dockerfile, docker-compose.yaml, openapi.yaml src/openapi_server/ main.py the application: includes the routers, loads the implementation apis/pets_api.py the routes of the "pets" API (one module per tag) apis/pets_api_base.py BasePetsApi: the methods to implement impl/__init__.py the implementation package, yours models/ pydantic models, one module per schema security_api.py the dependencies checking credentials tests/ pytest tests with FastAPI's TestClient ``` Routes validate requests, then call the methods of the base class of their API, which answer 501 Not Implemented. Implement them in a subclass, in a module of the implementation package: ```python from openapi_server.apis.pets_api_base import BasePetsApi from openapi_server.models import Pet class PetsApi(BasePetsApi): async def show_pet_by_id(self, *, pet_id: str) -> Pet: return Pet(id=1, name="Rex") ``` The application imports the modules of the implementation package when it starts, and the first subclass of a base class serves its requests. Tests can replace it with `app.dependency_overrides[get_pets_api]`. Credentials are validated the same way, by a subclass of `BaseSecurity` of `security_api.py`. The generated code covers: - routes with the status code, response class, documented responses (including `default`, ranges like `4XX` and headers), tags, summary, description, operation ID and deprecation of operations; routes of concrete paths come before the routes of templates matching them - path, query, header and cookie parameters with their aliases, defaults and constraints, JSON, text and binary request bodies, forms and file uploads (`UploadFile`) - models: classes with aliases for names that aren't valid Python names, constraints, defaults and descriptions; enums; `allOf` merged into one class; `oneOf`/`anyOf` as unions, with discriminators where the document has them; nullable types of OpenAPI 3.0 and 3.1; recursive and mutually referencing models - security: API keys (header, query, cookie), HTTP basic, bearer and other HTTP schemes, OAuth2 (all flows, with scopes) and OpenID Connect, with global security, alternatives (a request needs to meet one of the security requirements) and anonymous access (`{}`) Files are generated again on every run, except the implementation package, the tests of the APIs, `tests/conftest.py` and `.openapi-generator-ignore`, which are generated only when they don't exist. Files listed in `.openapi-generator-ignore` (which uses the syntax of `.gitignore`) aren't written. `.openapi-generator/FILES` lists the generated files. Differences from openapi-generator: - routes declare their status code and response class, e.g. 201 or 204 without a body, instead of documenting every response as 200 - the implementation is one instance of the subclass, resolved by a FastAPI dependency, instead of an instance per request; the tokens of security schemes are passed to it - models are idiomatic pydantic 2 models: `oneOf`/`anyOf` are unions (discriminated where possible) instead of wrapper classes, constraints use `pattern=` instead of the deprecated `regex=`, and there are no `to_dict()`/`from_dict()` helpers - OAuth2 schemes of the same flow don't collide, and OpenID Connect schemes are supported - mutual TLS security schemes are reported and left out, since FastAPI can't check them - unknown options are errors, not ignored - the input must be an OpenAPI 3.0, 3.1 or 3.2 document; convert Swagger 2.0 documents with `apigenie convert`, and bundle documents referencing other files with `apigenie bundle` Constructs that FastAPI can't express are reported as warnings and simplified: object query parameters (e.g. `deepObject`) are left out and the route receives the request instead; arrays in headers, cookies and non-exploded query parameters are received as strings; nested objects in forms are received as strings. Not supported: callbacks, webhooks and response links, which the served OpenAPI document leaves out, and `x-internal`, whose operations stay in it. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.controller-name` | on an operation or its path item: the API module of the operation, instead of its first tag | | `x-apigenie.codegen.action-name` | name of the function of an operation, instead of its `operationId` | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out, with `skipDisabled` | | `x-codegen-request-body-name` | on an operation or its request body: the name of the body parameter | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | The extensions can live in an overlay for this target instead of in the document, e.g. `fastapi.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: FastAPI names version: 1.0.0 actions: - target: $.paths['/pets'].post update: x-codegen-request-body-name: pet x-apigenie: codegen: action-name: add_pet ``` ```sh apigenie-generator server fastapi openapi.yaml --overlay fastapi.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_fastapi.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Python package of the generated code (`packageName`), e.g. `pet_store` or `acme.pet_store`. Defaults to `openapi_server`. ### --package-version Version of the generated project (`packageVersion`). Defaults to the version of the API when pip accepts it, otherwise to `1.0.0`. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p serverPort=8000`. Options given on the command line take precedence over the configuration file. Mapping options take comma-separated lists, e.g. `-p 'nameMappings=_links=links,$type=kind'`. ## Configuration Options can also be set in the configuration file, either directly or in an `additionalProperties` section, like in configuration files of openapi-generator: ```yaml version: '1.0' commands: generate: server_fastapi: packageName: pet_store serverPort: 8000 nameMappings: _links: links ``` | Option | Description | | ------ | ----------- | | `packageName` | Python package of the generated code (default `openapi_server`) | | `packageVersion` | Version of the project (defaults to the version of the API when pip accepts it, otherwise `1.0.0`) | | `serverPort` | Port the server listens to (defaults to the port of the first server, or 8080) | | `sourceFolder` | Directory of the package, relative to the output directory (default `src`) | | `fastapiImplementationPackage` | Package of the implementation of the APIs (default `impl`), inside the generated package unless useExternalImplementationPackage is set | | `useExternalImplementationPackage` | The implementation package is outside the generated package and isn't generated (default `false`) | | `generateTests` | Generate tests (tests/) (default `true`) | | `generateDocker` | Generate a Dockerfile and docker-compose.yaml (default `true`) | | `generatorVersion` | Version of the generator written into generated files | | `sortParamsByRequiredFlag` | Put required parameters before optional ones (default `true`) | | `skipDeprecated` | Don't generate code for deprecated operations (default `false`) | | `skipDisabled` | Don't generate code for operations disabled with x-apigenie (default `false`) | | `modelsLayout` | Where models are written: `modules` (a module per model, the default) or `single` (one module) | | `disallowAdditionalPropertiesIfNotPresent` | Models of schemas without additionalProperties reject unknown properties (extra="forbid") (default `false`) | | `hideGenerationTimestamp` | Accepted for compatibility with openapi-generator; generated files have no timestamps | | `modelNamePrefix` | Prefix of model names | | `modelNameSuffix` | Suffix of model names | | `modelNameMappings` | Mappings of schema names to model names (Schema=Model,...) | | `nameMappings` | Mappings of property names to field names (name=field_name,...) | | `parameterNameMappings` | Mappings of parameter names to Python names (name=python_name,...) | | `enumNameMappings` | Mappings of enum values to member names (value=NAME,...) | --- # server go Source: https://apigenie.pl/docs/reference/generator/server-go Generate a Go server for the `net/http` router of Go 1.22 from an OpenAPI document. From an AsyncAPI document, the command generates a Go Kafka application instead; see [apigenie-generator server go (AsyncAPI)](/docs/reference/generator/server-go-asyncapi). ## Usage ```sh apigenie-generator server go [...OPTIONS] INPUT_FILE ``` ## Description The generator writes a Go package whose code uses the standard library only. Its names and layering follow [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) (the `std-http-server` and `strict-server` targets), so code written against one of them is easy to move to the other. | File | Contents | | ---- | -------- | | `types.gen.go` | the types of the component schemas the operations use, and of their inline schemas | | `server.gen.go` | `ServerInterface`, the parameters structs, `ServerInterfaceWrapper`, `Operations`, `SecuritySchemes` and `HandlerWithOptions` | | `strict.gen.go` | `StrictServerInterface`, the request and response objects, and `NewStrictHandler` | | `spec.gen.go`, `openapi.yaml` | the OpenAPI document, embedded with `//go:embed` (`RawSpec()`) | | `runtime_*.gen.go` | parameter binding, validation, problem details, `Nullable[T]`, `Date`, multipart helpers | | `go.mod`, `cmd//main.go` | with `layout: module` (and `main: true`) | There are two ways to implement the server: - **`ServerInterface`**: one method per operation receives the `http.ResponseWriter`, the request, the path parameters and a struct with the query, header and cookie parameters, bound and validated. The method decodes the body and writes the response. - **`StrictServerInterface`**: one method per operation receives a request object (parameters and decoded, validated body) and returns a response object, e.g. `GetPet200JSONResponse`, with its status, typed headers and body. `NewStrictHandler` turns it into a `ServerInterface`. ```go mux := http.NewServeMux() api.HandlerWithOptions(api.NewStrictHandler(server, nil), api.StdHTTPServerOptions{ BaseURL: "/api/v1", BaseRouter: mux, SecurityHandler: authenticator, // runs before parameters are bound }) log.Fatal(http.ListenAndServe(":8080", mux)) ``` `HandlerWithOptions` registers one pattern per operation, e.g. `GET /pets/{petId}`, so routing, `405 Method Not Allowed` (with `Allow`) and `HEAD` requests are handled by `http.ServeMux`: - paths ending with `/` are registered as `/{$}`, so that they don't match the paths below them; - path parameters whose names aren't Go identifiers (`{pet-id}`) are renamed, and parameters sharing a path segment with other text (`/files/{name}.{ext}`) are split out of it; - patterns `http.ServeMux` can't register together (it panics), such as `GET /a/{x}/b` and `GET /a/b/{y}`, are served through one pattern generalizing them, which sends each request to the most specific operation. `route_conflicts: error` reports them as errors instead. ### Types - Optional properties are pointers with `omitempty`; slices, maps and `any` stay values. `optional_fields: omitzero` uses values with `omitzero` (Go 1.24). - Optional properties that can be null are `Nullable[T]`, which tells absent, null and set values apart; required ones are pointers. `nullable_type: pointer` uses pointers for both. - Required properties that are `readOnly` or `writeOnly` are pointers, as they are absent from requests or responses. - Structs that would contain themselves hold pointers. - Enums are defined types with constants named after the type and the value (`PetStatusAvailable`), `PetStatusValues()`, `IsValid()` and `Validate()`. - `oneOf` and `anyOf` schemas are structs with one pointer per variant. With a discriminator, the value goes to the variant its property selects: through the `mapping`, the name of the schema or the value the schema gives the property (`const`); other values go to the `defaultMapping` variant (`x-defaultMapping` in 3.0 and 3.1 documents), or are kept in `Raw`. Without a discriminator, the value goes to the first variant it decodes into without unknown properties and validates against. - `allOf` schemas are merged into one struct; an `anyOf` or `oneOf` whose members only list required properties (`anyOf: [{required: [email]}, {required: [phone]}]`) is checked by `Validate()`. - `additionalProperties` are kept in an `AdditionalProperties` map. - `date-time` strings are `time.Time`, `date` strings `Date`, `byte` and `binary` strings `[]byte` (files in multipart bodies). ### Validation Every type has a `Validate()` method checking the constraints of its schema (lengths, patterns, bounds, `multipleOf`, items, properties, enums and the `email`, `uuid`, `uri`, `hostname`, `ipv4`, `ipv6`, `date`, `date-time` and `time` formats). Parameters and request bodies are validated before the handlers are called, and failures are answered with [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details listing the failed checks: ```json {"title":"Bad Request","status":400,"detail":"The request is not valid.", "errors":[{"path":"limit","check":"maximum","message":"must be less than or equal to 100"}]} ``` `DefaultErrorHandler` maps errors to statuses: 400 for parameters and bodies, 413 for bodies larger than `max_body_bytes`, 415 for unsupported media types, the status of a `SecurityError` (401 by default) or of an error implementing `StatusCode() int`, and 500 otherwise. ### Extensions | Extension | Effect | | --------- | ------ | | `x-go-name` | name of the type of a schema, or of the field of a property | | `x-go-type` | Go type of a schema, e.g. `decimal.Decimal`; with `x-go-type-import: {path: ...}` for its package | | `x-go-type-skip-optional-pointer` | the optional property isn't a pointer | | `x-go-json-ignore` | the field gets the tag `json:"-"` | | `x-omitempty` | `true` or `false` overrides the `omitempty` of the field | | `x-enum-varnames`, `x-enumNames` | names of the enum constants | | `x-enum-descriptions` | comments of the enum constants | | `x-apigenie.codegen.name` | name of the type of a component schema, when it has no `x-go-name` | | `x-apigenie.codegen.action-name` | name of the handler method of an operation without an `operationId` (with the default `action_naming`, which lists `id` first) | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | The extensions can live in an overlay for this target instead of in the document, e.g. `go.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Go types version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-go-name: Animal - target: $.components.schemas.Pet.properties.price update: x-go-type: decimal.Decimal x-go-type-import: path: github.com/shopspring/decimal ``` ```sh apigenie-generator server go openapi.yaml --overlay go.overlay.yaml -o out ``` ### Limitations - Callbacks and webhooks aren't generated, and operation and path servers are ignored: every operation is registered below the same `BaseURL`. - `x-internal` isn't honoured: only `x-apigenie` `codegen.disabled` skips operations. - `text/event-stream` responses are written at once as text, so events can't be streamed; `itemSchema` isn't read, so stream items aren't typed (`application/jsonl` bodies are `io.Reader` streams). - `Validate()` doesn't check `prefixItems`, `patternProperties`, `if`/`then`/`else`, `dependentRequired`, `dependentSchemas` and `unevaluatedProperties`. - XML bodies are passed as streams (`io.Reader`), and form or multipart responses too. - Patterns using lookaround or backreferences, which Go's `regexp` package doesn't support, aren't checked (a warning says so). Patterns are compiled when first used. - `allOf` schemas are flattened: there are no type hierarchies. - Exported names of the runtime (`Problem`, `Date`, `File`, `Nullable`, `Operation`, ...) are reserved: schemas with these names get numbered types, e.g. `Problem2`. - The input must be an OpenAPI 3.0, 3.1 or 3.2 document; convert Swagger 2.0 documents with `apigenie convert`, and bundle documents referencing other files with `apigenie bundle`. ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_go.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the Go package. Defaults to the name of the output directory (`api` when it isn't a valid package name). ### --package-version Ignored: Go packages have no version. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p layout=module`. Options given on the command line take precedence over the configuration file. Lists are comma-separated (`-p include_tags=pets,store`) and mappings too (`-p type_mappings=uuid=github.com/google/uuid.UUID`). ## Configuration ```yaml version: '1.0' commands: generate: server_go: package: petstore layout: module module: github.com/acme/petstore type_mappings: uuid: github.com/google/uuid.UUID ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `package` | string | name of the output directory | Name of the Go package; also set by `--package-name`. | | `layout` | `package` or `module` | `package` | `module` also writes `go.mod`, making the output a module of its own. | | `module` | string | `example.com/` | Module path written to `go.mod`. | | `main` | boolean | `false` | With `layout: module`, writes `cmd//main.go` running a server whose operations answer 501, unless the file exists. | | `generate` | list of `types`, `server`, `strict` | all | Files to write. `strict` requires `server`, which requires `types`. | | `embedded_spec` | boolean | `true` | Writes `openapi.yaml` and `spec.gen.go` embedding it. | | `optional_fields` | `pointer` or `omitzero` | `pointer` | Optional properties as pointers with `omitempty`, or as values with `omitzero` (Go 1.24). | | `nullable_type` | `tristate` or `pointer` | `tristate` | Optional nullable properties as `Nullable[T]`, or as pointers. | | `include_tags` | list of strings | | Generates only the operations with one of these tags. | | `exclude_tags` | list of strings | | Skips the operations with one of these tags. | | `skip_prune` | boolean | `false` | Keeps the component schemas that no generated operation uses. | | `validate_requests` | boolean | `true` | Validates parameters and request bodies against their schemas. | | `validate_responses` | boolean | `false` | Validates the bodies of strict responses before writing them (500 when they aren't valid). | | `route_conflicts` | `warn` or `error` | `warn` | Serve the patterns `http.ServeMux` can't register together through a shared pattern, or report them as errors. | | `initialisms` | list of strings | | Words written in upper case in identifiers, besides `ID`, `URL`, `HTTP`, `JSON` and the other initialisms of golint. | | `type_mappings` | map of strings | | Go types of string formats or `x-go-type` names, e.g. `uuid: github.com/google/uuid.UUID`. | | `max_body_bytes` | integer | `0` (no limit) | Largest request body the strict server reads (413 beyond). | | `max_multipart_memory` | integer | `33554432` | Memory multipart bodies may take before their files are stored on disk. | | `action_naming` | list of `id`, `extension`, `url` | `id, extension, url` | Where operation names come from, in order of preference. | --- # server mcp Source: https://apigenie.pl/docs/reference/generator/server-mcp Generate a [Model Context Protocol](https://modelcontextprotocol.io) server in TypeScript from an OpenAPI document, so that AI agents call the API through typed tools. > **(experimental, outside the 1.0 compatibility promise)** MCP and its SDK are still moving, and > tool names and result shapes can change with them. `server mcp` says so in its `--help` and on > the first line it prints, on the standard error. See the > [experimental commands](/docs/reference/cli/overview#experimental-commands). ## Usage ```sh apigenie-generator server mcp [...OPTIONS] INPUT_FILE ``` ## Description The input must be an OpenAPI 3.0, 3.1 or 3.2 document; convert Swagger 2.0 documents with [`apigenie convert`](/docs/reference/cli/convert), and bundle documents referencing other files with [`apigenie bundle`](/docs/reference/cli/bundle). The generated npm package is a server on the official MCP SDK ([`@modelcontextprotocol/server`](https://www.npmjs.com/package/@modelcontextprotocol/server) 2), which MCP clients (Claude Code, Claude Desktop, IDEs) start and talk to over stdio: | File | Content | | ---------------- | ------------------------------------------------------------------------------- | | `src/client/` | a [fetch client](/docs/reference/generator/client-fetch) of the API, which the tools call | | `src/schemas.ts` | [Zod](https://zod.dev) (v4) schemas of the components, and of the arguments and results of the tools | | `src/tools.ts` | `registerTools(server)`, registering a tool per operation | | `src/config.ts` | the base URL and the credentials, read from environment variables | | `src/runtime.ts` | the call of an operation and its result as a tool result | | `src/server.ts` | `createServer()`, an `McpServer` with the tools | | `src/index.ts` | the entry point (the package's `bin`): the server on stdio | | `README.md` | the environment variables, the tools, and how to add the server to a client | ```sh apigenie-generator server mcp openapi.yaml -o petstore-mcp cd petstore-mcp && npm install && npm run build claude mcp add petstore --env PET_STORE_BASE_URL=https://petstore.example.com/v1 -- node "$PWD/dist/index.js" ``` ### Tools Every operation becomes a tool: - its name is the `operationId`, with other characters than letters, digits, `_` and `-` replaced by `_` (at most 64 characters), or the name of the client's function for operations without one; - its title is the summary, and its description the summary and the description (or the method and path), starting with "Deprecated." for deprecated operations; - its arguments are the parameters, by name, and the request body as `body` (`requestBody` when a parameter is named `body`), validated by their schemas before the API is called. Binary bodies, and the files of `multipart/form-data` bodies, are base64-encoded strings; - GET, HEAD and QUERY operations are marked read-only, DELETE operations destructive, and PUT and DELETE operations idempotent. A tool returns the status and the body of the response, as structured content (`{ "status": 200, "body": ... }`) described by the tool's output schema, and as JSON text. Binary bodies are returned as an embedded resource, with `{ status, contentType, size }`. A failed request is an error result with the status and the body of the response, and so is a response the API document doesn't describe, which lists what doesn't match. Operations with `x-mcp: false`, on the operation or its path item, don't become tools, nor do operations marked `x-internal: true` (unless `internal_operations` is set) or disabled with `x-apigenie`. ```yaml paths: /admin/reset: post: x-mcp: false ``` ### Environment variables The server reads the base URL of the API and the credentials of its security schemes from environment variables, prefixed by the API title in upper snake case (`PET_STORE` for "Pet Store", see `env_prefix`): | Variable | Value | | ------------------------------------------------ | ----------------------------------------------------- | | `_BASE_URL` | base URL of the API; the first server of the document by default | | `_` | API key of an `apiKey` scheme | | `__TOKEN` | token of an HTTP `bearer` scheme | | `__USERNAME`, `..._PASSWORD` | user name and password of an HTTP `basic` scheme | | `__ACCESS_TOKEN` | access token of an `oauth2` or `openIdConnect` scheme | `` is the name of the scheme in upper snake case (`apiKey` gives `API_KEY`). The generated `README.md` lists the variables of the API. Mutual TLS is not supported. ### Mock server With `--mock`, the package also contains the document (`openapi.json`) and an `npm run mock` script serving it with [`apigenie-server`](/docs/reference/server), and the tools call `http://127.0.0.1:4010` when no base URL is set, with the placeholder `mock` for the credentials that aren't set (the mock server only checks that they are present): a demo of the tools needs no running API and no credentials. ```sh apigenie-generator server mcp openapi.yaml -o petstore-mcp --mock cd petstore-mcp && npm install && npm run build npm run mock # in another terminal ``` ## Extensions | Extension | Effect | | --------- | ------ | | `x-mcp` | `false` on an operation or its path item: the operation isn't a tool | | `x-internal` | `true` on an operation or its path item leaves it out, unless `internal_operations` is set | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves it out | The extensions can live in an overlay for this target instead of in the document, e.g. `mcp.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: MCP tools version: 1.0.0 actions: - target: $.paths['/admin/reset'].post update: x-mcp: false ``` ```sh apigenie-generator server mcp openapi.yaml --overlay mcp.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_mcp.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the package and of the MCP server, `-mcp` by default. ### --package-version Version of the package and of the MCP server, the version of the API by default. ### --mock Make the tools call `apigenie-server` mocking the API when no base URL is set, as described above. Same as `mock: true`. ## Configuration ```yaml version: '1.0' commands: generate: server_mcp: include_tags: [pets, store] env_prefix: PETSTORE ``` | Option | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------ | | `include_tags` | only the operations with one of these tags become tools (a list or a comma-separated string); all by default | | `exclude_tags` | the operations with one of these tags don't become tools | | `internal_operations` | `true` also makes tools of the operations marked `x-internal: true` (default: `false`) | | `env_prefix` | prefix of the environment variables, e.g. `PETSTORE`; the API title in upper snake case by default | | `output_schema` | `false` leaves out the output schemas of the tools (default: `true`) | | `mock` | `true` points the tools at `apigenie-server` by default, as `--mock` does (default: `false`) | --- # server orpc Source: https://apigenie.pl/docs/reference/generator/server-orpc Generate a TypeScript server based on [oRPC](https://orpc.dev) from an OpenAPI document. ## Usage ```sh apigenie-generator server orpc [...OPTIONS] INPUT_FILE ``` ## Description The input must be an OpenAPI 3.0, 3.1 or 3.2 document; convert Swagger 2.0 documents with [`apigenie convert`](/docs/reference/cli/convert), and bundle documents referencing other files with [`apigenie bundle`](/docs/reference/cli/bundle). Every operation becomes an oRPC procedure, with [Zod](https://zod.dev) (v4) schemas validating its input and output. The generated npm package contains: | File | Content | | ------------------------- | -------------------------------------------------------------------------- | | `src/schemas.ts` | Zod schemas, with their types, of the components, bodies, inputs and outputs | | `src/contract/<group>.ts` | the contract of each procedure: route, input, output and errors | | `src/contract/index.ts` | the contract router, by group | | `src/handlers/<group>.ts` | a handler per procedure, throwing `NOT_IMPLEMENTED` | | `src/orpc.ts` | the context of the procedures and `implement(contract)` | | `src/router.ts` | the router assembling the handlers | | `src/server.ts` | a Node.js HTTP server with oRPC's `OpenAPIHandler` | | `src/main.ts` | starts the server on the port in `PORT` (3000 by default) | | `src/index.ts` | exports of the package | Procedures are grouped by the first tag of their operations, or the first segment of their paths, and named after their `operationId`s. The handlers, `orpc.ts`, `server.ts`, `main.ts`, `package.json` and `tsconfig.json` are meant to be edited, and are only written when they don't exist. Regenerating updates the schemas, the contract and the router; the TypeScript compiler then reports the handlers that new operations need. ```sh apigenie-generator server orpc openapi.yaml -o server cd server && npm install && npm run dev ``` The server answers the operations under the path of the first server of the document (e.g. `/v1`), and serves its OpenAPI document at `/spec.json` and an API reference at `/`. Operations with servers of their own (operation or path item `servers`) with another path, e.g. a health check on the root of a status host, are also answered at that path. ### Inputs and outputs A procedure receives the path parameters merged with the query parameters (GET) or the body (other methods), oRPC's `compact` input structure, unless that would lose part of the request: operations with header or cookie parameters, query parameters in other methods than GET, or bodies which can't be merged with the path parameters get the `detailed` structure, with `params`, `query`, `headers` (in lower case) and `body`. Parameters are converted from strings to the types of their schemas, and array parameters accept repeated values, a single value, and the delimited values of the `form` (with `explode: false`), `spaceDelimited` and `pipeDelimited` styles. Cookie parameters are read from the `Cookie` header (`headers.cookie`), parameters described by `content: application/json` are parsed as JSON, and a path segment of several parameters, such as `/labels/{labelId}.{extension}`, is routed as one parameter and split into its parameters. The body is read in any of the operation's media types: - JSON bodies, including other JSON media types such as `application/merge-patch+json`, which the server parses before oRPC, and JSON Lines, which the procedure receives as an array; - `application/x-www-form-urlencoded` and `multipart/form-data` bodies, whose fields are converted to the types of their schemas (an empty field is `null` where the schema allows it) and whose binary parts are `File`s; - `text/plain` bodies as strings, other text and binary bodies as `File`s. A procedure returns the body of its response. When an operation has success responses with different bodies, or response headers, it returns `{ status, headers, body }` objects instead. JSON bodies are objects; other bodies (text, files) are `Blob`s, which the server sends with their media type. A `text/event-stream` response is an `eventIterator()` of its `itemSchema` (the `contentSchema` of the events' `data`, or the item of the operation's JSON Lines response); when the operation also answers JSON, the handler returns either a value or an async iterator, as the client accepts. The `writeOnly` properties of a response are left out of what the server sends, and the `readOnly` properties of a request out of what the handler receives. A oneOf with a discriminator and a `defaultMapping` (`x-defaultMapping` in 3.0 and 3.1 documents) validates the option its property selects, or the default one; closed allOf compositions (`unevaluatedProperties: false`) reject properties none of their schemas declare. Each 4xx and 5xx response becomes an error of the procedure: `NOT_FOUND`, `CONFLICT` and the other codes of oRPC, or `HTTP_<status>` for other statuses. `4XX` and `5XX` ranges become the errors of their first status (`BAD_REQUEST`, `INTERNAL_SERVER_ERROR`), and a `default` response next to success responses `INTERNAL_SERVER_ERROR`, unless the document declares those statuses. Their response bodies are their `data`, which the server sends as the body of the response: ```ts throw errors.NOT_FOUND({ data: { code: 404, message: "No such pet" } }); ``` Not supported, and reported as warnings: path parameters in other styles than `simple`, object query parameters in the `form` style (read in the `deepObject` style), and operations with other methods than GET, POST, PUT, DELETE, PATCH and HEAD (QUERY is routed with oRPC 2), which are skipped. oRPC describes the procedures a server answers, not the requests it sends, so callbacks and webhooks are not generated; it streams async iterators as server-sent events only, so a JSON Lines response is answered as a `Blob` the handler builds. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the schema and type of a component schema (`AnimalSchema`, `Animal`) | | `x-apigenie.codegen.action-name` | name of the procedure of an operation, when `action_naming` lists `extension` | The extensions can live in an overlay for this target instead of in the document, e.g. `orpc.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: TypeScript names version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-apigenie: codegen: name: Animal ``` ```sh apigenie-generator server orpc openapi.yaml --overlay orpc.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_orpc.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the package, `<title>-server` by default. ### --package-version Version of the package, the version of the API by default. ## Configuration ```yaml version: '1.0' commands: generate: server_orpc: orpc_version: 1 input_structure: auto ``` | Option | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `orpc_version` | `1` (default) for oRPC 1, `2` for oRPC 2 (beta): routes declared with `oc.meta(openapi(...))` and `queryStyles` | | `input_structure` | `auto` (default), `compact` (header parameters, and query parameters of other methods than GET, are dropped) or `detailed` | | `layout` | `package` (default) for an npm package, `sources` for the sources only | | `import_extension` | extension of relative imports: `.js` (default, Node.js) or `""` (bundlers) | | `action_naming` | sources of procedure names, in order: `id` (operationId), `extension` (`x-apigenie`), `url`; `[id, url]` by default | | `additional_properties` | `loose` (default) keeps the properties of objects their schemas don't list, `strip` removes them | | `describe` | `true` attaches the descriptions of schemas with `.describe()` (default: `false`) | --- # server phoenix Source: https://apigenie.pl/docs/reference/generator/server-phoenix Generate a [Phoenix](https://www.phoenixframework.org) 1.7 application from an OpenAPI document. From an AsyncAPI document, the command generates Phoenix channels and Kafka pipelines instead; see [apigenie-generator server phoenix (AsyncAPI)](/docs/reference/generator/server-phoenix-asyncapi). ## Usage ```sh apigenie-generator server phoenix [...OPTIONS] INPUT_FILE ``` ## Description The generator writes a Mix project (Elixir 1.18 or later) depending on Phoenix 1.7, Bandit and Jason, without Ecto, HTML, assets or a mailer. Its code passes `mix format --check-formatted`, `mix compile --warnings-as-errors` and `mix credo --strict`, and `mix phx.server` serves it. | File | Contents | | ---- | -------- | | `lib/<app>_web/router.ex` | the routes of the operations, under the path of the first server | | `lib/<app>_web/api/<tag>.ex` | a behaviour per tag (`Default` for operations without tags), with a callback per operation | | `lib/<app>_web/controllers/<tag>_controller.ex` | an action per operation, calling the implementation of the behaviour | | `lib/<app>/models/*.ex` | a module per schema: structs, enums, unions and aliases, with `new/1`, `new!/1` and `to_map/1` | | `lib/<app>_web/runtime/*.ex` | the checks of credentials, the decoding of parameters and bodies, the encoding of responses | | `lib/<app>_web/authenticator.ex` | the behaviour checking credentials | | `lib/<app>_web/endpoint.ex`, `error_json.ex`, `lib/<app>/application.ex` | the endpoint, the errors Phoenix renders, the application | | `config/*.exs`, `mix.exs`, `.formatter.exs`, `.credo.exs`, `.gitignore`, `README.md` | the project | ```sh mix deps.get PORT=8080 mix phx.server ``` ### Routes Every operation has a route in `<App>Web.Router`, in the scope of the path of the first server (`/v2` for `https://api.example.com/v2`, or the `base_path` option). Methods without a Phoenix macro, such as `QUERY` and the `COPY` of `additionalOperations`, are routed with `match`. Static segments come before parameters (`/shipments/search` before `/shipments/{shipmentId}`), as Phoenix tries the routes in order. A segment holding parameters is captured whole and split by the runtime with its template, so `/labels/{labelId}.{extension}` works. ### Behaviours and implementations The operations of each tag are the callbacks of a behaviour, `<App>Web.Api.<Tag>`. A callback receives the connection, the parameters and the body: ```elixir defmodule MyApp.Shipments do @behaviour CarpetExpressWeb.Api.Shipments @impl true def get_shipment(_conn, %{shipment_id: id}, nil) do case MyApp.Store.fetch(id) do {:ok, shipment} -> {200, shipment} :error -> {404, %{"type" => "about:blank", "title" => "Not Found", "status" => 404}} end end end ``` and is configured as the implementation: ```elixir config :carpet_express, CarpetExpressWeb.Api.Shipments, impl: MyApp.Shipments ``` Operations without an implementation answer 501 Not Implemented. A callback returns `{status, body}`, `{status, body, headers}`, or the connection after sending a response itself (e.g. a chunked stream). The body is encoded in the media type the document gives the status (the exact code, then its range, then `default`): JSON with the type of the response, so a model struct or a plain map; text and bytes as they are; nothing for responses without content. ### Parameters and bodies The actions check the request before calling the implementation, and answer a problem document (RFC 9457, `application/problem+json`) when it doesn't match the description: - 401 when the request has none of the credentials of the operation's security requirements; - 400 when a parameter is missing or invalid (the `errors` list the parameters), or the body is missing, isn't JSON or doesn't match its schema; - 415 for a media type the operation doesn't accept. Parameters are decoded from their styles into a map with atom keys (`%{shipment_id: "...", limit: 20}`): `simple`, `label` and `matrix` path parameters, `form`, `spaceDelimited`, `pipeDelimited` and `deepObject` query parameters, headers, cookies, and parameters described with `content` as JSON. Integers, numbers and booleans are converted from text, models and enums decoded with their schemas. | Media type | Body | | ---------- | ---- | | JSON (`application/json`, `*+json`) | the model, a list or the value its schema describes | | `application/x-www-form-urlencoded` | the map of the fields | | `multipart/form-data` | the map of the parts, files as `Plug.Upload` structs | | `text/*` | a string | | anything else | the bytes | `Plug.Parsers` reads JSON, form and multipart bodies of up to 20 MB; the runtime parses the JSON and form bodies of methods Plug.Parsers leaves alone, such as `QUERY`. ### Security The first security requirement of an operation whose credentials the request presents is checked: API keys in headers, the query or cookies, HTTP bearer and basic, and OAuth2 and OpenID Connect tokens as bearer tokens. Without an implementation of `<App>Web.Authenticator`, every presented credential is accepted; with one, ```elixir config :carpet_express, CarpetExpressWeb.Authenticator, impl: MyApp.Authenticator ``` its `authenticate(conn, scheme, credential, scopes)` returns `{:ok, principal}`, assigned to the connection as `:principal`, or `{:error, reason}`, answered with 401. ### Types The models are the modules of [the Elixir models](/docs/reference/generator/client-elixir#types), shared with `client elixir`: a struct per object (with `allOf` flattened), atoms for string enums, a module dispatching on the discriminator for `oneOf` and `anyOf`, `Date`, `DateTime`, `NaiveDateTime` and `Time` for dates and times, binaries for `format: byte` (decoded from base64) and `format: binary`, `additional_properties` maps, and `| nil` for nullable values. ### Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the module of a component schema, unless `module_overrides` names it | | `x-apigenie.codegen.action-name` | name of the action and callback of an operation without an `operationId` (with the default `action_naming`, which lists `id` first) | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out | | `x-internal` | `true` on an operation or its path item leaves the operation out, unless `internal_operations` is set | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped | The extensions can live in an overlay for this target instead of in the document, e.g. `phoenix.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Elixir names version: 1.0.0 actions: - target: $.components.schemas.Pet update: x-apigenie: codegen: name: Animal - target: $.paths['/admin'] update: x-internal: true ``` ```sh apigenie-generator server phoenix openapi.yaml --overlay phoenix.overlay.yaml -o out ``` ### Limitations - `text/event-stream` and JSON Lines responses are sent whole unless the implementation sends the response itself. - The models don't validate `minimum`, `maximum`, `pattern` and the other constraints, and ignore `prefixItems`, `patternProperties` and conditional schemas. - `readOnly` and `writeOnly` properties are fields of the same struct. - Every operation is served under the base path; operation and path servers are ignored. - Callbacks, webhooks and links aren't generated. - The endpoint serves HTTP; mutual TLS needs an `https` configuration of the endpoint. ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_phoenix.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Name of the Mix application, converted to snake_case (`carpet-express` gives `carpet_express`, the root module `CarpetExpress` and the web modules `CarpetExpressWeb`). Defaults to the name of the output directory. ### --package-version Version of the Mix project; `0.1.0` by default. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p base_path=/api`. Options given on the command line take precedence over the configuration file. Lists are comma-separated (`-p include_tags=pets,store`) and mappings too (`-p module_overrides=pet-owner=Owner`). ## Configuration ```yaml version: '1.0' commands: generate: server_phoenix: app: carpet_express base_path: /v2 exclude_tags: [internal] ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `app` | string | name of the output directory | Name of the Mix application (snake_case, the directory under `lib/`); also set by `--package-name`. | | `namespace` | string | the application in PascalCase | Root module of the generated modules, e.g. `CarpetExpress`; the web modules are in `CarpetExpressWeb`. | | `base_path` | string | the path of the first server | Path the routes are mounted at, e.g. `/v2`; `/` mounts them at the root. | | `include_tags` | list of strings | | Generates only the operations with one of these tags. | | `exclude_tags` | list of strings | | Skips the operations with one of these tags. | | `skip_prune` | boolean | `false` | Keeps the component schemas that no generated operation uses. | | `internal_operations` | boolean | `false` | Also generates the operations marked `x-internal: true`. | | `dev_deps` | boolean | `true` | Lists credo as a dependency of `mix.exs` (`only: [:dev, :test]`). | | `action_naming` | list of `id`, `extension`, `url` | `id, extension, url` | Where the names of actions and callbacks come from, in order of preference. | | `field_prefix` | string | `field_` | Prefix of struct fields whose property names give no valid name. | | `module_overrides` | map of strings | | Module names of component schemas, e.g. `pet-owner: Owner`. | --- # server spring Source: https://apigenie.pl/docs/reference/generator/server-spring Generate a Spring Boot server, or Spring Cloud / HTTP interface clients, in Java. From an AsyncAPI document, the command generates a Spring Boot Kafka application instead; see [apigenie-generator server spring (AsyncAPI)](/docs/reference/generator/server-spring-asyncapi). ## Usage ```sh apigenie-generator server spring [...OPTIONS] INPUT_FILE ``` ## Description The generator produces the same code as the `spring` generator of [openapi-generator](https://openapi-generator.tech), and accepts the same options. A project generated by openapi-generator can be regenerated by API Genie, and vice versa. It supports three libraries: - `spring-boot` (default): a Spring Boot application, with API interfaces (`PetApi`), controllers implementing them (`PetApiController`), optional delegates (`PetApiDelegate`), models, SpringDoc configuration and a Maven `pom.xml` - `spring-cloud`: Feign clients (`PetApiClient`) and their security configuration - `spring-http-interface`: `@HttpExchange` interfaces and a configurator creating their proxies The `spring-cloud` and `spring-http-interface` clients are generated by the writers of [`apigenie-generator client java`](/docs/reference/generator/client-java) (its `feign` and `http-interface` libraries), which also generates standalone clients on Spring's `RestClient` and `RestTemplate`. The generated project contains: - `pom.xml` and `README.md` - the application class, `RFC3339DateFormat`, `application.properties` and a copy of the OpenAPI document in `src/main/resources/openapi.yaml` (servers) - API interfaces, grouped by the first segment of their paths or, with `useTags`, by tags - models: classes with fluent setters, `equals`, `hashCode` and `toString`, enums, interfaces for `oneOf` schemas, and class hierarchies for `allOf` schemas with discriminators - `.openapi-generator/FILES` (the list of generated files) and `.openapi-generator/VERSION` Controllers (`*ApiController.java`) are generated only if they don't exist, so they can be implemented. Files listed in `.openapi-generator-ignore` (which uses the syntax of `.gitignore`) are not written. The vendor extensions the generator reads are listed in "Extensions". Differences from openapi-generator: - `useSpringBoot3` is `true` by default - generated files are marked with `@Generated(value = "API Genie")` - unknown options are errors, not ignored - the input must be an OpenAPI 3.0, 3.1 or 3.2 document; convert Swagger 2.0 documents with `apigenie convert`, and bundle documents referencing other files with `apigenie bundle` - schemas whose names give the same class name (e.g. `app-permissions` and an inline `app_permissions`) get classes with numeric suffixes, e.g. `AppPermissions1` Not supported: QUERY and other methods besides the eight classic ones (`additionalOperations`), which are left out; callbacks and webhooks (only their payload models are generated); parameter styles (`deepObject`, `spaceDelimited`, `pipeDelimited`) and parameters described with `content`; request media types after the first one; streaming responses (`text/event-stream`, JSON Lines), which are generated as a single list; strings with a `contentMediaType` as files or binary bodies (only `format: binary` is); `prefixItems`, `patternProperties`, conditional schemas and `multipleOf`; the flows of OAuth 2 schemes and mutual TLS in the SpringDoc configuration. ## Extensions | Extension | Effect | | --------- | ------ | | `x-class-extra-annotation` | annotations of the class of a schema, e.g. `['@Entity']` | | `x-implements` | interfaces the class of a schema implements | | `x-field-extra-annotation` | annotations of the field of a property, or of the method parameter of a parameter | | `x-extra-annotation` | annotations of the getter of a property | | `x-setter-extra-annotation` | annotations of the setter of a property | | `x-pattern-message`, `x-size-message`, `x-minimum-message`, `x-maximum-message` | `message` of the `@Pattern`, `@Size`, `@Min`/`@DecimalMin` and `@Max`/`@DecimalMax` annotations of a property | | `x-jackson-json-include-policy` | `@JsonInclude` policy of a property, e.g. `non_empty` (`none` for no annotation) | | `x-jackson-json-setter-nulls` | `@JsonSetter(nulls = ...)` of a property, e.g. `skip` (`none` for no annotation) | | `x-enum-varnames` | names of the constants of an enum | | `x-enum-descriptions` | Javadoc of the constants of an enum | | `x-parent` | `true` makes a schema the superclass of the schemas extending it with `allOf`, without a discriminator | | `x-discriminator-value` | the discriminator value of a subclass in `@JsonSubTypes` (default: the name of its schema) | | `x-jackson-default-impl` | `defaultImpl` of the `@JsonTypeInfo` of a class with a discriminator | | `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the `defaultImpl` of `@JsonTypeInfo`, without `x-jackson-default-impl` | | `x-codegen-request-body-name` | on an operation or its request body: the name of the body parameter | | `x-apigenie.codegen.controller-name` | on an operation: the API class of the operation, instead of its tag or path | | `x-apigenie.codegen.action-name` | name of the method of an operation, instead of its `operationId` | | `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out, with `skipDisabled` | | `x-internal` | `true` on an operation leaves it out | | `x-operation-extra-annotation` | annotations of the method of an operation (not with `spring-http-interface`) | | `x-version-param` | `true` on a header or query parameter adds it to the `headers` or `params` of the `@RequestMapping` (not with `spring-http-interface`) | | `x-spring-paginated` | `true` replaces the `page`, `size` and `sort` parameters of an operation with a `Pageable`; `false` keeps them even with `autoXSpringPaginated` | | `x-spring-provide-args` | further arguments of the method of an operation, e.g. `['@RequestAttribute("tenant") String tenant']` (not with `spring-http-interface`) | The extensions can live in an overlay for this target instead of in the document, e.g. `spring.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Spring annotations version: 1.0.0 actions: - target: $.paths['/pets'].get update: x-spring-paginated: true - target: $.components.schemas.Pet update: x-class-extra-annotation: ['@com.acme.Audited'] ``` ```sh apigenie-generator server spring openapi.yaml --overlay spring.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_spring.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Base package of the generated code, e.g. `com.example`. API, model and configuration classes are generated in its `api`, `model` and `configuration` sub-packages, unless `apiPackage`, `modelPackage` or `configPackage` are set. ### --package-version Version of the generated project (`artifactVersion`). Defaults to the version of the API. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p delegatePattern=true`. Options given on the command line take precedence over the configuration file. Mapping options take comma-separated lists, e.g. `-p typeMappings=DateTime=java.time.Instant,UUID=String`. Applies to OpenAPI documents only; using it with an AsyncAPI document is an error. ## Configuration Options can also be set in the configuration file, either directly or in an `additionalProperties` section, like in configuration files of openapi-generator. The `asyncapi` key holds the options of the generation from AsyncAPI documents (see [apigenie-generator server spring (AsyncAPI)](/docs/reference/generator/server-spring-asyncapi#configuration)): ```yaml version: '1.0' commands: generate: server_spring: basePackage: com.example delegatePattern: true typeMappings: DateTime: java.time.Instant ``` | Option | Description | | ------ | ----------- | | `library` | Library template: spring-boot (server), spring-cloud (Feign client) or spring-http-interface (HTTP interface client) | | `basePackage` | Base package for generated code | | `invokerPackage` | Alias of basePackage | | `apiPackage` | Package for generated API classes | | `modelPackage` | Package for generated models | | `configPackage` | Package for generated configuration classes | | `groupId` | groupId in the generated pom.xml | | `artifactId` | artifactId in the generated pom.xml | | `artifactVersion` | Artifact version in the generated pom.xml | | `artifactDescription` | Artifact description in the generated pom.xml | | `artifactUrl` | Artifact URL in the generated pom.xml | | `licenseName` | License name in the generated pom.xml | | `licenseUrl` | License URL in the generated pom.xml | | `developerName` | Developer name in the generated pom.xml | | `developerEmail` | Developer email in the generated pom.xml | | `developerOrganization` | Developer organization in the generated pom.xml | | `developerOrganizationUrl` | Developer organization URL in the generated pom.xml | | `scmConnection` | SCM connection in the generated pom.xml | | `scmDeveloperConnection` | SCM developer connection in the generated pom.xml | | `scmUrl` | SCM URL in the generated pom.xml | | `parentGroupId` | groupId of the parent pom | | `parentArtifactId` | artifactId of the parent pom | | `parentVersion` | Version of the parent pom | | `snapshotVersion` | Append -SNAPSHOT to the artifact version | | `title` | Server title, used in property names (defaults to info.title) | | `serverPort` | Server port (defaults to the port of the first server, or 8080) | | `useSpringBoot3` | Generate code for Spring Boot 3 (jakarta); false means Spring Boot 2 | | `useSpringBoot4` | Generate code for Spring Boot 4 | | `useJackson3` | Use Jackson 3 (tools.jackson) instead of Jackson 2; requires useSpringBoot4 | | `optionalAcceptNullable` | Use Optional.ofNullable in fluent setters of Optional properties | | `useJspecify` | Use JSpecify (org.jspecify.annotations.Nullable) for null checks | | `hideGenerationTimestamp` | Omit the generation date from @Generated annotations | | `generationTimestamp` | Timestamp used in @Generated annotations | | `generatorVersion` | Generator version written into generated files | | `dateLibrary` | Date library: java8, java8-localdatetime, joda or legacy | | `documentationProvider` | springdoc, source or none | | `annotationLibrary` | swagger2 or none | | `useSwaggerUI` | Serve Swagger UI | | `interfaceOnly` | Generate only API interfaces, without controllers | | `delegatePattern` | Delegate request handling to XxxApiDelegate interfaces | | `skipDefaultInterface` | Don't generate default implementations of API methods | | `useTags` | Group operations by tag instead of the first path segment | | `requestMappingMode` | Where to put the class-level @RequestMapping: controller, api_interface or none | | `useResponseEntity` | Wrap return types in ResponseEntity | | `useOptional` | Use Optional for optional parameters | | `useSpringController` | Annotate API interfaces with @Controller / @RestController | | `returnSuccessCode` | Return the success code instead of 501 Not Implemented | | `unhandledException` | Declare 'throws Exception' on API methods | | `apiFirst` | Generate supporting files only | | `async` | Return CompletableFuture from API methods | | `reactive` | Generate reactive (WebFlux) code | | `responseWrapper` | Wrapper of return types: CompletableFuture, Callable, Future, ListenableFuture, DeferredResult or a fully qualified class name | | `includeHttpRequestContext` | Add the HTTP request (ServerWebExchange / HttpServletRequest) to API methods | | `singleContentTypes` | Use a single content type per operation | | `sortParamsByRequiredFlag` | Put required parameters before optional ones | | `apiNameSuffix` | Suffix of API class names | | `skipDeprecated` | Don't generate code for deprecated operations | | `skipDisabled` | Don't generate code for operations disabled with x-apigenie | | `autoXSpringPaginated` | Add x-spring-paginated to operations with 'page', 'size' and 'sort' query parameters | | `substituteGenericPagedModel` | Replace schemas of pages of items (a 'content' array and pagination metadata) with PagedModel<T> | | `generateSortValidation` | Validate the sort of Pageable parameters against the enum values of 'sort' parameters | | `generatePageableConstraintValidation` | Validate Pageable parameters against the limits of 'page' and 'size' parameters | | `virtualService` | Generate Virtualan annotations for virtual services | | `useSpringSecurityPreAuthorize` | Generate @PreAuthorize annotations from OAuth2/OpenID Connect scopes | | `springSecurityAuthorityPrefix` | Prefix of the authorities checked by @PreAuthorize annotations | | `implicitHeaders` | Describe header parameters with @Parameters instead of method parameters | | `implicitHeadersRegex` | Regular expression of header parameters described with @Parameters | | `useHttpServiceProxyFactoryInterfacesConfigurator` | Use a HttpServiceProxyFactory in HttpInterfacesAbstractConfigurator (spring-http-interface) | | `clientRegistrationId` | @ClientRegistrationId of API interfaces (spring-http-interface, Spring Boot 4) | | `useFeignClientUrl` | Use the url attribute of @FeignClient (spring-cloud) | | `useFeignClientContextId` | Use the contextId attribute of @FeignClient (spring-cloud) | | `useBeanValidation` | Use Bean Validation annotations | | `performBeanValidation` | Use Hibernate Validator annotations | | `useSpringBuiltInValidation` | Rely on Spring's built-in validation (no @Validated) | | `additionalNotNullAnnotations` | Add @NotNull to required path and body parameters | | `openApiNullable` | Use JsonNullable for nullable properties | | `serializableModel` | Make models implement Serializable | | `useOneOfInterfaces` | Represent oneOf schemas as interfaces | | `useDeductionForOneOfInterfaces` | Deduce oneOf subtypes from their properties | | `generatedConstructorWithRequiredArgs` | Generate constructors with required arguments | | `generateConstructorWithAllArgs` | Generate constructors with all arguments | | `generateBuilders` | Generate builders for models | | `sortModelPropertiesByRequiredFlag` | Put required properties first in constructors | | `enumPropertyNaming` | Naming of enum constants: MACRO_CASE, original, legacy, camelCase, PascalCase, snake_case or UPPERCASE | | `useEnumCaseInsensitive` | Match enum values ignoring case | | `withXml` | Add XML annotations to models | | `useSealed` | Use sealed classes and interfaces (Java 17+) | | `containerDefaultToNull` | Initialize containers with null instead of empty ones | | `legacyDiscriminatorBehavior` | Use the legacy discriminator behavior | | `disableDiscriminatorJsonIgnoreProperties` | Don't ignore discriminator properties during serialization | | `modelNamePrefix` | Prefix of model names | | `camelCaseDollarSign` | Keep the case of the first letter after a leading $ in names | | `useEnumValueInterface` | Make enums implement the ValuedEnum interface | | `generateJsonIncludeAnnotations` | Add @JsonInclude annotations to model properties | | `optionalNonNullPropertyJsonInclude` | @JsonInclude policy of optional, non-nullable properties: NON_NULL, NON_EMPTY, NON_DEFAULT or NONE | | `generateJsonSetterNullsAnnotations` | Add @JsonSetter(nulls = ...) annotations to optional, non-nullable properties | | `optionalNonNullPropertyJsonSetterNulls` | @JsonSetter(nulls = ...) mode of optional, non-nullable properties: SKIP or FAIL | | `modelNameSuffix` | Suffix of model names | | `additionalModelTypeAnnotations` | Annotations added to model classes, separated by semicolons | | `additionalEnumTypeAnnotations` | Annotations added to enum classes, separated by semicolons | | `additionalOneOfTypeAnnotations` | Annotations added to oneOf interfaces, separated by semicolons | | `typeInfoDefaultImpls` | Classes used as defaultImpl of @JsonTypeInfo, by schema name (Schema=Class,...) | | `typeMappings` | Mappings of OpenAPI types to Java types (type=JavaType,...) | | `importMappings` | Mappings of Java types to imports (Type=package.Type,...) | | `schemaMappings` | Mappings of schemas to existing classes (Schema=package.Class,...) | | `modelNameMappings` | Mappings of schema names to model names (Schema=Model,...) | | `nameMappings` | Mappings of property names to Java names (name=javaName,...) | | `parameterNameMappings` | Mappings of parameter names to Java names (name=javaName,...) | | `enumNameMappings` | Mappings of enum values to constant names (value=NAME,...) | | `contentMediaTypeAsBinary` | Map strings with a non-JSON contentMediaType to binary types, like format: binary | | `useImplicitOneOfDiscriminator` | Dispatch oneOf schemas without a discriminator on a property whose value is constant in every member | | `jsonAccessForReadWriteOnly` | Read readOnly properties from JSON only and write writeOnly ones only (@JsonProperty access) | --- # client csharp (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/client-csharp-asyncapi Generate a C# Kafka client library for the Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator client csharp [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator client csharp` generates from OpenAPI documents (see [apigenie-generator client csharp](/docs/reference/generator/client-csharp)) and from AsyncAPI 3.x documents. It tells them apart by their `openapi` or `asyncapi` field. Documents that reference other files are bundled first. From an AsyncAPI document it generates a .NET 8 class library (`Microsoft.NET.Sdk`) on the Kafka client [Confluent.Kafka](https://github.com/confluentinc/confluent-kafka-dotnet) (Apache-2.0, pinned at 2.15.1). It also references `Microsoft.Extensions.Hosting.Abstractions` and `Microsoft.Extensions.Options.ConfigurationExtensions`. By default the client implements the counterpart of the application the document describes: it sends the messages of the application's `receive` operations, and receives the messages of its `send` operations. Set the `perspective` option to `application` to generate the application's side instead. The two sides talk to each other, and to the application of [apigenie-generator server aspnet (AsyncAPI)](/docs/reference/generator/server-aspnet-asyncapi). The generator writes the Kafka channels: - the channels of servers with the `kafka` or `kafka-secure` protocol; - in documents without servers, the channels with a Kafka binding. Channels of other protocols are skipped and reported as info. ```csharp // the application's side of the Carpet Express Event Platform builder.Services.AddCarpetExpressKafkaClient(builder.Configuration); public sealed class Shipping(CarpetExpressKafkaClient kafka) { public async Task DeliveredAsync(ShipmentDelivered message, CancellationToken cancellationToken) { await kafka.ShipmentEvents.SendShipmentDeliveredAsync(message, cancellationToken); } public async Task<Uri?> LabelAsync(RenderLabel request, CancellationToken cancellationToken) { // sends the request with a new correlation id and waits for its reply var reply = await kafka.LabelRenderRequests.RequestRenderLabelAsync(request, TimeSpan.FromSeconds(10), cancellationToken); return reply is LabelRendered rendered ? rendered.Payload.Url : null; } } ``` The project's root namespace (and assembly name) is `root_namespace`, else `--package-name`, else the document's title in PascalCase followed by `.Client`. The class names start with the namespace's stem: its last segment, or the segment before a last `Client`, `Clients` or `Sdk`. For example, `CarpetExpress.Client` gives `CarpetExpressKafkaClient`. | File | Contents | | ---- | -------- | | `Models/*.cs`, `Serialization/*.cs`, `Messages/*.cs` | the payload types, the JSON context and the message records, as [apigenie-generator server aspnet (AsyncAPI)](/docs/reference/generator/server-aspnet-asyncapi) writes them | | `Kafka/<Name>KafkaClient.cs` | the client: a producer per channel it sends to | | `Kafka/<Channel>Producer.cs` | the `Send…Async` and `Request…Async` methods of a channel | | `Kafka/*Handler.cs`, `Kafka/*Consumer.cs` | the handler interfaces and consumers of the received operations | | `Kafka/<Name>KafkaClientOptions.cs` | the options, with the topics and ids | | `Kafka/<Name>KafkaClientServiceCollectionExtensions.cs`, `Kafka/<Name>KafkaClientBuilder.cs` | `Add<Name>KafkaClient` and the registration of handlers | | `Infrastructure/*.cs` | the Kafka runtime (producer, consumer service, reply consumer, headers) and the validation of messages | | `<RootNamespace>.csproj`, `README.md` | the project | - **Sent operations.** The client has a producer per channel, e.g. `client.ShipmentEvents`. A producer's `Send<Message>Async` validates a message and completes once the brokers have it. It writes the constant headers (e.g. `ce_type`), the key and the topic from the bindings. - **Request/reply.** A request/reply operation gets `Request<Message>Async(message, cancellationToken)` and an overload with a timeout. - It sends the request with its correlation id, or with a new Guid when the request has none. - It returns the reply: the reply message, or an interface of the replies (`IRequestLabelRenderReply`) when there are several. - It throws `TimeoutException` when no reply comes within the timeout (by default `ReplyTimeout`), and `OperationCanceledException` when the token is cancelled. One consumer per client reads the replies of every request. It has no consumer group, and starts with the first request reading from a minute before, so no reply is missed. - **Received operations.** They have handler interfaces and consumers, as in [apigenie-generator server aspnet (AsyncAPI)](/docs/reference/generator/server-aspnet-asyncapi): - the dispatch by a payload property or a header, with `OnUnknownAsync` for other records; - validation; - commits once the handler returns, and retries after `RetryDelay` when it throws; - replies sent with the request's correlation id. Register a handler with the builder: its consumer runs as a hosted service of the host. ```csharp // the counterpart's side: consume the shipment events and answer label requests builder.Services.AddCarpetExpressKafkaClient(builder.Configuration) .AddPublishShipmentEventsHandler<ShipmentEventsHandler>() .AddRequestLabelRenderHandler<LabelRenderer>(); ``` `Add<Name>KafkaClient(configuration, configure)` does the following: - binds the options to the `AsyncApi:Kafka` section of `configuration` when it is given, then applies `configure`; - registers the client and its producers as singletons; - returns the builder. Without dependency injection, `new CarpetExpressKafkaClient(options)` sends and requests; dispose it when done. ### Settings The options are those of [apigenie-generator server aspnet (AsyncAPI)](/docs/reference/generator/server-aspnet-asyncapi#settings): `BootstrapServers`, `ClientId`, `ReplyTimeout`, `RetryDelay`, `Topics`, `Operations` (the group and client ids of received operations), `Consumer` and `Producer`. A client that sends requests also has `ReplyClientId`, the client id of its reply consumer. It defaults to the client id of the first request/reply operation's Kafka binding. Without a value in the binding, the default is `<namespace stem in kebab case>-<operation>`. Not supported, and reported as warnings: - Avro payloads, and payloads of other non-JSON schema formats; - reply addresses given by runtime expressions; - Kafka topics with parameters. Security (SASL/SCRAM, mTLS) is not configured: set the librdkafka properties in `Consumer` and `Producer`. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | | `x-enum-varnames`, `x-enum-names` | names of the members of an enum, in PascalCase | | `x-enum-descriptions`, `x-enumDescriptions` | documentation comments of the members of an enum | The extensions can live in an overlay for this target instead of in the document, e.g. `csharp.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: C# names version: 1.0.0 actions: - target: $.components.schemas.OrderStatus update: x-enum-varnames: [Created, Paid, Shipped] - target: $.operations.publishAudit update: x-apigenie: codegen: disabled: true ``` ```sh apigenie-generator client csharp asyncapi.yaml --overlay csharp.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_csharp.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Root namespace of the generated code, e.g. `Acme.Events.Client`. Defaults to the title of the API in PascalCase, followed by `.Client`. ### --package-version Version of the project. Defaults to the version of the API when it is a .NET version, else 1.0.0. ### -p, --property Applies to OpenAPI documents only. Using it with an AsyncAPI document is an error. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator client csharp`. Its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: client_csharp: asyncapi: root_namespace: Acme.Events.Client validation: true perspective: counterpart ``` | Option | Description | | ------ | ----------- | | `root_namespace` | Root namespace and assembly name of the project (default: `--package-name`, or the title followed by `.Client`) | | `validation` | `true` (default): validate messages by their DataAnnotations before sending and after receiving them | | `perspective` | `counterpart` (default): the counterpart of the described application; `application`: the application itself | --- # client elixir (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/client-elixir-asyncapi Generate an Elixir client library of the WebSocket and Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator client elixir [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator client elixir` generates from OpenAPI documents (see [apigenie-generator client elixir](/docs/reference/generator/client-elixir)) and from AsyncAPI 3.x documents, telling them apart by their `openapi` or `asyncapi` field. Documents referencing other files are bundled first. From an AsyncAPI document it generates a Mix library (Elixir 1.18 or later) for the counterpart of the described application, by default: it sends what the application receives and receives what it sends. The WebSocket channels connect on [Mint.WebSocket](https://hexdocs.pm/mint_web_socket), the Kafka channels use [brod](https://github.com/kafka4beam/brod). Set the `perspective` option to `application` for the application's Kafka library; WebSocket channels are connected to in the counterpart perspective only. With Kafka channels the library depends on [snappyer](https://hex.pm/packages/snappyer), a NIF that needs a C++ compiler, so that brod reads Snappy-compressed records (the default of Go's franz-go producers); LZ4 and zstd records need `lz4b` or `ezstd` added by hand. The generator writes the channels of servers with the `ws`/`wss` and `kafka`/`kafka-secure` protocols, and channels with a WebSocket or Kafka binding in documents without servers. Channels of other protocols (MQTT, AMQP, HTTP) are skipped and reported as info. ```elixir {:ok, socket} = CarpetExpressEvents.WebSocket.TrackingStream.connect("shp_8Yb2Kk1qP0wZcT3uXr5sN7aD", url: "ws://localhost:4000", query: [include: ["location"]], handler: MyApp.Tracking ) {:ok, pickup} = CarpetExpressEvents.WebSocket.PickupDesk.connect(url: "ws://localhost:4000") {:ok, %CarpetExpressEvents.Models.PickupConfirmed{}} = CarpetExpressEvents.WebSocket.PickupDesk.request_pickup(pickup, request) ``` The library, for the application `carpet_express_events` (named by `app`, `--package-name` or the document's title): | File | Contents | | ---- | -------- | | `lib/<app>/models/*.ex` | the structs of the schemas the payloads use, decoded and encoded by `decode.ex` and `encode.ex` | | `lib/<app>/websocket/<channel>.ex` | per WebSocket channel, `connect`, and the functions sending its messages and requests | | `lib/<app>/websocket/<channel>/handler.ex` | the behaviour of the callbacks receiving what the server sends on the channel | | `lib/<app>/messages/*.ex` | per Kafka message, a struct (`payload`, `headers`, `key`) and the description of its record | | `lib/<app>/kafka.ex` | the supervisor of the Kafka processes and a function per topic | | `lib/<app>/kafka/<operation>.ex` | per sent operation, its send (and request) functions; per received operation, the behaviour of its callbacks | | `lib/<app>/kafka/<operation>_consumer.ex` | per received operation, its group subscriber | | `lib/<app>/runtime/` | the WebSocket connection (`ChannelSocket`) and the Kafka runtime | | `mix.exs`, `.formatter.exs`, `.credo.exs` | the project, a library without application | - **WebSocket channels, two framings.** `connect` (with the channel's address parameters first) opens a connection to the server of `:url` and joins the channel. With `framing: :phoenix`, the default, it speaks the Phoenix Channels protocol (version 2) of `apigenie-generator server phoenix`: the socket `/socket/websocket`, the channel's topic (the address segments joined by `:`), the ws binding's `:query` as the join payload, a heartbeat every 30 seconds. With `framing: :plain` it connects to the channel's address, the query in the URL, and the frames are the messages of the AsyncAPI document. `send_<message>` sends a message; for a request, the function named after it waits for the reply: the reply to its ref in the Phoenix framing, the next message with the request's correlation id in the plain one. What the server sends is decoded into its struct and given to the callbacks of the channel's `Handler` behaviour (the `:handler` option), or sent to the calling process. - **Kafka.** Add `{CarpetExpressEvents.Kafka, brokers: "localhost:9092"}` to your supervision tree. `send_<message>/1` checks a message's headers and returns once the brokers have the record; `request_<message>/2` waits for the reply on the reply topic, matched by the correlation id (generated when the request has none; `:timeout`, 30 seconds by default). Each received operation has a consumer group (`:brod_group_subscriber_v2`) handing its records to the behaviour's callbacks, as `server phoenix` does: told apart by a constant header (`ce_type`), payload property or decoding, with the optional `on_unknown/1` and `on_invalid/2`; a replier's callback returns its reply, sent with the request's correlation id. Group ids come from the `groupId` schemas of the Kafka operation bindings, else `<app in kebab case>-<operation>`, and the consumer modules' configuration overrides them (`config :app, <Consumer>, group_id: ..., offset_reset_policy: :earliest`). Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats, reply addresses given by runtime expressions, Kafka topics with parameters, and WebSocket addresses whose segments mix text and parameters. Security is not generated: the WebSocket connections carry no credentials (the streamToken is a query parameter) and the Kafka clients connect without SASL or TLS. The client ids of the Kafka bindings are not applied, the headers schema of the ws binding is not sent, and the models decode by type. ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### -o, --output Directory where the library is generated. ### --package-name Name of the Mix application, converted to snake_case (`carpet-express-events` gives `carpet_express_events`, root module `CarpetExpressEvents`); defaults to the title of the API. Its kebab case is the base of default group ids. ### --package-version Version of the Mix project; 0.1.0 by default. ### -p, --property Applies to OpenAPI documents only; using it with an AsyncAPI document is an error. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator client elixir`; its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: client_elixir: asyncapi: app: carpet_express_events perspective: counterpart ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `app` | string | from `--package-name` or the title | Name of the Mix application (snake_case) | | `namespace` | string | the application in PascalCase | Root module of the generated modules | | `perspective` | `counterpart` \| `application` | `counterpart` | The counterpart of the described application, or the application itself | | `field_prefix` | string | `field_` | Prefix of struct fields whose property names give no valid name | | `module_overrides` | map | | Module names of payload schemas, e.g. `ShipmentId: ShipmentRef` | | `dev_deps` | boolean | `true` | credo and dialyxir as dependencies of the dev and test environments | --- # client go (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/client-go-asyncapi Generate a Go Kafka client package for the Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator client go [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator client go` generates from OpenAPI documents (see [apigenie-generator client go](/docs/reference/generator/client-go)) and from AsyncAPI 3.x documents, telling them apart by their `openapi` or `asyncapi` field. Documents referencing other files are bundled first. From an AsyncAPI document it generates a Go module (Go 1.22) with one package, using the Kafka client [franz-go](https://github.com/twmb/franz-go) (`github.com/twmb/franz-go/pkg/kgo`, pinned at v1.18.1 in `go.mod`, with a complete `go.sum`). By default the client is the counterpart of the application the document describes: it sends the messages of the application's `receive` operations and receives those of its `send` operations. Set the `perspective` option to `application` to generate the described application's side instead. [apigenie-generator server go](/docs/reference/generator/server-go-asyncapi) generates the other side as an application with its own `main.go`. The generator writes the Kafka channels: the channels of servers with the `kafka` or `kafka-secure` protocol, and channels with a Kafka binding in documents without servers. Channels of other protocols are skipped and reported as info. With `perspective: application`, the client of the Carpet Express Event Platform sends the shipment events and requests labels: ```go client, err := events.NewClient(events.Options{ Brokers: []string{"localhost:9092"}, ClientID: "label-printer", }) if err != nil { log.Fatal(err) } defer client.Close() // a sent operation with several messages takes any of them err = client.SendPublishShipmentEvents(ctx, delivered) // request/reply: waits for the reply with the request's correlation id reply, err := client.RequestLabelRender(ctx, request) switch reply := reply.(type) { case events.LabelRendered: fmt.Println(reply.Payload.URL) case events.LabelRenderFailed: fmt.Println(reply.Payload.Title) } ``` In the default perspective, it receives them and renders the labels: ```go type labelFarm struct{} func (labelFarm) OnRenderLabel(ctx context.Context, message events.RenderLabel) (events.RequestLabelRenderReply, error) { var reply events.LabelRendered reply.Key = message.Key reply.Payload = render(message.Payload) return reply, nil // sent to TopicLabelRenderResults with the request's correlation id } func (labelFarm) OnUnknown(ctx context.Context, record *kgo.Record) error { return nil } // received operations are consumed in the background until Close err = client.ListenRequestLabelRender(labelFarm{}) err = client.ListenPublishShipmentEvents(shipmentTracker{}) ``` The package is named by `package_name`, `--package-name` or the document's title (letters and digits, lower case): | File | Contents | | ---- | -------- | | `types.gen.go` | the types of the schemas the payloads use: structs with `json` tags, enums, and unions as a struct whose `Value` holds one variant of a sealed interface; each with a `Validate` method | | `messages.gen.go` | a struct per message (`Key`, `Headers`, `Payload`), its headers struct and header constants, and the code writing, reading and validating records | | `kafka.gen.go` | the server hosts, topics, group and client ids, the handler and message interfaces, and `Client` | | `runtime_*.gen.go` | the Kafka runtime (consumers, requesters, headers), validation, `Nullable[T]`, `Date` | | `go.mod`, `go.sum` | the module, requiring franz-go | - **The client.** `NewClient(options)` creates the producer and, for every operation with a reply, one consumer of its reply topic that all of that operation's requests share. `Close` stops the listeners, commits the offsets of the records they handled, leaves their consumer groups and closes the clients. `Options` takes the brokers, `ClientID` (replacing the generated `ProducerClientID`), `ClientOptions` (franz-go options added to every client, e.g. `kgo.SASL`, `kgo.DialTLSConfig` or `kgo.AllowAutoTopicCreation`), `GroupIDs` (consumer groups by operation id), `OnError` and `ReplyTimeout`. - **Sent operations.** `Send<Operation>(ctx, message)` validates the message, writes its key, its headers (constant headers such as the `ce_specversion` and `ce_type` of CloudEvents included) and its JSON payload to the operation's topic, and returns once the brokers have it. With several messages, it takes an interface (`PublishShipmentEventsMessage`) that each of them implements. - **Request/reply** with a reply channel: `Request<Operation>(ctx, message)` sets a new correlation id unless the message has one, sends the request and waits for the reply with that id until the context is done (`Options.ReplyTimeout`, 30 seconds by default, when the context has no deadline). It returns the reply message, or an interface of the reply messages (`RequestLabelRenderReply`) when there are several. An operation named after the verb isn't repeated: `requestLabelRender` gives `RequestLabelRender`. - **Received operations.** `Listen<Operation>(handler)` consumes the operation's topic in the background, in its consumer group, until `Close`. The handler interface (`PublishShipmentEventsHandler`) has an `On<Message>` method per message and `OnUnknown`. Several messages are told apart by a payload property or a header with a distinct constant value (e.g. `type` or `ce_type`); otherwise the first message whose payload decodes strictly and is valid is chosen. Records that aren't valid messages, and handler errors, go to `Options.OnError` (logged by default). An operation with a reply gets `On<Message>(ctx, message) (Reply, error)`: the reply is sent to the reply topic with the request's correlation id copied. Group and client ids come from the Kafka operation bindings as for [apigenie-generator server go](/docs/reference/generator/server-go-asyncapi#description). Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats (the messages are skipped, and operations left without a message too), reply addresses given by runtime expressions, and Kafka topics with parameters. Security (SASL/SCRAM, mTLS) is not configured: pass `kgo.SASL` or `kgo.DialTLSConfig` in `Options.ClientOptions`. `Validate` doesn't check `not`, `if`/`then`/`else` and `dependencies`, tuples are `[]any` (only their item count is checked), and `contentEncoding: base64` strings stay strings. ## Extensions | Extension | Effect | | --------- | ------ | | `x-go-name` | name of the type of a schema, or of the field of a property | | `x-go-type` | Go type of a schema, e.g. `decimal.Decimal`; with `x-go-type-import: {path: ...}` for its package | | `x-go-type-skip-optional-pointer` | the optional property isn't a pointer | | `x-go-json-ignore` | the field gets the tag `json:"-"` | | `x-omitempty` | `true` or `false` overrides the `omitempty` of the field | | `x-enum-varnames`, `x-enumNames` | names of the enum constants | | `x-enum-descriptions` | comments of the enum constants | | `x-apigenie.codegen.name` | name of the type of a component schema, when it has no `x-go-name` | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `go.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Go types version: 1.0.0 actions: - target: $.components.schemas.Order update: x-go-name: PurchaseOrder - target: $.components.schemas.Order.properties.total update: x-go-type: decimal.Decimal x-go-type-import: path: github.com/shopspring/decimal ``` ```sh apigenie-generator client go asyncapi.yaml --overlay go.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_go.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the module is generated. ### --package-name Name of the Go package, as letters and digits in lower case (`carpet-express-events` gives `carpetexpressevents`); defaults to the title of the API (`events` when it gives no valid name). Its kebab case is the base of default group and client ids. ### --package-version Ignored: Go modules have no version in `go.mod`. ### -p, --property Applies to OpenAPI documents only; using it with an AsyncAPI document is an error. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator client go`; its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: client_go: asyncapi: module_path: github.com/acme/events package_name: events initialisms: [SKU] perspective: counterpart ``` | Option | Description | | ------ | ----------- | | `module_path` | Module path of `go.mod` (default: `example.com/<package>`) | | `package_name` | Name of the package, a lower-case Go identifier (default: from `--package-name` or the title) | | `initialisms` | Words written in capitals in identifiers, besides the common ones (`ID`, `URL`, ...) | | `perspective` | `counterpart` (default): the counterpart of the described application; `application`: the described application, sending what it sends | --- # client java (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/client-java-asyncapi Generate a Java Kafka client library for the Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator client java [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator client java` generates from OpenAPI documents (see [apigenie-generator client java](/docs/reference/generator/client-java)) and from AsyncAPI 3.x documents, telling them apart by their `openapi` or `asyncapi` field. Documents referencing other files are bundled first. From an AsyncAPI document it generates a Maven project of a plain Java 21 library on the Apache Kafka client (`kafka-clients` 4.1.0), without Spring. Payloads are JSON (Jackson) and are validated with Bean Validation. By default the client is the counterpart of the application the document describes: it sends the messages of the application's `receive` operations and receives those of its `send` operations. Set the `perspective` option to `application` to generate the described application's side instead. [apigenie-generator server spring](/docs/reference/generator/server-spring-asyncapi) generates the other side as a Spring Boot application. The two share their records, dispatch and header rules, so they exchange the same records. The generator writes the Kafka channels: the channels of servers with the `kafka` or `kafka-secure` protocol, and channels with a Kafka binding in documents without servers. Channels of other protocols are skipped and reported as info. With `perspective: application`, the client of the Carpet Express Event Platform sends the shipment events and requests labels: ```java var options = new CarpetExpressEventsKafkaClientOptions() .bootstrapServers("localhost:9092") .replyTimeout(Duration.ofSeconds(10)); try (var client = new CarpetExpressEventsKafkaClient(options)) { client.sendShipmentDelivered(delivered).get(); // request/reply: completes with the reply carrying the request's correlation id switch (client.requestLabelRender(request).get()) { case LabelRendered rendered -> System.out.println(rendered.payload().url()); case LabelRenderFailed failed -> System.out.println(failed.payload().title()); } } ``` In the default perspective, it receives them and renders the labels: ```java class LabelFarm implements RequestLabelRenderHandler { @Override public RequestLabelRenderReply onRenderLabel(RenderLabel message) { // sent to the labelRenderResults topic with the request's correlation id return new LabelRendered(message.key(), render(message.payload()), null); } } KafkaSubscription subscription = client.listenRequestLabelRender(new LabelFarm()); subscription.assigned().get(); // once the consumer group assigned partitions ``` The artifact id is `--package-name`, or the document's title followed by `-client`; the base package is `base_package`, or the title's (e.g. `com.example.carpetexpresseventplatform.client`): | Package | Contents | | ------- | -------- | | `model` | the records, enums and sealed interfaces of the schemas the payloads use, with Jackson and Bean Validation annotations | | `messages` | a record per message (`key`, `payload`, `headers`), its headers record with the constant headers (`CE_TYPE`, ...), and a sealed interface per reply | | `kafka` | `<Name>KafkaClient`, `<Name>KafkaClientOptions`, a handler interface per received operation, and the runtime: `KafkaJson`, `KafkaSubscription`, `KafkaReplies` | - **The client.** `new <Name>KafkaClient(options)` creates a producer per sending and replying operation, with the operation's client id; `close()` closes the subscriptions, the reply consumer and the producers. The options default to the document's server, topics, client ids and group ids. They also take the reply client id and timeout (30 seconds by default), and producer and consumer properties that replace the client's (security settings, for example). - **Sent operations.** `send<Message>(message)` validates the message, writes its key, its headers (constant headers such as the `ce_specversion` and `ce_type` of CloudEvents included) and its JSON payload to the operation's topic. It returns a `CompletableFuture<RecordMetadata>`. - **Request/reply** with a reply channel: `<operation>(message)` and `<operation>(message, timeout)` set a new correlation id unless the message has one (in its header, at its payload pointer, or in `kafka_correlationId`) and send the request. They return a `CompletableFuture` of the reply, or of the sealed interface of the reply messages (`RequestLabelRenderReply`); the future fails with a `TimeoutException` when no reply arrives in time. One consumer, without a consumer group, receives the replies of all requests and matches them by correlation id. - **Received operations.** `listen<Operation>(handler)` consumes the operation's topic on a virtual thread, in its consumer group (new groups start at the first record), until the returned `KafkaSubscription` or the client is closed. It commits the offsets of the records it handled. - The handler interface (`PublishShipmentEventsHandler`) has an `on<Message>` method per message, a default `onUnknown` and a default `onError`. - Several messages are told apart by a header or payload property with a distinct constant value (e.g. `ce_type`). Otherwise the first message whose payload reads strictly and is valid is chosen. - Records that aren't valid messages, and exceptions of the handler, go to `onError`. - A reply returned by the handler is sent to the reply topic with the request's correlation id. Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats (the messages are skipped, and operations left without a message too), reply addresses given by runtime expressions, and Kafka topics with parameters. Security (SASL/SCRAM, mTLS) is not configured: set `security.protocol`, `sasl.*` and `ssl.*` with `producerProperty` and `consumerProperty`. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the class of a schema | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `java.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Java names version: 1.0.0 actions: - target: $.components.schemas.Order update: x-apigenie: codegen: name: PurchaseOrder ``` ```sh apigenie-generator client java asyncapi.yaml --overlay java.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_java.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the Maven project is generated. ### --package-name The artifact id, by default the document's title followed by `-client`. Its base, without `-client`, is the base of default group and client ids. ### --package-version Version of the artifact; defaults to the version of the API. ### --library, -p, --property Apply to OpenAPI documents only; using them with an AsyncAPI document is an error. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator client java`; its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: client_java: asyncapi: base_package: com.acme.events group_id: com.acme perspective: counterpart ``` | Option | Description | | ------ | ----------- | | `base_package` | Package of the generated code (default: from the title, followed by `.client`) | | `group_id` | Maven `groupId` (default: the base package) | | `java_version` | Java release, from 21 (default: 21; the client needs virtual threads) | | `bean_validation` | Bean Validation annotations on the models, and validation of sent and received messages (default: `true`) | | `perspective` | `counterpart` (default): the counterpart of the described application; `application`: the described application, sending what it sends | --- # client kafkajs Source: https://apigenie.pl/docs/reference/generator/client-kafkajs Generate a TypeScript (Node.js) Kafka client of an AsyncAPI document, on KafkaJS. ## Usage ```sh apigenie-generator client kafkajs [...OPTIONS] INPUT_FILE ``` ## Description The input must be an AsyncAPI 3.x document. Documents referencing other files are bundled first. The generator handles the Kafka channels: channels of a server whose protocol is `kafka` or `kafka-secure`, or with a `kafka` binding when the document has no servers. Other channels are skipped with an info message. The generated code runs on Node.js with [KafkaJS](https://kafka.js.org) 2.2.4, its only dependency. An AsyncAPI document describes an application. By default the client is its counterpart: it sends the messages of `receive` operations and receives the messages of `send` operations. Set the `perspective` option to `application` to generate the application's side instead. ```ts import { KafkaClient } from "order-service-kafka-client"; const client = new KafkaClient({ brokers: ["localhost:9092"] }); const reply = await client.handleOrderCommands.sendPlaceOrderMessage({ payload: { type: "place", requestId: crypto.randomUUID(), items: [{ sku: "ABC-0001", quantity: 1 }] }, }); await client.listenPublishStockLevels({ onStockLevelMessage: (message) => console.log(message.payload.sku), }); await client.close(); ``` `KafkaClient` has `connect()` (the senders connect on first use otherwise), a sender property per sent operation, a `listen<Operation>(handler)` method per received operation, which resolves once its consumer joined its group, and `close()`. Every sent operation gets a sender property with a `send<Message>(message)` method per message. It validates the message, sets the constant headers (e.g. `ce_type`) and resolves once the brokers have it. A message is `{ key?, headers, payload }`, with the headers under their names on the wire (`ce_id`). The method of an operation with a `reply` sends the request with a new correlation id unless it has one, and resolves with the reply: the reply message, or `{ name, message }` when there are several. It rejects with `ReplyTimeoutError` when no reply arrives in time (`{ timeoutMs }`, `KafkaOptions.replyTimeoutMs`, 30 seconds by default). Every received operation gets a handler interface: `on<Message>(message, record)` for each of its messages and an optional `onUnknown(record)`. Records are matched to messages by their `ce_type` (or other constant) header, by a constant payload property, or, failing both, by the first message whose payload is valid. The handlers of an operation with a `reply` return the reply (or `undefined` for none), which is sent with the request's correlation id. Invalid records are reported to `KafkaOptions.onError` and skipped. A handler that throws is reported too and the record is retried (KafkaJS doesn't commit it). Payloads and headers are validated against their schemas by generated functions without dependencies; invalid messages throw `MessageValidationError`, whose `issues` point into `/headers` or `/payload`. `KafkaOptions` takes the `brokers`, the producer's `clientId`, `config` (KafkaJS options for every client: `ssl`, `sasl`, `retry`, `logLevel`, ...), `groupIds` replacing the generated consumer groups, `fromBeginning` (true by default) and `onError`. The generated code creates no topics. The package contains `src/types.ts` (payload types), `src/messages.ts` (message and header interfaces), `src/kafka.ts` (servers, topics, ids, senders, handlers and the `KafkaClient`), `src/codecs.ts` (validators and the descriptions of the messages the runtime works with), `src/runtime.ts`, `src/validation_runtime.ts` and `src/index.ts`, with `package.json` and `tsconfig.json`. Not supported, and reported as warnings: Avro payloads, topics with parameters, replies at runtime-expression addresses, and replies without correlation ids. No security code is generated (pass KafkaJS's `ssl` and `sasl` in `KafkaOptions.config`). [`apigenie-generator server kafkajs`](/docs/reference/generator/server-kafkajs) generates the other side on the same runtime. The design is ADR-120. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the type of a component schema | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `kafkajs.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: TypeScript names version: 1.0.0 actions: - target: $.components.schemas.Order update: x-apigenie: codegen: name: PurchaseOrder - target: $.operations.publishAudit update: x-apigenie: codegen: disabled: true ``` ```sh apigenie-generator client kafkajs asyncapi.yaml --overlay kafkajs.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_kafkajs.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the generated package. Defaults to the title of the API followed by `-kafka-client`. It is also the base of the generated consumer group and client ids. ### --package-version Version of the generated package. Defaults to the version of the API. ## Configuration ```yaml version: '1.0' commands: generate: client_kafkajs: layout: package import_extension: .js perspective: counterpart validation: true ``` | Option | Description | | ------ | ----------- | | `layout` | `package` (default): an npm package with sources in `src/`; `sources`: the sources only | | `import_extension` | Extension of relative imports: `.js` (default, Node.js) or an empty string (bundlers) | | `perspective` | `counterpart` (default): the counterpart of the described application; `application`: the application itself | | `validation` | `true` (default): validate payloads against their schemas; `false`: check the headers only | Unknown options are errors. --- # client python (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/client-python-asyncapi Generate a Python asyncio Kafka client package for the Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator client python [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator client python` generates from OpenAPI documents (see [apigenie-generator client python](/docs/reference/generator/client-python)) and from AsyncAPI 3.x documents, telling them apart by their `openapi` or `asyncapi` field. Documents referencing other files are bundled first. From an AsyncAPI document it generates a Python package (Python 3.10 or later) on [aiokafka](https://github.com/aio-libs/aiokafka), with [Pydantic](https://docs.pydantic.dev) 2 models of the payloads: the models of [apigenie-generator server faststream](/docs/reference/generator/server-faststream). By default the client is the counterpart of the application the document describes: it sends the messages of the application's `receive` operations and receives those of its `send` operations. Set the `perspective` option to `application` to generate the described application's side instead. The generator writes the Kafka channels: the channels of servers with the `kafka` or `kafka-secure` protocol, and channels with a Kafka binding in documents without servers. Channels of other protocols are skipped and reported as info. With `perspective: application`, the client of the Carpet Express Event Platform sends the shipment events and requests labels: ```python options = CarpetExpressEventsKafkaClientOptions(bootstrap_servers="localhost:9092") async with CarpetExpressEventsKafkaClient(options) as client: await client.send_shipment_delivered(delivered) # request/reply: returns the reply carrying the request's correlation id reply = await client.request_label_render(request, timeout=10.0) if isinstance(reply, messages.LabelRendered): print(reply.payload.url) ``` In the default perspective, it receives them and renders the labels: ```python class LabelFarm(RequestLabelRenderHandler): async def on_render_label(self, message: messages.RenderLabel) -> messages.RequestLabelRenderReply | None: # sent to the labelRenderResults topic with the request's correlation id return messages.LabelRendered(payload=render(message.payload), key=message.key) subscription = await client.listen_request_label_render(LabelFarm()) await subscription.assigned() # once the consumer group assigned partitions ``` The package is the snake case of `--package-name`, or of the document's title followed by `_client`; the distribution name is its kebab case: | File | Contents | | ---- | -------- | | `models.py`, `_models.py` | the Pydantic models of the payloads | | `messages.py` | a dataclass per message (`payload`, `headers`, `key`), its headers model with the constant headers (`CE_TYPE`, ...), and a type alias per reply union | | `client.py` | `<Name>KafkaClient`, `<Name>KafkaClientOptions` and a handler class per received operation | | `_kafka.py`, `_runtime.py` | the Kafka runtime (records, subscriptions, replies) and the support of headers and correlation ids | | `pyproject.toml` | the distribution, requiring aiokafka and Pydantic | - **The client.** It is an async context manager; `start()` starts a producer per sending and replying operation, with the operation's client id, and `close()` stops the subscriptions, the reply consumer and the producers. The options default to the document's server, topics, client ids and group ids. They also take the reply client id and timeout (30 seconds), and keyword arguments of the aiokafka producers and consumers that replace the client's (security settings, for example). - **Sent operations.** `send_<message>(message)` writes the message's key, its headers (constant headers such as the `ce_specversion` and `ce_type` of CloudEvents included) and its JSON payload to the operation's topic, and returns once the broker acknowledged it. - **Request/reply** with a reply channel: `request_<operation>(message, *, timeout=None)` sets a new correlation id unless the message has one (in its header, at its payload pointer, or in `kafka_correlationId`), sends the request and returns the reply, or raises `TimeoutError`. With several reply messages, the reply is one of them. One consumer, without a consumer group, receives the replies of all requests and matches them by correlation id. An operation named after the verb isn't repeated: `requestLabelRender` gives `request_label_render`. - **Received operations.** `listen_<operation>(handler)` consumes the operation's topic in a task, in its consumer group (new groups start at the first record), until the returned subscription or the client is closed. It commits the offsets of the records it handled. - The handler class (`PublishShipmentEventsHandler`) has an abstract `on_<message>` method per message, an `on_unknown` and an `on_error`. - Several messages are told apart by a header or payload property with a distinct constant value (e.g. `ce_type`). Otherwise the first message whose payload validates without unknown properties is chosen. - Records that aren't valid messages, and exceptions of the handler, go to `on_error`. - A reply returned by the handler is sent to the reply topic with the request's correlation id. Messages are validated by their models when they are built and when they are received; headers models ignore the record's other headers. The package passes `mypy --strict`. Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats (the messages are skipped, and operations left without a message too), reply addresses given by runtime expressions, and Kafka topics with parameters. Security (SASL/SCRAM, mTLS) is not configured: pass `security_protocol`, `sasl_*` and `ssl_context` in `producer_options` and `consumer_options`. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the model class of a component schema | | `x-enum-varnames`, `x-enumNames` | names of the members of an enum | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `python.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Python names version: 1.0.0 actions: - target: $.components.schemas.Order update: x-apigenie: codegen: name: PurchaseOrder - target: $.components.schemas.OrderStatus update: x-enum-varnames: [CREATED, PAID, SHIPPED] ``` ```sh apigenie-generator client python asyncapi.yaml --overlay python.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_python.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the distribution, by default the document's title followed by `-client`. Its kebab case is the base of default group and client ids, and its Pascal case the base of the class names. ### --package-version Version of the distribution; defaults to the version of the API. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator client python`; its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: client_python: asyncapi: perspective: counterpart enum_style: literal ``` | Option | Description | | ------ | ----------- | | `perspective` | `counterpart` (default): the counterpart of the described application; `application`: the described application, sending what it sends | | `enum_style`, `field_prefix`, `class_overrides` | Settings of the Pydantic models, as for [apigenie-generator client python](/docs/reference/generator/client-python#configuration) | --- # client ws Source: https://apigenie.pl/docs/reference/generator/client-ws Generate a TypeScript client of the WebSocket channels of an AsyncAPI document. ## Usage ```sh apigenie-generator client ws [...OPTIONS] INPUT_FILE ``` ## Description The input must be an AsyncAPI 3.x document. Documents referencing other files are bundled first. The generator creates one class per WebSocket channel, i.e. a channel with a `ws` binding, or a channel of a server whose protocol is `ws` or `wss` (every channel when the document has no servers). Other channels are skipped. The client uses the `WebSocket` class of browsers and of Node.js 22 or later, and has no dependencies. An AsyncAPI document describes an application. By default the client is its counterpart: it sends the messages of `receive` operations and receives the messages of `send` operations. Set the `perspective` option to `application` to generate the application instead. ```ts import { RoomClient } from "chat-api-client"; const room = new RoomClient(); room.onRoomEvent((event) => console.log(event.kind)); await room.connect({ params: { roomId: "general" }, query: { token } }); room.sendJoin({ type: "join", user }); ``` Each class has: - `connect(options)`: opens the connection. Options are the server (`server`, a key of the document's servers, or `baseUrl`), the values of server variables (`variables`), the values of the parameters of the channel address (`params`), the query parameters of the WebSocket binding (`query`), handshake headers (`headers`, see below), subprotocols (`protocols`) and a `webSocketFactory`. - `send<Message>(payload)` for every message the client sends. - `on<Message>(handler)` for every message it receives, when the channel receives one message, or when its messages can be told apart by a property with a distinct constant value (e.g. `type: { const: join }`). `onMessage(handler)` receives every message of a channel receiving several. Handlers return a function removing them. - `request<Operation>(payload, { timeoutMs })` for every operation whose `reply` is on the same channel, when the request and reply messages have a correlation id in their payloads (e.g. `$message.payload#/requestId`): it sends the request and resolves with the reply carrying the same id, or rejects after `timeoutMs` (30 seconds by default) or when the connection closes. The reply also reaches its `on<Message>` handler. Operation `requestPickup` gives `requestPickup`, `bookPickup` gives `requestBookPickup`. - `onError`, `onClose`, `onUnknownMessage`, `close()` and `connected`. Payloads are encoded by the content type of the message: JSON for `application/json` and `*/*+json` (the default), text for `text/*`, binary data (`ArrayBuffer`) otherwise. Browsers can't send headers with the WebSocket handshake, so the `headers` option requires a `webSocketFactory` creating sockets that can, e.g. of the `ws` package. The generated package contains `src/types.ts` (component schemas), `src/messages.ts` (payload types and type guards), `src/client.ts`, `src/metadata.ts` (servers and channels), `src/runtime.ts` and `src/index.ts`, with `package.json` and `tsconfig.json`. The reply messages of an operation's `reply` on the same channel are received messages like the others. Not supported, and reported as warnings: replies on another channel or at a runtime-expression address, and WebSocket binding methods other than `GET`. Security schemes and requirements are not generated. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | on a channel: the base of its names (`LobbyClient`); on a message: the base of its types (`PostPayload`); on a component schema: the name of its type | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `ws.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: WebSocket names version: 1.0.0 actions: - target: $.channels.room update: x-apigenie: codegen: name: Lobby - target: $.components.messages.ChatMessage update: x-apigenie: codegen: name: Post ``` ```sh apigenie-generator client ws asyncapi.yaml --overlay ws.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.client_ws.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the generated package. Defaults to the title of the API followed by `-client`. ### --package-version Version of the generated package. Defaults to the version of the API. ## Configuration ```yaml version: '1.0' commands: generate: client_ws: layout: package import_extension: .js perspective: counterpart ``` | Option | Description | | ------ | ----------- | | `layout` | `package` (default): an npm package with sources in `src/`; `sources`: the sources only | | `import_extension` | Extension of relative imports: `.js` (default, Node.js) or an empty string (bundlers) | | `perspective` | `counterpart` (default): the client of the described application; `application`: the application itself | --- # server aspnet (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/server-aspnet-asyncapi Generate an ASP.NET Core application of the Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator server aspnet [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator server aspnet` generates from OpenAPI documents (see [apigenie-generator server aspnet](/docs/reference/generator/server-aspnet)) and from AsyncAPI 3.x documents, telling them apart by their `openapi` or `asyncapi` field. Documents referencing other files are bundled first. From an AsyncAPI document it generates an ASP.NET Core project (.NET 8, `Microsoft.NET.Sdk.Web`) using the Kafka client [Confluent.Kafka](https://github.com/confluentinc/confluent-kafka-dotnet) (Apache-2.0, pinned at 2.15.1 in the project file). The consumers are hosted services of the application, so the same project can also serve a REST API. By default the application is the one the document describes: it sends the messages of `send` operations and receives the messages of `receive` operations. Set the `perspective` option to `counterpart` to swap them. The generator writes the Kafka channels: the channels of servers with the `kafka` or `kafka-secure` protocol, and channels with a Kafka binding in documents without servers. Channels of other protocols are skipped and reported as info. ```csharp public sealed class LabelFarm : IRequestLabelRenderHandler { public Task<IRequestLabelRenderReply?> OnRenderLabelAsync(RenderLabel message, CancellationToken cancellationToken) { IRequestLabelRenderReply reply = new LabelRendered { Key = message.Key, Payload = Render(message.Payload) }; // sent to Topics.LabelRenderResults with the request's correlation id return Task.FromResult<IRequestLabelRenderReply?>(reply); } } var builder = WebApplication.CreateBuilder(args); builder.Services.AddCarpetExpressKafka(builder.Configuration); builder.Services.AddScoped<IRequestLabelRenderHandler, LabelFarm>(); var app = builder.Build(); app.MapPost("/shipments/{id}/delivered", async (string id, PublishShipmentEventsSender sender) => await sender.SendShipmentDeliveredAsync(Delivered(id))); app.Run(); ``` The project's root namespace (and assembly name) is `root_namespace`, else `--package-name` or the document's title in PascalCase; the names of the application's classes start with the latter (`CarpetExpress`): | File | Contents | | ---- | -------- | | `Models/*.cs` | the types of the schemas the payloads use, as `apigenie-generator client csharp` writes them: records with `[JsonPropertyName]` and DataAnnotations, enums, and unions as abstract records | | `Serialization/*.cs` | the JSON context of the source generator of System.Text.Json, and the converters of unions, which read the discriminator wherever it is | | `Messages/*.cs` | a record per message (`Key`, `Headers`, `Payload`), its headers record and header constants, with `ToRecord()` and `FromRecord()` | | `Kafka/*.cs` | the settings, the handler interfaces and consumers of the received operations, the senders of the sent operations, the reply interfaces, and `Add<Name>Kafka` | | `Infrastructure/*.cs` | the Kafka runtime (producer, consumer service, requester, headers) and the validation of messages | | `<RootNamespace>.csproj`, `README.md` | the project | | `Program.cs`, `appsettings.json`, `Handlers/*.cs` | starting points, written once and never overwritten | - **Messages.** Headers are the message's headers merged with those of its traits as AsyncAPI does (JSON Merge Patch: a later `required` list replaces an earlier one). Header values are text on Kafka, so headers are `string`, `long`, `double` or `bool` properties, nullable when optional, with DataAnnotations for their length, pattern, format, values and range. Headers with a constant value, e.g. the `ce_specversion` and `ce_type` of CloudEvents in binary mode, are constants (`ShipmentStatusChanged.CeType`), which senders write and receivers compare. Messages are validated as a whole before they are sent and after they are received. - **Received operations.** A handler interface per operation (`IPublishShipmentEventsHandler`) has an `On<Message>Async` method per message and, when there are several messages, `OnUnknownAsync`, which ignores records by default. Register an implementation with the dependency injection container: the operation's consumer, a `BackgroundService` in the operation's consumer group, resolves it in a scope of its own for every record. Several messages are told apart by a payload property or a header with a distinct constant value (e.g. `type` or `ce_type`); otherwise the first message whose payload reads strictly and is valid is chosen. Records matching no message go to `OnUnknownAsync`. - **Delivery.** A record is committed once its handler returns. When the handler throws, the consumer logs the error and handles the record again after `RetryDelay`, so the records of a failing partition wait. Records that can't be read or are invalid messages are logged as warnings and committed. Stopping the host (Ctrl+C, SIGTERM) cancels the consumers, which close and leave their groups. - **Sent operations.** A sender per operation (`PublishShipmentEventsSender`), registered as a singleton, has a `Send<Message>Async` method per message that validates the message and completes once the brokers have it. Senders, requesters and repliers share one producer. - **Request/reply** with a reply channel: the requester's `Request<Message>Async` returns the reply (one of the reply messages, `IRequestLabelRenderReply`), matched by the request's correlation id, which is generated when the request has none; `ReplyTimeout` bounds the wait. Each requester consumes the reply topic without a consumer group, from a minute before it started, so a reply isn't missed and every instance of the application sees every reply. A replier's handler returns the reply (`null` for none), which is sent to the reply topic with the request's correlation id copied (to a header, or a payload property, as the `correlationId` locations say). ### Settings The `AsyncApi:Kafka` section of the configuration holds the settings, with the document's values as defaults; `appsettings.json` lists them, and environment variables override them as usual in ASP.NET Core (`AsyncApi__Kafka__BootstrapServers=localhost:9092`): ```json { "AsyncApi": { "Kafka": { "BootstrapServers": "kafka-eu.events.carpetexpress.example:9093", "ClientId": "carpet-express", "Topics": { "ShipmentEvents": "carpetexpress.shipment-events.v2" }, "Operations": { "PublishShipmentEvents": { "GroupId": "carpet-express-publish-shipment-events", "ClientId": "carpet-express-publish-shipment-events" } }, "ReplyTimeout": "00:00:30", "RetryDelay": "00:00:01", "Consumer": { "security.protocol": "SASL_SSL" }, "Producer": { "security.protocol": "SASL_SSL" } } } } ``` `BootstrapServers` defaults to the host of the first Kafka server, its variables replaced by their defaults. Group and client ids come from the `groupId` and `clientId` schemas of the Kafka operation bindings, of the operation or its traits: their `const`, single `enum` value, `default` or first example. Without a value, `<package name in kebab case>-<operation>` is used, and the documentation of the setting names the binding's pattern. The partitions, replicas and topic configuration of channel bindings are documented on the topic settings; the generated code creates no topics. `Consumer` and `Producer` take further librdkafka properties. Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats (the messages are skipped, and operations left without a message too), reply addresses given by runtime expressions, and Kafka topics with parameters. Security (SASL/SCRAM, mTLS) is not configured: set the librdkafka properties in `Consumer` and `Producer`. With a correlation id in the payload at a place that isn't a string or uuid property of the payload record, the request must carry the id and the replier's handler must copy it. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | | `x-enum-varnames`, `x-enum-names` | names of the members of an enum, in PascalCase | | `x-enum-descriptions`, `x-enumDescriptions` | documentation comments of the members of an enum | The extensions can live in an overlay for this target instead of in the document, e.g. `aspnet.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: ASP.NET names version: 1.0.0 actions: - target: $.components.schemas.OrderStatus update: x-enum-varnames: [Created, Paid, Shipped] - target: $.operations.publishAudit update: x-apigenie: codegen: disabled: true ``` ```sh apigenie-generator server aspnet asyncapi.yaml --overlay aspnet.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_aspnet.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Root namespace of the generated code and base of the names of the application's classes, in PascalCase (`carpet-express` gives `CarpetExpress`); defaults to the title of the API. Its kebab case is the base of default group and client ids. ### --package-version Version of the project; defaults to the version of the API when it is a .NET version, else 1.0.0. ### -p, --property Applies to OpenAPI documents only; using it with an AsyncAPI document is an error. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator server aspnet`; its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: server_aspnet: asyncapi: root_namespace: Acme.Events validation: true program: true perspective: application ``` | Option | Description | | ------ | ----------- | | `root_namespace` | Root namespace and assembly name of the project (default: from `--package-name` or the title) | | `validation` | `true` (default): validate messages by their DataAnnotations before sending and after receiving them | | `program` | `true` (default): write `Program.cs`, `appsettings.json` and the handler stubs when they don't exist | | `perspective` | `application` (default): the described application; `counterpart`: its counterpart, receiving what it sends | --- # server faststream Source: https://apigenie.pl/docs/reference/generator/server-faststream Generate a Python [FastStream](https://faststream.ag2.ai) application of the Kafka, AMQP (RabbitMQ) and MQTT channels of an AsyncAPI document. ## Usage ```sh apigenie-generator server faststream [...OPTIONS] INPUT_FILE ``` ## Description The input must be an AsyncAPI 3.x document. Documents referencing other files are bundled first. The application is the one the document describes: it receives the messages of `receive` operations and sends those of `send` operations. - one broker per server: `KafkaBroker` for `kafka` and `kafka-secure` servers (aiokafka), `RabbitBroker` for `amqp` and `amqps` (AMQP 0.9.1) and `MQTTBroker` for `mqtt`, `mqtts` and `secure-mqtt` (MQTT 5 or 3.1.1). A broker connects to the URL in an environment variable named after its server (`PRODUCTION_KAFKA_URL`), or to the server's address with the default values of its variables - a subscriber per receive operation, `@broker.subscriber(...)`, which calls a handler you write - a publisher per send operation, `@broker.publisher(...)`, and an `async` function publishing through it with typed arguments - Pydantic v2 models of the payloads and headers ```python from warehouse_events import models from warehouse_events.publishers import publish_audit, request_quote await publish_audit(models.AuditEntry(action="login"), region="eu") quote = await request_quote(models.QuoteRequest(sku="B"), timeout=5) ``` Run the application with `faststream run <package>.app:app`. ### Channels and bindings | Document | Generated code | | -------- | -------------- | | Channel address, Kafka binding `topic` | the topic; with parameters a `pattern=` subscription and `Path()` parameters | | Kafka operation binding `groupId` (`const` or a single `enum` value), also from operation traits | `group_id=` of the subscriber | | AMQP binding `is: queue` | a `RabbitQueue` (name, `durable`, `exclusive`, `auto_delete`) the operations publish to and consume from | | AMQP binding `is: routingKey` with an exchange | a `RabbitExchange` in `brokers.py`; the address is the routing key, with parameters a topic pattern and `Path()` parameters. Subscribers bind a queue named after the operation | | AMQP `vhost` | `virtualhost=` of the broker | | AMQP operation binding `priority`, `expiration`, `deliveryMode: 2`, `mandatory`, `userId`, `timestamp` | the publisher's `priority`, `expiration` (in seconds), `persist`, `mandatory`, `user_id`; a timestamp per message | | MQTT topic parameters (whole topic levels) | `Path()` parameters | | MQTT operation binding `qos`, `retain` | `qos=`, `retain=` | | MQTT server binding `clientId`, `cleanSession`, `keepAlive`, `sessionExpiryInterval`, `lastWill` | the broker's `client_id`, `clean_session`, `keepalive`, `session_expiry_interval`, `will` | ### Messages A JSON payload becomes a Pydantic model, a `text/*` one `str` and any other content type `bytes`. A payload referencing a schema of `components/schemas` is the model of that schema; other payloads and headers get a model named after their message (`OrderCreated`, `OrderCreatedHeaders`). The headers of message traits are merged into the message's own as AsyncAPI specifies (JSON Merge Patch: the message over its traits, later traits over earlier ones). Subscribers take the headers as typed `Header()` parameters and pass them to the handlers as keyword arguments; publish functions take a `headers` model. When several messages of a channel can be told apart by a property with a distinct constant value (`type: { const: created }`), the payload is a discriminated union and every message has a handler of its own (`consume_orders_order_created`); otherwise the handler of the operation gets the union. ### Request/reply - A send operation with a reply channel: its function publishes the request and returns the reply, waiting at most `timeout` seconds. A subscriber of the reply channel hands replies to the waiting requests by correlation id: the message's `correlationId` (a header, which the function fills in when the request has none, or a payload property), or FastStream's correlation id when the document declares none. FastStream's `broker.request()` isn't used: Kafka doesn't support it outside the test client, and RabbitMQ's waits on `amq.rabbitmq.reply-to` instead of the reply channel of the document. - A send operation with a reply address in a header (`$message.header#/replyTo`): its function takes a `reply_to` argument and sends it in that header. - A receive operation with a reply channel: the handler returns the reply, which is published to the reply channel with the request's correlation id header. - A receive operation with a reply address in a header: the handler returns the reply, which is published to the address in the request's header. ### Output ```text pyproject.toml faststream[cli,…], pydantic; `pip install -e .` README.md how to run, the environment variables, what isn't generated <package>/app.py the FastStream application <package>/brokers.py the brokers and AMQP exchanges <package>/subscribers.py <package>/publishers.py <package>/handlers.py the handlers you write <package>/models.py Pydantic models (shared with apigenie-generator client python) <package>/_models.py, _runtime.py, __init__.py, py.typed ``` `handlers.py` is written only when it doesn't exist: it holds your code. After regenerating, add the handlers of new operations to it; mypy reports the missing ones. ### Not generated Reported as info lines or warnings: - channels of WebSocket and HTTP servers, which FastStream has no broker for - messages with Avro (or other non-JSON Schema) payloads, and operations left without messages - security schemes: give the brokers a security object (`faststream.security`) in `brokers.py` - servers no generated operation uses; a channel with several servers is served by its first one (choose others with the `servers` option) - reply addresses in payloads, AMQP `cc` and `bcc`, MQTT topic parameters inside a topic level ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | on a component schema: the name of its model class; on an operation: the name of its function (in snake_case) | | `x-enum-varnames`, `x-enumNames` | names of the members of an enum | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `faststream.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: FastStream names version: 1.0.0 actions: - target: $.operations.publishOrder update: x-apigenie: codegen: name: announce_order - target: $.components.schemas.OrderStatus update: x-enum-varnames: [CREATED, PAID, SHIPPED] ``` ```sh apigenie-generator server faststream asyncapi.yaml --overlay faststream.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_faststream.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the generated distribution; the Python package is its snake_case form. Defaults to the title of the API. ### --package-version Version of the generated package. Defaults to the version of the API. ## Configuration ```yaml version: '1.0' commands: generate: server_faststream: servers: [production-kafka, customs-broker] enum_style: enum field_prefix: field_ class_overrides: ShipmentId: ShipmentKey ``` | Option | Description | | ------ | ----------- | | `servers` | Keys of the servers to generate brokers for; all Kafka, AMQP and MQTT servers by default | | `enum_style` | `enum` (default): `class Status(str, Enum)` for enums of component schemas; `literal`: `Literal[...]` everywhere | | `field_prefix` | Prefix of field names that aren't valid identifiers or clash with Pydantic, e.g. `field_json` (default `field_`) | | `class_overrides` | Class names of component schemas, by schema name | --- # server go (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/server-go-asyncapi Generate a Go Kafka application of the Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator server go [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator server go` generates from OpenAPI documents (see [apigenie-generator server go](/docs/reference/generator/server-go)) and from AsyncAPI 3.x documents, telling them apart by their `openapi` or `asyncapi` field. Documents referencing other files are bundled first. From an AsyncAPI document it generates a Go module (Go 1.22) using the Kafka client [franz-go](https://github.com/twmb/franz-go) (`github.com/twmb/franz-go/pkg/kgo`, pinned at v1.18.1 in `go.mod`, with a complete `go.sum`). By default the application is the one the document describes: it sends the messages of `send` operations and receives the messages of `receive` operations. Set the `perspective` option to `counterpart` to swap them. The generator writes the Kafka channels: the channels of servers with the `kafka` or `kafka-secure` protocol, and channels with a Kafka binding in documents without servers. Channels of other protocols are skipped and reported as info. ```go type labelFarm struct{} func (labelFarm) OnRenderLabel(ctx context.Context, message events.RenderLabel) (events.RequestLabelRenderReply, error) { var reply events.LabelRendered reply.Key = message.Key reply.Payload = render(message.Payload) return reply, nil // sent to TopicLabelRenderResults with the request's correlation id } func (labelFarm) OnUnknown(ctx context.Context, record *kgo.Record) error { return nil } func main() { var handlers events.Handlers handlers.RequestLabelRender = labelFarm{} // ... app, err := events.NewApp(events.Options{Brokers: []string{"localhost:9092"}}, handlers) if err != nil { log.Fatal(err) } defer app.Close() err = app.PublishShipmentEvents().SendShipmentDelivered(ctx, delivered) // ... _ = app.Run(ctx) // consumes until ctx is done } ``` The module has one package at its root, named by `package_name`, `--package-name` or the document's title (letters and digits, lower case): | File | Contents | | ---- | -------- | | `types.gen.go` | the types of the schemas the payloads use: structs with `json` tags, enums, and unions as a struct whose `Value` holds one variant of a sealed interface (`switch v := event.Value.(type)`); each with a `Validate` method | | `messages.gen.go` | a struct per message (`Key`, `Headers`, `Payload`), its headers struct and header constants, and the code writing, reading and validating records | | `kafka.gen.go` | the server hosts, topics, group and client ids, the handler interfaces, `Handlers`, `App` and the senders | | `runtime_*.gen.go` | the Kafka runtime (consumers, requesters, headers), validation, `Nullable[T]`, `Date` | | `go.mod`, `go.sum` | the module, requiring franz-go | | `cmd/<package>/main.go` | a starting point, written once and never overwritten | - **Messages.** Headers are the message's headers merged with those of its traits as AsyncAPI does (JSON Merge Patch: a later `required` list replaces an earlier one). Header values are text on Kafka, so headers are `string`, `int64`, `float64` or `bool` fields, pointers when optional. Headers with a constant value, e.g. the `ce_specversion` and `ce_type` of CloudEvents in binary mode, are constants (`ShipmentStatusChangedCeType`), which senders write and receivers compare. Messages are validated as a whole before they are sent and after they are received. - **Received operations.** `NewApp(options, handlers)` takes a handler per received operation (`PublishShipmentEventsHandler`) with an `On<Message>` method per message and `OnUnknown`. Several messages of a channel are told apart by a payload property or a header with a distinct constant value (e.g. `type` or `ce_type`); otherwise the first message whose payload decodes strictly and is valid is chosen. Records matching no message go to `OnUnknown`. `Run` consumes the operations' topics, each in its consumer group, until its context is done; `Close` commits the offsets of the handled records and leaves the groups. Records that aren't valid messages, and handler errors, go to `Options.OnError` (logged by default); the records are committed either way. - **Sent operations.** `app.PublishShipmentEvents()` returns the sender of the operation, with a `Send<Message>(ctx, message)` method per message that validates the message and returns once the brokers have it. - **Request/reply** with a reply channel: the requester's `Send<Message>` returns the reply (one of the reply messages, `RequestLabelRenderReply`), matched by the request's correlation id, which is generated when the request has none; `Options.ReplyTimeout` bounds the wait when the context has no deadline. A replier's handler returns the reply, which is sent to the reply topic with the request's correlation id copied (to a header, or a payload property, as the `correlationId` locations say). Group and client ids come from the `groupId` and `clientId` schemas of the Kafka operation bindings, of the operation or its traits: their `const`, single `enum` value, `default` or first example. Without a value, `<package name in kebab case>-<operation>` is used, and the doc comment of the constant names the binding's pattern. `Options.GroupIDs` replaces them by operation id. The partitions, replicas and topic configuration of channel bindings are documented on the topic constants; the generated code creates no topics. `main.go` connects to the brokers of the `-brokers` flag, else of the `KAFKA_BROKERS` environment variable (comma-separated), else `localhost:9092`, wires stub handlers that return `api.ErrNotImplemented`, and stops cleanly on SIGINT or SIGTERM. Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats (the messages are skipped, and operations left without a message too), reply addresses given by runtime expressions, and Kafka topics with parameters. Security (SASL/SCRAM, mTLS) is not configured: pass `kgo.SASL` or `kgo.DialTLSConfig` in `Options.ClientOptions`. `Validate` doesn't check `not`, `if`/`then`/`else` and `dependencies`, tuples are `[]any` (only their item count is checked), and `contentEncoding: base64` strings stay strings. With a correlation id in the payload at a place that isn't a string field of the payload's structs, the request must carry the id and the replier's handler must copy it. ## Extensions | Extension | Effect | | --------- | ------ | | `x-go-name` | name of the type of a schema, or of the field of a property | | `x-go-type` | Go type of a schema, e.g. `decimal.Decimal`; with `x-go-type-import: {path: ...}` for its package | | `x-go-type-skip-optional-pointer` | the optional property isn't a pointer | | `x-go-json-ignore` | the field gets the tag `json:"-"` | | `x-omitempty` | `true` or `false` overrides the `omitempty` of the field | | `x-enum-varnames`, `x-enumNames` | names of the enum constants | | `x-enum-descriptions` | comments of the enum constants | | `x-apigenie.codegen.name` | name of the type of a component schema, when it has no `x-go-name` | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `go.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Go types version: 1.0.0 actions: - target: $.components.schemas.Order update: x-go-name: PurchaseOrder - target: $.components.schemas.Order.properties.total update: x-go-type: decimal.Decimal x-go-type-import: path: github.com/shopspring/decimal ``` ```sh apigenie-generator server go asyncapi.yaml --overlay go.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_go.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the module is generated. ### --package-name Name of the Go package, as letters and digits in lower case (`carpet-express-events` gives `carpetexpressevents`); defaults to the title of the API (`events` when it gives no valid name). Its kebab case is the base of default group and client ids. ### --package-version Ignored: Go modules have no version in `go.mod`. ### -p, --property Applies to OpenAPI documents only; using it with an AsyncAPI document is an error. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator server go`; its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: server_go: asyncapi: module_path: github.com/acme/events package_name: events initialisms: [SKU] main: true perspective: application ``` | Option | Description | | ------ | ----------- | | `module_path` | Module path of `go.mod` (default: `example.com/<package>`) | | `package_name` | Name of the package, a lower-case Go identifier (default: from `--package-name` or the title) | | `initialisms` | Words written in capitals in identifiers, besides the common ones (`ID`, `URL`, ...) | | `main` | `true` (default): write `cmd/<package>/main.go` when it doesn't exist | | `perspective` | `application` (default): the described application; `counterpart`: its counterpart, receiving what it sends | --- # server kafkajs Source: https://apigenie.pl/docs/reference/generator/server-kafkajs Generate a TypeScript (Node.js) Kafka application of an AsyncAPI document, on KafkaJS. ## Usage ```sh apigenie-generator server kafkajs [...OPTIONS] INPUT_FILE ``` ## Description The input must be an AsyncAPI 3.x document. Documents referencing other files are bundled first. The generator handles the Kafka channels: channels of a server whose protocol is `kafka` or `kafka-secure`, or with a `kafka` binding when the document has no servers. Other channels are skipped with an info message. The generated code runs on Node.js with [KafkaJS](https://kafka.js.org) 2.2.4, its only dependency. The application is the one the document describes: it sends the messages of `send` operations and receives those of `receive` operations. Set the `perspective` option to `counterpart` to generate the other side. ```ts import { KafkaApp } from "order-service-kafka-app"; const app = new KafkaApp({ brokers: ["localhost:9092"] }, { handleOrderCommands: { onPlaceOrderMessage: (message) => ({ payload: { requestId: message.payload.requestId, orderId: "o-1" } }), onCancelOrderMessage: () => undefined, }, }); await app.start(); await app.publishStockLevels.sendStockLevelMessage({ key: "ABC-0001", payload: { sku: "ABC-0001", quantity: 3 } }); await app.stop(); ``` `KafkaApp` takes the handlers of all received operations. `start()` connects the producer and the consumers and resolves once every consumer joined its group; `stop()` commits, leaves the groups and disconnects. Each sent operation has a sender property. Every sent operation gets a sender property with a `send<Message>(message)` method per message. It validates the message, sets the constant headers (e.g. `ce_type`) and resolves once the brokers have it. A message is `{ key?, headers, payload }`, with the headers under their names on the wire (`ce_id`). The method of an operation with a `reply` sends the request with a new correlation id unless it has one, and resolves with the reply: the reply message, or `{ name, message }` when there are several. It rejects with `ReplyTimeoutError` when no reply arrives in time (`{ timeoutMs }`, `KafkaOptions.replyTimeoutMs`, 30 seconds by default). Every received operation gets a handler interface: `on<Message>(message, record)` for each of its messages and an optional `onUnknown(record)`. Records are matched to messages by their `ce_type` (or other constant) header, by a constant payload property, or, failing both, by the first message whose payload is valid. The handlers of an operation with a `reply` return the reply (or `undefined` for none), which is sent with the request's correlation id. Invalid records are reported to `KafkaOptions.onError` and skipped. A handler that throws is reported too and the record is retried (KafkaJS doesn't commit it). Payloads and headers are validated against their schemas by generated functions without dependencies; invalid messages throw `MessageValidationError`, whose `issues` point into `/headers` or `/payload`. `KafkaOptions` takes the `brokers`, the producer's `clientId`, `config` (KafkaJS options for every client: `ssl`, `sasl`, `retry`, `logLevel`, ...), `groupIds` replacing the generated consumer groups, `fromBeginning` (true by default) and `onError`. The generated code creates no topics. The package contains `src/types.ts` (payload types), `src/messages.ts` (message and header interfaces), `src/kafka.ts` (servers, topics, ids, senders, handlers and the `KafkaApp`), `src/codecs.ts` (validators and the descriptions of the messages the runtime works with), `src/runtime.ts`, `src/validation_runtime.ts` and `src/index.ts`, with `package.json` and `tsconfig.json`. Not supported, and reported as warnings: Avro payloads, topics with parameters, replies at runtime-expression addresses, and replies without correlation ids. No security code is generated (pass KafkaJS's `ssl` and `sasl` in `KafkaOptions.config`). [`apigenie-generator client kafkajs`](/docs/reference/generator/client-kafkajs) generates the other side on the same runtime. The design is ADR-120. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the type of a component schema | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `kafkajs.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: TypeScript names version: 1.0.0 actions: - target: $.components.schemas.Order update: x-apigenie: codegen: name: PurchaseOrder - target: $.operations.publishAudit update: x-apigenie: codegen: disabled: true ``` ```sh apigenie-generator server kafkajs asyncapi.yaml --overlay kafkajs.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_kafkajs.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the generated package. Defaults to the title of the API followed by `-kafka-app`. It is also the base of the generated consumer group and client ids. ### --package-version Version of the generated package. Defaults to the version of the API. ## Configuration ```yaml version: '1.0' commands: generate: server_kafkajs: layout: package import_extension: .js perspective: application validation: true ``` | Option | Description | | ------ | ----------- | | `layout` | `package` (default): an npm package with sources in `src/`; `sources`: the sources only | | `import_extension` | Extension of relative imports: `.js` (default, Node.js) or an empty string (bundlers) | | `perspective` | `application` (default): the described application; `counterpart`: its counterpart | | `validation` | `true` (default): validate payloads against their schemas; `false`: check the headers only | Unknown options are errors. --- # server phoenix (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/server-phoenix-asyncapi Generate a Phoenix application of the WebSocket and Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator server phoenix [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator server phoenix` generates from OpenAPI documents (see [apigenie-generator server phoenix](/docs/reference/generator/server-phoenix)) and from AsyncAPI 3.x documents, telling them apart by their `openapi` or `asyncapi` field. Documents referencing other files are bundled first. From an AsyncAPI document it generates a Mix project (Elixir 1.18 or later): the WebSocket channels on [Phoenix](https://www.phoenixframework.org) 1.7 with Bandit, and the Kafka channels on [brod](https://github.com/kafka4beam/brod) and [Broadway](https://hexdocs.pm/broadway_kafka). By default the application is the one the document describes: it sends the messages of `send` operations and receives the messages of `receive` operations. Set the `perspective` option to `counterpart` to swap them; the counterpart has the Kafka channels only, since Phoenix serves WebSocket channels as the application. The project depends on [snappyer](https://hex.pm/packages/snappyer), a NIF that needs a C++ compiler, so that brod reads Snappy-compressed records (the default of Go's franz-go producers); LZ4 and zstd records need `lz4b` or `ezstd` added by hand. The generator writes the channels of servers with the `ws`/`wss` and `kafka`/`kafka-secure` protocols, and channels with a WebSocket or Kafka binding in documents without servers. Channels of other protocols (MQTT, AMQP, HTTP) are skipped and reported as info. ```elixir defmodule MyApp.PickupDesk do @behaviour CarpetExpressEventsWeb.Channels.PickupDesk.Handler alias CarpetExpressEvents.Models @impl true def request_pickup(%Models.PickupRequest{} = request, _session) do # the reply gets the request's requestId when it has none {:reply, %Models.PickupConfirmed{request_id: nil, pickup: book(request)}} end end defmodule MyApp.LabelFarm do @behaviour CarpetExpressEvents.Kafka.RequestLabelRender @impl true def render_label(message, _metadata) do # sent to carpetexpress.label-render.results with the request's correlation id {:reply, %CarpetExpressEvents.Messages.LabelRendered{key: message.key, payload: render(message.payload)}} end end ``` ```elixir # config/config.exs config :carpet_express_events, CarpetExpressEventsWeb.Channels.PickupDesk.Handler, impl: MyApp.PickupDesk ``` The project, for the application `carpet_express_events` (named by `app`, `--package-name` or the document's title): | File | Contents | | ---- | -------- | | `lib/<app>/models/*.ex` | the structs of the schemas the payloads use, decoded and encoded by `decode.ex` and `encode.ex` (as the OpenAPI models of `server phoenix`) | | `lib/<app>/messages/*.ex` | per Kafka message, a struct (`payload`, `headers`, `key`) and the description of its record | | `lib/<app>/kafka.ex` | the supervisor of the Kafka processes and a function per topic | | `lib/<app>/kafka/<operation>.ex` | per sent operation, its send (and request) functions; per received operation, the behaviour of its callbacks | | `lib/<app>/kafka/<operation>_consumer.ex` | per received operation, its Broadway pipeline | | `lib/<app>_web/user_socket.ex`, `channels/*.ex` | the socket of the Phoenix clients, a Phoenix channel per WebSocket channel and the behaviour of its callbacks (`channels/<channel>/handler.ex`) | | `lib/<app>_web/endpoint.ex` | the endpoint: the socket, then the plain WebSockets of the channels | | `lib/<app>/runtime/`, `lib/<app>_web/runtime/` | the runtime of the Kafka processes and of the channels | | `config/*.exs`, `mix.exs`, `.formatter.exs`, `.credo.exs` | the project; `config/runtime.exs` reads `PORT` and `KAFKA_BROKERS` | - **WebSocket channels, two transports.** Each channel is a Phoenix channel: Phoenix clients join its topic on `/socket/websocket`, the address with its parameters as segments (`/tracking/{shipmentId}` is `tracking:<shipmentId>`), with the ws binding's query as the join payload. The same channel is served as a plain WebSocket at its address, where the frames are the messages as the AsyncAPI document describes them (the `prefix` of `<Root>Web.Runtime.PlainSocketPlug` mounts the addresses under a path). Both transports decode the address parameters and the query, call the optional `join/2` callback, decode received messages into their structs before the callbacks get them, and send what the channel module's `push_<message>/2` (one connection) and `broadcast_<message>` (every connection to the topic) functions send. A request's callback returns `{:reply, message}`: the reply of the Phoenix protocol, or a frame on a plain WebSocket, with the request's correlation id copied when it has none. - **Received Kafka operations.** A Broadway pipeline per operation reads its topic in its consumer group and hands each record to a callback of the operation's behaviour (`CarpetExpressEvents.Kafka.PublishShipmentEvents`), named after the message. Several messages of a channel are told apart by a header or a payload property with a distinct constant value (`ce_type`), otherwise by the first message whose payload and headers decode. Records matching no message go to the optional `on_unknown/1`, records that don't decode to the optional `on_invalid/2` (and fail). Batching: `batch_size` and `batch_timeout` from `x-apigenie-broadway` of the operation's (or channel's) Kafka binding, else the `broadway` option, else Broadway's 100 records and 1000 ms; the pipeline module's configuration overrides them, and the consumer group and offset reset policy too. - **Sent Kafka operations.** `send_<message>/1` checks the headers of a message, encodes it and returns once the brokers have the record, keyed by `key`. - **Request/reply on Kafka**, with a reply channel: `request_<message>/2` sends the request with its correlation id (generated when it has none) and waits for the reply (`:timeout`, 30 seconds by default), read by a consumer of the reply topic without consumer group; a replier's callback returns the reply, sent to the reply topic with the request's correlation id. Group ids come from the `groupId` schemas of the Kafka operation bindings (their `const`, single `enum` value, `default` or first example), else `<app in kebab case>-<operation>`. The partitions, replicas and topic configuration of channel bindings are documented on the topic functions; the generated code creates no topics. Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats (the messages are skipped, and operations left without a message too), reply addresses given by runtime expressions, Kafka topics with parameters, and WebSocket addresses whose segments mix text and parameters. Security is not generated: `UserSocket.connect/3` accepts every connection and the Kafka clients connect without SASL or TLS. The client ids of the Kafka bindings are documented but not applied, the headers schema of the ws binding is not read, and the models decode by type: `not`, `if`/`then`/`else`, `dependencies` and number and array constraints aren't checked. ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### -o, --output Directory where the project is generated. ### --package-name Name of the Mix application, converted to snake_case (`carpet-express-events` gives `carpet_express_events`, root module `CarpetExpressEvents`); defaults to the title of the API (`events` when it gives no name). Its kebab case is the base of default group ids. ### --package-version Version of the Mix project; 0.1.0 by default. ### -p, --property Applies to OpenAPI documents only; using it with an AsyncAPI document is an error. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator server phoenix`; its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: server_phoenix: asyncapi: app: carpet_express_events namespace: CarpetExpressEvents perspective: application broadway: { batch_size: 100, batch_timeout: 1000, concurrency: 10 } ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `app` | string | from `--package-name` or the title | Name of the Mix application (snake_case) | | `namespace` | string | the application in PascalCase | Root module of the generated modules; the web modules are in `<Root>Web` | | `perspective` | `application` \| `counterpart` | `application` | The described application, or its counterpart, receiving what it sends | | `field_prefix` | string | `field_` | Prefix of struct fields whose property names give no valid name | | `module_overrides` | map | | Module names of payload schemas, e.g. `ShipmentId: ShipmentRef` | | `dev_deps` | boolean | `true` | credo and dialyxir as dependencies of the dev and test environments | | `broadway` | map | | `batch_size`, `batch_timeout` (ms) and `concurrency` of the pipelines | --- # server spring (AsyncAPI) Source: https://apigenie.pl/docs/reference/generator/server-spring-asyncapi Generate a Spring Boot application of the Kafka channels of an AsyncAPI document. ## Usage ```sh apigenie-generator server spring [...OPTIONS] INPUT_FILE ``` ## Description `apigenie-generator server spring` generates from OpenAPI documents (see [apigenie-generator server spring](/docs/reference/generator/server-spring)) and from AsyncAPI 3.x documents, telling them apart by their `openapi` or `asyncapi` field. Documents referencing other files are bundled first. From an AsyncAPI document it generates a Maven project of a Spring Boot 3 application using [Spring for Apache Kafka](https://spring.io/projects/spring-kafka). By default the application is the one the document describes: it sends the messages of `send` operations and receives the messages of `receive` operations. Set the `perspective` option to `counterpart` to swap them. The generator has one part per protocol. Today it generates the Kafka channels: the channels of servers with the `kafka` or `kafka-secure` protocol, and channels with a Kafka binding in documents without servers. Channels of other protocols are skipped and reported as info. ```java @Component class LabelFarm implements RequestLabelRenderHandler { @Override public RequestLabelRenderReply onRenderLabel(RenderLabel message) { LabelRenderResult result = render(message.payload()); return new LabelRendered(message.key(), result, new LabelRenderedHeaders(null)); } } @Service class Shipments { private final PublishShipmentEventsSender events; void delivered(String shipmentId, ShipmentDeliveredData data, ShipmentDeliveredHeaders headers) { events.sendShipmentDelivered(new ShipmentDelivered(shipmentId, data, headers)); } } ``` The generated code has: - **models** (package `model`): a record per object schema the messages use, with Jackson and Jakarta Bean Validation annotations (`@NotNull` for required properties, `@Valid`, `@Pattern`, `@Size`, `@Min`/`@Max`, `@DecimalMin`/`@DecimalMax`, `@Email`); an enum per enumeration; a sealed interface per `oneOf` of objects (`@JsonTypeInfo` by the `discriminator` property, or by deduction). `allOf` members are merged into one record. Schemas under `#/components/schemas` keep their name; inline schemas are named after their message (`PlaceOrderPayload`) or their owner and property (`LabelRenderRequestFormat`). - **messages** (package `messages`): a record per message with its Kafka `key`, its `payload` and its `headers`, and a headers record with the message's headers, the headers of its traits merged as AsyncAPI does (JSON Merge Patch: a later `required` list replaces an earlier one). Headers with a constant value, e.g. the `ce_specversion` and `ce_type` of CloudEvents in binary mode, are constants of the headers record (`ShipmentStatusChangedHeaders.CE_TYPE`), which senders write and listeners compare. The key is a `String` validated by the key schema of the Kafka message binding. - **Kafka** (package `kafka`): - for every received operation, a handler interface (`ConsumeOrdersHandler`) with an `on<Message>` method per message, which you implement as a Spring bean, and a listener (`ConsumeOrdersListener`) with a `@KafkaListener` method. Several messages of a channel are told apart by a payload property or a header with a distinct constant value (e.g. `type` or `ce_type`); otherwise the first message whose payload is valid is chosen. Records matching no message go to `onUnknown`, which ignores them by default. - for every sent operation, a sender (`PublishShipmentEventsSender`) with a `send<Message>(message)` method per message, over a `KafkaTemplate` using the operation's client id. - request/reply operations with a reply channel: the requester's sender uses a `ReplyingKafkaTemplate` listening on the reply channel's topic and returns `CompletableFuture<<Operation>Reply>`, a sealed interface of the reply messages. Replies are matched by the header of the request message's `correlationId`, generated when the request has none. A replier's handler returns the reply, which the listener sends to the reply topic with the request's correlation id copied (from a header or a payload property, as the `correlationId` locations say). - `KafkaJson`, converting payloads (JSON) and headers (UTF-8 text) and validating messages, and `KafkaTopicsConfiguration`, creating the topics of the channels from their Kafka bindings (partitions, replicas, `topicConfiguration`) when `asyncapi.kafka.create-topics` is `true`. - **project files**: `pom.xml` (parent `spring-boot-starter-parent`, `spring-boot-starter`, `spring-boot-starter-json`, `spring-boot-starter-validation` (with `bean_validation`), `spring-kafka`, and the test starters), the `@SpringBootApplication` class and `src/main/resources/application.yaml`. `application.yaml` points `spring.kafka.bootstrap-servers` to the first Kafka server (server variables replaced by their default) and configures String keys and byte array values. The `asyncapi.kafka` section holds every Kafka server, the topic of every channel and the group and client ids of every operation, which the generated code reads, so that they can be changed without regenerating: ```yaml asyncapi: kafka: create-topics: false servers: production-kafka: bootstrap-servers: kafka-eu.events.carpetexpress.example:9093 schema-registry-url: https://schemas.events.carpetexpress.example channels: shipment-events: topic: carpetexpress.shipment-events.v2 operations: consume-carrier-scans: group-id: tracking-ingest client-id: carpet-express-consume-carrier-scans ``` Group and client ids come from the `groupId` and `clientId` schemas of the Kafka operation bindings, of the operation or its traits: their `const`, single `enum` value, `default` or first example. Without a value, `<artifact id>-<operation>` is used, with a comment naming the binding's pattern. Constraints Bean Validation can't express are line comments of the record or component: draft-07 `dependencies`, `if`/`then`/`else`, `not`, tuples (`items` arrays, mapped to `List` with `@Size`), `patternProperties`, `const` values, and `anyOf`/`oneOf` branches constraining the properties of an object. Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats (the messages are skipped, and operations left without a message too), reply addresses given by runtime expressions, and Kafka topics with parameters. Security (SASL/SCRAM, mTLS) and the schema registry are not configured. With a correlation id in the payload, the replier's reply must carry the property (the listener overwrites it with the request's), and a requester matches replies by the `kafka_correlationId` header, which the replier must copy. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | name of the class of a schema | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `spring.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: Java names version: 1.0.0 actions: - target: $.components.schemas.Order update: x-apigenie: codegen: name: PurchaseOrder ``` ```sh apigenie-generator server spring asyncapi.yaml --overlay spring.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_spring.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the project is generated. ### --package-name Maven artifact id of the project. Defaults to the title of the API in kebab case. The base package defaults to `com.example.` followed by the artifact id in lower case without other characters than letters and digits (prefixed with `app` when it starts with a digit or is a Java keyword). ### --package-version Version of the project. Defaults to the version of the API. ### -p, --property Applies to OpenAPI documents only; using it with an AsyncAPI document is an error. ## Configuration The options are read from the `asyncapi` section of the configuration of `apigenie-generator server spring`; its other keys configure the generation from OpenAPI documents. ```yaml version: '1.0' commands: generate: server_spring: asyncapi: base_package: com.acme.events group_id: com.acme java_version: 21 spring_boot_version: 3.3.13 bean_validation: true perspective: application ``` | Option | Description | | ------ | ----------- | | `base_package` | Package of the generated code (default: `com.example.<artifact id>`); models are in `.model`, messages in `.messages`, the Kafka code in `.kafka` | | `group_id` | Maven group id (default: the base package) | | `java_version` | Java release, 17 (default) or later | | `spring_boot_version` | Version of the Spring Boot 3 parent (default `3.3.13`) | | `bean_validation` | `true` (default): Bean Validation annotations, and validation of sent and received messages; `false`: none | | `perspective` | `application` (default): the described application; `counterpart`: its counterpart, receiving what it sends | --- # server ws Source: https://apigenie.pl/docs/reference/generator/server-ws Generate a TypeScript (Node.js) server of the WebSocket channels of an AsyncAPI document. ## Usage ```sh apigenie-generator server ws [...OPTIONS] INPUT_FILE ``` ## Description The input must be an AsyncAPI 3.x document. Documents referencing other files are bundled first. The server serves the WebSocket channels of the document (see [apigenie-generator client ws](/docs/reference/generator/client-ws)) at their addresses, using the [ws](https://github.com/websockets/ws) package. By default it implements the application the document describes: it sends the messages of `send` operations and receives the messages of `receive` operations. Set the `perspective` option to `counterpart` to swap them. ```ts import { createServer } from "chat-api-server"; const server = createServer({ room: { onConnect(session) { console.log(`joined ${session.params.roomId}`); }, onJoin(session, payload) { session.sendRoomEvent({ kind: "joined", user: payload.user }); }, }, }); await server.listen(8080); ``` For every channel, the generated code has: - a session class (`RoomSession`): a connection of a client, with the values of the address parameters (`params`), the query parameters converted to the types of the WebSocket binding (`query`), the handshake `headers`, `send<Message>(payload)` methods and `close()`. The reply messages of an operation whose `reply` is on the same channel get `send<Message>` methods too, and, when the request and reply messages have a correlation id in their payloads, `replyTo<Operation>(request, reply)` sends the reply with the request's correlation id copied into it (e.g. `session.replyToRequestPickup(request, confirmation)`). - a handler interface (`RoomHandler`) with `onConnect`, `on<Message>` for every message the server receives (when the channel receives one message, or when its messages can be told apart by a property with a distinct constant value), `onMessage` for every message of a channel receiving several, `onUnknownMessage`, `onClose` and `onError` The handlers of a session are called one at a time, in the order of the events; a handler returning a promise delays the next one until it settles. Errors thrown by handlers are passed to `onError`. `createServer(handlers, options)` serves the channels with handlers. Connections to other addresses are rejected with status 404. Options are an existing HTTP server to attach to (`server`) and a path prefix of all addresses (`basePath`). The result has `listen(port, host)`, `close()`, `address()` and `httpServer`. The generated package contains `src/types.ts`, `src/messages.ts`, `src/server.ts`, `src/metadata.ts`, `src/runtime.ts` and `src/index.ts`, with `package.json` (depending on `ws`) and `tsconfig.json`. Not supported, and reported as warnings: replies on another channel or at a runtime-expression address, channels without an address, and channels sharing an address with another channel (only the first one is served). Security schemes and requirements are not generated: the server checks no credentials on the handshake, and the headers schema of the WebSocket binding is not typed. ## Extensions | Extension | Effect | | --------- | ------ | | `x-apigenie.codegen.name` | on a channel: the base of its names (`LobbyHandler`); on a message: the base of its types (`PostPayload`); on a component schema: the name of its type | | `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) | The extensions can live in an overlay for this target instead of in the document, e.g. `ws.overlay.yaml`: ```yaml overlay: 1.0.0 info: title: WebSocket names version: 1.0.0 actions: - target: $.channels.room update: x-apigenie: codegen: name: Lobby - target: $.components.messages.ChatMessage update: x-apigenie: codegen: name: Post ``` ```sh apigenie-generator server ws asyncapi.yaml --overlay ws.overlay.yaml -o out ``` ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --overlay OVERLAY Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those listed under `commands.generate.server_ws.overlay` in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references. ### --strict Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`). ### -o, --output Directory where the package is generated. ### --package-name Name of the generated package. Defaults to the title of the API followed by `-server`. ### --package-version Version of the generated package. Defaults to the version of the API. ## Configuration ```yaml version: '1.0' commands: generate: server_ws: layout: package import_extension: .js perspective: application ``` | Option | Description | | ------ | ----------- | | `layout` | `package` (default): an npm package with sources in `src/`; `sources`: the sources only | | `import_extension` | Extension of relative imports: `.js` (default, Node.js) or an empty string (bundlers) | | `perspective` | `application` (default): the described application; `counterpart`: its client | --- # client elixir (Protobuf) Source: https://apigenie.pl/docs/reference/generator/client-elixir-protobuf Generate an Elixir gRPC client on [elixir-grpc](https://hexdocs.pm/grpc) from a `.proto` file and the files it imports. ## Usage ```sh apigenie-generator client elixir [...OPTIONS] INPUT_FILE.proto ``` ## Description `client elixir` generates a gRPC client when its input is a `.proto` file (an OpenAPI document gives a Req client, see [client elixir](/docs/reference/generator/client-elixir)). The input is the file declaring the services; the files it imports are looked up in the directories of `--proto-path`, then next to it, and `google/protobuf`, `google/api` and `google/rpc` imports need no files. Only proto3 files are read. The generator writes a Mix project (Elixir 1.18 or later) depending on `grpc` 1.0 with Mint, and `protobuf` 0.17. Its code passes `mix format --check-formatted`, `mix compile --warnings-as-errors`, `mix credo --strict` and `mix dialyzer`. No protoc is needed: the modules of the messages, services and stubs are those protoc-gen-elixir would write, named the same way (see [server elixir-grpc](/docs/reference/generator/server-elixir-grpc)). | File | Contents | | ---- | -------- | | `lib/proto/<path>.pb.ex` | per `.proto` file, a module per message, map entry and enum, and the `<Service>.Service` and `<Service>.Stub` modules of each service | | `lib/<app>.ex` | the root module: `connect/2`, the types of options and results | | `lib/<app>/<service>.ex` | a module per service, with a function per method | | `mix.exs`, `.formatter.exs`, `.gitignore`, `README.md` | the project | ```elixir {:ok, channel} = CarpetExpressClient.connect("localhost:50051") {:ok, shipment} = CarpetExpressClient.ShipmentService.get_shipment( channel, %Carpetexpress.V1.GetShipmentRequest{id: "sh_1"}, timeout: 5_000, metadata: %{"x-tenant" => "acme"} ) ``` ### Functions A method is a function named as GRPC.Stub names it (`GetShipment` is `get_shipment/3`): | Method | Arguments | Returns | | ------ | --------- | ------- | | unary | channel, request, options | `{:ok, response}` or `{:error, %GRPC.RPCError{}}` | | server streaming | channel, request, options | `{:ok, stream}`: an `Enumerable` of `{:ok, message}` and `{:error, %GRPC.RPCError{}}` items | | client streaming | channel, `Enumerable` of requests, options | `{:ok, response}` or `{:error, %GRPC.RPCError{}}` | | bidirectional streaming | channel, `Enumerable` of requests, options | like server streaming; the requests are sent from a task while the responses arrive | The options are those of GRPC.Stub: `:timeout` (milliseconds; 10 seconds for unary calls) or `:deadline` (a `DateTime`), `:metadata` (a map of headers) and `:compressor`. `connect/2` passes its options to `GRPC.Stub.connect/2` with the Mint adapter unless `:adapter` names another one. The Stub modules remain available for calls the functions don't cover, e.g. interleaving the requests and responses of a bidirectional stream. Deprecated methods are `@deprecated`. ### Limitations - `extend` blocks aren't generated; a warning names the file. - `(google.api.field_behavior) = REQUIRED` isn't checked before sending. ## Options ### -I, --proto-path DIR Directory searched for the imported files, like protoc's `--proto_path`. Can be repeated. Given with an OpenAPI input, it is an error. ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### -o, --output Directory where the project is generated. ### --package-name Name of the Mix application, converted to snake_case (`carpet-express-client` gives `carpet_express_client` and the root module `CarpetExpressClient`). Defaults to the name of the output directory. ### --package-version Version of the Mix project; `0.1.0` by default. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p package_prefix=acme.proto`. Options given on the command line take precedence over the configuration file. ## Configuration The options are in the `protobuf` section of `client_elixir`; the others concern OpenAPI input. ```yaml version: '1.0' commands: generate: client_elixir: protobuf: app: carpet_express_client dev_deps: false ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `app` | string | name of the output directory | Name of the Mix application (snake_case, the directory under `lib/`); also set by `--package-name`. | | `namespace` | string | the application in PascalCase | Root module of the client's modules, e.g. `CarpetExpressClient`. | | `package_prefix` | string | | Prepended to the package in the names of the modules of messages, enums and services, like protoc-gen-elixir's `package_prefix`. | | `dev_deps` | boolean | `true` | Lists credo and dialyxir as dependencies of `mix.exs` (`only: :dev`). | --- # server elixir-grpc Source: https://apigenie.pl/docs/reference/generator/server-elixir-grpc Generate an Elixir gRPC server on [elixir-grpc](https://hexdocs.pm/grpc_server) from a `.proto` file and the files it imports. ## Usage ```sh apigenie-generator server elixir-grpc [...OPTIONS] INPUT_FILE ``` ## Description The input is the `.proto` file declaring the services. The files it imports are looked up in the directories of `--proto-path`, then next to it; `google/protobuf`, `google/api` and `google/rpc` imports need no files. Only proto3 files are read. The generator writes a Mix project (Elixir 1.18 or later) depending on `grpc_server` 1.0 and `protobuf` 0.17. Its code passes `mix format --check-formatted`, `mix compile --warnings-as-errors`, `mix credo --strict` and `mix dialyzer`. No protoc is needed: the modules of the messages are those protoc-gen-elixir would write. | File | Contents | | ---- | -------- | | `lib/proto/<path>.pb.ex` | per `.proto` file, a module per message, map entry and enum (`use Protobuf`), and a `<Service>.Service` module per service (`use GRPC.Service`) | | `lib/<app>/<service>.ex` | a behaviour per service, with a callback per method | | `lib/<app>/<service>/server.ex` | the `GRPC.Server` of the service, calling the configured implementation | | `lib/<app>/endpoint.ex`, `application.ex` | the endpoint running the servers, and the application starting it | | `config/config.exs`, `config/runtime.exs` | the port (50051, or `PORT`) | | `mix.exs`, `.formatter.exs`, `.gitignore`, `README.md` | the project | ```sh mix deps.get mix run --no-halt ``` ### Messages Modules are named as protoc-gen-elixir names them: the package camelized segment by segment, then the message (`carpetexpress.v1.Shipment.Parcel` is `Carpetexpress.V1.Shipment.Parcel`). The `(elixirpb.file).module_prefix` option of a file, or the `package_prefix` option of the generator, change the prefix. Fields are declared with `field` and the options protoc-gen-elixir writes (`repeated`, `proto3_optional`, `json_name`, `map`, `enum`, `oneof`, `packed`, `deprecated`), oneofs with `oneof`. Comments become `@moduledoc` and `#` comments. Well-known types are the modules of the `protobuf` package (`Google.Protobuf.Timestamp`, ...), the types of `google/api`, `google/rpc` and `google/type` those of `googleapis`. ### Services A service is a behaviour, `<Root>.<Service>`, with a callback per method named as GRPC.Server names it (`GetShipment` is `get_shipment/2`). Implement it and configure the implementation: ```elixir config :carpet_express, CarpetExpress.ShipmentService, impl: MyApp.ShipmentService ``` ```elixir defmodule MyApp.ShipmentService do @behaviour CarpetExpress.ShipmentService @impl true def get_shipment(request, _stream), do: MyApp.Shipments.fetch!(request.id) @impl true def import_shipments(requests, _stream) do imported = requests |> Enum.map(&MyApp.Shipments.book/1) |> length() %Carpetexpress.V1.ImportShipmentsResponse{imported: imported} end # ... end ``` A callback receives the request, or an `Enumerable` of the requests of a client or bidirectional stream, and the `GRPC.Server.Stream` of the call. It returns the response, or an `Enumerable` of the responses of a server or bidirectional stream, which the server sends one by one. Raising `GRPC.RPCError` answers with its status. Methods without an implementation answer UNIMPLEMENTED. ### Limitations - `extend` blocks (custom options, proto2-style extensions) aren't generated; a warning names the file. - `(google.api.field_behavior) = REQUIRED` isn't checked: protobuf-elixir has no required fields. - HTTP/JSON transcoding (`google.api.http`) isn't turned on. ## Options ### -I, --proto-path DIR Directory searched for the imported files, like protoc's `--proto_path`. Can be repeated. ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### -o, --output Directory where the project is generated. ### --package-name Name of the Mix application, converted to snake_case (`carpet-express` gives `carpet_express` and the root module `CarpetExpress`). Defaults to the name of the output directory. ### --package-version Version of the Mix project; `0.1.0` by default. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p dev_deps=false`. Options given on the command line take precedence over the configuration file. ## Configuration ```yaml version: '1.0' commands: generate: server_elixir_grpc: app: carpet_express package_prefix: carpet_express.proto ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `app` | string | name of the output directory | Name of the Mix application (snake_case, the directory under `lib/`); also set by `--package-name`. | | `namespace` | string | the application in PascalCase | Root module of the modules of the application (behaviours, servers, endpoint), e.g. `CarpetExpress`. | | `package_prefix` | string | | Prepended to the package in the names of the modules of messages, enums and services, like protoc-gen-elixir's `package_prefix`. | | `dev_deps` | boolean | `true` | Lists credo and dialyxir as dependencies of `mix.exs` (`only: :dev`). | --- # server absinthe Source: https://apigenie.pl/docs/reference/generator/server-absinthe Generate an [Absinthe](https://hexdocs.pm/absinthe) server from a GraphQL schema (SDL). ## Usage ```sh apigenie-generator server absinthe [...OPTIONS] INPUT_FILE ``` The input is a GraphQL schema: a `.graphql`, `.graphqls` or `.gql` file. It must be valid and self-contained, and have a query type. ## Description The generator writes a Mix project (Elixir 1.18 or later) that depends on Absinthe 1.12, Absinthe.Plug, Plug, Bandit and Jason. The code passes `mix format --check-formatted`, `mix compile --warnings-as-errors` and `mix credo --strict`, and `mix run --no-halt` serves it. | File | Contents | | ---- | -------- | | `priv/graphql/schema.graphql` | the schema, as written | | `lib/<app>/schema.ex` | the schema module: `import_sdl` of the schema, and `hydrate/2` attaching resolvers, type resolution and scalars | | `lib/<app>/schema/directives.ex` | the custom directives of the schema, declared in a prototype schema | | `lib/<app>/resolvers/<type>.ex` | a behaviour per object type with root fields or fields with arguments, with a callback per field | | `lib/<app>/resolvers.ex` | the lookup of the configured implementations | | `lib/<app>/type_resolver.ex` | the object types of the values of interfaces and unions | | `lib/<app>/scalars.ex` | the parsing and serialization of the custom scalars | | `lib/<app>/loader.ex` | the Dataloader source, with the `dataloader` option | | `lib/<app>/router.ex` | the Plug router: the GraphQL endpoint, and GraphiQL in the dev environment | | `lib/<app>/application.ex` | the application, starting Bandit | ### The schema `<Root>.Schema` imports the schema from `priv/graphql/schema.graphql` with `import_sdl`, and recompiles when the file changes. Absinthe keeps everything the schema says: - descriptions of types, fields, arguments, enum values and input fields; - deprecations of fields, arguments, input fields and enum values; - default values, interfaces implementing interfaces, and `@oneOf` input types, which it enforces; - type extensions. Two things need code next to the SDL: - **Custom directives** need a prototype schema. Absinthe applies only the directives its prototype schema declares, so `<Root>.Schema.Directives` declares the directives of the schema, along with the enums their arguments use. - **Custom scalars** need parse and serialize functions, which `hydrate/2` attaches. ### Resolvers and implementations Root fields, and fields with arguments, are resolved by the module of their type: `<Root>.Resolvers.Query`, `<Root>.Resolvers.Mutation`, `<Root>.Resolvers.Warehouse`... Each one is a behaviour with a callback per field: - a root field's callback receives the arguments (a map with atom keys, as Absinthe gives them) and the resolution; - a nested field's callback receives the parent value first. ```elixir @callback track_shipment(args :: track_shipment_args(), resolution :: Absinthe.Resolution.t()) :: Resolvers.result() ``` Implement the behaviours and configure the implementations in `config/config.exs`. Regenerating never overwrites them: ```elixir config :carpet_express, CarpetExpress.Resolvers.Query, impl: MyApp.Query ``` A callback returns `{:ok, value}` or `{:error, reason}`. Fields without an implementation answer an error ("Query.trackShipment is not implemented"). The other fields read their value from the parent value with Absinthe's default resolution: the key that is the snake_case name of the field, in a map or a struct. ### Interfaces and unions `<Root>.TypeResolver` resolves the object type of a value of an interface or a union: - a map with a `__typename` key resolves to the type it names; - other values go to the `resolve_type(type, value, resolution)` callback of the implementation configured for `<Root>.TypeResolver`. ### Scalars `<Root>.Scalars` has a `parse_<scalar>/1` and a `serialize_<scalar>/1` function per custom scalar. Until an implementation of its `parse/2` and `serialize/2` callbacks is configured, they pass string, number and boolean literals and values through as they are. ### Router and GraphiQL `<Root>.Router` parses JSON, form and multipart bodies (`Absinthe.Plug.Parser`) and forwards the endpoint's path (`/graphql`) to `Absinthe.Plug`, which takes queries and mutations by POST and GET. When the application's `:graphiql` setting is true, as `config/dev.exs` sets it, the router also serves GraphiQL at `/graphiql`. Bandit listens on `127.0.0.1:4000` in development and on all interfaces in production; the `PORT` environment variable sets the port. ### Dataloader With `dataloader: true`, `context/1` puts a Dataloader in the context of the resolvers (`resolution.context.loader`), with a `Dataloader.KV` source named `<Root>.Loader`. Its batches go to the `fetch(batch_key, ids)` callback of the implementation configured for `<Root>.Loader`. ### Limitations - Subscriptions get their resolvers, but the application has no subscription transport (Absinthe.Phoenix channels), so they aren't served. - Absinthe's introspection doesn't expose the `specifiedByURL` of scalars, although the schema keeps `@specifiedBy`. - Absinthe can't import extensions of a root type that the schema definition gives another name (`schema { query: Root }` with `extend type Root`). The generator reports this as an error. - The arguments of custom directives can only have built-in scalar and enum types. ## Options ### -c, --config Path to the configuration file to use. See the "Configuration" section for more information. ### --no-config Doesn't look for `apigenie.yaml` (or `.apigenie.yaml`) in the current directory and its parents. ### -o, --output Directory where the project is generated. ### --package-name Name of the Mix application, converted to snake_case. `carpet-express` gives the application `carpet_express` and the root module `CarpetExpress`. Defaults to the name of the output directory. ### --package-version Version of the Mix project; `0.1.0` by default. ### -p, --property NAME=VALUE Sets a generator option (see below), e.g. `-p dataloader=true`. Options given on the command line take precedence over the configuration file. ## Configuration ```yaml version: '1.0' commands: generate: server_absinthe: app: carpet_express path: /api/graphql dataloader: true ``` | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `app` | string | name of the output directory | Name of the Mix application (snake_case, the directory under `lib/`); also set by `--package-name`. | | `namespace` | string | the application in PascalCase | Root module of the generated modules, e.g. `CarpetExpress`. | | `path` | string | `/graphql` | Path of the GraphQL endpoint. | | `graphiql_path` | string | `/graphiql` | Path of GraphiQL, served in the dev environment. | | `dataloader` | boolean | `false` | Adds a Dataloader to the context of the resolvers. | | `dev_deps` | boolean | `true` | Lists credo as a dependency of `mix.exs` (`only: [:dev, :test]`). | --- # server Source: https://apigenie.pl/docs/reference/server `apigenie-server` serves an OpenAPI 3.0, 3.1 or 3.2 document (or a Swagger 2.0 one, see [Swagger 2.0 documents](#swagger-20-documents)) as a mock API, the channels of an AsyncAPI 3.x document over WebSocket, HTTP and MQTT, the gRPC services of a `.proto` file, or a GraphQL schema as a GraphQL endpoint with subscriptions. It answers with the document's examples, with stubs, or with data generated from its schemas, validates requests, responses and messages, logs every exchange, and can make the network slow or unreliable on purpose. In front of a real server (`--proxy-to`) it becomes a validating proxy that contract-tests the server's traffic against the OpenAPI or Swagger document, the GraphQL schema, the AsyncAPI document (WebSocket, MQTT and HTTP channels) or the `.proto` file, with coverage and JSON or JUnit reports. It is installed next to `apigenie` by every package (archives, `.deb`, `.rpm`, npm, Homebrew) and has a Docker image of its own. ## Usage ```sh apigenie-server [OPTIONS] SPEC... apigenie-server session export DIR --har FILE ``` ```sh apigenie-server openapi.yaml curl http://127.0.0.1:4010/pets ``` The first line the server writes says where it listens: ```text Listening on http://127.0.0.1:4010/v1 (spec: openapi.yaml, 12 operations) Admin API: http://127.0.0.1:4010/__apigenie Dashboard: http://127.0.0.1:4010/__apigenie/ui ``` The [dashboard](#dashboard) shows the traffic as it happens, the operations and what answers them, the scenarios, the contract report, the data and, for an AsyncAPI document, the channels and their clients, in the browser. A GraphQL schema (`.graphql`, `.graphqls`, `.gql`) is served at `/graphql`: ```sh apigenie-server schema.graphql curl -X POST http://127.0.0.1:4010/graphql -H 'Content-Type: application/json' \ -d '{"query": "{ books { title author { name } } }"}' ``` ```text Listening on http://127.0.0.1:4010/graphql (spec: schema.graphql, 12 operations) GraphiQL: http://127.0.0.1:4010/graphql Subscriptions: ws://127.0.0.1:4010/graphql Admin API: http://127.0.0.1:4010/__apigenie Dashboard: http://127.0.0.1:4010/__apigenie/ui ``` In front of a real server it checks the traffic against the document instead of mocking it ([contract testing](#contract-testing-validating-proxy)): ```sh apigenie-server openapi.yaml --proxy-to http://localhost:8080 ``` ```text Listening on http://127.0.0.1:4010/v1 (spec: openapi.yaml, 12 operations) Proxy: http://localhost:8080 (requests: log, responses: log, unknown routes: forward, mode: always) Admin API: http://127.0.0.1:4010/__apigenie Dashboard: http://127.0.0.1:4010/__apigenie/ui ``` With `--log-format json` it is a JSON object (`{"event":"listening","url":...,"port":...}`), and every request after it is logged as one JSON object per line, which suits tools and tests. Several documents are served together on one port, each below a prefix of its own ([several services](#several-services)): ```sh apigenie-server pets.yaml orders.yaml events.asyncapi.yaml ``` ```text Listening on http://127.0.0.1:4010 (3 services) pets http://127.0.0.1:4010/pets/v1 (spec: pets.yaml, 12 operations) orders http://127.0.0.1:4010/orders (spec: orders.yaml, 5 operations) events http://127.0.0.1:4010/events (spec: events.asyncapi.yaml, 3 channels) Admin API: http://127.0.0.1:4010/__apigenie Dashboard: http://127.0.0.1:4010/__apigenie/ui ``` ## Options | Option | Default | Description | | --- | --- | --- | | `--host HOST` | `127.0.0.1` | The address to listen on; `0.0.0.0` or `::` for every interface. Also `APIGENIE_SERVER_HOST`. | | `--port`, `-p PORT` | `4010` | The port; `0` picks a free one. Also `APIGENIE_SERVER_PORT`. | | `--base-path PATH` | the servers' paths | Serve the operations under this prefix instead of the paths of the document's `servers`. | | `--config`, `-c FILE` | see below | The configuration file. | | `--no-config` | | Don't look for a configuration file. | | `--watch`, `-w` | | Reload when the document or the configuration changes. A broken version is reported and the previous one stays. | | `--seed N` | random | Seed of generated data and simulated failures: the same seed repeats a run. | | `--dynamic`, `-d` / `--static` | `--static` | Generate every response from its schema, or answer with examples first. | | `--strict` | | Answer 500 when a response the mock makes doesn't match the document. With `--proxy-to`: answer requests that don't match the document or schema without the upstream (422 for OpenAPI, GraphQL errors for GraphQL) and replace upstream responses that don't (502); AsyncAPI: messages that don't match aren't forwarded, the client gets a problem message instead; gRPC: requests and upstream messages are checked before they are forwarded, and a call that breaks the contract ends with INVALID_ARGUMENT or INTERNAL. | | `--log-format FORMAT` | `stylish` | `stylish` or `json`. | | `--log-level LEVEL` | `info` | `debug`, `info`, `warn` or `error`. | | `--log-file FILE` | | Also write the log to a file, as JSON lines. | | `--log-bodies`, `--log-headers` | | Log request and response bodies, request headers. | | `--quiet`, `-q` | | Only the first line on the standard output. | | `--latency MS` | | Delay every response: a number of milliseconds, or `MIN-MAX`. | | `--jitter MS` | | A random extra delay of up to this many milliseconds. | | `--drop-rate RATE` | | The share of responses (0 to 1) whose connection is reset. | | `--cors on\|off` | `on` | Answer CORS preflights and add CORS headers. | | `--admin on\|off` | `on` | The admin API under `/__apigenie`, and the [dashboard](#dashboard) at `/__apigenie/ui`. | | `--scenario NAME` | | Switch on a scenario of the configuration or the document; can be repeated. | | `--script FILE` | | A JavaScript module with hooks (see below); can be repeated. | | `--crud` | | Serve the collection resources of an OpenAPI document (`/things` and `/things/{id}`) from the [document store](#crud-resources): what a `POST` creates, a `GET` lists. The same as `db.crud: auto`. | | `--db-file FILE` | | Keep the [document store](#document-store) in this JSON file: loaded at the start, written when the server stops. Also `APIGENIE_SERVER_DB_FILE`. | | `--asyncapi-server NAME` | every server | AsyncAPI: serve this server of the document only. | | `--mqtt-port PORT` | `1883`, `8883` with `--tls-cert` | AsyncAPI: the port of the MQTT broker; `0` picks a free one. Also `APIGENIE_SERVER_MQTT_PORT`. | | `--proto-path`, `-I DIR` | the file's directory | gRPC: where imports are looked up, like protoc's `--proto_path`; can be repeated. Also `APIGENIE_SERVER_PROTO_PATH` (separated by `:`, `;` on Windows). | | `--stubs FILE\|DIR` | | A YAML or JSON stub file, or a directory of them; can be repeated. gRPC: stubs (see [Stubs](#stubs)). OpenAPI: the exchanges `--record-dir` wrote (see [Record and replay](#contract-testing-validating-proxy)). Also `APIGENIE_SERVER_STUBS`. | | `--proxy-to TARGET` | | OpenAPI: the real server (`http[s]://host[:port][/prefix]` or `HOST:PORT`) that gets every request, both checked against the document (see [Contract testing](#contract-testing-validating-proxy)). GraphQL: the real server's endpoint (`https://host/graphql`), requests and answers checked against the schema (see [GraphQL contract testing](#contract-testing-of-a-graphql-api)). AsyncAPI: the real WebSocket and HTTP server (`ws[s]://host[:port][/prefix]` or `http[s]://...`) and the real MQTT broker (`mqtt[s]://host[:port]`), one or both (repeat the option), the messages of both sides checked against the document (see [WebSocket contract testing](#contract-testing-of-a-websocket-api), [MQTT contract testing](#contract-testing-of-mqtt-channels) and [HTTP channels](#contract-testing-of-http-channels)). gRPC: an upstream server for the calls no stub answers, `HOST:PORT` for cleartext HTTP/2 or `https://host[:port]` for HTTP/2 over TLS, its answers checked against the `.proto` file (see [gRPC contract testing](#contract-testing-of-a-grpc-api)). `https://`, `wss://` and `mqtts://` upstreams are verified (see [TLS](#tls)). Also `APIGENIE_SERVER_PROXY_TO` (one target). | | `--tls-cert FILE` | | Serve HTTPS, WSS, gRPC and MQTT over TLS with this PEM certificate chain (the server's certificate first); needs `--tls-key`. See [TLS](#tls). Also `APIGENIE_SERVER_TLS_CERT`. | | `--tls-key FILE` | | The unencrypted PEM private key of `--tls-cert`. Also `APIGENIE_SERVER_TLS_KEY`. | | `--tls-client-ca FILE` | | Require a client certificate issued by the authorities of this PEM file on every HTTPS, WSS, gRPC and MQTTS connection (mTLS); the subject goes into the log and the contract report. Needs `--tls-cert`. See [Client certificates](#client-certificates-mtls). Also `APIGENIE_SERVER_TLS_CLIENT_CA`. | | `--ca-cert FILE` | the system's roots | A PEM file of certificate authorities trusted for `https://`, `wss://` and `mqtts://` upstreams, besides the system's; can be repeated. Also `APIGENIE_SERVER_CA_CERT` (separated by `:`, `;` on Windows). | | `--insecure` | | Don't verify the certificates of `https://`, `wss://` and `mqtts://` upstreams; the traffic stays encrypted. Also `APIGENIE_SERVER_INSECURE=1`. | | `--client-cert FILE` | | A PEM client certificate (chain) presented to `https://`, `wss://`, `mqtts://` and gRPC TLS upstreams that ask for one (mTLS); needs `--client-key`. Also `APIGENIE_SERVER_CLIENT_CERT`. | | `--client-key FILE` | | The unencrypted PEM private key of `--client-cert`. Also `APIGENIE_SERVER_CLIENT_KEY`. | | `--proxy-mode always\|fallback` | OpenAPI and GraphQL `always`, gRPC `fallback` | `always`: the upstream gets every request. `fallback`: OpenAPI: every request still goes to the server, and the mock answers when the server can't be reached, doesn't answer in time, or answers 404 or 501 for an operation of the document (see [Fallback mode](#contract-testing-validating-proxy)); GraphQL: the mock's examples, scenarios and scripts answer first; gRPC: stubs answer first. An AsyncAPI document's proxy relays every message. | | `--record-dir DIR` | | OpenAPI: write the exchanges the server answered as stub files, replayed with `--stubs`. gRPC: write the proxied calls as stub files. GraphQL: write the proxied answers as examples. Needs `--proxy-to`. Also `APIGENIE_SERVER_RECORD_DIR`. | | `--record-har FILE` | | OpenAPI: write every exchange through the proxy (the server's answers and the mock's own) as a HAR 1.2 file when the server stops. Needs `--proxy-to`. Also `APIGENIE_SERVER_RECORD_HAR`. | | `--record DIR` | | Record the session: every service's exchanges and messages in the order they happened, each as a file, and `session.yaml`; an exchange's test is its `X-Apigenie-Test` header (see [Session recording](#session-recording)). Every document, with or without `--proxy-to`. The directory must be new or empty. Also `APIGENIE_SERVER_RECORD`. | | `--record-redact HEADER\|/POINTER` | | What `--record` hides besides `Authorization`, `Cookie`, `Set-Cookie`, `Proxy-Authorization` and `X-Api-Key`: a header name, or a JSON pointer (`/password`) to a value of JSON bodies and messages; can be repeated. | | `--replay DIR` | | Answer from the session `--record` wrote: each request gets the next unused recorded exchange of its test (`X-Apigenie-Test`) that matches it, on every protocol (see [Session replay](#session-replay)). When the server stops, exit status 5 if a recorded exchange was never replayed or a request matched none. Also `APIGENIE_SERVER_REPLAY`. | | `--replay-ignore HEADER\|/POINTER` | | What `--replay` doesn't compare besides dates, `readOnly` fields and volatile headers: a header name, or a JSON pointer (`/id`, `/items/*/id`) into JSON bodies and messages; can be repeated. | | `--replay-missing fail\|generate\|passthrough` | `fail` | What a request no recorded exchange matches gets: a 502 problem naming the closest recording and how the request differs (`fail`), the mock's own answer (`generate`), or the answer of `--proxy-to` (`passthrough`). | | `--replay-update` | | With `--replay-missing passthrough`: record the run and, when the server stops, replace the `--replay` session with it. | | `--proxy-unknown forward\|reject` | `forward` | OpenAPI proxy: forward requests for no operation of the document (reported), or answer them with 404 or 405. | | `--report FILE` | | Write the contract report (coverage and violations) as JSON when the server stops. Also `APIGENIE_SERVER_REPORT`. | | `--report-junit FILE` | | Write the contract report as JUnit XML when the server stops. Also `APIGENIE_SERVER_REPORT_JUNIT`. | | `--fail-on none\|errors\|warnings` | `none` | Exit with 5 when the server stops after violations of that severity. | | `--min-coverage PERCENT` | | Exit with 5 when the server stops with less of the document covered (a GraphQL schema: its fields; an AsyncAPI document: its operations' messages; a `.proto` file: its methods; [several services](#several-services): their combined coverage). | | `--credits`, `--license [LIBRARY]`, `--license-list` | | Like `apigenie credits` and `apigenie license`. | | `--help`, `-h` / `--version`, `-v` | | Show the help (with the exit statuses and the per-request directives), or the version. | ## Exit status | Code | Meaning | | --- | --- | | 0 | Stopped with Ctrl+C (or `POST /__apigenie/shutdown`) | | 2 | Bad options, configuration, scripts or stub files, or an unexpected error | | 3 | The document can't be served (missing, unreadable, not OpenAPI 3, Swagger 2.0 or AsyncAPI 3, no channels the mock serves, a `.proto` file with errors, missing imports or no services) | | 4 | The address can't be used (the port is taken, ...) | | 5 | The contract report failed when the server stopped (`--fail-on`, `--min-coverage`), or the replay did (`--replay`: a recorded exchange was never replayed, or a request matched none) | | 130 | Stopped at once with a second Ctrl+C | ## Swagger 2.0 documents A Swagger 2.0 document (`swagger: "2.0"`) is served, proxied and contract-tested like the OpenAPI 3.2 document `apigenie convert --to openapi32` would make of it. The conversion happens in memory each time the document is read (at startup and on reloads); no file is written. Body and form parameters become request bodies, `produces` and `consumes` become media types, `host`, `basePath` and `schemes` become the servers, and `definitions`, `parameters`, `responses` and `securityDefinitions` move into `components`. Locations stay those of the Swagger file: warnings about the document give its lines, the problems of `x-apigenie-mock` extensions its lines and JSON pointers, and the contract report's `pointer` of each operation is a pointer into it. What the conversion can only approximate (a `collectionFormat` without counterpart, form parameters next to a body parameter, ...) is told once at startup, on the standard error, as an `info:` line with the file's line, column and JSON pointer: ```text info: pets.yaml:7:71: /paths/~1pets/get/parameters/0/collectionFormat: collectionFormat tsv has no counterpart and becomes style form (csv) ``` `GET /__apigenie/spec` returns the converted document, which is what is served. The document is converted alone: one that references other files can't be served (exit status 3) until `apigenie bundle` has put them into it. ## Responses - **Routing.** Paths are served under the path of the document's first server (`/v1` for `https://api.example.com/v1`) and as they are. Literal paths win over templated ones (`/pets/mine` over `/pets/{id}`). An unknown path is answered with 404, an unknown method with 405 and an `Allow` header, both as [problem details](https://www.rfc-editor.org/rfc/rfc9457). OpenAPI 3.2's `query` method and the methods of `additionalOperations` (`COPY`, ...) are routed like the others. - **Status.** The lowest 2xx response, else `2XX`, else the lowest declared, else `default`. - **Body.** Content negotiation follows `Accept` (406 when nothing fits). Without an `Accept`, or with `*/*`, the response takes the media type of the request's body when it has it: a SOAP 1.2 request (`application/soap+xml`) to an operation answering both SOAP versions gets a SOAP 1.2 envelope. In the default static mode the media type's `example`, its first named example, or the schema's example answer; what has none is generated from the schema. Generated values respect types, the formats of the OpenAPI format registry (`uuid`, `date-time`, `int8`, `decimal`, `http-date`, ...; see [Formats](/docs/reference/cli/formats)), patterns, lengths, ranges, `enum`, `const`, `allOf`, `oneOf`, `readOnly` and `writeOnly`. A value for `application/xml`, `text/xml` or a `+xml` type is written as XML (see [XML bodies](#xml-bodies)); a string example is sent as it is. - **Headers.** Response headers are filled in from their examples or schemas. `X-Apigenie-Mock-Operation` and `X-Apigenie-Mock-Source` (`example:cat`, `generated`, `scenario:outage`, `stub:getPet-1`) say where a response came from. - **Stubs.** `--stubs DIR` (or `stubs:` in the configuration) answers with recorded exchanges (see [Record and replay](#contract-testing-validating-proxy)) before the document's examples: a request's `Prefer` and the rules of scenarios and overrides still come first. ## Choosing a response per request The `Prefer` header (or query parameters, which are removed before validation) asks for a particular answer: | `Prefer` | Query | Effect | | --- | --- | --- | | `code=404` | `__code=404` | That status (500 when the operation doesn't declare it). | | `example=cat` | `__example=cat` | That named example. | | `dynamic=true` | `__dynamic=true` | A generated body. | | `delay=1500` | `__delay=1500` | Answer after 1.5 seconds. | | `drop=reset` | | Reset the connection (`close`, `timeout` and `truncate` too). | | `scenario=outage` | | Apply that scenario to this request. | ## Validation and security Requests are checked against their operation: path, query, header and cookie parameters in every style, and JSON, XML, form and multipart bodies. A request that doesn't match is answered with 422 and the problems, a body that can't be parsed (JSON, XML, multipart) with 400, an unknown media type with 415. Security requirements are checked for presence (API keys, HTTP basic and bearer, OAuth2 and OpenID Connect tokens; 401 with `WWW-Authenticate`), and against configured credentials in `enforce` mode (403). Responses the mock makes are checked too: problems are logged and counted in `X-Apigenie-Mock-Violations`, or answered with 500 under `--strict`. Schemas' `format` values are checked as the [OpenAPI format registry](/docs/reference/cli/formats) defines them, for the JSON types each format applies to (`int8` ranges, `http-date`, `decimal`, `language`, ...); `validation.formats: false` turns these checks off. Formats of your own, declared in [`openapi.custom_formats`](/docs/reference/cli/configuration#openapicustom_formats) of the configuration file, are accepted as they are, and an entry there naming a registry format replaces that format's type and its check. ### XML bodies Bodies of `application/xml`, `text/xml` and `+xml` media types are parsed as XML 1.0 with namespaces, mapped to a JSON value as the schema's `xml` objects describe it, and checked against the schema like JSON bodies; the mock, the validating proxy and `apigenie test` do the same, and the mock and `apigenie test --generate` write XML the same way in reverse. For this schema ```yaml Order: type: object xml: {name: order, namespace: "urn:orders", prefix: o} properties: id: {type: integer, xml: {nodeType: attribute}} # OpenAPI 3.0/3.1: attribute: true customer: {$ref: "#/components/schemas/Customer"} # Customer has xml: {name: Buyer} note: {type: string, xml: {nodeType: cdata}} tags: {type: array, items: {type: string, xml: {name: tag}}} lines: type: array xml: {name: Lines, nodeType: element} # 3.0/3.1: wrapped: true items: {$ref: "#/components/schemas/Line"} # Line has xml: {name: Line} ``` the body is ```xml <o:order xmlns:o="urn:orders" id="7"> <Buyer><name>Ann</name></Buyer> <![CDATA[Leave at the <door>]]> <tag>a</tag><tag>b</tag> <Lines><Line sku="x"><quantity>2</quantity></Line></Lines> </o:order> ``` - The root element is named by the schema's `xml.name`, else by the component a `$ref` points to; another name is a `xml-root` violation. A schema without either names no root. - A property is a child element named by its `xml.name` (its own, or that of the schema it references), else by the property name. `nodeType: attribute` makes it an attribute, `text` and `cdata` the element's character data. - An array's items repeat in the parent element, named by the items' `xml.name` or the property name; with `nodeType: element` (`wrapped: true`) they sit in a wrapper element. - A declared `xml.namespace` must match; without one, elements are matched by local name in any namespace. An element with a property's local name in another namespace isn't that property: it is kept as `{namespace}name`, so the property is missing and `additionalProperties: false` reports the element. - Child elements are written in the order the schema declares its properties, as XML Schema sequences require. - Text becomes an integer, number, boolean or null where the schema allows it (white space around such values is ignored); `xsi:nil="true"` is null. Elements and attributes the schema doesn't declare are kept by local name, so `additionalProperties: false` reports them. - A body that isn't well-formed is `malformed-xml` (`request.body.malformed`, answered with 400; `response.body.malformed`), with its line and column. Document type declarations are refused, so external entities and entity expansion are errors. A SOAP service described in WSDL 1.1 is served after [`apigenie convert --to openapi31`](/docs/reference/cli/convert#wsdl-11-input): the converted document describes the SOAP envelopes with these `xml` objects. ## Contract testing (validating proxy) With `--proxy-to`, an OpenAPI document is not mocked: every request goes to the real server, and the mock checks the request and the server's response against the document. (A GraphQL schema and an AsyncAPI document's WebSocket, MQTT and HTTP channels work the same way, with checks of their own: see [GraphQL contract testing](#contract-testing-of-a-graphql-api), [WebSocket contract testing](#contract-testing-of-a-websocket-api), [MQTT contract testing](#contract-testing-of-mqtt-channels) and [HTTP channels](#contract-testing-of-http-channels).) Point your tests, your frontend or a load test at the mock instead of the server, and the traffic becomes a contract test. To send the requests yourself, from `.http` files or generated from the document, with the same checks and reports, use [apigenie test](/docs/reference/cli/test). ```sh apigenie-server openapi.yaml --proxy-to http://localhost:8080 --report contract.json --fail-on errors ``` The upstream gets the request as it came, without the mock's `__` query parameters and `X-Apigenie-Mock-*` headers, with `Via`, `X-Forwarded-For`, `X-Forwarded-Host` and `X-Forwarded-Proto`, and `Accept-Encoding: identity` so that bodies can be checked. A path in the URL (`http://localhost:8080/api`) is put before the request's path. The admin API, CORS preflights and answers of scripts' `onRequest` stay local; the global network simulation, a request's `Prefer: delay=` or `drop=`, and scripts' `onResponse` apply to the upstream's responses. Scenario rules and examples don't: nothing is mocked, unless the server fails in fallback mode (below). **What is checked.** Each problem is a violation with a check, a severity and a location: | Check | Severity | When | | --- | --- | --- | | `request.path-or-method.unknown` | warn | The document has no operation for the method and path. | | `request.operation.deprecated`, `request.parameter.deprecated` | info | A deprecated operation or parameter is used (the response gets `Deprecation: true`). | | `request.authorization.missing`, `request.authorization.rejected` | error | No credentials for any security requirement; credentials other than the configured ones (`security.mode: enforce`). | | `request.authorization.oauth2-scheme`, `request.authorization.oauth2-scope` | warn | `apigenie test` only: a request with an OAuth 2 token for an operation that has no OAuth 2 or OpenID Connect scheme; a token requested with scopes the operation doesn't declare, or without those it requires. | | `request.path.incompatible`, `request.query.incompatible`, `request.header.incompatible`, `request.cookie.incompatible` | error | A parameter is missing or doesn't match its schema (every style). | | `request.query.unknown` | warn | An undeclared query parameter (`validation.unknown_query_parameters: error`). | | `request.content-type.incompatible`, `request.body.malformed`, `request.body.incompatible` | error | The body's media type isn't declared, the body can't be parsed (JSON, XML, multipart), or doesn't match its schema (for XML, see [XML bodies](#xml-bodies)). | | `response.status.unknown` | error for 2xx, else warn | No response covers the status (exact code, then `2XX`, then `default`). | | `response.status.default` | info | Only `default` covers the status. | | `response.status.server-error` | info | The server answered with 5xx. | | `response.content-type.incompatible` | error | The response's media type isn't one of the declared ones. | | `response.body.incompatible`, `response.body.malformed` | error | The body is missing, doesn't match its schema (`writeOnly` properties aren't required), or isn't JSON or XML where the media type says so. | | `response.body.unexpected` | warn | A body where the response declares no content. | | `response.body.unvalidatable` | info | A body that can't be checked: binary, compressed. | | `response.header.incompatible` | error | A declared header is missing when required, or doesn't match its schema. | | `response.header.undefined` | off | A header the response doesn't declare (common ones such as `Date` or `ETag` aside). | `contract.severity` in the configuration changes a check's severity (`error`, `warn`, `info`) or turns it off (`ignore`). **Report or enforce.** By default a proxy reports only (`validation.request` and `validation.response` are `log` unless the configuration says otherwise), since test suites send invalid requests on purpose to see the server refuse them. Every response then carries the violations of its exchange: ```http X-Apigenie-Mock-Source: proxy X-Apigenie-Mock-Operation: getPet X-Apigenie-Mock-Violations: 1 X-Apigenie-Mock-Violations-Detail: [{"location":["response","body","/id"],"check":"response.body.incompatible","code":"type","severity":"error","message":"expected type integer, found string"}] ``` The detail is cut at 8 KB, with a last item counting what was left out. The log has the same violations, `upstreamStatus` for each request, and `fallback: true` for the mock's own answers in fallback mode. `--strict` (or `enforce` in the configuration) turns errors into answers. A request with an error doesn't reach the server: it gets 401 or 403 (security), 415 (media type), 400 (malformed body) or 422. A response with an error is replaced by a 502: ```json { "type": "https://apigenie.pl/mock/errors#invalid-upstream-response", "title": "The upstream response does not match the specification", "status": 502, "detail": "response.body/id: expected type integer, found string", "validation": [{"location": "response.body/id", "code": "type", "message": "expected type integer, found string", "severity": "error", "check": "response.body.incompatible", "path": ["response", "body", "/id"]}], "upstream": {"status": 200, "contentType": "application/json"} } ``` Warnings and notes never block anything. A server that can't be reached is answered with 502 (`upstream-unreachable`, `upstream-bad-response`, `upstream-response-too-large`), one that doesn't answer in time with 504 (`upstream-timeout`). **Fallback mode.** With `--proxy-mode fallback` (or `proxy.mode: fallback`), every request still goes to the server first, but the mock answers itself, as it would without `--proxy-to` (examples, generated data, scenario rules, `Prefer`), when the server can't be reached, doesn't answer in time, or answers 404 or 501 for an operation of the document. A frontend can then work against a server that is down or only partly implemented. The answer says so: ```http X-Apigenie-Mock-Source: generated X-Apigenie-Mock-Upstream-Status: 404 ``` The log record has `fallback: true` and `upstreamStatus` (absent when the server didn't answer). The server's 404 or 501 isn't checked against the document, so it isn't a `response.status.unknown` violation. Requests for no operation of the document are not the mock's to answer: the server's 404 reaches the client. An answer that isn't HTTP or is too large is still a 502: the server did answer. **Record and replay.** `--record-dir DIR` writes each exchange the server answered as a stub file, `getPet-1.yaml` (`GET_health-1.yaml` for a route of no operation; numbers already taken are skipped), and `--stubs DIR` makes a mock without `--proxy-to` answer with them: ```sh apigenie-server openapi.yaml --proxy-to http://localhost:8080 --record-dir recorded apigenie-server openapi.yaml --stubs recorded ``` ```yaml stubs: - name: getPet-1 operationId: getPet request: method: GET path: /pets/1 # as the client sent it, without the proxy's prefix query: full=true # without the mock's __ parameters headers: {Accept: application/json, Authorization: "[redacted]"} # match: {headers: [X-Tenant]} # these request headers must be equal too response: status: 200 headers: {Content-Type: application/json, ETag: '"v1"'} body: {id: 1, name: Rex} # JSON; text: for other text, base64: for bytes ``` A request matches a stub when its method, path, query (in any order) and body (JSON compared as JSON) are the same; headers count only when `match.headers` names them. The first stub that matches answers, files in name order; its source is `stub:getPet-1`. Requests still go through security and request validation; the answer isn't checked again. Answers the mock gave or replaced (fallback, `--strict`) aren't recorded. The values of `Authorization`, `Cookie`, `Set-Cookie`, `Proxy-Authorization` and `X-Api-Key` are written as `[redacted]` (`proxy.redact` sets the list), and what the connection or the proxy adds (`Host`, `User-Agent`, `Via`, `X-Forwarded-*`, `Date`, ...) is left out. A recorded header that matters for matching has to be restored by hand. **HAR.** `--record-har session.har` writes every exchange through the proxy as an HTTP Archive (HAR 1.2) when the server stops: the server's answers and the mock's own (fallback answers, rejected requests, replaced responses, the problems of a failed server). Each entry's `comment` is `proxy` or `mock`, and `_apigenie` has the source, the operation, the server's status, `fallback` and the violations. Headers of `proxy.redact` are written as `[redacted]`; binary bodies are in Base64, and bodies above 1 MiB are left out (their sizes stay). Browsers' developer tools, Postman and HAR viewers read it. **Coverage.** The mock counts what the traffic exercised: each operation, its request body (seen with a 2xx or 3xx answer) and each declared response (`200`, `4XX`, `default`). An operation is `covered` when all of these were seen, `partial`, or `not-tested`; requests for no operation are listed as undocumented. Mocked traffic is counted too, without `--proxy-to` or in fallback mode; the report tells the two apart: `summary.sources` and each operation's `sources` count the answers of the server (`proxy`) and of the mock (`mock`, also the problems of a failed server and rejected requests), `mockedResponses` the statuses the mock answered with, and, when the mock answered any, the summary printed at the end has a `Sources:` line. **Reports.** `GET /__apigenie/contract` returns the report at any time, and `DELETE` starts it over (a reload does too). When the server stops (Ctrl+C or `POST /__apigenie/shutdown`) it prints a summary, writes `--report` (JSON) and `--report-junit` (one test case per operation, skipped when not tested, failed with error violations; one per undocumented route; one for the coverage threshold), and exits with 5 when `--fail-on` or `--min-coverage` isn't met. In CI: ```sh apigenie-server openapi.yaml --proxy-to http://localhost:8080 --port 4010 \ --report contract.json --report-junit contract.xml --fail-on errors --min-coverage 80 & API_URL=http://localhost:4010 npm test curl -X POST http://localhost:4010/__apigenie/shutdown wait $! # 5 when the contract failed ``` The JSON report has `summary` (requests, their sources, violations by severity, coverage of branches and operations, the gate with its reasons), `operations` (the JSON `pointer` of the operation in the document, e.g. `/paths/~1pets/get`, requests, responses seen and missing, undeclared statuses, sources and the mock's statuses, violations by check), `undocumented` and `violations` (each distinct one with its count and the first time it was seen). Its `formatVersion` is the version of the format, which changes only when a member is removed or changes meaning (see [Compatibility](/docs/reference/cli/compatibility)); `version`, its former name, is deprecated and will be removed in the next release. The JSON Schema of the members the OpenAPI, AsyncAPI, GraphQL and gRPC reports share is [apigenie-contract-report.json](https://apigenie.pl/schemas/apigenie-contract-report.json). ## Network simulation Latency (fixed, uniform, normal or log-normal), jitter, dropped connections (reset, closed without an answer, never answered, or truncated bodies) and bandwidth limits apply to every response, an operation's, a scenario's or one request's. ## Scenarios and extensions A scenario is a named set of rules switched on and off together. A rule matches requests (by `operationId`, `method`, `path`, `tag`, headers or query) and sets the status, an example, a literal body, headers, the network simulation, or a probability. Scenarios come from the configuration, from `x-apigenie-mock` in the document, or from the admin API. ```yaml paths: /orders: get: x-apigenie-mock: delay: 200 headers: {X-Region: eu} scenarios: empty: {body: []} broken: {status: 500, drop: {probability: 0.5, mode: reset}} ``` A schema can name the [faker](/docs/reference/cli/faker) provider that generates its values with `x-apigenie-mock-faker` or `x-faker`: a name (`person.firstName`, `internet.email`, `location.city`...), `{"number.int": [1, 10]}` with arguments by position, `{"string.alpha": {length: 6}}` with arguments by name, or `{provider: number.int, args: [1, 10], options: {...}}`. Arguments are numbers, strings and booleans. Names of older faker.js versions (`name.firstName`, `address.city`, `datatype.uuid`) work too. An unknown name, or arguments the provider doesn't take, are answered with 500 and faker's message. Records (`person.profile`, `location.address`, `finance.card`...) are objects, or their JSON text for a string schema. ```yaml properties: owner: {type: string, x-faker: person.fullName} age: {type: integer, x-faker: {number.int: [18, 90]}} code: {type: string, x-faker: {string.alpha: {length: 6, casing: upper}}} customer: {type: object, x-faker: {person.profile: {country: PL}}} ``` With `generation.smart_names`, plain string properties called like `email`, `first_name`, `city`, `zip_code` or `phone` get such values without an extension. `generation.null_probability` sets how often a schema with OAS 3.0 `nullable: true` is answered with `null`. `x-apigenie-mock` on an operation (or its path item) accepts `status`, `example`, `dynamic`, `body`, `body_file`, `headers`, `latency`, `jitter`, `drop`, `bandwidth`, `delay`, `probability`, `validate_request`, `scenarios` and `resource` (see [CRUD resources](#crud-resources)); on a response, `delay`, `latency`, `jitter` and `headers`. ## AsyncAPI For an AsyncAPI 3.x document the mock plays the application the document describes. Servers with the `ws`, `http` and `mqtt` protocols are served (and `wss`, `https` and `mqtts` over TLS with `--tls-cert`, cleartext without). Kafka and AMQP channels aren't served, but are read from a real cluster or broker with `--proxy-to kafka://` ([below](#contract-testing-of-kafka-channels)) and `--proxy-to amqp://` ([below](#contract-testing-of-amqp-channels)); the others are listed as not served. - **WebSocket.** A client connects to a channel's address under its server's `pathname` (`ws://127.0.0.1:4010/v1/rooms/42` for `rooms/{roomId}`). It gets the messages of the channel's `send` operations, one after another, every second by default: the message's examples in turn, or values generated from its payload schema (always with `--dynamic`). What it sends is checked against the `receive` operations: a match is answered with the operation's `reply`, anything else with a problem details message. - **HTTP.** A `receive` operation takes messages with `POST` (or its HTTP binding's method) and answers 202, or 200 with its reply; an invalid message gets 422. A `send` operation answers `GET` with a message. - **MQTT.** A broker on its own port takes subscriptions to the channels' topics (wildcards included: a parameter of an address takes its default, first enum value or example) and publishes the `send` operations' messages with their MQTT binding's QoS. Messages clients publish go to the other subscribers, as with any broker, once they match a `receive` operation; its reply is published to the reply channel's topic. MQTT 3.1.1 and 5 clients are served; MQTT 5 subscribers also get the MQTT 5 properties of a message: its headers as user properties (the first example's, else generated from the header schemas), and the payload format indicator, content type, correlation data and response topic of its MQTT binding (generated from their schemas when they are schemas) and the operation binding's message expiry. An MQTT 5 client's properties and user properties are checked as the [MQTT proxy](#contract-testing-of-mqtt-channels) checks them; its request is answered on the topic of its reply address user property, or on its Response Topic when the reply channel has `address: null`, with its correlation id and Correlation Data. Retained messages expire with their Message Expiry Interval. `x-apigenie-mock` on a `send` operation sets `interval` (milliseconds), `count` (messages per client, 0 for no limit), `jitter`, `messages` (the names to send) and `publish: false`. ```yaml operations: sendNotices: action: send channel: {$ref: '#/channels/room'} x-apigenie-mock: {interval: 500, count: 10} ``` The log has `ws-open`, `ws-in`, `ws-out` and `ws-close` events for WebSocket clients and `mqtt-subscribe`, `mqtt-in`, `mqtt-out` and `mqtt-unsubscribe` ones for MQTT, with the channel's address, the operation and the message. When the server stops, WebSocket clients get the close code 1001 and MQTT 5 clients a DISCONNECT; after a reload they reconnect to the new version. ### Contract testing of a WebSocket API With `--proxy-to ws://host:port` an AsyncAPI document's WebSocket channels aren't mocked: each client of a channel gets a connection to the real server, and every message of either side is checked against the document. Point a frontend or a test suite at the mock instead of the server. ```sh apigenie-server chat.yaml --proxy-to ws://localhost:9000 --report contract.json --fail-on errors ``` The mock connects to the server first, with the client's path under the URL's prefix, its query, `Origin`, cookies, `Authorization` and `Sec-WebSocket-Protocol`, plus `Via` and `X-Forwarded-*`; the client gets the subprotocol the server chose. A server that can't be reached answers the client's handshake with 502 (504 when it doesn't answer in time), and a server that refuses it (401, 404, ...) has its answer passed on. What clients send is checked against the channel's `receive` operations, what the server sends against its `send` operations. A client's message of an operation with a `reply` on the same channel waits for its reply: the first message the server sends that is one of the reply's messages is checked as that reply (logged as `name (reply)` with the request's operation), not against the `send` operations. Messages are relayed whole (fragments are put together first); pings are answered on each side; a close frame goes on with its code. No WebSocket extension is negotiated (`permessage-deflate` is left out of the handshake). HTTP channels go to the same server (see [below](#contract-testing-of-http-channels)); the MQTT channels to the broker of a second `--proxy-to mqtt://...`, or they keep being mocked. | Check | Severity | When | | --- | --- | --- | | `message.incompatible` | error | The message matches none of the messages of the channel's operations in its direction. | | `message.malformed` | error | The message isn't JSON where JSON is expected. | | `message.frame.unexpected` | warn | A binary frame for a text or JSON message, or a text frame for a binary one (`image/png`, `application/octet-stream`, Protobuf, Avro, ...). | | `message.qos.unexpected` | warn | MQTT: a client published at a QoS other than the one of the operation's MQTT binding. | | `channel.parameter.invalid` | error | A parameter of the channel's address (a path segment, a topic level) has a value outside the parameter's `enum`. | | `channel.receive.undeclared` | warn | A client sent a message on a channel the application has no `receive` operation for. | | `channel.send.undeclared` | warn | The server sent a message on a channel it has no `send` operation for. | | `message.reply.uncorrelated` | error | The reply's `correlationId` (in the payload) isn't the request's, or is missing. | | `operation.query.invalid` | error | HTTP channels: the request's query doesn't match the `query` of the operation's HTTP binding. | | `message.headers.invalid` | error | HTTP channels: the request's or the reply's header fields don't match the message's `headers` or its HTTP binding's `headers`. | `validation.request` sets what happens to the clients' messages and `validation.response` to the server's: `log` by default for a proxy (reported, and forwarded), `enforce` with `--strict`. In enforce mode a message with an error isn't forwarded, and the client gets a problem message instead (`asyncapi.validate_incoming` stays the mock's setting): ```json {"type": "https://apigenie.pl/mock/errors#invalid-upstream-message", "title": "The upstream's message doesn't match the specification", "status": 422, "channel": "rooms/1", "direction": "upstream", "validation": [{"location": "message/text", "code": "type", "message": "expected type string, found integer", "check": "message.incompatible", "severity": "error"}]} ``` A client's message gets `#invalid-message` with `"direction": "client"`. The log has the `ws-open`, `ws-in`, `ws-out` and `ws-close` events with the operation, the message's name, the violations and `source: proxy` (`mock` for what the proxy answered itself). `contract.severity` changes the checks' severities as for the other proxies. **Coverage and reports.** Each message of each operation is a branch: it is covered once a valid one went by. An operation is `covered`, `partial` or `not-tested`; a message that matches none is counted for the operation it was checked against first. `GET /__apigenie/contract` returns the report (`"specification": "asyncapi"`, connections, messages in and out, channels, operations, violations), `--report` and `--report-junit` write it when the server stops (a test case per operation), and `--fail-on` and `--min-coverage` (the share of messages) make the exit status 5. ### Contract testing of MQTT channels With `--proxy-to mqtt://host:port` the mock's broker relays to a real broker: each client that connects to the mock gets a connection of its own to the broker, and every message published either way is checked against the document. ```sh apigenie-server devices.yaml --proxy-to mqtt://localhost:1884 --mqtt-port 1883 --report contract.json apigenie-server events.yaml --proxy-to ws://localhost:9000 --proxy-to mqtt://localhost:1884 ``` The upstream connection has the client's MQTT version, client identifier (the one the mock assigns when the client gives none), clean start, keep-alive, will, user name and password. The client's CONNECT gets the broker's CONNACK, a refusal included; a broker that can't be reached is answered with "server unavailable" (return code 3, MQTT 5 0x88). Subscriptions are the broker's: the SUBACK is its answer, and the broker decides who gets which message. What clients publish is checked against the topic's channel's `receive` operations, with the QoS of the operation's MQTT binding when it gives one (`message.qos.unexpected`); what the broker delivers against the `send` operations. A subscriber also gets other clients' messages, so on a channel without `send` operations a delivery is checked against the `receive` operations. Topics of no channel are relayed unchecked (debug level in the log). In enforce mode a client's message with an error isn't forwarded (MQTT 5 clients get reason code 0x99 in their acknowledgement), and a broker's message with an error isn't delivered. A client that disconnects is disconnected from the broker; one whose connection is lost has its upstream connection dropped too, so the broker publishes its will; a client the broker disconnects is disconnected. A reload keeps the clients connected. The log has `mqtt-connect`, `mqtt-subscribe`, `mqtt-in`, `mqtt-out`, `mqtt-unsubscribe` and `mqtt-disconnect` events with `source: proxy` (`mock` for what the proxy decided itself), and coverage and reports work as for WebSocket. MQTT 5 properties are relayed both ways: user properties, payload format indicator, content type, response topic, correlation data and message expiry (not the topic alias and subscription identifiers, which belong to one connection). An MQTT 5 message's are checked against the MQTT bindings (0.2.0): | Binding | Checked (`message.properties.invalid`, `error`) | | --- | --- | | message `payloadFormatIndicator` | The Payload Format Indicator is the same (absent counts as 0) | | message `contentType` | The Content Type is the same, ignoring case | | message `correlationData` | Correlation Data is present and matches the schema | | message `responseTopic` | A Response Topic is present, the string given or matching the schema | | operation `messageExpiryInterval` | A Message Expiry Interval is present and no longer | Its user properties are its headers, checked against the message's `headers` (`message.headers.invalid`). MQTT 3.1.1 messages have no properties: nothing is checked for them. The report counts the messages whose bindings state properties, checked or not: `summary.properties: {"checked": 3, "unchecked": 1}`, `properties` of each operation, and a summary line `MQTT 5 properties: checked on 3 messages, not on 1 (MQTT 3.1.1)`. A client's QoS 1 and 2 messages are acknowledged by the mock before the broker acknowledges them, and the mock delivers at most QoS 1. An `mqtts://` broker (port 8883 by default) is reached over TLS and verified as `https://` upstreams are (see [TLS](#tls)). ### Contract testing of Kafka channels There is no Kafka broker in the mock. With `--proxy-to kafka://host:port` (port 9092 by default; `kafkas://`, port 9093, over TLS) the server is a client of the real cluster, a validating tap: it reads the topics of the document's Kafka channels and checks every record on them, whoever produced it. ```sh apigenie-server shop.yaml --proxy-to kafka://localhost:9092 --report contract.json apigenie-server events.yaml --proxy-to mqtt://localhost:1883 --proxy-to kafka://localhost:9092 ``` A channel with a `kafka` or `kafka-secure` server is a Kafka channel; its topic is the channel's Kafka binding `topic`, else its address. Parameters of the address match the topic's segments, so `stock.{region}` reads `stock.eu` and `stock.us`; the cluster's topics are looked at again every 10 seconds for new ones. The tap reads every partition from the offset it had when the server started, outside any consumer group, so it never takes partitions from the application's consumers and commits no offsets. A topic the cluster doesn't have yet is reported once and read once it appears; a lost connection is reported and tried again. A record doesn't say who produced it, so the channel's operations tell. On a channel with only `send` operations it is the application's (a `kafka-out` event, checked against them); with only `receive` operations it is a client's (`kafka-in`); with both it is checked as the application's, and as a client's when that doesn't match. What is checked: | What | Checked against | Check | | --- | --- | --- | | The value | The operations' messages | `message.incompatible`, `message.malformed` | | The headers | The message's `headers` (values read as the type the schema says) | `message.headers.invalid` | | The key | The message's Kafka binding `key` (an error too when the record has none) | `message.key.invalid` (`error`) | | The partition | The channel's Kafka binding `partitions` | `message.partition.invalid` (`warn`) | | The topic | The address's parameters | `channel.parameter.invalid` | Records are on the cluster before the tap sees them, so enforce mode reports them like log mode. Kafka operations count in the coverage, `GET /__apigenie/contract`, `--report` and `--report-junit` like any other, and the report's `upstream` lists the `kafka://` URL. The log has `kafka-in` and `kafka-out` events with the topic as `channel`, the record's partition and offset in the message, and its key (as `kafka-key`) and headers as the event's headers; what the tap found or failed at are `server` events with `source: kafka`. The SASL PLAIN user and password come from `APIGENIE_KAFKA_USERNAME` and `APIGENIE_KAFKA_PASSWORD` (`apigenie test` reads them too), never from the URL. `kafkas://` brokers are verified as `https://` upstreams are (see [TLS](#tls)). The cluster needs Kafka 2.3 or later. Compressed record batches aren't read: they are reported (`3 record(s) at offset 40 compressed with zstd`) and skipped. A `kafka://` upstream goes with one document, not with [several services](#several-services). See ADR-170 for what the client does and doesn't do. ### Contract testing of AMQP channels There is no AMQP broker in the mock either. With `--proxy-to amqp://host:port/vhost` (port 5672 by default; `amqps://`, port 5671, over TLS; the path is the virtual host, `/%2Fcarpet-express` for `/carpet-express`, none for `/`) the server is an AMQP 0-9-1 client of the real broker, a validating tap. ```sh APIGENIE_AMQP_USERNAME=conformance APIGENIE_AMQP_PASSWORD=conformance \ apigenie-server customs.yaml --proxy-to amqp://localhost:5672/%2Fcarpet-express --report contract.json ``` A channel with an `amqp` or `amqps` server is an AMQP channel. Its AMQP channel binding says what it is: - **`is: routingKey`** (the default): the address is a routing key of `exchange.name` (of `exchange.type`, topic by default). The tap binds a queue of its own (exclusive, deleted when it disconnects) to the exchange: for a topic exchange with the address's parameters as `*` words (`customs.{country}.filing` is `customs.*.filing`), for a fanout or headers exchange with any key. Direct exchanges bind the address itself, so their addresses can't have parameters. It reads that queue, so it gets a copy of every message the exchange routes there, and the application's queues are left alone. An exchange the broker doesn't have is reported, and looked for again every 10 seconds. - **`is: queue`**, and routing keys of the default exchange: a queue the application consumes. Reading it would take the application's messages, so the tap doesn't, and says so when it starts. What `apigenie test` and the admin API send there is checked. Messages are checked as the [Kafka tap](#contract-testing-of-kafka-channels) checks records, in the same direction: their body and headers against the operations' messages, and their properties against the AMQP bindings (0.3.0) (`message.properties.invalid`, `error`): | Binding | Checked | | --- | --- | | operation `deliveryMode`, `priority`, `userId`, `replyTo` | The property is present and the same | | operation `expiration` | An expiration is present and no longer | | operation `timestamp: true` | A timestamp is present | | message `contentEncoding` | The content encoding is the same | | message `messageType` | The `type` property is the same | The log has `amqp-in` and `amqp-out` events with the routing key as `channel` and the exchange in the message; coverage and reports work as for Kafka, and enforce mode reports too. The PLAIN user and password come from `APIGENIE_AMQP_USERNAME` and `APIGENIE_AMQP_PASSWORD` (`guest` without them), never from the URL. An `amqp://` upstream goes with one document. See ADR-171. ### Contract testing of HTTP channels With `--proxy-to http://host:port` (or the `ws://` server of the WebSocket channels) an AsyncAPI document's HTTP channels go to that server through the same transport as an OpenAPI document's proxy (`Via`, `X-Forwarded-*`, 502 and 504 problems), checked against the document instead of an OpenAPI one: - A `receive` operation takes requests with its HTTP binding's method (POST by default). The request's body is checked against the operation's messages, its query against the binding's `query` schema (`operation.query.invalid`) and its header fields against the message's `headers` and HTTP binding `headers`, its traits' included (`message.headers.invalid`). A 2XX response's body and header fields are checked against its `reply`. - A `send` operation is asked with GET, as the mock serves it; a 2XX response with a body is checked against its messages (not its header fields: they are the webhook's). - Other paths get 404 and other methods 405, without the server. Query parameters and header fields are read as the type their schema says (`integer`, `number`, `boolean`, an `array` from repeated parameters); header fields the schemas don't name are left out. `apigenie test` checks the requests of HTTP channels with the same code and the same checks (see [Requests of HTTP channels](/docs/reference/cli/test#requests-of-http-channels)). In enforce mode an invalid request gets 422 (`#invalid-message`) without the server, and an invalid response is replaced by 502 (`#invalid-upstream-message`). Responses carry `X-Apigenie-Mock-Source`, `X-Apigenie-Mock-Operation` and `X-Apigenie-Mock-Violations`, and each exchange counts as a message of its operation in the coverage. Responses other than 2XX and the reply's `statusCode` binding aren't checked; fallback mode, `--record-dir` and `--record-har` are for OpenAPI documents. ## Protobuf / gRPC For a `.proto` file the mock serves its services over gRPC, on the same port as HTTP: clients connect with cleartext HTTP/2 (`-plaintext` in grpcurl, `createInsecure()` in grpc-js, `--http2-prior-knowledge` in buf curl), or over TLS when the mock has `--tls-cert` (grpcurl without `-plaintext`, `createSsl()` in grpc-js; see [TLS](#tls)). Imports are looked up in `--proto-path` directories, then next to the file, then among the embedded standard files: the well-known types, `google/api/annotations.proto`, `google/rpc/status.proto`, `code.proto` and `error_details.proto`. ```sh apigenie-server greeter.proto --stubs stubs/ buf curl --protocol grpc --http2-prior-knowledge -d '{"name":"Ann"}' \ http://127.0.0.1:4010/acme.greeter.v1.Greeter/SayHello ``` ```text Listening on http://127.0.0.1:4010 (spec: greeter.proto, 1 service, 3 methods, gRPC over HTTP/2) ``` Unary, server-streaming, client-streaming and bidirectional methods are served. A call is answered by the first of: 1. Its `x-apigenie-mock-*` metadata (below). 2. A script's `onRequest`. 3. The rules of the active scenarios. 4. The stubs. 5. The upstream of `--proxy-to`. 6. Messages generated from the response type, seeded by `--seed`: one, or one to three for a server stream; with `grpc.generation: false`, UNIMPLEMENTED. Fields marked `(google.api.field_behavior) = REQUIRED` are always set (not to their default value; lists and string- or bool-keyed maps get an item), except where that would make a message contain itself; fields marked `INPUT_ONLY` are left out. The response metadata `x-apigenie-mock-source` (`stub:hello-ann`, `generated`, `proxy`, `scenario:outage`) and `x-apigenie-mock-stub` say which one it was. ### Stubs Stub files are YAML or JSON: a list, `{stubs: [...]}`, or a single stub. Their JSON Schema is [apigenie-mock-grpc-stubs.json](/schemas/apigenie-mock-grpc-stubs.json); add `# yaml-language-server: $schema=https://apigenie.pl/schemas/apigenie-mock-grpc-stubs.json` to a file for completion in editors. ```yaml stubs: - name: hello-ann method: acme.greeter.v1.Greeter/SayHello priority: 10 request: equals: {name: Ann} response: headers: {x-greeting: formal} message: {message: "Hello Ann", sentAt: "2026-10-04T10:00:00Z", mood: MOOD_HAPPY} trailers: {x-cost: "1"} - name: hello-tenant method: acme.greeter.v1.Greeter/SayHello request: contains: {name: Bob} metadata: equals: {x-tenant: acme} response: message: {message: "Hello Bob of {{metadata.x-tenant}}"} - name: hello-empty method: acme.greeter.v1.Greeter/SayHello request: equals: {} response: status: code: INVALID_ARGUMENT message: name is required details: - "@type": type.googleapis.com/google.rpc.BadRequest fieldViolations: [{field: name, description: must not be empty}] - name: hello-all method: acme.greeter.v1.Greeter/SayHelloToAll response: each: {message: {message: "Hi {{message.name}}"}} ``` - **`method`** is `package.Service/Method` (or `method: Method` with `service: package.Service`). An unknown method stops the start with exit status 2 and the methods there are. - **`request`** matches the request message, written as proto3 JSON: `equals` compares canonical forms, so default values may be left out; `contains` matches a subset, recursively (arrays as subsets, `ignoreArrayOrder` for any order); `matches` holds regular expressions for string fields. `request.metadata` matches the metadata the same three ways. - **`requests`** matches the messages of a client stream, after the client has finished sending: a list matched one by one, or `{every: matcher}`. - **`priority`** (higher first, then the order of the files) and **`times`** (how many calls a stub answers before it's skipped) order the candidates. - **`response`** has `headers`, `message` or `messages` (a list of `{message, delay}` for a stream), `each` (bidirectional streams: one answer per message received), `trailers`, `status` (`code` as a name or a number, `message`, `details` as `google.protobuf.Any` JSON), `delay` (milliseconds), `drop` (`reset`, `close`, `timeout`, `truncate`) and `generate: true` (fill the fields the message doesn't give). - **Templates.** String values can use `{{request.name}}`, `{{requests[1].name}}`, `{{requests.length}}`, `{{message.name}}` (the message `each` answers), `{{metadata.x-tenant}}` and `{{fake:person.firstName}}`. A value that is a template alone keeps the type of what it refers to. ### Metadata directives | Metadata | Effect | | --- | --- | | `x-apigenie-mock-code: NOT_FOUND` | End the call with that status (a name or a number). | | `x-apigenie-mock-message: text` | The status message that goes with it. | | `x-apigenie-mock-stub: hello-ann` | Answer with that stub. | | `x-apigenie-mock-delay: 1500` | Answer after 1.5 seconds. | | `x-apigenie-mock-drop: reset` | Reset the connection (`close`, `timeout` and `truncate` too). | | `x-apigenie-mock-scenario: outage` | Apply that scenario to this call. | | `x-apigenie-mock-dynamic: true` | Generate the answer. | ### What a gRPC server does - `grpc-timeout` sets a deadline: a call that would answer later ends with DEADLINE_EXCEEDED. - Messages larger than `grpc.max_message_bytes` (4 MiB) fail with RESOURCE_EXHAUSTED; compressed ones with UNIMPLEMENTED and `grpc-accept-encoding: identity`; undecodable ones with INVALID_ARGUMENT. An unknown method is UNIMPLEMENTED. - Requests are validated against their message type (`grpc.validation.request`): a violation is INVALID_ARGUMENT with a `google.rpc.BadRequest` in `grpc-status-details-bin`. Fields marked `[(google.api.field_behavior) = REQUIRED]` must be set (see [gRPC contract testing](#contract-testing-of-a-grpc-api)). - Errors are sent as Trailers-Only responses; `grpc-message` is percent-encoded, `-bin` metadata base64-encoded. - `grpc.reflection.v1` and `v1alpha` let grpcurl, buf curl, Postman, Kreya and Bruno list the services and fetch their descriptors; `grpc.health.v1.Health` answers `Check` and `Watch` with SERVING for every service and for `""`. - Scenario rules match `operationId` (`acme.greeter.v1.Greeter/SayHello`), `path`, or the tags `grpc` and the service's name, and set `grpc_status` and `grpc_message` besides delays and drops: ```yaml scenarios: outage: rules: - match: {operationId: acme.greeter.v1.Greeter/SayHello} grpc_status: UNAVAILABLE grpc_message: the greeter is down ``` ### Proxy and record ```sh apigenie-server greeter.proto --proxy-to greeter.internal:50051 --record-dir recorded/ apigenie-server greeter.proto --stubs recorded/ ``` With `--proxy-to`, calls no stub answers go to the upstream server (every call with `grpc.proxy.mode: always`), with their metadata and deadline; an unreachable upstream is UNAVAILABLE. `--record-dir` writes each proxied call as a stub file (`Greeter.SayHello-1.yaml`, ...) that matches its request and gives the upstream's answer. Transport headers are left out, and so is the metadata of `grpc.proxy.redact` (`authorization`, `cookie`, `x-api-key` and `proxy-authorization` by default). ### Contract testing of a gRPC API The proxy checks both sides of every call it forwards against the `.proto` file. Point your clients, tests or a load test at the mock, and the traffic becomes a contract test of the server: ```sh apigenie-server greeter.proto --proxy-to greeter.internal:50051 --report contract.json --report-junit contract.xml --fail-on errors ``` Messages are decoded with their method's types and checked against them: their JSON Schema (the proto3 JSON form), the fields marked `[(google.api.field_behavior) = REQUIRED]` (proto3 has no required fields; a REQUIRED field without explicit presence must not have its default value), and deprecated fields and enum values. The status of the answer is checked against the gRPC protocol and the `google.rpc` conventions. Each violation has a check and a severity that `contract.severity` changes: | Check | Severity | What it means | | --- | --- | --- | | `request.method.unknown` | warn | The call is for a method the file doesn't declare; it is answered UNIMPLEMENTED, not forwarded | | `request.message.malformed` | error | A request message can't be decoded as the input type | | `request.message.incompatible` | error | A request message doesn't match its type, or misses a REQUIRED field. A field that came with another wire type than its declared type's (a string where an `int64` is declared) is dropped by decoding, as protobuf parsers do, and reported with the code `wire-type` | | `request.message.deprecated` | info | A request sets a deprecated field or enum value | | `response.status.invalid` | error | An HTTP status other than 200, a content type other than `application/grpc`, a missing or unknown `grpc-status`, a `grpc-message` that isn't percent-encoded | | `response.status.details-incompatible` | error | `grpc-status-details-bin` isn't a base64 `google.rpc.Status`, or its `code` isn't `grpc-status`; a warning for a detail of a type the file doesn't know | | `response.status.server-error` | info | The upstream answered UNKNOWN, INTERNAL, UNAVAILABLE, DATA_LOSS or UNIMPLEMENTED | | `response.message.malformed` | error | A response message can't be decoded as the output type | | `response.message.incompatible` | error | A response message doesn't match its type, or misses a REQUIRED field. A field that came with another wire type than its declared type's (a string where an `int64` is declared) is dropped by decoding, as protobuf parsers do, and reported with the code `wire-type` | | `response.message.count` | error | A unary or client-streaming call ended OK without exactly one message; a warning for messages before an error status | | `response.message.deprecated` | info | A response sets a deprecated field or enum value | | `response.metadata.invalid` | warn | A `-bin` metadata value isn't base64, or a metadata name isn't lowercase letters, digits, `-`, `_` and `.` | Locations are the message's index and a JSON pointer, `message[0]/items/2/sku`; a message that can't be decoded is located at the field the decoder stopped at. **Report or enforce.** With `--proxy-to`, `grpc.validation.request` and `grpc.validation.response` are `log` unless the configuration sets them: violations are logged and reported, and calls go through as they are, since test suites send invalid calls on purpose. `--strict` (or `enforce`) checks each request message before it goes to the upstream (a violation is INVALID_ARGUMENT with a `google.rpc.BadRequest`) and each upstream message before it goes to the client: a violation ends the call with INTERNAL, `the upstream's answer isn't a valid acme.greeter.v1.HelloReply: ...`. Enforcing holds each message until it is whole, so streams lag by one message. Without `--proxy-to`, requests are enforced as before. **Coverage and reports.** The mock counts the calls of each method of the file (built-in reflection and health aside): `covered` when a call had no error violation, whatever its status, `partial` when every call had one, `not-tested` otherwise. Mocked calls count too, under `sources`. `GET /__apigenie/contract` is the report; when the server stops it prints a summary, writes `--report` and `--report-junit` (a test case per method, named by service and method, one per undocumented method and one for the coverage threshold) and exits with 5 when `--fail-on` or `--min-coverage` (the share of methods) isn't met. The JSON report has `"specification": "grpc"`, `summary` (`calls`, `documented`, `undocumented`, `sources`, `messages`, `violations`, `coverage.methods`, `gate`), `services`, `methods` (each with its `kind`, `status`, `calls`, `statuses` by status name, `messages` and `violations` by check), `undocumented` and `violations` (with the `method` and the `direction`, `in` or `out`). The upstream is `HOST:PORT` for cleartext HTTP/2, or `https://host[:port]` for HTTP/2 over TLS (see [TLS](#tls)). Compressed messages (an encoding the client offered and the server chose) are forwarded unchecked. [`apigenie test`](/docs/reference/cli/test) sends `GRPC` requests of `.http` files with the same checks and reports. ### Scripts and the log Scripts see a gRPC call as a `POST` to `/package.Service/Method` whose `operationId` is `package.Service/Method`, with the metadata as headers and the message as a JSON body. In `onResponse`, the status is in the `grpc-status` and `grpc-message` headers and the body is the first message; `onMessage` gets each message of a stream, with `direction` `in` or `out`. The log has a `grpc` event for each call with its status `code`, and `grpc-in` and `grpc-out` events for streamed messages. ## GraphQL A GraphQL schema is served as a GraphQL endpoint, `/graphql` under `--base-path` (`graphql.path` changes it). Every query, mutation and subscription the schema allows is answered, with no resolvers to write. - **GraphQL over HTTP.** `POST` with `application/json` (`{query, variables, operationName, extensions}`) or `application/graphql`, `GET` with the same as query parameters (queries only: a mutation over `GET` gets 405), and batches (a JSON array of requests, `graphql.max_batch`). Clients that accept `application/graphql-response+json` get 400 for requests that fail before execution; `application/json` clients get 200 for every well-formed request, as the specification asks. - **Validation.** Requests are checked against the schema with the rules of graphql-js and its messages (unknown fields and arguments, wrong values and variable types, fragments that can't apply, fields that can't be merged...). Errors have `locations` and `extensions.code` (`GRAPHQL_PARSE_FAILED`, `GRAPHQL_VALIDATION_FAILED`, `BAD_USER_INPUT`), and the log shows each as a violation. - **Execution.** Fragments, inline fragments, aliases, variables with defaults, `@skip`, `@include` and `__typename` behave as in a real server. Interfaces and unions return one of their object types. A field that fails is null and its error has the field's `path`; a non-null field's null goes up to the nearest nullable one. - **Introspection.** `__schema` and `__type` are complete, so GraphiQL, Apollo Sandbox and code generators work against the mock (`graphql.introspection: false` turns it off). The SDL is at `/graphql/schema.graphql` and `GET /__apigenie/spec`. - **GraphiQL.** A browser opening the endpoint gets GraphiQL, loaded from esm.sh (`graphql.playground: false` turns it off). ### Generated data Values follow the field's type: enum values, list lengths between `graphql.lists.min` and `max` (or a `first`, `last`, `limit`, `count`... argument, the field's own or its connection's), nulls with `graphql.null_probability`, and realistic values for fields named like `email`, `city`, `phone`, `url` or `firstName`, for `name`, `title` and `genre` by the type they belong to (`Author.name` is a person's, `Book.title` a book's), and for custom scalars by their name (`DateTime`, `Date`, `URL`, `UUID`, `JSON`, `Long`, `Decimal`, `EmailAddress`...). `--seed` repeats a run. The schema can say more, with graphql-faker's directives or the mock's own. They need no definitions in the schema and don't show in introspection: ```graphql type User { name: String @fake(type: fullName) email: String @fake(type: email, options: {emailProvider: "example.com"}) joined: String @fake(type: pastDate, options: {dateFormat: "YYYY-MM-DD"}) role: Role @examples(values: ["ADMIN", "EDITOR"]) tags: [String!] @listLength(min: 1, max: 3) score: Int @mock(min: 0, max: 100) code: String @mock(faker: "string.numeric", args: [6]) nickname: String @mock(nullProbability: 0.5) } scalar Money @mock(min: 1, max: 500) ``` - `@fake(type:, options:)` takes the 64 types of graphql-faker and the options most used (`minMoney`, `maxMoney`, `decimalPlaces`, `minNumber`, `maxNumber`, `precisionNumber`, `dateFormat`, `dateFrom`, `dateTo`, `loremSize`, `imageSize`, `imageKeywords`, `passwordLength`, `emailProvider`, `useFullAddress`). Data is English whatever the `locale`. - `@examples(values:)` picks one of the values; `@listLength(min:, max:)` sizes a list. - `@mock` takes `faker` (any faker provider, as `x-faker`) with `args` (a list by position or an object by name), `value` or `values`, `min` and `max` for numbers, `length` or `minLength` and `maxLength` for lists, and `nullProbability`. On a root field, `delay` (milliseconds) delays the response and `errorRate` with `error` makes it fail; on a subscription field, `interval` and `count` pace its events. `graphql.fields` does the same from the configuration, for a field (`Type.field`) or a scalar, over the schema's directives. A schema that defines a directive of the same name keeps it: the mock leaves it alone. ### The store Objects whose type has an `id` field are entities: once made, their fields keep their values for the next requests, so `{ user(id: "7") { name } }` answers the same name every time. The usual root fields work on them, by their names and types: | Field | What it does | | --- | --- | | `user(id: ID!): User`, `node(id: ID!): Node` | The entity with that id; a new one when there is none (`graphql.store.unknown_ids: null` answers null). | | `users: [User!]!` | The users, made up to the list's length once, then the same. | | `usersConnection(first:, after:, last:, before:)` | The users as a Relay connection: `edges { cursor node }`, `nodes`, `pageInfo`, `totalCount`. | | `createUser(input: ...)`, `addUser(...)`, `newUser`, `registerUser`... | A new user with the input's fields; payload types get it in their field and `clientMutationId` back. | | `updateUser(id:, input:)`, `editUser`, `setUser`... | The user, with the input's fields. | | `deleteUser(id:)`, `removeUser`... | Removes the user; returns it, `true`, its id or a payload. | `--dynamic` and `graphql.store.enabled: false` turn the store off; `POST /__apigenie/seed` empties it, and so does a reload. `graphql.store.max_entities` (10000) bounds it. ### Examples An example answers a request with data of its own. Operation examples match an operation's name, its document (layout and comments don't count) and some of its variables; field examples are the value of a field for some arguments, in any operation: ```yaml # schema_examples/users.yaml, next to schema.graphql: an example, or a list of them - name: ada operation: GetUser variables: {id: "1"} response: data: {user: {id: "1", name: Ada Lovelace}} - name: grace # a field example field: Query.user arguments: {id: "2"} data: {name: Grace Hopper} ``` Example data is cut to the request's selection set, and fields it lacks are generated, so one example serves every query of its operation. Examples come from `graphql.examples` in the configuration, from `graphql.examples_files` (files and directories of `.json`, `.yaml` and `.yml`, each an example or a list of them) and from the directory named after the schema (`schema.graphql` takes `schema_examples/`). `Prefer: example=ada` picks one by name; `--dynamic` and `Prefer: dynamic=true` leave them out. An example's `errors` and `extensions` are added to the response, and `status` sets the HTTP status. ### Scenarios and errors Rules of scenarios and `operations` match GraphQL operations by the operation's name (`operationId: GetUser`) or by a root field (`operationId: Query.user`, or `tag: Query.user`). Besides `status`, `headers`, `body` (the whole response), `example`, `dynamic` and the simulation, they take `errors`: ```yaml scenarios: users-down: rules: - match: {operationId: Query.user} errors: - message: The users service is down. path: [user] extensions: {code: UNAVAILABLE} - Partial results # no path: added to the response's errors ``` An error with a path fails that field: it is null (or null up to the nearest nullable field) and the error has its locations. `Prefer: code=`, `example=`, `dynamic=`, `delay=`, `drop=` and `scenario=` apply as for OpenAPI. ### Subscriptions WebSocket clients of the endpoint speak `graphql-transport-ws` (the `graphql-ws` library: Apollo Client, urql, GraphiQL) or the legacy `graphql-ws` of `subscriptions-transport-ws`, by the subprotocol they offer. A subscription gets an event every `graphql.subscriptions.interval` (1000 ms), each a fresh execution of its operation, until `count` events (0: until the client completes it); a field's `@mock(interval:, count:)` wins. Queries and mutations over WebSocket get one result. A request that accepts `text/event-stream` gets the same as server-sent events (`next` events, then `complete`: graphql-sse's distinct connections mode). The log has `ws-open`, `ws-in`, `ws-out` and `ws-close` events; the server's stop closes the clients with 1001 and a reload with 1012. ### Contract testing of a GraphQL API With `--proxy-to`, the GraphQL endpoint goes to a real GraphQL server, and the mock checks every request against the schema and every answer against the schema and the request's selection set. Point your frontend, your tests or a load test at the mock, and the traffic becomes a contract test of the server: ```sh apigenie-server schema.graphql --proxy-to http://localhost:4000/graphql --report contract.json --fail-on errors ``` ```text Listening on http://127.0.0.1:4010/graphql (spec: schema.graphql, 12 operations) Proxy: http://localhost:4000/graphql (requests: log, responses: log, unknown routes: forward, mode: always) ``` The URL's path is the server's endpoint (`HOST:PORT` alone keeps the mock's, `/graphql`). The upstream gets the request as it came (a batch stays a batch), without the mock's `__` query parameters and `X-Apigenie-Mock-*` headers, with `Via` and `X-Forwarded-*`, and `Accept-Encoding: identity`. GraphiQL, `/graphql/schema.graphql`, the admin API and `OPTIONS` stay local. Requests for other paths are forwarded unchecked (`request.path.unknown`), or answered 404 with `--proxy-unknown reject`. A proxy makes up no subscription events: WebSocket and server-sent events subscriptions aren't served. **What is checked.** Requests as the mock checks them (graphql-js's rules and messages), and each answer as graphql-js would have produced it, field by field: | Check | Severity | When | | --- | --- | --- | | `request.body.malformed` | error | Not a GraphQL request: not JSON, no query, an unsupported content type. | | `request.query.malformed`, `request.operation.unknown` | error | The query can't be parsed; `operationName` names no operation, or is missing. | | `request.query.invalid`, `request.variables.incompatible` | error | The operation isn't valid against the schema; a variable doesn't match its type. | | `request.method.incompatible` | error | A mutation over `GET`. | | `request.field.deprecated` | info | A deprecated field, argument, input field or enum value is used. | | `request.path.unknown` | warn | A request for another path than the endpoint (forwarded unchecked). | | `response.status.incompatible` | error | A 1xx or 3xx status, or a 4xx with `data`: an executed operation that says it failed. | | `response.status.server-error` | info | The server answered with 5xx. | | `response.content-type.incompatible` | error | Neither `application/json` nor `application/graphql-response+json`. | | `response.body.malformed` | error | Not JSON, or a batch not answered with a list of as many results. | | `response.shape.incompatible` | error | Not a map of `data`, `errors` and `extensions`: other keys, neither `data` nor `errors`, `data: null` without `errors`, empty `errors`. | | `response.errors.incompatible` | error | An error without a string `message`, with `locations` that aren't `{line, column}`, a `path` of other than names and indexes, `extensions` that aren't an object. | | `response.errors.unexpected`, `response.errors.path-unknown` | warn | An error's entries outside `extensions`; a `path` that names no field the operation selects. | | `response.data.incompatible` | error | A selected field (or alias) missing; `null` in a non-null position that no error's `path` explains; a list, an object or a scalar where the type says otherwise; `Int` beyond 32 bits, a non-finite `Float`, a `String`, `Boolean` or `ID` of another kind, an unknown enum value; a `__typename` that isn't a possible type. | | `response.data.unexpected` | warn | A key the operation didn't select. | | `response.data.typename-missing` | info | An object of an interface or union came without `__typename`: only the selections outside type conditions are checked. | | `response.data.unvalidatable` | info | Nesting deeper than 64, or more than 100 violations in one answer. | | `response.field-error` | warn | The server ran the operation, but fields failed: `errors` next to `data` (`null` or partial), one per error, e.g. `the field book failed: not implemented`. Valid GraphQL, but a contract test shouldn't pass it silently; `--fail-on warnings` (or `contract.severity`) fails on it. | | `response.request-error` | warn | The server didn't execute the operation (authentication, persisted queries...). | | `response.schema-drift` | error | The server rejected an operation the schema allows (`GRAPHQL_VALIDATION_FAILED`, "Cannot query field..."): its schema differs. | | `schema.drift`, `schema.addition` | error, info | The server's schema (introspected) lacks or changed something of the schema; has more. | | `schema.unchecked` | info | The server's schema can't be introspected. | Fragments, inline fragments, `@skip` and `@include` (with the request's variables), aliases and merged fields are followed as the server must; custom scalars take any JSON value. `contract.severity` changes a check's severity or turns it off, as for OpenAPI. **The server's schema.** With the first request it forwards, the proxy also sends the server the usual introspection query (with that request's `Authorization`, `Cookie` and `X-Api-Key`), turns the answer into a schema and compares it with the served one, as `apigenie diff` does: what the server lacks or changed incompatibly is `schema.drift` (located by `Type.field`), what it adds `schema.addition`. The log has a `server` event with the result (`schema of http://localhost:4000/graphql: 1 breaking difference, 2 additions`), and the report a `schema` section. A server that isn't up yet is asked again with the next request; `POST /__apigenie/contract/schema-check` asks now. `proxy.schema_check: false` turns it off. **Report or enforce.** As for OpenAPI, the proxy reports by default: answers carry `X-Apigenie-Mock-Violations` and `X-Apigenie-Mock-Violations-Detail` (locations such as `["response", "body", "/data/book/title"]`, `/2/data/...` for the third result of a batch). With `--strict`, a request with errors isn't forwarded: it gets the GraphQL errors the mock answers with (`GRAPHQL_VALIDATION_FAILED`...). An answer with errors is replaced by the `invalid-upstream-response` 502 problem. Warnings and notes never block. **Fallback mode.** `--proxy-mode fallback` (`proxy.mode`) lets scenario rules, examples and the scripts' `onGraphQL` answer first, as for a mock; what nothing answers goes to the server. A batch goes to the server whole as soon as one of its operations does. **Recording.** `--record-dir DIR` writes the answer to each valid operation as an example file (`GetBook-1.yaml`: `name`, `operation`, `query`, `variables`, `response`, and `status` when not 200), which the mock serves as it is: point `graphql.examples_files` at the directory, or name it after the schema (`schema_examples/`). Bodies are recorded as they are. **Coverage and reports.** The mock counts the schema's fields that requests selected (every field of its object and interface types, as GraphQL Inspector's coverage does) and its root fields (`Query.book`): `covered`, `partial` (some fields under it never selected) or `not-tested`. Mocked traffic counts too. `GET /__apigenie/contract` is the report; when the server stops it prints a summary, writes `--report` and `--report-junit` (a test case per root field, one for the server's schema, one for the coverage threshold) and exits with 5 when `--fail-on` or `--min-coverage` (the share of fields) isn't met. The JSON report has `summary`, `schema`, `rootFields`, `fields`, `uncoveredFields`, `operations` (by name), `otherRoutes` and `violations`. ## Scripts A script is an ES module whose default export has any of five hooks: ```js import { limit } from './settings.mjs'; export default { // before the document's operations: a response answers the request onRequest(req, ctx) { if (req.path === '/pets/13') { return ctx.response(404, { title: 'No such pet' }); } }, // the response about to be sent: change it in place onResponse(req, res, ctx) { if (req.operationId === 'listPets') { res.body = res.body.slice(0, limit); res.headers['x-total-count'] = '3'; res.delay = 250; } }, // AsyncAPI messages, in and out: change msg.payload, or return false to drop the message onMessage(msg, ctx) { if (msg.direction === 'out') msg.payload.sentAt = ctx.now(); }, // GraphQL operations (each subscription event too): a result answers, its data completed onGraphQL(operation, ctx) { if (operation.name === 'GetUser') { return { data: { user: { id: operation.variables.id, name: ctx.fake('person.fullName') } } }; } }, // after the scripts were loaded, at the start and after each reload onReload(ctx) {}, }; ``` - `req` has `method`, `path`, `query` (an object; repeated parameters are arrays), `headers` (lower-case names), `body` (parsed JSON, or the text), `rawBody`, `operationId` and `route` (in `onResponse`), `remote` and `connectionId`. - `res` has `status`, `headers`, `body`, `source`, and `delay` (milliseconds) and `drop` (`reset`, `close`, `timeout`, `truncate`) to set. A response returned by `onRequest` has the same fields. - `msg` has `direction` (`in` or `out`), `channel`, `operation`, `message`, `contentType` and `payload`. - `operation` has `name`, `type` (`query`, `mutation`, `subscription`), `query`, `variables` and `rootFields` (`["Query.user"]`). The result's data is cut to the selection set and completed with generated values, like an example's. - `ctx` has `state` (`get`, `set`, `delete` and `keys` of JSON values kept between requests, the admin API's `/state`), `db` (collections of documents, see [Document store](#document-store)), `log(level, ...values)`, `random()` (seeded by `--seed`), `now()`, `response(status, body, headers)`, `fake(name, ...arguments)` (the faker providers of `x-faker`, with arguments by position and a last object by name: `fake('string.alpha', {length: 4})`) and `scenarios` (`activate`, `deactivate`, `active`). Scripts run in a sandbox: the language only, modules next to the script, no file system, network or timers. Each call has `scripting.timeout_ms` (1000) and `scripting.memory_limit_mb` (64); a hook that throws or runs out of time is logged, and answered with 500 when `scripting.fail_mode` is `error`. `--watch` reloads changed scripts, and a broken script keeps the previous version. ## Session recording `--record DIR` keeps a run whole: every exchange the server answered, of every service and every protocol, in the order it happened, each marked with the test that sent it, as files a replay can read and a reviewer can diff: ```sh apigenie-server openapi.yaml --proxy-to http://localhost:8080 --record sessions/checkout apigenie-server --config apigenie-server.yaml --record sessions/checkout # several services apigenie-server session export sessions/checkout --har checkout.har ``` ```text sessions/checkout/ session.yaml # the session and the exchanges in order pets/0001-getPet.yaml # an HTTP exchange of the service pets (a stub) pets/0002-createPet.yaml events/0003-ws-in-sendMessage.yaml # a WebSocket message from a client events/0004-ws-out-sendMessage.yaml # a message to it chat/0005-Chat.Talk.yaml # a gRPC call with its messages (a stub) ``` A server of one document writes the files in `DIR` itself (`--stubs DIR` and the GraphQL examples skip `session.yaml`). `session.yaml` names the session (the directory's name unless `record.name` or `/record/start` says), the server, when it started and stopped, the services (name, document, `specification`, the SHA-256 of the document's bytes, mount) and the exchanges: ```yaml name: checkout server: apigenie-server 1.0.0 started: 2026-10-13T10:00:00.000Z stopped: 2026-10-13T10:00:42.512Z services: - {name: pets, spec: pets.yaml, specification: openapi, sha256: cfada1c0..., mount: /pets} - {name: events, spec: events.yaml, specification: asyncapi, sha256: 9b2f..., mount: /events} exchanges: - {sequence: 1, id: 2, service: pets, test: checkout-happy, kind: http, time: 2026-10-13T10:00:01.555Z, durationMs: 3, source: proxy, summary: GET /pets/1 200, file: pets/0001-getPet.yaml} - {sequence: 3, id: 7, service: events, test: checkout-happy, kind: ws, time: ..., source: mock, summary: ws-in rooms/7 notice, file: events/0003-ws-in-sendMessage.yaml} ``` **The exchange files.** Each has a `session` note (the index's line) next to the protocol's own form, so what already reads that form reads the file: - `http`: `{session, stubs: [stub]}`, the stub form of [`--record-dir`](#contract-testing-validating-proxy), so `--stubs sessions/checkout/pets` makes a mock answer with the service's exchanges as before (the first matching stub answers, whatever the test). OpenAPI exchanges, AsyncAPI HTTP channels and GraphQL requests that aren't one operation answered with JSON (batches, errors, GraphiQL). The answer is kept as the client got it, less the CORS headers the server adds to every answer. The request's `X-Apigenie-Test` header is left out. - `graphql`: one operation and its answer, in the [example form](#examples) (`name`, `operation`, `query`, `variables`, `response`, `status`) the mock's examples load; the note's `path` is the endpoint. - `grpc`: `{session, stubs: [stub]}`, the stub form of a `.proto` file's [`--record-dir`](#proxy-and-record), with the whole streams (at most 100 messages per side); the note's `order` (`[in, out, in, out]`) says which side each message came from, `code` the status. The `x-apigenie-test` metadata is left out. - `ws` and `mqtt`: one file per message of either side: ```yaml session: {sequence: 3, id: 7, service: events, test: checkout-happy, kind: ws, time: ..., source: mock} message: direction: in # from the client; out: to it channel: rooms/7 # the channel's address, and the route it matched route: rooms/{roomId} operation: sendMessage name: notice contentType: application/json connection: 4 json: {text: hi} # text: for other text, base64: for bytes ``` **Tests.** An exchange's `test` is the `X-Apigenie-Test` header of the request or of the WebSocket handshake, or the `x-apigenie-test` metadata of a gRPC call; MQTT messages have none of their own. Without one, it is the current **mark**: `POST /__apigenie/record/mark {"test": "checkout-happy"}` names the test of the exchanges to come (`{}` clears it), for clients that can't send the header. A Playwright suite sends the header with `page.setExtraHTTPHeaders` and marks the test in `beforeEach`. **Order.** `sequence` numbers the exchanges in the order they completed, which is the log's order; a request still in flight when a short one is answered comes after it. Each exchange keeps its `time` and `durationMs` (a gRPC call its `started`), so a tool can sort by arrival. What isn't an exchange isn't recorded: requests the client abandoned or the simulation dropped, CORS preflights, the admin API's own traffic, the dashboard. **Redaction.** The values of `Authorization`, `Cookie`, `Set-Cookie`, `Proxy-Authorization` and `X-Api-Key` are written as `[redacted]`, as in `--record-dir`. `--record-redact` (or `record.redact`) adds header and metadata names, and JSON pointers, which start with `/`, to values of JSON bodies, message payloads, gRPC messages and GraphQL variables and answers: `--record-redact X-Session --record-redact /owner/password`. A pointer is matched exactly (`/items/0/key` is the first item's key). A redacted request body no longer matches on replay. **Start and stop.** `--record` records from the start and writes `session.yaml` when the server stops; the startup line says `Recording the session to DIR` (`record` in JSON). The admin API records a part of a run: `POST /__apigenie/record/start {"dir": "sessions/run-7", "name": "run 7"}` (`dir` defaults to `record.dir`; 409 while a session is on, 400 for a directory that isn't empty), `POST /__apigenie/record/stop` (the session's `dir`, `name`, `exchanges`, `file`; 409 without one), `GET /__apigenie/record` (`recording`, and the same while one is on, `test` for the mark). The [dashboard](#dashboard)'s Traffic screen has Record session and Save session. A session has at most 10 000 exchanges: past that it counts on but writes no more files, with one warning in the log and `truncated: true` in the index. When the server dies before writing `session.yaml`, the exchange files tell: `session export` rebuilds the index from their notes. **Export.** `apigenie-server session export DIR --har FILE` writes the HTTP exchanges, GraphQL operations (as `POST` requests to the endpoint) and gRPC calls (as `POST` requests with `application/grpc+json` bodies, the messages as JSON arrays, and a `grpc-status` header) as a HAR 1.2 file, in the session's order, each entry with `_apigenie.service`, `test`, `sequence` and `kind`; WebSocket and MQTT messages have no form in HAR and are left out (the command says how many). URLs keep the services' mount prefixes. The design is ADR-168. ## Session replay `--replay DIR` serves the services of a [recorded session](#session-recording) the way they answered when it was recorded, without the real servers: each request gets the next unused recorded exchange of its test that matches it, so a test suite replays the same run every time, and fails when the application asks for something the recording doesn't have. ```sh apigenie-server openapi.yaml --record sessions/checkout --proxy-to http://localhost:8080 # once apigenie-server openapi.yaml --replay sessions/checkout # every run apigenie-server --config apigenie-server.yaml # replay: {dir: sessions/checkout}, several services ``` **Tests and order.** A request's test is its `X-Apigenie-Test` header (of a WebSocket handshake too) or a gRPC call's `x-apigenie-test` metadata. It draws from the exchanges recorded under that test, in their order: the first `GET /pets/1` of test `checkout` gets the first one recorded for it, the second the second, and a third matches none. Tests replay side by side, each with its own counters, so the parallel workers of a suite don't take each other's exchanges. Requests without a tag share one test that draws from the whole session in order, for suites that don't tag their requests (and MQTT, whose clients send none). `POST /__apigenie/replay/reset {"test": "checkout"}` (or with the header) restarts a test's counters, at the start of each test; without a test it restarts every test. **What is compared.** The method and path; the query, in any order; the headers the recording kept (not `Host`, `User-Agent`, `Accept-Encoding` and the other headers a client changes from run to run: `Date`, `Origin`, `Referer`, `Accept-Language`, `Cache-Control`, the `Sec-*` headers, `traceparent`, `X-Request-Id`, ...), a recorded `[redacted]` value standing for any; and the body, JSON regardless of key order and number formatting. Left out: - strings that are both RFC 3339 dates or date-times (`2026-10-13`, `2026-10-13T10:00:00Z`), in bodies and query parameters; - the fields of an OpenAPI request body that its schema marks `readOnly`, which a client sends back as it got them (`id`, `createdAt`); - what `--replay-ignore` (or `replay.ignore`) names: headers, and JSON pointers into bodies, messages, gRPC messages and GraphQL variables (`--replay-ignore /cartId --replay-ignore /items/*/addedAt`; `*` stands for any index or key). **When nothing matches.** `--replay-missing fail`, the default, answers with a 502 `application/problem+json` naming the closest recording (of the same test and kind; the same operation first, then the fewest differences) and how the request differs from it: ```json { "type": "https://apigenie.pl/mock/errors#replay-missing", "title": "No recorded exchange matches", "status": 502, "detail": "no recorded exchange of test 'checkout' matches POST /carts; the closest, pets/0004-createCart.yaml, differs at /items/0/quantity", "test": "checkout", "closest": {"file": "pets/0004-createCart.yaml", "sequence": 4, "summary": "POST /carts 201", "used": false}, "differences": [{"where": "/items/0/quantity", "recorded": 1, "received": 2}] } ``` `used: true` says the matching exchange was replayed already in that test (a request sent once more than when it was recorded). `generate` lets the mock answer (stubs, the document store, examples, generated values); `passthrough` forwards the request to `--proxy-to`, and with `--replay-update` records the run (replayed and forwarded exchanges alike) into `DIR.update` and puts it in place of `DIR` when the server stops: the way to refresh a recording after the application changed on purpose. A request that matched none is reported either way. **Every protocol.** - HTTP (OpenAPI, AsyncAPI HTTP channels, GraphQL batches): the recorded status, headers and body, with `X-Apigenie-Mock-Source: replay:0002-getPet.yaml`. - GraphQL queries and mutations: by operation name, query (formatting aside) and variables; the recorded answer. One none matches gets a `REPLAY_MISSING` error (status 502) with the same members in `extensions.replay`. - GraphQL subscriptions (graphql-transport-ws, graphql-ws, SSE) and AsyncAPI WebSocket channels: a connection replays a recorded connection of its route and test as the server's side: what the server sent first (`connection_ack`, the first scheduled messages) at once, then, after each message the client sends that is the recorded one, the messages that followed it. The subscriptions' ids are the client's own. A message that isn't the recorded one gets the problem as a message (GraphQL: an `error` message). The scheduled messages and generated replies are off. - MQTT: a subscriber of a topic gets the messages a subscriber got before any client published; a published message that is the next recorded one gets the messages that followed it. - gRPC: unary and server-streaming calls by their metadata and first request, client streams by all their requests, bidirectional streams in their recorded interleaving (the session's `order`): a message the client sends that isn't the recorded one fails the call. A call none matches fails with `FAILED_PRECONDITION`, the problem in the `apigenie-replay-problem` trailer. WebSocket and MQTT messages can't be forwarded one at a time: under `passthrough` they are answered as under `fail`. **The report.** When the server stops it prints how many recorded exchanges were replayed, the ones never replayed and the requests that matched none, with their tests (with `--log-format json`, one `{"event": "replay", ...}` line), and exits with status 5 if there are any; `--replay-update` prints the report but doesn't fail. `GET /__apigenie/replay` gives the same report at any time, with `passed` and the exchanges each test used since its reset. The startup line says `Replaying the session in DIR (N exchanges, missing: fail)` (`replay` in JSON). A document that changed since the session was recorded (another SHA-256) and a service the session lacks are warned about at startup. `--stubs`, the GraphQL examples and the gRPC stubs stay: the per-exchange forms, where the first match answers whatever the test. The design is ADR-169. ## Replay in a test suite A Playwright suite recorded against the real backend once replays it in every run, each test with its own exchanges. The server starts with the suite and every request of a test carries its name: ```ts // playwright.config.ts import { defineConfig } from "@playwright/test"; export default defineConfig({ webServer: { command: "apigenie-server openapi.yaml --port 4010 --replay recordings/checkout", url: "http://127.0.0.1:4010/__apigenie", reuseExistingServer: false, }, globalTeardown: "./replay-teardown.ts", }); ``` ```ts // checkout.spec.ts import { expect, test } from "@playwright/test"; const admin = "http://127.0.0.1:4010/__apigenie"; test.beforeEach(async ({ page }, testInfo) => { const name = testInfo.titlePath.join(" > "); // every request of the page names the test, the API's included await page.setExtraHTTPHeaders({ "X-Apigenie-Test": name }); // the test starts at its first recorded exchange, also when it is retried await fetch(`${admin}/replay/reset`, { method: "POST", body: JSON.stringify({ test: name }) }); }); test("checks out a cart", async ({ page }) => { await page.goto("/cart"); await page.getByRole("button", { name: "Check out" }).click(); await expect(page.getByText("Thank you")).toBeVisible(); }); ``` ```ts // replay-teardown.ts: the run fails when the application drifted from the recording export default async function teardown() { const report = await (await fetch("http://127.0.0.1:4010/__apigenie/replay")).json(); if (!report.passed) { throw new Error( `the replay failed: ${report.neverReplayedCount} exchanges never replayed, ` + `${report.unmatchedCount} requests matched none: ${JSON.stringify(report.unmatched, null, 2)}`, ); } } ``` The server answers the CORS preflight of the header for a page served from another origin. To record the suite, run it once with `--record recordings/checkout --proxy-to https://staging.example.com` in place of `--replay`: the same header tags the recorded exchanges with the tests. Playwright stops its web servers without letting them finish, so such a recording has no `session.yaml`; the replay reads the index from the exchange files, as `session export` does. On CI, a request the recording lacks gets a 502, so the test that sent it fails at that step, with the closest recording and the differences in the response; the teardown fails the run when a test no longer sends what was recorded (exchanges never replayed). A job that runs the server itself sees the same as its exit status: ```sh apigenie-server openapi.yaml --port 4010 --replay recordings/checkout --log-format json > replay.log & server=$! npx playwright test curl -s -X POST http://127.0.0.1:4010/__apigenie/shutdown wait $server # 5 when the run drifted from the recording; replay.log has the report ``` ## Several services One `apigenie-server` serves several documents at once: an application's backends, a gateway's services, the HTTP and the messaging side of a system. Each document is a **service** with a name and a **mount**, where its clients reach it. Several `SPEC`s on the command line are services named after their files (`pets.openapi.yaml` is `pets`) and mounted at `/<name>`; a `.proto` file gets a port of its own. A single `SPEC` is served as before, without a prefix. The configuration's `services` list says more: ```yaml version: "1.0" server: {port: 4010} validation: {request: enforce} # the services' defaults services: - name: pets spec: pets.yaml # mounted at /pets - name: orders spec: orders.yaml mount: /shop/orders proxy: {target: "http://localhost:8080", mode: fallback, record_har: session.har} scenarios: outage: {rules: [{status: 503}]} - name: events spec: events.asyncapi.yaml mount: {host: events.localhost} - name: greeter spec: greeter.proto mount: {port: 50051} ``` - **What a service sets**: `name` and `spec` (required), `mount`, and the settings of its document: `proxy`, `stubs`, `scenarios`, `overrides`, `validation`, `security`, `operations`, `responses`, `generation`, `headers`, `asyncapi`, `graphql` and `grpc`. These keys at the top of the file are every service's defaults; a service's `stubs`, `scenarios`, `overrides` and `proxy` replace the defaults rather than adding to them. `server`, `cors`, `simulation`, `tls`, `admin`, `log`, `contract`, `scripts`, `db`, `request_overrides` and `asyncapi.mqtt_port` are the server's. - **Mounts.** A path prefix (`/shop/orders`, default `/<name>`) on the server's port: the service gets the requests below it, without the prefix, so its own base path and routes are unchanged. `{host: events.localhost}` takes the requests whose `Host` header names that host (any port), and may add a `prefix`. `{port: N}` is a listener of the service's own (`0` picks a free one), which a `.proto` file's service needs: gRPC clients can't add a prefix. Requests the server's port gets go to the services by host first, then to the one with the longest prefix (prefixes end at a `/`); the others get 404 `no-service`, which lists the mounts. Two services can't share a name, nor can one's mount hide another's (`/shop` and `/shop/orders`), nor can a mount cover the admin API. - **Proxies** forward the path without the mount's prefix, after their own (`--proxy-to`'s path). `--proxy-to`, `--stubs`, `--record-dir` and `--record-har` name one document's upstream and recordings, so with several services they are refused: set them per service. - **WebSocket**: an AsyncAPI service's channels and a GraphQL service's subscriptions share the server's port, below the service's mount (`ws://127.0.0.1:4010/events/v1/rooms/1`). - **MQTT**: one broker for every service (`--mqtt-port`). Topics are served as the documents name them, unless the MQTT channels of two services can match the same topic (`doors/+/open` in both, or `status` and `#`): then the topics of each of those services start with its name (`front/doors/7/open`). The startup line shows the prefix (`MQTT topics under front/`). An `mqtt://` upstream goes with one document. - **The log** names the service of each event (`"service": "orders"` in JSON, `[orders]` in the stylish log); event paths are within the service, without its prefix. Ids are unique across the services. - **The admin API** (`/__apigenie`) covers every service: `GET /__apigenie/services` lists them with their mounts and URLs, the items of `/routes` (full paths), `/scenarios`, `/overrides`, `/log` and the status's `documents` name their service, and `?service=NAME` narrows any endpoint to one service. An endpoint of one service's resources (a scenario of two services, `PUT /overrides`, `/state`, `/db`, `/spec`) needs it when more than one service could be meant, and answers 400 `admin-service-required` otherwise; a service's own endpoints (the gRPC ones, `/contract/violations`) answer 404 without it. `PUT /simulation`, `POST /seed` and `POST /reload` apply to every service unless one is named. - **The contract report** (`--report`, `GET /__apigenie/contract`) has a section per service in `sections`, each a whole report of its document with its `name` and `mount`, and a `summary` that counts them together: requests, violations and `coverage.combined`, the units of every service added up (OpenAPI branches, GraphQL fields, gRPC methods, AsyncAPI messages). `--fail-on` and `--min-coverage` apply to that summary: a violation of any service fails the run, and the combined coverage must reach the threshold (the reason names the service with the lowest). The JUnit report has a suite per service. - **The HAR file** of the proxies is one per server: every service that records names the same file, and each entry's `_apigenie.service` names its service; URLs keep the mount's prefix. - **Reloads** (`--watch`): a service reloads when its document, stubs or scripts change; a change of the configuration reloads every service. Adding or removing a service needs a restart. ## Document store Scripts keep documents in collections: `ctx.db.<collection>`, made the first time a script names it. Writes are kept in memory for the run (see `--db-file`), shared by every script and shown by the admin API's `/__apigenie/db`. ```js export default { onRequest(req, ctx) { if (req.method === 'POST' && req.path === '/notes') { return { status: 201, body: ctx.db.notes.insert(req.body) }; } if (req.path === '/notes') { return { body: ctx.db.notes.find({ tags: { $contains: 'home' } }, { sort: '-id', limit: 10 }) }; } }, }; ``` | Method | Returns | | --- | --- | | `insert(document)` | The document as stored, with its id. | | `get(id)` | The document, or `undefined`. `"3"` finds `3`. | | `find(where, {limit, offset, sort})` | An array of documents. | | `count(where)` | A number. | | `all()` | Every document, in the order they were inserted. | | `update(id, patch)` | The document after a [JSON merge patch](https://www.rfc-editor.org/rfc/rfc7396) (`null` removes a property), or `undefined` for an unknown id. The id stays. | | `replace(id, document)` | The new document with the old id, or `undefined`. | | `delete(id)` | `true` when there was such a document. | | `clear()` | How many documents went. | `ctx.db.collections()` lists the collections and `ctx.db.reset()` (`reset('notes')` for one) puts them back to their seeds. - **Queries.** `where` is an object of fields and values, all of which must match: `{status: 'open'}`, `{'address.city': 'Kraków'}` (a path), or operators: `$eq`, `$ne`, `$in` (an array of values), `$gt`, `$gte`, `$lt`, `$lte` (numbers and strings), `$contains` (a substring, or an item of an array): `{weight: {$gte: 5, $lt: 20}}`. Numbers and strings are never equal. `sort` is `'field'`, `'-field'` (descending), `'a,-b'`, an array of them or `[{field, descending}]`; missing values sort last. Anything fancier is a JavaScript filter on a limited result. - **Ids.** A document keeps its own `id` (a string or an integer), else gets the next one: 1, 2, 3... (`"1"`, `"2"`... for a resource whose id is a string, a UUID made from the run's `--seed` for `format: uuid`). An id in use is refused, and the script's call throws. - **Schemas.** The collection of a [resource](#crud-resources) is bound to its schema: a new document gets the `readOnly` properties it lacks (generated, like `createdAt`), and every write is checked. A document that doesn't match is kept and reported in the log, or refused (422 for a request, a throw for a script) when `validation.response` is `enforce` (`--strict`). - **Seeds.** `db.seed` names a file of documents (a JSON or YAML array) per collection, or a directory of them (`seed/shipments.json` seeds `shipments`). A resource's collection without one is seeded from its list operation's example, else with `db.generate` (3) documents generated with the run's seed. `ctx.db.reset()` and `POST /__apigenie/db/reset` go back to the seeds, not to nothing. A seed that doesn't match the schema is kept and reported at the start; a file that can't be read stops the server with exit status 2. - **Tests.** A request with an `X-Apigenie-Test: <id>` header reads and writes a namespace of its own, which starts as a copy of the seeds: parallel test runs don't see each other's documents. The admin API's `/db` endpoints take the same header (or `?test=<id>`). - **Snapshots.** `PUT /__apigenie/db/snapshots/{name}` copies the documents of every collection, `POST /__apigenie/db/snapshots/{name}/restore` puts them back. - **Persistence.** In memory by default, so a restart is a clean slate. With `--db-file` (or `db.file`), the store is read from that JSON file at the start (a file that can't be read is reported, and the store starts empty) and written there when the server stops or on `POST /__apigenie/db/save`. The file holds `formatVersion` 1, the collections' seeds, the documents of each namespace and the snapshots. Seed files don't replace what the file holds. - **Log.** Every write is a `db` event: the collection, `insert 3`, `update 3`, `delete 3`, `clear 2 documents`, `reset`, `restore <name>`, `seed 3 documents from file`, who wrote (`script:mock.mjs`, `admin`) and the test's namespace (`test`). `onMessage` and `onGraphQL` use the default namespace. - **After a reload.** The documents stay; collections are bound again, new ones seeded, and documents that no longer match their schema are reported. - **Several services.** Each [service](#several-services) has a store of its own, which its scripts' `ctx.db` and its resources use; `db.seed` seeds every service's collections, and the admin API's `/db` endpoints take `?service=` when several could be meant. `--db-file` (and `db.file`) keep the store of a server of one document; with several services it exits with status 2. - **Limits.** A collection keeps up to `db.max_documents` (10,000) documents per namespace. The store is not a database: no transactions, relations or durability promises. ### CRUD resources With `db.crud: auto` (or `--crud`), a path and the path of its items, such as `/shipments` and `/shipments/{id}`, make a resource whose collection (`shipments`) its operations read and write; no script is needed. Without it (`db.crud: explicit`, the default), only the paths an `x-apigenie-mock: {resource: ...}` extension names are resources; `db.crud: off` serves none. | Operation | Answer | | --- | --- | | `GET /shipments` | The documents, paged and filtered by the operation's query parameters. | | `POST /shipments` | The document stored, with its id and `readOnly` properties; 201 when the operation declares it. | | `GET /shipments/{id}` | The document, or 404. | | `PUT /shipments/{id}` | The document replaced (the id and `readOnly` properties stay), or 404. | | `PATCH /shipments/{id}` | The document after a JSON merge patch, or 404. | | `DELETE /shipments/{id}` | 204 when the operation declares it, else the document that went; 404 for an unknown id. | - **The schema** is the list response's items, or the items of its object's array property (an envelope such as `{data: [...], total: 3}`: one array property, or the one called `data`, `items`, `results` or like the collection), else the item response's or the request body's. The id is the schema's `id` property, else the path parameter when it is a property; integers count, strings with `format: uuid` are UUIDs. Answers leave `writeOnly` properties out. - **Lists** page by the list operation's parameters: `offset` and `limit`, `page` (from 1) and `per_page` (`page_size`, `pageSize`, `size`, `limit`), or `cursor` (`after`, `page_token`) and a size, the size's `default` when not given (20 without one). An envelope gets the page in its array property, and `total` (`totalCount`, `total_count`...), `hasMore`, `nextCursor`, `page`, `offset` and `limit` filled in when it declares them; its other members are generated. Other query parameters named like a property of the documents filter by it (`?status=open`), and `sort` (`order_by`) sorts: `-createdAt`, `status:desc,id`. - **What comes first.** Requests are validated before the store sees them (422 under `validation.request: enforce`). A request's `Prefer` (or `__` parameters), a scenario's or an override's rule, an extension that sets the status, an example or a body, and stubs answer instead of the store. The store's answers are validated like any other, and say `X-Apigenie-Mock-Source: db`. - **The extension**, on a path item or an operation of either path: `resource: shipments` (the collection's name), `resource: false` (not a resource, also with `db.crud: auto`) or `resource: {name: parcels, id: code, schema: Parcel, list: entries}` (the id property, the schema under `components/schemas` or as a `#/` reference, the envelope's array property). - **Proxies.** A document served with `--proxy-to` has no resources: the server's state is the upstream's. ```yaml paths: /shipments: x-apigenie-mock: {resource: {name: shipments, schema: Shipment}} get: ... /shipments/{id}: get: ... ``` ## Configuration `apigenie-server` reads `--config`, else `apigenie-server.yaml` (`.yml`, `.json`), then `apigenie-mock.yaml` (`.yml`, `.json`; the name before the rename, still read) or `.apigenie.yaml` in the current directory, else the shared `apigenie.yaml` of the project (in the current directory or a parent, up to the root of the repository; see [configuration](/docs/reference/cli/configuration#where-the-file-is-found)). In a file of its own the settings are at the top level; in a shared configuration they are under `commands.mock`. The command line wins over the environment variables, which win over the file; [`apigenie config --for mock --origin`](/docs/reference/cli/config) shows which one set each value. The JSON Schema of a file of its own is `https://apigenie.pl/schemas/apigenie-server-config/latest.json`. ```yaml version: "1.0" specs: openapi.yaml server: {host: 127.0.0.1, port: 4010, base_path: /api, trailing_slash: ignore, watch: true} # strict_base_path: serve the paths only under the prefix (true with base_path) cors: {enabled: true, origins: ["*"], allow_credentials: true, expose_headers: ["*"], max_age: 600} responses: {mode: static} headers: {diagnostics: true} generation: seed: 42 max_depth: 8 null_probability: 0 smart_names: false limits: {min_items: 1, max_items: 5, min_length: 1, max_length: 32, min_properties: 0, max_properties: 10, minimum: 0, maximum: 1000} validation: {request: enforce, response: log, formats: true, unknown_query_parameters: allow} security: mode: check # off, check (presence) or enforce (the credentials below) credentials: {api_keys: {apiKey: [secret]}, bearer_tokens: [token], basic: [{user: u, password: p}]} request_overrides: {header: true, query_params: true} simulation: {latency: 100-300, jitter: 20, drop: {probability: 0.01, mode: reset}} operations: getPet: {status: 404} GET /pets: {latency: 1000} stubs: [recorded] # OpenAPI: recorded exchanges to answer with (--stubs) scenarios: outage: description: the backend is down rules: - match: {tag: pets} status: 503 headers: {Retry-After: "30"} body: {title: Service unavailable} overrides: [] scripts: [mock.mjs] # relative to the configuration file scripting: {timeout_ms: 1000, memory_limit_mb: 64, fail_mode: log} asyncapi: server: null # the one server to serve publish: {enabled: true, interval: 1000, count: 0, jitter: 0} validate_incoming: enforce # off, log or enforce mqtt_port: 1883 # 8883 when tls.cert serves MQTT over TLS grpc: proto_path: [protos] # relative to the configuration file stubs: [stubs] max_message_bytes: 4194304 reflection: true health: true validation: {request: enforce, response: log} # off, log or enforce; both log with --proxy-to unless set here generation: true # false: UNIMPLEMENTED when nothing answers proxy: {target: null, mode: fallback, record_dir: null, redact: [authorization, cookie, x-api-key, proxy-authorization]} # target: HOST:PORT (h2c) or https://host[:port] (TLS) history_size: 1000 graphql: path: /graphql # under server.base_path introspection: true playground: true # GraphiQL schema_endpoint: true # /graphql/schema.graphql allow_get_mutations: false lists: {min: 1, max: 3} null_probability: 0 max_depth: 0 # deeper operations are rejected; 0 for no limit max_batch: 10 fields: User.email: {faker: internet.email} Book.pages: {min: 50, max: 900} Status: {values: [OPEN, CLOSED]} examples: [] # as the files of examples_files examples_files: [more_examples] discover_examples: true # schema_examples/ next to schema.graphql store: {enabled: true, max_entities: 10000, unknown_ids: generate} subscriptions: {enabled: true, interval: 1000, count: 0, jitter: 0, legacy_protocol: true, init_timeout: 3000} proxy: # OpenAPI, GraphQL: contract testing against a real server (gRPC: grpc.proxy) target: http://localhost:8080 # or https://, HOST:PORT, ws:// or wss:// (AsyncAPI); OpenAPI, AsyncAPI: a path is put before the requests' paths, # AsyncAPI: a list may add an mqtt:// or mqtts:// broker: [ws://localhost:9000, mqtt://localhost:1884] # GraphQL: the path is the server's endpoint unknown_routes: forward # or reject (404, 405) mode: always # or fallback: OpenAPI: the mock answers when the server can't be # reached, times out or answers 404/501 for an operation; # GraphQL: examples, scenarios, scripts answer first schema_check: true # GraphQL: introspect the server and compare the schemas record_dir: null # OpenAPI: write the exchanges as stub files there; # GraphQL: write the answers as examples there record_har: null # OpenAPI: write the session as a HAR file redact: [authorization, cookie, set-cookie, proxy-authorization, x-api-key] timeout_ms: 30000 connect_timeout_ms: 5000 max_in_flight: 64 max_response_bytes: 16777216 contract: report: contract.json # relative to the configuration file report_junit: contract.xml fail_on: none # none, errors or warnings: exit status 5 min_coverage: 0 # percent of operations, request bodies and responses (GraphQL: fields; AsyncAPI: messages; gRPC: methods) severity: {response.header.undefined: warn, response.status.default: ignore} # GraphQL's, AsyncAPI's and gRPC's checks too record: # see Session recording; the directory relative to the configuration file dir: null # record the session there from the start (--record); new or empty name: null # the session's name; the directory's when not given redact: [] # hidden besides proxy.redact: header names, and JSON pointers (/password) to values of bodies and messages replay: # see Session replay; the directory relative to the configuration file dir: null # answer from the session recorded there (--replay) missing: fail # fail (502 problem), generate (the mock answers) or passthrough (proxy.target answers) ignore: [] # not compared besides dates, readOnly fields and volatile headers: header names, JSON pointers (/id, /items/*/id) update: false # with missing: passthrough, record the run and replace the session with it at stop admin: {enabled: true, path: /__apigenie, port: null, allow_remote: false, log_size: 1000} log: {level: info, format: stylish, bodies: false, body_limit: 4096, headers: false, file: null} tls: # see TLS; paths relative to the configuration file cert: null # serve HTTPS with this certificate chain and key (both or neither) key: null client_ca: null # require client certificates of these authorities (mTLS); needs cert ca_certs: [] # trusted for https://, wss:// and mqtts:// upstreams, besides the system's roots insecure: false # don't verify upstream certificates client_cert: null # presented to upstreams that ask for a client certificate (with client_key) client_key: null db: # see Document store; paths relative to the configuration file crud: explicit # auto: serve /things and /things/{id} from the store (--crud); explicit: only x-apigenie-mock resources; off seed: {notes: seed/notes.json} # or a directory of <collection>.json/.yaml files: seed, seed/*.json generate: 3 # documents generated for a resource's collection nothing else seeds max_documents: 10000 # per collection and namespace; 0 for no limit file: null # keep the store across runs (--db-file) ``` A mistake in the file stops the server with exit status 2, the position and the JSON pointer of the value; an unknown setting is reported with the closest known name. ## Dashboard `http://127.0.0.1:4010/__apigenie/ui` is a page served by the server itself, for a browser on the same machine: it follows the [admin API](#admin-api)'s rules (local clients only unless `admin.allow_remote`, on `admin.port` when the admin API has a port of its own, off with `--admin off`). The header on every screen names the documents and how they answer (mock, proxy, generated), where they listen, the seed, the uptime, the requests and violations so far, and has Reload and Stop; with [several services](#several-services) a switcher narrows every screen to one. | Screen | What it shows and does | | --- | --- | | Traffic | Every exchange as it happens (the [event stream](#the-event-stream)), with filters; an exchange's request and answer (headers and bodies, captured while the page is open) and its validation problems with where they point in the document. Copy as curl, Replay, Save as stub (`POST /stubs`), Add to `.http` (a file built up in the browser, to copy or download), Copy as YAML (the answer as an `operations` entry of the configuration). Record session and Save session: a [session recording](#session-recording) of what happens from here, written where the server runs. | | Operations | The operations by tag, with their hits, their last status and what answered them last (an example, a generated body, a scenario, a proxy). For one operation: a fixed answer (a declared status, one of its examples, a generated body, a dropped connection), a latency and whether its requests are validated, saved as an always-on [override](#scenarios-and-extensions) (`PUT /overrides`) until the server stops, with the `Prefer` header (and `__` parameters) that do the same for one request. | | Scenarios and network | The scenarios of the configuration, the document and the admin API, switched on and off, with their rules and hits; a builder for a new one (by operation, tag, method and path, header or query parameter; status, example, latency, probability); the network simulation of every response (fixed, uniform, normal or log-normal latency, jitter, dropped connections, bandwidth, the seed) with the response times of the last requests. | | Contract | Whether the server would pass `--fail-on` and `--min-coverage` if it stopped now, the coverage, the violations by check (and the exchanges they were seen in), the operations never called, the report as JSON or JUnit to download, and Start over (`DELETE /contract`). | | Data | The [document store](#document-store)'s collections with their documents, Reset to seed, snapshots to save and restore; and `ctx.state`, the values the scripts keep, to edit or reset. | | Channels | For an [AsyncAPI](#asyncapi) document only (the screen is listed when a served document has channels): the channels by WebSocket, MQTT and HTTP, with their operations, the clients connected and the messages counted (`GET /asyncapi/channels`); for a channel, its messages in and out as the event stream brings them (with an exchange's payload and validation problems, and Open in Traffic), its clients (`GET /asyncapi/clients`, followed through the `ws-open`, `ws-close`, `mqtt-subscribe` and `mqtt-unsubscribe` events), and Publish: a message sent as the application would (`POST /asyncapi/publish`), to every client of the channel or to one, with a payload started from the document's examples or left to the server; a payload the message's schema refuses shows the violations and goes nowhere, until Send anyway; Copy as curl. | Every panel copies its settings as the configuration's YAML, or as `x-apigenie-mock` for the document, so what was tried in the dashboard is kept in a file (Channels changes no setting: it copies the publish as curl). The page follows the system's light or dark theme, or the one chosen in the header. The page is built from the web components package (`src/app_web`, the `apg-server-*` elements) and embedded in the binary; nothing is fetched from the network. The design is ADR-167. ![The Traffic screen of the dashboard](/server-dashboard-light.png) ## Admin API On by default for local clients only (`admin.allow_remote` opens it, `admin.port` moves it to a port of its own). The `/db` endpoints work on the namespace of an `X-Apigenie-Test` header (or a `?test=` parameter), the default one without it: | Endpoint | Description | | --- | --- | | `GET /__apigenie` | Status: version, `url` (where clients reach the server), document (with its proxies and stubs; an AsyncAPI document's `mqttProxy` and `mqttClients`), seed, uptime (`uptimeSeconds`, `startedAt`), requests (the clients', not the admin API's), active scenarios, simulation, `logSize`. | | `GET /__apigenie/ui/` | The [dashboard](#dashboard): the page and its files (`GET /__apigenie/ui` redirects to it). | | `GET /__apigenie/routes` | The operations served, with their `hits`, `lastStatus`, `lastSource` (what answered last) and `lastAt`; an OpenAPI operation's `tags`, `deprecated` and `responses` (each status with its content types and the names of its examples). | | `DELETE /__apigenie/hits` | Start the hits of routes and scenarios over (a reload keeps them). | | `GET /__apigenie/services` | The services: name, mount, URL, document (see [several services](#several-services)). | | `GET /__apigenie/scenarios`, `PUT` / `DELETE /__apigenie/scenarios/{name}` | List (with `hits`, `lastStatus`, `lastSource` and `lastAt`, and the rules as `ruleList` next to their count `rules`), add or remove scenarios. | | `POST /__apigenie/scenarios/{name}/activate`, `/deactivate` | Switch a scenario on or off. | | `GET`, `PUT /__apigenie/overrides` | The always-on rules, whole: what `GET` answers, `PUT` takes back. | | `GET`, `PUT /__apigenie/simulation` | The network simulation of every response. | | `POST /__apigenie/seed` | Reseed: `{"seed": 42}`. | | `GET`, `DELETE /__apigenie/log?limit=&after=&level=` | The recent events, with headers and bodies when they were captured (see [log entries](#log-entries)). | | `GET /__apigenie/events?type=&level=&service=&after=` | The events as they happen, as Server-Sent Events (see [the event stream](#the-event-stream)). | | `POST /__apigenie/stubs` | A stub file of a logged exchange: `{"id": 42, "dir": "stubs", "name": "rex"}` (see [stubs from the log](#stubs-from-the-log)). | | `GET /__apigenie/record` | The [session recording](#session-recording): `recording`, and the session's `dir`, `name`, `started`, `exchanges` while one is on; `test`, the mark. | | `POST /__apigenie/record/start`, `/stop` | Record a session in `{"dir": "sessions/run-7", "name": "run 7"}` (`dir` defaults to `record.dir`), or stop it and write `session.yaml`. | | `POST /__apigenie/record/mark` | `{"test": "checkout-happy"}`: the test of the exchanges to come that carry no `X-Apigenie-Test` of their own; `{}` clears it. | | `GET /__apigenie/replay` | The [session replay](#session-replay)'s report so far: `exchanges`, `replayed`, `neverReplayed`, `unmatched` (with their tests and closest recordings), `passed`, and `tests`, the exchanges each test used since its reset. | | `POST /__apigenie/replay/reset` | Restart a test's counters: `{"test": "checkout"}`, or the `X-Apigenie-Test` header, or `?test=`; without one, every test's. 204. | | `POST /__apigenie/reload`, `POST /__apigenie/shutdown` | Reload, or stop gracefully. | | `GET /__apigenie/spec` | The served document, bundled (converted to OpenAPI 3.2 for a Swagger 2.0 document; a `.proto` file with its imports, or a GraphQL schema, as text). | | `GET`, `PUT`, `DELETE /__apigenie/state/{key}` | Values kept between requests. | | `GET /__apigenie/db` | The [document store](#document-store): its file, namespaces, snapshots and collections (documents, seeds, id, and the schema and operations of a resource's collection). | | `GET /__apigenie/db/{name}?limit=&offset=` | A collection's documents (100 by default) and `total`. | | `PUT`, `DELETE /__apigenie/db/{name}` | Replace every document of a collection (a JSON array; all or nothing: 422 when the schema refuses one, 409 for an id used twice), or remove them. | | `POST /__apigenie/db/reset` | Back to the seeds: with `X-Apigenie-Test`, that namespace; without it, the default one, and the tests' namespaces go. `{"collections": ["notes"]}` resets some. | | `GET /__apigenie/db/snapshots`, `PUT`, `DELETE /__apigenie/db/snapshots/{name}` | List, take (or replace) or remove a snapshot of the documents. | | `POST /__apigenie/db/snapshots/{name}/restore` | Put a snapshot's documents back. | | `POST /__apigenie/db/save` | Write the store to `--db-file` now (501 without one). | | `DELETE /__apigenie/db/namespaces/{test}` | Forget a test's namespace. | | `GET`, `DELETE /__apigenie/contract` | The contract report (coverage and violations), or start it over. | | `GET /__apigenie/contract.json`, `GET /__apigenie/contract.xml` | The contract report as `--report` (JSON) and `--report-junit` (JUnit XML) would write it now, to save. | | `GET /__apigenie/contract/violations?operation=&check=&limit=` | The violations seen, by operation (id or `GET /pets/{id}`; GraphQL: the operation's name or a root field such as `Query.book`; AsyncAPI: the operation's key; gRPC: `package.Service/Method` or its path) and check. | | `POST /__apigenie/contract/schema-check` | GraphQL proxy: introspect the server and compare its schema now (202; the result is logged and reported). | | `GET /__apigenie/asyncapi/channels` | AsyncAPI: the channels, their operations and messages (with the document's examples), the clients connected, and the messages counted (see [AsyncAPI channels](#asyncapi-channels-from-the-admin-api)). | | `GET /__apigenie/asyncapi/clients` | AsyncAPI: the WebSocket clients and MQTT subscriptions of the channels. | | `POST /__apigenie/asyncapi/publish` | AsyncAPI: send a message to a channel's clients, or to one of them: `{"channel": "rooms/42", "payload": {"text": "hi"}}`, `{"channel": "room", "client": 7, ...}`. | | `GET /__apigenie/grpc/services` | gRPC: the services and their methods. | | `GET`, `POST`, `DELETE /__apigenie/grpc/stubs` | gRPC: list, add (one stub or a list) or remove the stubs added at run time (`?scope=all`: every stub). | | `GET`, `PUT`, `DELETE /__apigenie/grpc/stubs/{name}` | gRPC: one stub. | | `POST /__apigenie/grpc/stubs/reset`, `GET /__apigenie/grpc/stubs/schema` | gRPC: back to the stub files, and the stubs' JSON Schema. | | `GET`, `DELETE /__apigenie/grpc/history?method=&limit=&after=&q=` | gRPC: the calls received, newest last. | | `POST /__apigenie/grpc/verify` | gRPC: count the calls that match `{method, request, metadata, stub, code}`. | A server of [several services](#several-services) names the service of each item, and takes `?service=NAME` on every endpoint. ### The event stream `GET /__apigenie/events` keeps the connection open and writes each event of the log as it happens, as [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html): `id:` is the log entry's id, `event:` its kind, and `data:` the same JSON object as a `/log` entry. A dashboard reads it with `EventSource`; a script with `curl -N`: ```bash curl -N 'http://127.0.0.1:4010/__apigenie/events?type=request,violation' ``` ```text : connected id: 12 event: request data: {"event":"request","id":12,"time":"2026-10-12T09:00:00.000Z","level":"info","method":"POST","path":"/pets","operationId":"addPet","status":201,...} id: 13 event: request data: {"event":"request","id":13,...,"status":422,"violations":[...]} id: 13 event: violation data: {"event":"request","id":13,...,"status":422,"violations":[...]} ``` | Event | When | | --- | --- | | `request` | An exchange, request and answer together (there is no separate response event). | | `violation` | After an event with violations: the same id and data. | | `ws-open`, `ws-in`, `ws-out`, `ws-close`, `mqtt-*`, `grpc`, `grpc-in`, `grpc-out` | WebSocket, MQTT and gRPC traffic, as in the log. | | `scenario` | A scenario was added, replaced, removed, activated or deactivated through the admin API (`message` says which). | | `reload` | The documents were reloaded (`POST /reload` or `--watch`). | | `contract-updated` | The contract report's coverage changed (at most once a second), or it started over (`message: "reset"`, after `DELETE /contract` or a reload). | | `db` | A write to the [document store](#document-store): the collection, what changed, who wrote. | | `admin`, `script`, `server` | The admin API's own exchanges, the scripts' logs, the server's errors. | `?type=` keeps the named events, `?level=` those at a level and above (default `debug`), `?service=` one service's. `?after=ID`, or the `Last-Event-ID` header a reconnecting `EventSource` sends, first replays the log's later entries, so a client that reconnects misses nothing the log still holds. A comment every 15 seconds keeps the connection alive. A client that falls more than 1 MiB behind is disconnected. The stream follows the admin API's rules: local clients only unless `admin.allow_remote`, on `admin.port` when the admin API has a port of its own. ### Log entries While at least one event stream is open, the server captures the headers and bodies of every exchange, as `log.headers: true` and `log.bodies: true` do. `/log` entries and stream events then carry `headers` and `responseHeaders` (arrays of `[name, value]`), `requestBody` and `responseBody`, and `captured: {"headers": true, "bodies": true}`. A body that isn't UTF-8 is in Base64 with `requestBodyEncoding` (or `responseBodyEncoding`) `"base64"`. A body is kept up to `log.body_limit` bytes (4096 by default); a longer one is cut and marked `requestBodyTruncated` or `responseBodyTruncated`, and `requestSize` and `responseSize` keep the whole sizes. ### Stubs from the log `POST /__apigenie/stubs` makes a [stub file](#contract-testing-validating-proxy) of a logged exchange of an OpenAPI document, as `--record-dir` writes them: the client's headers without the connection's, the hidden headers of `proxy.redact` as `[redacted]`, and the query without the mock's own `__` parameters. ```bash curl -X POST http://127.0.0.1:4010/__apigenie/stubs -d '{"id": 12, "dir": "stubs", "name": "new-pet"}' ``` With `dir`, or else the service's `proxy.record_dir`, the stub is written to a new file there, `<operationId>-N.yaml`, and the answer (201) has the `file` and the `stub`. Without either the answer (200) is the file's content, `{"stubs": [...]}`. The entry must still be in the log, and must have been captured whole (an open event stream, or `log.headers` and `log.bodies`, and a `log.body_limit` above its bodies' sizes): else 404 or 409. The running mock answers with the new stub after `POST /__apigenie/reload` when `dir` is one of its `stubs` directories. ### AsyncAPI channels from the admin API `GET /__apigenie/asyncapi/channels` lists the channels: `key`, `address`, `protocols`, `paths`, `messages` (their names) and `messageList` (each message's `name`, `contentType`, `summary` and `examples`, the document's, each with its `payload` and `headers`: a starting point for a payload to publish), `operations` (with `qos` and `retain` for MQTT), the `clients` connected now and the contract report's `connections`, `messagesIn`, `messagesOut` and `unmatched` counts. `GET /__apigenie/asyncapi/clients` lists the WebSocket clients and MQTT subscriptions: `id`, `protocol`, `remote`, `channel`, `address`, `route`, `path` (the WebSocket path, or the MQTT filter), `clientId` (MQTT), `connectedAt` and `messagesOut`. `POST /__apigenie/asyncapi/publish` sends a message to the clients of a channel, as the schedule sends one: scripts see it, and each delivery is logged as `ws-out` or `mqtt-out` with `source: "admin"`. ```bash curl -X POST http://127.0.0.1:4010/__apigenie/asyncapi/publish \ -d '{"channel": "rooms/42", "message": "notice", "payload": {"text": "maintenance at noon"}}' ``` ```json {"channel": "room", "address": "rooms/42", "operation": "sendNotices", "message": "notice", "source": "admin", "contentType": "application/json", "payload": "{\"text\":\"maintenance at noon\"}", "delivered": 1, "webSocket": 1, "mqtt": 0, "clients": [7]} ``` With a [Kafka tap](#contract-testing-of-kafka-channels), a Kafka channel's message is produced to the cluster instead. The record's `key`, `headers` (an object of strings) and `partition` may be given; without them the key and headers are made from the message's Kafka binding and headers, and the partition is the key's. The record is checked as the tap checks records (422 with the `violations`, nothing produced, unless `"validate": false`), produced, and answered with where it went; a cluster that can't be reached or refuses it answers 502. The tap then reads it like any other record. ```bash curl -X POST http://127.0.0.1:4010/__apigenie/asyncapi/publish \ -d '{"channel": "orders", "payload": {"sku": "A-1"}, "key": "cust-1", "headers": {"correlationId": "c-1"}}' ``` ```json {"channel": "orders", "operation": "placeOrder", "message": "order", "source": "admin", "contentType": "application/json", "payload": "{\"sku\":\"A-1\"}", "kafka": {"upstream": "kafka://localhost:9092", "topic": "shop.orders", "partition": 1, "offset": 42, "key": "cust-1", "headers": {"correlationId": "c-1"}}} ``` With an [AMQP tap](#contract-testing-of-amqp-channels), an AMQP channel's message is published to the broker: a queue channel's through the default exchange, any other to its exchange with `routingKey` (else the address given, else the address with its parameters filled). Its `properties` (`contentType`, `contentEncoding`, `deliveryMode`, `priority`, `correlationId`, `replyTo`, `expiration`, `messageId`, `timestamp`, `type`, `userId`, `appId`) and `headers` may be given; without them they come from the message's and the operation's AMQP bindings and the message's headers. The message is checked (422), published mandatory with publisher confirms, and answered with `{"amqp": {"upstream", "exchange", "routingKey", "headers"}}`; a message no queue takes answers 409 `asyncapi-unroutable`. `channel` is a channel's key (every client of the channel gets the message) or an address with its parameters (that address's clients only); `client`, an `id` of `/asyncapi/clients`, narrows it to that one client. `message` names one of the channel's messages; without it, the first message of its first `send` operation is sent. `payload` is the message as JSON, `raw` as text, and without either one is made as for the schedule. A payload that doesn't match the message's schema answers 422 with the `violations` and is sent to nobody, unless `"validate": false`. Unknown channels and messages answer 404, as does a `client` that isn't connected to the channel (`asyncapi-unknown-client`). An admin publish isn't retained by the MQTT broker. Only clients the mock matched to a channel get it. Clients of a validating proxy are counted in the status, not listed. A server of several services lists every AsyncAPI service's channels and clients, and needs `?service=` to publish when several services serve AsyncAPI. ## TLS `apigenie-server` serves TLS itself, and its proxies reach `https://` and `wss://` servers and `mqtts://` brokers. TLS 1.2 and 1.3 come from Mbed TLS. It's built with what modern servers use: AES-GCM and ChaCha20-Poly1305, ECDHE key exchange on x25519, P-256, P-384 and P-521, and RSA and ECDSA certificates. A server that offers only static RSA or DHE key exchange, or other ciphers, can't be reached. **Serving.** `--tls-cert server.pem --tls-key server.key` (or `tls.cert` and `tls.key` in the configuration) turns the port into HTTPS. WebSocket channels and GraphQL subscriptions become WSS, and clients that ask for `h2` by ALPN get HTTP/2, which is how browsers and gRPC clients over TLS connect. The admin API on a port of its own (`admin.port`) uses the same certificate. The MQTT broker of an AsyncAPI document serves MQTT over TLS too, on port 8883 unless `--mqtt-port` says otherwise. The startup line shows `https://` and `mqtts://` URLs and, as JSON, `"tls": true`. The certificate file may hold the chain, the server's certificate first. The key must be PEM and not encrypted. For local development, `mkcert localhost 127.0.0.1` makes a pair your system trusts: ```sh apigenie-server openapi.yaml --tls-cert localhost+1.pem --tls-key localhost+1-key.pem curl https://localhost:4010/pets ``` **Upstreams.** `--proxy-to https://api.example.com` (OpenAPI, GraphQL, AsyncAPI HTTP channels), `wss://...` (AsyncAPI WebSocket channels), `mqtts://...` (AsyncAPI MQTT channels, port 8883 unless given) and `https://host[:port]` (gRPC, HTTP/2 over TLS) connect over TLS, port 443 unless given. Certificates are verified against the system's roots: - macOS: the system keychain, and the certificates an administrator or the user marked as always trusted; - Windows: the ROOT store; - Linux: the first CA bundle found (`/etc/ssl/certs/ca-certificates.crt`, `/etc/pki/tls/certs/ca-bundle.crt`, ...). `SSL_CERT_FILE` and `SSL_CERT_DIR` replace them on every system. `--ca-cert FILE` adds authorities, such as a company's own root or a test CA, and `--insecure` skips verification altogether. The host name of the URL is checked against the certificate (an IP address against its IP addresses). An upstream whose certificate is rejected gets the usual 502 `upstream-unreachable`, with the reason in the detail. ```yaml tls: cert: certs/server.pem # relative to the configuration file key: certs/server.key ca_certs: [certs/company-root.pem] insecure: false ``` ### Client certificates (mTLS) **Requiring them.** `--tls-client-ca clients-ca.pem` (or `tls.client_ca`) makes every HTTPS, WSS, gRPC and MQTTS connection present a client certificate issued by an authority of that PEM file, as a service behind mutual TLS does. It needs `--tls-cert`. The certificate's extended key usage must allow client authentication when it has one. - A client without a certificate, or with one the authorities didn't issue, fails the TLS handshake. The log gets a warning that says why, for example `TLS handshake with 127.0.0.1 failed: the client presented no certificate`. - The subject of an accepted certificate (`CN=billing, O=Example`) is in each event of the JSON log (`"clientSubject"`), on a `~ client` line of the stylish log, and in the admin API's log. - The contract report lists the clients: `clients` in JSON (`[{"subject": "CN=billing, O=Example", "exchanges": 12}]`, counting HTTP requests, gRPC calls, WebSocket connections and MQTT messages), a `client` property per subject in JUnit, and a `Clients:` line in the summary. - The admin API asks for a certificate too, on the main port and on `admin.port`. ```sh apigenie-server openapi.yaml --tls-cert server.pem --tls-key server.key --tls-client-ca clients-ca.pem curl --cacert ca.pem --cert client.pem --key client.key https://localhost:4010/pets ``` **Presenting one.** `--client-cert client.pem --client-key client.key` (or `tls.client_cert` and `tls.client_key`) is the certificate the proxies present to `https://`, `wss://`, `mqtts://` and gRPC TLS upstreams that ask for one. Upstreams that don't ask never see it. It works with `--insecure` too. The key must be PEM and not encrypted. An upstream that refuses the proxy for want of a certificate gets the usual 502 `upstream-unreachable`, which says so in the detail (an MQTT client's CONNECT gets return code 3). ```yaml tls: cert: certs/server.pem key: certs/server.key client_ca: certs/partners-ca.pem client_cert: certs/proxy.pem client_key: certs/proxy.key ``` **Not supported.** There is no session resumption: every connection does a full handshake. Neither certificate revocation lists (CRL) nor OCSP are checked, so a revoked client or upstream certificate is accepted as long as its chain is valid. Remove a revoked client's authority from `--tls-client-ca`, or issue client certificates with short lifetimes. ## Docker ```sh docker run --rm -p 4010:4010 -v "$PWD:/mnt/data" mykulyak/apigenie-server openapi.yaml ``` It serves GraphQL schemas the same way (`mykulyak/apigenie-server schema.graphql`). The image listens on every interface of the container (`APIGENIE_SERVER_HOST=0.0.0.0`) and exposes port 4010, and 1883 for the MQTT broker (`-p 1883:1883`). It has the CA certificates of Alpine Linux, so `https://` upstreams are verified; mount a certificate and its key to serve HTTPS (`-v "$PWD/certs:/certs" ... --tls-cert /certs/server.pem --tls-key /certs/server.key`). ## npm ```sh npx --package @mykulyak/api-genie apigenie-server openapi.yaml ``` ## Deprecated names `apigenie-server` was called `apigenie-mock` before. The old names keep working until 1.1, with a one-line notice on standard error: - `apigenie-mock`: a symlink to `apigenie-server` in the archives, `.deb`, `.rpm` and Homebrew, a copy in the Windows archive, and a command of the npm package. - `APIGENIE_MOCK_*` variables: read when the `APIGENIE_SERVER_*` variable of the same name is unset. - `mykulyak/apigenie-mock`: its `latest` tag only prints the new image name and exits with 2; the older tags still hold the old server. What did not change: the `apigenie-mock.yaml` configuration file (still read, after `apigenie-server.yaml`), the `commands.mock` section of a shared configuration, the `x-apigenie-mock` extensions, the `X-Apigenie-Mock-*` headers and the `/__apigenie` admin API. ## Limits - TLS: no session resumption, no revocation checks (OCSP, CRLs) of server or client certificates (see [Client certificates](#client-certificates-mtls)). The OpenAPI and GraphQL proxies speak HTTP/1.1 to the server, a connection per request; the AsyncAPI one connects to WebSocket servers without extensions. The AsyncAPI proxy relays WebSocket messages up to 16 MiB; of MQTT 5 it relays the message properties, not those of CONNECT, SUBSCRIBE or DISCONNECT, nor subscription options and identifiers, and the broker publishes wills without their delay. Request and response bodies are buffered (16 MiB by default), so streams such as server-sent events don't fit. A HAR file is kept in memory until the server stops. OpenAPI stubs match exact paths and queries, not templates. - gRPC: native gRPC over HTTP/2 (cleartext, or TLS with `--tls-cert`) only; gRPC-Web, the Connect protocol and REST transcoding (`google.api.http`) aren't served, and messages can't be compressed. Reflection doesn't include custom options. - Several services: one MQTT broker for all of them, so an `mqtt://` upstream goes with one document; one HAR file. Services can't be added or removed without a restart. - Session recording: exchanges are numbered in the order they completed; a gRPC call keeps at most 100 messages per side, a session at most 10 000 exchanges; a `.proto` document's hash covers the file, not its imports; MQTT messages keep neither QoS nor retain nor MQTT 5 properties; `session export` leaves WebSocket and MQTT messages out of the HAR file. - The document store is the OpenAPI document's and the scripts'; the GraphQL mock keeps its own [store](#the-store), and AsyncAPI and gRPC mocks see the documents only through scripts. CRUD resources are a path and its items' path: nested collections (`/users/{id}/orders`) are collections of their own, not filtered by the parent's id, and `readOnly` properties are filled in at the top level only. - AsyncAPI: Kafka, AMQP and other protocols aren't served; Avro and Protobuf payloads are sent from their examples only, and not validated. The MQTT broker keeps no sessions between connections and sends at most QoS 1. - XML bodies: no DTDs (and so no entities but the five predefined ones), UTF-8 and UTF-16 only; mixed content and `xsi:type` aren't mapped, and `nodeType: none` is honoured on arrays only. - Callbacks, webhooks and links are not mocked. OpenAPI 3.2 streams described by `itemSchema` (server-sent events, JSON Lines) are answered with an empty body. - GraphQL: no federation (`_entities`, `_service`), `@defer` and `@stream`, persisted queries or file uploads. Scenarios don't apply to subscriptions over WebSocket or server-sent events. The proxy doesn't forward subscriptions, and its schema check leaves out what only recent servers introspect (`@specifiedBy` URLs, `@oneOf`, repeatable directives, deprecated arguments) and the directives applied in the schema. --- # catalog Source: https://apigenie.pl/docs/reference/catalog `apigenie-catalog` lists the API specifications of a repository, and records their names and owners in the configuration file. It is installed next to `apigenie` by every archive and package, npm and Homebrew; `apigenie catalog ...` prints the `apigenie-catalog` command to run instead. > **(experimental, outside the 1.0 compatibility promise)** `apigenie-catalog` is the first part > of a catalog kept in git: release tags, pinned consumers and a catalog spanning several > repositories will add options and settings. ## Usage ```sh apigenie-catalog list [DIR] [--json] [--config FILE | --no-config] [--git PROGRAM] apigenie-catalog init [DIR] [--prune] [--dry-run] [--config FILE] [--git PROGRAM] apigenie-catalog --version | --credits | --license [LIBRARY] | --license-list ``` ## Description The repository that holds your specifications is the catalog: nothing is uploaded anywhere. `apigenie-catalog list` works in any repository without configuration. `apigenie-catalog init` writes a `catalog` section in [apigenie.yaml](/docs/reference/cli/configuration#catalog) for what can't be derived from the specifications, such as a name that stays when a file moves and an owner. ### What is a specification `apigenie-catalog list` looks at the files below DIR (the current directory by default): - in a Git repository, the files Git tracks or would track: committed and staged files, and new files that no ignore rule (`.gitignore`, `.git/info/exclude`, your global excludes file) excludes; - outside git, every file except those in directories whose name starts with a dot, and in `node_modules`. Files matching a pattern of `catalog.exclude` are left out. Of the others, these are specifications: | Format | Files | | ------ | ----- | | OpenAPI, AsyncAPI, Swagger | JSON and YAML files whose root has the `openapi`, `asyncapi` or `swagger` property | | Protobuf | every `.proto` file | | GraphQL | `.graphql`, `.graphqls` and `.gql` files with type definitions (operation documents are left out) | The files of a split document that only hold components have no `openapi` property, so only its root file is listed. Files that can't be read are reported on the standard error. ### What is listed | Column | JSON | Value | | ------ | ---- | ----- | | NAME | `name` | the name given in the catalog section, else derived: `info.title` (`Orders API` is `orders-api`), the Protobuf package (`acme.pricing.v1` is `acme-pricing-v1`), or the file name (GraphQL) | | FORMAT | `format`, `specVersion` | the format and its version: `OpenAPI 3.1.0`, `Protobuf proto3`, `GraphQL` | | VERSION | `version` | `info.version` | | OWNER | `owner` | the owner given in the catalog section; the column is shown when any specification has one | | PATH | `path` | the file, relative to the root of the catalog | | COMMIT | `commit` | the last commit changing the file (short id and day); `-` for a file not committed yet and outside git | ```text $ apigenie-catalog list NAME FORMAT VERSION PATH COMMIT order-events AsyncAPI 3.0.0 2.0.0 apis/events.json 96175d8 2026-10-12 orders-api OpenAPI 3.1.0 1.2.0 apis/orders/openapi.yaml 4d0c2e1 2026-10-14 schema GraphQL - graphql/schema.graphql 96175d8 2026-10-12 acme-pricing-v1 Protobuf proto3 - proto/pricing.proto 96175d8 2026-10-12 ``` Two specifications with the same derived name are reported on the standard error; name them in the catalog section, which `apigenie-catalog init` does. The **root of the catalog** is the directory of the configuration file (the one given with `--config`, else `apigenie.yaml` found in DIR or a parent up to the root of the repository), else the root of the Git repository, else DIR. Paths are relative to it, in the output and in the catalog section. DIR only narrows the search and must be inside the root. ### apigenie-catalog list | Option | Description | | ------ | ----------- | | `DIR` | The directory to look for specifications in. Defaults to the current directory. | | `--json` | Prints the catalog as JSON (see below). | | `--config FILE` | Uses FILE instead of the configuration file found in the repository. | | `--no-config` | Uses no configuration file: no names, owners or exclusions. | | `--git PROGRAM` | The git program. Defaults to `git`. | With `--json`, the output follows [apigenie-catalog-report.json](https://apigenie.pl/schemas/apigenie-catalog-report.json): ```json { "formatVersion": 1, "root": "/home/me/specs", "config": "/home/me/specs/apigenie.yaml", "git": true, "specs": [ { "name": "orders-api", "title": "Orders API", "format": "openapi", "specVersion": "3.1.0", "version": "1.2.0", "path": "apis/orders/openapi.yaml", "owner": "team-commerce", "listed": true, "commit": { "id": "4d0c2e1f…", "shortId": "4d0c2e1", "author": "Jane Doe", "date": "2026-10-14T09:12:00+02:00", "subject": "Add order cancellation" } } ], "warnings": [] } ``` `listed` tells whether the catalog section lists the file. ### apigenie-catalog init Writes the `catalog` section of the configuration file from what `apigenie-catalog list` finds: one entry per specification with its name, its path and `owner: null`, for you to fill in. ```yaml catalog: specs: - name: order-events path: apis/events.json owner: null - name: orders-api path: apis/orders/openapi.yaml owner: null ``` - Without a configuration file, it creates `apigenie.yaml` at the root of the repository (or the file given with `--config`). - In an existing file, it appends the section, or replaces the lines of the section, and leaves the rest of the file as it is. A section written in flow style (`catalog: {...}`) and JSON files are rewritten whole; comments are kept. - Run it again after adding specifications: new ones are added, the entries you edited stay as they are, and entries whose files are gone are reported as "not found". `--prune` removes them. - Names that two specifications would share get a number: `orders-api-2`. | Option | Description | | ------ | ----------- | | `DIR` | The directory to look for specifications in. Defaults to the current directory. | | `--prune` | Removes the entries whose files are gone. | | `--dry-run` | Prints the configuration file instead of writing it. | | `--config FILE` | Writes FILE instead of the configuration file found in the repository; it is created if missing. | | `--git PROGRAM` | The git program. Defaults to `git`. | ```text $ apigenie-catalog init Updated /home/me/specs/apigenie.yaml: 2 added, 4 kept, 1 not found added apis/billing.yaml added apis/orders-v2.yaml not found legacy/swagger.yaml Remove the entries of the files that are gone with --prune. ``` The file is checked against the [configuration schema](/docs/reference/cli/config) before it is written, and `apigenie config check` accepts it. ## Exit status | Status | Meaning | | ------ | ------- | | 0 | Success, also when files can't be read or names are shared (reported on the standard error). | | 2 | The directory doesn't exist or is outside the root of the catalog, the configuration file is invalid, git fails, an unknown `--license` library, or a usage error. | These are the statuses of every command ([exit status](/docs/reference/cli/overview#exit-status)). ## See also - [Configuration](/docs/reference/cli/configuration#catalog): the `catalog` section. - [changelog](/docs/reference/cli/changelog): the history of a specification from git. --- # The editor's hosts Source: https://apigenie.pl/docs/reference/editor/hosts The API Genie editor edits OpenAPI 3.0, OpenAPI 3.1 and AsyncAPI 3.0 files in forms, on three hosts, with the same page in each: - **Web**: the editor's page in a browser (Chromium, Edge or Firefox), from a folder opened through the File System Access API or dropped on the page, at https://apigenie.pl/editor/, which every website deploy rebuilds from `main` (ADR-174). - **Desktop**: the same page in a window of its own, with installers for macOS, Windows and Linux on https://github.com/mykulyak/api-genie/releases (the `desktop-X.Y.Z` releases, from 1.0.0 on). Around the page, the desktop app adds what needs the machine: the system `git`, and Node.js sidecars for the broker client and the mock server. - **VS Code**: the same page in the API Genie extension's custom editor, *Open with API Genie Editor* (the `ide-X.Y.Z` releases, on the Marketplace from 1.0.0 on). VS Code owns the text, undo, saving, the colours and the code and diff editors; the extension runs the sidecars with VS Code's own runtime. *About* in the editor's toolbar says which host you are in, the editor's version and the host's own. ## What each host offers In the tables, **same** means the feature works as in the web editor; any other cell says what that host does instead, or adds a caveat after *same;*. <!-- host-matrix: generated by src/app_editor/src/app/host-features.test.ts from host-features.ts; UPDATE_GOLDEN=1 npm test rewrites it --> ### Folders | Feature | Web | Desktop | VS Code | | --- | --- | --- | --- | | Open folder… (the host's folder picker) | same; needs the File System Access API (Chromium, Edge); elsewhere, drop the folder on the page | same | VS Code opens the folder: the file's workspace folder, with its other spec files | | Drop a folder or files on the page | same | No drop: open the folder with the dialog or on the command line | VS Code opens the folder | | Start on a folder | A page has no command line: open or drop a folder | same; the folder given on the command line, `apigenie-editor <folder>` | The custom editor opens on the file's folder | | Save a folder the host can't write to (dropped files) as downloads | same | same | same | ### Toolbar | Feature | Web | Desktop | VS Code | | --- | --- | --- | --- | | Undo and Redo | same | same | VS Code's undo, per document, which also undoes what was typed in its text editor | | Save, with the count of unsaved files | same | same | VS Code saves its documents; the unsaved marker is VS Code's | | Export bundled (the spec and the files it refers to, in one file) | same; needs the openapi_bundle WebAssembly module, which a build with APIGENIE_WASM_DIR ships | same; needs the openapi_bundle WebAssembly module, which the installers don't ship yet | same; needs the openapi_bundle WebAssembly module, which the extension doesn't ship yet | | Mock server (Prism) serving the spec as it is edited | Runs a Node.js sidecar, which a page can't start | same | same | | Theme: the system's, light or dark | same | same | The VS Code theme's colours; no setting of the editor's own | | About: the editor's version, the host and its version | same | same | same | ### Panels | Feature | Web | Desktop | VS Code | | --- | --- | --- | --- | | API tree: search, filters, problem badges, rename and delete | same | same | same | | File list with unsaved and problem markers | same | same | same | | Forms for every node, with the Raw section and quick fixes | same | same | same | | Code tab: the file's text in Monaco, following the selection | same | same | VS Code's text editor: *Show in the text editor* opens it at the node | | Preview: the docs of the selected node, OpenAPI and AsyncAPI | same | same | same | | Flow: the AsyncAPI message flow diagram, editable | same | same | same | | Changes: the API changes since the last save, with their severity | same | same | same | | Changes: the text diff since the last save | same | same | *Show the text changes in the diff editor* opens VS Code's, on the live document | | Git tab: status, diff, commit, branches | No Git tab: it runs the system git, which a page can't | same | No Git tab: VS Code's Source Control view does that | | Problems: validation, the repository's linters and Spectral, under the fields and in the tree | same | same | same | ### Editing | Feature | Web | Desktop | VS Code | | --- | --- | --- | --- | | Show as code: the node's text in Monaco, inside the form | same | same | *Show in the text editor* opens VS Code's at the node | | Import: Swagger 2.0, AsyncAPI 2.x and Postman collections, with the conversion shown as a diff | same | same | *Show the conversion in the diff editor* opens VS Code's | | Ctrl+S / Cmd+S saves | same | same | Asks VS Code to save | | Ctrl+Z / Cmd+Z and Ctrl+Shift+Z undo and redo | same | same | VS Code's undo | ### Try it out | Feature | Web | Desktop | VS Code | | --- | --- | --- | --- | | Try it out: HTTP requests from the operation's preview | same | same | same | | CORS proxy setting for servers that refuse the page's requests | same | same | Not needed: the extension host sends the requests, and CORS doesn't apply | | Authorize with OAuth 2.0 authorization-code and implicit flows (a popup) | same | same | Only the flows without a browser step (client credentials, password); paste a token otherwise | | Send Try it out to the mock server | Runs a Node.js sidecar, which a page can't start | same | same | | Try it: publish and subscribe on Kafka, AMQP, MQTT and WebSocket servers (AsyncAPI operations) | Runs a Node.js sidecar, which a page can't start | same | same | <!-- /host-matrix --> ## Host limits Each difference above comes from something the host can't do, or does itself. ### The web editor has no Git tab, broker Try it or mock server They run the system `git` and Node.js sidecars (the broker client, Prism), which a web page can't start. The desktop app and the VS Code extension have them. They may come to the web editor later through integration packages for the frameworks the generators target; none is planned yet. ### VS Code has its own text editor, diff editor and Source Control The custom editor shows no Code tab and no Monaco diffs: *Show in the text editor* opens VS Code's text editor at the node, and *Show the text changes in the diff editor* and *Show the conversion in the diff editor* open VS Code's diff editor. The Git tab's job is VS Code's Source Control view's. Undo, the unsaved marker and Save are VS Code's, since the document is VS Code's, and the editor takes the colours of the VS Code theme. ### VS Code opens folders itself The custom editor works on the file's workspace folder, with its other spec files for `$ref`s; there is no folder picker and no drop. Try it out sends its requests from the extension host, so no CORS proxy is needed, but the OAuth 2.0 flows with a browser step (authorization code, implicit) can't return to a webview: use client credentials or a token. --- # audience-defined Source: https://apigenie.pl/docs/linter/openapi/rules/audience-defined This rule checks whether an OpenAPI schema defines its target audience. ## Rule Details Defining target audience for an API allows implementing different levels of quality of design, discoverability, completeness of the documentation, as well as extra steps in the API lifecycle. The `audience-defined` rule is designed to ensure that the `#/info/x-audience` field is defined in an OpenAPI schema, and has one of allowed values. The allowed values for this attribute are: - `internal-team` - internal API used by the team. This is most close in meaning to private API. - `internal-company` - internal API accessible to the whole company. - `partner` - API accessible to external clients. - `external` - public API, accessible to everyone. This is a synonym to public API. ## Configuration None. ## Examples This rule should be used whenever an OpenAPI schema is being defined. For instance: ```yaml openapi: 3.0.3 info: title: Some public API version: 1.2.3 x-audience: external paths: ... ``` ## When Not to Use It N/A. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # contact-defined Source: https://apigenie.pl/docs/linter/openapi/rules/contact-defined This rule checks whether the contact object is present in an OpenAPI schema. ## Rule Details The `contact-defined` rule is designed to ensure that the contact object is defined in an OpenAPI schema. This object is important as it provides contact information for the exposed API. The rule requires the contact object to have the `name` attribute, and one or both the `url` and `email` attributes. ## Configuration None. ## Examples **Bad**. In case of the following API, clients do not know whom to contact in case of problems. ```yaml openapi: 3.1.0 info: title: A public API version: 0.2.0 paths: ... ``` **Good**. The following private API defines a contact by specifying a team name and a Slack channel. ```yaml openapi: 3.1.0 info: title: A private API version: 12.4.0 contact: name: team-one url: https://your-company.slack.com/archives/C0278047U8G paths: ... ``` ## When Not to Use It You may skip this rule if the point of contact is well known and does not change with time. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # id-defined Source: https://apigenie.pl/docs/linter/openapi/rules/id-defined This rule checks whether the `#/info/x-id` attribute is present in an OpenAPI schema and if it's a valid UUID. ## Rule Details Uniquely identifying an API in tracking and managing different versions of the API. OpenAPI, however, does not provide an "identifier" field for an API. The `id-defined` rule fills this gap. It ensures that ID attribute is defined in an OpenAPI info object, and that its value matches the expected format. ## Configuration The rule configuration must be an object with two attributes, `attribute` and `pattern`. `attribute` specified the name of the ID attribute, and `pattern` specifies its expected format. Neither of them can be empty or missing. `attribute` must start with `x-`, as the ID attribute is an extension. `pattern` value can be `uuid` which is treated as UUID regexp, or a string representing ECMAScript regexp. **Configuration Example**. The following configuration makes the rule look for the `#/info/x-ident` attribute, whose value must be a sequence of one or more lower case letters. ```yaml lint: rules: id-defined: - error - attribute: x-ident pattern: "[a-z]+" ``` ## Examples This rule should be used whenever an OpenAPI schema is being defined. For instance: **Good**. Now we can rename, version or modify the schema file, or introduce any other change, and still be able to track the API. ```yaml openapi: 3.0.3 info: title: Payments version: 1.2.8 x-id: 123e4567-e89b-12d3-a456-426614174000 paths: ... ``` **Bad**. In the following example, it is difficult to track the evolution of the API. Renaming a schema file, or creating a different API with the same title will lead to ambiguities. ```yaml openapi: 3.0.3 info: title: Payments version: 1.2.4 paths: ... ``` **Bad**. In the following example, value of the `x-id` field is not an UUID. It makes it difficult to ensure the uniqueness of this identifier. ```yaml openapi: 3.0.3 info: title: Payments version: 1.2.8 x-id: 237849128738 paths: ... ``` ## Automatic fixes If `pattern` is set to `uuid`, this rule becomes auto-fixing. If the linter is invoked with the `--fix` option, it automatically generates an UUID v4 in correct place of the schema. ## When Not to Use It We recommend to always set this attribute. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # info-defined Source: https://apigenie.pl/docs/linter/openapi/rules/info-defined This rule checks whether the info object is present in an OpenAPI schema and if it contains the `summary`, `description`, and `version` fields. ## Rule Details The rationale behind this rule is to enforce the inclusion of basic information in the API specification. This helps users of the API to understand its purpose, functionality, and version. The `info-defined` rule is designed to ensure that the `#/info` object is defined in an OpenAPI schema and that it contains the `summary`, `description`, and `version` fields. These fields are crucial as they provide basic information about the API. ## Configuration None. ## Examples This rule should be used whenever an OpenAPI schema is being defined. For instance: ## When Not to Use It We recommend to always include these fields in OpenAPI schemas. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # json-api/content-type Source: https://apigenie.pl/docs/linter/openapi/rules/json-api-content-type This rule ensures that every request and response in an OpenAPI schema provides the `application/vnd.api+json` media type. ## Rule Details The `json-api/content-type` rule is designed to ensure that every request and response in an OpenAPI schema provides the `application/vnd.api+json` media type. This rule is specifically useful for APIs that adhere to the JSON:API specification. ## Configuration None. ## Examples **Good**. By marking the response with the correct media type, API authors enable enhancements specific to JSON:API. ```yaml openapi: 3.1.0 info: ... paths: /users: get: responses: "200": description: A list of users. content: application/vnd.api+json: schema: $ref: "#/components/schemas/UserListResponse" ``` **Bad**. Although the response is a valid JSON:API document, it is not obvious from this definition. ```yaml openapi: 3.1.0 info: ... paths: /users: get: responses: "200": description: A list of users. content: application/json: schema: $ref: "#/components/schemas/UserListResponse" ``` ## When Not to Use It If the API is not meant to be JSON:API compatible. ## Automatic fixes When invoked with `--fix`, a sole `application/json` content entry is renamed to `application/vnd.api+json`, preserving its full media-type value. Content maps with multiple entries remain manual. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # json-api/resource-type-unique Source: https://apigenie.pl/docs/linter/openapi/rules/json-api-resource-type-unique This rule ensures that all resource schemas in a JSON:API have unique resource IDs. ## Rule Details Within the JSON:API specification a uniqueness of a resource depends on a uniqueness of its type. Therefore, we need a way to check the latter. The `json-api/resource-type-unique` rule is designed to ensure that all resource schemas in a JSON:API have unique resource IDs. This rule is specifically useful for APIs that adhere to the JSON:API specification. To determine the resource type, the rule checks both the `const` and `enum` properties of corresponding JSON schemas. ## Configuration None. ## Examples **Good**. The following schema defines two resources with unique types. ```yaml openapi: 3.1.0 info: ... paths: ... components: schemas: User: type: object properties: type: type: string const: user ... Employee: type: object properties: type: type: string enum: - employee ... ``` **Bad**. The following schema does not define allowed value for a resource. ```yaml openapi: 3.1.0 info: ... paths: ... components: schemas: User: type: object properties: type: # this property does not specify allowed values type: string ... ``` **Bad**. The following schema defines two distinct resources with identical resource types. ```yaml openapi: 3.1.0 info: ... paths: ... components: schemas: User: type: object properties: type: type: string const: person ... Employee: type: object properties: type: type: string enum: - person ... ``` ## When Not to Use It When you do not use the JSON:API specification. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # json-api/schema-validation Source: https://apigenie.pl/docs/linter/openapi/rules/json-api-schema-validation This rule ensures that request and response documents in a JSON:API conform to the JSON:API specification. ## Rule Details The `json-api/schema-validation` rule is designed to ensure that request and response documents in a JSON:API conform to the JSON:API specification. This includes checking the document's structure, as well as the structure of any contained resources. This rule is specifically useful for APIs that adhere to the JSON:API specification. ## Configuration None. ## Examples **Good**. The following response defines a minimalistic but valid document schema. ```yaml --- paths: /users/current: get: responses: 2XX: content: application/vnd.api+json: schema: type: object properties: jsonapi: type: object data: type: object ``` **Bad**. The following schema defines string response, which is not a valid type for JSON:API documents. ```yaml --- paths: /users/current: get: responses: 2XX: content: application/vnd.api+json: schema: type: string ``` ## When Not to Use It You should not use this rule if your API is not compatible with JSON:API. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # json-schema-validation Source: https://apigenie.pl/docs/linter/openapi/rules/json-schema-validation Validates OpenAPI 3.0 schema against JSON meta-schema. ## Rule Details This rule ensures that an OpenAPI 3.0 schema validates against the JSON meta-schema for OpenAPI 3.0. Any issues found by the schema validator will be reported by the linter. ## Configuration None. ## Examples None. ## When Not to Use It When your schemas adhere to version 3.1 of OpenAPI specification. ## Compatibility This rule is compatible with OpenAPI 3.0 version only. --- # license-defined Source: https://apigenie.pl/docs/linter/openapi/rules/license-defined This rule checks whether the licensing information is present in an OpenAPI schema. ## Rule Details The `license-defined` rule is designed to ensure that the licensing information is defined in an OpenAPI schema. This information is crucial as it specifies the terms under which the API can be used. The rationale behind this rule is to enforce the inclusion of licensing information in the API specification. This helps users of the API to understand the terms of use and any restrictions that may apply. ## Configuration None. ## Examples This rule should be used whenever an OpenAPI schema is being defined. **Good**. The following schema provides a link to its license, thus making it easier to understand how it can be used. ```yaml info: license: name: Apache 2.0 url: https://your-company.com/api/LICENSE ``` **Bad**. The following API does not specify the licensing terms, thus forcing potential users to spend time on searching for it elsewhere. ## When Not to Use It You may skip licensing information for team-level APIs. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # no-content-response-defined Source: https://apigenie.pl/docs/linter/openapi/rules/no-content-response-defined This rule ensures that responses for HTTP status 204 do not define their response bodies. ## Rule Details The rationale behind this rule is to enforce the correct usage of HTTP status codes as per the HTTP specification. This helps in ensuring that the API behaves as expected and can interact correctly with clients. The `no-content-response-defined` rule is designed to ensure that responses for HTTP status 204 (No Content) do not define their response bodies. According to the HTTP specification, a 204 response must not include a message body. ## Configuration None. ## Examples This rule should be used whenever an OpenAPI schema is being defined. **Good**. The following schema does not define any response body, thus making it obvious. ```yaml paths: /users/{id}: delete: responses: "204": description: User deleted successfully. ``` **Bad**. The following schema defines a non-empty response body, thus defying the purpose of the 204 status code. ```yaml paths: /users/{id}: delete: responses: "204": description: User deleted successfully. content: application/json: schema: type: object ``` ## When Not to Use It We recommend to always use this rule. ## Automatic fixes When invoked with `--fix`, this rule removes `content` from enabled `204` responses. ## Compatibility This rule is compatible with all OpenAPI 3.x version. --- # openapi-json-schema-validation Source: https://apigenie.pl/docs/linter/openapi/rules/openapi-json-schema-validation This rule validates an OpenAPI specification against the OpenAPI 3.x JSON schema. ## Rule Details The rationale behind this rule is to enforce the compliance of the API specification with the OpenAPI 3.x standard. The `openapi-json-schema-validation` rule is designed to validate an OpenAPI specification against the OpenAPI 3.x JSON schema. This ensures that the API specification is compliant with the OpenAPI 3.x standard. ## Configuration None. ## Examples This rule should be used whenever an OpenAPI schema is being defined. **Good**. The following is a minimalistic but valid OpenAPI spec. ```yaml openapi: 3.0.0 info: title: Sample API version: 1.0.0 paths: {} ``` **Bad**. The following schema probably contains a typo. `path` should really be named `paths`. ```yaml openapi: 3.0.0 info: title: Sample API version: 1.0.0 path: {} ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # operation-description-defined Source: https://apigenie.pl/docs/linter/openapi/rules/operation-description-defined This rule checks each operation's description in an OpenAPI schema and warns if the description is empty. ## Rule Details Meaningful descriptions make it easier to use an API correctly. The `operation-description-defined` rule is designed to ensure that each operation in an OpenAPI schema has a description. This description is crucial as it provides information about what the operation does. The rule checks all operations defined in an OpenAPI schema and raises an issue if operation's description: - is missing or empty - consist of whitespace characters only - consist of "empty" words like "TBD", "TODO" and so on - are shorter than the value specified in the configuration ## Configuration The configuration block is a single positive integer, which specifies the minimal number of characters in a "meaningful" description. The default value is 10. **Configuration Example** ```yaml lint: rules: operation-description-defined: - warning - 20 ``` ## Examples **Good**. The following operation has a meaningful and useful description. ```yaml paths: /users: get: description: Retrieves a list of users responses: "200": description: | Returns a paged list of user resources, optionally filtered and sorted. ``` **Bad**. Operation does not have a description. It is not immediatelu clear, for example, whether this collection can be filtered. ```yaml paths: /users: get: description: Retrieves a list of users responses: "200": {} ``` **Bad**. The following operation have a meaningless description. ```yaml paths: /users: get: description: TBD responses: "200": {} ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # operation-document-not-primitive Source: https://apigenie.pl/docs/linter/openapi/rules/operation-document-not-primitive Check operation responses and warn if they are defined as primitive values. ## Rule Details Using primitive values for responses has several drawbacks: - it does not provide information about response structure - it does not provide place for extension - it makes error handling more difficult The `operation-document-not-primitive` rule is designed to ensure that each operation's response in an OpenAPI schema is not a primitive value. This is because response bodies should typically be objects or arrays that allow for more complex and detailed responses. ## Configuration None. ## Examples **Good**. It is clear that the response body contains. ```yaml paths: /users: get: responses: "200": description: A list of users. content: application/json: schema: type: array items: $ref: "#/components/schemas/User" ``` **Bad**. It is not cleat what is inside the response. Plain textual description, error message or both ? ```yaml paths: /users: get: responses: "200": description: A list of users. content: application/json: schema: type: string ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # operation-id-defined Source: https://apigenie.pl/docs/linter/openapi/rules/operation-id-defined This rule checks each operation defined in an OpenAPI schema and warns if operation IDs are missing or empty. ## Rule Details Assigning operation a unique identifier helps with managing them. Identifiers can also be used by code generators to produce implementation of API calls. The `operation-id-defined` rule is designed to ensure that each operation in an OpenAPI schema has non-empty operation ID. ## Configuration The optional `nonPathNaming` value controls generated webhook and component path-item names: `scope-prefix` (default), `path-like`, or `scope-suffix`. ## Examples **Good**. The following operation has a descriptive name that makes it easy to use the operation properly. ```yaml paths: /users: get: operationId: readUserList responses: "200": description: Returns a list of users. ``` **Bad**. The following operation does not have a name. It is not entirely clear what it does. Using e.g. `cancelReservation` would help with it. ```yaml paths: /reservations: delete: responses: ... ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule generates deterministic IDs from the HTTP method and location, rendering path placeholders as `ByName`. Existing IDs are reserved and collisions receive numeric suffixes. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # operation-id-unique Source: https://apigenie.pl/docs/linter/openapi/rules/operation-id-unique This rule checks whether operations defined in an OpenAPI schema have unique IDs. ## Rule Details Duplicate operation ID make it difficult to reason about their purpose, as well as about. The `operation-id-unique` rule checks IDs of operations defined in an OpenAPI schema and warn if duplicates found. Empty operation IDs do not count as duplicates. ## Configuration The optional `nonPathNaming` value controls generated webhook and component path-item names: `scope-prefix` (default), `path-like`, or `scope-suffix`. ## Examples **Good**. It is immediately visible what each operation does. ```yaml paths: /users: get: operationId: getUsers responses: "200": description: A list of users. /users/{id}: get: operationId: getUserById responses: "200": description: A single user. ``` **Bad**. Two operations have identical IDs, which makes it difficult to decide which one to use in each case. ```yaml paths: /downloads: patch: # changes the status to 'cancelled' operationId: cancelDownload ... delete: operationId: cancelDownload ... ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule keeps the first occurrence and regenerates later duplicates using the same deterministic generator and collision policy as `operation-id-defined`. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # operation-rate-limiting-defined Source: https://apigenie.pl/docs/linter/openapi/rules/operation-rate-limiting-defined Warn if operation does not define rate limiting headers. ## Rule Details In case your API imposes some rate limits (for example, because of overload or lack of payments), it is worthwhile to explcitly indicate it in the OpenAPI schema. This will allow your users to manage requests accordingly. The `operation-rate-limiting-defined` rule checks operations defined in an OpenAPI schema and warns if their responses do not define any rate limiting headers. Expected header names: - `Retry-After` - `RateLimit-Limit` or `X-RateLimit-Limit` ## Configuration None. ## Examples ```yaml paths: /users: get: responses: '200': description: A list of users. headers: RateLimit-Limit: description: The maximum number of requests you're permitted to make per hour. schema: type: integer ``` ## When Not to Use It If your API does not impose any rate limits. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # operation-security-defined Source: https://apigenie.pl/docs/linter/openapi/rules/operation-security-defined Warn if OpenAPI operation does not have any sequrity requirements. ## Rule Details Operations may restrict access to themselves. Specifying this in OpenAPI contracts helps users of the API to understand the security measures and manage their requests accordingly. This rule checks every operation in an OpenAPI contract and warns when an operation does not have security requirements defined. This includes cases: - there is no global security requirements defined, and an operation does not override them - an operation specifically overrides security requirements, resetting them to empty set ## Configuration None. ## Examples **Good**. Operation inherits non-empty security requirements defined globally. ```yaml paths: /users: get: responses: "200": description: A list of users. security: - api_key: [] components: securitySchemes: api_key: ... ``` **Good**. Operation defines its own security requirements. ```yaml paths: /users: get: security: - api_key: [] responses: "200": description: A list of users. components: securitySchemes: api_key: ... ``` **Bad**. Neither OpenAPI schema, nor operation define security requirements ```yaml paths: /users: get: responses: "200": description: A list of users. components: ... ``` **Bad**. An operation resets security requirements to an empty set ```yaml paths: /users: get: security: [] responses: "200": description: A list of users. components: securitySchemes: api_key: ... ``` ## When Not to Use It You may disable this rule when your API does not restrict access to itself in any way. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # operation-success-response Source: https://apigenie.pl/docs/linter/openapi/rules/operation-success-response This rule warns when an operation in an OpenAPI schema does not define any success response, i.e., those corresponding to 2xx or 3xx codes. ## Rule Details It is important to understand when a request to an API was successful. Standard way of representing a success are HTTP status codes. The `operation-success-response` rule is designed to ensure that each operation in an OpenAPI schema defines a success response. These responses are crucial as they provide information about the successful execution of the operation. ## Configuration None. ## Examples **Good**. Operation defines success response using specific status code. ```yaml paths: /users: get: responses: "200": description: A list of users. ``` **Good**. Operation defines success response using generic status code. ```yaml paths: /users: get: responses: 2XX: description: A list of users. ``` **Bad**. Operation does not define any success response, thus making it difficult to understand how to use it. ```yaml paths: /users: get: responses: 400: {} ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # operation-tags-defined Source: https://apigenie.pl/docs/linter/openapi/rules/operation-tags-defined This rule checks each operations' tags in an OpenAPI schema and warns if any of them are empty lists. ## Rule Details Tags help undertand the logical grouping of OpenAPI operations, which in turn helps in using the API correctly. The `operation-tags-defined` rule is designed to ensure that each operation in an OpenAPI schema has at least one tag. Tags are used for logical grouping of operations, which aids in understanding and using the API. A situation when OpenAPI schema itself does not define any tags is not treated as an issue. ## Configuration None. ## Examples **Good**. OpenAPI defines tags, which are used by an operation ```yaml paths: /users: get: tags: - Users responses: "200": description: A list of users. tags: - name: Users ``` **Good**. OpenAPI does not define any tags. ```yaml paths: /users: get: responses: "200": description: A list of users. ``` **Bad**. Operation does not define any tags. ```yaml paths: /users: get: responses: "200": description: A list of users. tags: - name: Users ``` ## When Not to Use It When you decide not to use tags in your API contracts. In this case, the list of tags in OpenAPI object should be empty. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # parameter-description-defined Source: https://apigenie.pl/docs/linter/openapi/rules/parameter-description-defined This rule checks every parameter defined in an OpenAPI schema and warns when a its description is empty or missing. ## Rule Details Meaningful descriptions make it easier to use an API correctly. The `parameter-description-defined` rule is designed to ensure that each parameter in an OpenAPI schema has a meaningful description. These descriptions are crucial as they provide information about the purpose and usage of the parameter. The rule checks all parameters defined in an OpenAPI schema and raises an issue if parameter's description: - is missing or empty - consist of whitespace characters only - consist of "empty" words like "TBD", "TODO" and so on - are shorter than the value specified in the configuration ## Configuration The configuration block is a single positive integer, which specifies the minimal number of characters in a "meaningful" description. The default value is 10. **Configuration Example** ```yaml lint: rules: parameter-description-defined: - warning - 20 ``` ## Examples **Good**. Parameter description helps to understand its purpose and intended way of usage. ```yaml paths: /users/{id}: get: parameters: - name: id in: path description: | The ID of the user to retrieve. Can also be `self`, which it interpreted as an ID of the currently logged in user. required: true schema: type: string ``` **Bad**. Without description it is not immediately obvious how this parameter is treated. ```yaml paths: /users/{id}: get: parameters: - name: id in: path required: true schema: type: string ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # parameter-naming Source: https://apigenie.pl/docs/linter/openapi/rules/parameter-naming This rule checks all parameters in an OpenAPI schema and warns if they do not adhere to the specified naming convention. ## Rule Details The `parameter-naming` rule is designed to ensure that each parameter in an OpenAPI schema adheres to a specific naming convention. The recognised naming conventions are: - `const` - THIS_IS_CONST_CASE - `camel` - thisIsCamelCase, also with acronyms (userID) - `camel-strict` - thisIsCamelCase, but no two capitals in a row (userId, not userID) - `kebab` - this-is-kebab-case - `pascal` - ThisIsPascalCase, also with acronyms (HTTPServer) - `pascal-strict` - ThisIsPascalCase, but no two capitals in a row (HttpServer, not HTTPServer) - `header` - This-Is-Header-Case - `snake` - this_is_snake_case It is possible to configure the naming convention separately for each parameter location. The default values are: - query parameters - `kebab` - header parameter - `header` - other parameters - `snake` ## Configuration The configuration block can be a string or an object. If it is a string, its value is intepreted as the default naming convention, and is used for all parameter types. If it is an object, it must contain the `default` property, and may contain any of the `path`, `query`, `header` and `cookie` properties. Value of each property is the naming convention name (see the "Rule Details" section for a list of recognized names). Value of the `default` property is used as a default naming convention, and values from other properties override naming convention for specific parameter types. **Configuration Example 1**. The following two examples are equivalent. They specify that the expected naming convention for all parameters is `PascalCase`. ```yaml lint: rules: parameter-naming: - warning - pascal ``` ```yaml lint: rules: parameter-naming: - warning - default: pascal ``` **Configuration Example 2**. This configuration specifies sets the default naming convention to be `snake`, and overrides it for query parameters with `kebab`, as well as for header parameters with `header`. ```yaml lint: rules: parameter-naming: - warning - default: snake query: kebab header: header ``` ## Examples None. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # parameters-unique Source: https://apigenie.pl/docs/linter/openapi/rules/parameters-unique Check parameters of operations and path items and warn if they have duplicate parameters. ## Rule Details If an operation or a path item defines multiple parameters with the same location and name, this signals an error. This `parameters-unique` rule checks parameters of every path item and every operation in OpenAPI schema and warns if any of them has parameters with the same name and location. ## Configuration None. ## Examples **Good**. Parameters are unique. ```yaml paths: /users/{id}: get: parameters: - name: id in: path description: The ID of the user to retrieve. required: true schema: type: string - name: with-relations in: query description: If true, also returns related resources. required: false schema: type: boolean ``` **Good**. While parameter names are identical, their locations are not. ```yaml paths: /users/{id}: get: parameters: - name: id in: path description: The ID of the user to retrieve. required: true schema: type: string - name: id in: query description: Controversial, but still OK. required: false schema: type: boolean ``` **Bad**. Duplicate parameter definitions lead to confusion, which most likely is an error. ```yaml paths: /users/{id}: get: parameters: - name: id in: path description: The ID of the user to retrieve. required: true schema: type: string - name: id in: path description: The ID of the user to retrieve. required: true schema: type: boolean ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule removes a later duplicate only when its complete source definition is structurally identical to the first definition. Conflicting duplicate parameters remain manual. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # patch-request-body-partial Source: https://apigenie.pl/docs/linter/openapi/rules/patch-request-body-partial This rule checks that `PATCH` request bodies do not require properties. ## Rule Details `PATCH` applies a partial modification to a resource (RFC 5789). With a merge patch (RFC 7396) the client sends only the properties to change, so none of them should be required. A `PATCH` body with required properties forces clients to resend values they do not want to change, which is what `PUT` is for. The rule checks object schemas of `PATCH` request bodies, including the properties required by their `allOf` members. `application/json-patch+json` bodies are skipped: they are arrays of operations, whose properties are rightly required. ## Configuration This rule accepts an object with the following options: - `allowedRequired`: names of properties which may be required, for example a version used for optimistic locking. Defaults to none. ```yaml commands: lint: rules: patch-request-body-partial: - warning - allowedRequired: [version] ``` ## Examples **Good**. ```yaml paths: /users/{id}: patch: requestBody: content: application/merge-patch+json: schema: type: object properties: name: type: string email: type: string ``` **Bad**. Clients must send `name` even to change only `email`. ```yaml paths: /users/{id}: patch: requestBody: content: application/json: schema: type: object required: [name] properties: name: type: string email: type: string ``` ## When Not to Use It Many APIs use `PATCH` for an update whose only field is required, such as the body of a comment. Disable the rule, or list those properties in `allowedRequired`, when `PATCH` does not mean a partial update in your API. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # path-parameters-defined Source: https://apigenie.pl/docs/linter/openapi/rules/path-parameters-defined This rule ensures that for all parameter placeholders defined in path item URLs, there are path parameters defined in corresponding path items or their operations. ## Rule Details The `path-parameters-defined` rule is designed to ensure that for all parameter placeholders defined in path item URLs, there are corresponding path parameters defined in the path items or their operations. This helps to maintain consistency and clarity in the API's structure. ## Configuration The optional `defaultType` selects the schema type for generated path parameters. It defaults to `string`; set it to `null` to disable automatic insertion. ## Examples **Good**. ```yaml paths: /users/{userId}: parameters: - name: userId in: path required: true schema: type: string get: summary: Get a user by ID ``` **Bad**. Definition of the `userId` parameter is missing. ```yaml paths: /users/{userId}: get: summary: Get a user by ID ``` ## When Not to Use It If your API does not use path parameters or if you have other means of ensuring that all parameter placeholders have corresponding path parameters, you may not need to use this rule. ## Automatic fixes When invoked with `--fix`, missing placeholders are added as path-level parameters with `in: path`, `required: true`, and the configured schema type. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # path-parameters-on-items Source: https://apigenie.pl/docs/linter/openapi/rules/path-parameters-on-items This rule ensures that all path parameters are defined on path item levels, and not on the operation level. ## Rule Details Defining path parameters as children of path items, and not operations helps to maintain consistency and clarity in the API's structure, as well as to avoid duplicate parameter definitions. The `path-parameters-on-items` rule is designed to ensure that all path parameters are defined on path item levels, rather than on the operation level. ## Configuration None. ## Examples **Good**. ```yaml paths: /users/{userId}: parameters: - name: userId in: path required: true schema: type: string get: summary: Get a user by ID ``` **Bad**. Definition of the `userId` parameter has to be duplicated in all operations. ```yaml paths: /users/{userId}: get: summary: Get a user by ID parameters: - name: userId in: path required: true schema: type: string delete: summary: Delete a user by ID parameters: - name: userId in: path required: true schema: type: string ``` ## When Not to Use It If your API does not use path parameters or if you have other means of ensuring that parameters are defined at the correct level, you may not need to use this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # paths-max-count Source: https://apigenie.pl/docs/linter/openapi/rules/paths-max-count This rule ensures that an API does not define too many path items. ## Rule Details An API with more than a certain number of path items (e.g., 8) is considered too broad, which can make it difficult to maintain and understand. The `paths-max-count` rule is designed to ensure that an API does not define too many path items. The rule works by counting the number of path items defined in the API. If the count exceeds the specified limit, the rule raises a warning. ## Configuration The configuration block is a single positive integer, which specifies the maximal number of path items in an API. The default value is 9. **Configuration Example** ```yaml lint: rules: paths-max-count: - warning - 20 ``` ## Examples **Good**. ```yaml paths: /users: get: summary: Get all users /users/{userId}: get: summary: Get a user by ID /users/{userId}/posts: get: summary: Get a user's posts /posts: get: summary: Get all posts /posts/{postId}: get: summary: Get a post by ID /posts/{postId}/comments: get: summary: Get a post's comments /comments: get: summary: Get all comments /comments/{commentId}: get: summary: Get a comment by ID ``` **Bad**. ```yaml paths: /users: get: summary: Get all users /users/{userId}: get: summary: Get a user by ID /users/{userId}/posts: get: summary: Get a user's posts /posts: get: summary: Get all posts /posts/{postId}: get: summary: Get a post by ID /posts/{postId}/comments: get: summary: Get a post's comments /comments: get: summary: Get all comments /comments/{commentId}: get: summary: Get a comment by ID /tags: get: summary: Get all tags /tags/{tagId}: get: summary: Get a tag by ID ``` ## When Not to Use It If your API does not have a limit on the number of path items it can define, you may not need to use this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # paths-max-depth Source: https://apigenie.pl/docs/linter/openapi/rules/paths-max-depth This rule ensures that path item URLs do not contain too many segments. ## Rule Details Path URLs with more 4 or 5 segments most often mean that the API is too complex. Limiting the number of segments in a URL enhances the readability and maintainability of the API's structure. The `paths-max-depth` rule is designed to ensure that all path item URLs do not contain too many segments. A segment is part of a URL bounded by forward slashes, or the start or end of strings. ## Configuration The configuration block is a single positive integer, which specifies the maximal number of segments in an URL. The default value is 3. **Configuration Example** ```yaml lint: rules: paths-max-depth: - warning - 6 ``` ## Examples **Good**. ```yaml paths: /users/{userId}: get: summary: Get a user by ID ``` **Bad**. ```yaml paths: /users/{userId}/posts/{postId}/comments/{commentId}: get: summary: Get a comment of a post of a user ``` ## When Not to Use It If your API does not have a limit on the number of segments in its path item URLs, you may not need to use this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # paths-sorted Source: https://apigenie.pl/docs/linter/openapi/rules/paths-sorted This rule checks URLs of path items defined in an OpenAPI schema, and warns if they are not placed in alphabetical order. ## Rule Details The `paths-sorted` rule is designed to ensure that the URLs of path items in an OpenAPI schema are placed in alphabetical order. This improves the readability and maintainability of the API specification. ## Configuration None. ## Examples **Good**. `/users` are placed after `/products`. ```yaml paths: /products: get: responses: "200": description: A list of products. /users: get: responses: "200": description: A list of users. ``` **Good**. `/users/self` is placed before `/users/{id}` as the more specific URL. This is similar to how route handlers are to be evaluated by most frameworks. ```yaml paths: /users/self: get: responses: "200": description: Information about current user. /users/{id}: get: responses: "200": description: Information about a user with given ID. ``` **Bad**. `/users/self` is placed after `/users/{id}`, differently from the way we write code in most frameworks. ```yaml paths: /users/{id}: get: responses: "200": description: Information about a user with given ID. /users/self: get: responses: "200": description: Information about current user. ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # paths-unique Source: https://apigenie.pl/docs/linter/openapi/rules/paths-unique This rule checks whether URLs of path items are unique. ## Rule Details Unique URLs prevent confusion and misinterpretation of the API's structure. The `paths-unique` rule is designed to ensure that all URLs of path items are unique. URLs that differ only in parameter names, like `/users/{id}` and `/users/{userId}`, are treated as identical. ## Configuration None. ## Examples **Good**. ```yaml paths: /users/{userId}: get: summary: Get a user by ID /users/{userId}/posts: get: summary: Get a user's posts ``` **Bad**. ```yaml paths: /users/{userId}: get: summary: Get a user by ID /users/{id}: get: summary: Get a user by ID ``` ## When Not to Use It If your API does not use path parameters or if you have other means of ensuring URL uniqueness, you may not need to use this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # paths-well-formed Source: https://apigenie.pl/docs/linter/openapi/rules/paths-well-formed This rule checks whether URLs of path items are well-formed. ## Rule Details Well-formed URLs enhance the readability and maintainability of the API's structure. The `paths-well-formed` rule is designed to ensure that all URLs of path items are well-formed. This includes: - starting with a forward slash - not ending with a forward slash - not having multiple consecutive slashes (e.g., `//`) - not having empty parameter placeholders (e.g., `/{}/` parts) ## Configuration None. ## Examples **Good**. ```yaml paths: /users/{userId}: get: summary: Get a user by ID ``` **Bad**. ```yaml paths: //users/{}/: get: summary: Get a user by ID ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # privacy/no-credentials-in-url Source: https://apigenie.pl/docs/linter/openapi/rules/privacy-no-credentials-in-url This rule reports passwords, tokens, API keys and other credentials passed in path or query parameters, and API keys that a security scheme sends in the query string. ## Rule Details URLs are not secret. Servers, load balancers and proxies log them, browsers keep them in their history and bookmarks, and they leak to other sites in the `Referer` header. A credential in a URL is a credential in all of these places (OWASP API Security Top 10 API2:2023 Broken Authentication, CWE-598). The rule looks at every `in: path` and `in: query` parameter, including the parameters of `components.parameters`, and reports: - a parameter whose name contains a credential term, in any naming convention: `password`, `passwd`, `secret`, `token` (`access_token`, `resetToken`, ...), `api key`, `private key`, `session id`, `auth`, `credentials`, `otp`, `pin`, `cvv`, and a few more; - a parameter whose schema has `format: password`; - an object parameter (`style: form`, `deepObject` or `content`) with such a property, since the serialization puts property names and values in the URL; - an `apiKey` security scheme with `in: query`. A term matches whole words of the name: `nextPageToken`, `continuation_token` and `author` are not reported, `pinCode` is. Names that describe a credential instead of holding it, where the term is followed by `id`, `name`, `type`, `status`, `count`, `expiry` and the like (`secret_name`, `signing_key_id`, `token_type_hint`), are not reported; `session_id` is. The OAuth 2.0 `authorization_code`, which the redirect carries in the query by design, is not reported either. ## Configuration An object with two optional properties: - `allow`: parameter and property names that are never reported, compared case-insensitively. - `terms`: more names to report, in any naming convention (`magicLink` also finds `magic_link`). ```yaml commands: lint: rules: privacy/no-credentials-in-url: - error - allow: [token] terms: [magicLink] ``` ## Examples **Good**. The token travels in the `Authorization` header. ```yaml components: securitySchemes: bearer: type: http scheme: bearer ``` **Bad**. Every request URL contains the key. ```yaml paths: /reports: get: parameters: - name: api_key in: query schema: type: string ``` ## When Not to Use It When a URL is meant to be a short-lived capability (a pre-signed download link, a password reset link sent by e-mail), disable the rule on that parameter with `x-apigenie-lint-disable`. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # privacy/no-personal-data-in-url Source: https://apigenie.pl/docs/linter/openapi/rules/privacy-no-personal-data-in-url This rule reports personal data, such as e-mail addresses, names, dates of birth, phone numbers and national identification numbers, passed in path or query parameters. ## Rule Details Personal data in a URL is copied to every access log, proxy, analytics tool and browser history on its way, mostly outside the retention and access controls that protect the data store. That works against data minimisation and integrity and confidentiality (GDPR Art. 5(1)(c) and (f)), data protection by design (Art. 25) and the security of processing (Art. 32), and makes erasure requests (Art. 17) hard to honour. The rule looks at every `in: path` and `in: query` parameter, including the parameters of `components.parameters`, and reports: - a parameter whose name contains a personal data term, in any naming convention: `email`, `phone`, `msisdn`, `first name`, `last name`, `surname`, `date of birth`, `dob`, `ssn`, `national id`, `tax id`, `passport`, `iban`, `card number`, `street address`, `ip address`, and more; - special categories of personal data (Art. 9) and criminal records (Art. 10): `health`, `diagnosis`, `religion`, `ethnicity`, `sexual orientation`, `biometric`, `genetic`, `criminal record`; - a parameter whose schema has `format: email` or `format: idn-email`; - an object parameter (`style: form`, `deepObject` or `content`) with such a property. A term matches whole words of the name, so `name` or `country` alone are not reported, while `customerEmail` and `LAST_NAME` are. A term followed by `type`, `status`, `verified`, `count` or `format` describes the data rather than holding it (`email_verified`) and is not reported. Credentials are reported by `privacy/no-credentials-in-url`. In a path, identify the resource by an opaque identifier (`/customers/{customerId}`, not `/customers/{email}`). For a search by personal data, send the criteria in the body of a `POST` (`POST /customers/search`) or, where the API supports it, of a `QUERY` request. ## Configuration An object with two optional properties: - `allow`: parameter and property names that are never reported, compared case-insensitively. - `terms`: more names to report, in any naming convention (`loyalty card` also finds `loyaltyCardNo`). ```yaml commands: lint: rules: privacy/no-personal-data-in-url: - warning - allow: [phone] terms: [loyalty card] ``` ## Examples **Good**. The search criteria are in the request body. ```yaml paths: /customers/search: post: requestBody: content: application/json: schema: type: object properties: email: type: string format: email ``` **Bad**. The e-mail address is part of the URL. ```yaml paths: /customers: get: parameters: - name: email in: query schema: type: string ``` ## When Not to Use It For data that the API publishes on purpose (a public author directory), allow the names or disable the rule on the parameter with `x-apigenie-lint-disable`. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # safe-markdown Source: https://apigenie.pl/docs/linter/openapi/rules/safe-markdown This rule ensures that markdown strings (mostly descriptions) do not contain unsafe operations like script tags or eval() constructs. ## Rule Details Unsafe constructs embedded in markdown strings can pose security risks. The `safe-markdown` rule is designed to ensure that markdown strings, primarily used in descriptions, do not contain unsafe operations such as `script` tags or `eval()` constructs. ## Configuration None. ## Examples **Good**. ```yaml components: schemas: User: type: object description: "This object represents a user in the system." ``` **Bad**. ```yaml components: schemas: User: type: object description: "This object represents a user in the system. <script>alert('Unsafe')</script>" ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule escapes the delimiters of recognized script elements and `eval()` calls. It preserves the surrounding Markdown and does not alter code spans or fenced code blocks. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-conditional-effective Source: https://apigenie.pl/docs/linter/openapi/rules/schema-conditional-effective This rule checks that `if`, `then` and `else` in schemas have an effect. ## Rule Details JSON Schema applies `then` to values matching `if` and `else` to the other values. Several combinations silently do nothing, usually because of a typo or a keyword placed at the wrong level: - `then` or `else` without `if` is ignored. - `if` without `then` and `else` has no effect. - `else` never applies when `if` accepts every value, e.g. `if: {}` or an `if` with only a `description`; `then` never applies when `if` is `false`. - `then` or `else` accepting every value (e.g. `{}`) has no effect. References are followed, so `if: {$ref: ...}` is checked against the referenced schema. ## Configuration None. ## Examples **Good**. Card payments require a card number, the others an IBAN. ```yaml components: schemas: Payment: type: object if: properties: kind: const: card then: required: [cardNumber] else: required: [iban] ``` **Bad**. `then` is ignored because the schema has no `if`. ```yaml components: schemas: Payment: type: object then: required: [cardNumber] ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule applies to OpenAPI 3.1 and later, where `if`, `then` and `else` are schema keywords. OpenAPI 3.0 documents are skipped. --- # schema-constraints Source: https://apigenie.pl/docs/linter/openapi/rules/schema-constraints This rule ensures that schema objects define constraints restricting the size of the underlying data. ## Rule Details Defining constraints enhances the robustness and reliability of the API's structure. The `schema-constraints` rule is designed to ensure that all schema objects define constraints restricting the size of the underlying data. This includes: - maximum length, pattern, or format for strings. - maximum size for arrays. - maximum number f properties for objects. - minimum and/or maximum value (either strict or exclusive on each side) for integers. ## Configuration The configuration should be an object whose keys are supported schema types, and values are boolean values. At least one schema type must be specified. Supported schema types are `integer`, `number`, `string`, `array` and `object`. By default, constraints for all supported schema types are verified. **Configuration Example**. The rule with the following configuration checks only array and object schemas, and does not check numbers and strings. ```yaml lint: rules: schema-constraints: - warning - array: true object: true ``` ## Examples **Good**. ```yaml components: schemas: User: type: object properties: username: type: string maxLength: 20 pattern: "^[a-z0-9_-]{3,16}$" roles: type: array items: type: string maxItems: 5 age: type: integer minimum: 18 maximum: 100 maxProperties: 3 ``` **Bad**. ```yaml components: schemas: User: type: object properties: username: type: string roles: type: array items: type: string age: type: integer ``` ## When Not to Use It If your API does not require specific constraints on its schema objects, you may not need to use this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-datetime-suffix Source: https://apigenie.pl/docs/linter/openapi/rules/schema-datetime-suffix This rule ensures that names of schema properties that represent date-times end with specified suffix. ## Rule Details This naming convention enhances the readability and understanding of the API's structure. The `schema-datetime-suffix` rule ensures that names of properties representing date-times end with specified suffix. The default suffix is `_at`, but you can change it in the configuration. ## Configuration The configuration value is a single string representing the expected suffix. It cannot be empty. **Configuration Example** ```yaml lint: rules: schema-datetime-suffix: - warning - _tm ``` ## Examples **Good**. ```yaml components: schemas: User: type: object properties: createdAt: type: string format: date-time ``` **Bad**. ```yaml components: schemas: User: type: object properties: creationDate: type: string format: date-time ``` ## When Not to Use It If your API does not follow a specific naming convention for date-time properties in its schema objects, you may not need to use this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-discriminator-defined Source: https://apigenie.pl/docs/linter/openapi/rules/schema-discriminator-defined This rule ensures that whenever a schema object defines a `oneOf` clause, it also defines a discriminator property. ## Rule Details The `schema-discriminator-defined` rule is designed to ensure that all schema objects that define a `oneOf` clause also define the `discriminator` property. The `discriminator` property is used to aid in the deserialization of `oneOf` schemas. ## Configuration None. ## Examples **Good**. ```yaml components: schemas: Pet: type: object discriminator: propertyName: petType oneOf: - $ref: "#/components/schemas/Cat" - $ref: "#/components/schemas/Dog" ``` **Bad**. It is not obvious when a pet is a cat, and when it is a dog. ```yaml components: schemas: Pet: type: object oneOf: - $ref: "#/components/schemas/Cat" - $ref: "#/components/schemas/Dog" ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule adds `discriminator.propertyName` only when every `oneOf` alternative exposes one shared property with distinct `const` values. All other discriminator choices remain manual. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-enum-naming Source: https://apigenie.pl/docs/linter/openapi/rules/schema-enum-naming This rule ensures that all string values defined in `enum` or `const` properties of schema objects adhere to a predefined naming convention. ## Rule Details Adhering to a consistent naming convention enhances the readability and maintainability of the API's structure. The `schema-enum-naming` rule is designed to ensure that all string values defined in `enum` or `const` properties of schema objects adhere to a predefined naming convention. Currently supported naming conventions are `const`, `camel`, `camel-strict`, `kebab`, `pascal`, `pascal-strict`, `header`, and `snake` (the strict forms reject two capitals in a row, e.g. `userID`). ## Configuration The configuration block is a naming conventing name. Its default value is `const`. ```yaml lint: rules: paths-max-count: - warning - kebab ``` ## Examples **Good**. ```yaml components: schemas: UserStatus: type: string enum: - ACTIVE - INACTIVE - BLOCKED ``` **Bad**. To use `UsesStatus` properly, a user must always consult the documentation. ```yaml components: schemas: UserStatus: type: string enum: - activeStatus - inactive_status - BLOCKED_STATUS - blocked_status ``` ## When Not to Use It If your API does not follow a specific naming convention for `enum` or `const` values in its schema objects, you may not need to use this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-enum-no-duplicates Source: https://apigenie.pl/docs/linter/openapi/rules/schema-enum-no-duplicates This rule ensures that schema objects do not have duplicate values in their `enum` or `const` properties. ## Rule Details Duplicate values can lead to confusion and misinterpretation of the API's structure. The `schema-enum-no-duplicates` rule is designed to ensure that all schema objects do not have duplicate values in their `enum` or `const` properties. The rule works by iterating over all the schema objects and checking if their `enum` or `const` properties have duplicate values. If a duplicate value is found, the rule raises a warning. ## Configuration None. ## Examples **Good**. ```yaml components: schemas: UserStatus: type: string enum: - active - inactive ``` **Bad**. ```yaml components: schemas: UserStatus: type: string enum: - active - active ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-enum-types Source: https://apigenie.pl/docs/linter/openapi/rules/schema-enum-types This rule checks schema objects and warns if allowed schema values (from `enum` and `const` properties) are consistent with schema types (i.e. their type property). ## Rule Details Inconsistencies between allowed values and type can lead to confusion and misinterpretation of the API's structure. The `schema-enum-types` rule is designed to ensure that the allowed values of a schema object, as defined by its enum and const properties, are consistent with its type. ## Configuration None. ## Examples **Good**. ```yaml components: schemas: UserStatus: type: string enum: - active - inactive ``` **Bad**. ```yaml components: schemas: UserStatus: type: string enum: - 1 - 2 ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, a non-empty enum whose values all have one JSON type sets the schema type to that type. Mixed-type enums and schemas with `format` remain manual. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-example-defined Source: https://apigenie.pl/docs/linter/openapi/rules/schema-example-defined This rule ensures that all non-trivial schemas have at least one example defined. ## Rule Details Providing examples for these schemas enhances the readability and understanding of the API's structure. The `schema-example-defined` rule is designed to ensure that all non-trivial schemas have at least one example defined. Non-trivial schemas include strings with a defined pattern property, objects, and arrays. ## Configuration None. ## Examples **Good**. ```yaml components: schemas: User: type: object properties: username: type: string pattern: "^[a-z0-9_-]{3,16}$" example: username: "john_doe" ``` **Bad**. It is not immediately visible what are valid values for the `username` field. ```yaml components: schemas: User: type: object properties: username: type: string pattern: "^[a-z0-9_-]{3,16}$" ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-format-defined Source: https://apigenie.pl/docs/linter/openapi/rules/schema-format-defined This rule ensures that every schema object that is a string or a number has its format defined. ## Rule Details Defining the format of a string or numeric schema objects enhances the readability and understanding of the API's structure. The `schema-format-defined` rule is designed to ensure that all schema objects whose type is `number` or `integer` have also a `format` property defined: an integer format of the [OpenAPI format registry](/docs/reference/cli/formats) (`int8` to `uint64`, `int32`, `int64`, `double-int`, `sf-integer`) or `unixtime` for `integer`, a number format (`float`, `double`, `decimal`, ...) for `number`, or a format of `openapi.custom_formats` of the matching type. ## Configuration None. ## Examples **Good**. Now it is obvious that `birthDate` is a ISO8601 date string. ```yaml components: schemas: User: type: object properties: birthDate: type: string format: date ``` **Bad**. It is not immediately obvious how `birthDate` is formated. Is it `YYYY-MM-DD` or `DD-MM-YY`, or perhaps `YY/MM/DD` ? ```yaml components: schemas: User: type: object properties: birthDate: type: string ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-format-deprecated Source: https://apigenie.pl/docs/linter/openapi/rules/schema-format-deprecated This rule reports the formats which the [OpenAPI format registry](https://spec.openapis.org/registry/format/) marks as deprecated, and names what replaces them. ## Rule Details From OpenAPI 3.1, schemas are JSON Schema 2020-12, which describes encoded content with `contentEncoding` and `contentMediaType`. The registry deprecates the formats that said the same thing in OpenAPI 3.0: | Format | Use instead | | ------ | ----------- | | `byte` | `contentEncoding: base64` | | `base64url` | `contentEncoding: base64url` | | `binary` | `contentMediaType`, or the media type of the body or part without a schema | OpenAPI 3.0 has neither keyword, so in 3.0 documents the rule reports these formats as hints: they show where the document will need a change when it moves to OpenAPI 3.1, without counting as warnings or failing the run. See [Formats](/docs/reference/cli/formats) for every format and what API Genie does with it. ## Configuration None. ## Examples **Good**. ```yaml openapi: 3.1.0 components: schemas: Avatar: type: string contentEncoding: base64 contentMediaType: image/png ``` **Bad**. ```yaml openapi: 3.1.0 components: schemas: Avatar: type: string format: byte ``` ## When Not to Use It When the document must stay readable by tools that only understand the OpenAPI 3.0 formats. ## Compatibility OpenAPI 3.1 and 3.2 documents get warnings; OpenAPI 3.0 documents get hints. --- # schema-format-type-mismatch Source: https://apigenie.pl/docs/linter/openapi/rules/schema-format-type-mismatch This rule checks all schema objects and warns if any of them have their `type` and `format` properties contradicting each other. ## Rule Details Contradicting `type` and `format` properties can lead to confusion and misinterpretation of the API's structure. The `schema-format-type-mismatch` rule is designed to ensure that all schema objects have their `type` and `format` properties in agreement. It reports a schema whose `format` is in the [OpenAPI format registry](https://spec.openapis.org/registry/format/) when none of the schema's types is a JSON type of that format (see [Formats](/docs/reference/cli/formats) for the list). For example: - `date`, `uuid`, `email`, `http-date`, `language` and the other string formats need `type: string`; - `int8` to `uint64`, `int32`, `int64`, `double-int` and `sf-integer` need `type: integer` or `type: number`; - `float`, `double` and `sf-decimal` need `type: number` (not `integer`); - `int64` and `uint64` also allow `type: string`, and `decimal` and `decimal128` allow `string` and `number`, as the registry does; - `unixtime` allows `integer`, `number` and `string`. The formats of `openapi.custom_formats` in the [configuration file](/docs/reference/cli/configuration) are checked against the type given there, and override the registry's. A schema without a `type` is reported too. Formats neither in the registry nor custom are left to [schema-format-unknown](/docs/linter/openapi/rules/schema-format-unknown). ## Configuration The optional `fixTypeFromFormat` boolean controls whether `--fix` replaces a mismatched schema type with the recognized format's type. It defaults to `true`. ## Examples **Good**. ```yaml components: schemas: User: type: object properties: birthDate: type: string format: date ``` **Bad**. ```yaml components: schemas: User: type: object properties: birthDate: type: integer format: date ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, the schema gets the singleton type of its format: integer formats set `integer`, `decimal` and `decimal128` set `string`, the other number formats set `number` and string formats set `string`. Unknown formats remain manual. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-format-unknown Source: https://apigenie.pl/docs/linter/openapi/rules/schema-format-unknown This rule reports a `format` which is neither in the [OpenAPI format registry](https://spec.openapis.org/registry/format/) nor declared in `openapi.custom_formats` of the configuration file. ## Rule Details Validators, mock servers and code generators ignore formats they don't know, so a misspelled format (`date_time`, `uuid4`) silently stops being checked. The `schema-format-unknown` rule finds them. Formats your API defines on purpose are declared in the [configuration file](/docs/reference/cli/configuration#openapicustom_formats): ```yaml openapi: custom_formats: - {name: currency-code, type: string} ``` The rule is off in the `recommended` ruleset, since OpenAPI allows any format, and a warning in `strict` and `all`. See [Formats](/docs/reference/cli/formats) for the registry's formats. ## Configuration None. ## Examples **Good**. ```yaml components: schemas: Id: type: string format: uuid ``` **Bad**. ```yaml components: schemas: Id: type: string format: uuid4 ``` ## When Not to Use It When the document uses many formats of its own and listing them in the configuration file isn't worth it. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-keyword-support Source: https://apigenie.pl/docs/linter/openapi/rules/schema-keyword-support This rule reports schema keywords that the tools consuming the document are unlikely to support, and suggests the rewrite where one exists. ## Rule Details OpenAPI 3.1 schemas are JSON Schema 2020-12, but much of the tooling around them is not. Validators of older drafts, OpenAPI 3.0 tools and most code generators ignore the keywords they don't know, so `unevaluatedProperties: false` stops rejecting anything, a `prefixItems` tuple turns into an untyped array and an `if`/`then` constraint silently disappears from the generated client. The rule checks each schema against one or more **targets** and reports, once per keyword, the first target that does not support it: | Target | Who it stands for | | ------ | ----------------- | | `draft4` | Validators stuck at draft-04, e.g. NJsonSchema's default dialect, older PHP and Python libraries | | `draft6` | Draft-06 validators | | `draft7` | Validators stopping at draft-07: gojsonschema, everit-org/json-schema, Ajv's default export; also the default schema dialect of AsyncAPI 2.x and 3.0 | | `draft2019-09` | Validators without draft 2020-12 | | `openapi30` | The Schema Object of OpenAPI 3.0 and the tools built for it, e.g. NSwag | | `codegen` | The keywords most code generators ignore or can't express in types (the default) | What each target lacks, and the suggested rewrite: | Keyword or form | Not supported by | Suggested rewrite | | --------------- | ---------------- | ----------------- | | `unevaluatedProperties` | drafts 4 to 7, `openapi30`, `codegen` | merge the `allOf` members into one schema and use `additionalProperties` | | `unevaluatedItems` | drafts 4 to 7 | `additionalItems` next to the array form of `items` | | `unevaluatedItems` | `openapi30`, `codegen` | none | | `prefixItems` | drafts 4 to 2019-09 | the array form of `items`, `additionalItems` for the rest | | `prefixItems` | `openapi30`, `codegen` | an object with named properties | | `$dynamicRef`, `$recursiveRef` | drafts 4 to 7, `openapi30`, `codegen` | `$ref` to the schema it resolves to | | `$dynamicRef` | `draft2019-09` | `$recursiveRef` | | `$dynamicAnchor`, `$recursiveAnchor` | drafts 4 to 7, `openapi30`, `codegen` | drop it once the reference to it is a `$ref` | | `$anchor` | drafts 4 to 7, `openapi30`, `codegen` | reference the schema by a JSON pointer | | `$vocabulary` | drafts 4 to 7, `openapi30`, `codegen` | drop it | | `$defs` | drafts 4 to 7 | `definitions` | | `$defs` | `openapi30` | move the schemas to `components/schemas` | | `dependentRequired` | drafts 4 to 7 | `dependencies` with an array of property names | | `dependentSchemas` | drafts 4 to 7 | `dependencies` with a schema | | `dependentRequired`, `dependentSchemas`, `dependencies` | `openapi30`, `codegen` | none | | `dependencies` | `draft2019-09` | `dependentRequired` or `dependentSchemas` | | `if` (with `then`/`else`) | `draft4`, `draft6`, `openapi30`, `codegen` | `oneOf` with one alternative per case | | `contentSchema`, `minContains`, `maxContains` | drafts 4 to 7, `openapi30`, `codegen` | none | | `contains` | `draft4`, `openapi30`, `codegen` | none | | `propertyNames` | `draft4`, `openapi30` | none | | `const` | `draft4`, `openapi30` | an `enum` with one value | | `patternProperties` | `openapi30` | `properties` for fixed names, `additionalProperties` for the rest | | `patternProperties` | `codegen` | `additionalProperties` | | `not` | `codegen` | none | | `additionalItems` | `openapi30` | none | | `$id`, `$schema`, `$comment`, `examples`, `contentEncoding`, `contentMediaType` | `openapi30` | drop `$id` and `$schema`; `description` for `$comment`; `example`; `format: byte` or `format: binary` | | `$id` | `draft4` | `id` | | a `type` array | `openapi30` | one `type` with `nullable: true`, or `anyOf` | | a `type` array of several non-null types | `codegen` | `oneOf` with one schema per type | | `type: null` | `openapi30` | `nullable: true` | | a numeric `exclusiveMinimum`/`exclusiveMaximum` | `draft4`, `openapi30` | `minimum`/`maximum` with the boolean form | | a boolean `exclusiveMinimum`/`exclusiveMaximum` | drafts 6 to 2019-09 | the bound as the value | | `nullable: true` | drafts 4 to 2019-09 | add `"null"` to `type` | | `$ref` with keywords that constrain the value | drafts 4 to 7, `openapi30`, `codegen` | wrap the reference in `allOf`; the siblings are ignored otherwise | Annotations a validator ignores without harm (`description`, `examples`, `readOnly`, `deprecated`, `$comment`, the content keywords) are reported only for `openapi30`, whose Schema Object allows no keywords besides its own and `x-` extensions. `then` and `else` are covered by the report on `if`. A `$ref` next to annotations only (`description`, `summary`, `example`, ...) is not reported. `apigenie convert --from jsonschema --to draft7|draft4|openapi30` performs most of these rewrites on a standalone JSON Schema document, including the `allOf` flattening that replaces `unevaluatedProperties`. ## Configuration The options are a target name, an array of target names, or an object with: - `targets`: a target name or an array of target names; `codegen` when omitted. - `allow`: keywords never reported, for the ones your tools are known to support. **Configuration Example 1**. Keep a 3.1 document usable by OpenAPI 3.0 tooling. ```yaml lint: rules: schema-keyword-support: - warning - openapi30 ``` **Configuration Example 2**. Check against draft-07 validators and the code generators, accepting `not`. ```yaml lint: rules: schema-keyword-support: - warning - targets: [draft7, codegen] allow: [not] ``` ## Examples **Good**. A closed object that every target understands. ```yaml components: schemas: Pet: type: object required: [name] properties: name: type: string additionalProperties: false ``` **Bad**. `unevaluatedProperties` is ignored by OpenAPI 3.0 tools, draft-07 validators and most code generators, so they accept any extra property. ```yaml components: schemas: Pet: allOf: - $ref: '#/components/schemas/Animal' properties: name: type: string unevaluatedProperties: false ``` ## When Not to Use It When every consumer of the document is known to support JSON Schema 2020-12 in full, e.g. a validator such as Ajv 2020, networknt, santhosh-tekuri/jsonschema or Python's jsonschema, and no code is generated from it. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. In OpenAPI 3.0 documents it finds keywords the Schema Object does not have (with the `openapi30` target) and the OpenAPI forms that JSON Schema validators don't understand (with the draft targets). --- # schema-no-additional-properties Source: https://apigenie.pl/docs/linter/openapi/rules/schema-no-additional-properties This rule ensures that schema objects of type "object" explicitly forbid additional properties. ## Rule Details Allowing additional properties can lead to inconsistencies in the API's structure, as well as to security risks. If a malicious actor can inject unsupported fields into an API request, and it will not be validated, the server may function incorrectly. The `schema-no-additional-properties` rule is designed to ensure that all schema objects of type "object" explicitly forbid additional properties. This is done by checking if the `additionalProperties` field is set to `false`. ## Configuration None. ## Examples **Good**. ```yaml components: schemas: User: type: object properties: firstName: type: string lastName: type: string additionalProperties: false ``` **Bad**. The User schema object does not forbid additional properties. ```yaml components: schemas: User: type: object properties: firstName: type: string lastName: type: string ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule sets `additionalProperties: false` on enabled schemas whose declared type includes `object`. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-no-contradictions Source: https://apigenie.pl/docs/linter/openapi/rules/schema-no-contradictions This rule reports schemas with contradicting constraints, which reject every value or some of the properties they describe. ## Rule Details The rule merges each schema with its `allOf` members, following references, and reports contradictions it can prove: - `allOf` members requiring types with nothing in common, such as `string` and `integer`; - bounds excluding every value: `minimum` above `maximum` (taking `exclusiveMinimum` and `exclusiveMaximum` into account, and integers when the type is `integer`), `minLength` above `maxLength`, `minItems` above `maxItems`, `minProperties` above `maxProperties`, or more required properties than `maxProperties`; - an `allOf` member rejecting every value, e.g. `false` or `not: {}`, and `anyOf` or `oneOf` whose every branch does; - `enum` or `const` values of an `allOf` member none of which satisfies the whole schema; - a required property whose schema rejects every value, or which an `allOf` member with `additionalProperties: false` does not declare; - a property which can never be present because an `allOf` member with `additionalProperties: false` does not declare it. This is the common mistake of closing a base schema and extending it with `allOf`; use `unevaluatedProperties: false` (OpenAPI 3.1) on the combined schema instead. A contradiction is reported once, at the schema it originates from, and not again at every schema using that one. Formats are not checked, and constraints the rule cannot reason about (such as `pattern`) never lead to a report. ## Configuration None. ## Examples **Good**. `Cat` declares every property it allows. ```yaml components: schemas: Pet: type: object required: [petType] properties: petType: type: string Cat: allOf: - $ref: '#/components/schemas/Pet' - properties: meow: type: boolean unevaluatedProperties: false ``` **Bad**. `Pet` forbids `meow`, so a `Cat` can never have it. ```yaml components: schemas: Pet: type: object additionalProperties: false required: [petType] properties: petType: type: string Cat: allOf: - $ref: '#/components/schemas/Pet' - properties: meow: type: boolean ``` **Bad**. No value is both a string and an integer. ```yaml components: schemas: Id: allOf: - type: string - type: integer ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. In OpenAPI 3.0 documents, `nullable` and the boolean `exclusiveMinimum`/`exclusiveMaximum` are taken into account, and keywords beside `$ref` are ignored as the specification requires. --- # schema-no-optional-nullable-properties Source: https://apigenie.pl/docs/linter/openapi/rules/schema-no-optional-nullable-properties This rule disallows schema properties that are both optional and nullable. ## Rule Details An optional nullable property represents an absent value in two ways: the property may be omitted, or it may be present with the value `null`. Requiring a property to be nullable or allowing it to be omitted makes the API contract unambiguous. A property is nullable when its schema, or the schema it references, allows `null`: - a `null` type, e.g. `type: [string, 'null']`; - `nullable: true` in OpenAPI 3.0 documents; - `null` in `enum` or as `const`; - an `anyOf` or `oneOf` branch allowing `null`, e.g. `anyOf: [{$ref: ...}, {type: 'null'}]`; - an `allOf` whose every member allows `null`. A property counts as required when the schema, one of its `allOf` members, or a schema using it in `allOf` lists it in `required`. In `PATCH` request bodies and `application/merge-patch+json` documents, `null` means "remove this value" (RFC 7396), so optional nullable properties are expected there and the rule skips them and every schema they contain. ## Configuration This rule accepts an object with the following options: - `ignorePatchBodies`: skip the schemas of `PATCH` request bodies and merge-patch documents. Defaults to `true`. ```yaml commands: lint: rules: schema-no-optional-nullable-properties: - warning - ignorePatchBodies: false ``` ## Examples **Good**. The nullable property is required. ```yaml components: schemas: User: type: object required: [middleName] properties: middleName: type: [string, 'null'] ``` **Good**. The optional property cannot be `null`. ```yaml components: schemas: User: type: object properties: middleName: type: string ``` **Bad**. `middleName` can be omitted or set to `null`. ```yaml components: schemas: User: type: object properties: middleName: type: [string, 'null'] ``` ## When Not to Use It Disable this rule when omission and an explicit `null` have distinct meanings in your API. ## Compatibility This rule is compatible with all OpenAPI 3.x versions: it recognizes `nullable: true` in OpenAPI 3.0 documents and `null` types in OpenAPI 3.1 and later. --- # schema-oneof-no-overlap Source: https://apigenie.pl/docs/linter/openapi/rules/schema-oneof-no-overlap This rule reports `oneOf` branches that a single value can match. ## Rule Details A value is valid against `oneOf` only if it matches exactly one branch. When branches overlap, the values matching several of them are rejected, which is rarely intended: two object schemas without `additionalProperties: false` both accept `{}`, and `number` overlaps `integer`. The rule reports: - identical branches, such as two references to the same schema or two schemas with the same content; - a value matching two branches. The rule tries the values the branches document (`example`, `examples`, `default`, `const` and `enum`) and small values it builds from their constraints, and reports only a value that it has validated against both branches and against the keywords beside `oneOf`. The message shows that value. `oneOf` with a `discriminator` is skipped, because tools choose the branch named by the discriminator instead of validating every branch. Branches with recursive or external references are skipped too. ## Configuration None. ## Examples **Good**. Each branch forbids the other one's properties. ```yaml components: schemas: Pet: oneOf: - type: object additionalProperties: false required: [meow] properties: meow: type: boolean - type: object additionalProperties: false required: [bark] properties: bark: type: boolean ``` **Good**. A discriminator selects the branch. ```yaml components: schemas: Pet: oneOf: - $ref: '#/components/schemas/Cat' - $ref: '#/components/schemas/Dog' discriminator: propertyName: petType ``` **Bad**. `{"name": ""}` matches both branches, so it is rejected. ```yaml components: schemas: Pet: oneOf: - type: object required: [name] properties: name: type: string meow: type: boolean - type: object required: [name] properties: name: type: string bark: type: boolean ``` ## When Not to Use It Disable this rule when the tools consuming your document do not validate `oneOf` strictly. Where overlapping branches are intended, `anyOf` expresses them correctly. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-property-naming Source: https://apigenie.pl/docs/linter/openapi/rules/schema-property-naming This rule checks all schema objects and warns if any of them define properties whose names do not adhere to the specified naming convention. ## Rule Details Adhering to a consistent naming convention enhances the readability and maintainability of the API's structure. The `schema-property-naming` rule is designed to ensure that all properties defined in schema objects adhere to a specified naming convention. Currently recognised naming conventions are `const`, `camel`, `camel-strict`, `kebab`, `pascal`, `pascal-strict`, `header`, and `snake` (the strict forms reject two capitals in a row, e.g. `userID`). ## Configuration The configuration block is a naming conventing name. Its default value is `snake`. ```yaml lint: rules: paths-max-count: - warning - kebab ``` ## Examples **Good**. ```yaml components: schemas: User: type: object properties: firstName: type: string lastName: type: string ``` **Bad**. ```yaml components: schemas: User: type: object properties: first_name: type: string last_name: type: string ``` ## When Not to Use It If your API does not follow a specific naming convention for schema property names, you may not need to use this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # schema-required-properties-defined Source: https://apigenie.pl/docs/linter/openapi/rules/schema-required-properties-defined This rule checks that every name listed in `required` is defined in `properties`. ## Rule Details A required property which is not defined is usually a typo (`required: [nmae]`) or a property removed from `properties` but not from `required`. Its value is not described, and generated code often cannot represent it. The rule looks for the definition wherever the schema's value is also described: - in the schema's `allOf` members, following references; - in the schemas using it in `allOf`, `anyOf`, `oneOf`, `not`, `if`, `then` or `else`, and in their other members. This accepts common patterns such as `anyOf: [{required: [email]}, {required: [phone]}]` below a schema defining both properties, or a mixin requiring a property that the schemas using it define. Schemas which allow undeclared properties on purpose are skipped: those with `patternProperties`, a schema in `additionalProperties`, or `additionalProperties: true`. Schemas whose references cannot be resolved are skipped too. `required` in a schema which does not allow objects, for example beside `items` in an array schema instead of inside it, has no effect and is reported as such. ## Configuration None. ## Examples **Good**. ```yaml components: schemas: User: type: object required: [id, name] properties: id: type: string name: type: string ``` **Bad**. `nmae` is not defined. ```yaml components: schemas: User: type: object required: [id, nmae] properties: id: type: string name: type: string ``` **Bad**. `required` applies to the array, not to its items. ```yaml components: schemas: Packages: type: array items: type: object properties: name: type: string required: [name] ``` ## When Not to Use It Disable this rule when required properties are intentionally left undescribed. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # server-urls Source: https://apigenie.pl/docs/linter/openapi/rules/server-urls This rule warns if a server defined by an OpenAPI schema uses HTTP protocol while not being from localhost, or uses upper-case letters. ## Rule Details Using HTTP for non-localhost servers can lead to security issues, and using upper-case letters in URLs can lead to potential case sensitivity problems. The `server-urls` rule is designed to ensure that all servers defined in an OpenAPI schema use HTTPS protocol unless they are from localhost, and that they do not use upper-case letters. ## Configuration The optional `upgradeHttp` boolean controls whether automatic fixes upgrade non-local `http` URLs to `https`. It defaults to `false`. ## Examples **Good**. ```yaml servers: - url: https://api.example.com/v1 description: Production server - url: http://localhost:8080/v1 description: Local development server ``` **Bad**. ```yaml servers: - url: http://api.example.com/v1 description: Production server - url: https://API.EXAMPLE.COM/v1 description: Production server with upper-case letters ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule lowercases URL schemes and hostnames. It preserves credentials, ports, paths, query strings, fragments, and server variable spelling because those values can be case-sensitive. Set `upgradeHttp: true` to also change non-local `http` schemes to `https`. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # servers-defined Source: https://apigenie.pl/docs/linter/openapi/rules/servers-defined This rule warns if an OpenAPI schema does not define any server. ## Rule Details The `servers-defined` rule is designed to ensure that an OpenAPI schema defines at least one server. Not defining servers effectively disables the live documentation feature, where potential users can try to call an API server. ## Configuration None. ## Examples **Good**. ```yaml servers: - url: https://api.dev.example.com/v1 description: Development server ``` **Bad**. ```yaml # No servers defined ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # spell-check Source: https://apigenie.pl/docs/linter/openapi/rules/spell-check This rule checks the spelling of the texts written for humans: titles, summaries and descriptions. ## Rule Details The rule reports every misspelled word, with up to three suggested corrections, for example `Misspelled word 'recieve' in description (did you mean 'receive', 'relieve', 'reverie'?)`. The issue points at the object whose text contains the word. The rule checks: - the title, summary and description of the info object, - the summary and description of path items, operations, tags, examples and references, - the description of servers, server variables, parameters, request bodies, responses, headers, links, security schemes and external documentation, - the title and description of schemas. Descriptions are CommonMark: the rule checks their prose, and skips code spans, code blocks, raw HTML and link destinations. Titles and summaries are plain text, in which `backticked` spans are skipped, too. The rule also skips the tokens which are not plain words: - URLs, e-mail addresses, paths, and file and domain names (`openapi.yaml`, `example.com`), - identifiers: `snake_case`, `camelCase` and names with inner capitals (`OpenAPI`, `PetStore`), - acronyms, also in the plural (`JSON`, `APIs`, `ID's`), - tokens with digits (`v2`, `401`) and single letters. The spell checker is a C++ port of [Hunspell](https://hunspell.github.io). By default it uses the built-in English (US) dictionary from [SCOWL](http://wordlist.aspell.net) (as shipped with LibreOffice), extended with words common in API descriptions, such as `webhook`, `endpoint`, `namespace`, `nullable` and `json`. Any Hunspell dictionary can be used instead. The rule is part of the `strict` and `all` rulesets. ## Configuration This rule accepts an object with the following options: - `words`: extra words to accept, for example product names. A word is accepted as given, capitalized and in capitals: `petstore` accepts `petstore`, `Petstore` and `PETSTORE`. - `dictionary` and `affix`: paths of a Hunspell dictionary (`.dic`) and its affix file (`.aff`) to use instead of the built-in dictionary. They go together. Dictionaries in UTF-8, ISO-8859-1, ISO-8859-2 and ISO-8859-15 are supported. - `suggestions`: how many corrections to suggest for a word, from 0 to 10 (default 3). Suggestions are the costly part of the rule; on a very large document with many unknown words, `0` makes it about ten times faster. ```yaml commands: lint: rules: spell-check: - warning - words: [petstore, Fluffy] ``` ```yaml commands: lint: rules: spell-check: - warning - dictionary: dictionaries/en_GB.dic affix: dictionaries/en_GB.aff ``` ## Examples **Good**. ```yaml paths: /users: get: summary: Retrieve a list of users description: This operation retrieves the `users` of the system as JSON. ``` **Bad**. ```yaml paths: /users: get: summary: Retrive a list of users description: This operation retrives a list of users from the sytem. ``` ## When Not to Use It You may disable this rule if your documentation is written in a language you have no dictionary for. To silence it for a single object and its children, add `x-lint-disable: [spell-check]` to the object. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # standard-media-types Source: https://apigenie.pl/docs/linter/openapi/rules/standard-media-types This rule checks whether requests and responses use only standard media types. ## Rule Details Using non-standard media types can lead to interoperability issues in the API's structure. The `standard-media-types` rule is designed to ensure that all requests and responses in a schema use only standard media types. The rule works by iterating over all the requests and responses defined in the schema and checking if they use any non-standard media types. If such a media type is found, the rule raises a warning. ## Configuration None. ## Examples **Good**. ```yaml paths: /users: get: responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/User" ``` **Bad**. `application/x-custom` is not a standard media type. ```yaml paths: /users: get: responses: "200": description: OK content: application/x-custom: schema: $ref: "#/components/schemas/User" ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # standard-response-codes Source: https://apigenie.pl/docs/linter/openapi/rules/standard-response-codes This rule checks whether operations define responses for standard HTTP codes only, as described in RFC 9110. ## Rule Details Defining responses for non-standard codes can lead to confusion and interoperability issues in the API's structure. The `standard-response-codes` rule is designed to ensure that operations define responses for standard HTTP codes only, as described in RFC 9110. The rule works by iterating over all the operations defined in the schema and checking if they define responses for any non-standard HTTP codes. If such a response is found, the rule raises a warning. ## Configuration None. ## Examples **Good**. ```yaml paths: /users: get: responses: "200": description: OK "400": description: Bad Request "404": description: Not Found ``` **Bad**. ```yaml paths: /users: get: responses: "200": description: OK "460": description: Non-standard Code ``` ## When Not to Use It You may turn this rule off for for internal APIs. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # standard-response-errors Source: https://apigenie.pl/docs/linter/openapi/rules/standard-response-errors This rule ensures that bodies for "error" responses are adhere to the same convention. ## Rule Details Having errors formatted in the same way significantly simplifies their handling. The `standard-response-errors` rule is designed to ensure that the bodies of all responses for 4xx and 5xx codes in a schema adhere to the same convention. Currently supported conventions are: - Problem JSON. This convention provides a standardized format for sending details about errors in HTTP APIs, enhancing the consistency and interoperability of the API's structure. - JSON API. By this convention, a response body is an array of Error objects, as defined in JSON:API specification. ## Configuration The configuration is a single string representing the desired convention. Currently supported values are: - `problem-json` - [Problem JSON](https://datatracker.ietf.org/doc/html/rfc7807). This is the default value - `json-api` - convention, where a response body is a list of [JSON:API Error objects](https://jsonapi.org/format/#error-objects) **Configuration Example** ```yaml lint: standard-response-errors: - error - problem-json ``` ## Examples **Good**. ```yaml paths: /users: get: responses: "400": description: Bad Request content: application/problem+json: schema: type: object properties: type: type: string title: type: string status: type: integer detail: type: string ``` **Bad**. ```yaml paths: /users: get: responses: "400": description: Bad Request content: application/json: schema: type: object properties: error: type: string message: type: string ``` **Good** with the `json-api` convention. The response must use the `application/vnd.api+json` media type, and its schema must be an object whose `errors` property is an array of objects. ```yaml paths: /users: get: responses: "400": description: Bad Request content: application/vnd.api+json: schema: type: object properties: errors: type: array items: type: object properties: status: type: string title: type: string detail: type: string ``` ## When Not to Use It When your team or organisation has different conventions for formatting errors. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # tag-description-defined Source: https://apigenie.pl/docs/linter/openapi/rules/tag-description-defined This rule checks whether each tag defined in an OpenAPI schema has a meaningful description. ## Rule Details Tag descriptions provide necessary context about the purpose and usage of the tag, improving the readability and understanding of the API's structure. The `tag-description-defined` rule checks all tags defined in an OpenAPI schema and raises an issue if any of them have description missing or empty. Besides missing and empty descriptions this rule disregards descriptions that: - consist of whitespace characters only - consist of "empty" words like "TBD", "TODO" and so on - are shorter than the value specified in the configuration ## Configuration The configuration block is a single positive integer, which specifies the minimal number of characters in a "meaningful" description. The default value is 10. **Configuration Example** ```yaml lint: rules: tag-description-defined: - warning - 20 ``` ## Examples **Good**. ```yaml tags: - name: User description: Operations related to the User entity. - name: Product description: Operations related to the Product entity. ``` **Bad**. ```yaml tags: - name: User description: '' - name: Product ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # tags-sorted Source: https://apigenie.pl/docs/linter/openapi/rules/tags-sorted This rule checks whether tags defined in an OpenAPI schema are sorted alphabetically according to their names. ## Rule Details Keeping tags sorted alphabetically improves the readability of the API's schema. The `tags-sorted` rule checks all tags defined by an OpenAPI schema and raises an issue if the tags are not sorted. ## Configuration None. ## Examples **Good**. ```yaml tags: - name: Admin - name: Product - name: User ``` **Bad**. ```yaml tags: - name: User - name: Admin - name: Product ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # tags-unique Source: https://apigenie.pl/docs/linter/openapi/rules/tags-unique This rule checks for schemas that define multiple tags with the same name, considering tag names as case-insensitive. ## Rule Details Duplicate tag names can lead to confusion and misinterpretation of the API's structure. The `tags-unique` rule is designed to ensure that all tags defined in a schema have unique names. Names are considered case-insensitive. ## Configuration None. ## Examples **Good**. ```yaml tags: - name: User - name: Product ``` **Bad**. ```yaml tags: - name: user description: First tag - name: User description: Is this another tag, or a typo ? ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule removes case-insensitive duplicates with identical metadata, and removes empty duplicates in favor of an informative definition. Conflicting descriptions or external documentation remain manual. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # unambiguous-response-codes Source: https://apigenie.pl/docs/linter/openapi/rules/unambiguous-response-codes This rule checks for operations that define responses for both a generic code (like 2XX) and a specific code in the same range (like 204). ## Rule Details When an operation defines responses for both "generic" code, such as 2XX, as well as for the specific code from the same range, such as 204, it is unclear which one should be used. The `unambiguous-response-codes` rule is designed to prevent such situations. It ensures that operations do not define responses for both generic code and specific code in the same range. If such an operation is found, the rule raises an issue. ## Configuration None. ## Examples **Good**. Operation defines both generic and specific responses, but they fall into different ranges. ```yaml paths: /users: get: responses: "103": description: Returns preloading hints. "2XX": description: A list of users. ``` **Bad**. ```yaml paths: /users: get: responses: "2XX": description: A list of users. "204": description: No Content. ``` ## When Not to Use It If your company decides to treat generic responses as defaults, and specific responses as overrides to that defaults. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # unused-request-body Source: https://apigenie.pl/docs/linter/openapi/rules/unused-request-body This rule checks for operations that normally cannot have a request body (i.e., `GET`, `TRACE`, or `HEAD`), but still define one. ## Rule Details Defining a request body for operation which cannot have one, such as `GET`, `TRACE` or `HEAD` can lead to confusion and unnecessary complexity in the API's structure. The `unused-request-body` rule works by iterating over all the operations defined in the schema and checking if any `GET`, `TRACE` or `HEAD` operations have a defined request body. If such an operation is found, the rule raises a warning. ## Configuration None. ## Examples **Good**. ```yaml paths: /users: get: responses: "200": description: A list of users. ``` **Bad**. The GET operation on `/users` has a defined request body. ```yaml paths: /users: get: requestBody: content: application/json: schema: $ref: "#/components/schemas/User" responses: "200": description: A list of users. ``` ## When Not to Use It We recommend to always enable this rule. ## Automatic fixes When invoked with `--fix`, this rule removes `requestBody` from enabled `GET`, `HEAD`, and `TRACE` operations. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # unused-shared-component Source: https://apigenie.pl/docs/linter/openapi/rules/unused-shared-component This rule checks whether all components defined in an OpenAPI schema are used. ## Rule Details The `unused-shared-component` rule is designed to ensure that each shared component defined in an OpenAPI schema is used at least once. Unused components can lead to confusion and unnecessary bloat in the API's structure. The rule starts from the parts of the document outside `components` (paths, webhooks, security requirements, tags, extensions, ...) and follows references (`$ref`, discriminator mappings and security requirement names) transitively. Every component it cannot reach is reported, including components used only by other unused components. A document without operations (no path items and no webhooks) is treated as a library of components for other documents and is not checked. ## Automatic Fixes The automatic fix removes components proven unreachable from document roots. It retains disabled components and their dependencies, plus exact local targets named by source-only or unresolved local references and discriminator mappings. External references do not retain unrelated local components. ## Configuration None. ## Examples **Good**. The shared `User` schema is referenced by path item parameter. ```yaml components: schemas: User: type: object properties: id: type: integer name: type: string paths: /users/{id}: get: parameters: - name: id in: path required: true schema: $ref: "#/components/schemas/User" ``` **Bad**. The shared `User` schema is not used. ```yaml components: schemas: User: type: object properties: id: type: integer name: type: string paths: /users/{id}: get: parameters: - name: id in: path required: true schema: type: integer ``` ## When Not to Use It Disable the rule for documents that have operations but also publish components for other documents to reference. ## Compatibility This rule is compatible with all OpenAPI 3.x versions. --- # audience-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/audience-defined This rule checks whether the intended audience of an API is declared in the `#/info/x-audience` extension of an AsyncAPI document. ## Rule Details Knowing who an API is built for helps to decide how strict its review should be, how carefully breaking changes must be handled, and where the API can be published. AsyncAPI does not provide a field for this information. The `audience-defined` rule fills this gap. It requires the `x-audience` extension to be present in the info object, and its value to be one of the following strings: - `internal-team` - the API is used only by the team that owns it, - `internal-company` - the API is used by other teams in the same company, - `partner` - the API is shared with selected partners, - `public` - the API is publicly available. The rule reports `API audience (x-audience) is missing` when the extension is absent, and `API audience (x-audience) has invalid value (...)` when its value is not one of the strings listed above (including values that are not strings at all). ## Configuration None. ## Examples **Good**. The audience of the API is clearly stated. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 x-audience: internal-company ``` **Bad**. The audience is not declared. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 ``` **Bad**. The audience has a value that is not recognized. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 x-audience: everyone ``` ## When Not to Use It You may disable this rule if your organization does not classify its APIs by their audience. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # channel-addresses-unique Source: https://apigenie.pl/docs/linter/asyncapi/rules/channel-addresses-unique This rule checks that no two channels of an AsyncAPI document share the same address, including addresses that differ only by the names of their parameters. ## Rule Details In AsyncAPI 3.x the key of a channel in the `channels` map is only an identifier; the actual topic, queue or routing key is given by the channel's `address`. Two channels with the same address describe the same destination, which is usually a copy-and-paste mistake and makes it unclear which of the two definitions (and which set of messages) is authoritative. The `channel-addresses-unique` rule compares the addresses of all channels defined in the root `channels` map. Before comparing, the name of every parameter expression is dropped, so `users.{userId}.events` and `users.{id}.events` are considered duplicates, as they match exactly the same set of concrete addresses. The rule reports `Duplicate channel address found` on every channel whose address duplicates the address of a channel defined earlier in the document. Channels without an `address` (or with `address: null`, meaning the address is dynamic or unknown) are ignored. Channels defined under `components/channels` are not checked. ## Configuration None. ## Examples **Good**. Each channel has its own address. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petCreated: address: pets.{petId}.created parameters: petId: description: Identifier of the pet. petDeleted: address: pets.{petId}.deleted parameters: petId: description: Identifier of the pet. ``` **Bad**. Both channels point to the same destination; they differ only by the parameter name. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petEvents: address: pets.{petId}.events parameters: petId: description: Identifier of the pet. animalEvents: address: pets.{animalId}.events parameters: animalId: description: Identifier of the animal. ``` ## When Not to Use It You may disable this rule if you intentionally describe the same destination with several channels, for example when the channels are available on different servers and carry different messages. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # channel-parameters-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/channel-parameters-defined This rule checks that the parameters used in a channel address are declared in the channel's `parameters` map, and that the map declares no parameters the address does not use. ## Rule Details A channel address may contain parameter expressions, such as `{userId}` in `users.{userId}.signedUp`. The AsyncAPI specification requires every such expression to have a matching entry in the channel's `parameters` map, whose key is the parameter name. Parameters that are declared but never used in the address are almost always leftovers from an earlier version of the address. The `channel-parameters-defined` rule checks every channel defined in the root `channels` map and in `components/channels` (references to channels are checked where the channel is defined). For each channel it reports: - `Channel parameter '<name>' is not defined` for every parameter used in the address that has no entry in the `parameters` map, - `Channel parameter '<name>' is not used in the channel address` for every entry of the `parameters` map that does not appear in the address. Channels without an `address` (or with `address: null`) are ignored, since their parameters cannot be verified. ## Configuration None. ## Examples **Good**. The address and the parameters map agree. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 channels: userSignedUp: address: users.{userId}.signedUp parameters: userId: description: Identifier of the user. ``` **Bad**. `userId` is used in the address but not declared, and `region` is declared but not used. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 channels: userSignedUp: address: users.{userId}.signedUp parameters: region: description: Region the user signed up in. ``` ## Automatic fixes When the linter is invoked with the `--fix` option, this rule rewrites the `parameters` map of every reported channel so that it matches the address: declared parameters that the address uses are kept unchanged (including references), missing parameters are added as empty Parameter objects, and unused ones are removed. The parameters are ordered as they first appear in the address. If the address uses no parameters at all, the `parameters` map is removed. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # contact-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/contact-defined This rule checks whether complete contact information is present in an AsyncAPI document. ## Rule Details The `contact-defined` rule ensures that the `info.contact` object is defined and that its `name`, `email` and `url` properties are all present and not empty. Contact information tells consumers of the API whom to ask for help, report problems to, or notify about their use of the API. The rule reports: - `Contact object is missing` when there is no `info.contact` object, - `Contact name is missing or empty` when `name` is missing or empty, - `Contact email is missing` when `email` is missing or empty, - `Contact URL is missing` when `url` is missing or empty. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 contact: name: Pet Platform Team email: pets@your-company.com url: https://your-company.com/teams/pets ``` **Bad**. The contact object is present, but it is not possible to reach the team by email. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 contact: name: Pet Platform Team url: https://your-company.com/teams/pets ``` ## When Not to Use It You may skip contact information for APIs that are used only by the team that owns them. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # id-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/id-defined This rule checks whether the application identifier (the root `id` field) is present in an AsyncAPI document, and whether its value matches the expected format. ## Rule Details Uniquely identifying an application is essential for tracking and managing different versions of its API. Titles and file names change over time, and different applications may share a title. AsyncAPI provides the root `id` field for this purpose; the specification requires its value to be a URI (for example a URN). The `id-defined` rule ensures that the `id` field is defined, and that its value matches the configured pattern. The rule reports `Application identifier (id) is missing` when the field is absent, and `Application identifier (id) has invalid format` when its value does not match the pattern. ## Configuration The rule configuration must be an object with a single optional attribute, `pattern`, which specifies the expected format of the identifier. When given, it must be a non-empty string: - `uri` (the default) - any absolute URI, i.e. a scheme followed by a colon and at least one non-whitespace character (e.g. `urn:example:pets` or `https://your-company.com/apis/pets`), - `uuid` - a UUID URN, i.e. `urn:uuid:` followed by a UUID (e.g. `urn:uuid:9c9c1d3c-3d4c-4a8e-9f54-0b2f7d7e6c11`), - any other string is treated as an ECMAScript regular expression, which must match the whole identifier. **Configuration Example**. The following configuration requires the identifier to be a UUID URN. ```yaml version: "1.0" commands: lint: asyncapi: rules: id-defined: - error - pattern: uuid ``` **Configuration Example**. The following configuration requires the identifier to be a URN in the `your-company` namespace. ```yaml version: "1.0" commands: lint: asyncapi: rules: id-defined: - error - pattern: "urn:your-company:[a-z-]+" ``` ## Examples **Good**. The application can be tracked independently of its title and version. ```yaml asyncapi: 3.0.0 id: urn:uuid:9c9c1d3c-3d4c-4a8e-9f54-0b2f7d7e6c11 info: title: Pet Events version: 1.2.8 ``` **Bad**. There is no identifier, so it is difficult to track the evolution of the API. Renaming the file, or creating a different API with the same title, leads to ambiguities. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.2.4 ``` **Bad**. The identifier is not a URI. ```yaml asyncapi: 3.0.0 id: pet events info: title: Pet Events version: 1.2.8 ``` ## Automatic fixes If `pattern` is `uri` or `uuid`, this rule is auto-fixing. When the linter is invoked with the `--fix` option, it sets the `id` field to a newly generated UUID URN (`urn:uuid:` followed by a random UUID v4). An existing identifier with an invalid format is replaced. Identifiers checked against a custom regular expression are not fixed. ## When Not to Use It We recommend to always set the application identifier. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # info-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/info-defined This rule checks whether the info object of an AsyncAPI document provides a description and a version. ## Rule Details The info object is the first thing readers of an API see. Besides the mandatory title, it should explain what the application does and which version of the API is being described. The `info-defined` rule reports: - `Info description is missing or empty` when `info.description` is missing or empty, - `Info version is empty` when `info.version` is an empty string. The title is required by the AsyncAPI specification itself and is not checked by this rule. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 description: Events published when pets are adopted, returned or updated. ``` **Bad**. The document does not explain what the application does, and its version is empty. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: "" ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # license-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/license-defined This rule checks whether the licensing information is present in an AsyncAPI document. ## Rule Details The `license-defined` rule is designed to ensure that the `info.license` object is defined, and that both its `name` and its `url` are provided. This information is crucial as it specifies the terms under which the API can be used. The rationale behind this rule is to enforce the inclusion of licensing information in the API specification. This helps users of the API to understand the terms of use and any restrictions that may apply. The rule reports: - `License object is missing` when there is no `info.license` object, - `License name is empty` when `name` is empty, - `License URL is missing or empty` when `url` is missing or empty. Unlike OpenAPI 3.1, AsyncAPI license objects have no SPDX `identifier` field, so the rule always requires a URL. ## Configuration None. ## Examples **Good**. The document provides a link to its license, thus making it easier to understand how the API can be used. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 license: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 ``` **Bad**. The license has a name, but no link to its terms. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 license: name: Proprietary ``` ## When Not to Use It You may skip licensing information for team-level APIs. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # operation-description-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/operation-description-defined This rule checks whether every operation of an AsyncAPI document has a meaningful description. ## Rule Details An operation describes what the application does with a channel: whether it sends or receives messages, and why. A good description explains when the messages are produced or consumed, and what the application does with them. The `operation-description-defined` rule reports `Operation has no meaningful description` for every operation (in the root `operations` map and in `components/operations`) whose `description` is missing or shorter than the configured minimal length. Descriptions coming from operation traits are not taken into account. ## Configuration The configuration block is a positive integer: the minimal length of a description, in characters. The default value is `10`. **Configuration Example**. The following configuration requires operation descriptions to be at least 30 characters long. ```yaml version: "1.0" commands: lint: asyncapi: rules: operation-description-defined: [error, 30] ``` ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" description: Updates the shelter inventory when a pet is adopted. ``` **Bad**. The operation has no description. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" ``` **Bad**. The description is too short to be useful. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" description: Adopted ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # operation-security-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/operation-security-defined This rule checks whether every operation of an AsyncAPI document is protected by a security requirement. ## Rule Details In AsyncAPI 3.x, security requirements can be declared on operations, on operation traits, and on servers. The `operation-security-defined` rule reports `Security requirement is not defined` for every operation that is secured in none of the following ways: - the operation has a non-empty `security` list, - at least one of the operation's traits has a non-empty `security` list, - every server the operation is available on has a non-empty `security` list. The servers of an operation are the servers listed in the `servers` field of its channel or, when the channel does not list any servers, all servers of the document. An operation whose channel lists no servers in a document that has no servers at all, or whose channel reference cannot be resolved, is not considered secured by servers. ## Configuration None. ## Examples **Good**. The operation declares its own security requirement. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted operations: publishPetAdopted: action: send channel: $ref: "#/channels/petAdopted" security: - $ref: "#/components/securitySchemes/apiKey" components: securitySchemes: apiKey: type: httpApiKey name: X-API-Key in: header ``` **Good**. The only server of the channel requires authentication. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 servers: production: host: broker.your-company.com:9093 protocol: kafka-secure security: - $ref: "#/components/securitySchemes/saslScram" channels: petAdopted: address: pets.adopted servers: - $ref: "#/servers/production" operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" components: securitySchemes: saslScram: type: scramSha512 ``` **Bad**. The channel is available on all servers, and one of them does not require authentication. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 servers: production: host: broker.your-company.com:9093 protocol: kafka-secure security: - $ref: "#/components/securitySchemes/saslScram" legacy: host: legacy.your-company.com:9092 protocol: kafka channels: petAdopted: address: pets.adopted operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" components: securitySchemes: saslScram: type: scramSha512 ``` ## When Not to Use It You may disable this rule for APIs that are intentionally public, or that are available only inside a trusted network. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # operation-tags-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/operation-tags-defined This rule checks whether every operation of an AsyncAPI document has at least one tag assigned. ## Rule Details Tags group related operations. Documentation generators use them to organize operations into sections, and consumers use them to find the operations they are interested in. The `operation-tags-defined` rule reports `Operation has no tags defined` for every operation (in the root `operations` map and in `components/operations`) whose own `tags` list is missing or empty. Tags coming from operation traits are not taken into account. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" tags: - name: adoption ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" ``` ## When Not to Use It You may disable this rule for small APIs, whose operations do not need to be grouped. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # parameter-description-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/parameter-description-defined This rule checks whether every channel parameter of an AsyncAPI document has a meaningful description. ## Rule Details Channel parameters are the variable parts of channel addresses, such as `userId` in `users.{userId}.signedUp`. A description explains what the parameter identifies and which values it may take. The `parameter-description-defined` rule checks the entries of the `parameters` map of every channel (in the root `channels` map and in `components/channels`), as well as the entries of `components/parameters`. References are checked where the referenced parameter is defined. The rule reports `Parameter '<name>' has no meaningful description` for every parameter whose description: - is missing, empty or blank, - is shorter than the configured minimal length, - is a placeholder: `TODO`, `FIXME` or `TBD` (case-insensitive). ## Configuration The configuration block is a positive integer: the minimal length of a description, in characters. The default value is `10`. **Configuration Example**. The following configuration requires parameter descriptions to be at least 20 characters long. ```yaml version: "1.0" commands: lint: asyncapi: rules: parameter-description-defined: [warning, 20] ``` ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 channels: userSignedUp: address: users.{userId}.signedUp parameters: userId: description: Identifier of the user who signed up. ``` **Bad**. The first parameter has no description at all, the second one has a placeholder. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 channels: userSignedUp: address: users.{region}.{userId}.signedUp parameters: region: {} userId: description: TODO ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # parameter-naming Source: https://apigenie.pl/docs/linter/asyncapi/rules/parameter-naming This rule checks all channel parameters in an AsyncAPI document and warns if their names do not adhere to the specified naming convention. ## Rule Details The `parameter-naming` rule is designed to ensure that the name of each channel parameter adheres to a specific naming convention. In AsyncAPI 3.x, the name of a parameter is its key in the `parameters` map, which is the same name that appears between braces in the channel address. The rule checks the parameters of every channel (in the root `channels` map and in `components/channels`), as well as the entries of `components/parameters`. For every parameter whose name is not in the expected convention, it reports `Parameter should be named <name>`, where `<name>` is the parameter name converted to that convention. The recognised naming conventions are: - `const` - THIS_IS_CONST_CASE - `camel` - thisIsCamelCase, also with acronyms (userID) - `camel-strict` - thisIsCamelCase, but no two capitals in a row (userId, not userID) - `kebab` - this-is-kebab-case - `pascal` - ThisIsPascalCase, also with acronyms (HTTPServer) - `pascal-strict` - ThisIsPascalCase, but no two capitals in a row (HttpServer, not HTTPServer) - `header` - This-Is-Header-Case - `snake` - this_is_snake_case The default convention is `snake`. Unlike OpenAPI, AsyncAPI parameters have no location (path, query, header or cookie), so a single convention applies to all of them. ## Configuration The configuration block can be a string or an object. If it is a string, its value is the name of the naming convention (see the "Rule Details" section for a list of recognized names). If it is an object, it must contain the `default` property, whose value is the name of the naming convention. Other properties are ignored. **Configuration Example**. The following two examples are equivalent. They specify that the expected naming convention for all parameters is `camelCase`. ```yaml version: "1.0" commands: lint: asyncapi: rules: parameter-naming: [warning, camel] ``` ```yaml version: "1.0" commands: lint: asyncapi: rules: parameter-naming: - warning - default: camel ``` ## Examples **Good**. With the default configuration. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 channels: userSignedUp: address: users.{user_id}.signedUp parameters: user_id: description: Identifier of the user. ``` **Bad**. With the default configuration, the linter reports `Parameter should be named user_id`. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 channels: userSignedUp: address: users.{userId}.signedUp parameters: userId: description: Identifier of the user. ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # safe-markdown Source: https://apigenie.pl/docs/linter/asyncapi/rules/safe-markdown This rule ensures that Markdown strings (descriptions) do not contain unsafe constructs like script tags or `eval()` calls. ## Rule Details Unsafe constructs embedded in Markdown strings can pose security risks when the documentation is rendered, for example by a documentation portal. The `safe-markdown` rule checks the `description` field of every object that supports CommonMark descriptions: the info object, servers, server variables, channels, parameters, operations, operation traits, operation reply addresses, messages, message traits, correlation IDs, tags, external documentation, schemas and security schemes. It reports: - `Markdown contains <script> tag` when a description contains a `script` element, - `Markdown contains eval() function` when a description contains an `eval()` call. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 description: Events published when pets are adopted or returned. ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 description: "Events published when pets are adopted. <script>alert('Unsafe')</script>" ``` ## Automatic fixes When invoked with `--fix`, this rule escapes the delimiters of recognized script elements and `eval()` calls. It preserves the surrounding Markdown and does not alter code spans or fenced code blocks. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-constraints Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-constraints This rule ensures that schema objects define constraints restricting the size of the underlying data. ## Rule Details Defining constraints enhances the robustness and reliability of the API's structure, and protects consumers from unexpectedly large messages. The `schema-constraints` rule checks every schema of an AsyncAPI document (component schemas, message payloads and headers, parameter schemas, and all schemas nested in them) and requires, depending on the schema type: - integers - both a lower bound (`minimum` or `exclusiveMinimum`) and an upper bound (`maximum` or `exclusiveMaximum`), - numbers - both a lower bound and an upper bound, as for integers, - strings - `maxLength`, `pattern`, `format` or `enum`, - arrays - `maxItems`, - objects - `maxProperties`, or `additionalProperties: false`. A schema with several types (e.g. `type: [string, integer]`) is checked for each of them. The reported messages are `Integer schema does not define constraints restricting its range`, `Numeric schema does not define constraints restricting its range`, `String schema does not define constraints restricting its length`, `Array schema does not define constraints restricting its size` and `Object schema does not define constraints restricting its size`. ## Configuration The configuration should be an object whose keys are supported schema types, and values are boolean values. Only the types set to `true` are checked, and at least one schema type must be set to `true`. Supported schema types are `integer`, `number`, `string`, `array` and `object`. By default, constraints for all supported schema types are verified. The `recommended` ruleset checks only `number` and `string` schemas. **Configuration Example**. The rule with the following configuration checks only array and object schemas, and does not check numbers and strings. ```yaml version: "1.0" commands: lint: asyncapi: rules: schema-constraints: - warning - array: true object: true ``` ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: User: type: object properties: username: type: string maxLength: 20 pattern: "^[a-z0-9_-]{3,16}$" roles: type: array items: type: string enum: [admin, member] maxItems: 5 age: type: integer minimum: 18 maximum: 100 maxProperties: 3 ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: User: type: object properties: username: type: string roles: type: array items: type: string age: type: integer ``` ## When Not to Use It If your API does not require specific constraints on its schema objects, you may not need to use this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-datetime-suffix Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-datetime-suffix This rule ensures that the names of date and time properties end with a common suffix. ## Rule Details A consistent suffix makes temporal properties easy to recognize, and distinguishes them from other properties describing the same concept (e.g. `created_at` versus `created_by`). The `schema-datetime-suffix` rule checks the properties of every object schema of an AsyncAPI document. For every property whose schema has the `date`, `date-time` or `time` format and whose name does not end with the configured suffix, it reports `Property name <name> should end with <suffix>`. The issue is reported at the property itself, not at a (possibly shared) referenced schema. The default suffix is `_at`. ## Configuration The configuration block is a non-empty string: the required suffix. **Configuration Example**. The following configuration requires temporal properties to end with `At`, which fits camelCase property names. ```yaml version: "1.0" commands: lint: asyncapi: rules: schema-datetime-suffix: [warning, At] ``` ## Examples **Good**. With the default configuration. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 components: schemas: PetAdopted: type: object properties: adopted_at: type: string format: date-time ``` **Bad**. With the default configuration. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 components: schemas: PetAdopted: type: object properties: adoption_date: type: string format: date-time ``` ## When Not to Use It You may disable this rule if your API follows a different convention for naming temporal properties. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-enum-naming Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-enum-naming This rule ensures that all string values defined in the `enum` properties of schema objects adhere to a predefined naming convention. ## Rule Details Adhering to a consistent naming convention enhances the readability and maintainability of the API's structure. The `schema-enum-naming` rule checks the string values of the `enum` list of every schema of an AsyncAPI document. For every value that is not in the expected convention, it reports `Schema enum value '<value>' is not in the '<convention>' convention`. Values of other types (numbers, booleans, `null`, ...) are ignored. The recognised naming conventions are: - `const` - THIS_IS_CONST_CASE - `camel` - thisIsCamelCase, also with acronyms (userID) - `camel-strict` - thisIsCamelCase, but no two capitals in a row (userId, not userID) - `kebab` - this-is-kebab-case - `pascal` - ThisIsPascalCase, also with acronyms (HTTPServer) - `pascal-strict` - ThisIsPascalCase, but no two capitals in a row (HttpServer, not HTTPServer) - `header` - This-Is-Header-Case - `snake` - this_is_snake_case The default convention is `const`. ## Configuration The configuration block is a naming convention name (see the "Rule Details" section for a list of recognized names). ```yaml version: "1.0" commands: lint: asyncapi: rules: schema-enum-naming: [warning, kebab] ``` ## Examples **Good**. With the default configuration. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: UserStatus: type: string enum: - ACTIVE - INACTIVE - BLOCKED ``` **Bad**. To use `UserStatus` properly, a consumer must always consult the documentation. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: UserStatus: type: string enum: - activeStatus - inactive_status - BLOCKED_STATUS ``` ## When Not to Use It If your API does not follow a specific naming convention for `enum` values in its schema objects, you may not need to use this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-enum-no-duplicates Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-enum-no-duplicates This rule ensures that the `enum` lists of schema objects contain no duplicate values. ## Rule Details Duplicate enum values carry no information, and usually indicate a typo or a merge mistake (for example, a value that was meant to be different). The `schema-enum-no-duplicates` rule checks the `enum` list of every schema of an AsyncAPI document, and reports `Duplicate enum value` once for every value that repeats an earlier value of the same list. Values of any type are compared, including numbers, objects and arrays. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 components: schemas: PetStatus: type: string enum: [AVAILABLE, ADOPTED, RETURNED] ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 components: schemas: PetStatus: type: string enum: [AVAILABLE, ADOPTED, AVAILABLE] ``` ## Automatic fixes When invoked with `--fix`, this rule removes the duplicate values, keeping the first occurrence of each value. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-enum-types Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-enum-types This rule ensures that the values listed in the `enum` of a schema are allowed by the schema's `type`. ## Rule Details An enum value whose type is not allowed by the schema can never be sent or received: a message containing it would fail validation. Such values usually come from a missing or wrong `type`, or from unquoted YAML values (e.g. `yes`, `1.0`) that are parsed as something other than strings. The `schema-enum-types` rule checks every schema of an AsyncAPI document that has an `enum` list, and reports `Enum value type is not in the schema type` when one of the values is not allowed by the schema's `type`: - strings require `string`, booleans require `boolean`, `null` requires `null`, arrays require `array` and objects require `object`, - integer values require `integer` or `number`, - other numeric values require `number`. A schema that has an `enum` but no `type` is reported as well, since none of its values is allowed by an explicit type. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 components: schemas: PetStatus: type: [string, "null"] enum: [AVAILABLE, ADOPTED, null] Rating: type: number enum: [1, 2.5, 5] ``` **Bad**. The values are integers, but the schema allows only strings. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 components: schemas: Priority: type: string enum: [1, 2, 3] ``` ## Automatic fixes When invoked with `--fix`, this rule sets the `type` of a reported schema to the type of its enum values, provided that all values have the same type and the schema has no `format`. In the "Bad" example above, the type of `Priority` is changed to `integer`. Schemas mixing values of different types are not fixed. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-example-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-example-defined This rule ensures that top-level schemas of an AsyncAPI document provide examples. ## Rule Details Examples make schemas easier to understand, and are used by documentation generators, mock servers and test tools to produce realistic messages. The `schema-example-defined` rule checks top-level schemas only; nested schemas (such as properties or array items) are not checked. Top-level schemas are: - the entries of `components/schemas` (references are skipped). The rule reports `Schema does not define examples` for these. - inline message payloads, i.e. payloads that are not references. The rule reports `Message payload schema does not define examples` for these. Referenced payloads are checked as component schemas. A schema counts as having examples when it has a non-empty `examples` list, when it has a `const` value, or when it is a boolean schema (`true` or `false`). In addition, a message whose `examples` list contains at least one example with a `payload` counts as providing examples for its inline payload. ## Configuration None. ## Examples **Good**. The component schema has its own examples, and the inline payload is illustrated by a message example. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted messages: petAdopted: payload: type: object properties: name: type: string examples: - payload: name: Rex components: schemas: PetStatus: type: string enum: [AVAILABLE, ADOPTED] examples: [AVAILABLE] ``` **Bad**. Neither the component schema nor the inline payload provide examples. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted messages: petAdopted: payload: type: object properties: name: type: string components: schemas: PetStatus: type: string enum: [AVAILABLE, ADOPTED] ``` ## When Not to Use It You may disable this rule if your documentation and tooling do not use examples. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-format-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-format-defined This rule ensures that numeric schemas specify the precision of their values with a `format`. ## Rule Details JSON Schema does not limit the size or the precision of numbers. Without a `format`, code generators and consumers written in strongly typed languages have to guess which native type to use, which often leads to overflows or to lost precision. The `schema-format-defined` rule checks every schema of an AsyncAPI document and reports: - `Schema with integer type does not define proper format` when a schema of type `integer` does not have the `int32` or `int64` format, - `Schema with number type does not define proper format` when a schema of type `number` does not have the `float` or `double` format. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Order Events version: 1.0.0 components: schemas: OrderPlaced: type: object properties: quantity: type: integer format: int32 total: type: number format: double ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Order Events version: 1.0.0 components: schemas: OrderPlaced: type: object properties: quantity: type: integer total: type: number format: decimal ``` ## When Not to Use It You may disable this rule if your messages are consumed only by clients written in dynamically typed languages, where the precision of numbers does not matter. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-format-deprecated Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-format-deprecated This rule reports the formats which the [OpenAPI format registry](https://spec.openapis.org/registry/format/) marks as deprecated from OpenAPI 3.1, and names what replaces them. ## Rule Details | Format | Use instead | | ------ | ----------- | | `byte` | `contentEncoding: base64` | | `base64url` | `contentEncoding: base64url` | | `binary` | `contentMediaType`, or the media type of the body or part without a schema | AsyncAPI schemas are a superset of JSON Schema draft 7, which has `contentEncoding` and `contentMediaType`, so the issues are warnings in AsyncAPI 2.x and 3.x documents. See [Formats](/docs/reference/cli/formats) for every format. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 components: schemas: Avatar: type: string contentEncoding: base64 ``` **Bad**. ```yaml asyncapi: 3.0.0 components: schemas: Avatar: type: string format: byte ``` ## When Not to Use It When the documents must stay readable by tools that only know the older formats. --- # schema-format-type-mismatch Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-format-type-mismatch This rule ensures that the `format` of a schema is consistent with its `type`. ## Rule Details Formats apply to values of specific types. A schema such as `type: integer, format: email` is contradictory: validators ignore the format, and code generators produce unexpected types. The `schema-format-type-mismatch` rule checks every schema of an AsyncAPI document whose `format` is in the [OpenAPI format registry](https://spec.openapis.org/registry/format/) or in `openapi.custom_formats` of the configuration file, and reports `Schema type and format do not match` when none of the schema's types is a JSON type of the format: - the integer formats (`int8` to `uint64`, `int32`, `int64`, `double-int`, `sf-integer`) need `integer` or `number`, and `int64` and `uint64` also allow `string`; - `float`, `double` and `sf-decimal` need `number`; - `decimal` and `decimal128` allow `string` and `number`, `unixtime` also `integer`; - the others (`date`, `date-time`, `uuid`, `email`, `http-date`, ...) need `string`. A schema with one of these formats but without a `type` is reported as well. Other formats are left to [schema-format-unknown](/docs/linter/asyncapi/rules/schema-format-unknown). The [Formats](/docs/reference/cli/formats) page lists every format. ## Configuration The configuration block is an object with a single optional boolean property, `fixTypeFromFormat`, which controls whether reported issues can be fixed automatically (see "Automatic fixes"). The default value is `true`. **Configuration Example**. The following configuration reports mismatches, but does not fix them. ```yaml version: "1.0" commands: lint: asyncapi: rules: schema-format-type-mismatch: - error - fixTypeFromFormat: false ``` ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: UserSignedUp: type: object properties: email: type: string format: email signed_up_at: type: string format: date-time ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: UserSignedUp: type: object properties: email: type: integer format: email signed_up_at: format: date-time ``` ## Automatic fixes When `fixTypeFromFormat` is `true` and the linter is invoked with the `--fix` option, this rule sets the `type` of every reported schema to the type the format applies to. In the "Bad" example above, both properties get `type: string`. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-format-unknown Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-format-unknown This rule reports a `format` of a AsyncAPI document which is neither in the [OpenAPI format registry](https://spec.openapis.org/registry/format/) nor declared in `openapi.custom_formats` of the [configuration file](/docs/reference/cli/configuration#openapicustom_formats). ## Rule Details Tools ignore formats they don't know, so a misspelled format (`date_time`, `uuid4`) silently stops being checked. Formats defined on purpose are declared in the configuration file: ```yaml openapi: custom_formats: - {name: currency-code, type: string} ``` The rule is off in the `recommended` ruleset and a warning in `strict` and `all`. See [Formats](/docs/reference/cli/formats) for the registry's formats. ## Configuration None. ## When Not to Use It When the documents use many formats of their own and listing them in the configuration file isn't worth it. --- # schema-keyword-support Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-keyword-support This rule reports schema keywords that the tools consuming the document are unlikely to support, and suggests the rewrite where one exists. ## Rule Details The AsyncAPI Schema Object is a superset of JSON Schema draft-07, yet schemas copied from OpenAPI 3.1 or from JSON Schema 2020-12 documents bring keywords of the later drafts along. AsyncAPI tools, draft-07 validators and most code generators ignore the keywords they don't know, so `unevaluatedProperties: false` stops rejecting anything, a `prefixItems` tuple turns into an untyped array and an `if`/`then` constraint silently disappears from the generated client. The rule checks each schema against one or more **targets** and reports, once per keyword, the first target that does not support it: | Target | Who it stands for | | ------ | ----------------- | | `draft4` | Validators stuck at draft-04, e.g. NJsonSchema's default dialect, older PHP and Python libraries | | `draft6` | Draft-06 validators | | `draft7` | Validators stopping at draft-07: gojsonschema, everit-org/json-schema, Ajv's default export; also the default schema dialect of AsyncAPI 2.x and 3.0 | | `draft2019-09` | Validators without draft 2020-12 | | `openapi30` | The Schema Object of OpenAPI 3.0 and the tools built for it, e.g. NSwag | | `codegen` | The keywords most code generators ignore or can't express in types (the default) | What each target lacks, and the suggested rewrite: | Keyword or form | Not supported by | Suggested rewrite | | --------------- | ---------------- | ----------------- | | `unevaluatedProperties` | drafts 4 to 7, `openapi30`, `codegen` | merge the `allOf` members into one schema and use `additionalProperties` | | `unevaluatedItems` | drafts 4 to 7 | `additionalItems` next to the array form of `items` | | `unevaluatedItems` | `openapi30`, `codegen` | none | | `prefixItems` | drafts 4 to 2019-09 | the array form of `items`, `additionalItems` for the rest | | `prefixItems` | `openapi30`, `codegen` | an object with named properties | | `$dynamicRef`, `$recursiveRef` | drafts 4 to 7, `openapi30`, `codegen` | `$ref` to the schema it resolves to | | `$dynamicRef` | `draft2019-09` | `$recursiveRef` | | `$dynamicAnchor`, `$recursiveAnchor` | drafts 4 to 7, `openapi30`, `codegen` | drop it once the reference to it is a `$ref` | | `$anchor` | drafts 4 to 7, `openapi30`, `codegen` | reference the schema by a JSON pointer | | `$vocabulary` | drafts 4 to 7, `openapi30`, `codegen` | drop it | | `$defs` | drafts 4 to 7 | `definitions` | | `$defs` | `openapi30` | move the schemas to `components/schemas` | | `dependentRequired` | drafts 4 to 7 | `dependencies` with an array of property names | | `dependentSchemas` | drafts 4 to 7 | `dependencies` with a schema | | `dependentRequired`, `dependentSchemas`, `dependencies` | `openapi30`, `codegen` | none | | `dependencies` | `draft2019-09` | `dependentRequired` or `dependentSchemas` | | `if` (with `then`/`else`) | `draft4`, `draft6`, `openapi30`, `codegen` | `oneOf` with one alternative per case | | `contentSchema`, `minContains`, `maxContains` | drafts 4 to 7, `openapi30`, `codegen` | none | | `contains` | `draft4`, `openapi30`, `codegen` | none | | `propertyNames` | `draft4`, `openapi30` | none | | `const` | `draft4`, `openapi30` | an `enum` with one value | | `patternProperties` | `openapi30` | `properties` for fixed names, `additionalProperties` for the rest | | `patternProperties` | `codegen` | `additionalProperties` | | `not` | `codegen` | none | | `additionalItems` | `openapi30` | none | | `$id`, `$schema`, `$comment`, `examples`, `contentEncoding`, `contentMediaType` | `openapi30` | drop `$id` and `$schema`; `description` for `$comment`; `example`; `format: byte` or `format: binary` | | `$id` | `draft4` | `id` | | a `type` array | `openapi30` | one `type` with `nullable: true`, or `anyOf` | | a `type` array of several non-null types | `codegen` | `oneOf` with one schema per type | | `type: null` | `openapi30` | `nullable: true` | | a numeric `exclusiveMinimum`/`exclusiveMaximum` | `draft4`, `openapi30` | `minimum`/`maximum` with the boolean form | | a boolean `exclusiveMinimum`/`exclusiveMaximum` | drafts 6 to 2019-09 | the bound as the value | | `nullable: true` | drafts 4 to 2019-09 | add `"null"` to `type` | | `$ref` with keywords that constrain the value | drafts 4 to 7, `openapi30`, `codegen` | wrap the reference in `allOf`; the siblings are ignored otherwise | Annotations a validator ignores without harm (`description`, `examples`, `readOnly`, `deprecated`, `$comment`, the content keywords) are reported only for `openapi30`, whose Schema Object allows no keywords besides its own and `x-` extensions. `then` and `else` are covered by the report on `if`. A `$ref` next to annotations only (`description`, `summary`, `example`, ...) is not reported. `apigenie convert --from jsonschema --to draft7|draft4|openapi30` performs most of these rewrites on a standalone JSON Schema document, including the `allOf` flattening that replaces `unevaluatedProperties`. ## Configuration The options are a target name, an array of target names, or an object with: - `targets`: a target name or an array of target names; `codegen` when omitted. - `allow`: keywords never reported, for the ones your tools are known to support. **Configuration Example 1**. Find the keywords AsyncAPI's draft-07 dialect doesn't have. ```yaml lint: asyncapi: rules: schema-keyword-support: - warning - draft7 ``` **Configuration Example 2**. Check against draft-07 validators and the code generators, accepting `not`. ```yaml lint: asyncapi: rules: schema-keyword-support: - warning - targets: [draft7, codegen] allow: [not] ``` ## Examples **Good**. A closed object that every target understands. ```yaml components: schemas: Pet: type: object required: [name] properties: name: type: string additionalProperties: false ``` **Bad**. `unevaluatedProperties` is not an AsyncAPI keyword, and draft-07 validators and most code generators ignore it, so they accept any extra property. ```yaml components: schemas: Pet: allOf: - $ref: '#/components/schemas/Animal' properties: name: type: string unevaluatedProperties: false ``` ## When Not to Use It When every consumer of the document is known to support JSON Schema 2020-12 in full, e.g. a validator such as Ajv 2020, networknt, santhosh-tekuri/jsonschema or Python's jsonschema, and no code is generated from it. ## Compatibility This rule is compatible with AsyncAPI 2.x and 3.x. --- # schema-no-additional-properties Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-no-additional-properties This rule ensures that object schemas explicitly disallow additional properties. ## Rule Details By default, JSON Schema allows objects to contain properties that are not listed in `properties`. Messages containing unexpected properties are then considered valid, which hides typos and makes it easy to leak data that was never meant to be published. The `schema-no-additional-properties` rule reports `Object schema does not disable additional properties` for every schema of type `object` that does not have `additionalProperties: false`. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: UserSignedUp: type: object properties: user_id: type: string additionalProperties: false ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: UserSignedUp: type: object properties: user_id: type: string ``` ## Automatic fixes When invoked with `--fix`, this rule sets `additionalProperties: false` on every reported schema, replacing any other value of `additionalProperties`. ## When Not to Use It You may disable this rule if your messages are designed to be extensible, or if consumers are expected to ignore unknown properties. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # schema-property-naming Source: https://apigenie.pl/docs/linter/asyncapi/rules/schema-property-naming This rule ensures that the names of schema properties adhere to a predefined naming convention. ## Rule Details Adhering to a consistent naming convention enhances the readability and maintainability of the API's structure, and makes messages easier to map to native data structures. The `schema-property-naming` rule checks the keys of the `properties` map of every schema of an AsyncAPI document. For every property whose name is not in the expected convention, it reports `Schema property '<name>' is not in the '<convention>' convention`. The recognised naming conventions are: - `const` - THIS_IS_CONST_CASE - `camel` - thisIsCamelCase, also with acronyms (userID) - `camel-strict` - thisIsCamelCase, but no two capitals in a row (userId, not userID) - `kebab` - this-is-kebab-case - `pascal` - ThisIsPascalCase, also with acronyms (HTTPServer) - `pascal-strict` - ThisIsPascalCase, but no two capitals in a row (HttpServer, not HTTPServer) - `header` - This-Is-Header-Case - `snake` - this_is_snake_case The default convention is `snake`. ## Configuration The configuration block is a naming convention name (see the "Rule Details" section for a list of recognized names). ```yaml version: "1.0" commands: lint: asyncapi: rules: schema-property-naming: [error, camel] ``` ## Examples **Good**. With the default configuration. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: UserSignedUp: type: object properties: user_id: type: string display_name: type: string ``` **Bad**. With the default configuration. ```yaml asyncapi: 3.0.0 info: title: User Events version: 1.0.0 components: schemas: UserSignedUp: type: object properties: userId: type: string DisplayName: type: string ``` ## When Not to Use It If your API does not follow a specific naming convention for schema properties, you may not need to use this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # server-hosts Source: https://apigenie.pl/docs/linter/asyncapi/rules/server-hosts This rule checks that the servers of an AsyncAPI document have well-formed hosts and protocols, and that they use secure protocols. ## Rule Details In AsyncAPI 3.x, a server is described by separate `host`, `protocol` and `pathname` fields. A common mistake, especially when migrating from AsyncAPI 2.x (where servers had a single `url`), is to put a complete URL into `host`. The `server-hosts` rule checks every server in the root `servers` map and reports: - `Server host must not contain a scheme or a path` when `host` contains `://` or `/`. The scheme belongs to `protocol`, and the path to `pathname`. - `Server host is not lower case` when `host` contains upper-case letters. Host names are case-insensitive, and lower case is their canonical form. - `Server protocol is not lower case` when `protocol` contains upper-case letters. - `Server uses an insecure protocol (<protocol>)` when the protocol is `http` or `ws`, unless the host is `localhost`, `127.0.0.1` or `[::1]` (with or without a port). Use `https` or `wss` instead. Server variables (e.g. `{environment}` in `{environment}.your-company.com`) are not required to be lower case. ## Configuration The configuration block is an object with a single optional boolean property, `upgradeInsecure`. When it is `true`, the automatic fix also replaces insecure protocols with their secure variants (see "Automatic fixes"). The default value is `false`. **Configuration Example**. ```yaml version: "1.0" commands: lint: asyncapi: rules: server-hosts: - warning - upgradeInsecure: true ``` ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 servers: production: host: broker.your-company.com:9092 protocol: kafka staging: host: "{environment}.your-company.com" protocol: wss pathname: /events variables: environment: default: staging local: host: localhost:8080 protocol: ws ``` **Bad**. The first server has an upper-case host and protocol, the second one puts a complete URL into `host`, and the third one uses an insecure protocol. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 servers: production: host: Broker.Your-Company.com:9092 protocol: Kafka staging: host: wss://staging.your-company.com/events protocol: wss public: host: events.your-company.com protocol: ws ``` ## Automatic fixes When invoked with `--fix`, this rule converts hosts and protocols to lower case, leaving server variables untouched. Hosts that contain a scheme or a path are not changed, as the rule cannot tell how to split them. If `upgradeInsecure` is `true`, the fix also replaces `http` with `https`, and `ws` with `wss`, for all servers other than `localhost`, `127.0.0.1` and `[::1]`. Make sure that the servers actually support the secure protocol before enabling this option. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # servers-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/servers-defined This rule checks whether at least one server is defined in an AsyncAPI document. ## Rule Details Servers tell consumers where the application's channels are available: which broker to connect to, and with which protocol. Without them, the document describes messages but not how to reach them. The `servers-defined` rule reports `No servers defined` when the root `servers` map is missing or empty. Servers defined only under `components/servers` do not count. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 servers: production: host: broker.your-company.com:9092 protocol: kafka ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted ``` ## When Not to Use It You may disable this rule for documents that describe messages shared by several applications, and whose servers are defined elsewhere. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # spell-check Source: https://apigenie.pl/docs/linter/asyncapi/rules/spell-check This rule checks the spelling of the texts written for humans: titles, summaries and descriptions. ## Rule Details The rule reports every misspelled word, with up to three suggested corrections, for example `Misspelled word 'Recieve' in summary (did you mean 'Receive', 'Relieve', 'Reverie'?)`. The issue points at the object whose text contains the word. The rule checks: - the title and description of the info object, - the title, summary and description of servers, channels, operations, operation traits, messages and message traits, - the summary and description of message examples, - the title and description of schemas, - the description of server variables, parameters, operation reply addresses, correlation IDs, tags, external documentation and security schemes. Descriptions are CommonMark: the rule checks their prose, and skips code spans, code blocks, raw HTML and link destinations. Titles and summaries are plain text, in which `backticked` spans are skipped, too. The rule also skips the tokens which are not plain words: - URLs, e-mail addresses, paths, and file and domain names (`asyncapi.yaml`, `example.com`), - identifiers: `snake_case`, `camelCase` and names with inner capitals (`AsyncAPI`, `PetStore`), - acronyms, also in the plural (`MQTT`, `APIs`, `ID's`), - tokens with digits (`v2`, `401`) and single letters. The spell checker is a C++ port of [Hunspell](https://hunspell.github.io). By default it uses the built-in English (US) dictionary from [SCOWL](http://wordlist.aspell.net) (as shipped with LibreOffice), extended with words common in API descriptions, such as `webhook`, `endpoint`, `namespace`, `nullable` and `json`. Any Hunspell dictionary can be used instead. The rule is part of the `strict` and `all` rulesets. ## Configuration This rule accepts an object with the following options: - `words`: extra words to accept, for example product names. A word is accepted as given, capitalized and in capitals: `petstore` accepts `petstore`, `Petstore` and `PETSTORE`. - `dictionary` and `affix`: paths of a Hunspell dictionary (`.dic`) and its affix file (`.aff`) to use instead of the built-in dictionary. They go together. Dictionaries in UTF-8, ISO-8859-1, ISO-8859-2 and ISO-8859-15 are supported. - `suggestions`: how many corrections to suggest for a word, from 0 to 10 (default 3). Suggestions are the costly part of the rule; on a very large document with many unknown words, `0` makes it about ten times faster. **Configuration Example** ```yaml version: "1.0" commands: lint: asyncapi: rules: spell-check: - warning - words: [petstore] dictionary: path/to/en_GB.dic affix: path/to/en_GB.aff ``` ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" summary: Receive adoption events description: Updates the shelter inventory when a pet is adopted. ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted operations: onPetAdopted: action: receive channel: $ref: "#/channels/petAdopted" summary: Recieve adoption events description: Updates the shelter inventroy when a pet is adopted. ``` ## When Not to Use It You may disable this rule if your documentation is written in a language you have no dictionary for. To silence it for a single object and its children, add `x-lint-disable: [spell-check]` to the object. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # standard-media-types Source: https://apigenie.pl/docs/linter/asyncapi/rules/standard-media-types This rule checks whether messages use only standard media types. ## Rule Details Using non-standard media types can lead to interoperability issues: consumers and tools may not know how to decode the messages. The `standard-media-types` rule checks the following fields of an AsyncAPI document: - the root `defaultContentType`, - the `contentType` of every message, - the `contentType` of every message trait. For every value that is not a recognized media type, the rule reports `Non-standard media type is used (<value>)`. Media type parameters (such as `; charset=utf-8`) are allowed and ignored. A media type is recognized when it has the `type/subtype` form and its subtype is one of the well-known subtypes, such as `json`, `xml`, `plain`, `octet-stream`, `problem+json`, `vnd.api+json` or `x-www-form-urlencoded`. Note that some media types commonly used with message brokers, for example `application/vnd.apache.avro+json`, `application/x-protobuf` or `application/cloudevents+json`, are not currently recognized. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 defaultContentType: application/json channels: petAdopted: address: pets.adopted messages: petAdopted: contentType: application/json; charset=utf-8 payload: type: object ``` **Bad**. `json` is not a media type, and `application/pet` uses a subtype that is not registered. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 defaultContentType: json channels: petAdopted: address: pets.adopted messages: petAdopted: contentType: application/pet payload: type: object ``` ## When Not to Use It You may disable this rule if your messages use vendor-specific media types, or binary serialization formats that the rule does not recognize. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # tag-description-defined Source: https://apigenie.pl/docs/linter/asyncapi/rules/tag-description-defined This rule checks whether every tag of an AsyncAPI document has a meaningful description. ## Rule Details Documentation generators usually render tags as section headers, and their descriptions as the introduction of each section. A tag without a description leaves readers guessing what the grouped operations have in common. The `tag-description-defined` rule checks every Tag object of the document, wherever it is defined: in `info.tags`, in the tags of servers, channels, operations, operation traits, messages and message traits, and in `components/tags`. It reports `Tag '<name>' has no meaningful description defined` for every tag whose description: - is missing, empty or blank, - is shorter than the configured minimal length, - is a placeholder: `TODO`, `FIXME` or `TBD` (case-insensitive). ## Configuration The configuration block is a positive integer: the minimal length of a description, in characters. The default value is `10`. **Configuration Example**. ```yaml version: "1.0" commands: lint: asyncapi: rules: tag-description-defined: [warning, 20] ``` ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 tags: - name: adoption description: Events related to adopting and returning pets. ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 tags: - name: adoption - name: inventory description: TBD ``` ## When Not to Use It You may disable this rule if the tag names are self-explanatory. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # tags-sorted Source: https://apigenie.pl/docs/linter/asyncapi/rules/tags-sorted This rule checks whether the tags listed in `info.tags` are sorted by their names. ## Rule Details A sorted list of tags is easier to scan, and makes it obvious where a new tag should be added. The `tags-sorted` rule checks the `info.tags` list of an AsyncAPI document, and reports `Tags are not sorted: <name>` for every tag whose name sorts before the name of the preceding tag. Tags of operations, messages and other objects are not checked. Names are compared character by character (by their byte values), so the comparison is case-sensitive: all upper-case letters sort before lower-case ones. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 tags: - name: adoption - name: inventory - name: medical ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 tags: - name: inventory - name: adoption - name: medical ``` ## Automatic fixes When invoked with `--fix`, this rule sorts the `info.tags` list by tag names, keeping the content of every tag unchanged. ## When Not to Use It You may disable this rule if the order of tags is meaningful, for example if documentation generators should render sections in a specific order. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # tags-unique Source: https://apigenie.pl/docs/linter/asyncapi/rules/tags-unique This rule checks whether the names of the tags listed in `info.tags` are unique. ## Rule Details Duplicate tags are confusing: documentation generators may render the same section twice, or merge the tags in an unpredictable way. The `tags-unique` rule checks the `info.tags` list of an AsyncAPI document, and reports `Tag name is not unique (<name>)` for every tag whose name repeats the name of another tag in the list. Names are compared case-insensitively, so `Pets` and `pets` are duplicates. Tags of operations, messages and other objects are not checked. ## Configuration None. ## Examples **Good**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 tags: - name: adoption description: Events related to adopting and returning pets. - name: inventory description: Events related to the shelter inventory. ``` **Bad**. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 tags: - name: adoption description: Events related to adopting and returning pets. - name: Adoption ``` ## Automatic fixes When invoked with `--fix`, this rule removes redundant duplicates: - a duplicate that contains only a `name` is removed; if the earlier tag contains only a `name` and the later one has more content, the earlier tag is removed instead, - a duplicate whose content (apart from the `name`) is identical to the earlier tag is removed. Duplicates that differ in their descriptions or other properties are reported but not fixed, as the rule cannot tell which one is correct. In the "Bad" example above, the second tag is removed. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # unused-shared-component Source: https://apigenie.pl/docs/linter/asyncapi/rules/unused-shared-component This rule checks whether every shared component of an AsyncAPI document is actually used. ## Rule Details Components that are never referenced make the document longer and harder to maintain, and suggest that something is missing (for example, a message that was defined but never attached to a channel). The `unused-shared-component` rule checks every entry of every component category: `schemas`, `servers`, `channels`, `operations`, `messages`, `securitySchemes`, `serverVariables`, `parameters`, `correlationIds`, `replies`, `replyAddresses`, `externalDocs`, `tags`, `operationTraits`, `messageTraits`, `serverBindings`, `channelBindings`, `operationBindings` and `messageBindings`. It reports `Unused shared component` for every component that is not reachable from the rest of the document. A component is reachable when a local `$ref` of the form `#/components/<category>/<name>` (possibly pointing deeper into the component, e.g. `#/components/schemas/Pet/properties/name`) appears anywhere outside `components`, or inside another reachable component. References are followed transitively, so a schema used only by an unused message is unused as well. References nested in untyped values, such as bindings or payloads in other schema formats, count too. A component that has this rule disabled (see below) is always considered used, and so is everything it references. ## Configuration None. ## Examples **Good**. The message is used by the channel, and the schema by the message. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted messages: petAdopted: $ref: "#/components/messages/PetAdopted" components: messages: PetAdopted: payload: $ref: "#/components/schemas/Pet" schemas: Pet: type: object ``` **Bad**. Nothing references the `PetReturned` message, so both the message and the `Shelter` schema it uses are unused. ```yaml asyncapi: 3.0.0 info: title: Pet Events version: 1.0.0 channels: petAdopted: address: pets.adopted messages: petAdopted: $ref: "#/components/messages/PetAdopted" components: messages: PetAdopted: payload: $ref: "#/components/schemas/Pet" PetReturned: payload: $ref: "#/components/schemas/Shelter" schemas: Pet: type: object Shelter: type: object ``` To keep a component that is meant to be referenced from other documents, disable the rule on it: ```yaml components: schemas: Shelter: type: object x-lint-disable: [unused-shared-component] ``` ## Automatic fixes When invoked with `--fix`, this rule removes all unused components. When every entry of a component category is unused, the whole category map is removed, so that no empty map is left behind. ## When Not to Use It You may disable this rule for documents that serve as libraries of shared components, referenced from other documents. ## Compatibility This rule is compatible with AsyncAPI 3.x documents. AsyncAPI 2.x documents are rejected by the reader and are not linted. --- # audience-defined Source: https://apigenie.pl/docs/linter/swagger/rules/audience-defined This rule checks whether the intended audience of an API is declared in the `x-audience` extension of the info object of a Swagger 2.0 document. ## Rule Details Knowing who an API is built for helps to decide how strict its review should be, how carefully breaking changes must be handled, and where the API can be published. Swagger 2.0 does not provide a field for this information. The `audience-defined` rule fills this gap. It requires the `x-audience` extension to be present in the info object, and its value to be one of the following strings: - `internal-team` - the API is used only by the team that owns it, - `internal-company` - the API is used by other teams in the same company, - `partner` - the API is shared with selected partners, - `public` - the API is publicly available. The rule reports `API audience (x-audience) is missing` when the extension is absent, and `API audience (x-audience) has invalid value (...)` when its value is not one of the strings listed above. For values that are not strings at all the parentheses are empty. ## Configuration None. ## Examples **Good**. The audience of the API is clearly stated. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 x-audience: internal-company paths: {} ``` **Bad**. The audience is not declared. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: {} ``` **Bad**. The audience has a value that is not recognized. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 x-audience: everyone paths: {} ``` ## When Not to Use It You may disable this rule if your organization does not classify its APIs by their audience. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `audience-defined` rules. --- # contact-defined Source: https://apigenie.pl/docs/linter/swagger/rules/contact-defined This rule checks whether complete contact information is present in a Swagger 2.0 document. ## Rule Details The `contact-defined` rule ensures that the `info.contact` object is defined and that its `name`, `email` and `url` properties are all present and not empty. Contact information tells consumers of the API whom to ask for help, report problems to, or notify about their use of the API. The rule reports: - `Contact object is missing` when there is no `info.contact` object, - `Contact name is missing or empty` when `name` is missing or empty, - `Contact email is missing` when `email` is missing or empty, - `Contact URL is missing` when `url` is missing or empty. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 contact: name: Pet Platform Team email: pets@your-company.com url: https://your-company.com/teams/pets paths: {} ``` **Bad**. The contact object is present, but it is not possible to reach the team by email. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 contact: name: Pet Platform Team url: https://your-company.com/teams/pets paths: {} ``` ## When Not to Use It You may skip contact information for APIs that are used only by the team that owns them. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `contact-defined` rules. --- # host-defined Source: https://apigenie.pl/docs/linter/swagger/rules/host-defined This rule checks whether a Swagger 2.0 document declares the host that serves the API. ## Rule Details Swagger 2.0 describes where an API is available with three root properties: `host` (the name or IP address, optionally with a port), `basePath` and `schemes`. When `host` is missing, tools fall back to the host that served the document, which is rarely what consumers want: generated clients and API consoles cannot reach the API without extra configuration. The `host-defined` rule reports `No host defined` when the root `host` property is missing or empty. A `basePath` or `schemes` alone do not satisfy the rule. This is the Swagger 2.0 counterpart of the OpenAPI `servers-defined` rule. ## Configuration None. ## Examples **Good**. The host of the API is declared. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 host: api.example.com basePath: /v1 schemes: [https] paths: {} ``` **Bad**. Only the base path is declared. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 basePath: /v1 paths: {} ``` ## When Not to Use It You may disable this rule for documents that are served by the API itself and are meant to be used against the host they are loaded from. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has the `servers-defined` rule. --- # id-defined Source: https://apigenie.pl/docs/linter/swagger/rules/id-defined This rule checks whether the info object of a Swagger 2.0 document has an identifier extension (`x-id` by default), and whether its value has the expected format (a UUID by default). ## Rule Details A stable identifier makes it possible to track an API across renames, new versions and moved files. Swagger 2.0 does not provide an identifier field for an API, so the rule requires an `info` extension to hold it. The rule reports: - `API identifier (x-id) is missing` when the extension is absent, - `API identifier (x-id) has invalid format` when its value is not a string, or does not fully match the expected pattern. The name in parentheses is the configured attribute. ## Configuration The rule accepts an object with two options. When options are given, both are required: - `attribute`: the name of the extension that holds the identifier. It must not be empty and must start with `x-`. Default: `x-id`. - `pattern`: the expected format of the identifier. `uuid` (the default) accepts UUIDs in the `8-4-4-4-12` hexadecimal form, any other value is an ECMAScript regular expression that must match the whole identifier. **Configuration Example**. The rule looks for the `info.x-ident` extension, whose value must be one or more lower case letters. ```yaml version: "1.0" commands: lint: swagger: rules: id-defined: - error - attribute: x-ident pattern: "[a-z]+" ``` ## Examples **Good**. The API can be tracked whatever happens to its title or file name. ```yaml swagger: '2.0' info: title: Payments version: 1.2.8 x-id: 123e4567-e89b-12d3-a456-426614174000 paths: {} ``` **Bad**. The API has no identifier. ```yaml swagger: '2.0' info: title: Payments version: 1.2.4 paths: {} ``` **Bad**. The identifier is not a UUID. ```yaml swagger: '2.0' info: title: Payments version: 1.2.8 x-id: 237849128738 paths: {} ``` ## Automatic fixes The rule is auto-fixable only while `pattern` is `uuid`. Then `--fix` sets the configured attribute of the info object to a newly generated UUID v4, both when the attribute is missing and when its value is invalid. With a custom pattern the issues are reported but not fixed. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `id-defined` rules. --- # info-defined Source: https://apigenie.pl/docs/linter/swagger/rules/info-defined This rule checks whether the info object of a Swagger 2.0 document provides a description and a version. ## Rule Details The info object is the first thing readers of an API see. Besides the mandatory title, it should explain what the application does and which version of the API is being described. The `info-defined` rule reports: - `Info description is missing or empty` when `info.description` is missing or empty, - `Info version is empty` when `info.version` is an empty string. The title is required by the Swagger specification itself and is not checked by this rule. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 description: Manages the pets of a shelter and their adoptions. paths: {} ``` **Bad**. The document does not explain what the API does, and its version is empty. ```yaml swagger: '2.0' info: title: Pet Store version: '' paths: {} ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `info-defined` rules. --- # license-defined Source: https://apigenie.pl/docs/linter/swagger/rules/license-defined This rule checks whether the licensing information is present in a Swagger 2.0 document. ## Rule Details The `license-defined` rule ensures that the `info.license` object is defined, and that both its `name` and its `url` are provided. The license specifies the terms under which the API can be used, and helps consumers of the API to understand any restrictions that apply. The rule reports: - `License object is missing` when there is no `info.license` object, - `License name is empty` when `name` is empty, - `License URL is missing or empty` when `url` is missing or empty. Swagger 2.0 license objects have no SPDX `identifier` field (it was added in OpenAPI 3.1), so the rule always requires a URL. ## Configuration None. ## Examples **Good**. The document links to its license, which makes it easier to understand how the API can be used. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 license: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 paths: {} ``` **Bad**. The license has a name, but no link to its terms. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 license: name: Proprietary paths: {} ``` ## When Not to Use It You may skip licensing information for team-level APIs. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `license-defined` rules. --- # media-types-defined Source: https://apigenie.pl/docs/linter/swagger/rules/media-types-defined This rule checks whether the operations of a Swagger 2.0 document that send or receive a body declare its media types. ## Rule Details Swagger 2.0 declares the media types of request bodies with `consumes`, and those of response bodies with `produces`. Both can be set on an operation or, as defaults for all operations, on the root object. Without them, clients and code generators have to guess the format of the payloads. The `media-types-defined` rule reports: - `Operation accepts a request body but defines no consumes` when an operation has a `body` or `formData` parameter, either its own or one inherited from its path item (references to `#/parameters` are resolved), and its effective `consumes` list is empty, - `Operation returns a response body but defines no produces` when any response of an operation (references to `#/responses` are resolved) has a `schema`, and its effective `produces` list is empty. An operation's own `consumes` or `produces` replaces the root one, as in the specification, so an operation that sets `produces: []` has no response media types even when the root object declares some. ## Configuration None. ## Examples **Good**. The root object declares the default media types. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 consumes: [application/json] produces: [application/json] paths: /pets: post: parameters: - name: pet in: body required: true schema: $ref: '#/definitions/Pet' responses: '201': description: The created pet. schema: $ref: '#/definitions/Pet' definitions: Pet: type: object properties: name: type: string ``` **Bad**. The operation sends and receives a body, but no media types are declared. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: post: parameters: - name: pet in: body required: true schema: $ref: '#/definitions/Pet' responses: '201': description: The created pet. schema: $ref: '#/definitions/Pet' definitions: Pet: type: object properties: name: type: string ``` ## When Not to Use It You may disable this rule if all consumers of your API assume a single media type, such as `application/json`, without it being declared. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents declare media types as the keys of `content` maps, so the OpenAPI linter has no counterpart of this rule. --- # no-content-response-defined Source: https://apigenie.pl/docs/linter/swagger/rules/no-content-response-defined This rule checks whether `204 No Content` responses of a Swagger 2.0 document declare a body. ## Rule Details By definition a `204` response carries no content. In Swagger 2.0 the body of a response is described by its `schema` property, so a `204` response with a `schema` contradicts its status code and misleads consumers and code generators. The `no-content-response-defined` rule reports `204 response may not have a body defined` for every `204` response of an operation that has a `schema`. Response references (`$ref`) are not followed. Headers are allowed. ## Configuration None. ## Examples **Good**. The `204` response has no schema. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{petId}: delete: parameters: - name: petId in: path required: true type: string responses: '204': description: The pet was deleted. ``` **Bad**. The `204` response declares a schema. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{petId}: delete: parameters: - name: petId in: path required: true type: string responses: '204': description: The pet was deleted. schema: type: object ``` ## Automatic fixes When invoked with `--fix`, this rule removes the `schema` property from `204` responses. The rest of the response, such as its description and headers, is kept. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # operation-description-defined Source: https://apigenie.pl/docs/linter/swagger/rules/operation-description-defined This rule checks whether every operation of a Swagger 2.0 document has a meaningful description. ## Rule Details A description tells consumers what an operation does, when to call it and what side effects it has. Generated documentation and clients show it next to the operation. The `operation-description-defined` rule reports `Operation has no meaningful description` for every operation whose `description` is missing or shorter than the configured minimal length (10 characters by default). The `summary` property does not count as a description. ## Configuration A single positive integer: the minimal number of characters of a description. The default is 10. ```yaml commands: lint: swagger: rules: operation-description-defined: [warning, 20] ``` ## Examples **Good**. The operation has a meaningful description. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: description: Lists every pet of the store, ordered by name. responses: '200': description: A list of pets. ``` **Bad**. The first operation has no description, the second one has a description that is too short. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: summary: List pets responses: '200': description: A list of pets. post: description: Adds responses: '201': description: The pet was added. ``` ## When Not to Use It You may disable this rule when operations are documented outside of the Swagger document. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # operation-id-defined Source: https://apigenie.pl/docs/linter/swagger/rules/operation-id-defined This rule checks whether every operation of a Swagger 2.0 document has an operation ID. ## Rule Details The `operationId` property identifies an operation. Code generators use it to name the methods of generated clients and servers, and documentation tools use it to build links to operations. The `operation-id-defined` rule reports `Operation ID is not defined` for every operation whose `operationId` is missing or empty. ## Configuration None. ## Examples **Good**. The operation has an operation ID. ```yaml swagger: '2.0' info: title: Reservations version: 1.0.0 paths: /reservations/{reservationId}: delete: operationId: cancelReservation parameters: - name: reservationId in: path required: true type: string responses: '204': description: The reservation was cancelled. ``` **Bad**. The operation has no operation ID. ```yaml swagger: '2.0' info: title: Reservations version: 1.0.0 paths: /reservations/{reservationId}: delete: parameters: - name: reservationId in: path required: true type: string responses: '204': description: The reservation was cancelled. ``` ## Automatic fixes When invoked with `--fix`, this rule sets `operationId` to an ID generated from the HTTP method and the path. Each literal path segment is appended in PascalCase, and each path parameter is appended as `By` followed by its name in PascalCase: `GET /users/{userId}` becomes `getUsersByUserId`, and `GET /` becomes `getRoot`. IDs already used in the document are reserved, and a generated ID that collides with a reserved one gets a numeric suffix starting at 2 (`getUsers2`). ## When Not to Use It You may disable this rule when no tooling relies on operation IDs. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # operation-id-unique Source: https://apigenie.pl/docs/linter/swagger/rules/operation-id-unique This rule checks whether the operation IDs of a Swagger 2.0 document are unique. ## Rule Details The Swagger 2.0 specification requires the `operationId` to be unique among all operations of an API. Duplicate IDs break code generators, which name methods after them, and make links to operations ambiguous. The `operation-id-unique` rule reports `Operation ID is not unique <id>` for every operation that repeats an ID already used by an earlier operation, in document order. The first occurrence is not reported. Missing and empty IDs are ignored; the `operation-id-defined` rule reports them. ## Configuration None. ## Examples **Good**. Each operation has its own ID. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: /users: get: operationId: getUsers responses: '200': description: A list of users. /users/{id}: get: operationId: getUserById parameters: - name: id in: path required: true type: string responses: '200': description: A single user. ``` **Bad**. Two operations share the same ID. ```yaml swagger: '2.0' info: title: Downloads version: 1.0.0 paths: /downloads/{id}: parameters: - name: id in: path required: true type: string patch: operationId: cancelDownload responses: '200': description: The download was updated. delete: operationId: cancelDownload responses: '204': description: The download was cancelled. ``` ## Automatic fixes When invoked with `--fix`, this rule keeps the first occurrence of an ID and replaces every later duplicate with an ID generated the same way as by the `operation-id-defined` rule, from the HTTP method and the path (for example `getPetsById` for `GET /pets/{id}`). IDs already used in the document are reserved, and collisions get a numeric suffix starting at 2. ## When Not to Use It We recommend to always enable this rule, since duplicate operation IDs violate the Swagger 2.0 specification. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # operation-rate-limiting-defined Source: https://apigenie.pl/docs/linter/swagger/rules/operation-rate-limiting-defined This rule checks whether the responses of Swagger 2.0 operations declare rate limiting headers. ## Rule Details When an API limits the rate of requests, declaring the rate limiting headers lets consumers pace their requests and recover from throttling. The `operation-rate-limiting-defined` rule checks the responses of every operation: - a response whose status code starts with `2` or `4` must declare a `RateLimit-Limit`, `X-RateLimit-Limit` or `X-Rate-Limit-Limit` header, otherwise the rule reports `Missing rate limiting header`; - a `429` or `503` response must declare a `Retry-After` header, otherwise the rule reports `Missing Retry-After header`. A `429` response is therefore checked for both headers. Header names are compared case-sensitively. When a response is a `$ref` to a shared response under the root `responses`, the headers of the referenced response are used. The `default` response and other status codes are not checked. ## Configuration None. ## Examples **Good**. The success response declares a rate limit header, and the shared `429` response declares both `Retry-After` and a rate limit header. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: responses: '200': description: A list of pets. headers: X-RateLimit-Limit: description: The number of requests allowed per hour. type: integer '429': $ref: '#/responses/TooManyRequests' responses: TooManyRequests: description: Too many requests. headers: Retry-After: description: The number of seconds to wait before retrying. type: integer RateLimit-Limit: description: The number of requests allowed per hour. type: integer ``` **Bad**. The `200` response has no rate limit header and the `503` response has no `Retry-After` header. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: responses: '200': description: A list of pets. '503': description: The service is unavailable. ``` ## When Not to Use It You may disable this rule if your API does not impose any rate limits. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # operation-security-defined Source: https://apigenie.pl/docs/linter/swagger/rules/operation-security-defined This rule checks whether every operation of a Swagger 2.0 document is covered by a security requirement. ## Rule Details Swagger 2.0 declares security schemes under `securityDefinitions` and applies them with `security` requirements, either at the root of the document (for every operation) or on individual operations. An operation without any requirement is documented as publicly accessible, which is often an oversight. When the root `security` list is not empty, every operation is covered and the rule reports nothing. Otherwise the `operation-security-defined` rule reports `Security requirement is not defined` for every operation whose own `security` list is missing or empty. An explicit `security: []` on an operation is reported as well. ## Configuration None. ## Examples **Good**. The root security requirement applies to every operation. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 securityDefinitions: apiKey: type: apiKey name: X-API-Key in: header security: - apiKey: [] paths: /pets: get: responses: '200': description: A list of pets. ``` **Good**. Each operation declares its own security requirement. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 securityDefinitions: basic: type: basic paths: /pets: get: security: - basic: [] responses: '200': description: A list of pets. ``` **Bad**. There is no root security requirement and the `post` operation has none of its own. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 securityDefinitions: basic: type: basic paths: /pets: get: security: - basic: [] responses: '200': description: A list of pets. post: responses: '201': description: The pet was added. ``` ## When Not to Use It You may disable this rule for public APIs, or disable it on the individual operations that are intentionally public. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # operation-success-response Source: https://apigenie.pl/docs/linter/swagger/rules/operation-success-response This rule checks whether every operation of a Swagger 2.0 document declares at least one success response. ## Rule Details Consumers and code generators rely on the success response of an operation to know what a successful call returns. An operation that only documents errors leaves that unspecified. The `operation-success-response` rule reports `Operation must have at least one success response` for every operation whose responses contain no status code starting with `2` or `3` and no `default` response. ## Configuration None. ## Examples **Good**. The operation declares a `200` response. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: responses: '200': description: A list of pets. '500': description: Internal error. ``` **Bad**. The operation only declares error responses. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: responses: '400': description: Invalid request. '500': description: Internal error. ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # operation-tags-defined Source: https://apigenie.pl/docs/linter/swagger/rules/operation-tags-defined This rule checks whether every operation of a Swagger 2.0 document has at least one tag. ## Rule Details Tags group operations. Documentation tools use them to build sections, and code generators use them to split generated clients into classes or modules. Untagged operations usually end up in a catch-all group. The `operation-tags-defined` rule reports `Operation has no tags defined` for every operation whose `tags` list is missing or empty. ## Configuration None. ## Examples **Good**. The operation is tagged. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 tags: - name: pets description: Operations on pets. paths: /pets: get: tags: [pets] responses: '200': description: A list of pets. ``` **Bad**. The first operation has no tags, the second has an empty list. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: responses: '200': description: A list of pets. post: tags: [] responses: '201': description: The pet was added. ``` ## When Not to Use It You may disable this rule for small APIs where grouping operations brings no value. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # parameter-description-defined Source: https://apigenie.pl/docs/linter/swagger/rules/parameter-description-defined This rule checks whether every parameter of a Swagger 2.0 document has a meaningful description. ## Rule Details A parameter's name rarely tells everything about its meaning, accepted values and effect. A description fills that gap in generated documentation and clients. The `parameter-description-defined` rule checks parameters of operations, of path items and the shared parameters under the root `parameters`, and reports `Parameter '<name>' has no meaningful description` when the `description`: - is missing or empty, - is shorter than the configured minimal length (10 characters by default), - consists of whitespace only, - is `TODO`, `FIXME` or `TBD` (case-insensitive). Parameter references (`$ref`) are skipped; the referenced shared parameter is checked where it is defined. ## Configuration A single positive integer: the minimal number of characters of a description. The default is 10. ```yaml commands: lint: swagger: rules: parameter-description-defined: [warning, 20] ``` ## Examples **Good**. Every parameter has a meaningful description. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: parameters: - $ref: '#/parameters/limit' responses: '200': description: A list of pets. parameters: limit: name: limit in: query type: integer description: The maximum number of pets to return. ``` **Bad**. The path parameter has no description and the query parameter has a description that is too short. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{id}: parameters: - name: id in: path required: true type: string get: parameters: - name: fields in: query type: string description: Fields responses: '200': description: A single pet. ``` ## When Not to Use It You may disable this rule when parameters are self-explanatory or documented outside of the Swagger document. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # parameter-naming Source: https://apigenie.pl/docs/linter/swagger/rules/parameter-naming This rule checks whether parameter names of a Swagger 2.0 document follow a naming convention. ## Rule Details Consistent parameter names make an API easier to learn and use. Different parameter locations usually follow different conventions: HTTP headers are traditionally written in `Header-Case`, while query parameters often use `kebab-case` or `snake_case`. The `parameter-naming` rule checks parameters of operations, of path items and the shared parameters under the root `parameters`, converts each name to the convention configured for its location (`path`, `query`, `header` or `formData`) and reports `Parameter should be named <converted name>` when the converted name differs from the actual one. Body parameters are skipped, since their names do not appear in requests. Parameter references (`$ref`) are skipped; the referenced shared parameter is checked where it is defined. Without options, query parameters must use `kebab`, header parameters `header` and path and form data parameters `snake`. ## Configuration Supported conventions are `camel`, `camel-strict`, `const`, `pascal`, `pascal-strict`, `snake`, `kebab` and `header` (the strict forms reject two capitals in a row, e.g. `userID`). The options are either a string or an object: - a string sets the convention for parameters in every location, including query and header parameters; - an object must have a `default` property with the convention for every location, and may override it for individual locations with the `path`, `query`, `header` and `formData` properties. Locations that are not listed use the `default` convention. ```yaml commands: lint: swagger: rules: parameter-naming: - warning - default: snake query: camel header: header ``` ## Examples **Good**. Parameters follow the default conventions. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{pet_id}: get: parameters: - name: pet_id in: path required: true type: string - name: page-size in: query type: integer - name: X-Request-Id in: header type: string responses: '200': description: A single pet. ``` **Bad**. With the default conventions, `pageSize` should be named `page-size`, `x-request-id` should be named `X-Request-Id` and `firstName` should be named `first_name`. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: post: consumes: [application/x-www-form-urlencoded] parameters: - name: pageSize in: query type: integer - name: x-request-id in: header type: string - name: firstName in: formData type: string responses: '201': description: The pet was added. ``` ## When Not to Use It You may disable this rule when parameter names are dictated by an existing implementation that cannot be changed. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # parameters-unique Source: https://apigenie.pl/docs/linter/swagger/rules/parameters-unique This rule checks whether the parameter lists of a Swagger 2.0 document contain duplicates. ## Rule Details The Swagger 2.0 specification requires a parameter list not to contain duplicate parameters, where a parameter is identified by the combination of its `name` and its location (`in`). With duplicates it is unclear which definition applies. The `parameters-unique` rule checks the `parameters` of every path item and of every operation separately, and reports `Duplicate parameter name '<name>' in location '<location>'` for every parameter that repeats the name and location of an earlier parameter of the same list. Parameter references (`$ref`) are compared by the parameter they point to; unresolved references are ignored. A parameter with the same name in a different location (for example a query parameter and a header both named `id`) is not a duplicate. ## Configuration None. ## Examples **Good**. The parameters differ by name or location. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: parameters: - name: id in: query type: string - name: id in: header type: string responses: '200': description: A list of pets. ``` **Bad**. The inline `limit` parameter duplicates the referenced one. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: parameters: - $ref: '#/parameters/limit' - name: limit in: query type: string responses: '200': description: A list of pets. parameters: limit: name: limit in: query type: integer ``` ## Automatic fixes When invoked with `--fix`, this rule removes a duplicate parameter from its list when it is structurally identical to the first parameter with the same name and location (references are compared by their targets). Duplicates that differ from the first parameter are reported but not fixed, since the rule cannot tell which definition is the right one. ## When Not to Use It We recommend to always enable this rule, since duplicate parameters violate the Swagger 2.0 specification. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # path-parameters-defined Source: https://apigenie.pl/docs/linter/swagger/rules/path-parameters-defined This rule checks whether every parameter of a path template is declared as a path parameter. ## Rule Details The Swagger 2.0 specification requires every template expression of a path (such as `{userId}` in `/users/{userId}`) to correspond to a parameter with `in: path`. Without it, tools do not know the type of the parameter and cannot build request URLs. The `path-parameters-defined` rule checks every operation and reports `One or more path parameters are missing` when a template expression of its path has no matching `in: path` parameter, either among the parameters of the path item or among the parameters of the operation. Parameter references (`$ref`) are resolved; a parameter with the right name in another location (for example `in: query`) does not count. Path parameters that do not appear in the template are not reported. ## Configuration The options are an object with an optional `defaultType` property, which sets the `type` of the parameters added by the automatic fix: `string` (default), `integer`, `number` or `boolean`. Set it to `null` to disable the automatic fix. ```yaml commands: lint: swagger: rules: path-parameters-defined: - error - defaultType: integer ``` ## Examples **Good**. The path parameter is declared on the path item. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: /users/{userId}: parameters: - name: userId in: path required: true type: string get: responses: '200': description: A single user. delete: responses: '204': description: The user was deleted. ``` **Bad**. The `get` operation declares `userId` as a query parameter and the `delete` operation does not declare it at all. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: /users/{userId}: get: parameters: - name: userId in: query type: string responses: '200': description: A single user. delete: responses: '204': description: The user was deleted. ``` ## Automatic fixes When invoked with `--fix`, this rule appends the missing parameters, in the order they appear in the path template, to the `parameters` of the path item. Each added parameter has the form `{name: <name>, in: path, required: true, type: <defaultType>}`. No fix is offered when `defaultType` is `null`. ## When Not to Use It We recommend to always enable this rule, since undeclared path parameters violate the Swagger 2.0 specification. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # path-parameters-on-items Source: https://apigenie.pl/docs/linter/swagger/rules/path-parameters-on-items This rule checks whether path parameters are declared on path items rather than on individual operations. ## Rule Details A path parameter belongs to the path, so every operation of a path item shares it. Declaring it once in the `parameters` of the path item avoids repeating the same definition in every operation and keeps the definitions consistent. The `path-parameters-on-items` rule reports `One or more path parameters are missing or not defined in path item` for every path item when a template expression of its path has no matching `in: path` parameter in the path item's own `parameters`. Parameters declared on operations do not count. Parameter references (`$ref`) are resolved. ## Configuration None. ## Examples **Good**. The path parameter is declared on the path item. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{petId}: parameters: - $ref: '#/parameters/petId' get: responses: '200': description: A single pet. parameters: petId: name: petId in: path required: true type: string ``` **Bad**. The path parameter is declared on the operation. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{petId}: get: parameters: - name: petId in: path required: true type: string responses: '200': description: A single pet. ``` ## When Not to Use It You may disable this rule if your style guide prefers declaring path parameters on operations. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # paths-max-count Source: https://apigenie.pl/docs/linter/swagger/rules/paths-max-count This rule checks whether a Swagger 2.0 document defines more paths than a configured maximum. ## Rule Details A large number of paths may indicate that an API covers too many concerns and could be split into smaller, more focused APIs. The `paths-max-count` rule counts the entries of the root `paths` object and reports `Too many paths found (<count>), maximum number is <maximum>` on the document root when the count exceeds the configured maximum (8 by default). Each path counts once, whatever the number of its operations. ## Configuration A single positive integer: the maximum number of paths. The default is 8. ```yaml commands: lint: swagger: rules: paths-max-count: [warning, 20] ``` ## Examples **Good**. With a maximum of 2, the document defines 2 paths. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: responses: '200': description: A list of pets. /owners: get: responses: '200': description: A list of owners. ``` **Bad**. With a maximum of 2, the document defines 3 paths. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: responses: '200': description: A list of pets. /owners: get: responses: '200': description: A list of owners. /visits: get: responses: '200': description: A list of visits. ``` ## When Not to Use It You may disable this rule for APIs that are large by design. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # paths-max-depth Source: https://apigenie.pl/docs/linter/swagger/rules/paths-max-depth This rule checks whether the paths of a Swagger 2.0 document are nested deeper than a configured maximum. ## Rule Details Deeply nested paths are hard to read and remember, and often expose internal relationships between resources that consumers do not need to know about. The `paths-max-depth` rule counts the non-empty segments of each path, path template expressions included, and reports `Path URL is too much nested (<path>)` on the path item when the count exceeds the configured maximum (4 by default). For example, `/pets/{id}/toys` has 3 segments. The root `basePath` is not counted. ## Configuration A single positive integer: the maximum number of path segments. The default is 4. ```yaml commands: lint: swagger: rules: paths-max-depth: [warning, 3] ``` ## Examples **Good**. With the default maximum of 4, the path has 3 segments. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{petId}/toys: get: parameters: - name: petId in: path required: true type: string responses: '200': description: The toys of a pet. ``` **Bad**. With the default maximum of 4, the path has 6 segments. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /owners/{ownerId}/pets/{petId}/toys/{toyId}: parameters: - name: ownerId in: path required: true type: string - name: petId in: path required: true type: string - name: toyId in: path required: true type: string get: responses: '200': description: A single toy. ``` ## When Not to Use It You may disable this rule when the resource hierarchy of your API is inherently deep. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # paths-sorted Source: https://apigenie.pl/docs/linter/swagger/rules/paths-sorted This rule checks whether the paths of a Swagger 2.0 document are sorted. ## Rule Details Sorted paths make a large document easier to navigate and keep diffs small when paths are added. The `paths-sorted` rule walks the entries of the root `paths` object in the order they appear in the document and reports `Paths are not sorted: <path>` for every path that is not greater than the previous one. Paths are compared character by character (byte order), so `/Pets` sorts before `/pets` and `/pets` before `/pets/{id}`. ## Configuration None. ## Examples **Good**. The paths are sorted. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /owners: get: responses: '200': description: A list of owners. /pets: get: responses: '200': description: A list of pets. ``` **Bad**. `/owners` is listed after `/pets`. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: responses: '200': description: A list of pets. /owners: get: responses: '200': description: A list of owners. ``` ## Automatic fixes When invoked with `--fix`, this rule reorders the entries of the `paths` object so that the paths are sorted in byte order. ## When Not to Use It You may disable this rule when paths are intentionally grouped in another order, for example by resource or by workflow. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # paths-unique Source: https://apigenie.pl/docs/linter/swagger/rules/paths-unique This rule checks whether a Swagger 2.0 document defines paths that are equivalent to each other. ## Rule Details Paths such as `/pets/{id}` and `/pets/{petId}` differ only by the names of their template expressions, so they match exactly the same requests. Tools cannot tell which path item a request belongs to. The `paths-unique` rule normalizes every path by replacing each template expression with `{}`, collapsing repeated slashes and removing a trailing slash, and reports `Duplicate path found` on every path item whose normalized path equals the normalized path of an earlier one. Paths are compared case-sensitively. ## Configuration None. ## Examples **Good**. The paths match different requests. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{petId}: get: parameters: - name: petId in: path required: true type: string responses: '200': description: A single pet. /pets/{petId}/toys: get: parameters: - name: petId in: path required: true type: string responses: '200': description: The toys of a pet. ``` **Bad**. Both paths match the same requests. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{id}: get: parameters: - name: id in: path required: true type: string responses: '200': description: A single pet. /pets/{petId}/: delete: parameters: - name: petId in: path required: true type: string responses: '204': description: The pet was deleted. ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # paths-well-formed Source: https://apigenie.pl/docs/linter/swagger/rules/paths-well-formed This rule checks whether the paths of a Swagger 2.0 document are well-formed. ## Rule Details Consistently formatted paths are easier to read and avoid surprises with servers that treat `/pets`, `/pets/` and `/Pets` as different resources. The `paths-well-formed` rule reports `Path is not well-formed <path>` on every path item whose path: - does not start with a slash, - contains empty segments (`//`), - ends with a slash (except the root path `/`), - contains upper-case ASCII letters outside template expressions. Template expressions such as `{petId}` are left as they are, so parameter names may use any case. ## Configuration None. ## Examples **Good**. The paths are well-formed. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets/{petId}: get: parameters: - name: petId in: path required: true type: string responses: '200': description: A single pet. ``` **Bad**. The path has an upper-case letter, an empty segment and a trailing slash. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /Pets//{petId}/: get: parameters: - name: petId in: path required: true type: string responses: '200': description: A single pet. ``` ## Automatic fixes When invoked with `--fix`, this rule renames each malformed path to its normalized form: a leading slash is added, empty segments and the trailing slash are removed, and letters outside template expressions are lower-cased. For example, `/Pets//{petId}/` becomes `/pets/{petId}`. ## When Not to Use It You may disable this rule when the paths are dictated by an existing implementation that cannot be changed. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # safe-markdown Source: https://apigenie.pl/docs/linter/swagger/rules/safe-markdown This rule ensures that the GFM descriptions of a Swagger 2.0 document do not contain unsafe constructs like `script` elements or `eval()` calls. ## Rule Details Unsafe constructs embedded in descriptions can pose security risks when the documentation is rendered, for example by a documentation portal or an API console. The `safe-markdown` rule checks the `description` field of the info object, operations, parameters, responses (but not response references), headers, schemas, security definitions, tags and external documentation objects. It reports: - `Markdown contains <script> tag` when a description contains a `script` element (an opening tag followed by a closing tag), - `Markdown contains eval() function` when a description contains `eval` followed by a parenthesized argument list. Both checks are case-insensitive, and skip code spans and code blocks. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 description: Manages the pets of a shelter and their adoptions. paths: {} ``` **Bad**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 description: "Manages the pets of a shelter. <script>alert('Unsafe')</script>" paths: /pets: get: description: Evaluates eval(filter) on the server. responses: '200': description: The list of pets. ``` ## Automatic fixes When invoked with `--fix`, the rule rewrites the affected descriptions: the `<` of the opening and closing `script` tags is replaced with `<`, and the `(` after `eval` with `(`. The rest of the description, including code spans and code blocks, is preserved. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `safe-markdown` rules. --- # schema-constraints Source: https://apigenie.pl/docs/linter/swagger/rules/schema-constraints This rule ensures that typed objects of a Swagger 2.0 document define constraints restricting the size of the underlying data. ## Rule Details Defining constraints enhances the robustness and reliability of the API, and protects consumers and servers from unexpectedly large values. The `schema-constraints` rule checks every object of the document that has a `type`: schemas (definitions, properties, `items`, `allOf` members, `additionalProperties`, body parameter and response schemas), non-body parameters, response headers and `items` objects. Objects with a `$ref` are skipped (their targets are checked where they are defined), and body parameters are checked through their schema. Depending on the type, the rule requires: - `integer` - both `maximum` and `minimum`; it reports `Integer schema does not define constraints restricting its range`, - `number` - both `maximum` and `minimum`; it reports `Numeric schema does not define constraints restricting its range`, - `string` - `maxLength`, `pattern`, `format` or `enum`; it reports `String schema does not define constraints restricting its length`, - `array` - `maxItems`; it reports `Array schema does not define constraints restricting its size`, - `object` - `maxProperties` or `additionalProperties: false`; it reports `Object schema does not define constraints restricting its size`. Only schemas are checked for this type. Objects of type `boolean` or `file`, and schemas without a `type`, are not checked. ## Configuration The configuration is an object whose keys are the types `integer`, `number`, `string`, `array` and `object`, and whose values are booleans. Only the types set to `true` are checked, and at least one type must be set to `true`. By default, all five types are checked. The `recommended` ruleset checks only `number` and `string`. **Configuration Example**. The rule with the following configuration checks only arrays and objects. ```yaml version: "1.0" commands: lint: swagger: rules: schema-constraints: - warning - array: true object: true ``` ## Examples **Good**. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: /users: get: parameters: - name: limit in: query type: integer minimum: 1 maximum: 100 responses: '200': description: Users schema: type: array maxItems: 100 items: $ref: '#/definitions/User' definitions: User: type: object additionalProperties: false properties: username: type: string pattern: "^[a-z0-9_-]{3,16}$" roles: type: array maxItems: 5 items: type: string enum: [admin, member] age: type: integer minimum: 18 maximum: 100 ``` **Bad**. None of the typed objects is constrained. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: /users: get: parameters: - name: limit in: query type: integer responses: '200': description: Users schema: type: array items: $ref: '#/definitions/User' definitions: User: type: object properties: username: type: string roles: type: array items: type: string age: type: integer ``` ## When Not to Use It If your API does not need to restrict the size of its values, you may not need to use this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-datetime-suffix Source: https://apigenie.pl/docs/linter/swagger/rules/schema-datetime-suffix This rule ensures that the names of date and date-time properties end with a common suffix. ## Rule Details A consistent suffix makes temporal properties easy to recognize, and distinguishes them from other properties describing the same concept (e.g. `created_at` versus `created_by`). The `schema-datetime-suffix` rule checks the `properties` of every schema of a Swagger 2.0 document. For every property whose schema has the `date` or `date-time` format and whose name does not end with the configured suffix, it reports `Property name <name> should end with <suffix>`. A property defined with a `$ref` has the format of the referenced schema. The issue is reported at the property itself, not at a (possibly shared) referenced schema. The default suffix is `_at`. ## Configuration The configuration block is a non-empty string: the required suffix. **Configuration Example**. The following configuration requires temporal properties to end with `At`, which fits camelCase property names. ```yaml version: "1.0" commands: lint: swagger: rules: schema-datetime-suffix: [warning, At] ``` ## Examples **Good**. With the default configuration. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Adoption: type: object properties: adopted_at: type: string format: date-time born_at: $ref: '#/definitions/Day' Day: type: string format: date ``` **Bad**. With the default configuration, both properties are reported. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Adoption: type: object properties: adoption_date: type: string format: date-time birthday: $ref: '#/definitions/Day' Day: type: string format: date ``` ## When Not to Use It You may disable this rule if your API follows a different convention for naming temporal properties. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-enum-naming Source: https://apigenie.pl/docs/linter/swagger/rules/schema-enum-naming This rule ensures that the string values of `enum` lists adhere to a predefined naming convention. ## Rule Details Adhering to a consistent naming convention enhances the readability and maintainability of the API. The `schema-enum-naming` rule checks the `enum` list of every typed object of a Swagger 2.0 document: schemas, non-body parameters, response headers and `items` objects (objects with a `$ref` are checked where they are defined). For every string value that is not in the expected convention, it reports `Schema enum value '<value>' is not in the '<convention>' convention`, where `<convention>` is the display name of the convention (e.g. `CONST_CASE`). Values of other types (numbers, booleans, ...) are ignored. The recognized naming conventions are: - `const` - THIS_IS_CONST_CASE - `camel` - thisIsCamelCase, also with acronyms (userID) - `camel-strict` - thisIsCamelCase, but no two capitals in a row (userId, not userID) - `kebab` - this-is-kebab-case - `pascal` - ThisIsPascalCase, also with acronyms (HTTPServer) - `pascal-strict` - ThisIsPascalCase, but no two capitals in a row (HttpServer, not HTTPServer) - `header` - This-Is-Header-Case - `snake` - this_is_snake_case The default convention is `const`. ## Configuration The configuration block is a naming convention name (see the "Rule Details" section for a list of recognized names). ```yaml version: "1.0" commands: lint: swagger: rules: schema-enum-naming: [warning, kebab] ``` ## Examples **Good**. With the default configuration. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: /users: get: parameters: - name: sort in: query type: string enum: [ASCENDING, DESCENDING] responses: '200': description: Users definitions: UserStatus: type: string enum: - ACTIVE - INACTIVE - BLOCKED ``` **Bad**. To use `UserStatus` properly, a consumer must always consult the documentation. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: {} definitions: UserStatus: type: string enum: - activeStatus - inactive_status - BLOCKED_STATUS ``` ## When Not to Use It If your API does not follow a specific naming convention for `enum` values, you may not need to use this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-enum-no-duplicates Source: https://apigenie.pl/docs/linter/swagger/rules/schema-enum-no-duplicates This rule ensures that `enum` lists do not contain duplicate values. ## Rule Details A duplicate enum value is redundant and usually a copy-paste mistake. It may also break code generators, which turn enum values into constants that must be unique. The `schema-enum-no-duplicates` rule checks the `enum` list of every typed object of a Swagger 2.0 document: schemas, non-body parameters, response headers and `items` objects (objects with a `$ref` are checked where they are defined). It reports `Duplicate enum value` once for every value that is equal to a value listed before it. Values are compared structurally: arrays are equal when their elements are equal in order, and objects are equal when they have the same keys with equal values, regardless of the order of the keys. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: {} definitions: UserStatus: type: string enum: [ACTIVE, INACTIVE] ``` **Bad**. `ACTIVE` is listed twice, and the two points differ only in the order of their keys. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: {} definitions: UserStatus: type: string enum: [ACTIVE, INACTIVE, ACTIVE] Corner: type: object enum: - { x: 1, y: 2 } - { y: 2, x: 1 } ``` ## Automatic fixes When invoked with `--fix`, this rule removes every duplicate value, keeping the first occurrence of each value. ## When Not to Use It There is no good reason to keep duplicate enum values, so this rule should always be enabled. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-enum-types Source: https://apigenie.pl/docs/linter/swagger/rules/schema-enum-types This rule ensures that all values of an `enum` list match the declared `type`. ## Rule Details An enum value that does not match the type can never be valid, and usually indicates a mistake in either the type or the values. The `schema-enum-types` rule checks every typed object of a Swagger 2.0 document that has an `enum` list: schemas, non-body parameters, response headers and `items` objects (objects with a `$ref` are checked where they are defined). It reports `Enum value type is not in the schema type` when the object has no `type`, or when at least one value does not match it: - `string` accepts strings, - `integer` accepts integral numbers, - `number` accepts integral and fractional numbers, - `boolean` accepts `true` and `false`, - `array` accepts arrays, - `object` accepts objects. Swagger 2.0 has no `null` type, so `null` never matches, and no value matches the `file` type. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Tasks version: 1.0.0 paths: {} definitions: Status: type: string enum: [OPEN, CLOSED] Ratio: type: number enum: [1, 2.5] ``` **Bad**. The values of `Priority` are integers, and `Status` has no type. ```yaml swagger: '2.0' info: title: Tasks version: 1.0.0 paths: {} definitions: Priority: type: string enum: [1, 2, 3] Status: enum: [OPEN, CLOSED] ``` ## Automatic fixes When invoked with `--fix`, this rule sets the `type` of a reported object to the type of its enum values, provided that all values have the same type (and are not `null`) and the object has no `format`. In the "Bad" example above, `Priority` gets `type: integer` and `Status` gets `type: string`. Objects mixing values of different types are not fixed. ## When Not to Use It There is no good reason to keep enum values that do not match the type, so this rule should always be enabled. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-example-defined Source: https://apigenie.pl/docs/linter/swagger/rules/schema-example-defined This rule ensures that the schemas of a Swagger 2.0 document provide examples. ## Rule Details Examples make an API easier to understand, and are used by documentation tools, mock servers and API consoles. The `schema-example-defined` rule checks: - every entry of `definitions`, reporting `Schema does not define examples` when it has no `example`, - the inline schema of every body parameter, reporting `Body parameter schema does not define examples` when it has no `example`, - the inline schema of every response, reporting `Response schema does not define examples` when it has no `example` and the response has no `examples`. Schemas defined with a `$ref` are not reported: the referenced definition is checked instead. Schemas nested in other schemas (properties, `items`, ...) are not checked. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: /pets: post: parameters: - name: pet in: body required: true schema: $ref: '#/definitions/Pet' responses: '201': description: Created schema: type: object properties: id: type: integer examples: application/json: id: 42 definitions: Pet: type: object properties: name: type: string example: name: Rex ``` **Bad**. The inline body parameter schema and the `PetId` definition, used by the response, provide no examples. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: /pets: post: parameters: - name: pet in: body required: true schema: type: object properties: name: type: string responses: '201': description: Created schema: $ref: '#/definitions/PetId' definitions: PetId: type: object properties: id: type: integer ``` ## When Not to Use It You may disable this rule if examples are provided outside the document. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-format-defined Source: https://apigenie.pl/docs/linter/swagger/rules/schema-format-defined This rule ensures that numeric objects declare the size of their values with a `format`. ## Rule Details The `integer` and `number` types do not say how large values may be or which precision they have. The formats `int32`, `int64`, `float` and `double` let consumers and code generators choose suitable native types. The `schema-format-defined` rule checks every typed object of a Swagger 2.0 document: schemas, non-body parameters, response headers and `items` objects (objects with a `$ref` are checked where they are defined). It reports: - `Schema with integer type does not define proper format` for an `integer` object whose `format` is not `int32` or `int64`, - `Schema with number type does not define proper format` for a `number` object whose `format` is not `float` or `double`. Objects of other types are not checked. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: /pets: get: parameters: - name: limit in: query type: integer format: int32 responses: '200': description: Pets definitions: Pet: type: object properties: id: type: integer format: int64 weight: type: number format: double ``` **Bad**. The parameter has no format, and the properties have formats of other types. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: /pets: get: parameters: - name: limit in: query type: integer responses: '200': description: Pets definitions: Pet: type: object properties: id: type: integer format: uuid weight: type: number format: int32 ``` ## When Not to Use It You may disable this rule if the size of numeric values does not matter for your API. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-format-deprecated Source: https://apigenie.pl/docs/linter/swagger/rules/schema-format-deprecated This rule reports the formats which the [OpenAPI format registry](https://spec.openapis.org/registry/format/) marks as deprecated from OpenAPI 3.1, and names what replaces them. ## Rule Details | Format | Use instead | | ------ | ----------- | | `byte` | `contentEncoding: base64` | | `base64url` | `contentEncoding: base64url` | | `binary` | `contentMediaType`, or the media type of the body or part without a schema | Swagger 2.0 has no replacement for these formats, so the issues are always hints: they mark what needs a change when the document is converted to OpenAPI 3.1, and never count as warnings. See [Formats](/docs/reference/cli/formats) for every format. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' definitions: Avatar: type: string ``` **Bad**. ```yaml swagger: '2.0' definitions: Avatar: type: string format: byte ``` ## When Not to Use It When the documents must stay readable by tools that only know the older formats. --- # schema-format-type-mismatch Source: https://apigenie.pl/docs/linter/swagger/rules/schema-format-type-mismatch This rule ensures that the `format` of an object is one that applies to its `type`. ## Rule Details Each well-known format applies to a single type. A format paired with another type (e.g. `type: integer` with `format: date-time`) is contradictory, and tools interpret it inconsistently. The `schema-format-type-mismatch` rule checks every object of a Swagger 2.0 document that has a `format`: schemas, non-body parameters, response headers and `items` objects (objects with a `$ref` are checked where they are defined). It checks the formats Swagger 2.0 defines and the JSON Schema ones it allows, with the JSON types the [OpenAPI format registry](https://spec.openapis.org/registry/format/) gives them, and the formats of `openapi.custom_formats` in the configuration file: - `int32`, `int64` - `integer` or `number`, and `string` for `int64`, - `float`, `double` - `number`, - `byte`, `binary`, `date`, `date-time`, `password`, `email`, `hostname`, `ipv4`, `ipv6`, `uri`, `uuid` - `string`. For these formats, it reports `Schema type and format do not match` when the object has another `type` or no `type` at all. Other formats are left to [schema-format-unknown](/docs/linter/swagger/rules/schema-format-unknown). ## Configuration The configuration block is an object with a single optional boolean property, `fixTypeFromFormat`, which controls whether reported issues can be fixed automatically (see "Automatic fixes"). The default value is `true`. **Configuration Example**. The following configuration reports mismatches, but does not fix them. ```yaml version: "1.0" commands: lint: swagger: rules: schema-format-type-mismatch: - error - fixTypeFromFormat: false ``` ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Pet: type: object properties: id: type: string format: uuid born_at: type: string format: date-time weight: type: number format: float ``` **Bad**. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Pet: type: object properties: id: type: integer format: uuid born_at: format: date-time ``` ## Automatic fixes When `fixTypeFromFormat` is `true` and the linter is invoked with the `--fix` option, this rule sets the `type` of every reported object to the type the format applies to. In the "Bad" example above, both properties get `type: string`. ## When Not to Use It You may disable this rule if your API uses well-known format names with a different meaning. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-format-unknown Source: https://apigenie.pl/docs/linter/swagger/rules/schema-format-unknown This rule reports a `format` of a Swagger 2.0 document which is neither in the [OpenAPI format registry](https://spec.openapis.org/registry/format/) nor declared in `openapi.custom_formats` of the [configuration file](/docs/reference/cli/configuration#openapicustom_formats). ## Rule Details Tools ignore formats they don't know, so a misspelled format (`date_time`, `uuid4`) silently stops being checked. Formats defined on purpose are declared in the configuration file: ```yaml openapi: custom_formats: - {name: currency-code, type: string} ``` The rule is off in the `recommended` ruleset and a warning in `strict` and `all`. See [Formats](/docs/reference/cli/formats) for the registry's formats. ## Configuration None. ## When Not to Use It When the documents use many formats of their own and listing them in the configuration file isn't worth it. --- # schema-no-additional-properties Source: https://apigenie.pl/docs/linter/swagger/rules/schema-no-additional-properties This rule ensures that object schemas do not accept arbitrary additional properties. ## Rule Details An object schema accepts properties that are not listed in `properties` unless `additionalProperties` is `false`. Open objects hide typos in property names, and let undocumented data pass through the API. The `schema-no-additional-properties` rule checks every schema of a Swagger 2.0 document with `type: object` (schemas with a `$ref` are checked where they are defined). It reports `Object schema does not disable additional properties` when `additionalProperties` is absent or `true`. A schema value of `additionalProperties` describes a map whose values have that schema. This is a deliberate choice, and such schemas are not reported. ## Configuration None. ## Examples **Good**. `Pet` is closed, and `Labels` is a map of strings. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Pet: type: object additionalProperties: false properties: name: type: string labels: $ref: '#/definitions/Labels' Labels: type: object additionalProperties: type: string ``` **Bad**. Both `Pet` and its `owner` property accept any additional property. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Pet: type: object properties: name: type: string owner: type: object additionalProperties: true properties: name: type: string ``` ## Automatic fixes When invoked with `--fix`, this rule sets `additionalProperties: false` on every reported schema. ## When Not to Use It You may disable this rule if your API deliberately accepts or returns properties that are not described, for example to stay compatible with future versions. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-property-naming Source: https://apigenie.pl/docs/linter/swagger/rules/schema-property-naming This rule ensures that the names of schema properties adhere to a predefined naming convention. ## Rule Details Adhering to a consistent naming convention enhances the readability and maintainability of the API, and makes payloads easier to map to native data structures. The `schema-property-naming` rule checks the keys of the `properties` map of every schema of a Swagger 2.0 document (schemas with a `$ref` are checked where they are defined). For every property whose name is not in the expected convention, it reports `Schema property '<name>' is not in the '<convention>' convention`, where `<convention>` is the display name of the convention (e.g. `snake_case`). The issue is reported at the schema that declares the property. The recognized naming conventions are: - `const` - THIS_IS_CONST_CASE - `camel` - thisIsCamelCase, also with acronyms (userID) - `camel-strict` - thisIsCamelCase, but no two capitals in a row (userId, not userID) - `kebab` - this-is-kebab-case - `pascal` - ThisIsPascalCase, also with acronyms (HTTPServer) - `pascal-strict` - ThisIsPascalCase, but no two capitals in a row (HttpServer, not HTTPServer) - `header` - This-Is-Header-Case - `snake` - this_is_snake_case The default convention is `snake`. ## Configuration The configuration block is a naming convention name (see the "Rule Details" section for a list of recognized names). ```yaml version: "1.0" commands: lint: swagger: rules: schema-property-naming: [error, camel] ``` ## Examples **Good**. With the default configuration. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: {} definitions: User: type: object properties: first_name: type: string last_name: type: string ``` **Bad**. With the default configuration. ```yaml swagger: '2.0' info: title: Users version: 1.0.0 paths: {} definitions: User: type: object properties: firstName: type: string LastName: type: string ``` ## When Not to Use It If your API does not follow a specific naming convention for property names, you may not need to use this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schema-required-properties-defined Source: https://apigenie.pl/docs/linter/swagger/rules/schema-required-properties-defined This rule checks that every name listed in `required` is declared in `properties`. ## Rule Details A required property which is not declared is usually a typo (`required: [nmae]`) or a property removed from `properties` but not from `required`. Its value is not described, and generated code often cannot represent it. The `schema-required-properties-defined` rule checks every schema of a Swagger 2.0 document that has a `required` list. It looks for the declaration of each name wherever the value of the schema is also described: - in the `properties` of the schema itself, - in its `allOf` members, following references, - in the schemas it is an `allOf` member of, and in their other members. For every name that is not declared, it reports `Required property '<name>' is not defined in 'properties'`. Schemas which may allow undeclared properties on purpose are skipped: when a schema of the group has a schema in `additionalProperties` (a map) or `additionalProperties: true`. Schemas whose group contains a reference that cannot be resolved are skipped too. `required` in a schema that declares types (itself or through its `allOf` members) none of which is `object`, for example beside `items` in an array schema instead of inside it, has no effect. The rule reports it with `'required' has no effect because the schema does not allow objects`. ## Configuration None. ## Examples **Good**. `name` is declared by the `allOf` member `Named`. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Named: type: object properties: name: type: string Pet: allOf: - $ref: '#/definitions/Named' - type: object required: [name, age] properties: age: type: integer ``` **Bad**. `nmae` is not declared. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Pet: type: object required: [nmae] properties: name: type: string ``` **Bad**. `required` applies to the array, not to its items. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Pets: type: array required: [name] items: type: object properties: name: type: string ``` ## When Not to Use It Disable this rule when required properties are intentionally left undescribed. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # schemes-secure Source: https://apigenie.pl/docs/linter/swagger/rules/schemes-secure This rule checks whether a Swagger 2.0 document serves its API over secure transfer protocols only, and whether its host is written in lower case. ## Rule Details The root `schemes` list, and the `schemes` lists of operations that override it, name the transfer protocols of the API. `http` and `ws` send requests, credentials and responses unencrypted, so production APIs should use `https` and `wss` instead. The `schemes-secure` rule reports: - `Insecure scheme (http)` or `Insecure scheme (ws)` for every insecure scheme in the root `schemes` list and in the `schemes` list of every operation, - `Host is not lower case` when the root `host` contains upper case letters. Host names are case-insensitive, and lower case is their canonical form. Insecure schemes are allowed when `host` is a loopback address: `localhost` (in any letter case), `127.0.0.1` or `[::1]`, with or without a port. When `host` is missing, insecure schemes are reported. ## Configuration The rule accepts an object with one option: - `upgradeInsecure`: whether `--fix` replaces insecure schemes with their secure variants. Default: `false`. **Configuration Example** ```yaml version: "1.0" commands: lint: swagger: rules: schemes-secure: - error - upgradeInsecure: true ``` ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 host: api.example.com schemes: [https, wss] paths: {} ``` **Good**. A local development server may use `http`. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 host: localhost:8080 schemes: [http] paths: {} ``` **Bad**. The root object allows `http`, an operation uses `ws`, and the host is not lower case. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 host: API.Example.com schemes: [http, https] paths: /pets/events: get: schemes: [ws] responses: '101': description: Switching to the event stream. ``` ## Automatic fixes When invoked with `--fix`, the rule always converts the root `host` to lower case. With `upgradeInsecure: true`, it also rewrites every root and operation `schemes` list that contains an insecure scheme, unless the host is a loopback address: `http` becomes `https`, `ws` becomes `wss`, and duplicates are removed while the order is kept. For example, `[http, https, ws]` becomes `[https, wss]`. Without this option the insecure schemes are reported, but not changed, because switching protocols requires the server to support them. ## When Not to Use It You may disable this rule for documents that describe APIs that are only reachable in a trusted network, or for an operation with `x-lint-disable: [schemes-secure]`. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, whose `server-urls` rule checks the URLs of servers. --- # spell-check Source: https://apigenie.pl/docs/linter/swagger/rules/spell-check This rule checks the spelling of the texts of a Swagger 2.0 document written for humans: titles, summaries and descriptions. ## Rule Details The rule reports every misspelled word, with up to three suggested corrections, for example `Misspelled word 'Recieve' in summary (did you mean 'Receive', 'Relieve', 'Reverie'?)`. When there are no suggestions, the part in parentheses is omitted. The issue points at the object whose text contains the word. The rule checks: - the title and description of the info object, - the summary and description of operations, - the title and description of schemas, - the description of parameters, responses (but not response references), headers, security definitions, tags and external documentation objects. Descriptions are Markdown (GFM in Swagger 2.0): the rule checks their prose, and skips code spans, code blocks, raw HTML and link destinations. Titles and summaries are plain text, in which `backticked` spans are skipped, too. The rule also skips the tokens which are not plain words: - URLs, e-mail addresses, paths, and file and domain names (`swagger.yaml`, `example.com`), - identifiers: `snake_case`, `camelCase` and names with inner capitals (`PetStore`), - acronyms, also in the plural (`HTTP`, `APIs`, `ID's`), - tokens with digits (`v2`, `401`) and single letters. The spell checker is a C++ port of [Hunspell](https://hunspell.github.io). By default it uses the built-in English (US) dictionary from [SCOWL](http://wordlist.aspell.net) (as shipped with LibreOffice), extended with words common in API descriptions, such as `webhook`, `endpoint`, `namespace`, `nullable` and `json`. Any Hunspell dictionary can be used instead. The rule is part of the `strict` and `all` rulesets. ## Configuration This rule accepts an object with the following options: - `words`: extra words to accept, for example product names. A word is accepted as given, capitalized and in capitals: `petstore` accepts `petstore`, `Petstore` and `PETSTORE`. - `dictionary` and `affix`: paths of a Hunspell dictionary (`.dic`) and its affix file (`.aff`) to use instead of the built-in dictionary. They go together. Dictionaries in UTF-8, ISO-8859-1, ISO-8859-2 and ISO-8859-15 are supported. - `suggestions`: how many corrections to suggest for a word, from 0 to 10 (default 3). Suggestions are the costly part of the rule; on a very large document with many unknown words, `0` makes it about ten times faster. **Configuration Example** ```yaml version: "1.0" commands: lint: swagger: rules: spell-check: - warning - words: [petstore] dictionary: path/to/en_GB.dic affix: path/to/en_GB.aff ``` ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: summary: Receive the list of pets description: Returns the pets available for adoption. responses: '200': description: The list of pets. ``` **Bad**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: summary: Recieve the list of pets description: Returns the pets availabel for adoption. responses: '200': description: The list of pets. ``` ## When Not to Use It You may disable this rule if your documentation is written in a language you have no dictionary for. To silence it for a single object and its children, add `x-lint-disable: [spell-check]` to the object. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `spell-check` rules. --- # standard-media-types Source: https://apigenie.pl/docs/linter/swagger/rules/standard-media-types This rule checks whether a Swagger 2.0 document uses only well-known media types. ## Rule Details Non-standard or misspelled media types lead to interoperability issues: clients and servers may fail to negotiate the format of requests and responses. The `standard-media-types` rule checks: - the root `consumes` and `produces` lists, - the `consumes` and `produces` lists of every operation, - the media type keys of the `examples` of every response (response references are skipped). It reports `Non-standard media type is used (<media type>)` for every value that is not on the built-in list of well-known media types, such as `application/json`, `application/xml`, `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain` or `application/octet-stream`. Wildcards (`*/*`, `application/*`) are accepted, and parameters after `;` (like `charset=utf-8`) are ignored. Values without a subtype, like `json`, are reported. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 consumes: [application/json] produces: [application/json, application/xml] paths: /pets: get: produces: [text/plain] responses: '200': description: The list of pets. examples: text/plain: Rex, Fluffy ``` **Bad**. `application/x-custom` and `text/foo` are not well-known media types, and `json` has no subtype. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 consumes: [application/x-custom] paths: /pets: get: produces: [json] responses: '200': description: The list of pets. examples: text/foo: Rex ``` ## When Not to Use It You may disable this rule if your API deliberately uses vendor-specific media types that are not on the built-in list. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `standard-media-types` rules. --- # swagger-validation Source: https://apigenie.pl/docs/linter/swagger/rules/swagger-validation This rule reports violations of the Swagger 2.0 specification that span several objects, and therefore cannot be detected while a single object is read. ## Rule Details The reader checks the structure of every object of a document, but some requirements of the specification relate several objects to each other: a security requirement must name a declared security definition, a path parameter must appear in its path template, and so on. The `swagger-validation` rule runs these checks: | Check | Requirement | | --- | --- | | `unique-operation-id` | `operationId` values are unique across the document | | `unique-parameter` | (`name`, `in`) pairs are unique within a parameter list | | `single-body-parameter` | an operation has at most one `body` parameter | | `body-form-data-exclusive` | `body` and `formData` parameters are not mixed | | `file-parameter-consumes` | `file` parameters require `multipart/form-data` or `application/x-www-form-urlencoded` in `consumes`; other `formData` parameters are reported when `consumes` lists neither | | `path-parameter-declared` | every path template variable has a `path` parameter | | `path-parameter-in-template` | every `path` parameter appears in the path template | | `security-scheme-defined` | security requirements refer to `securityDefinitions` | | `security-scope-defined` | `oauth2` requirements use declared scopes | | `security-scopes-empty` | requirements of other schemes have empty scope lists | | `unresolved-reference` | local references (`#/definitions`, `#/parameters`, `#/responses`) resolve | | `valid-scheme` | schemes are `http`, `https`, `ws` or `wss` | | `host-format` | `host` contains neither a scheme nor a path | | `unique-tag` | tag names are unique | | `discriminator-required` | a `discriminator` property is listed in `required` | Each message is the message of the check, followed by the check name in parentheses, for example `An operation can have at most one body parameter (single-body-parameter)` or `Unresolved reference '#/definitions/Missing' (unresolved-reference)`. All issues are reported with the severity of this rule. By default all checks run except `unique-operation-id`, `unique-parameter`, `path-parameter-declared` and `unique-tag`. These are covered by the dedicated rules `operation-id-unique`, `parameters-unique`, `path-parameters-defined` and `tags-unique`, which can also fix some of the issues. ## Configuration The rule accepts an object with one option: - `checks`: the names of the checks to run. The list replaces the default set of checks, so include every check you want. Unknown names are rejected. **Configuration Example**. Run all checks, including those of the dedicated rules (which you would then disable). ```yaml version: "1.0" commands: lint: swagger: rules: swagger-validation: - error - checks: - unique-operation-id - unique-parameter - single-body-parameter - body-form-data-exclusive - file-parameter-consumes - path-parameter-declared - path-parameter-in-template - security-scheme-defined - security-scope-defined - security-scopes-empty - unresolved-reference - valid-scheme - host-format - unique-tag - discriminator-required ``` ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 host: api.example.com schemes: [https] paths: /pets/{petId}: get: parameters: - name: petId in: path required: true type: string responses: '200': description: The pet. ``` **Bad**. The host includes a scheme, `ftp` is not a valid scheme, the security requirement names an undeclared security definition, the operation has two body parameters, and one of them refers to a missing definition. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 host: https://api.example.com schemes: [ftp] paths: /pets: post: parameters: - name: a in: body schema: $ref: '#/definitions/Missing' - name: b in: body schema: type: object security: - unknown: [] responses: '200': description: OK ``` ## When Not to Use It We recommend to always enable this rule: documents that violate these requirements are not valid Swagger 2.0 documents, and tools may reject them or misinterpret them. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, whose `openapi-json-schema-validation` rule validates them against the OpenAPI 3.x JSON schema. --- # tag-description-defined Source: https://apigenie.pl/docs/linter/swagger/rules/tag-description-defined This rule checks whether each tag declared in the root `tags` array of a Swagger 2.0 document has a meaningful description. ## Rule Details Tag descriptions explain the purpose of the groups of operations, which makes the structure of the API easier to understand in documentation and API consoles. The `tag-description-defined` rule reports `Tag '<name>' has no meaningful description defined` for every tag whose `description`: - is missing or empty, - is shorter than the configured minimal length (10 characters by default), - consists of whitespace only, - is one of the placeholders `TODO`, `FIXME` or `TBD` (in any letter case). ## Configuration A single positive integer: the minimal length of a meaningful description. The default value is 10. **Configuration Example** ```yaml version: "1.0" commands: lint: swagger: rules: tag-description-defined: [warning, 20] ``` ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: {} tags: - name: pets description: Operations on the pets of the shelter. - name: users description: Operations on the accounts of the shelter staff. ``` **Bad**. The first tag has a placeholder description, the second has none. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: {} tags: - name: pets description: TBD - name: users ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `tag-description-defined` rules. --- # tags-sorted Source: https://apigenie.pl/docs/linter/swagger/rules/tags-sorted This rule checks whether the tags in the root `tags` array of a Swagger 2.0 document are sorted by name. ## Rule Details Many tools show tags in the order in which they are declared. Keeping them sorted makes the document and its rendered documentation easier to browse. The `tags-sorted` rule compares the name of each tag with the name of the previous one, and reports `Tags are not sorted: <name>` for every tag that comes after a tag with a greater name. Names are compared byte by byte, so the comparison is case-sensitive and upper case letters sort before lower case ones. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: {} tags: - name: owners - name: pets - name: users ``` **Bad**. `owners` comes after `pets`. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: {} tags: - name: users - name: pets - name: owners ``` ## Automatic fixes When invoked with `--fix`, the rule reorders the elements of the root `tags` array by name, using the same comparison. The tag objects themselves are not changed. ## When Not to Use It You may disable this rule if the order of your tags is meaningful, for example when it defines the order of sections in the generated documentation. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `tags-sorted` rules. --- # tags-unique Source: https://apigenie.pl/docs/linter/swagger/rules/tags-unique This rule checks whether the tags in the root `tags` array of a Swagger 2.0 document have unique names, comparing names case-insensitively. ## Rule Details The Swagger 2.0 specification requires tag names to be unique. Tags whose names differ only in letter case are just as confusing: it is unclear whether they describe one group of operations or two. The `tags-unique` rule reports `Tag name is not unique (<name>)` for each duplicate. Names are compared after converting ASCII letters to lower case. The issue normally points at the later duplicate; when the earlier tag has only a `name` and the later one has more properties, it points at the earlier tag instead. The `unique-tag` check of the `swagger-validation` rule reports exact duplicates too, but it is disabled by default in favor of this rule. ## Configuration None. ## Examples **Good**. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: {} tags: - name: pets - name: users ``` **Bad**. Two tags differ only in letter case. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: {} tags: - name: pets description: Operations on the pets of the shelter. - name: Pets description: Is this another tag, or a typo? ``` ## Automatic fixes When invoked with `--fix`, the rule removes a duplicate tag from the root `tags` array when it is safe to do so: - a duplicate that has only a `name` is removed, - when the earlier tag has only a `name` and the later duplicate has more properties, the earlier tag is removed, - a duplicate whose properties other than `name` are equal to those of the earlier tag is removed. Duplicates with different descriptions, external documentation or extensions are reported, but left for manual resolution. ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x and AsyncAPI documents are linted by the OpenAPI and AsyncAPI linters, which have their own `tags-unique` rules. --- # unused-request-body Source: https://apigenie.pl/docs/linter/swagger/rules/unused-request-body This rule checks whether `GET` and `HEAD` operations of a Swagger 2.0 document declare a request body. ## Rule Details HTTP gives no meaning to the body of a `GET` or `HEAD` request. Many clients, proxies and servers drop or reject such a body, so a body declared for these operations is unlikely to reach the API. In Swagger 2.0 the request body is described by a parameter with `in: body` or by parameters with `in: formData`. The `unused-request-body` rule reports `Unused request body` for every `get` or `head` operation whose own `parameters` contain a body or form data parameter. Parameter references (`$ref`) are resolved, so a reference to a shared body or form data parameter counts as well. Parameters declared on the path item are not checked. ## Configuration None. ## Examples **Good**. The `get` operation takes query parameters and the `post` operation takes a body. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: parameters: - name: limit in: query type: integer responses: '200': description: A list of pets. post: parameters: - name: pet in: body schema: $ref: '#/definitions/Pet' responses: '201': description: The pet was added. definitions: Pet: type: object properties: name: type: string ``` **Bad**. The `get` operation declares a body parameter and the `head` operation references a form data parameter. ```yaml swagger: '2.0' info: title: Pet Store version: 1.0.0 paths: /pets: get: parameters: - name: filter in: body schema: type: object - name: limit in: query type: integer responses: '200': description: A list of pets. head: parameters: - $ref: '#/parameters/name' responses: '200': description: The list of pets exists. parameters: name: name: name in: formData type: string ``` ## Automatic fixes When invoked with `--fix`, this rule removes the body and form data parameters (including references to them) from the `parameters` of `get` and `head` operations. Other parameters are kept, and shared parameters under the root `parameters` are not changed. ## When Not to Use It You may disable this rule when your API and every client and intermediary between them are known to support bodies on `GET` requests. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # unused-shared-component Source: https://apigenie.pl/docs/linter/swagger/rules/unused-shared-component This rule checks whether every shared object of a Swagger 2.0 document is actually used. ## Rule Details Shared objects that are never used make the document longer and harder to maintain, and suggest that something is missing (for example, a response that was defined but never attached to an operation). The `unused-shared-component` rule checks every entry of the root `definitions`, `parameters`, `responses` and `securityDefinitions` maps, and reports `Unused shared component` for every entry that is not used: - An entry of `definitions`, `parameters` or `responses` is used when a local `$ref` of the form `#/<section>/<name>` (possibly pointing deeper into the entry, e.g. `#/definitions/Pet/properties/name`) appears anywhere outside these three sections, or inside another used entry. References are followed transitively, so a definition used only by an unused response is unused as well. References nested in untyped values, such as vendor extensions, count too. - An entry of `securityDefinitions` is used when its name appears in the root `security` requirements or in the `security` requirements of an operation. An entry that has this rule disabled (see below) is always considered used, and so is everything it references. ## Configuration None. ## Examples **Good**. Every shared object is used, directly or through another one. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 securityDefinitions: apiKey: type: apiKey name: X-API-Key in: header security: - apiKey: [] paths: /pets: get: parameters: - $ref: '#/parameters/limit' responses: '200': $ref: '#/responses/Pets' parameters: limit: name: limit in: query type: integer responses: Pets: description: Pets schema: type: array items: $ref: '#/definitions/Pet' definitions: Pet: type: object properties: owner: $ref: '#/definitions/Owner' Owner: type: object ``` **Bad**. The `basic` security scheme, the `offset` parameter and the `Shelter` definition are not used. ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 securityDefinitions: apiKey: type: apiKey name: X-API-Key in: header basic: type: basic security: - apiKey: [] paths: /pets: get: parameters: - $ref: '#/parameters/limit' responses: '200': description: Pets parameters: limit: name: limit in: query type: integer offset: name: offset in: query type: integer definitions: Shelter: type: object ``` To keep a shared object that is meant to be referenced from other documents, disable the rule on it: ```yaml swagger: '2.0' info: title: Pets version: 1.0.0 paths: {} definitions: Library: x-lint-disable: [unused-shared-component] type: object properties: item: $ref: '#/definitions/Item' Item: type: object ``` ## Automatic fixes When invoked with `--fix`, this rule removes all unused entries. When every entry of a section is unused, the whole section is removed, so that no empty map is left behind. ## When Not to Use It You may disable this rule for documents that serve as libraries of shared objects, referenced from other documents. ## Compatibility This rule is compatible with Swagger 2.0 documents. OpenAPI 3.x documents are linted by the OpenAPI linter, which has a rule of the same name. --- # argument-description-defined Source: https://apigenie.pl/docs/linter/graphql/rules/argument-description-defined This rule checks that the arguments of fields and directives have a description. ## Rule Details Descriptions are part of the schema: introspection serves them to GraphiQL, IDEs, documentation generators and code generators. A missing or blank description is reported; type extensions have no descriptions and are not checked. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: argument-description-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable argument-description-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { user("The identifier of the user." id: ID!): User } ``` **Bad**. The linter reports `Argument 'Query.user(id:)' should have a description`. ```graphql type Query { user(id: ID!): User } ``` --- # argument-naming Source: https://apigenie.pl/docs/linter/graphql/rules/argument-naming This rule checks that the argument names follow a naming convention, camelCase by default. ## Rule Details Consistent names make a schema predictable for its clients: generated code, documentation and queries read the same everywhere. The rule reports a name that doesn't follow the convention, with the name converted to it as a suggestion. ## Configuration The configuration block can be a string, the convention, or an object with the properties: - `convention`: `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`) - `allowLeadingUnderscore`: whether leading underscores are ignored, e.g. in `_service`; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: argument-naming: [warning, { convention: snake_case, allowLeadingUnderscore: true }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable argument-naming` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(orderBy: String): [User] } ``` **Bad**. The linter reports `Argument name 'order_by' should be camelCase, e.g. 'orderBy'`. ```graphql type Query { users(order_by: String): [User] } ``` --- # arguments-sorted Source: https://apigenie.pl/docs/linter/graphql/rules/arguments-sorted This rule checks that the arguments of field and directive definitions are in alphabetical order. ## Rule Details Sorted definitions are easier to find and produce smaller diffs. Names are compared ignoring case; the first name out of order of each list is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: arguments-sorted: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable arguments-sorted` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(first: Int, last: Int): [User] } ``` **Bad**. The linter reports `Argument 'first' should come before 'last'`. ```graphql type Query { users(last: Int, first: Int): [User] } ``` ## When Not to Use It Disable this rule when the order of definitions is meaningful, e.g. the most important fields first. --- # custom-scalar-specified-by Source: https://apigenie.pl/docs/linter/graphql/rules/custom-scalar-specified-by This rule checks that custom scalars link their specification with `@specifiedBy`. ## Rule Details A custom scalar is a contract between the server and its clients; the URL of its specification tells clients how to parse and serialize it. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: custom-scalar-specified-by: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable custom-scalar-specified-by` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql scalar DateTime @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339") ``` **Bad**. The linter reports `Scalar type 'DateTime' should link its specification with @specifiedBy(url:)`. ```graphql scalar DateTime ``` --- # deprecation-date-defined Source: https://apigenie.pl/docs/linter/graphql/rules/deprecation-date-defined This rule checks that `@deprecated` directives have a deletion date that has not passed. ## Rule Details A deletion date tells clients how long a deprecated element will stay, and reminds the schema authors to remove it. The date is an argument of `@deprecated`, `deletionDate` by default, in the format `YYYY-MM-DD`. A missing or invalid date, or a date in the past, is reported. The argument has to be added to the definition of `@deprecated` that the server uses. ## Configuration The configuration block is an object with the properties: - `argumentName`: the argument with the date, `deletionDate` by default - `today`: the current date as `YYYY-MM-DD`, for reproducible runs; the date of the run by default ```yaml version: "1.0" commands: lint: graphql: rules: deprecation-date-defined: [warning, { argumentName: removeAfter }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable deprecation-date-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { login: String @deprecated(reason: "Use `name`.", deletionDate: "2099-01-01") } ``` **Bad**. The linter reports `The deprecation of field 'User.login' should have a deletion date ('deletionDate' argument)`. ```graphql type User { login: String @deprecated(reason: "Use `name`.") } ``` --- # deprecation-reason-defined Source: https://apigenie.pl/docs/linter/graphql/rules/deprecation-reason-defined This rule checks that `@deprecated` directives have a reason. ## Rule Details The reason is the only place where a schema tells its clients what to use instead of a deprecated field, argument or enum value. A missing or blank `reason` is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: deprecation-reason-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable deprecation-reason-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { login: String @deprecated(reason: "Use `name`.") } ``` **Bad**. The linter reports `The deprecation of field 'User.login' should have a reason`. ```graphql type User { login: String @deprecated } ``` --- # description-capitalized Source: https://apigenie.pl/docs/linter/graphql/rules/description-capitalized This rule checks that descriptions start with a capital letter. ## Rule Details Descriptions are sentences shown by documentation tools. Descriptions starting with a character other than a letter, such as a backtick or a digit, are not reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: description-capitalized: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable description-capitalized` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql "The users of the shop." type User { id: ID! } ``` **Bad**. The linter reports `The description of object type 'User' should start with a capital letter`. ```graphql "the users of the shop." type User { id: ID! } ``` --- # description-style Source: https://apigenie.pl/docs/linter/graphql/rules/description-style This rule checks that descriptions are written as block strings, or as single-line strings. ## Rule Details One style of descriptions makes a schema easier to read and to diff. Block strings (`"""..."""`) are the default, as they allow descriptions to grow over several lines. ## Configuration The configuration block can be a string, `block` or `inline`, or an object with the `style` property. ```yaml version: "1.0" commands: lint: graphql: rules: description-style: [error, inline] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable description-style` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql """ A user of the shop. """ type User { id: ID! } ``` **Bad**. The linter reports `The description of object type 'User' should be a block string ("""...""")`. ```graphql "A user of the shop." type User { id: ID! } ``` --- # directive-description-defined Source: https://apigenie.pl/docs/linter/graphql/rules/directive-description-defined This rule checks that directive definitions have a description. ## Rule Details Descriptions are part of the schema: introspection serves them to GraphiQL, IDEs, documentation generators and code generators. A missing or blank description is reported; type extensions have no descriptions and are not checked. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: directive-description-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable directive-description-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql "Requires the role to read the field." directive @auth(role: String) on FIELD_DEFINITION ``` **Bad**. The linter reports `Directive '@auth' should have a description`. ```graphql directive @auth(role: String) on FIELD_DEFINITION ``` --- # directive-naming Source: https://apigenie.pl/docs/linter/graphql/rules/directive-naming This rule checks that directive names follow a naming convention, camelCase by default. ## Rule Details Consistent names make a schema predictable for its clients: generated code, documentation and queries read the same everywhere. The rule reports a name that doesn't follow the convention, with the name converted to it as a suggestion. ## Configuration The configuration block can be a string, the convention, or an object with the properties: - `convention`: `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`) - `allowLeadingUnderscore`: whether leading underscores are ignored, e.g. in `_service`; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: directive-naming: [warning, { convention: snake_case, allowLeadingUnderscore: true }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable directive-naming` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql directive @cacheControl on FIELD_DEFINITION ``` **Bad**. The linter reports `Directive name 'CacheControl' should be camelCase, e.g. 'cacheControl'`. ```graphql directive @CacheControl on FIELD_DEFINITION ``` --- # enum-value-description-defined Source: https://apigenie.pl/docs/linter/graphql/rules/enum-value-description-defined This rule checks that enum values have a description. ## Rule Details Descriptions are part of the schema: introspection serves them to GraphiQL, IDEs, documentation generators and code generators. A missing or blank description is reported; type extensions have no descriptions and are not checked. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: enum-value-description-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable enum-value-description-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql enum Kind { "A cat." CAT } ``` **Bad**. The linter reports `Enum value 'Kind.CAT' should have a description`. ```graphql enum Kind { CAT } ``` --- # enum-value-naming Source: https://apigenie.pl/docs/linter/graphql/rules/enum-value-naming This rule checks that enum value names follow a naming convention, CONST_CASE by default. ## Rule Details Consistent names make a schema predictable for its clients: generated code, documentation and queries read the same everywhere. The rule reports a name that doesn't follow the convention, with the name converted to it as a suggestion. ## Configuration The configuration block can be a string, the convention, or an object with the properties: - `convention`: `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`) - `allowLeadingUnderscore`: whether leading underscores are ignored, e.g. in `_service`; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: enum-value-naming: [warning, { convention: snake_case, allowLeadingUnderscore: true }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable enum-value-naming` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql enum Kind { SMALL_DOG } ``` **Bad**. The linter reports `Enum value name 'smallDog' should be CONST_CASE, e.g. 'SMALL_DOG'`. ```graphql enum Kind { smallDog } ``` --- # enum-values-sorted Source: https://apigenie.pl/docs/linter/graphql/rules/enum-values-sorted This rule checks that the values of each enum type definition and extension are in alphabetical order. ## Rule Details Sorted definitions are easier to find and produce smaller diffs. Names are compared ignoring case; the first name out of order of each list is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: enum-values-sorted: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable enum-values-sorted` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql enum Kind { BIRD, CAT } ``` **Bad**. The linter reports `Enum value 'BIRD' should come before 'CAT'`. ```graphql enum Kind { CAT, BIRD } ``` ## When Not to Use It Disable this rule when the order of definitions is meaningful, e.g. the most important fields first. --- # field-description-defined Source: https://apigenie.pl/docs/linter/graphql/rules/field-description-defined This rule checks that the fields of object and interface types have a description. ## Rule Details Descriptions are part of the schema: introspection serves them to GraphiQL, IDEs, documentation generators and code generators. A missing or blank description is reported; type extensions have no descriptions and are not checked. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: field-description-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable field-description-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { "The identifier of the user." id: ID! } ``` **Bad**. The linter reports `Field 'User.id' should have a description`. ```graphql type User { id: ID! } ``` --- # field-naming Source: https://apigenie.pl/docs/linter/graphql/rules/field-naming This rule checks that the field names follow a naming convention, camelCase by default. ## Rule Details Consistent names make a schema predictable for its clients: generated code, documentation and queries read the same everywhere. The rule reports a name that doesn't follow the convention, with the name converted to it as a suggestion. ## Configuration The configuration block can be a string, the convention, or an object with the properties: - `convention`: `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`) - `allowLeadingUnderscore`: whether leading underscores are ignored, e.g. in `_service`; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: field-naming: [warning, { convention: snake_case, allowLeadingUnderscore: true }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable field-naming` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { firstName: String } ``` **Bad**. The linter reports `Field name 'first_name' should be camelCase, e.g. 'firstName'`. ```graphql type User { first_name: String } ``` --- # fields-on-correct-type Source: https://apigenie.pl/docs/linter/graphql/rules/fields-on-correct-type This rule checks that the fields selected by operations are defined by their parent types. ## Rule Details A field that its type doesn't define is rejected by the server. `__typename`, and `__schema` and `__type` on the query root type, are allowed. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: fields-on-correct-type: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable fields-on-correct-type` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Me { me { id name } } ``` **Bad**. The linter reports `Cannot query field "nmae" on type "User".`. ```graphql query Me { me { id nmae } } ``` ## Compatibility The rule checks operations against the schema files named by the `schema` property of the configuration, or against the type system of their own document. It reports nothing without a schema. --- # fields-sorted Source: https://apigenie.pl/docs/linter/graphql/rules/fields-sorted This rule checks that the fields of each object and interface type definition and extension are in alphabetical order. ## Rule Details Sorted definitions are easier to find and produce smaller diffs. Names are compared ignoring case; the first name out of order of each list is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: fields-sorted: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable fields-sorted` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { email: String, id: ID!, name: String } ``` **Bad**. The linter reports `Field 'email' should come before 'id'`. ```graphql type User { id: ID!, email: String } ``` ## When Not to Use It Disable this rule when the order of definitions is meaningful, e.g. the most important fields first. --- # fragment-naming Source: https://apigenie.pl/docs/linter/graphql/rules/fragment-naming This rule checks that fragment names follow a naming convention, PascalCase by default. ## Rule Details Consistent names make a schema predictable for its clients: generated code, documentation and queries read the same everywhere. The rule reports a name that doesn't follow the convention, with the name converted to it as a suggestion. ## Configuration The configuration block can be a string, the convention, or an object with the properties: - `convention`: `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`) - `allowLeadingUnderscore`: whether leading underscores are ignored, e.g. in `_service`; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: fragment-naming: [warning, { convention: snake_case, allowLeadingUnderscore: true }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable fragment-naming` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql fragment UserFields on User { id } ``` **Bad**. The linter reports `Fragment name 'userFields' should be PascalCase, e.g. 'UserFields'`. ```graphql fragment userFields on User { id } ``` --- # input-field-description-defined Source: https://apigenie.pl/docs/linter/graphql/rules/input-field-description-defined This rule checks that the fields of input object types have a description. ## Rule Details Descriptions are part of the schema: introspection serves them to GraphiQL, IDEs, documentation generators and code generators. A missing or blank description is reported; type extensions have no descriptions and are not checked. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: input-field-description-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable input-field-description-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql input UserFilter { "Part of the name." name: String } ``` **Bad**. The linter reports `Input field 'UserFilter.name' should have a description`. ```graphql input UserFilter { name: String } ``` --- # input-field-naming Source: https://apigenie.pl/docs/linter/graphql/rules/input-field-naming This rule checks that the input field names follow a naming convention, camelCase by default. ## Rule Details Consistent names make a schema predictable for its clients: generated code, documentation and queries read the same everywhere. The rule reports a name that doesn't follow the convention, with the name converted to it as a suggestion. ## Configuration The configuration block can be a string, the convention, or an object with the properties: - `convention`: `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`) - `allowLeadingUnderscore`: whether leading underscores are ignored, e.g. in `_service`; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: input-field-naming: [warning, { convention: snake_case, allowLeadingUnderscore: true }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable input-field-naming` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql input UserFilter { firstName: String } ``` **Bad**. The linter reports `Input field name 'FirstName' should be camelCase, e.g. 'firstName'`. ```graphql input UserFilter { FirstName: String } ``` --- # input-fields-sorted Source: https://apigenie.pl/docs/linter/graphql/rules/input-fields-sorted This rule checks that the fields of each input object type definition and extension are in alphabetical order. ## Rule Details Sorted definitions are easier to find and produce smaller diffs. Names are compared ignoring case; the first name out of order of each list is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: input-fields-sorted: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable input-fields-sorted` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql input UserFilter { kind: Kind, name: String } ``` **Bad**. The linter reports `Input field 'kind' should come before 'name'`. ```graphql input UserFilter { name: String, kind: Kind } ``` ## When Not to Use It Disable this rule when the order of definitions is meaningful, e.g. the most important fields first. --- # input-no-circular-non-null Source: https://apigenie.pl/docs/linter/graphql/rules/input-no-circular-non-null This rule checks that input object types don't reference themselves through non-null fields. ## Rule Details No value can satisfy an input object type that requires itself, directly or through other input types, through non-null fields; the specification forbids it. Nullable and list fields break the cycle. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: input-no-circular-non-null: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable input-no-circular-non-null` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql input Filter { and: [Filter!], not: Filter } ``` **Bad**. The linter reports `Cannot reference Input Object "Filter" within itself through a series of non-null fields: "not".`. ```graphql input Filter { not: Filter! } ``` --- # input-output-types Source: https://apigenie.pl/docs/linter/graphql/rules/input-output-types This rule checks that arguments and input fields have input types, and fields have output types, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: input-output-types: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable input-output-types` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(filter: UserFilter): [User] } input UserFilter { name: String } type User { id: ID! } ``` **Bad**. The linter reports `The type of Query.users(filter:) must be Input Type but got: User.`. ```graphql type Query { users(filter: User): [User] } type User { id: ID! } ``` --- # input-type-suffix Source: https://apigenie.pl/docs/linter/graphql/rules/input-type-suffix This rule checks that input object type names end with a suffix, `Input` by default. ## Rule Details The suffix tells input types from the output types they often mirror, e.g. `User` and `UserInput`. Type extensions are not checked. ## Configuration The configuration block can be a string, the suffix, or an object with the `suffix` property. The suffix cannot be empty. ```yaml version: "1.0" commands: lint: graphql: rules: input-type-suffix: [warning, Params] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable input-type-suffix` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql input CreateUserInput { name: String } ``` **Bad**. The linter reports `Input object type 'CreateUser' should end with 'Input'`. ```graphql input CreateUser { name: String } ``` --- # interface-implementation Source: https://apigenie.pl/docs/linter/graphql/rules/interface-implementation This rule checks that object and interface types implement their interfaces with all their fields, compatible field types and matching arguments, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: interface-implementation: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable interface-implementation` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql interface Node { id: ID! } type User implements Node { id: ID! } ``` **Bad**. The linter reports `Interface field Node.id expected but User does not provide it.`. ```graphql interface Node { id: ID! } type User implements Node { name: String } ``` --- # known-argument-names Source: https://apigenie.pl/docs/linter/graphql/rules/known-argument-names This rule checks that the arguments given to fields and directives are defined by them. ## Rule Details Unknown arguments are rejected by the server. The arguments of `@skip`, `@include` and of the directives of the schema are checked. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: known-argument-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable known-argument-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query User { user(id: 1) { id } } ``` **Bad**. The linter reports `Unknown argument "userId" on field "Query.user".`. ```graphql query User { user(userId: 1) { id } } ``` ## Compatibility The rule checks operations against the schema files named by the `schema` property of the configuration, or against the type system of their own document. It reports nothing without a schema. --- # known-directive-locations Source: https://apigenie.pl/docs/linter/graphql/rules/known-directive-locations This rule checks that directives are used at one of the locations of their definition, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: known-directive-locations: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable known-directive-locations` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql directive @auth on FIELD_DEFINITION type Query { a: Int @auth } ``` **Bad**. The linter reports `Directive "@auth" may not be used on OBJECT.`. ```graphql directive @auth on FIELD_DEFINITION type Query @auth { a: Int } ``` ## Compatibility The rule applies to schemas and to operations. The built-in directives and those defined in the document are known; other directives are not checked. --- # known-fragment-names Source: https://apigenie.pl/docs/linter/graphql/rules/known-fragment-names This rule checks that fragment spreads name fragments of the document, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: known-fragment-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable known-fragment-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q { user { ...A } } fragment A on User { id } ``` **Bad**. The linter reports `Unknown fragment "A".`. ```graphql query Q { user { ...A } } ``` ## Compatibility The rule reports references to types and fragments that are not in the document only when the configuration sets `self_contained: true`, since schemas and operations are often split across files. --- # known-types Source: https://apigenie.pl/docs/linter/graphql/rules/known-types This rule checks that the types referenced by type system definitions are built-in scalars or defined in the document, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: known-types: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable known-types` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { user: User } type User { id: ID! } ``` **Bad**. The linter reports `Unknown type "User".`. ```graphql type Query { user: User } ``` ## Compatibility The rule reports references to types and fragments that are not in the document only when the configuration sets `self_contained: true`, since schemas and operations are often split across files. --- # lone-anonymous-operation Source: https://apigenie.pl/docs/linter/graphql/rules/lone-anonymous-operation This rule checks that an anonymous operation is the only operation of the document, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: lone-anonymous-operation: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable lone-anonymous-operation` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql { a } ``` **Bad**. The linter reports `This anonymous operation must be the only defined operation.`. ```graphql { a } query B { b } ``` --- # lone-executable-definition Source: https://apigenie.pl/docs/linter/graphql/rules/lone-executable-definition This rule checks that a document holds a single operation or fragment. ## Rule Details One definition per file keeps files small, and lets tools map files to operations. ## Configuration The configuration block is an object with the `ignore` property: the kinds of definitions (`query`, `mutation`, `subscription`, `fragment`) that may be added to a document. ```yaml version: "1.0" commands: lint: graphql: rules: lone-executable-definition: [error, { ignore: [fragment] }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable lone-executable-definition` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Me { me { id } } ``` **Bad**. The linter reports `Operation 'Users' should be in a document of its own; a document should hold one operation or fragment`. ```graphql query Me { me { id } } query Users { users { id } } ``` --- # lone-schema-definition Source: https://apigenie.pl/docs/linter/graphql/rules/lone-schema-definition This rule checks that a document has at most one schema definition, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: lone-schema-definition: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable lone-schema-definition` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql schema { query: Query } type Query { a: Int } ``` **Bad**. The linter reports `Must provide only one schema definition.`. ```graphql schema { query: Query } schema { query: Query } type Query { a: Int } ``` --- # match-document-filename Source: https://apigenie.pl/docs/linter/graphql/rules/match-document-filename This rule checks that the file name of a document with one operation or fragment is the name of the definition. ## Rule Details Files named after their operation are easy to find. Documents with several definitions are not checked (see `lone-executable-definition`). ## Configuration The configuration block is an object with the properties: - `style`: the case of the file name, `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`); the name as written by default - `fileExtension`: the extension the file must have, e.g. `.gql`; any by default ```yaml version: "1.0" commands: lint: graphql: rules: match-document-filename: [error, { style: kebab-case, fileExtension: .graphql }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable match-document-filename` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql # GetUser.graphql query GetUser { me { id } } ``` **Bad**. The linter reports `The file name 'user.graphql' should match the query 'GetUser', e.g. 'GetUser.graphql'`. ```graphql # user.graphql query GetUser { me { id } } ``` --- # mutation-input-argument Source: https://apigenie.pl/docs/linter/graphql/rules/mutation-input-argument This rule checks that mutation fields take a single argument, `input`, of a non-null input object type. ## Rule Details A single input object can evolve without breaking clients: fields are added to the input type rather than as new arguments of the mutation. Mutations without arguments are not reported. ## Configuration The configuration block is an object with the properties: - `argumentName`: the name of the argument, `input` by default - `requireNonNull`: whether the argument must be non-null, `true` by default ```yaml version: "1.0" commands: lint: graphql: rules: mutation-input-argument: [error, { argumentName: data, requireNonNull: false }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable mutation-input-argument` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Mutation { createUser(input: CreateUserInput!): CreateUserPayload } input CreateUserInput { name: String } ``` **Bad**. The linter reports `Field 'Mutation.createUser' should take a single argument named 'input'`. ```graphql type Mutation { createUser(name: String, email: String): CreateUserPayload } ``` --- # mutation-payload-suffix Source: https://apigenie.pl/docs/linter/graphql/rules/mutation-payload-suffix This rule checks that the result types of mutation fields end with a suffix, `Payload` by default. ## Rule Details Payload types are specific to their mutation; the suffix tells them from the domain types they contain. Scalar and enum results are not reported (see `no-scalar-result-on-mutation`). ## Configuration The configuration block can be a string, the suffix, or an object with the `suffix` property. ```yaml version: "1.0" commands: lint: graphql: rules: mutation-payload-suffix: [error, Result] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable mutation-payload-suffix` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Mutation { createUser(input: CreateUserInput!): CreateUserPayload } ``` **Bad**. The linter reports `The result type 'User' of field 'Mutation.createUser' should end with 'Payload'`. ```graphql type Mutation { createUser(input: CreateUserInput!): User } ``` --- # no-deprecated Source: https://apigenie.pl/docs/linter/graphql/rules/no-deprecated This rule checks that operations don't use deprecated fields, arguments and enum values. ## Rule Details Deprecated elements will be removed from the schema; operations using them will break. The message includes the reason of the deprecation. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-deprecated: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-deprecated` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Me { me { name } } ``` **Bad**. The linter reports `The field 'User.login' is deprecated: Use name.`. ```graphql query Me { me { login } } ``` ## Compatibility The rule checks operations against the schema files named by the `schema` property of the configuration, or against the type system of their own document. It reports nothing without a schema. --- # no-deprecated-required-argument Source: https://apigenie.pl/docs/linter/graphql/rules/no-deprecated-required-argument This rule checks that required arguments and input fields are not deprecated. ## Rule Details Clients cannot stop sending a required argument (non-null and without a default value), so deprecating it has no effect; the specification forbids it. Make the argument optional, or give it a default value, before deprecating it. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-deprecated-required-argument: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-deprecated-required-argument` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(limit: Int = 10 @deprecated(reason: "Use `first`.")): [User] } ``` **Bad**. The linter reports `Required argument 'Query.users(limit:)' cannot be deprecated; make it optional or give it a default value first`. ```graphql type Query { users(limit: Int! @deprecated(reason: "Use `first`.")): [User] } ``` --- # no-fragment-cycles Source: https://apigenie.pl/docs/linter/graphql/rules/no-fragment-cycles This rule checks that fragments don't spread themselves, directly or indirectly, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-fragment-cycles: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-fragment-cycles` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql fragment A on User { id } ``` **Bad**. The linter reports `Cannot spread fragment "A" within itself via "B".`. ```graphql fragment A on User { ...B } fragment B on User { ...A } ``` --- # no-get-prefix-on-query-fields Source: https://apigenie.pl/docs/linter/graphql/rules/no-get-prefix-on-query-fields This rule checks that the fields of the query root type are not named `getSomething`. ## Rule Details Query fields are nouns: the operation type already says that they read data. `user(id:)` is the convention, `getUser(id:)` comes from RPC APIs. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-get-prefix-on-query-fields: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-get-prefix-on-query-fields` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { user(id: ID!): User } ``` **Bad**. The linter reports `Query field 'getUser' should not start with 'get', e.g. 'user'`. ```graphql type Query { getUser(id: ID!): User } ``` --- # no-hashtag-description Source: https://apigenie.pl/docs/linter/graphql/rules/no-hashtag-description This rule checks that definitions are documented with descriptions rather than with `#` comments. ## Rule Details Comments are dropped by GraphQL servers: introspection, GraphiQL and generated code never show them. The rule reports a `#` comment directly above a definition that can have a description and has none. Comments separated from the definition by a blank line, and `apigenie-lint-disable` comments, are not reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-hashtag-description: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-hashtag-description` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql "The users of the shop." type User { id: ID! } ``` **Bad**. The linter reports `Use a description instead of a comment to document object type 'User'`. ```graphql # The users of the shop. type User { id: ID! } ``` --- # no-mixed-definitions Source: https://apigenie.pl/docs/linter/graphql/rules/no-mixed-definitions This rule checks that a document holds either a schema or operations, not both. ## Rule Details Schemas and operations have different owners and lifecycles: schema files are served, operation files are compiled into clients. Each operation and fragment of a document with type system definitions is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-mixed-definitions: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-mixed-definitions` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { me: User } ``` **Bad**. The linter reports `Operation 'Me' should not be in a document with type system definitions`. ```graphql type Query { me: User } query Me { me { id } } ``` --- # no-one-place-fragments Source: https://apigenie.pl/docs/linter/graphql/rules/no-one-place-fragments This rule checks that fragments are spread more than once in the document. ## Rule Details A fragment spread once adds indirection without reuse; its selections are better inlined. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-one-place-fragments: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-one-place-fragments` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q { a: user(id: 1) { ...UserFields } b: user(id: 2) { ...UserFields } } fragment UserFields on User { id } ``` **Bad**. The linter reports `Fragment 'UserFields' is spread only once; inline its selections`. ```graphql query Q { me { ...UserFields } } fragment UserFields on User { id } ``` --- # no-root-type Source: https://apigenie.pl/docs/linter/graphql/rules/no-root-type This rule checks that the schema doesn't have the root operation types the configuration disallows. ## Rule Details Some APIs are read-only, or don't support subscriptions. The rule reports the disallowed root operation types; with the default configuration it reports nothing. ## Configuration The configuration block is an object with the `disallow` property: a list of `mutation` and `subscription`. ```yaml version: "1.0" commands: lint: graphql: rules: no-root-type: [error, { disallow: [mutation, subscription] }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-root-type` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { me: User } ``` **Bad**. With the options `{ disallow: [mutation] }`, the linter reports `The schema should not have a mutation root operation type ('Mutation')`. ```graphql type Query { me: User } type Mutation { logout: Boolean } ``` --- # no-scalar-result-on-mutation Source: https://apigenie.pl/docs/linter/graphql/rules/no-scalar-result-on-mutation This rule checks that mutation fields return object types rather than scalars. ## Rule Details A payload object can grow, e.g. with the changed objects or with errors, while a scalar result can only be replaced by a breaking change. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-scalar-result-on-mutation: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-scalar-result-on-mutation` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Mutation { logout: LogoutPayload } type LogoutPayload { success: Boolean } ``` **Bad**. The linter reports `Field 'Mutation.logout' should return an object type rather than the scalar 'Boolean'`. ```graphql type Mutation { logout: Boolean } ``` --- # no-type-suffix Source: https://apigenie.pl/docs/linter/graphql/rules/no-type-suffix This rule checks that type names don't end with a redundant suffix such as `Type` or `Enum`. ## Rule Details The kind of a type is already part of its definition and of introspection; `UserType` or `KindEnum` only make names longer. By default the suffixes are `Type`, `Object`, `Interface`, `Union`, `Enum`, `Scalar` and `Input`; `Input` is allowed on input object types (see `input-type-suffix`). ## Configuration The configuration block is an object with the `suffixes` property, the list of suffixes that are reported. ```yaml version: "1.0" commands: lint: graphql: rules: no-type-suffix: [error, { suffixes: [Type, Model] }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-type-suffix` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { id: ID! } enum Kind { CAT } ``` **Bad**. The linter reports `Object type 'UserType' should not end with 'Type'`. ```graphql type UserType { id: ID! } ``` --- # no-typename-prefix Source: https://apigenie.pl/docs/linter/graphql/rules/no-typename-prefix This rule checks that field names don't start with the name of their type, as in `User.userId`. ## Rule Details The type already gives the context of a field: `user.id` reads better than `user.userId`. A field is reported when its name starts with the type name in camelCase followed by an upper-case letter, a digit or an underscore, so `User.users` is not reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-typename-prefix: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-typename-prefix` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { id: ID!, name: String } ``` **Bad**. The linter reports `Field 'userId' should not start with the name of its type 'User'`. ```graphql type User { userId: ID! } ``` --- # no-undefined-variables Source: https://apigenie.pl/docs/linter/graphql/rules/no-undefined-variables This rule checks that the variables used by an operation, including in the fragments it spreads, are defined by the operation, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-undefined-variables: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-undefined-variables` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q($id: ID!) { user(id: $id) { id } } ``` **Bad**. The linter reports `Variable "$id" is not defined by operation "Q".`. ```graphql query Q { user(id: $id) { id } } ``` --- # no-unused-fragments Source: https://apigenie.pl/docs/linter/graphql/rules/no-unused-fragments This rule checks that every fragment is spread by an operation, directly or through other fragments, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-unused-fragments: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-unused-fragments` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q { user { ...A } } fragment A on User { id } ``` **Bad**. The linter reports `Fragment "A" is never used.`. ```graphql query Q { user { id } } fragment A on User { id } ``` ## Compatibility The rule reports references to types and fragments that are not in the document only when the configuration sets `self_contained: true`, since schemas and operations are often split across files. --- # no-unused-variables Source: https://apigenie.pl/docs/linter/graphql/rules/no-unused-variables This rule checks that the variables defined by an operation are used, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: no-unused-variables: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable no-unused-variables` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q($id: ID!) { user(id: $id) { id } } ``` **Bad**. The linter reports `Variable "$id" is never used in operation "Q".`. ```graphql query Q($id: ID!) { me { id } } ``` --- # one-field-subscriptions Source: https://apigenie.pl/docs/linter/graphql/rules/one-field-subscriptions This rule checks that subscriptions select a single root field, as the specification requires. ## Rule Details A subscription maps one event stream to responses; selecting several root fields, or an introspection field, is invalid. Inline fragments and the fragments of the document are followed. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: one-field-subscriptions: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable one-field-subscriptions` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql subscription OnMessage { messageAdded { id } } ``` **Bad**. The linter reports `Subscription "OnEvents" must select only one top level field.`. ```graphql subscription OnEvents { messageAdded { id } userJoined { id } } ``` --- # oneof-fields-nullable Source: https://apigenie.pl/docs/linter/graphql/rules/oneof-fields-nullable This rule checks that the fields of `@oneOf` input object types are nullable and have no default value. ## Rule Details Exactly one field of a `@oneOf` input object is given; a non-null field or a default value would make every value invalid, as the specification of `@oneOf` requires. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: oneof-fields-nullable: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable oneof-fields-nullable` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql input UserBy @oneOf { id: ID, email: String } ``` **Bad**. The linter reports `Input field 'UserBy.id' of the @oneOf input object type should be nullable`. ```graphql input UserBy @oneOf { id: ID!, email: String } ``` --- # operation-known-types Source: https://apigenie.pl/docs/linter/graphql/rules/operation-known-types This rule checks that the types of variables and the type conditions of fragments are types of the schema. ## Rule Details Unknown types are rejected by the server. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: operation-known-types: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable operation-known-types` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Users($kind: Kind) { users(kind: $kind) { id } } ``` **Bad**. The linter reports `Unknown type "UserKind".`. ```graphql query Users($kind: UserKind) { users(kind: $kind) { id } } ``` ## Compatibility The rule checks operations against the schema files named by the `schema` property of the configuration, or against the type system of their own document. It reports nothing without a schema. --- # operation-name-defined Source: https://apigenie.pl/docs/linter/graphql/rules/operation-name-defined This rule checks that operations have names. ## Rule Details Names identify operations in server logs, metrics, persisted queries and developer tools. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: operation-name-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable operation-name-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Me { me { id } } ``` **Bad**. The linter reports `The query should have a name`. ```graphql { me { id } } ``` --- # operation-name-suffix Source: https://apigenie.pl/docs/linter/graphql/rules/operation-name-suffix This rule checks that operation and fragment names don't end with a redundant suffix. ## Rule Details The keyword already gives the kind of a definition: `query GetUserQuery` repeats it. The default suffixes are `Query`, `Mutation`, `Subscription` and `Fragment`. ## Configuration The configuration block is an object with the `suffixes` property, the list of suffixes that are reported. ```yaml version: "1.0" commands: lint: graphql: rules: operation-name-suffix: [error, { suffixes: [Query, Mutation] }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable operation-name-suffix` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query GetUser { me { id } } ``` **Bad**. The linter reports `Operation 'GetUserQuery' should not end with 'Query'`. ```graphql query GetUserQuery { me { id } } ``` --- # operation-naming Source: https://apigenie.pl/docs/linter/graphql/rules/operation-naming This rule checks that operation names follow a naming convention, PascalCase by default. ## Rule Details Consistent names make a schema predictable for its clients: generated code, documentation and queries read the same everywhere. The rule reports a name that doesn't follow the convention, with the name converted to it as a suggestion. ## Configuration The configuration block can be a string, the convention, or an object with the properties: - `convention`: `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`) - `allowLeadingUnderscore`: whether leading underscores are ignored, e.g. in `_service`; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: operation-naming: [warning, { convention: snake_case, allowLeadingUnderscore: true }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable operation-naming` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query GetUser { me { id } } ``` **Bad**. The linter reports `Operation name 'get_user' should be PascalCase, e.g. 'GetUser'`. ```graphql query get_user { me { id } } ``` --- # possible-type-extensions Source: https://apigenie.pl/docs/linter/graphql/rules/possible-type-extensions This rule checks that extended types have the same kind as their definition, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: possible-type-extensions: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable possible-type-extensions` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { id: ID! } extend type User { name: String } ``` **Bad**. The linter reports `Cannot extend non-object type "User".`. ```graphql scalar User extend type User { name: String } ``` --- # query-root-defined Source: https://apigenie.pl/docs/linter/graphql/rules/query-root-defined This rule checks that the schema has a query root operation type. ## Rule Details A schema must have a query root type. Without a schema definition, the type named `Query` is the query root type. Documents that hold only a part of a schema should not use this rule. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: query-root-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable query-root-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { me: User } type User { id: ID! } ``` **Bad**. The linter reports `The schema should have a query root operation type (type Query)`. ```graphql type User { id: ID! } ``` ## When Not to Use It Disable this rule when the schema is split across files, and the query root type is in another file. --- # relay-arguments Source: https://apigenie.pl/docs/linter/graphql/rules/relay-arguments This rule checks that fields returning connections take the pagination arguments of the Relay Cursor Connections specification. ## Rule Details Forward pagination takes `first: Int` and `after` (String or a scalar), backward pagination `last: Int` and `before`. By default both pairs are required. ## Configuration The configuration block is an object with the `includeBoth` property: whether both pairs of arguments are required (`true` by default) or one is enough. ```yaml version: "1.0" commands: lint: graphql: rules: relay-arguments: [error, { includeBoth: false }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable relay-arguments` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(first: Int, after: String, last: Int, before: String): UserConnection } interface Node { id: ID! } type User implements Node { id: ID! } type UserConnection { edges: [UserEdge], pageInfo: PageInfo! } type UserEdge { node: User, cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } ``` **Bad**. The linter reports `Field 'Query.users' returns a connection and should take the arguments first: Int, after: String, last: Int and before: String`. ```graphql type Query { users: UserConnection } ``` --- # relay-connection-types Source: https://apigenie.pl/docs/linter/graphql/rules/relay-connection-types This rule checks that connection types follow the Relay Cursor Connections specification. ## Rule Details Types named `*Connection` are object types with an `edges` field returning a list of edge types and a `pageInfo: PageInfo!` field. Object types that have both fields are named `*Connection`. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: relay-connection-types: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable relay-connection-types` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(first: Int, after: String, last: Int, before: String): UserConnection } interface Node { id: ID! } type User implements Node { id: ID! } type UserConnection { edges: [UserEdge], pageInfo: PageInfo! } type UserEdge { node: User, cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } ``` **Bad**. The linter reports `Connection type 'UserConnection' should have an 'edges' field returning a list of edges`. ```graphql type UserConnection { users: [User] } ``` --- # relay-edge-types Source: https://apigenie.pl/docs/linter/graphql/rules/relay-edge-types This rule checks that the edge types of connections follow the Relay Cursor Connections specification. ## Rule Details The edge types returned by the `edges` field of connection types are object types with a `node` field that is not a list and a `cursor` field of type String or a scalar. ## Configuration The configuration block is an object with the boolean properties: - `withEdgeSuffix`: edge type names end with `Edge`; `true` by default - `shouldImplementNode`: the object types of nodes implement the `Node` interface; `true` by default - `listTypeCanWrapOnlyEdgeType`: the list fields of connection types return edge types; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: relay-edge-types: [error, { shouldImplementNode: false }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable relay-edge-types` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(first: Int, after: String, last: Int, before: String): UserConnection } interface Node { id: ID! } type User implements Node { id: ID! } type UserConnection { edges: [UserEdge], pageInfo: PageInfo! } type UserEdge { node: User, cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } ``` **Bad**. The linter reports `Edge type 'UserEdge' should have a 'node' field that is not a list`. ```graphql type UserConnection { edges: [UserEdge], pageInfo: PageInfo! } type UserEdge { node: [User] } ``` --- # relay-page-info Source: https://apigenie.pl/docs/linter/graphql/rules/relay-page-info This rule checks that a schema with connections has a `PageInfo` type as the Relay Cursor Connections specification defines it. ## Rule Details `PageInfo` is an object type with the fields `hasNextPage: Boolean!`, `hasPreviousPage: Boolean!`, and the nullable `startCursor` and `endCursor` of type String or a scalar. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: relay-page-info: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable relay-page-info` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(first: Int, after: String, last: Int, before: String): UserConnection } interface Node { id: ID! } type User implements Node { id: ID! } type UserConnection { edges: [UserEdge], pageInfo: PageInfo! } type UserEdge { node: User, cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } ``` **Bad**. The linter reports `PageInfo should have a field 'hasNextPage: Boolean!'`. ```graphql type UserConnection { edges: [UserEdge], pageInfo: PageInfo! } type PageInfo { hasNextPage: Boolean } ``` --- # require-selections Source: https://apigenie.pl/docs/linter/graphql/rules/require-selections This rule checks that selections of objects with an identifier select it. ## Rule Details Clients such as Apollo Client and Relay cache and update objects by their identifier; an object fetched without it cannot be normalized. The identifier may be selected directly, in inline fragments or in the fragments of the document. ## Configuration The configuration block can be a string, the name of the identifier field, or an object with the `fieldName` property. The name is `id` by default. ```yaml version: "1.0" commands: lint: graphql: rules: require-selections: [error, uuid] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable require-selections` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Me { me { id name } } ``` **Bad**. The linter reports `Field 'me' should select 'id', which identifies its type 'User'`. ```graphql query Me { me { name } } ``` ## Compatibility The rule checks operations against the schema files named by the `schema` property of the configuration, or against the type system of their own document. It reports nothing without a schema. --- # required-arguments-provided Source: https://apigenie.pl/docs/linter/graphql/rules/required-arguments-provided This rule checks that the required arguments of fields and directives are given. ## Rule Details Arguments that are non-null and without default value are required; the server rejects operations that omit them. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: required-arguments-provided: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable required-arguments-provided` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query User { user(id: 1) { id } } ``` **Bad**. The linter reports `Field "user" argument "id" of type "ID!" is required, but it was not provided.`. ```graphql query User { user { id } } ``` ## Compatibility The rule checks operations against the schema files named by the `schema` property of the configuration, or against the type system of their own document. It reports nothing without a schema. --- # reserved-names Source: https://apigenie.pl/docs/linter/graphql/rules/reserved-names This rule checks that the names of types, fields, arguments, enum values and directives don't start with "__", as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: reserved-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable reserved-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { id: ID! } ``` **Bad**. The linter reports `Name "__id" must not begin with "__", which is reserved by GraphQL introspection.`. ```graphql type User { __id: ID! } ``` --- # root-fields-nullable Source: https://apigenie.pl/docs/linter/graphql/rules/root-fields-nullable This rule checks that the fields of the query and mutation root types are nullable. ## Rule Details An error in a non-null field nulls its parent; for a root field, the parent is the whole `data` of the response. Nullable root fields keep the other fields of a response when one of them fails. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: root-fields-nullable: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable root-fields-nullable` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { user(id: ID!): User } ``` **Bad**. The linter reports `Field 'Query.user' of the query root type should be nullable, not 'User!'`. ```graphql type Query { user(id: ID!): User! } ``` --- # selection-set-depth Source: https://apigenie.pl/docs/linter/graphql/rules/selection-set-depth This rule checks that selection sets are nested at most 7 levels deep. ## Rule Details Deep queries are expensive to execute and often a sign of over-fetching. The fields of the fragments of the document are counted where they are spread; the root fields have the depth 1. ## Configuration The configuration block is an object with the properties: - `maxDepth`: the maximum depth, from 1 to 1000, 7 by default - `ignore`: the names of fields whose selections are not counted, e.g. `edges` ```yaml version: "1.0" commands: lint: graphql: rules: selection-set-depth: [warning, { maxDepth: 10, ignore: [edges, node] }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable selection-set-depth` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Me { me { friends { name } } } ``` **Bad**. The linter reports `Field 'name' of operation 'Me' is nested deeper than the maximum depth of 7`. ```graphql query Me { me { friends { friends { friends { friends { friends { friends { name } } } } } } } } ``` --- # selections-sorted Source: https://apigenie.pl/docs/linter/graphql/rules/selections-sorted This rule checks that the fields of each selection set of operations and fragments are in alphabetical order. ## Rule Details Sorted definitions are easier to find and produce smaller diffs. Names are compared ignoring case; the first name out of order of each list is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: selections-sorted: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable selections-sorted` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Me { me { id name } } ``` **Bad**. The linter reports `Field 'id' should come before 'name'`. ```graphql query Me { me { name id } } ``` ## When Not to Use It Disable this rule when the order of definitions is meaningful, e.g. the most important fields first. --- # spell-check Source: https://apigenie.pl/docs/linter/graphql/rules/spell-check This rule checks the spelling of descriptions and comments. ## Rule Details Descriptions are the documentation of a schema. The rule checks them, as Markdown, and the comments of the definitions that can have descriptions, with a built-in English dictionary. Code in backticks, identifiers and URLs are skipped. ## Configuration The configuration block is an object with the properties: - `dictionary` and `affix`: the paths of Hunspell dictionary and affix files that replace the built-in dictionary; they must be given together - `words`: extra words, e.g. product names - `suggestions`: the maximum number of suggestions, from 0 to 10, 3 by default ```yaml version: "1.0" commands: lint: graphql: rules: spell-check: [warning, { words: [Acme], suggestions: 1 }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable spell-check` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql "The users of the service." type User { id: ID! } ``` **Bad**. The linter reports `Misspelled word 'sevrice' in description (did you mean 'service'?)`. ```graphql "The users of the sevrice." type User { id: ID! } ``` --- # strict-id-in-types Source: https://apigenie.pl/docs/linter/graphql/rules/strict-id-in-types This rule checks that object types have exactly one non-null identifier field, `id: ID!` by default. ## Rule Details Clients such as Apollo Client and Relay cache objects by their identifier. Root operation types are not checked; payload and connection types usually need exceptions. ## Configuration The configuration block is an object with the properties: - `acceptedIdNames`: the names of identifier fields, `[id]` by default - `acceptedIdTypes`: the types of identifier fields, `[ID]` by default - `exceptions`: an object with `types`, the names of types that are not checked, and `suffixes`, the suffixes of the names of types that are not checked ```yaml version: "1.0" commands: lint: graphql: rules: strict-id-in-types: [error, { exceptions: { suffixes: [Payload, Connection, Edge] } }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable strict-id-in-types` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { id: ID!, name: String } ``` **Bad**. The linter reports `Object type 'User' should have exactly one non-null field named 'id' of type 'ID'`. ```graphql type User { name: String } ``` --- # type-description-defined Source: https://apigenie.pl/docs/linter/graphql/rules/type-description-defined This rule checks that type definitions have a description. ## Rule Details Descriptions are part of the schema: introspection serves them to GraphiQL, IDEs, documentation generators and code generators. A missing or blank description is reported; type extensions have no descriptions and are not checked. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: type-description-defined: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable type-description-defined` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql "A user of the shop." type User { id: ID! } ``` **Bad**. The linter reports `Object type 'User' should have a description`. ```graphql type User { id: ID! } ``` --- # type-naming Source: https://apigenie.pl/docs/linter/graphql/rules/type-naming This rule checks that type names follow a naming convention, PascalCase by default. ## Rule Details Consistent names make a schema predictable for its clients: generated code, documentation and queries read the same everywhere. The rule reports a name that doesn't follow the convention, with the name converted to it as a suggestion. ## Configuration The configuration block can be a string, the convention, or an object with the properties: - `convention`: `camelCase`, `camelCaseStrict`, `PascalCase`, `PascalCaseStrict`, `snake_case`, `CONST_CASE` (or `UPPER_CASE`), `kebab-case` or `Header-Case`; the strict forms reject two capitals in a row (`userID`) - `allowLeadingUnderscore`: whether leading underscores are ignored, e.g. in `_service`; `false` by default ```yaml version: "1.0" commands: lint: graphql: rules: type-naming: [warning, { convention: snake_case, allowLeadingUnderscore: true }] ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable type-naming` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type UserProfile { id: ID! } ``` **Bad**. The linter reports `Type name 'user_profile' should be PascalCase, e.g. 'UserProfile'`. ```graphql type user_profile { id: ID! } ``` --- # types-sorted Source: https://apigenie.pl/docs/linter/graphql/rules/types-sorted This rule checks that the type definitions of a document, and its directive definitions are in alphabetical order. ## Rule Details Sorted definitions are easier to find and produce smaller diffs. Names are compared ignoring case; the first name out of order of each list is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: types-sorted: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable types-sorted` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { me: User } type User { id: ID! } ``` **Bad**. The linter reports `Type 'Query' should come before 'User'`. ```graphql type User { id: ID! } type Query { me: User } ``` ## When Not to Use It Disable this rule when the order of definitions is meaningful, e.g. the most important fields first. --- # unique-argument-definition-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-argument-definition-names This rule checks that the argument names of a field or directive definition are unique, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-argument-definition-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-argument-definition-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { users(first: Int, after: String): [User] } ``` **Bad**. The linter reports `Argument "Query.users(first:)" can only be defined once.`. ```graphql type Query { users(first: Int, first: String): [User] } ``` --- # unique-argument-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-argument-names This rule checks that the argument names of a field or directive are unique, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-argument-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-argument-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q { users(first: 1, after: "a") { id } } ``` **Bad**. The linter reports `There can be only one argument named "first".`. ```graphql query Q { users(first: 1, first: 2) { id } } ``` --- # unique-directive-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-directive-names This rule checks that directive names are unique, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-directive-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-directive-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql directive @auth on FIELD_DEFINITION ``` **Bad**. The linter reports `There can be only one directive named "@auth".`. ```graphql directive @auth on FIELD_DEFINITION directive @auth on OBJECT ``` --- # unique-directives-per-location Source: https://apigenie.pl/docs/linter/graphql/rules/unique-directives-per-location This rule checks that non-repeatable directives are used once per location, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-directives-per-location: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-directives-per-location` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { a: Int @deprecated } ``` **Bad**. The linter reports `The directive "@deprecated" can only be used once at this location.`. ```graphql type Query { a: Int @deprecated @deprecated } ``` ## Compatibility The rule applies to schemas and to operations. The built-in directives and those defined in the document are known; other directives are not checked. --- # unique-enum-value-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-enum-value-names This rule checks that the values of an enum are unique across the enum and its extensions, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-enum-value-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-enum-value-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql enum Kind { CAT } extend enum Kind { DOG } ``` **Bad**. The linter reports `Enum value "Kind.CAT" can only be defined once.`. ```graphql enum Kind { CAT } extend enum Kind { CAT } ``` --- # unique-field-definition-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-field-definition-names This rule checks that the field names of a type are unique across the type and its extensions, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-field-definition-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-field-definition-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { id: ID! } extend type User { name: String } ``` **Bad**. The linter reports `Field "User.id" can only be defined once.`. ```graphql type User { id: ID! } extend type User { id: ID } ``` --- # unique-fragment-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-fragment-names This rule checks that fragment names are unique, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-fragment-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-fragment-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q { user { ...A ...B } } fragment A on User { id } fragment B on User { name } ``` **Bad**. The linter reports `There can be only one fragment named "A".`. ```graphql query Q { user { ...A } } fragment A on User { id } fragment A on User { name } ``` --- # unique-input-field-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-input-field-names This rule checks that the field names of an object value are unique, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-input-field-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-input-field-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q { users(filter: { name: "a", kind: ADMIN }) { id } } ``` **Bad**. The linter reports `There can be only one input field named "name".`. ```graphql query Q { users(filter: { name: "a", name: "b" }) { id } } ``` --- # unique-operation-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-operation-names This rule checks that operation names are unique, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-operation-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-operation-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query A { a } query B { b } ``` **Bad**. The linter reports `There can be only one operation named "A".`. ```graphql query A { a } query A { b } ``` --- # unique-operation-types Source: https://apigenie.pl/docs/linter/graphql/rules/unique-operation-types This rule checks that each root operation type is defined once across the schema definition and its extensions, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-operation-types: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-operation-types` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql schema { query: Query } extend schema { mutation: Mutation } ``` **Bad**. The linter reports `There can be only one query type in schema.`. ```graphql schema { query: Query } extend schema { query: Other } ``` --- # unique-type-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-type-names This rule checks that type names are unique, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-type-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-type-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type User { id: ID! } extend type User { name: String } ``` **Bad**. The linter reports `There can be only one type named "User".`. ```graphql type User { id: ID! } type User { name: String } ``` --- # unique-variable-names Source: https://apigenie.pl/docs/linter/graphql/rules/unique-variable-names This rule checks that the variable names of an operation are unique, as the GraphQL specification requires. ## Rule Details The rule reports the issues of the validation rule of the same name, with the messages of graphql-js. A document that breaks it is rejected by GraphQL servers and tools, so the rule should stay enabled with the `error` severity. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unique-variable-names: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unique-variable-names` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Q($a: Int, $b: Int) { f(a: $a, b: $b) } ``` **Bad**. The linter reports `There can be only one variable named "$a".`. ```graphql query Q($a: Int, $a: Int) { f(a: $a) } ``` --- # unreachable-types Source: https://apigenie.pl/docs/linter/graphql/rules/unreachable-types This rule checks that every type is reachable from the root operation types. ## Rule Details A type that no field, argument or union reaches is dead code: clients cannot use it. Types are reached through the fields, arguments, input fields, union members and interfaces of reached types, the implementations of reached interfaces, and the arguments of directive definitions. Documents without root operation types are not checked. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unreachable-types: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unreachable-types` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql type Query { user: User } type User { id: ID! } ``` **Bad**. The linter reports `Object type 'Orphan' is not reachable from the root operation types`. ```graphql type Query { user: User } type User { id: ID! } type Orphan { id: ID! } ``` ## When Not to Use It Disable this rule when the schema is split across files. --- # unused-directive-definition Source: https://apigenie.pl/docs/linter/graphql/rules/unused-directive-definition This rule checks that the directives defined for schemas are applied in the document. ## Rule Details An unused directive definition is dead code. Directives with executable locations (e.g. `FIELD`) are meant for operations and are not reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: unused-directive-definition: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable unused-directive-definition` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to definitions. ## Examples **Good**. ```graphql directive @auth on FIELD_DEFINITION type Query { me: User @auth } ``` **Bad**. The linter reports `Directive '@auth' is defined but never applied`. ```graphql directive @auth on FIELD_DEFINITION type Query { me: User } ``` ## When Not to Use It Disable this rule when the directives are defined in a file and applied in others. --- # variables-sorted Source: https://apigenie.pl/docs/linter/graphql/rules/variables-sorted This rule checks that the variable definitions of each operation are in alphabetical order. ## Rule Details Sorted definitions are easier to find and produce smaller diffs. Names are compared ignoring case; the first name out of order of each list is reported. ## Configuration This rule has no options. ```yaml version: "1.0" commands: lint: graphql: rules: variables-sorted: warning ``` The rule can be disabled for a node and everything nested in it with an `# apigenie-lint-disable variables-sorted` comment on the node, or for a whole file with the comment at the top of the file, separated from the first definition by a blank line. It applies to operations and fragments. ## Examples **Good**. ```graphql query Users($first: Int, $kind: Kind) { users(first: $first, kind: $kind) { id } } ``` **Bad**. The linter reports `Variable 'first' should come before 'kind'`. ```graphql query Users($kind: Kind, $first: Int) { users(first: $first, kind: $kind) { id } } ``` ## When Not to Use It Disable this rule when the order of definitions is meaningful, e.g. the most important fields first. --- # directory-same-package Source: https://apigenie.pl/docs/linter/protobuf/rules/directory-same-package This rule checks whether all the `.proto` files of a directory declare the same package. ## Rule Details Many code generators, for example the Go one, turn a directory into a single package of the generated code. Files of different Protobuf packages in one directory then produce code that does not compile, and they make the layout of the API harder to follow. The `directory-same-package` rule compares the package of the linted file with the packages of the other `.proto` files in the same directory (subdirectories are not included). If any of them declares another package, or no package while the linted file has one (or the other way round), the rule reports: `Other files of the directory declare other packages: <files>` where `<files>` lists each such file as `'<package>' (<file name>)`, or as `no package (<file name>)` when the file has no package. The issue is reported at the package declaration, or at the syntax statement of a file without a package. Files of the directory that cannot be read, for example because of syntax errors, or because they are proto2 or editions files, are ignored. ## Configuration This rule has no options. ## Examples **Good**. The directory `acme/pets/v1` contains `pet.proto`: ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; } ``` and `owner.proto`: ```proto syntax = "proto3"; package acme.pets.v1; message Owner { string name = 1; } ``` **Bad**. The directory `acme/pets/v1` contains the same `pet.proto` as above, and this `owner.proto`: ```proto syntax = "proto3"; package acme.owners.v1; message Owner { string name = 1; } ``` The linter reports `Other files of the directory declare other packages: 'acme.owners.v1' (owner.proto)` in `pet.proto`, and `Other files of the directory declare other packages: 'acme.pets.v1' (pet.proto)` in `owner.proto`. ## When Not to Use It You may disable this rule if your build tools and code generators do not map directories to packages, and you deliberately keep several packages in one directory. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule reads the other `.proto` files in the directory of the linted file. --- # enum-comment-defined Source: https://apigenie.pl/docs/linter/protobuf/rules/enum-comment-defined This rule checks whether every enum of a Protobuf file has a leading comment. ## Rule Details Comments are the documentation of a Protobuf API: `protoc` passes them to the code generators, which copy them to the generated code, and documentation generators render them. The `enum-comment-defined` rule checks every enum, including enums nested in messages, and reports `Enum '<name>' has no leading comment` when there is no leading comment, or when it contains only whitespace. A leading comment is the comment right before the declaration, as `protoc` attaches it: a comment separated from the declaration by a blank line is a detached comment, and a comment after the declaration on the same line is a trailing comment; neither counts. Both `//` and `/* */` comments count. The rule is part of the `strict` and `all` rulesets. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; // The species of a pet. enum PetKind { PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; } ``` **Bad**. The linter reports `Enum 'PetKind' has no leading comment`. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; } ``` ## When Not to Use It You may disable this rule if the enums of your files are documented elsewhere, or if their names are descriptive enough. Lines with an `apigenie-lint-disable` directive do not count as documentation: a leading comment made only of directives does not satisfy the rule. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # enum-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/enum-naming This rule checks whether the name of every enum of a Protobuf file is in `PascalCase`. ## Rule Details PascalCase enum names match the type naming conventions of most target languages, and are what the Protobuf style guide recommends. The `enum-value-prefix` rule derives the expected prefix of the values from the enum name. The `enum-naming` rule checks every enum, including enums nested in messages, and reports `Enum name '<name>' should be PascalCase` for every name that is not in `PascalCase`. A name is in `PascalCase` when it starts with an upper-case letter and contains only letters and digits, such as `PetOwner` (acronyms such as `HTTPServer` are accepted). When the name converted to `PascalCase` differs from the original and is itself valid, the message ends with a suggestion, for example `, e.g. '<converted>'`. To convert a name, it is split into words at underscores and other separators, at lower-to-upper case changes and before the last capital of an acronym, and the words are capitalized and joined, so `pet_owner` becomes `PetOwner`. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; } ``` **Bad**. The linter reports `Enum name 'pet_kind' should be PascalCase, e.g. 'PetKind'`. ```proto syntax = "proto3"; package acme.pets.v1; enum pet_kind { PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; } ``` ## When Not to Use It You may disable this rule if the names are part of an existing API that you cannot rename. To allow a single enum, add an `// apigenie-lint-disable enum-naming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # enum-no-allow-alias Source: https://apigenie.pl/docs/linter/protobuf/rules/enum-no-allow-alias This rule checks whether any enum sets the `allow_alias` option. ## Rule Details With `option allow_alias = true;`, several values of an enum can share a number. Only one of the names survives serialization, though: the JSON and text formats always write the first name of the number, so a client that sends one name may read back another. Aliases also make renaming values harder than it looks, since both names have to be kept in sync with every consumer. The `enum-no-allow-alias` rule reports `Enum '<enum>' should not allow aliases` at the `allow_alias` option of every enum that sets it to `true`. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; PET_KIND_CAT = 2; } ``` **Bad**. The linter reports `Enum 'PetKind' should not allow aliases`. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { option allow_alias = true; PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; PET_KIND_HOUND = 1; PET_KIND_CAT = 2; } ``` ## When Not to Use It You may disable this rule while renaming enum values: an alias lets the old and the new name coexist until all clients have moved to the new one. To allow a single enum, add an `// apigenie-lint-disable enum-no-allow-alias` comment to the enum. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # enum-value-comment-defined Source: https://apigenie.pl/docs/linter/protobuf/rules/enum-value-comment-defined This rule checks whether every enum value of a Protobuf file has a leading comment. ## Rule Details Comments are the documentation of a Protobuf API: `protoc` passes them to the code generators, which copy them to the generated code, and documentation generators render them. The `enum-value-comment-defined` rule checks every enum value, and reports `Enum value '<name>' has no leading comment` when there is no leading comment, or when it contains only whitespace. A leading comment is the comment right before the declaration, as `protoc` attaches it: a comment separated from the declaration by a blank line is a detached comment, and a comment after the declaration on the same line is a trailing comment; neither counts. Both `//` and `/* */` comments count. The rule is only part of the `all` ruleset. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; // The species of a pet. enum PetKind { // The species is not known. PET_KIND_UNSPECIFIED = 0; // A dog. PET_KIND_DOG = 1; } ``` **Bad**. The linter reports `Enum value 'PET_KIND_DOG' has no leading comment`. ```proto syntax = "proto3"; package acme.pets.v1; // The species of a pet. enum PetKind { // The species is not known. PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; } ``` ## When Not to Use It You may disable this rule if the values of your files are documented elsewhere, or if their names are descriptive enough. To exempt the values of a single enum, add an `// apigenie-lint-disable enum-value-comment-defined` comment to the enum. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # enum-value-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/enum-value-naming This rule checks whether the name of every enum value of a Protobuf file is in `UPPER_SNAKE_CASE`. ## Rule Details Enum values are constants, and `UPPER_SNAKE_CASE` is how most target languages write constants. It is also what the Protobuf style guide recommends. The `enum-value-naming` rule checks every enum value, and reports `Enum value name '<name>' should be UPPER_SNAKE_CASE` for every name that is not in `UPPER_SNAKE_CASE`. A name is in `UPPER_SNAKE_CASE` when it starts with an upper-case letter and contains only upper-case letters, digits and single underscores, with no underscore at the end, such as `PET_KIND_DOG`. When the name converted to `UPPER_SNAKE_CASE` differs from the original and is itself valid, the message ends with a suggestion, for example `, e.g. '<converted>'`. To convert a name, it is split into words at underscores and other separators, at lower-to-upper case changes and before the last capital of an acronym, and the words are joined in upper case with underscores, so `petKindDog` becomes `PET_KIND_DOG`. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; } ``` **Bad**. The linter reports `Enum value name 'petKindDog' should be UPPER_SNAKE_CASE, e.g. 'PET_KIND_DOG'`. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { PET_KIND_UNSPECIFIED = 0; petKindDog = 1; } ``` ## When Not to Use It You may disable this rule if the names are part of an existing API that you cannot rename. To allow a single enum value, add an `// apigenie-lint-disable enum-value-naming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # enum-value-prefix Source: https://apigenie.pl/docs/linter/protobuf/rules/enum-value-prefix This rule checks whether the names of the values of every enum start with the name of the enum in `UPPER_SNAKE_CASE`, for example `PET_KIND_DOG` in the enum `PetKind`. ## Rule Details Protobuf enum values follow C++ scoping rules: they are siblings of their enum, not children of it. Two enums of the same package or message therefore cannot both have a value named `UNKNOWN` or `OTHER`. Prefixing every value with the name of its enum avoids such clashes, and keeps the values unambiguous in the generated code of languages without scoped enums. The `enum-value-prefix` rule converts the name of each enum to `UPPER_SNAKE_CASE` and appends an underscore, and reports `Enum value '<value>' should start with '<prefix>'` for every value whose name does not start with this prefix. The name is split into words at underscores, at lower-to-upper case changes and before the last capital of an acronym, so the prefix of `HTTPMethod` is `HTTP_METHOD_`. The prefix is made of the name of the enum only, also for enums nested in messages. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; PET_KIND_CAT = 2; } ``` **Bad**. The linter reports `Enum value 'UNSPECIFIED' should start with 'PET_KIND_'`, `Enum value 'DOG' should start with 'PET_KIND_'` and `Enum value 'CAT' should start with 'PET_KIND_'`. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { UNSPECIFIED = 0; DOG = 1; CAT = 2; } ``` ## When Not to Use It You may disable this rule if the enum values are part of an existing API that you cannot rename. To allow the values of a single enum, add an `// apigenie-lint-disable enum-value-prefix` comment to the enum. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # enum-zero-value-suffix Source: https://apigenie.pl/docs/linter/protobuf/rules/enum-zero-value-suffix This rule checks whether the zero value of every enum ends with a suffix, `_UNSPECIFIED` by default. ## Rule Details In proto3, the zero value is the default of every enum field: a field that was never set reads as zero. A name like `PET_KIND_UNSPECIFIED` makes it clear that the zero value means "not set", rather than a real value such as `PET_KIND_DOG` that clients would then receive by accident. The `enum-zero-value-suffix` rule checks every enum value whose number is `0`, and reports `Enum zero value '<value>' should end with '<suffix>'` when its name does not end with the suffix. When an enum allows aliases, every value numbered `0` is checked. ## Configuration The configuration block can be a string or an object. If it is a string, its value is the suffix. If it is an object, it must contain only the `suffix` property, whose value is the suffix. The suffix cannot be empty. **Configuration Example**. The following two examples are equivalent. They specify that the zero values must end with `_UNKNOWN`. ```yaml version: "1.0" commands: lint: protobuf: rules: enum-zero-value-suffix: [warning, _UNKNOWN] ``` ```yaml version: "1.0" commands: lint: protobuf: rules: enum-zero-value-suffix: - warning - suffix: _UNKNOWN ``` ## Examples **Good**. With the default configuration. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { PET_KIND_UNSPECIFIED = 0; PET_KIND_DOG = 1; } ``` **Bad**. With the default configuration, the linter reports `Enum zero value 'PET_KIND_UNKNOWN' should end with '_UNSPECIFIED'`. ```proto syntax = "proto3"; package acme.pets.v1; enum PetKind { PET_KIND_UNKNOWN = 0; PET_KIND_DOG = 1; } ``` ## When Not to Use It You may disable this rule if the zero value of some enums is a meaningful value, for example in enums mirroring an external standard. To allow a single enum, add an `// apigenie-lint-disable enum-zero-value-suffix` comment to the enum. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # field-comment-defined Source: https://apigenie.pl/docs/linter/protobuf/rules/field-comment-defined This rule checks whether every field of a Protobuf file has a leading comment. ## Rule Details Comments are the documentation of a Protobuf API: `protoc` passes them to the code generators, which copy them to the generated code, and documentation generators render them. The `field-comment-defined` rule checks every field, including the fields of oneofs, map fields and extension fields, and reports `Field '<name>' has no leading comment` when there is no leading comment, or when it contains only whitespace. A leading comment is the comment right before the declaration, as `protoc` attaches it: a comment separated from the declaration by a blank line is a detached comment, and a comment after the declaration on the same line is a trailing comment; neither counts. Both `//` and `/* */` comments count. The rule is only part of the `all` ruleset. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; // A pet that lives in the shelter. message Pet { // The name the shelter gave to the pet. string name = 1; // Free-form labels, such as the color of the pet. map<string, string> labels = 2; } ``` **Bad**. The linter reports `Field 'labels' has no leading comment`. ```proto syntax = "proto3"; package acme.pets.v1; // A pet that lives in the shelter. message Pet { // The name the shelter gave to the pet. string name = 1; map<string, string> labels = 2; // Free-form labels. } ``` ## When Not to Use It You may disable this rule if the fields of your files are documented elsewhere, or if their names are descriptive enough. To exempt the fields of a single message, add an `// apigenie-lint-disable field-comment-defined` comment to the message. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # field-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/field-naming This rule checks whether the name of every field of a Protobuf file is in `lower_snake_case`. ## Rule Details Code generators derive the accessor names and the JSON names of fields from their Protobuf names, assuming `lower_snake_case`: `owner_name` becomes `ownerName` in JSON and `getOwnerName()` in Java. Other conventions produce surprising names, and can make the JSON names of two fields collide. The `field-naming` rule checks every field, including the fields of oneofs, map fields and extension fields, and reports `Field name '<name>' should be lower_snake_case` for every name that is not in `lower_snake_case`. A name is in `lower_snake_case` when it starts with a lower-case letter and contains only lower-case letters, digits and single underscores, with no underscore at the end, such as `owner_name` or `address_2`. When the name converted to `lower_snake_case` differs from the original and is itself valid, the message ends with a suggestion, for example `, e.g. '<converted>'`. To convert a name, it is split into words at underscores and other separators, at lower-to-upper case changes and before the last capital of an acronym, and the words are joined in lower case with underscores, so `ownerName` becomes `owner_name` and `HTTPServer` becomes `http_server`. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string owner_name = 1; map<string, string> labels = 2; } ``` **Bad**. The linter reports `Field name 'ownerName' should be lower_snake_case, e.g. 'owner_name'`. ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string ownerName = 1; map<string, string> labels = 2; } ``` ## When Not to Use It You may disable this rule if the names are part of an existing API that you cannot rename. To allow a single field, add an `// apigenie-lint-disable field-naming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # file-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/file-naming This rule checks whether the name of every Protobuf file is in `lower_snake_case`, with the `.proto` extension. ## Rule Details Code generators derive the names of the generated files, and sometimes of classes, from the names of the `.proto` files. `lower_snake_case` names such as `pet_owner.proto` are safe on every file system and in every target language, and are what the Protobuf style guide recommends. The `file-naming` rule checks the name of the linted file, without its directory. The name must end with `.proto`, and the part before the extension must start with a lower-case letter or a digit (as in `2fa.proto`), and contain only lower-case letters, digits and single underscores, with no underscore at the end. Otherwise the rule reports: `File name '<name>' should be lower_snake_case.proto` followed by a suggestion such as `, e.g. 'pet_store.proto'`. To build the suggestion, the part before the extension is split into words at underscores and other separators such as `-` and `.`, at lower-to-upper case changes and before the last capital of an acronym, and the words are joined in lower case with underscores. Files linted without a path are not checked. ## Configuration This rule has no options. ## Examples **Good**. The file `pet_store.proto`: ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; } ``` **Bad**. The same content in the file `PetStore.proto` makes the linter report `File name 'PetStore.proto' should be lower_snake_case.proto, e.g. 'pet_store.proto'`, and in the file `pet-store.proto`, `File name 'pet-store.proto' should be lower_snake_case.proto, e.g. 'pet_store.proto'`. ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; } ``` ## When Not to Use It You may disable this rule if the file names are dictated by an existing project layout that you cannot change. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule uses the path of the linted file, but not its content. --- # import-no-cycle Source: https://apigenie.pl/docs/linter/protobuf/rules/import-no-cycle This rule checks whether Protobuf files import each other in a cycle. ## Rule Details `protoc` rejects files that import themselves through a chain of imports, so such files cannot be compiled at all. For every import of the linted file, the `import-no-cycle` rule follows the imports of the imported file, and of the files it imports, looking for a path back to the linted file. If there is one, it reports: `Import '<import>' creates an import cycle: <file name> -> <import> -> ... -> <import>` A file that imports itself is reported with `Import '<import>' creates an import cycle: the file imports itself`. The cycle starts with the name of the linted file, followed by the imports that lead back to it, as they are written in the import statements. Every file of the cycle that is linted reports its own import. Imports that cannot be found are ignored (see `import-resolved`). Files linted without a path are not checked. ## Configuration This rule has no options. ## Examples **Good**. `acme/pets/v1/pet.proto` imports `acme/pets/v1/owner.proto`, which imports nothing from the package. ```proto syntax = "proto3"; package acme.pets.v1; import "acme/pets/v1/owner.proto"; message Pet { string name = 1; Owner owner = 2; } ``` **Bad**. The same `acme/pets/v1/pet.proto`, while `acme/pets/v1/owner.proto` imports it back: ```proto syntax = "proto3"; package acme.pets.v1; import "acme/pets/v1/pet.proto"; message Owner { string name = 1; repeated Pet pets = 2; } ``` The linter reports, in `pet.proto`: `Import 'acme/pets/v1/owner.proto' creates an import cycle: pet.proto -> acme/pets/v1/owner.proto -> acme/pets/v1/pet.proto` and in `owner.proto`: `Import 'acme/pets/v1/pet.proto' creates an import cycle: owner.proto -> acme/pets/v1/pet.proto -> acme/pets/v1/owner.proto` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule reads the imported files, found in the import paths and in the directories above the importing file. --- # import-no-public Source: https://apigenie.pl/docs/linter/protobuf/rules/import-no-public This rule checks whether a Protobuf file uses `import public`. ## Rule Details A public import makes the declarations of the imported file visible to every file that imports the importing one. It is meant as a temporary measure when moving declarations between files, but it hides the real dependencies of the importers, and several code generators support it poorly or not at all. The `import-no-public` rule reports `Import '<import>' should not be public` for every `import public` statement. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; import "google/protobuf/timestamp.proto"; message Pet { string name = 1; google.protobuf.Timestamp born_at = 2; } ``` **Bad**. The linter reports `Import 'google/protobuf/timestamp.proto' should not be public`. ```proto syntax = "proto3"; package acme.pets.v1; import public "google/protobuf/timestamp.proto"; message Pet { string name = 1; google.protobuf.Timestamp born_at = 2; } ``` ## When Not to Use It You may disable this rule while moving declarations from one file to another, when a public import keeps the existing importers working. To allow a single import, add an `// apigenie-lint-disable import-no-public` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # import-no-weak Source: https://apigenie.pl/docs/linter/protobuf/rules/import-no-weak This rule checks whether a Protobuf file uses `import weak`. ## Rule Details A weak import lets a file compile when the imported file is not linked into the program. It is an undocumented feature, supported by few code generators, and its semantics differ between them. The `import-no-weak` rule reports `Import '<import>' should not be weak` for every `import weak` statement. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; import "google/protobuf/timestamp.proto"; message Pet { string name = 1; google.protobuf.Timestamp born_at = 2; } ``` **Bad**. The linter reports `Import 'google/protobuf/timestamp.proto' should not be weak`. ```proto syntax = "proto3"; package acme.pets.v1; import weak "google/protobuf/timestamp.proto"; message Pet { string name = 1; google.protobuf.Timestamp born_at = 2; } ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # import-resolved Source: https://apigenie.pl/docs/linter/protobuf/rules/import-resolved This rule checks whether every file imported by a Protobuf file can be found. ## Rule Details An import that cannot be found breaks the compilation of the file. It also keeps the linter from checking the rules that need the imported declarations: `import-used`, for example, stays silent for a file whose names it cannot resolve. The `import-resolved` rule looks up every import the way the linter does for all rules: 1. in the import paths, in order: first those given with `--proto-path`, then those of the `import_paths` key of the configuration (relative to the directory of the configuration file); 2. then in the directory of the importing file and in each directory above it, nearest first. The well-known files of the Protobuf distribution (`google/protobuf/timestamp.proto`, `google/protobuf/empty.proto` and the other files of `google/protobuf`) are known built in, so they are always found, even when they are not on the import paths. For every import that is not found, the rule reports: `Import '<import>' is not found in the import paths or the directories above the file` A file that is found but cannot be read, for example because of syntax errors, or because it is a proto2 or editions file, counts as found. To add an import path in the configuration: ```yaml version: "1.0" commands: lint: protobuf: import_paths: [proto, third_party/googleapis] ``` ## Configuration This rule has no options. ## Examples **Good**. The file `acme/pets/v1/pet.proto`, next to `acme/pets/v1/owner.proto`: ```proto syntax = "proto3"; package acme.pets.v1; import "acme/pets/v1/owner.proto"; import "google/protobuf/timestamp.proto"; message Pet { string name = 1; Owner owner = 2; google.protobuf.Timestamp born_at = 3; } ``` **Bad**. No `acme/pets/v1/kind.proto` exists in the import paths or in the directories above the file. The linter reports: `Import 'acme/pets/v1/kind.proto' is not found in the import paths or the directories above the file` ```proto syntax = "proto3"; package acme.pets.v1; import "acme/pets/v1/kind.proto"; message Pet { string name = 1; Kind kind = 2; } ``` ## When Not to Use It You may disable this rule when the imported files are generated or fetched during the build, and are not available where the linter runs. Consider adding their location to `import_paths` instead. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule looks for the imported files in the import paths and in the directories above the importing file. --- # import-used Source: https://apigenie.pl/docs/linter/protobuf/rules/import-used This rule checks whether every file imported by a Protobuf file is actually used. ## Rule Details Unused imports add dependencies that are not needed: the generated code imports packages it does not use, which some languages reject, and the build has to fetch and compile more files. The `import-used` rule collects the names a file refers to: the types of fields and map values, the request and response types of RPCs, the extended types, the custom options used in option names (`(acme.options.v1.sensitive)`), and the extensions and `Any` types in option values. It resolves these names like `protoc` does, among the declarations of the file, of the files it imports and of the files those import publicly. An import is used when it declares one of these names, or when one of the files it imports publicly, directly or indirectly, does. For every import that is not used, the rule reports `Import '<import>' is unused`. The rule does not report: - public imports, which are there for the importers of the file rather than for the file itself; - imports that are not found (see `import-resolved`), and imports whose file, or one of the files it imports publicly, cannot be read, for example because of syntax errors, or because it is a proto2 or editions file (`google/protobuf/descriptor.proto` is known built in, though); - any import of a file in which some name cannot be resolved: the name may come from a file the linter cannot read, so it cannot tell which imports are unused. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; import "google/protobuf/timestamp.proto"; message Pet { string name = 1; google.protobuf.Timestamp born_at = 2; } ``` **Bad**. The linter reports `Import 'google/protobuf/duration.proto' is unused`. ```proto syntax = "proto3"; package acme.pets.v1; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; message Pet { string name = 1; google.protobuf.Timestamp born_at = 2; } ``` ## Automatic fixes When invoked with `--fix`, this rule removes the unused import statements. ## When Not to Use It You may disable this rule if some imports are needed only for their side effects in the generated code, for example to register extensions. To keep a single import, add an `// apigenie-lint-disable import-used` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule reads the imported files, and the files they import publicly, found in the import paths and in the directories above the importing file. --- # imports-sorted Source: https://apigenie.pl/docs/linter/protobuf/rules/imports-sorted This rule checks whether the import statements of a Protobuf file are sorted by path. ## Rule Details Sorted imports are easier to scan, make duplicates obvious, and keep merge conflicts small when several people add imports to the same file. The `imports-sorted` rule compares the paths of the import statements in the order they appear in the file, whatever their kind (plain, `public` or `weak`). At the first import whose path sorts before the path of the preceding one, it reports: `Imports are not sorted: '<import>' should come before '<previous import>'` Only the first import out of order is reported. Paths are compared character by character (by their byte values), so the comparison is case-sensitive: all upper-case letters sort before lower-case ones. The rule is part of the `strict` and `all` rulesets. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; import "acme/pets/v1/owner.proto"; import "google/protobuf/timestamp.proto"; message Pet { string name = 1; Owner owner = 2; google.protobuf.Timestamp born_at = 3; } ``` **Bad**. The linter reports `Imports are not sorted: 'acme/pets/v1/owner.proto' should come before 'google/protobuf/timestamp.proto'`. ```proto syntax = "proto3"; package acme.pets.v1; import "google/protobuf/timestamp.proto"; import "acme/pets/v1/owner.proto"; message Pet { string name = 1; Owner owner = 2; google.protobuf.Timestamp born_at = 3; } ``` ## Automatic fixes When invoked with `--fix`, this rule sorts the import statements by path, keeping the kind (`public` or `weak`) of each import. Every import statement is replaced by the one that belongs at its place, so imports separated by other statements stay at the same places in the file. The fix is only offered when none of the import statements has a comment, neither before, after nor inside the statement, since moving the statements would separate the comments from them. Otherwise the issue is reported without a fix, and the imports must be sorted by hand. ## When Not to Use It You may disable this rule if you group the imports in another way, for example the imports of the file's own package first. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # message-comment-defined Source: https://apigenie.pl/docs/linter/protobuf/rules/message-comment-defined This rule checks whether every message of a Protobuf file has a leading comment. ## Rule Details Comments are the documentation of a Protobuf API: `protoc` passes them to the code generators, which copy them to the generated code, and documentation generators render them. The `message-comment-defined` rule checks every message, including nested messages, and reports `Message '<name>' has no leading comment` when there is no leading comment, or when it contains only whitespace. A leading comment is the comment right before the declaration, as `protoc` attaches it: a comment separated from the declaration by a blank line is a detached comment, and a comment after the declaration on the same line is a trailing comment; neither counts. Both `//` and `/* */` comments count. The rule is part of the `strict` and `all` rulesets. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; // A pet that lives in the shelter. message Pet { string name = 1; } ``` **Bad**. The linter reports `Message 'Pet' has no leading comment`. ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; } ``` ## When Not to Use It You may disable this rule if the messages of your files are documented elsewhere, or if their names are descriptive enough. Lines with an `apigenie-lint-disable` directive do not count as documentation: a leading comment made only of directives does not satisfy the rule. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # message-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/message-naming This rule checks whether the name of every message of a Protobuf file is in `PascalCase`. ## Rule Details PascalCase message names match the type naming conventions of most target languages, and are what the Protobuf style guide recommends. The `message-naming` rule checks every message, including nested messages, and reports `Message name '<name>' should be PascalCase` for every name that is not in `PascalCase`. A name is in `PascalCase` when it starts with an upper-case letter and contains only letters and digits, such as `PetOwner` (acronyms such as `HTTPServer` are accepted). When the name converted to `PascalCase` differs from the original and is itself valid, the message ends with a suggestion, for example `, e.g. '<converted>'`. To convert a name, it is split into words at underscores and other separators, at lower-to-upper case changes and before the last capital of an acronym, and the words are capitalized and joined, so `pet_owner` becomes `PetOwner`. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; message PetOwner { string name = 1; } ``` **Bad**. The linter reports `Message name 'pet_owner' should be PascalCase, e.g. 'PetOwner'`. ```proto syntax = "proto3"; package acme.pets.v1; message pet_owner { string name = 1; } ``` ## When Not to Use It You may disable this rule if the names are part of an existing API that you cannot rename. To allow a single message, add an `// apigenie-lint-disable message-naming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # oneof-comment-defined Source: https://apigenie.pl/docs/linter/protobuf/rules/oneof-comment-defined This rule checks whether every oneof of a Protobuf file has a leading comment. ## Rule Details Comments are the documentation of a Protobuf API: `protoc` passes them to the code generators, which copy them to the generated code, and documentation generators render them. The `oneof-comment-defined` rule checks every oneof, and reports `Oneof '<name>' has no leading comment` when there is no leading comment, or when it contains only whitespace. A leading comment is the comment right before the declaration, as `protoc` attaches it: a comment separated from the declaration by a blank line is a detached comment, and a comment after the declaration on the same line is a trailing comment; neither counts. Both `//` and `/* */` comments count. The rule is only part of the `all` ruleset. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; // A pet that lives in the shelter. message Pet { string name = 1; // How the shelter can reach the owner. oneof contact { string email = 2; string phone = 3; } } ``` **Bad**. The linter reports `Oneof 'contact' has no leading comment`. ```proto syntax = "proto3"; package acme.pets.v1; // A pet that lives in the shelter. message Pet { string name = 1; oneof contact { string email = 2; string phone = 3; } } ``` ## When Not to Use It You may disable this rule if the oneofs of your files are documented elsewhere, or if their names are descriptive enough. To exempt the oneofs of a single message, add an `// apigenie-lint-disable oneof-comment-defined` comment to the message. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # oneof-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/oneof-naming This rule checks whether the name of every oneof of a Protobuf file is in `lower_snake_case`. ## Rule Details Code generators derive the names of the accessors and case enums of oneofs from their Protobuf names, assuming `lower_snake_case`, like for fields. The `oneof-naming` rule checks every oneof, and reports `Oneof name '<name>' should be lower_snake_case` for every name that is not in `lower_snake_case`. A name is in `lower_snake_case` when it starts with a lower-case letter and contains only lower-case letters, digits and single underscores, with no underscore at the end, such as `owner_name` or `address_2`. When the name converted to `lower_snake_case` differs from the original and is itself valid, the message ends with a suggestion, for example `, e.g. '<converted>'`. To convert a name, it is split into words at underscores and other separators, at lower-to-upper case changes and before the last capital of an acronym, and the words are joined in lower case with underscores, so `ownerName` becomes `owner_name` and `HTTPServer` becomes `http_server`. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; oneof owner_contact { string email = 2; string phone = 3; } } ``` **Bad**. The linter reports `Oneof name 'ownerContact' should be lower_snake_case, e.g. 'owner_contact'`. ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; oneof ownerContact { string email = 2; string phone = 3; } } ``` ## When Not to Use It You may disable this rule if the names are part of an existing API that you cannot rename. To allow a single oneof, add an `// apigenie-lint-disable oneof-naming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # package-defined Source: https://apigenie.pl/docs/linter/protobuf/rules/package-defined This rule checks whether every Protobuf file declares a package. ## Rule Details Without a `package` declaration, the messages, enums and services of a file live in the global namespace, where they can clash with the declarations of any other file. Most code generators also derive the names of the generated packages or namespaces from the Protobuf package. The `package-defined` rule reports `File has no package declaration` for every file without a `package` statement. The issue is reported at the syntax statement. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; } ``` **Bad**. The linter reports `File has no package declaration`. ```proto syntax = "proto3"; message Pet { string name = 1; } ``` ## When Not to Use It We recommend to always enable this rule. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # package-directory-match Source: https://apigenie.pl/docs/linter/protobuf/rules/package-directory-match This rule checks whether every Protobuf file is in the directory that corresponds to its package, for example `acme/pets/v1` for the package `acme.pets.v1`. ## Rule Details When the directories mirror the packages, the import path of a file tells where its declarations live, and the files of a package are easy to find. Many code generators and build tools expect this layout. The expected directory of a package is its name with the dots replaced by slashes. The `package-directory-match` rule compares it with the directory of the file in one of two ways: - If the file is inside one of the import paths (given with `--proto-path` or with the `import_paths` key of the configuration), the directory of the file relative to the first import path that contains it must be the expected directory. Otherwise the rule reports `Package '<package>' should be in directory '<expected>' of import path '<import path>', not in '<directory>'`, where `<directory>` is `.` for a file directly in the import path. The import path is shown as an absolute path. - Otherwise, the directory of the file must end with the expected directory. If it does not, the rule reports `Package '<package>' should be in a directory ending with '<expected>'`. Import paths in the configuration are relative to the directory of the configuration file: ```yaml version: "1.0" commands: lint: protobuf: import_paths: [proto] ``` Files without a package, and files linted without a path, are not checked. ## Configuration This rule has no options. ## Examples **Good**. The file `proto/acme/pets/v1/pet.proto`: ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; } ``` **Bad**. Without import paths, the file `proto/pets/v1/pet.proto` makes the linter report `Package 'acme.pets.v1' should be in a directory ending with 'acme/pets/v1'`. With the import path `proto`, and the project in `/work`, it reports `Package 'acme.pets.v1' should be in directory 'acme/pets/v1' of import path '/work/proto', not in 'pets/v1'`. ```proto syntax = "proto3"; package acme.pets.v1; message Pet { string name = 1; } ``` ## When Not to Use It You may disable this rule if your build places the files in directories that are unrelated to their packages, for example a flat directory of `.proto` files. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule uses the path of the linted file and the import paths, but does not read other files. --- # package-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/package-naming This rule checks whether the package name of a Protobuf file consists of `lower_snake_case` components separated by dots. ## Rule Details Package names such as `acme.pet_store.v1` map cleanly to directories and to the package and namespace names of the generated code in every language. The `package-naming` rule splits the package name at dots and checks that every component is `lower_snake_case`: it starts with a lower-case letter, and contains only lower-case letters, digits and single underscores, with no underscore at the end. If a component is not, the rule reports `Package name '<name>' should be lower_snake_case components separated by dots`, followed by a suggestion such as `, e.g. 'acme.pet_store.v1'` when converting the components gives a different name. To build the suggestion, each component is split into words at underscores and other separators, at lower-to-upper case changes and before the last capital of an acronym (so that `HTTPServer` becomes `http_server`), and the words are joined in lower case with underscores. Files without a package are not checked; see the `package-defined` rule. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pet_store.v1; message Pet { string name = 1; } ``` **Bad**. The linter reports `Package name 'Acme.PetStore.v1' should be lower_snake_case components separated by dots, e.g. 'acme.pet_store.v1'`. ```proto syntax = "proto3"; package Acme.PetStore.v1; message Pet { string name = 1; } ``` ## When Not to Use It You may disable this rule if the package names are dictated by an existing API that you cannot rename. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # package-no-import-cycle Source: https://apigenie.pl/docs/linter/protobuf/rules/package-no-import-cycle This rule checks whether packages import each other in a cycle. ## Rule Details Even when no file imports itself through a chain of imports, two packages can depend on each other: a file of package A imports a file of package B, and another file of package B imports a file of package A. Code generators that turn Protobuf packages into packages of the target language, like the Go one, produce code with an import cycle that does not compile. The `package-no-import-cycle` rule builds the graph of the packages: package A imports package B when any file of A imports a file of B. For every import of the linted file that refers to a file of another package, it looks for a path in this graph from that package back to the package of the linted file. If there is one, it reports: `Import '<import>' creates a package import cycle: <package> -> ... -> <package>` The cycle starts and ends with the package of the linted file. Each imported package is reported once per file, at the first import that leads to it. The graph contains the files the linter knows about: the files being linted, the other `.proto` files in their directories, and the files these import, directly or indirectly. Imports that cannot be found are ignored. Files without a package are not checked, and are not part of the graph. ## Configuration This rule has no options. ## Examples **Good**. `acme/pets/v1/pet.proto` imports a file of `acme.owners.v1`, which does not import anything from `acme.pets.v1`. ```proto syntax = "proto3"; package acme.pets.v1; import "acme/owners/v1/owner.proto"; message Pet { string name = 1; acme.owners.v1.Owner owner = 2; } ``` **Bad**. The same `acme/pets/v1/pet.proto`, while `acme/owners/v1/owner.proto` imports another file of `acme.pets.v1`: ```proto syntax = "proto3"; package acme.owners.v1; import "acme/pets/v1/kind.proto"; message Owner { string name = 1; repeated acme.pets.v1.Kind preferred_kinds = 2; } ``` No file imports itself, but when `pet.proto` is linted, the linter reports: `Import 'acme/owners/v1/owner.proto' creates a package import cycle: acme.pets.v1 -> acme.owners.v1 -> acme.pets.v1` ## When Not to Use It We recommend to always enable this rule. To accept an import that is part of a known cycle, add an `// apigenie-lint-disable package-no-import-cycle` comment to the import statement. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule reads the imported files, found in the import paths and in the directories above the importing file, and the other files in the directories of the linted files. --- # package-options-consistent Source: https://apigenie.pl/docs/linter/protobuf/rules/package-options-consistent This rule checks whether the files of a package set the same language-specific file options, such as `go_package` and `java_package`. ## Rule Details Options like `go_package`, `java_package` and `csharp_namespace` tell the code generators where to put the generated code. When the files of one Protobuf package disagree on them, the code of the package is scattered over several packages or namespaces of the target language, or does not compile at all. The `package-options-consistent` rule compares the options of the linted file with those of the other files of the same package. For every checked option whose value differs from that of another file, including when the option is set in one file only, it reports: `Option '<option>' differs between the files of package '<package>': <value> here, <value> in <file name>` where each value is the value in quotes, or `unset`. At most one issue is reported per option, for the first other file that differs. The issue is reported at the option statement, or at the package declaration when the linted file does not set the option. String values are compared after decoding their escapes; other values, such as `true`, as they are written. By default the rule checks these options: `csharp_namespace`, `go_package`, `java_multiple_files`, `java_package`, `objc_class_prefix`, `php_namespace`, `ruby_package` and `swift_prefix`. The other files of the package are those the linter knows about: the files being linted, the other `.proto` files in their directories, and the files these import, directly or indirectly. Files that cannot be read, for example because of syntax errors, or because they are proto2 or editions files, are ignored. Files without a package are not checked. ## Configuration The configuration block can be an array of option names, or an object with the `options` property, whose value is such an array. The list replaces the default list of options. **Configuration Example**. The following two examples are equivalent. They specify that only `go_package` and `java_package` must be consistent. ```yaml version: "1.0" commands: lint: protobuf: rules: package-options-consistent: [error, [go_package, java_package]] ``` ```yaml version: "1.0" commands: lint: protobuf: rules: package-options-consistent: - error - options: [go_package, java_package] ``` ## Examples **Good**. The directory `acme/pets/v1` contains `pet.proto`: ```proto syntax = "proto3"; package acme.pets.v1; option go_package = "example.com/acme/pets/v1;petsv1"; message Pet { string name = 1; } ``` and `owner.proto`: ```proto syntax = "proto3"; package acme.pets.v1; option go_package = "example.com/acme/pets/v1;petsv1"; message Owner { string name = 1; } ``` **Bad**. The directory contains the same `pet.proto` as above, and this `owner.proto`: ```proto syntax = "proto3"; package acme.pets.v1; message Owner { string name = 1; } ``` The linter reports, in `pet.proto`: `Option 'go_package' differs between the files of package 'acme.pets.v1': 'example.com/acme/pets/v1;petsv1' here, unset in owner.proto` and in `owner.proto`: `Option 'go_package' differs between the files of package 'acme.pets.v1': unset here, 'example.com/acme/pets/v1;petsv1' in pet.proto` ## When Not to Use It We recommend to always enable this rule. If you do not generate code for some languages, you may restrict the list of options to those you use. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule reads the other files of the package: the other files in the directories of the linted files, and the imported files, found in the import paths and in the directories above the importing file. --- # package-same-directory Source: https://apigenie.pl/docs/linter/protobuf/rules/package-same-directory This rule checks whether all the files of a package are in the same directory. ## Rule Details A package spread over several directories is hard to find, and code generators that map directories to packages of the generated code, like the Go one, split it into several packages that cannot see each other. The `package-same-directory` rule looks for other files that declare the same package as the linted file but are in another directory. If there are some, it reports: `Package '<package>' is also declared in other directories: <directories>` where `<directories>` lists each other directory once, as `'<directory>' (<file name>)` with the absolute path of the directory and the name of one of its files. The issue is reported at the package declaration. The rule can only compare the files the linter knows about: the files being linted, the other `.proto` files in their directories, and the files these import, directly or indirectly. A file of the package that none of them reaches is not seen. Files that cannot be read, for example because of syntax errors, or because they are proto2 or editions files, are ignored. Files without a package are not checked. ## Configuration This rule has no options. ## Examples **Good**. The files of `acme.pets.v1` are all in `acme/pets/v1`. `acme/pets/v1/pet.proto`: ```proto syntax = "proto3"; package acme.pets.v1; import "acme/pets/v1/owner.proto"; message Pet { string name = 1; Owner owner = 2; } ``` and `acme/pets/v1/owner.proto`: ```proto syntax = "proto3"; package acme.pets.v1; message Owner { string name = 1; } ``` **Bad**. The same `pet.proto` imports `acme/pets/v1/owners/owner.proto`, which declares the same package in a subdirectory: ```proto syntax = "proto3"; package acme.pets.v1; import "acme/pets/v1/owners/owner.proto"; message Pet { string name = 1; Owner owner = 2; } ``` With the project in `/work`, the linter reports, in `pet.proto`: `Package 'acme.pets.v1' is also declared in other directories: '/work/acme/pets/v1/owners' (owner.proto)` ## When Not to Use It You may disable this rule if your build tools and code generators do not map directories to packages, and you deliberately split large packages into several directories. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule reads the other files of the package: the other files in the directories of the linted files, and the imported files, found in the import paths and in the directories above the importing file. --- # package-version-suffix Source: https://apigenie.pl/docs/linter/protobuf/rules/package-version-suffix This rule checks whether the last component of the package name of a Protobuf file is a version, such as `v1` or `v1beta2`. ## Rule Details Versioned packages let a new, incompatible version of an API live next to the old one: the messages and services of `acme.pets.v2` do not clash with those of `acme.pets.v1`, neither in the Protobuf namespace nor in the generated code. The `package-version-suffix` rule reports `Package '<name>' should end with a version component, e.g. v1 or v1beta1` when the last component of the package name is not a version. A version component is: - `v` followed by a major version that does not start with `0`: `v1`, `v2`, `v10`, - optionally followed by a stability, `alpha` or `beta`, itself optionally followed by a number: `v1alpha`, `v1beta2`, - optionally with a point release between the major version and the stability: `v1p1alpha`, `v1p2beta3`. A point release requires a stability, so `v1p1` is not a version, - or followed by `test` and any lowercase letters and digits: `v2test`, `v1testfoo`. These are the versions buf accepts. Components such as `v0`, `V1`, `version1`, `v1final` and `v1p1test` are not versions. Files without a package are not checked; see the `package-defined` rule. The rule is part of the `strict` and `all` rulesets. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1beta1; message Pet { string name = 1; } ``` **Bad**. The linter reports `Package 'acme.pets' should end with a version component, e.g. v1 or v1beta1`. ```proto syntax = "proto3"; package acme.pets; message Pet { string name = 1; } ``` ## When Not to Use It You may disable this rule if your APIs are versioned in another way, for example by the repository or the service name, or if the files are internal and never published. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # rpc-comment-defined Source: https://apigenie.pl/docs/linter/protobuf/rules/rpc-comment-defined This rule checks whether every RPC of a Protobuf file has a leading comment. ## Rule Details Comments are the documentation of a Protobuf API: `protoc` passes them to the code generators, which copy them to the generated code, and documentation generators render them. The `rpc-comment-defined` rule checks every RPC of every service, and reports `RPC '<name>' has no leading comment` when there is no leading comment, or when it contains only whitespace. A leading comment is the comment right before the declaration, as `protoc` attaches it: a comment separated from the declaration by a blank line is a detached comment, and a comment after the declaration on the same line is a trailing comment; neither counts. Both `//` and `/* */` comments count. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { // Returns the pet with the given name. rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` **Bad**. The linter reports `RPC 'GetPet' has no leading comment`. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` ## When Not to Use It You may disable this rule if the RPCs of your files are documented elsewhere, or if their names are descriptive enough. To exempt the RPCs of a single service, add an `// apigenie-lint-disable rpc-comment-defined` comment to the service. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # rpc-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/rpc-naming This rule checks whether the name of every RPC of a Protobuf file is in `PascalCase`. ## Rule Details RPC names become method names in the generated code and appear in the paths of gRPC requests (`/acme.pets.v1.PetService/GetPet`). PascalCase is what the Protobuf style guide recommends, and the `rpc-request-standard-name` and `rpc-response-standard-name` rules derive the names of the request and response types from them. The `rpc-naming` rule checks every RPC of every service, and reports `RPC name '<name>' should be PascalCase` for every name that is not in `PascalCase`. A name is in `PascalCase` when it starts with an upper-case letter and contains only letters and digits, such as `PetOwner` (acronyms such as `HTTPServer` are accepted). When the name converted to `PascalCase` differs from the original and is itself valid, the message ends with a suggestion, for example `, e.g. '<converted>'`. To convert a name, it is split into words at underscores and other separators, at lower-to-upper case changes and before the last capital of an acronym, and the words are capitalized and joined, so `pet_owner` becomes `PetOwner`. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` **Bad**. The linter reports `RPC name 'get_pet' should be PascalCase, e.g. 'GetPet'`. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc get_pet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` ## When Not to Use It You may disable this rule if the names are part of an existing API that you cannot rename. To allow a single RPC, add an `// apigenie-lint-disable rpc-naming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # rpc-no-client-streaming Source: https://apigenie.pl/docs/linter/protobuf/rules/rpc-no-client-streaming This rule checks whether any RPC uses client streaming. ## Rule Details Client-streaming RPCs, whose request is declared with `stream`, are not supported by every transport: gRPC-Web and most HTTP/JSON gateways, for example, cannot carry them. They are also harder to retry, load-balance and debug than unary RPCs. The `rpc-no-client-streaming` rule reports `RPC '<rpc>' should not use client streaming` for every RPC with a streamed request, including bidirectional streaming RPCs. The rule is only part of the `all` ruleset. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc UploadPhoto(UploadPhotoRequest) returns (UploadPhotoResponse); } message UploadPhotoRequest { bytes photo = 1; } message UploadPhotoResponse { string url = 1; } ``` **Bad**. The linter reports `RPC 'UploadPhotos' should not use client streaming`. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc UploadPhotos(stream UploadPhotosRequest) returns (UploadPhotosResponse); } message UploadPhotosRequest { bytes photo = 1; } message UploadPhotosResponse { repeated string urls = 1; } ``` ## When Not to Use It You may disable this rule if all clients use a transport that supports streaming, such as gRPC over HTTP/2. To allow a single RPC, add an `// apigenie-lint-disable rpc-no-client-streaming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # rpc-no-server-streaming Source: https://apigenie.pl/docs/linter/protobuf/rules/rpc-no-server-streaming This rule checks whether any RPC uses server streaming. ## Rule Details Server-streaming RPCs, whose response is declared with `stream`, are not supported by every transport: most HTTP/JSON gateways, for example, cannot carry them, and long-lived streams are harder to load-balance and debug than unary RPCs. The `rpc-no-server-streaming` rule reports `RPC '<rpc>' should not use server streaming` for every RPC with a streamed response, including bidirectional streaming RPCs. The rule is only part of the `all` ruleset. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc ListPets(ListPetsRequest) returns (ListPetsResponse); } message ListPetsRequest { int32 page_size = 1; string page_token = 2; } message ListPetsResponse { repeated string names = 1; string next_page_token = 2; } ``` **Bad**. The linter reports `RPC 'WatchPets' should not use server streaming`. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc WatchPets(WatchPetsRequest) returns (stream WatchPetsResponse); } message WatchPetsRequest { string shelter = 1; } message WatchPetsResponse { string name = 1; } ``` ## When Not to Use It You may disable this rule if all clients use a transport that supports streaming, such as gRPC over HTTP/2, and some RPCs need to push updates. To allow a single RPC, add an `// apigenie-lint-disable rpc-no-server-streaming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # rpc-request-response-unique Source: https://apigenie.pl/docs/linter/protobuf/rules/rpc-request-response-unique This rule checks whether every message is used as the request or the response of a single RPC. ## Rule Details A message shared by several RPCs, or used both as a request and as a response, ties these RPCs together: a field added for one of them appears in all the others, and cannot be removed again without breaking them. The `rpc-request-response-unique` rule goes through the RPCs of all the services of the file in order, first the request and then the response type of each. When a type was already used by an earlier RPC, or as the request of the same RPC, it reports, at the RPC that uses it again: `Message '<type>' is used more than once: as <first use> and as <this use>` where each use reads `the request of '<rpc>'` or `the response of '<rpc>'`; in files with several services, RPCs are named with their service, e.g. `'PetService.GetPet'`. An RPC whose request and response are the same message is reported too. In the message, `<type>` is the type name as written in the RPC. Types are compared by their fully-qualified names, so `GetPetResponse` and `acme.pets.v1.GetPetResponse` are the same message. Only the RPCs of the linted file are compared. ## Configuration The configuration block is an object with boolean properties: - `allow_google_protobuf_empty_requests`: whether `google.protobuf.Empty` may be the request of several RPCs (default `false`). - `allow_google_protobuf_empty_responses`: whether `google.protobuf.Empty` may be the response of several RPCs (default `false`). **Configuration Example** ```yaml version: "1.0" commands: lint: protobuf: rules: rpc-request-response-unique: - warning - allow_google_protobuf_empty_requests: true allow_google_protobuf_empty_responses: true ``` ## Examples **Good**. With the default configuration. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (GetPetResponse); rpc UpdatePet(UpdatePetRequest) returns (UpdatePetResponse); } message Pet { string name = 1; } message GetPetRequest { string name = 1; } message GetPetResponse { Pet pet = 1; } message UpdatePetRequest { Pet pet = 1; } message UpdatePetResponse { Pet pet = 1; } ``` **Bad**. With the default configuration, the linter reports `Message 'Pet' is used more than once: as the response of 'GetPet' and as the response of 'UpdatePet'`. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (Pet); rpc UpdatePet(UpdatePetRequest) returns (Pet); } message Pet { string name = 1; } message GetPetRequest { string name = 1; } message UpdatePetRequest { Pet pet = 1; } ``` ## When Not to Use It You may disable this rule if the RPCs are part of an existing API that you cannot change. To allow a single RPC, add an `// apigenie-lint-disable rpc-request-response-unique` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. The rule resolves the request and response types among the declarations of the file and of the files it imports; a type that cannot be resolved is compared by its name as written, without a leading dot. --- # rpc-request-standard-name Source: https://apigenie.pl/docs/linter/protobuf/rules/rpc-request-standard-name This rule checks whether the request type of every RPC is named after the RPC, for example `GetPetRequest` for the RPC `GetPet`. ## Rule Details When every RPC has its own request message, fields can be added to the request of one RPC without changing the others. Naming the message after the RPC makes this one-to-one relation visible. The `rpc-request-standard-name` rule accepts two names for the request type of the RPC `<rpc>` in the service `<service>`: `<rpc>Request` and `<service><rpc>Request`. Only the last component of the type name is compared, so `acme.pets.v1.GetPetRequest` is accepted as well. For any other request type, the rule reports: `Request type of RPC '<rpc>' should be named '<rpc>Request' or '<service><rpc>Request', not '<type>'` where `<type>` is the type name as written in the RPC. ## Configuration The configuration block is an object with boolean properties: - `allow_google_protobuf_empty_requests`: whether `google.protobuf.Empty` is accepted as a request type (default `false`). The type is resolved, so `.google.protobuf.Empty` is recognized, too. - `allow_google_protobuf_empty_responses`: accepted for symmetry with the related rules, but ignored by this rule. **Configuration Example** ```yaml version: "1.0" commands: lint: protobuf: rules: rpc-request-standard-name: - warning - allow_google_protobuf_empty_requests: true ``` ## Examples **Good**. With the default configuration. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (GetPetResponse); rpc ListPets(PetServiceListPetsRequest) returns (ListPetsResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } message PetServiceListPetsRequest { int32 page_size = 1; } message ListPetsResponse { repeated string names = 1; } ``` **Bad**. With the default configuration, the linter reports `Request type of RPC 'GetPet' should be named 'GetPetRequest' or 'PetServiceGetPetRequest', not 'PetId'`. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(PetId) returns (GetPetResponse); } message PetId { string name = 1; } message GetPetResponse { string name = 1; } ``` ## When Not to Use It You may disable this rule if the RPCs are part of an existing API that you cannot change. To allow a single RPC, add an `// apigenie-lint-disable rpc-request-standard-name` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. To recognize `google.protobuf.Empty`, the rule resolves the request types among the declarations of the file and of the files it imports; `google/protobuf/empty.proto` is known built in. A type that cannot be resolved is compared as written, without a leading dot. --- # rpc-response-standard-name Source: https://apigenie.pl/docs/linter/protobuf/rules/rpc-response-standard-name This rule checks whether the response type of every RPC is named after the RPC, for example `GetPetResponse` for the RPC `GetPet`. ## Rule Details When every RPC has its own response message, fields can be added to the response of one RPC without changing the others: returning a bare `Pet` from `GetPet` leaves no room for metadata that only this RPC needs. Naming the message after the RPC makes this one-to-one relation visible. The `rpc-response-standard-name` rule accepts two names for the response type of the RPC `<rpc>` in the service `<service>`: `<rpc>Response` and `<service><rpc>Response`. Only the last component of the type name is compared, so `acme.pets.v1.GetPetResponse` is accepted as well. For any other response type, the rule reports: `Response type of RPC '<rpc>' should be named '<rpc>Response' or '<service><rpc>Response', not '<type>'` where `<type>` is the type name as written in the RPC. ## Configuration The configuration block is an object with boolean properties: - `allow_google_protobuf_empty_responses`: whether `google.protobuf.Empty` is accepted as a response type (default `false`). The type is resolved, so `.google.protobuf.Empty` is recognized, too. - `allow_google_protobuf_empty_requests`: accepted for symmetry with the related rules, but ignored by this rule. **Configuration Example** ```yaml version: "1.0" commands: lint: protobuf: rules: rpc-response-standard-name: - warning - allow_google_protobuf_empty_responses: true ``` ## Examples **Good**. With the default configuration. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (GetPetResponse); rpc ListPets(ListPetsRequest) returns (PetServiceListPetsResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } message ListPetsRequest { int32 page_size = 1; } message PetServiceListPetsResponse { repeated string names = 1; } ``` **Good**. With `allow_google_protobuf_empty_responses: true`. ```proto syntax = "proto3"; package acme.pets.v1; import "google/protobuf/empty.proto"; service PetService { rpc DeletePet(DeletePetRequest) returns (google.protobuf.Empty); } message DeletePetRequest { string name = 1; } ``` **Bad**. With the default configuration, the linter reports `Response type of RPC 'GetPet' should be named 'GetPetResponse' or 'PetServiceGetPetResponse', not 'Pet'`. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (Pet); } message GetPetRequest { string name = 1; } message Pet { string name = 1; } ``` ## When Not to Use It You may disable this rule if the RPCs are part of an existing API that you cannot change. To allow a single RPC, add an `// apigenie-lint-disable rpc-response-standard-name` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. To recognize `google.protobuf.Empty`, the rule resolves the response types among the declarations of the file and of the files it imports; `google/protobuf/empty.proto` is known built in. A type that cannot be resolved is compared as written, without a leading dot. --- # service-comment-defined Source: https://apigenie.pl/docs/linter/protobuf/rules/service-comment-defined This rule checks whether every service of a Protobuf file has a leading comment. ## Rule Details Comments are the documentation of a Protobuf API: `protoc` passes them to the code generators, which copy them to the generated code, and documentation generators render them. The `service-comment-defined` rule checks every service, and reports `Service '<name>' has no leading comment` when there is no leading comment, or when it contains only whitespace. A leading comment is the comment right before the declaration, as `protoc` attaches it: a comment separated from the declaration by a blank line is a detached comment, and a comment after the declaration on the same line is a trailing comment; neither counts. Both `//` and `/* */` comments count. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; // Manages the pets of the shelter. service PetService { rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` **Bad**. The linter reports `Service 'PetService' has no leading comment`. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` ## When Not to Use It You may disable this rule if the services of your files are documented elsewhere, or if their names are descriptive enough. Lines with an `apigenie-lint-disable` directive do not count as documentation: a leading comment made only of directives does not satisfy the rule. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # service-naming Source: https://apigenie.pl/docs/linter/protobuf/rules/service-naming This rule checks whether the name of every service of a Protobuf file is in `PascalCase`. ## Rule Details The service name becomes the base of the names of the generated client and server classes, so it should follow the type naming conventions of the target languages. The `service-naming` rule checks every service, and reports `Service name '<name>' should be PascalCase` for every name that is not in `PascalCase`. A name is in `PascalCase` when it starts with an upper-case letter and contains only letters and digits, such as `PetOwner` (acronyms such as `HTTPServer` are accepted). When the name converted to `PascalCase` differs from the original and is itself valid, the message ends with a suggestion, for example `, e.g. '<converted>'`. To convert a name, it is split into words at underscores and other separators, at lower-to-upper case changes and before the last capital of an acronym, and the words are capitalized and joined, so `pet_owner` becomes `PetOwner`. ## Configuration This rule has no options. ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; service PetService { rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` **Bad**. The linter reports `Service name 'pet_service' should be PascalCase, e.g. 'PetService'`. ```proto syntax = "proto3"; package acme.pets.v1; service pet_service { rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` ## When Not to Use It You may disable this rule if the names are part of an existing API that you cannot rename. To allow a single service, add an `// apigenie-lint-disable service-naming` comment to it. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # service-suffix Source: https://apigenie.pl/docs/linter/protobuf/rules/service-suffix This rule checks whether the name of every service ends with a suffix, `Service` by default. ## Rule Details A common suffix makes services easy to tell apart from messages in the Protobuf files and in the generated code, where the service name becomes the base of the client and server classes. The `service-suffix` rule reports `Service name '<service>' should end with '<suffix>'` for every service whose name does not end with the suffix. The comparison is case-sensitive. ## Configuration The configuration block can be a string or an object. If it is a string, its value is the suffix. If it is an object, it must contain only the `suffix` property, whose value is the suffix. The suffix cannot be empty. **Configuration Example**. The following two examples are equivalent. They specify that service names must end with `API`. ```yaml version: "1.0" commands: lint: protobuf: rules: service-suffix: [warning, API] ``` ```yaml version: "1.0" commands: lint: protobuf: rules: service-suffix: - warning - suffix: API ``` ## Examples **Good**. With the default configuration. ```proto syntax = "proto3"; package acme.pets.v1; service PetStoreService { rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` **Bad**. With the default configuration, the linter reports `Service name 'PetStore' should end with 'Service'`. ```proto syntax = "proto3"; package acme.pets.v1; service PetStore { rpc GetPet(GetPetRequest) returns (GetPetResponse); } message GetPetRequest { string name = 1; } message GetPetResponse { string name = 1; } ``` ## When Not to Use It You may disable this rule if the service names are part of an existing API that you cannot rename. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted. --- # spell-check Source: https://apigenie.pl/docs/linter/protobuf/rules/spell-check This rule checks the spelling of the comments of a Protobuf file. ## Rule Details Comments are the documentation of a Protobuf API: code generators copy them to the generated code, and documentation generators render them. The rule reports every misspelled word, with up to three suggested corrections, for example `Misspelled word 'nmae' in comment (did you mean 'name', 'nae', 'mae'?)`, or `Misspelled word '<word>' in comment` when there is no suggestion. The rule checks the leading, trailing and detached comments of every declaration of the file: the syntax statement (including a license header before it), the package, imports and options, messages, fields, oneofs, enums, enum values, services and RPCs. The issue points at the declaration the comment is attached to. Comments inside a statement, such as `int32 /* type */ count = 1;`, are not checked. Comments are read as CommonMark: the rule checks their prose, and skips code spans, code blocks, raw HTML and link destinations. The rule also skips the tokens which are not plain words: - URLs, e-mail addresses, paths, and file and domain names (`pet.proto`, `example.com`), - identifiers: `snake_case`, `camelCase` and names with inner capitals (`PetService`), - acronyms, also in the plural (`RPC`, `APIs`, `ID's`), - tokens with digits (`v1`, `404`) and single letters. The spell checker is a C++ port of [Hunspell](https://hunspell.github.io). By default it uses the built-in English (US) dictionary from [SCOWL](http://wordlist.aspell.net) (as shipped with LibreOffice), extended with words common in API descriptions, such as `webhook`, `endpoint`, `namespace`, `nullable` and `json`. Any Hunspell dictionary can be used instead. The rule is part of the `strict` and `all` rulesets. ## Configuration This rule accepts an object with the following options: - `words`: extra words to accept, for example product names. A word is accepted as given, capitalized and in capitals: `petstore` accepts `petstore`, `Petstore` and `PETSTORE`. - `dictionary` and `affix`: paths of a Hunspell dictionary (`.dic`) and its affix file (`.aff`) to use instead of the built-in dictionary. They go together. Dictionaries in UTF-8, ISO-8859-1, ISO-8859-2 and ISO-8859-15 are supported. - `suggestions`: how many corrections to suggest for a word, from 0 to 10 (default 3). Suggestions are the costly part of the rule; on large files with many unknown words, `0` makes it much faster. **Configuration Example** ```yaml version: "1.0" commands: lint: protobuf: rules: spell-check: - warning - words: [petstore] dictionary: path/to/en_GB.dic affix: path/to/en_GB.aff ``` ## Examples **Good**. ```proto syntax = "proto3"; package acme.pets.v1; // A pet that lives in the shelter. message Pet { // The name of the pet. string name = 1; // Must not be empty. } ``` **Bad**. The linter reports, both at the `name` field, `Misspelled word 'nmae' in comment (did you mean 'name', 'nae', 'mae'?)` and `Misspelled word 'emtpy' in comment (did you mean 'empty', 'mtpy'?)`. ```proto syntax = "proto3"; package acme.pets.v1; // A pet that lives in the shelter. message Pet { // The nmae of the pet. string name = 1; // Must not be emtpy. } ``` ## When Not to Use It You may disable this rule if your comments are written in a language you have no dictionary for. To accept the names of your products and other domain terms, add them to the `words` option rather than disabling the rule. ## Compatibility This rule is compatible with proto3 files. proto2 and editions files are rejected by the reader and are not linted.