@contractkit/plugin-openapi 0.8.15 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$ci.log +4 -4
- package/.turbo/turbo-test$colon$ci.log +9 -9
- package/CHANGELOG.md +29 -0
- package/README.md +1 -1
- package/dist/codegen-openapi.d.ts.map +1 -1
- package/dist/index.js +10 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/codegen-openapi.ts +10 -7
- package/tests/codegen-openapi.test.ts +24 -2
- package/tests/helpers.ts +4 -1
|
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
|
|
|
5
5
|
[34mCLI[39m tsup v8.5.1
|
|
6
6
|
[34mCLI[39m Target: esnext
|
|
7
7
|
[34mESM[39m Build start
|
|
8
|
-
[32mESM[39m [1mdist/index.js [22m[32m20.
|
|
9
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
10
|
-
[32mESM[39m ⚡️ Build success in
|
|
8
|
+
[32mESM[39m [1mdist/index.js [22m[32m20.28 KB[39m
|
|
9
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m47.04 KB[39m
|
|
10
|
+
[32mESM[39m ⚡️ Build success in 162ms
|
|
11
11
|
[34mDTS[39m Build start
|
|
12
|
-
[32mDTS[39m ⚡️ Build success in
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 5200ms
|
|
13
13
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.60 KB[39m
|
|
@@ -3,20 +3,20 @@ $ vitest run --coverage
|
|
|
3
3
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.5 [39m[90m/home/runner/work/ContractKit/ContractKit/packages/plugin-openapi[39m
|
|
4
4
|
[2mCoverage enabled with [22m[33mv8[39m
|
|
5
5
|
|
|
6
|
-
[32m✓[39m tests/codegen-openapi.test.ts [2m([22m[
|
|
6
|
+
[32m✓[39m tests/codegen-openapi.test.ts [2m([22m[2m57 tests[22m[2m)[22m[33m 339[2mms[22m[39m
|
|
7
7
|
|
|
8
8
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
9
|
-
[2m Tests [22m [1m[
|
|
10
|
-
[2m Start at [22m
|
|
11
|
-
[2m Duration [22m
|
|
9
|
+
[2m Tests [22m [1m[32m57 passed[39m[22m[90m (57)[39m
|
|
10
|
+
[2m Start at [22m 17:32:01
|
|
11
|
+
[2m Duration [22m 6.46s[2m (transform 1.62s, setup 0ms, import 4.71s, tests 339ms, environment 0ms)[22m
|
|
12
12
|
|
|
13
13
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
14
14
|
-------------------|---------|----------|---------|---------|-------------------
|
|
15
15
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
16
16
|
-------------------|---------|----------|---------|---------|-------------------
|
|
17
|
-
All files |
|
|
18
|
-
src |
|
|
19
|
-
...en-openapi.ts |
|
|
20
|
-
tests | 73.
|
|
21
|
-
helpers.ts | 73.
|
|
17
|
+
All files | 80.12 | 75.23 | 82.25 | 82.41 |
|
|
18
|
+
src | 80.81 | 75.52 | 86.11 | 82.96 |
|
|
19
|
+
...en-openapi.ts | 80.81 | 75.52 | 86.11 | 82.96 | ...04-707,731,741
|
|
20
|
+
tests | 73.91 | 72.97 | 76.92 | 77.5 |
|
|
21
|
+
helpers.ts | 73.91 | 72.97 | 76.92 | 77.5 | ...18,141,152-153
|
|
22
22
|
-------------------|---------|----------|---------|---------|-------------------
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @contractkit/contractkit-plugin-openapi
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [ab69718]
|
|
8
|
+
- @contractkit/core@0.26.0
|
|
9
|
+
|
|
10
|
+
## 0.9.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- 85d7566: Let an operation emit more than one status, and a status serve more than one content type.
|
|
15
|
+
|
|
16
|
+
A status code could previously declare only one mime — the parser warned `Duplicate response body` and dropped the rest — and the generated router pinned both `ctx.status` and `ctx.type` to whichever response happened to be listed first with a body. An endpoint serving several formats had to declare one lying mime and let browsers sniff, and a service had no way to say which status it produced.
|
|
17
|
+
|
|
18
|
+
A status now holds every declared `mime: Type` line (`OpResponseNode.bodies`). When there is more than one, the service picks at runtime and the router sets `ctx.type` from the returned `contentType`. When an operation produces more than one status, the service returns a union discriminated on `status` and the handler switches on it, so each status writes only its own headers, mime and body. Both SDKs mirror the router: the TypeScript and Python clients return a matching union, report which mime came back, and pass the non-2xx statuses they expect to the shared fetch so a declared `304` no longer surfaces as an error. `SdkError` now takes a body type parameter, and each operation exports a `…ErrorBody` alias for the statuses that stay on the throw path.
|
|
19
|
+
|
|
20
|
+
Which statuses the service produces is derived from the declaration: **a status is emitted if it has a block, or is 2xx.** An empty block (`304: {}`) says the service returns that status carrying nothing; a bare `304:` says it is documented and something else produces it. `404(documented): { … }` is the one modifier, forcing a block-carrying status back out.
|
|
21
|
+
|
|
22
|
+
Two long-standing bugs in the same area go with it. The formatter deleted any comment written inside a `response` block — above a status code, above a mime line, above a `headers:` block, or before a closing brace — so `pnpm format` silently threw away the notes explaining why a contract looks the way it does; all four positions now round-trip. And the generated router declared a `_ZodBinary` helper for a binary _response_ body, which is a plain `Buffer` annotation with no schema behind it, leaving an unused const that tripped `noUnusedLocals` downstream; helpers are now chosen from the code that was actually generated, the same way imports already were.
|
|
23
|
+
|
|
24
|
+
**If you are already on a pre-release version, two things change under you.** A contract that declares a body on an error status — `404: { application/json: Problem }` alongside a `200` — now returns it from the service instead of throwing, and the SDK return type becomes a union; add `(documented)` to that status to keep the previous behaviour. Contracts whose error statuses are bodyless (`400:`, `404:`) are unaffected. Anything reading the AST directly should move from `OpResponseNode.contentType`/`bodyType` to `bodies`, which replaces them.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies [85d7566]
|
|
29
|
+
- Updated dependencies [90d19ee]
|
|
30
|
+
- @contractkit/core@0.25.0
|
|
31
|
+
|
|
3
32
|
## 0.8.15
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @contractkit/contractkit-plugin-openapi
|
|
2
2
|
|
|
3
|
-
ContractKit plugin that generates an OpenAPI 3.
|
|
3
|
+
ContractKit plugin that generates an OpenAPI 3.1 YAML specification from `.ck` contract and operation files.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen-openapi.d.ts","sourceRoot":"","sources":["../src/codegen-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,UAAU,EAOb,MAAM,mBAAmB,CAAC;AAG3B,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,+HAA+H;AAC/H,MAAM,WAAW,aAAa;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AA8FD,8HAA8H;AAC9H,MAAM,WAAW,qBAAqB;IAClC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CAC3D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAoFlE;AA0ID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,mBAAmB,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsFxG;
|
|
1
|
+
{"version":3,"file":"codegen-openapi.d.ts","sourceRoot":"","sources":["../src/codegen-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,UAAU,EAOb,MAAM,mBAAmB,CAAC;AAG3B,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,+HAA+H;AAC/H,MAAM,WAAW,aAAa;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AA8FD,8HAA8H;AAC9H,MAAM,WAAW,qBAAqB;IAClC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CAC3D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAoFlE;AA0ID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,mBAAmB,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsFxG;AAgMD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAI,GAAG,MAAM,CA+DzD"}
|
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ function collectPublicTypeRefs(opRoots, includeInternal = false) {
|
|
|
57
57
|
for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);
|
|
58
58
|
}
|
|
59
59
|
for (const resp of op.responses) {
|
|
60
|
-
|
|
60
|
+
for (const body of resp.bodies) collectRefsFromType(body.bodyType, refs);
|
|
61
61
|
if (resp.headers) {
|
|
62
62
|
for (const h of resp.headers) collectRefsFromType(h.type, refs);
|
|
63
63
|
}
|
|
@@ -484,12 +484,15 @@ function buildOperation(route, op) {
|
|
|
484
484
|
const responseObject = {
|
|
485
485
|
description: statusDescription(resp.statusCode)
|
|
486
486
|
};
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
487
|
+
const bodies = resp.bodies;
|
|
488
|
+
if (bodies.length > 0) {
|
|
489
|
+
const content = {};
|
|
490
|
+
for (const body of bodies) {
|
|
491
|
+
content[body.contentType] = {
|
|
492
|
+
schema: typeToSchema(body.bodyType)
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
responseObject.content = content;
|
|
493
496
|
}
|
|
494
497
|
if (resp.headers && resp.headers.length > 0) {
|
|
495
498
|
const headers = {};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/codegen-openapi.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { generateOpenApi } from './codegen-openapi.js';\nimport type { ContractKitPlugin } from '@contractkit/core';\nimport type { OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\nexport type { OpenApiServerEntry, OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\n\nexport interface OpenApiPluginOptions extends OpenApiConfig {\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n// ─── Default export: loaded via plugins array, reads config from ctx.options ─\n\nconst plugin: ContractKitPlugin = {\n name: 'openapi',\n cacheKey: 'openapi',\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const { securitySchemes, ...openapiConfig } = ctx.options as OpenApiPluginOptions;\n const base = openapiConfig.baseDir ? resolve(ctx.rootDir, openapiConfig.baseDir) : ctx.rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n ctx.emitFile(outPath, generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes }));\n },\n};\n\nexport default plugin;\n\n// ─── Factory: for programmatic use with explicit config ────────────────────\n\nexport function createOpenApiPlugin(\n openapiConfig: OpenApiConfig,\n rootDir: string,\n securitySchemes?: Record<string, OpenApiSecurityScheme>,\n): ContractKitPlugin {\n return {\n name: 'openapi',\n cacheKey: `openapi:${JSON.stringify(openapiConfig)}`,\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const base = openapiConfig.baseDir ? resolve(rootDir, openapiConfig.baseDir) : rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n const content = generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes });\n ctx.emitFile(outPath, content);\n },\n };\n}\n","import type {\n ContractRootNode,\n OpRootNode,\n ContractTypeNode,\n FieldNode,\n ModelNode,\n OpRouteNode,\n OpOperationNode,\n ParamSource,\n} from '@contractkit/core';\nimport { resolveModifiers, resolveSecurity, SECURITY_NONE } from '@contractkit/core';\n\n/** A single entry for the OpenAPI `servers` array. */\nexport interface OpenApiServerEntry {\n url: string;\n description?: string;\n}\n\n/** A named entry for `components.securitySchemes` (e.g. an HTTP bearer or API-key scheme). */\nexport interface OpenApiSecurityScheme {\n type: string;\n scheme?: string;\n bearerFormat?: string;\n name?: string;\n in?: string;\n}\n\n/** Plugin configuration controlling the generated OpenAPI document (info block, servers, security, internal-op visibility). */\nexport interface OpenApiConfig {\n baseDir?: string;\n output?: string;\n info?: {\n title?: string;\n version?: string;\n description?: string;\n };\n servers?: OpenApiServerEntry[];\n /** Global OpenAPI security requirements (e.g. [{ bearerAuth: [] }]). Distinct from scheme definitions. */\n security?: Record<string, string[]>[];\n /**\n * Whether to document operations marked `internal`. Defaults to `false` — internal ops\n * are omitted from the spec so external consumers don't see them. Set to `true` for an\n * internal-use spec.\n */\n includeInternal?: boolean;\n}\n\n// ─── Type reachability ────────────────────────────────────────────────────\n\nfunction collectRefsFromType(type: ContractTypeNode, out: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n out.add(type.name);\n break;\n case 'array':\n collectRefsFromType(type.item, out);\n break;\n case 'tuple':\n for (const item of type.items) collectRefsFromType(item, out);\n break;\n case 'record':\n collectRefsFromType(type.value, out);\n break;\n case 'union':\n case 'discriminatedUnion':\n case 'intersection':\n for (const member of type.members) collectRefsFromType(member, out);\n break;\n case 'lazy':\n collectRefsFromType(type.inner, out);\n break;\n case 'inlineObject':\n for (const field of type.fields) collectRefsFromType(field.type, out);\n break;\n }\n}\n\nfunction collectParamSourceRefs(source: ParamSource | undefined, out: Set<string>): void {\n if (!source) return;\n if (source.kind === 'ref') {\n out.add(source.name);\n return;\n }\n if (source.kind === 'params') {\n for (const p of source.nodes) collectRefsFromType(p.type, out);\n return;\n }\n collectRefsFromType(source.node, out);\n}\n\n/** Collect all type names directly referenced by public operations (seed set). */\nfunction collectPublicTypeRefs(opRoots: OpRootNode[], includeInternal = false): Set<string> {\n const refs = new Set<string>();\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n for (const op of route.operations) {\n if (!includeInternal && resolveModifiers(route, op).includes('internal')) continue;\n if (op.request) {\n for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);\n }\n for (const resp of op.responses) {\n if (resp.bodyType) collectRefsFromType(resp.bodyType, refs);\n if (resp.headers) {\n for (const h of resp.headers) collectRefsFromType(h.type, refs);\n }\n }\n collectParamSourceRefs(route.params, refs);\n collectParamSourceRefs(op.query, refs);\n collectParamSourceRefs(op.headers, refs);\n }\n }\n }\n return refs;\n}\n\n/** BFS-expand seed type names through the contract model graph. */\nfunction computeReachableSchemas(seeds: Set<string>, modelMap: Map<string, ModelNode>): Set<string> {\n const reachable = new Set<string>(seeds);\n const frontier = [...seeds];\n while (frontier.length > 0) {\n const name = frontier.pop()!;\n const model = modelMap.get(name);\n if (!model) continue;\n const refs = new Set<string>();\n if (model.type) collectRefsFromType(model.type, refs);\n for (const field of model.fields) collectRefsFromType(field.type, refs);\n if (model.bases) for (const b of model.bases) refs.add(b);\n for (const ref of refs) {\n if (!reachable.has(ref)) {\n reachable.add(ref);\n frontier.push(ref);\n }\n }\n }\n return reachable;\n}\n\n// ─── Public entry point ────────────────────────────────────────────────────\n\n/** Inputs to {@link generateOpenApi}: the parsed contracts/operations plus the OpenAPI config and shared security schemes. */\nexport interface OpenApiCodegenContext {\n contractRoots: ContractRootNode[];\n opRoots: OpRootNode[];\n config: OpenApiConfig;\n /** Named OpenAPI security scheme definitions to include in components.securitySchemes */\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n/**\n * Generate an OpenAPI 3.0 document (YAML) from the parsed contracts and operations.\n *\n * Emits `paths` from the operations and `components.schemas` from the contracts, honoring\n * `config.includeInternal` for `operation(internal)` visibility.\n *\n * @returns The OpenAPI document serialized as a YAML string.\n */\nexport function generateOpenApi(ctx: OpenApiCodegenContext): string {\n const { contractRoots, opRoots, config, securitySchemes } = ctx;\n const includeInternal = config.includeInternal ?? false;\n\n const doc: Record<string, unknown> = {\n openapi: '3.1.0',\n info: {\n title: config.info?.title ?? 'API',\n version: config.info?.version ?? '0.0.1',\n ...(config.info?.description ? { description: config.info.description } : {}),\n },\n };\n\n if (config.servers && config.servers.length > 0) {\n doc.servers = config.servers;\n }\n\n if (config.security && config.security.length > 0) {\n doc.security = config.security;\n }\n\n // Build component schemas from all contract models\n const allSchemas: Record<string, unknown> = {};\n const modelMap = new Map<string, ModelNode>();\n\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n modelMap.set(model.name, model);\n }\n }\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n allSchemas[model.name] = modelToSchema(model, modelMap);\n }\n }\n\n // Build paths from all operation files\n const paths: Record<string, Record<string, unknown>> = {};\n\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n const oaPath = convertPath(route.path);\n\n for (const op of route.operations) {\n const mods = resolveModifiers(route, op);\n if (!includeInternal && mods.includes('internal')) continue;\n // Lazily initialize the path object so all-internal routes\n // leave no empty entry in the output\n if (!paths[oaPath]) paths[oaPath] = {};\n const operation = buildOperation(route, op);\n if (mods.includes('deprecated')) (operation as Record<string, unknown>).deprecated = true;\n paths[oaPath][op.method] = operation;\n }\n }\n }\n\n doc.paths = paths;\n\n // Filter schemas to only include types reachable from public operations.\n // When there are no op files, all schemas are included (no filtering).\n const schemas: Record<string, unknown> =\n opRoots.length > 0\n ? (() => {\n const reachable = computeReachableSchemas(collectPublicTypeRefs(opRoots, includeInternal), modelMap);\n const filtered: Record<string, unknown> = {};\n for (const [name, schema] of Object.entries(allSchemas)) {\n if (reachable.has(name)) filtered[name] = schema;\n }\n return filtered;\n })()\n : allSchemas;\n\n const components: Record<string, unknown> = {};\n if (Object.keys(schemas).length > 0) {\n components.schemas = schemas;\n }\n if (securitySchemes && Object.keys(securitySchemes).length > 0) {\n components.securitySchemes = securitySchemes;\n }\n if (Object.keys(components).length > 0) {\n doc.components = components;\n }\n\n return toYaml(doc);\n}\n\n// ─── Path conversion ──────────────────────────────────────────────────────\n\n/** Path is already in OpenAPI `{param}` style — return as-is. */\nfunction convertPath(path: string): string {\n return path;\n}\n\n// ─── Schema conversion ───────────────────────────────────────────────────\n\nfunction modelToSchema(model: ModelNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n // Type alias (no fields)\n if (model.type) {\n const schema = typeToSchema(model.type, modelMap);\n if (model.description) schema.description = model.description;\n return schema;\n }\n\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n const prop = fieldToSchema(field, modelMap);\n properties[field.name] = prop;\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = {\n type: 'object',\n properties,\n };\n\n if (required.length > 0) {\n schema.required = required;\n }\n\n if (model.bases && model.bases.length > 0) {\n return {\n allOf: [...model.bases.map(b => ({ $ref: `#/components/schemas/${b}` })), schema],\n };\n }\n\n if (model.description) {\n schema.description = model.description;\n }\n if (model.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction fieldToSchema(field: FieldNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n let schema = typeToSchema(field.type, modelMap);\n\n if (field.nullable) {\n schema = wrapNullable(schema);\n }\n if (field.visibility === 'readonly') {\n schema.readOnly = true;\n } else if (field.visibility === 'writeonly') {\n schema.writeOnly = true;\n }\n if (field.default !== undefined) {\n schema.default = field.default;\n }\n if (field.description) {\n schema.description = field.description;\n }\n if (field.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction typeToSchema(type: ContractTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n switch (type.kind) {\n case 'scalar':\n return scalarToSchema(type);\n case 'array':\n return arrayToSchema(type, modelMap);\n case 'tuple':\n return { type: 'array', prefixItems: type.items.map(i => typeToSchema(i, modelMap)) };\n case 'record':\n return { type: 'object', additionalProperties: typeToSchema(type.value, modelMap) };\n case 'enum':\n return { type: 'string', enum: type.values };\n case 'literal':\n return { const: type.value };\n case 'union':\n return { oneOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'discriminatedUnion': {\n const oneOf = type.members.map(m => typeToSchema(m, modelMap));\n const mapping: Record<string, string> = {};\n for (const member of type.members) {\n if (member.kind !== 'ref') continue;\n const literalValues = resolveDiscriminatorLiterals(member.name, type.discriminator, modelMap);\n if (literalValues.length === 0) continue;\n for (const v of literalValues) {\n mapping[v] = `#/components/schemas/${member.name}`;\n }\n }\n const result: Record<string, unknown> = {\n oneOf,\n discriminator: { propertyName: type.discriminator },\n };\n if (Object.keys(mapping).length > 0) {\n (result.discriminator as Record<string, unknown>).mapping = mapping;\n }\n return result;\n }\n case 'intersection':\n return { allOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'ref':\n return { $ref: `#/components/schemas/${type.name}` };\n case 'inlineObject':\n return inlineObjectToSchema(type.fields, modelMap);\n case 'lazy':\n return typeToSchema(type.inner, modelMap);\n }\n}\n\n/** Resolve literal values of a model's discriminator field. Returns [] if not resolvable. */\nfunction resolveDiscriminatorLiterals(modelName: string, discriminator: string, modelMap?: Map<string, ModelNode>): string[] {\n if (!modelMap) return [];\n const model = modelMap.get(modelName);\n if (!model) return [];\n const field = model.fields.find(f => f.name === discriminator);\n if (!field) return [];\n if (field.type.kind === 'literal') return [String(field.type.value)];\n if (field.type.kind === 'enum') return field.type.values;\n return [];\n}\n\n/**\n * Map a ContractKit scalar type node to its OpenAPI schema object (`{ type, format, ... }`),\n * carrying across constraints like min/max/length.\n *\n * @throws {Error} If the scalar name is not mapped — every member of the closed scalar set must\n * have an explicit case, so an unmapped name signals a scalar was added to core without updating\n * this plugin (rather than silently emitting a type-less, permissive schema).\n */\nexport function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Record<string, unknown> {\n const s: Record<string, unknown> = {};\n\n switch (type.name) {\n case 'string':\n s.type = 'string';\n if (type.min !== undefined) s.minLength = Number(type.min);\n if (type.max !== undefined) s.maxLength = Number(type.max);\n if (type.len !== undefined) {\n s.minLength = type.len;\n s.maxLength = type.len;\n }\n if (type.regex) s.pattern = type.regex;\n break;\n case 'number':\n s.type = 'number';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'int':\n s.type = 'integer';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'bigint':\n s.type = 'integer';\n s.format = 'int64';\n break;\n case 'boolean':\n s.type = 'boolean';\n break;\n case 'date':\n s.type = 'string';\n s.format = 'date';\n break;\n case 'time':\n s.type = 'string';\n s.format = 'time';\n break;\n case 'datetime':\n s.type = 'string';\n s.format = 'date-time';\n break;\n case 'duration':\n s.type = 'string';\n s.format = 'duration';\n break;\n case 'interval':\n s.type = 'string';\n s.format = 'interval';\n break;\n case 'email':\n s.type = 'string';\n s.format = 'email';\n break;\n case 'url':\n s.type = 'string';\n s.format = 'uri';\n break;\n case 'uuid':\n s.type = 'string';\n s.format = 'uuid';\n break;\n case 'unknown':\n // No type constraint\n break;\n case 'null':\n s.type = 'null';\n break;\n case 'object':\n s.type = 'object';\n break;\n case 'binary':\n s.type = 'string';\n s.format = 'binary';\n break;\n case 'json':\n // Any JSON value — no type constraint\n break;\n default: {\n const _exhaustive: never = type.name;\n throw new Error(`plugin-openapi: unmapped scalar '${String(_exhaustive)}' — add a case`);\n }\n }\n\n return s;\n}\n\nfunction arrayToSchema(type: import('@contractkit/core').ArrayTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const s: Record<string, unknown> = { type: 'array', items: typeToSchema(type.item, modelMap) };\n if (type.min !== undefined) s.minItems = type.min;\n if (type.max !== undefined) s.maxItems = type.max;\n return s;\n}\n\nfunction inlineObjectToSchema(fields: FieldNode[], modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of fields) {\n properties[field.name] = fieldToSchema(field, modelMap);\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = { type: 'object', properties };\n if (required.length > 0) schema.required = required;\n return schema;\n}\n\nfunction wrapNullable(schema: Record<string, unknown>): Record<string, unknown> {\n // OpenAPI 3.1 uses JSON Schema nullable via oneOf or type array\n if (schema.$ref) {\n return { oneOf: [schema, { type: 'null' }] };\n }\n if (typeof schema.type === 'string') {\n schema.type = [schema.type, 'null'];\n }\n return schema;\n}\n\n// ─── Operation building ─────────────────────────────────────────────────\n\nfunction buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string, unknown> {\n const operation: Record<string, unknown> = {};\n\n // operationId from service binding or SDK name\n if (op.sdk) {\n operation.operationId = op.sdk;\n } else if (op.service) {\n const methodPart = op.service.split('.').pop();\n if (methodPart) operation.operationId = methodPart;\n }\n\n if (op.description) {\n operation.description = op.description;\n }\n\n // Parameters: path params + query + headers\n const parameters: Record<string, unknown>[] = [];\n\n if (route.params) {\n parameters.push(...paramSourceToParams(route.params, 'path'));\n }\n if (op.query) {\n parameters.push(...paramSourceToParams(op.query, 'query'));\n }\n if (op.headers) {\n parameters.push(...paramSourceToParams(op.headers, 'header'));\n }\n\n if (parameters.length > 0) {\n operation.parameters = parameters;\n }\n\n // Request body\n if (op.request && op.request.bodies.length > 0) {\n const content: Record<string, { schema: ReturnType<typeof typeToSchema> }> = {};\n for (const body of op.request.bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n operation.requestBody = { required: true, content };\n }\n\n // Effective security (operation-level wins; falls back to route-level)\n // security: none → empty array (explicit public endpoint, overrides global default)\n // security: { fields } → omit operation-level entry (rely on global security from config)\n const effectiveSecurity = resolveSecurity(route, op);\n if (effectiveSecurity === SECURITY_NONE) {\n operation.security = [];\n }\n\n // Responses\n const responses: Record<string, unknown> = {};\n for (const resp of op.responses) {\n const statusKey = String(resp.statusCode);\n const responseObject: Record<string, unknown> = {\n description: statusDescription(resp.statusCode),\n };\n if (resp.bodyType && resp.contentType) {\n responseObject.content = {\n [resp.contentType]: {\n schema: typeToSchema(resp.bodyType),\n },\n };\n }\n if (resp.headers && resp.headers.length > 0) {\n const headers: Record<string, unknown> = {};\n for (const h of resp.headers) {\n const headerObject: Record<string, unknown> = {\n schema: typeToSchema(h.type),\n };\n if (!h.optional) headerObject.required = true;\n if (h.description) headerObject.description = h.description;\n headers[h.name] = headerObject;\n }\n responseObject.headers = headers;\n }\n responses[statusKey] = responseObject;\n }\n\n if (Object.keys(responses).length > 0) {\n operation.responses = responses;\n }\n\n return operation;\n}\n\nfunction paramSourceToParams(source: ParamSource, location: 'path' | 'query' | 'header'): Record<string, unknown>[] {\n if (source.kind === 'ref') {\n // Type reference name used as param source — emit a single $ref\n return [\n {\n name: source.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.name}` },\n },\n ];\n }\n\n if (source.kind === 'params') {\n // Inline param declarations\n return source.nodes.map(p => ({\n name: p.name,\n in: location,\n required: location === 'path',\n schema: typeToSchema(p.type),\n }));\n }\n\n // ContractTypeNode (inline object or other type) — if it's an inlineObject, expand fields\n if (source.node.kind === 'inlineObject') {\n return source.node.fields.map(f => ({\n name: f.name,\n in: location,\n required: location === 'path' ? true : !f.optional,\n schema: typeToSchema(f.type),\n }));\n }\n\n // For a ref type used as param source\n if (source.node.kind === 'ref') {\n return [\n {\n name: source.node.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.node.name}` },\n },\n ];\n }\n\n return [];\n}\n\nfunction statusDescription(code: number): string {\n const descriptions: Record<number, string> = {\n 200: 'Successful response',\n 201: 'Created',\n 204: 'No content',\n 400: 'Bad request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not found',\n 409: 'Conflict',\n 422: 'Unprocessable entity',\n 500: 'Internal server error',\n };\n return descriptions[code] ?? `Response ${code}`;\n}\n\n// ─── YAML serializer ──────────────────────────────────────────────────────\n\n/**\n * Minimal YAML serializer sufficient for OpenAPI documents.\n * Avoids external dependency while producing clean, readable output.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n if (value === null || value === undefined) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (typeof value === 'number') return String(value);\n if (typeof value === 'bigint') return String(value);\n\n if (typeof value === 'string') {\n return yamlString(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n\n // Check if all items are simple scalars (for inline arrays like enum values, required lists)\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n const inline = `[${items.join(', ')}]`;\n if (inline.length < 80) return inline;\n }\n\n const lines: string[] = [];\n const pad = ' '.repeat(indent);\n for (const item of value) {\n if (isPlainObject(item)) {\n const entries = Object.entries(item as Record<string, unknown>);\n if (entries.length > 0) {\n const [firstKey, firstVal] = entries[0]!;\n const firstValStr = isComplex(firstVal) ? `\\n${toYamlValue(firstVal, indent + 2)}` : ` ${toYaml(firstVal, indent + 2)}`;\n lines.push(`${pad}- ${yamlKey(firstKey)}:${firstValStr}`);\n for (let i = 1; i < entries.length; i++) {\n const [k, v] = entries[i]!;\n const valStr = isComplex(v) ? `\\n${toYamlValue(v, indent + 2)}` : ` ${toYaml(v, indent + 2)}`;\n lines.push(`${pad} ${yamlKey(k)}:${valStr}`);\n }\n } else {\n lines.push(`${pad}- {}`);\n }\n } else {\n lines.push(`${pad}- ${toYaml(item, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n if (isPlainObject(value)) {\n const obj = value as Record<string, unknown>;\n const entries = Object.entries(obj);\n if (entries.length === 0) return '{}';\n\n const pad = ' '.repeat(indent);\n const lines: string[] = [];\n for (const [key, val] of entries) {\n if (isComplex(val)) {\n lines.push(`${pad}${yamlKey(key)}:`);\n lines.push(toYamlValue(val, indent + 1));\n } else {\n lines.push(`${pad}${yamlKey(key)}: ${toYaml(val, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n return String(value);\n}\n\nfunction toYamlValue(value: unknown, indent: number): string {\n if (Array.isArray(value)) {\n return toYaml(value, indent);\n }\n if (isPlainObject(value)) {\n return toYaml(value, indent);\n }\n return ' '.repeat(indent) + toYaml(value, indent);\n}\n\nfunction isComplex(value: unknown): boolean {\n if (Array.isArray(value)) {\n // Simple scalar arrays can be inlined\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n return `[${items.join(', ')}]`.length >= 80;\n }\n return true;\n }\n return isPlainObject(value);\n}\n\nfunction isPlainObject(value: unknown): boolean {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction yamlString(s: string): string {\n // Use plain style if safe, otherwise single-quoted\n if (s === '') return \"''\";\n if (/^[\\w./-]+$/.test(s) && !/^(true|false|null|yes|no|on|off)$/i.test(s) && !/^\\d/.test(s)) {\n return s;\n }\n // Single-quote, escaping internal single quotes by doubling\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nfunction yamlKey(key: string): string {\n // Keys with special chars need quoting\n if (/^[\\w-]+$/.test(key) && !/^(true|false|null|yes|no|on|off)$/i.test(key)) {\n return key;\n }\n return `'${key.replace(/'/g, \"''\")}'`;\n}\n"],"mappings":"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","names":["resolve","resolveModifiers","resolveSecurity","SECURITY_NONE","collectRefsFromType","type","out","kind","add","name","item","items","value","member","members","inner","field","fields","collectParamSourceRefs","source","p","nodes","node","collectPublicTypeRefs","opRoots","includeInternal","refs","Set","opRoot","route","routes","op","operations","resolveModifiers","includes","request","body","bodies","bodyType","resp","responses","headers","h","params","query","computeReachableSchemas","seeds","modelMap","reachable","frontier","length","pop","model","get","bases","b","ref","has","push","generateOpenApi","ctx","contractRoots","config","securitySchemes","doc","openapi","info","title","version","description","servers","security","allSchemas","Map","contractRoot","models","set","modelToSchema","paths","oaPath","convertPath","path","mods","operation","buildOperation","deprecated","method","schemas","filtered","schema","Object","entries","components","keys","toYaml","typeToSchema","properties","required","prop","fieldToSchema","optional","allOf","map","$ref","nullable","wrapNullable","visibility","readOnly","writeOnly","default","undefined","scalarToSchema","arrayToSchema","prefixItems","i","additionalProperties","enum","values","const","oneOf","m","mapping","literalValues","resolveDiscriminatorLiterals","discriminator","v","result","propertyName","inlineObjectToSchema","modelName","find","f","String","s","min","minLength","Number","max","maxLength","len","regex","pattern","minimum","maximum","format","_exhaustive","Error","minItems","maxItems","sdk","operationId","service","methodPart","split","parameters","paramSourceToParams","content","contentType","requestBody","effectiveSecurity","resolveSecurity","SECURITY_NONE","statusKey","statusCode","responseObject","statusDescription","headerObject","location","in","code","descriptions","indent","yamlString","Array","isArray","every","inline","join","lines","pad","repeat","isPlainObject","firstKey","firstVal","firstValStr","isComplex","toYamlValue","yamlKey","k","valStr","obj","key","val","test","replace","plugin","name","cacheKey","generateTargets","contractRoots","opRoots","ctx","securitySchemes","openapiConfig","options","base","baseDir","resolve","rootDir","outPath","output","emitFile","generateOpenApi","config","createOpenApiPlugin","JSON","stringify","content"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/codegen-openapi.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { generateOpenApi } from './codegen-openapi.js';\nimport type { ContractKitPlugin } from '@contractkit/core';\nimport type { OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\nexport type { OpenApiServerEntry, OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\n\nexport interface OpenApiPluginOptions extends OpenApiConfig {\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n// ─── Default export: loaded via plugins array, reads config from ctx.options ─\n\nconst plugin: ContractKitPlugin = {\n name: 'openapi',\n cacheKey: 'openapi',\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const { securitySchemes, ...openapiConfig } = ctx.options as OpenApiPluginOptions;\n const base = openapiConfig.baseDir ? resolve(ctx.rootDir, openapiConfig.baseDir) : ctx.rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n ctx.emitFile(outPath, generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes }));\n },\n};\n\nexport default plugin;\n\n// ─── Factory: for programmatic use with explicit config ────────────────────\n\nexport function createOpenApiPlugin(\n openapiConfig: OpenApiConfig,\n rootDir: string,\n securitySchemes?: Record<string, OpenApiSecurityScheme>,\n): ContractKitPlugin {\n return {\n name: 'openapi',\n cacheKey: `openapi:${JSON.stringify(openapiConfig)}`,\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const base = openapiConfig.baseDir ? resolve(rootDir, openapiConfig.baseDir) : rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n const content = generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes });\n ctx.emitFile(outPath, content);\n },\n };\n}\n","import type {\n ContractRootNode,\n OpRootNode,\n ContractTypeNode,\n FieldNode,\n ModelNode,\n OpRouteNode,\n OpOperationNode,\n ParamSource,\n} from '@contractkit/core';\nimport { resolveModifiers, resolveSecurity, SECURITY_NONE } from '@contractkit/core';\n\n/** A single entry for the OpenAPI `servers` array. */\nexport interface OpenApiServerEntry {\n url: string;\n description?: string;\n}\n\n/** A named entry for `components.securitySchemes` (e.g. an HTTP bearer or API-key scheme). */\nexport interface OpenApiSecurityScheme {\n type: string;\n scheme?: string;\n bearerFormat?: string;\n name?: string;\n in?: string;\n}\n\n/** Plugin configuration controlling the generated OpenAPI document (info block, servers, security, internal-op visibility). */\nexport interface OpenApiConfig {\n baseDir?: string;\n output?: string;\n info?: {\n title?: string;\n version?: string;\n description?: string;\n };\n servers?: OpenApiServerEntry[];\n /** Global OpenAPI security requirements (e.g. [{ bearerAuth: [] }]). Distinct from scheme definitions. */\n security?: Record<string, string[]>[];\n /**\n * Whether to document operations marked `internal`. Defaults to `false` — internal ops\n * are omitted from the spec so external consumers don't see them. Set to `true` for an\n * internal-use spec.\n */\n includeInternal?: boolean;\n}\n\n// ─── Type reachability ────────────────────────────────────────────────────\n\nfunction collectRefsFromType(type: ContractTypeNode, out: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n out.add(type.name);\n break;\n case 'array':\n collectRefsFromType(type.item, out);\n break;\n case 'tuple':\n for (const item of type.items) collectRefsFromType(item, out);\n break;\n case 'record':\n collectRefsFromType(type.value, out);\n break;\n case 'union':\n case 'discriminatedUnion':\n case 'intersection':\n for (const member of type.members) collectRefsFromType(member, out);\n break;\n case 'lazy':\n collectRefsFromType(type.inner, out);\n break;\n case 'inlineObject':\n for (const field of type.fields) collectRefsFromType(field.type, out);\n break;\n }\n}\n\nfunction collectParamSourceRefs(source: ParamSource | undefined, out: Set<string>): void {\n if (!source) return;\n if (source.kind === 'ref') {\n out.add(source.name);\n return;\n }\n if (source.kind === 'params') {\n for (const p of source.nodes) collectRefsFromType(p.type, out);\n return;\n }\n collectRefsFromType(source.node, out);\n}\n\n/** Collect all type names directly referenced by public operations (seed set). */\nfunction collectPublicTypeRefs(opRoots: OpRootNode[], includeInternal = false): Set<string> {\n const refs = new Set<string>();\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n for (const op of route.operations) {\n if (!includeInternal && resolveModifiers(route, op).includes('internal')) continue;\n if (op.request) {\n for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);\n }\n for (const resp of op.responses) {\n for (const body of resp.bodies) collectRefsFromType(body.bodyType, refs);\n if (resp.headers) {\n for (const h of resp.headers) collectRefsFromType(h.type, refs);\n }\n }\n collectParamSourceRefs(route.params, refs);\n collectParamSourceRefs(op.query, refs);\n collectParamSourceRefs(op.headers, refs);\n }\n }\n }\n return refs;\n}\n\n/** BFS-expand seed type names through the contract model graph. */\nfunction computeReachableSchemas(seeds: Set<string>, modelMap: Map<string, ModelNode>): Set<string> {\n const reachable = new Set<string>(seeds);\n const frontier = [...seeds];\n while (frontier.length > 0) {\n const name = frontier.pop()!;\n const model = modelMap.get(name);\n if (!model) continue;\n const refs = new Set<string>();\n if (model.type) collectRefsFromType(model.type, refs);\n for (const field of model.fields) collectRefsFromType(field.type, refs);\n if (model.bases) for (const b of model.bases) refs.add(b);\n for (const ref of refs) {\n if (!reachable.has(ref)) {\n reachable.add(ref);\n frontier.push(ref);\n }\n }\n }\n return reachable;\n}\n\n// ─── Public entry point ────────────────────────────────────────────────────\n\n/** Inputs to {@link generateOpenApi}: the parsed contracts/operations plus the OpenAPI config and shared security schemes. */\nexport interface OpenApiCodegenContext {\n contractRoots: ContractRootNode[];\n opRoots: OpRootNode[];\n config: OpenApiConfig;\n /** Named OpenAPI security scheme definitions to include in components.securitySchemes */\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n/**\n * Generate an OpenAPI 3.0 document (YAML) from the parsed contracts and operations.\n *\n * Emits `paths` from the operations and `components.schemas` from the contracts, honoring\n * `config.includeInternal` for `operation(internal)` visibility.\n *\n * @returns The OpenAPI document serialized as a YAML string.\n */\nexport function generateOpenApi(ctx: OpenApiCodegenContext): string {\n const { contractRoots, opRoots, config, securitySchemes } = ctx;\n const includeInternal = config.includeInternal ?? false;\n\n const doc: Record<string, unknown> = {\n openapi: '3.1.0',\n info: {\n title: config.info?.title ?? 'API',\n version: config.info?.version ?? '0.0.1',\n ...(config.info?.description ? { description: config.info.description } : {}),\n },\n };\n\n if (config.servers && config.servers.length > 0) {\n doc.servers = config.servers;\n }\n\n if (config.security && config.security.length > 0) {\n doc.security = config.security;\n }\n\n // Build component schemas from all contract models\n const allSchemas: Record<string, unknown> = {};\n const modelMap = new Map<string, ModelNode>();\n\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n modelMap.set(model.name, model);\n }\n }\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n allSchemas[model.name] = modelToSchema(model, modelMap);\n }\n }\n\n // Build paths from all operation files\n const paths: Record<string, Record<string, unknown>> = {};\n\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n const oaPath = convertPath(route.path);\n\n for (const op of route.operations) {\n const mods = resolveModifiers(route, op);\n if (!includeInternal && mods.includes('internal')) continue;\n // Lazily initialize the path object so all-internal routes\n // leave no empty entry in the output\n if (!paths[oaPath]) paths[oaPath] = {};\n const operation = buildOperation(route, op);\n if (mods.includes('deprecated')) (operation as Record<string, unknown>).deprecated = true;\n paths[oaPath][op.method] = operation;\n }\n }\n }\n\n doc.paths = paths;\n\n // Filter schemas to only include types reachable from public operations.\n // When there are no op files, all schemas are included (no filtering).\n const schemas: Record<string, unknown> =\n opRoots.length > 0\n ? (() => {\n const reachable = computeReachableSchemas(collectPublicTypeRefs(opRoots, includeInternal), modelMap);\n const filtered: Record<string, unknown> = {};\n for (const [name, schema] of Object.entries(allSchemas)) {\n if (reachable.has(name)) filtered[name] = schema;\n }\n return filtered;\n })()\n : allSchemas;\n\n const components: Record<string, unknown> = {};\n if (Object.keys(schemas).length > 0) {\n components.schemas = schemas;\n }\n if (securitySchemes && Object.keys(securitySchemes).length > 0) {\n components.securitySchemes = securitySchemes;\n }\n if (Object.keys(components).length > 0) {\n doc.components = components;\n }\n\n return toYaml(doc);\n}\n\n// ─── Path conversion ──────────────────────────────────────────────────────\n\n/** Path is already in OpenAPI `{param}` style — return as-is. */\nfunction convertPath(path: string): string {\n return path;\n}\n\n// ─── Schema conversion ───────────────────────────────────────────────────\n\nfunction modelToSchema(model: ModelNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n // Type alias (no fields)\n if (model.type) {\n const schema = typeToSchema(model.type, modelMap);\n if (model.description) schema.description = model.description;\n return schema;\n }\n\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n const prop = fieldToSchema(field, modelMap);\n properties[field.name] = prop;\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = {\n type: 'object',\n properties,\n };\n\n if (required.length > 0) {\n schema.required = required;\n }\n\n if (model.bases && model.bases.length > 0) {\n return {\n allOf: [...model.bases.map(b => ({ $ref: `#/components/schemas/${b}` })), schema],\n };\n }\n\n if (model.description) {\n schema.description = model.description;\n }\n if (model.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction fieldToSchema(field: FieldNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n let schema = typeToSchema(field.type, modelMap);\n\n if (field.nullable) {\n schema = wrapNullable(schema);\n }\n if (field.visibility === 'readonly') {\n schema.readOnly = true;\n } else if (field.visibility === 'writeonly') {\n schema.writeOnly = true;\n }\n if (field.default !== undefined) {\n schema.default = field.default;\n }\n if (field.description) {\n schema.description = field.description;\n }\n if (field.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction typeToSchema(type: ContractTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n switch (type.kind) {\n case 'scalar':\n return scalarToSchema(type);\n case 'array':\n return arrayToSchema(type, modelMap);\n case 'tuple':\n return { type: 'array', prefixItems: type.items.map(i => typeToSchema(i, modelMap)) };\n case 'record':\n return { type: 'object', additionalProperties: typeToSchema(type.value, modelMap) };\n case 'enum':\n return { type: 'string', enum: type.values };\n case 'literal':\n return { const: type.value };\n case 'union':\n return { oneOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'discriminatedUnion': {\n const oneOf = type.members.map(m => typeToSchema(m, modelMap));\n const mapping: Record<string, string> = {};\n for (const member of type.members) {\n if (member.kind !== 'ref') continue;\n const literalValues = resolveDiscriminatorLiterals(member.name, type.discriminator, modelMap);\n if (literalValues.length === 0) continue;\n for (const v of literalValues) {\n mapping[v] = `#/components/schemas/${member.name}`;\n }\n }\n const result: Record<string, unknown> = {\n oneOf,\n discriminator: { propertyName: type.discriminator },\n };\n if (Object.keys(mapping).length > 0) {\n (result.discriminator as Record<string, unknown>).mapping = mapping;\n }\n return result;\n }\n case 'intersection':\n return { allOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'ref':\n return { $ref: `#/components/schemas/${type.name}` };\n case 'inlineObject':\n return inlineObjectToSchema(type.fields, modelMap);\n case 'lazy':\n return typeToSchema(type.inner, modelMap);\n }\n}\n\n/** Resolve literal values of a model's discriminator field. Returns [] if not resolvable. */\nfunction resolveDiscriminatorLiterals(modelName: string, discriminator: string, modelMap?: Map<string, ModelNode>): string[] {\n if (!modelMap) return [];\n const model = modelMap.get(modelName);\n if (!model) return [];\n const field = model.fields.find(f => f.name === discriminator);\n if (!field) return [];\n if (field.type.kind === 'literal') return [String(field.type.value)];\n if (field.type.kind === 'enum') return field.type.values;\n return [];\n}\n\n/**\n * Map a ContractKit scalar type node to its OpenAPI schema object (`{ type, format, ... }`),\n * carrying across constraints like min/max/length.\n *\n * @throws {Error} If the scalar name is not mapped — every member of the closed scalar set must\n * have an explicit case, so an unmapped name signals a scalar was added to core without updating\n * this plugin (rather than silently emitting a type-less, permissive schema).\n */\nexport function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Record<string, unknown> {\n const s: Record<string, unknown> = {};\n\n switch (type.name) {\n case 'string':\n s.type = 'string';\n if (type.min !== undefined) s.minLength = Number(type.min);\n if (type.max !== undefined) s.maxLength = Number(type.max);\n if (type.len !== undefined) {\n s.minLength = type.len;\n s.maxLength = type.len;\n }\n if (type.regex) s.pattern = type.regex;\n break;\n case 'number':\n s.type = 'number';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'int':\n s.type = 'integer';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'bigint':\n s.type = 'integer';\n s.format = 'int64';\n break;\n case 'boolean':\n s.type = 'boolean';\n break;\n case 'date':\n s.type = 'string';\n s.format = 'date';\n break;\n case 'time':\n s.type = 'string';\n s.format = 'time';\n break;\n case 'datetime':\n s.type = 'string';\n s.format = 'date-time';\n break;\n case 'duration':\n s.type = 'string';\n s.format = 'duration';\n break;\n case 'interval':\n s.type = 'string';\n s.format = 'interval';\n break;\n case 'email':\n s.type = 'string';\n s.format = 'email';\n break;\n case 'url':\n s.type = 'string';\n s.format = 'uri';\n break;\n case 'uuid':\n s.type = 'string';\n s.format = 'uuid';\n break;\n case 'unknown':\n // No type constraint\n break;\n case 'null':\n s.type = 'null';\n break;\n case 'object':\n s.type = 'object';\n break;\n case 'binary':\n s.type = 'string';\n s.format = 'binary';\n break;\n case 'json':\n // Any JSON value — no type constraint\n break;\n default: {\n const _exhaustive: never = type.name;\n throw new Error(`plugin-openapi: unmapped scalar '${String(_exhaustive)}' — add a case`);\n }\n }\n\n return s;\n}\n\nfunction arrayToSchema(type: import('@contractkit/core').ArrayTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const s: Record<string, unknown> = { type: 'array', items: typeToSchema(type.item, modelMap) };\n if (type.min !== undefined) s.minItems = type.min;\n if (type.max !== undefined) s.maxItems = type.max;\n return s;\n}\n\nfunction inlineObjectToSchema(fields: FieldNode[], modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of fields) {\n properties[field.name] = fieldToSchema(field, modelMap);\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = { type: 'object', properties };\n if (required.length > 0) schema.required = required;\n return schema;\n}\n\nfunction wrapNullable(schema: Record<string, unknown>): Record<string, unknown> {\n // OpenAPI 3.1 uses JSON Schema nullable via oneOf or type array\n if (schema.$ref) {\n return { oneOf: [schema, { type: 'null' }] };\n }\n if (typeof schema.type === 'string') {\n schema.type = [schema.type, 'null'];\n }\n return schema;\n}\n\n// ─── Operation building ─────────────────────────────────────────────────\n\nfunction buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string, unknown> {\n const operation: Record<string, unknown> = {};\n\n // operationId from service binding or SDK name\n if (op.sdk) {\n operation.operationId = op.sdk;\n } else if (op.service) {\n const methodPart = op.service.split('.').pop();\n if (methodPart) operation.operationId = methodPart;\n }\n\n if (op.description) {\n operation.description = op.description;\n }\n\n // Parameters: path params + query + headers\n const parameters: Record<string, unknown>[] = [];\n\n if (route.params) {\n parameters.push(...paramSourceToParams(route.params, 'path'));\n }\n if (op.query) {\n parameters.push(...paramSourceToParams(op.query, 'query'));\n }\n if (op.headers) {\n parameters.push(...paramSourceToParams(op.headers, 'header'));\n }\n\n if (parameters.length > 0) {\n operation.parameters = parameters;\n }\n\n // Request body\n if (op.request && op.request.bodies.length > 0) {\n const content: Record<string, { schema: ReturnType<typeof typeToSchema> }> = {};\n for (const body of op.request.bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n operation.requestBody = { required: true, content };\n }\n\n // Effective security (operation-level wins; falls back to route-level)\n // security: none → empty array (explicit public endpoint, overrides global default)\n // security: { fields } → omit operation-level entry (rely on global security from config)\n const effectiveSecurity = resolveSecurity(route, op);\n if (effectiveSecurity === SECURITY_NONE) {\n operation.security = [];\n }\n\n // Responses\n const responses: Record<string, unknown> = {};\n for (const resp of op.responses) {\n const statusKey = String(resp.statusCode);\n const responseObject: Record<string, unknown> = {\n description: statusDescription(resp.statusCode),\n };\n const bodies = resp.bodies;\n if (bodies.length > 0) {\n // One `content` entry per declared mime — this is where a status serving several\n // formats becomes visible to every consumer of the spec.\n const content: Record<string, unknown> = {};\n for (const body of bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n responseObject.content = content;\n }\n if (resp.headers && resp.headers.length > 0) {\n const headers: Record<string, unknown> = {};\n for (const h of resp.headers) {\n const headerObject: Record<string, unknown> = {\n schema: typeToSchema(h.type),\n };\n if (!h.optional) headerObject.required = true;\n if (h.description) headerObject.description = h.description;\n headers[h.name] = headerObject;\n }\n responseObject.headers = headers;\n }\n responses[statusKey] = responseObject;\n }\n\n if (Object.keys(responses).length > 0) {\n operation.responses = responses;\n }\n\n return operation;\n}\n\nfunction paramSourceToParams(source: ParamSource, location: 'path' | 'query' | 'header'): Record<string, unknown>[] {\n if (source.kind === 'ref') {\n // Type reference name used as param source — emit a single $ref\n return [\n {\n name: source.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.name}` },\n },\n ];\n }\n\n if (source.kind === 'params') {\n // Inline param declarations\n return source.nodes.map(p => ({\n name: p.name,\n in: location,\n required: location === 'path',\n schema: typeToSchema(p.type),\n }));\n }\n\n // ContractTypeNode (inline object or other type) — if it's an inlineObject, expand fields\n if (source.node.kind === 'inlineObject') {\n return source.node.fields.map(f => ({\n name: f.name,\n in: location,\n required: location === 'path' ? true : !f.optional,\n schema: typeToSchema(f.type),\n }));\n }\n\n // For a ref type used as param source\n if (source.node.kind === 'ref') {\n return [\n {\n name: source.node.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.node.name}` },\n },\n ];\n }\n\n return [];\n}\n\nfunction statusDescription(code: number): string {\n const descriptions: Record<number, string> = {\n 200: 'Successful response',\n 201: 'Created',\n 204: 'No content',\n 400: 'Bad request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not found',\n 409: 'Conflict',\n 422: 'Unprocessable entity',\n 500: 'Internal server error',\n };\n return descriptions[code] ?? `Response ${code}`;\n}\n\n// ─── YAML serializer ──────────────────────────────────────────────────────\n\n/**\n * Minimal YAML serializer sufficient for OpenAPI documents.\n * Avoids external dependency while producing clean, readable output.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n if (value === null || value === undefined) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (typeof value === 'number') return String(value);\n if (typeof value === 'bigint') return String(value);\n\n if (typeof value === 'string') {\n return yamlString(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n\n // Check if all items are simple scalars (for inline arrays like enum values, required lists)\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n const inline = `[${items.join(', ')}]`;\n if (inline.length < 80) return inline;\n }\n\n const lines: string[] = [];\n const pad = ' '.repeat(indent);\n for (const item of value) {\n if (isPlainObject(item)) {\n const entries = Object.entries(item as Record<string, unknown>);\n if (entries.length > 0) {\n const [firstKey, firstVal] = entries[0]!;\n const firstValStr = isComplex(firstVal) ? `\\n${toYamlValue(firstVal, indent + 2)}` : ` ${toYaml(firstVal, indent + 2)}`;\n lines.push(`${pad}- ${yamlKey(firstKey)}:${firstValStr}`);\n for (let i = 1; i < entries.length; i++) {\n const [k, v] = entries[i]!;\n const valStr = isComplex(v) ? `\\n${toYamlValue(v, indent + 2)}` : ` ${toYaml(v, indent + 2)}`;\n lines.push(`${pad} ${yamlKey(k)}:${valStr}`);\n }\n } else {\n lines.push(`${pad}- {}`);\n }\n } else {\n lines.push(`${pad}- ${toYaml(item, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n if (isPlainObject(value)) {\n const obj = value as Record<string, unknown>;\n const entries = Object.entries(obj);\n if (entries.length === 0) return '{}';\n\n const pad = ' '.repeat(indent);\n const lines: string[] = [];\n for (const [key, val] of entries) {\n if (isComplex(val)) {\n lines.push(`${pad}${yamlKey(key)}:`);\n lines.push(toYamlValue(val, indent + 1));\n } else {\n lines.push(`${pad}${yamlKey(key)}: ${toYaml(val, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n return String(value);\n}\n\nfunction toYamlValue(value: unknown, indent: number): string {\n if (Array.isArray(value)) {\n return toYaml(value, indent);\n }\n if (isPlainObject(value)) {\n return toYaml(value, indent);\n }\n return ' '.repeat(indent) + toYaml(value, indent);\n}\n\nfunction isComplex(value: unknown): boolean {\n if (Array.isArray(value)) {\n // Simple scalar arrays can be inlined\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n return `[${items.join(', ')}]`.length >= 80;\n }\n return true;\n }\n return isPlainObject(value);\n}\n\nfunction isPlainObject(value: unknown): boolean {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction yamlString(s: string): string {\n // Use plain style if safe, otherwise single-quoted\n if (s === '') return \"''\";\n if (/^[\\w./-]+$/.test(s) && !/^(true|false|null|yes|no|on|off)$/i.test(s) && !/^\\d/.test(s)) {\n return s;\n }\n // Single-quote, escaping internal single quotes by doubling\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nfunction yamlKey(key: string): string {\n // Keys with special chars need quoting\n if (/^[\\w-]+$/.test(key) && !/^(true|false|null|yes|no|on|off)$/i.test(key)) {\n return key;\n }\n return `'${key.replace(/'/g, \"''\")}'`;\n}\n"],"mappings":"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","names":["resolve","resolveModifiers","resolveSecurity","SECURITY_NONE","collectRefsFromType","type","out","kind","add","name","item","items","value","member","members","inner","field","fields","collectParamSourceRefs","source","p","nodes","node","collectPublicTypeRefs","opRoots","includeInternal","refs","Set","opRoot","route","routes","op","operations","resolveModifiers","includes","request","body","bodies","bodyType","resp","responses","headers","h","params","query","computeReachableSchemas","seeds","modelMap","reachable","frontier","length","pop","model","get","bases","b","ref","has","push","generateOpenApi","ctx","contractRoots","config","securitySchemes","doc","openapi","info","title","version","description","servers","security","allSchemas","Map","contractRoot","models","set","modelToSchema","paths","oaPath","convertPath","path","mods","operation","buildOperation","deprecated","method","schemas","filtered","schema","Object","entries","components","keys","toYaml","typeToSchema","properties","required","prop","fieldToSchema","optional","allOf","map","$ref","nullable","wrapNullable","visibility","readOnly","writeOnly","default","undefined","scalarToSchema","arrayToSchema","prefixItems","i","additionalProperties","enum","values","const","oneOf","m","mapping","literalValues","resolveDiscriminatorLiterals","discriminator","v","result","propertyName","inlineObjectToSchema","modelName","find","f","String","s","min","minLength","Number","max","maxLength","len","regex","pattern","minimum","maximum","format","_exhaustive","Error","minItems","maxItems","sdk","operationId","service","methodPart","split","parameters","paramSourceToParams","content","contentType","requestBody","effectiveSecurity","resolveSecurity","SECURITY_NONE","statusKey","statusCode","responseObject","statusDescription","headerObject","location","in","code","descriptions","indent","yamlString","Array","isArray","every","inline","join","lines","pad","repeat","isPlainObject","firstKey","firstVal","firstValStr","isComplex","toYamlValue","yamlKey","k","valStr","obj","key","val","test","replace","plugin","name","cacheKey","generateTargets","contractRoots","opRoots","ctx","securitySchemes","openapiConfig","options","base","baseDir","resolve","rootDir","outPath","output","emitFile","generateOpenApi","config","createOpenApiPlugin","JSON","stringify","content"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractkit/plugin-openapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "ContractKit built-in plugin: OpenAPI 3.0 YAML generation",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marooned Software",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
".": "./dist/index.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@contractkit/core": "0.
|
|
29
|
+
"@contractkit/core": "0.26.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@repo/config-eslint": "0.3.1",
|
package/src/codegen-openapi.ts
CHANGED
|
@@ -99,7 +99,7 @@ function collectPublicTypeRefs(opRoots: OpRootNode[], includeInternal = false):
|
|
|
99
99
|
for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);
|
|
100
100
|
}
|
|
101
101
|
for (const resp of op.responses) {
|
|
102
|
-
|
|
102
|
+
for (const body of resp.bodies) collectRefsFromType(body.bodyType, refs);
|
|
103
103
|
if (resp.headers) {
|
|
104
104
|
for (const h of resp.headers) collectRefsFromType(h.type, refs);
|
|
105
105
|
}
|
|
@@ -564,12 +564,15 @@ function buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string,
|
|
|
564
564
|
const responseObject: Record<string, unknown> = {
|
|
565
565
|
description: statusDescription(resp.statusCode),
|
|
566
566
|
};
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
567
|
+
const bodies = resp.bodies;
|
|
568
|
+
if (bodies.length > 0) {
|
|
569
|
+
// One `content` entry per declared mime — this is where a status serving several
|
|
570
|
+
// formats becomes visible to every consumer of the spec.
|
|
571
|
+
const content: Record<string, unknown> = {};
|
|
572
|
+
for (const body of bodies) {
|
|
573
|
+
content[body.contentType] = { schema: typeToSchema(body.bodyType) };
|
|
574
|
+
}
|
|
575
|
+
responseObject.content = content;
|
|
573
576
|
}
|
|
574
577
|
if (resp.headers && resp.headers.length > 0) {
|
|
575
578
|
const headers: Record<string, unknown> = {};
|
|
@@ -361,6 +361,28 @@ describe('generateOpenApi', () => {
|
|
|
361
361
|
expect(output).toContain("'$ref': '#/components/schemas/User'");
|
|
362
362
|
});
|
|
363
363
|
|
|
364
|
+
it('gives a status one content entry per declared mime', () => {
|
|
365
|
+
const op = opRoot([
|
|
366
|
+
opRoute('/art', [
|
|
367
|
+
opOperation('get', {
|
|
368
|
+
responses: [
|
|
369
|
+
{
|
|
370
|
+
statusCode: 200,
|
|
371
|
+
hasBlock: true,
|
|
372
|
+
bodies: [
|
|
373
|
+
{ contentType: 'image/png', bodyType: { kind: 'scalar', name: 'binary' } },
|
|
374
|
+
{ contentType: 'image/jpeg', bodyType: { kind: 'scalar', name: 'binary' } },
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
}),
|
|
379
|
+
]),
|
|
380
|
+
]);
|
|
381
|
+
const output = generateOpenApi({ contractRoots: [], opRoots: [op], config: {} });
|
|
382
|
+
expect(output).toContain("'image/png':");
|
|
383
|
+
expect(output).toContain("'image/jpeg':");
|
|
384
|
+
});
|
|
385
|
+
|
|
364
386
|
it('generates POST with request body', () => {
|
|
365
387
|
const op = opRoot([
|
|
366
388
|
opRoute('/users', [
|
|
@@ -487,8 +509,8 @@ describe('generateOpenApi', () => {
|
|
|
487
509
|
responses: [
|
|
488
510
|
{
|
|
489
511
|
statusCode: 200,
|
|
490
|
-
|
|
491
|
-
bodyType: refType('Transfer'),
|
|
512
|
+
hasBlock: true,
|
|
513
|
+
bodies: [{ contentType: 'application/json', bodyType: refType('Transfer') }],
|
|
492
514
|
headers: [
|
|
493
515
|
{ name: 'preference-applied', optional: true, type: scalarType('string') },
|
|
494
516
|
{ name: 'etag', optional: false, type: scalarType('string'), description: 'cache validator' },
|
package/tests/helpers.ts
CHANGED
|
@@ -129,7 +129,10 @@ export function opRequest(
|
|
|
129
129
|
export function opResponse(statusCode: number, bodyType?: string | ContractTypeNode, contentType?: 'application/json'): OpResponseNode {
|
|
130
130
|
const bt: ContractTypeNode | undefined =
|
|
131
131
|
bodyType === undefined ? undefined : typeof bodyType === 'string' ? parseBodyTypeString(bodyType) : bodyType;
|
|
132
|
-
|
|
132
|
+
// A body with no explicit mime defaults to JSON, matching how every plugin used to read
|
|
133
|
+
// the old singular contentType field.
|
|
134
|
+
const bodies = bt === undefined ? [] : [{ contentType: contentType ?? 'application/json', bodyType: bt }];
|
|
135
|
+
return { statusCode, bodies, ...(bt !== undefined ? { hasBlock: true } : {}) };
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
function parseBodyTypeString(s: string): ContractTypeNode {
|