OpenAPI¶
Direction of integration. Helix generates OpenAPI 3.0.3 specs from a
rest:block. It does not load an external OpenAPI document and generate workflows from it — that direction is not yet supported. If you have an OpenAPI spec from another team, hand-translate the operations intorest:verbs.
Source: pkg/dsl/rest_openapi.go (the generator) and
pkg/dsl/rest_reifier.go (the /openapi.json route that serves it).
How it works¶
When a rest: block is reified, the reifier:
- Iterates over every verb in every
rest:block. - Builds an
openAPISpecwith paths, operations, request bodies, responses, parameters, and security schemes derived from the verb definitions (pkg/dsl/rest_openapi.go: GenerateOpenAPI). - Marshals the spec to indented JSON.
- Registers a route on the same
host:portas therest:block that servesGET /openapi.jsonwith the marshalled bytes (pkg/dsl/rest_reifier.go: buildOpenAPIRoute).
The spec is generated once at startup from the RestDefinitions
in the loaded YAML. There is no live reload of the spec — restart
the engine (or use helix run --watch) when you change the rest:
block.
What the generator produces¶
A snippet like:
rest:
- path: "/api/v1"
tag: "Users"
description: "User management API"
produces: "application/json"
security:
- type: bearer
scheme: "JWT"
verbs:
- method: get
path: "/users"
target: direct://listUsers
description: "List all users"
- method: get
path: "/users/{id}"
target: direct://getUser
description: "Get a single user"
response-type: "User"
params:
- name: id
type: path
data-type: integer
required: true
- method: post
path: "/users"
target: direct://createUser
request-type: "User"
response-type: "User"
produces:
{
"openapi": "3.0.3",
"info": {
"title": "Helix REST API",
"version": "1.0.0"
},
"paths": {
"/api/v1/users": {
"get": {
"tags": ["Users"],
"summary": "List all users",
"operationId": "get_api_v1_users",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": { "schema": { "type": "object" } }
}
}
},
"security": [{ "bearerAuth": [] }]
},
"post": {
"tags": ["Users"],
"operationId": "post_api_v1_users",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/User" }
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/User" }
}
}
}
},
"security": [{ "bearerAuth": [] }]
}
},
"/api/v1/users/{id}": {
"get": {
"tags": ["Users"],
"summary": "Get a single user",
"operationId": "get_api_v1_users_id",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "integer" }
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/User" }
}
}
}
},
"security": [{ "bearerAuth": [] }]
}
}
},
"tags": [
{ "name": "Users", "description": "User management API" }
],
"components": {
"securitySchemes": {
"bearerAuth": { "type": "http", "scheme": "JWT" }
}
}
}
Field mapping reference¶
rest: block → top-level spec¶
| Source | Mapping |
|---|---|
path: |
Each verb's path: is joined to this to produce the OpenAPI path key. |
tag: |
Operation tags: [<tag>] on every verb; also emitted as a top-level tags: entry. |
description: |
The top-level tag's description. |
security: |
Each entry becomes a components.securitySchemes entry and is attached to every operation under security:. |
Verb → OpenAPI operation¶
| Source | Mapping |
|---|---|
method: |
Operation key (get / post / put / delete / patch / head / options). |
path: |
Appended to the block-level path: to form the OpenAPI path. |
description: |
Operation summary:. |
| auto | operationId: = <method>_<sanitised path>. Slashes, braces, and hyphens are replaced with underscores. |
request-type: |
requestBody.content.<mime>.schema.$ref: "#/components/schemas/<RequestType>". Only emitted for POST / PUT / PATCH. |
response-type: |
Same $ref: on the 200 response. When unset, the response schema is { "type": "object" }. |
params: |
Each non-body entry → parameters[] with in: <type>, name:, required:, description:, schema:. |
params: type=body |
Captured into requestBody.description. The body schema is the request-type: ref or { "type": "object" }. |
Param data-type → JSON Schema type¶
data-type (YAML) |
OpenAPI schema.type |
|---|---|
integer, int |
integer |
number, float, double |
number |
boolean, bool |
boolean |
array |
array |
| any other (incl. empty) | string |
Auto-extracted path parameters¶
If path: "/users/{id}/orders/{orderId}" declares placeholders that
aren't in the params: list, the generator still emits OpenAPI
parameters[] entries for them, with in: path, required: true,
and schema: { type: "string" }.
Security mapping¶
| YAML | OpenAPI securityScheme |
|---|---|
type: basic |
{ type: http, scheme: basic } |
type: bearer, scheme: <s> |
{ type: http, scheme: <s or "bearer"> } |
type: apiKey, name: <n>, in: <i> |
{ type: apiKey, name: <n or "X-API-Key">, in: <i or "header"> } |
type: oauth2 |
{ type: oauth2 } (flow details not yet supported) |
The scheme name attached to each operation is built from
security.name if set, otherwise <type>Auth (e.g. bearerAuth,
apiKeyAuth, basicAuth, oauth2Auth).
What is not supported¶
This section is exhaustive — it covers features users coming from codegen-heavy OpenAPI workflows commonly expect but Helix doesn't implement.
- Loading an OpenAPI spec into the engine. There is no
openapi:top-level YAML key, nohelix import-openapi, and no--openapiflag. A spec coming into the engine is a manual port to arest:block. - Schema definitions (
components.schemas).request-type:andresponse-type:produce$ref:entries, but the schemas themselves are not defined anywhere in the generated spec. Add the schema definitions yourself (e.g. via an OpenAPI post-processor) or document them externally. - Multiple response codes. Every operation emits only
200. No204,400,404,500, etc. - Per-operation security overrides. Security is block-level only —
every verb shares the block's
security:list. - Request body content types other than the verb's
consumes. OnerequestBody.contententry per verb. - Examples. No
example:/examples:emission. - Links / callbacks / webhooks / discriminators. Not generated.
- OpenAPI 3.1. The generator emits
openapi: "3.0.3"literally. - YAML output. Only JSON. The endpoint is
/openapi.json; there is no/openapi.yaml.
Composition example — split spec across two rest: blocks¶
When you declare two rest: blocks with different tag: values they
both contribute paths to the same /openapi.json (provided they share
host+port). Useful when the public API and admin API share a server:
rest:
- path: "/api/public"
tag: "Public"
description: "Public endpoints"
verbs:
- method: get
path: "/health"
target: direct://health
- path: "/api/admin"
tag: "Admin"
description: "Admin endpoints (auth required)"
security:
- type: bearer
verbs:
- method: post
path: "/users"
target: direct://create-user
request-type: "User"
response-type: "User"
GET /openapi.json returns a single spec containing both
/api/public/health and /api/admin/users, with Public and Admin
in tags[].
Note that today only the first rest: block's /openapi.json route
is registered (pkg/dsl/rest_reifier.go: buildOpenAPIRoute is called
once per RestDefinition). When two blocks share host+port and only
the second's spec is needed, declare the second-block verbs in the
first block, or rely on the single spec that covers both.
See also¶
- REST DSL — the input shape this generator consumes
- Workflow DSL — what each verb's
target:resolves to - http-server component — serves
/openapi.json
Source¶
pkg/dsl/rest_openapi.go—GenerateOpenAPI()and theopenAPISpectypespkg/dsl/rest_reifier.go—buildOpenAPIRoute()and the spec-serving processorpkg/workflow/rest_definition.go— the input structs the generator reads