@contractkit/plugin-openapi 0.9.2 → 0.9.3
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 -8
- package/CHANGELOG.md +22 -0
- package/dist/codegen-openapi.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/codegen-openapi.ts +13 -0
- package/tests/round-trip-ck.test.ts +134 -0
|
@@ -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[32m47.
|
|
10
|
-
[32mESM[39m ⚡️ Build success in
|
|
8
|
+
[32mESM[39m [1mdist/index.js [22m[32m20.43 KB[39m
|
|
9
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m47.89 KB[39m
|
|
10
|
+
[32mESM[39m ⚡️ Build success in 76ms
|
|
11
11
|
[34mDTS[39m Build start
|
|
12
|
-
[32mDTS[39m ⚡️ Build success in
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 980ms
|
|
13
13
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.60 KB[39m
|
|
@@ -3,20 +3,21 @@ $ 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[2m57 tests[22m[2m)[22m[
|
|
6
|
+
[32m✓[39m tests/codegen-openapi.test.ts [2m([22m[2m57 tests[22m[2m)[22m[32m 84[2mms[22m[39m
|
|
7
|
+
[32m✓[39m tests/round-trip-ck.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 153[2mms[22m[39m
|
|
7
8
|
|
|
8
|
-
[2m Test Files [22m [1m[
|
|
9
|
-
[2m Tests [22m [1m[
|
|
10
|
-
[2m Start at [22m
|
|
11
|
-
[2m Duration [22m
|
|
9
|
+
[2m Test Files [22m [1m[32m2 passed[39m[22m[90m (2)[39m
|
|
10
|
+
[2m Tests [22m [1m[32m59 passed[39m[22m[90m (59)[39m
|
|
11
|
+
[2m Start at [22m 16:35:40
|
|
12
|
+
[2m Duration [22m 2.30s[2m (transform 983ms, setup 0ms, import 3.32s, tests 237ms, environment 0ms)[22m
|
|
12
13
|
|
|
13
14
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
14
15
|
-------------------|---------|----------|---------|---------|-------------------
|
|
15
16
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
16
17
|
-------------------|---------|----------|---------|---------|-------------------
|
|
17
|
-
All files | 80.
|
|
18
|
-
src |
|
|
19
|
-
...en-openapi.ts |
|
|
18
|
+
All files | 80.51 | 76.14 | 82.25 | 82.58 |
|
|
19
|
+
src | 81.23 | 76.55 | 86.11 | 83.14 |
|
|
20
|
+
...en-openapi.ts | 81.23 | 76.55 | 86.11 | 83.14 | ...17-720,744,754
|
|
20
21
|
tests | 73.91 | 72.97 | 76.92 | 77.5 |
|
|
21
22
|
helpers.ts | 73.91 | 72.97 | 76.92 | 77.5 | ...18,141,152-153
|
|
22
23
|
-------------------|---------|----------|---------|---------|-------------------
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @contractkit/contractkit-plugin-openapi
|
|
2
2
|
|
|
3
|
+
## 0.9.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8987af2: Carry `name:` and the documented-response marker into the generated spec
|
|
8
|
+
|
|
9
|
+
OpenAPI has no way to say whether a service produces a status or merely documents it, so the
|
|
10
|
+
distinction `.ck` draws was lost on the way out and could not be recovered on the way back in:
|
|
11
|
+
a `.ck` → OpenAPI → `.ck` round trip turned every `(documented)` error response into a
|
|
12
|
+
service-produced one, silently changing what the generated router and SDKs do.
|
|
13
|
+
|
|
14
|
+
A response marked `(documented)` now carries `x-contractkit-emit: documented`, which
|
|
15
|
+
`@contractkit/openapi-to-ck` honours on import. Vendor extensions are spec-legal and ignored by
|
|
16
|
+
other tooling.
|
|
17
|
+
|
|
18
|
+
An operation's `name:` is now emitted as the OpenAPI `summary`, which it previously omitted
|
|
19
|
+
entirely.
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [aea5e21]
|
|
22
|
+
- Updated dependencies [5dc2693]
|
|
23
|
+
- @contractkit/core@0.27.0
|
|
24
|
+
|
|
3
25
|
## 0.9.2
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -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;AA6MD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAI,GAAG,MAAM,CA+DzD"}
|
package/dist/index.js
CHANGED
|
@@ -446,6 +446,9 @@ function buildOperation(route, op) {
|
|
|
446
446
|
const methodPart = op.service.split(".").pop();
|
|
447
447
|
if (methodPart) operation.operationId = methodPart;
|
|
448
448
|
}
|
|
449
|
+
if (op.name) {
|
|
450
|
+
operation.summary = op.name;
|
|
451
|
+
}
|
|
449
452
|
if (op.description) {
|
|
450
453
|
operation.description = op.description;
|
|
451
454
|
}
|
|
@@ -506,6 +509,9 @@ function buildOperation(route, op) {
|
|
|
506
509
|
}
|
|
507
510
|
responseObject.headers = headers;
|
|
508
511
|
}
|
|
512
|
+
if (resp.emit === "documented") {
|
|
513
|
+
responseObject["x-contractkit-emit"] = "documented";
|
|
514
|
+
}
|
|
509
515
|
responses[statusKey] = responseObject;
|
|
510
516
|
}
|
|
511
517
|
if (Object.keys(responses).length > 0) {
|
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 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"]}
|
|
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 // `name:` is the operation's human-readable label, which is what `summary` is for. Without\n // it the name is lost on the way out, and lost again on the way back through `openapi-to-ck`.\n if (op.name) {\n operation.summary = op.name;\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 // OpenAPI cannot say whether the service produces a status or merely documents it, so\n // the distinction `.ck` draws would be lost on every round trip — every `(documented)`\n // error would come back as service-produced. A vendor extension is spec-legal and\n // ignored by other tooling, so carry it rather than drop it.\n if (resp.emit === 'documented') {\n responseObject['x-contractkit-emit'] = 'documented';\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","summary","parameters","paramSourceToParams","content","contentType","requestBody","effectiveSecurity","resolveSecurity","SECURITY_NONE","statusKey","statusCode","responseObject","statusDescription","headerObject","emit","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.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "ContractKit built-in plugin: OpenAPI 3.0 YAML generation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
".": "./dist/index.js"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@contractkit/core": "0.
|
|
30
|
+
"@contractkit/core": "0.27.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
+
"yaml": "^2.8.3",
|
|
34
|
+
"@contractkit/openapi-to-ck": "0.11.0",
|
|
33
35
|
"@repo/config-eslint": "0.3.1",
|
|
34
36
|
"@repo/config-typescript": "0.1.0"
|
|
35
37
|
},
|
package/src/codegen-openapi.ts
CHANGED
|
@@ -519,6 +519,12 @@ function buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string,
|
|
|
519
519
|
if (methodPart) operation.operationId = methodPart;
|
|
520
520
|
}
|
|
521
521
|
|
|
522
|
+
// `name:` is the operation's human-readable label, which is what `summary` is for. Without
|
|
523
|
+
// it the name is lost on the way out, and lost again on the way back through `openapi-to-ck`.
|
|
524
|
+
if (op.name) {
|
|
525
|
+
operation.summary = op.name;
|
|
526
|
+
}
|
|
527
|
+
|
|
522
528
|
if (op.description) {
|
|
523
529
|
operation.description = op.description;
|
|
524
530
|
}
|
|
@@ -586,6 +592,13 @@ function buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string,
|
|
|
586
592
|
}
|
|
587
593
|
responseObject.headers = headers;
|
|
588
594
|
}
|
|
595
|
+
// OpenAPI cannot say whether the service produces a status or merely documents it, so
|
|
596
|
+
// the distinction `.ck` draws would be lost on every round trip — every `(documented)`
|
|
597
|
+
// error would come back as service-produced. A vendor extension is spec-legal and
|
|
598
|
+
// ignored by other tooling, so carry it rather than drop it.
|
|
599
|
+
if (resp.emit === 'documented') {
|
|
600
|
+
responseObject['x-contractkit-emit'] = 'documented';
|
|
601
|
+
}
|
|
589
602
|
responses[statusKey] = responseObject;
|
|
590
603
|
}
|
|
591
604
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseCk, decomposeCk, applyOptionsDefaults, DiagnosticCollector, emittedResponses, thrownResponses } from '@contractkit/core';
|
|
3
|
+
import { convertOpenApiToCk } from '@contractkit/openapi-to-ck';
|
|
4
|
+
import { parse as parseYaml } from 'yaml';
|
|
5
|
+
import { generateOpenApi } from '../src/codegen-openapi.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `.ck` → OpenAPI → `.ck`.
|
|
9
|
+
*
|
|
10
|
+
* The two directions live in the same repo and can drift apart silently: OpenAPI has no way to
|
|
11
|
+
* say whether the service produces a status or merely documents it, so without the
|
|
12
|
+
* `x-contractkit-emit` extension every `(documented)` error came back as service-produced — a
|
|
13
|
+
* round trip that quietly changed what the generated router and SDKs do.
|
|
14
|
+
*
|
|
15
|
+
* The trip is lossy by design in places (`service:` bindings, named security policies, `plugins:`
|
|
16
|
+
* blocks have no OpenAPI representation). What is asserted here is the contract of what must
|
|
17
|
+
* survive; anything listed as an exclusion is a documented limitation, not an oversight.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const SOURCE = `
|
|
21
|
+
contract Pet: {
|
|
22
|
+
id: readonly uuid
|
|
23
|
+
name: string(min=1, max=64)
|
|
24
|
+
code?: string(regex=/^[a-z]+$/)
|
|
25
|
+
status: enum(available, "on hold")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
contract ApiError: {
|
|
29
|
+
message: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
operation /pets/{petId}: {
|
|
33
|
+
params: {
|
|
34
|
+
petId: uuid
|
|
35
|
+
}
|
|
36
|
+
get: {
|
|
37
|
+
name: Fetch one pet
|
|
38
|
+
sdk: getPet
|
|
39
|
+
query: {
|
|
40
|
+
expand?: boolean
|
|
41
|
+
}
|
|
42
|
+
response: {
|
|
43
|
+
200: {
|
|
44
|
+
application/json: Pet
|
|
45
|
+
text/csv: string
|
|
46
|
+
headers: {
|
|
47
|
+
ETag: string
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
404(documented): {
|
|
51
|
+
application/json: ApiError
|
|
52
|
+
}
|
|
53
|
+
500(documented): {
|
|
54
|
+
application/json: ApiError
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
function parse(source: string, file = 'api.ck') {
|
|
62
|
+
const diag = new DiagnosticCollector();
|
|
63
|
+
const root = parseCk(source, file, diag);
|
|
64
|
+
expect(diag.hasErrors()).toBe(false);
|
|
65
|
+
applyOptionsDefaults(root, diag);
|
|
66
|
+
return root;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe('ck → openapi → ck', () => {
|
|
70
|
+
it('preserves the shape a generator depends on', async () => {
|
|
71
|
+
const before = parse(SOURCE);
|
|
72
|
+
const { contract, op } = decomposeCk(before);
|
|
73
|
+
const yaml = generateOpenApi({ contractRoots: [contract], opRoots: [op], config: {} });
|
|
74
|
+
|
|
75
|
+
const result = await convertOpenApiToCk({ input: parseYaml(yaml) as Record<string, unknown>, split: 'single' });
|
|
76
|
+
expect(result.warnings.filter(w => w.message.includes('does not parse'))).toEqual([]);
|
|
77
|
+
const after = parse(result.files.get('api.ck')!);
|
|
78
|
+
|
|
79
|
+
const opBefore = before.routes[0]!.operations[0]!;
|
|
80
|
+
const opAfter = after.routes[0]!.operations[0]!;
|
|
81
|
+
|
|
82
|
+
// The emitted/documented split is the thing this round trip used to destroy.
|
|
83
|
+
expect(emittedResponses(opAfter).map(r => r.statusCode)).toEqual(emittedResponses(opBefore).map(r => r.statusCode));
|
|
84
|
+
expect(thrownResponses(opAfter).map(r => r.statusCode)).toEqual(thrownResponses(opBefore).map(r => r.statusCode));
|
|
85
|
+
expect(emittedResponses(opAfter).map(r => r.statusCode)).toEqual([200]);
|
|
86
|
+
expect(thrownResponses(opAfter).map(r => r.statusCode)).toEqual([404, 500]);
|
|
87
|
+
|
|
88
|
+
// Several content types on one status.
|
|
89
|
+
const ok = opAfter.responses.find(r => r.statusCode === 200)!;
|
|
90
|
+
expect(ok.bodies.map(b => b.contentType)).toEqual(['application/json', 'text/csv']);
|
|
91
|
+
expect(ok.headers!.map(h => h.name)).toEqual(['ETag']);
|
|
92
|
+
|
|
93
|
+
// Route, verb, name and sdk binding.
|
|
94
|
+
expect(after.routes[0]!.path).toBe('/pets/{petId}');
|
|
95
|
+
expect(opAfter.method).toBe('get');
|
|
96
|
+
expect(opAfter.name).toBe('Fetch one pet');
|
|
97
|
+
expect(opAfter.sdk).toBe('getPet');
|
|
98
|
+
|
|
99
|
+
// Models, including a regex and a quoted enum value.
|
|
100
|
+
const pet = after.models.find(m => m.name === 'Pet')!;
|
|
101
|
+
expect(pet.fields.map(f => f.name)).toEqual(['id', 'name', 'code', 'status']);
|
|
102
|
+
expect(pet.fields.find(f => f.name === 'code')!.type).toEqual({ kind: 'scalar', name: 'string', regex: '^[a-z]+$' });
|
|
103
|
+
expect(pet.fields.find(f => f.name === 'status')!.type).toEqual({ kind: 'enum', values: ['available', 'on hold'] });
|
|
104
|
+
expect(pet.fields.find(f => f.name === 'name')!.type).toEqual({ kind: 'scalar', name: 'string', min: 1, max: 64 });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('carries the documented marker through the vendor extension, not the status heuristic', async () => {
|
|
108
|
+
// A 2xx marked `(documented)` cannot be recovered from the status code alone, so this
|
|
109
|
+
// isolates the extension from the 4xx/5xx default.
|
|
110
|
+
const before = parse(`
|
|
111
|
+
contract Job: { id: uuid }
|
|
112
|
+
|
|
113
|
+
operation /jobs: {
|
|
114
|
+
post: {
|
|
115
|
+
sdk: startJob
|
|
116
|
+
response: {
|
|
117
|
+
202(documented): {
|
|
118
|
+
application/json: Job
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`);
|
|
124
|
+
const { contract, op } = decomposeCk(before);
|
|
125
|
+
const yaml = generateOpenApi({ contractRoots: [contract], opRoots: [op], config: {} });
|
|
126
|
+
expect(yaml).toContain('x-contractkit-emit');
|
|
127
|
+
|
|
128
|
+
const result = await convertOpenApiToCk({ input: parseYaml(yaml) as Record<string, unknown>, split: 'single' });
|
|
129
|
+
const after = parse(result.files.get('api.ck')!);
|
|
130
|
+
const resp = after.routes[0]!.operations[0]!.responses[0]!;
|
|
131
|
+
expect(resp.statusCode).toBe(202);
|
|
132
|
+
expect(resp.emit).toBe('documented');
|
|
133
|
+
});
|
|
134
|
+
});
|