From 9e2f664106f696c5341925ef04153848c5dcfda3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:16:52 +0000 Subject: [PATCH 1/3] Initial plan From a3b88dcc8034b12dc0a97c32e524bab33f75c6b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:51:08 +0000 Subject: [PATCH 2/3] fix: parse components.headers to resolve crash when schemas reference header components Agent-Logs-Url: https://github.com/hey-api/openapi-ts/sessions/8e289c88-b983-48a9-84b3-25f60cecebbd --- .../openapi-ts-tests/main/test/3.0.x.test.ts | 7 +++++ .../3.0.x/components-headers/index.ts | 3 +++ .../3.0.x/components-headers/types.gen.ts | 27 +++++++++++++++++++ .../__snapshots__/3.0.x/external/index.ts | 2 +- .../__snapshots__/3.0.x/external/types.gen.ts | 6 +++++ .../__snapshots__/3.1.x/external/index.ts | 2 +- .../__snapshots__/3.1.x/external/types.gen.ts | 6 +++++ packages/shared/src/ir/graph.ts | 2 +- packages/shared/src/ir/types.ts | 1 + .../shared/src/openApi/3.0.x/parser/index.ts | 19 ++++++++++++- .../shared/src/openApi/3.0.x/parser/schema.ts | 27 +++++++++++++++++++ .../shared/src/openApi/3.1.x/parser/index.ts | 19 ++++++++++++- .../shared/src/openApi/3.1.x/parser/schema.ts | 27 +++++++++++++++++++ specs/3.0.x/components-headers.yaml | 27 +++++++++++++++++++ 14 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/types.gen.ts create mode 100644 specs/3.0.x/components-headers.yaml diff --git a/packages/openapi-ts-tests/main/test/3.0.x.test.ts b/packages/openapi-ts-tests/main/test/3.0.x.test.ts index 8d1c8c360b..15593d0fde 100644 --- a/packages/openapi-ts-tests/main/test/3.0.x.test.ts +++ b/packages/openapi-ts-tests/main/test/3.0.x.test.ts @@ -151,6 +151,13 @@ describe(`OpenAPI ${version}`, () => { }), description: 'handles snake_case identifier casing', }, + { + config: createConfig({ + input: 'components-headers.yaml', + output: 'components-headers', + }), + description: 'handles reusable header components', + }, { config: createConfig({ input: 'components-request-bodies.json', diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/index.ts new file mode 100644 index 0000000000..38b1794ee0 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { ClientOptions, Foo, GetFooData, GetFooResponse, GetFooResponses, XRateLimit } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/types.gen.ts new file mode 100644 index 0000000000..72f36b930c --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/types.gen.ts @@ -0,0 +1,27 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type XRateLimit = number; + +export type Foo = { + rateLimit?: XRateLimit; +}; + +export type GetFooData = { + body?: never; + path?: never; + query?: never; + url: '/foo'; +}; + +export type GetFooResponses = { + /** + * OK + */ + 200: Foo; +}; + +export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/index.ts index fb12847e21..5e40b58fbe 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { _1, ClientOptions, Deep, ExternalAllOfSchema, ExternalAnyOfSchema, ExternalArraySchema, ExternalDeepParam, ExternalDoubleNestedNumeric, ExternalDoubleNestedProp, ExternalIdParam, ExternalMixedBody, ExternalMixedProperties, ExternalModelBody, ExternalNested, ExternalNestedBody, ExternalNestedNumeric, ExternalNestedNumericObjectA, ExternalNestedNumericObjectB, ExternalNestedObjectA, ExternalNestedObjectB, ExternalNumericParam, ExternalSchemaA, ExternalSchemaB, ExternalSchemaC, ExternalSchemaExternalProp, ExternalSchemaExternalPropAlias, ExternalSchemaPathA, ExternalSchemaPathB, ExternalSchemaPropertyA, ExternalSchemaPropertyB, ExternalSchemaPropertyC, ExternalSchemaPropertyD, ExternalSharedModel, ExternalSharedModelWithUuid, ExternalUnionSchema, ExternalUuidBody, ExternalUuidParam, GetExternalArrayData, GetExternalArrayResponse, GetExternalArrayResponses, GetExternalMixedData, GetExternalMixedResponse, GetExternalMixedResponses, GetExternalModelData, GetExternalModelError, GetExternalModelErrors, GetExternalModelResponse, GetExternalModelResponses, GetExternalNestedData, GetExternalNestedResponse, GetExternalNestedResponses, GetExternalPropertiesByIdData, GetExternalPropertiesByIdResponse, GetExternalPropertiesByIdResponses, GetExternalUnionData, GetExternalUnionResponse, GetExternalUnionResponses, GetExternalUuidData, GetExternalUuidResponse, GetExternalUuidResponses, Id, Name, PostExternalArrayData, PostExternalArrayResponse, PostExternalArrayResponses, PostExternalMixedData, PostExternalMixedResponse, PostExternalMixedResponses, PostExternalModelData, PostExternalModelError, PostExternalModelErrors, PostExternalModelResponse, PostExternalModelResponses, PostExternalNestedData, PostExternalNestedResponse, PostExternalNestedResponses, PostExternalUnionData, PostExternalUnionResponse, PostExternalUnionResponses, PutExternalUuidData, PutExternalUuidResponse, PutExternalUuidResponses } from './types.gen'; +export type { _1, ClientOptions, Deep, ExternalAllOfSchema, ExternalAnyOfSchema, ExternalArraySchema, ExternalDeepHeader, ExternalDeepParam, ExternalDoubleNestedNumeric, ExternalDoubleNestedProp, ExternalIdHeader, ExternalIdParam, ExternalMixedBody, ExternalMixedProperties, ExternalModelBody, ExternalNested, ExternalNestedBody, ExternalNestedNumeric, ExternalNestedNumericObjectA, ExternalNestedNumericObjectB, ExternalNestedObjectA, ExternalNestedObjectB, ExternalNumericParam, ExternalSchemaA, ExternalSchemaB, ExternalSchemaC, ExternalSchemaExternalProp, ExternalSchemaExternalPropAlias, ExternalSchemaPathA, ExternalSchemaPathB, ExternalSchemaPropertyA, ExternalSchemaPropertyB, ExternalSchemaPropertyC, ExternalSchemaPropertyD, ExternalSharedModel, ExternalSharedModelWithUuid, ExternalUnionSchema, ExternalUuidBody, ExternalUuidHeader, ExternalUuidParam, GetExternalArrayData, GetExternalArrayResponse, GetExternalArrayResponses, GetExternalMixedData, GetExternalMixedResponse, GetExternalMixedResponses, GetExternalModelData, GetExternalModelError, GetExternalModelErrors, GetExternalModelResponse, GetExternalModelResponses, GetExternalNestedData, GetExternalNestedResponse, GetExternalNestedResponses, GetExternalPropertiesByIdData, GetExternalPropertiesByIdResponse, GetExternalPropertiesByIdResponses, GetExternalUnionData, GetExternalUnionResponse, GetExternalUnionResponses, GetExternalUuidData, GetExternalUuidResponse, GetExternalUuidResponses, Id, Name, PostExternalArrayData, PostExternalArrayResponse, PostExternalArrayResponses, PostExternalMixedData, PostExternalMixedResponse, PostExternalMixedResponses, PostExternalModelData, PostExternalModelError, PostExternalModelErrors, PostExternalModelResponse, PostExternalModelResponses, PostExternalNestedData, PostExternalNestedResponse, PostExternalNestedResponses, PostExternalUnionData, PostExternalUnionResponse, PostExternalUnionResponses, PutExternalUuidData, PutExternalUuidResponse, PutExternalUuidResponses } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/types.gen.ts index a6c2f4e474..0ff36a2b40 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/types.gen.ts @@ -4,6 +4,12 @@ export type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); }; +export type ExternalIdHeader = Id; + +export type ExternalUuidHeader = ExternalSharedModelWithUuid; + +export type ExternalDeepHeader = Deep; + export type _1 = string; export type ExternalSchemaA = ExternalSharedModel; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/index.ts index fb12847e21..5e40b58fbe 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { _1, ClientOptions, Deep, ExternalAllOfSchema, ExternalAnyOfSchema, ExternalArraySchema, ExternalDeepParam, ExternalDoubleNestedNumeric, ExternalDoubleNestedProp, ExternalIdParam, ExternalMixedBody, ExternalMixedProperties, ExternalModelBody, ExternalNested, ExternalNestedBody, ExternalNestedNumeric, ExternalNestedNumericObjectA, ExternalNestedNumericObjectB, ExternalNestedObjectA, ExternalNestedObjectB, ExternalNumericParam, ExternalSchemaA, ExternalSchemaB, ExternalSchemaC, ExternalSchemaExternalProp, ExternalSchemaExternalPropAlias, ExternalSchemaPathA, ExternalSchemaPathB, ExternalSchemaPropertyA, ExternalSchemaPropertyB, ExternalSchemaPropertyC, ExternalSchemaPropertyD, ExternalSharedModel, ExternalSharedModelWithUuid, ExternalUnionSchema, ExternalUuidBody, ExternalUuidParam, GetExternalArrayData, GetExternalArrayResponse, GetExternalArrayResponses, GetExternalMixedData, GetExternalMixedResponse, GetExternalMixedResponses, GetExternalModelData, GetExternalModelError, GetExternalModelErrors, GetExternalModelResponse, GetExternalModelResponses, GetExternalNestedData, GetExternalNestedResponse, GetExternalNestedResponses, GetExternalPropertiesByIdData, GetExternalPropertiesByIdResponse, GetExternalPropertiesByIdResponses, GetExternalUnionData, GetExternalUnionResponse, GetExternalUnionResponses, GetExternalUuidData, GetExternalUuidResponse, GetExternalUuidResponses, Id, Name, PostExternalArrayData, PostExternalArrayResponse, PostExternalArrayResponses, PostExternalMixedData, PostExternalMixedResponse, PostExternalMixedResponses, PostExternalModelData, PostExternalModelError, PostExternalModelErrors, PostExternalModelResponse, PostExternalModelResponses, PostExternalNestedData, PostExternalNestedResponse, PostExternalNestedResponses, PostExternalUnionData, PostExternalUnionResponse, PostExternalUnionResponses, PutExternalUuidData, PutExternalUuidResponse, PutExternalUuidResponses } from './types.gen'; +export type { _1, ClientOptions, Deep, ExternalAllOfSchema, ExternalAnyOfSchema, ExternalArraySchema, ExternalDeepHeader, ExternalDeepParam, ExternalDoubleNestedNumeric, ExternalDoubleNestedProp, ExternalIdHeader, ExternalIdParam, ExternalMixedBody, ExternalMixedProperties, ExternalModelBody, ExternalNested, ExternalNestedBody, ExternalNestedNumeric, ExternalNestedNumericObjectA, ExternalNestedNumericObjectB, ExternalNestedObjectA, ExternalNestedObjectB, ExternalNumericParam, ExternalSchemaA, ExternalSchemaB, ExternalSchemaC, ExternalSchemaExternalProp, ExternalSchemaExternalPropAlias, ExternalSchemaPathA, ExternalSchemaPathB, ExternalSchemaPropertyA, ExternalSchemaPropertyB, ExternalSchemaPropertyC, ExternalSchemaPropertyD, ExternalSharedModel, ExternalSharedModelWithUuid, ExternalUnionSchema, ExternalUuidBody, ExternalUuidHeader, ExternalUuidParam, GetExternalArrayData, GetExternalArrayResponse, GetExternalArrayResponses, GetExternalMixedData, GetExternalMixedResponse, GetExternalMixedResponses, GetExternalModelData, GetExternalModelError, GetExternalModelErrors, GetExternalModelResponse, GetExternalModelResponses, GetExternalNestedData, GetExternalNestedResponse, GetExternalNestedResponses, GetExternalPropertiesByIdData, GetExternalPropertiesByIdResponse, GetExternalPropertiesByIdResponses, GetExternalUnionData, GetExternalUnionResponse, GetExternalUnionResponses, GetExternalUuidData, GetExternalUuidResponse, GetExternalUuidResponses, Id, Name, PostExternalArrayData, PostExternalArrayResponse, PostExternalArrayResponses, PostExternalMixedData, PostExternalMixedResponse, PostExternalMixedResponses, PostExternalModelData, PostExternalModelError, PostExternalModelErrors, PostExternalModelResponse, PostExternalModelResponses, PostExternalNestedData, PostExternalNestedResponse, PostExternalNestedResponses, PostExternalUnionData, PostExternalUnionResponse, PostExternalUnionResponses, PutExternalUuidData, PutExternalUuidResponse, PutExternalUuidResponses } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/types.gen.ts index 86205456f8..066f39a86a 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/types.gen.ts @@ -4,6 +4,12 @@ export type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); }; +export type ExternalIdHeader = Id; + +export type ExternalUuidHeader = ExternalSharedModelWithUuid; + +export type ExternalDeepHeader = Deep; + export type _1 = string; /** diff --git a/packages/shared/src/ir/graph.ts b/packages/shared/src/ir/graph.ts index b2df820046..b31182d53a 100644 --- a/packages/shared/src/ir/graph.ts +++ b/packages/shared/src/ir/graph.ts @@ -23,7 +23,7 @@ export const matchIrPointerToGroup: MatchPointerToGroupFn = (poi operation: /^#\/paths\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/, parameter: /^#\/components\/parameters\/[^/]+$/, requestBody: /^#\/components\/requestBodies\/[^/]+$/, - schema: /^#\/components\/schemas\/[^/]+$/, + schema: /^#\/components\/(headers|schemas)\/[^/]+$/, server: /^#\/servers\/(\d+|[^/]+)$/, webhook: /^#\/webhooks\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/, }; diff --git a/packages/shared/src/ir/types.ts b/packages/shared/src/ir/types.ts index b4a6c150bb..45716d4e00 100644 --- a/packages/shared/src/ir/types.ts +++ b/packages/shared/src/ir/types.ts @@ -17,6 +17,7 @@ interface IRBodyObject { } interface IRComponentsObject { + headers?: Record; parameters?: Record; requestBodies?: Record; schemas?: Record; diff --git a/packages/shared/src/openApi/3.0.x/parser/index.ts b/packages/shared/src/openApi/3.0.x/parser/index.ts index d9d60fa4e2..feb4ebd390 100644 --- a/packages/shared/src/openApi/3.0.x/parser/index.ts +++ b/packages/shared/src/openApi/3.0.x/parser/index.ts @@ -16,7 +16,7 @@ import { filterSpec } from './filter'; import { parsePathOperation } from './operation'; import { parametersArrayToObject, parseParameter } from './parameter'; import { parseRequestBody } from './requestBody'; -import { parseSchema } from './schema'; +import { parseHeader, parseSchema } from './schema'; import { parseServers } from './server'; import { validateOpenApiSpec } from './validate'; @@ -62,6 +62,23 @@ export const parseV3_0_X = (context: Context) => { securitySchemesMap.set(name, securitySchemeObject); } + for (const name in context.spec.components.headers) { + const $ref = `#/components/headers/${name}`; + const headerOrReference = context.spec.components.headers[name]!; + const header = + '$ref' in headerOrReference + ? context.resolveRef(headerOrReference.$ref) + : headerOrReference; + + if (header.schema) { + parseHeader({ + $ref, + context, + schema: header.schema, + }); + } + } + for (const name in context.spec.components.parameters) { const $ref = `#/components/parameters/${name}`; const parameterOrReference = context.spec.components.parameters[name]!; diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index 9361776716..f7d156ff62 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -1347,3 +1347,30 @@ export const parseSchema = ({ }, }); }; + +export const parseHeader = ({ + $ref, + context, + schema, +}: { + $ref: string; + context: Context; + schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; +}) => { + if (!context.ir.components) { + context.ir.components = {}; + } + + if (!context.ir.components.headers) { + context.ir.components.headers = {}; + } + + context.ir.components.headers[refToName($ref)] = schemaToIrSchema({ + context, + schema, + state: { + $ref, + circularReferenceTracker: new Set(), + }, + }); +}; diff --git a/packages/shared/src/openApi/3.1.x/parser/index.ts b/packages/shared/src/openApi/3.1.x/parser/index.ts index 352dbc218a..c8b767181f 100644 --- a/packages/shared/src/openApi/3.1.x/parser/index.ts +++ b/packages/shared/src/openApi/3.1.x/parser/index.ts @@ -16,7 +16,7 @@ import { filterSpec } from './filter'; import { parsePathOperation } from './operation'; import { parametersArrayToObject, parseParameter } from './parameter'; import { parseRequestBody } from './requestBody'; -import { parseSchema } from './schema'; +import { parseHeader, parseSchema } from './schema'; import { parseServers } from './server'; import { validateOpenApiSpec } from './validate'; import { parseWebhooks } from './webhook'; @@ -63,6 +63,23 @@ export const parseV3_1_X = (context: Context) => { securitySchemesMap.set(name, securitySchemeObject); } + for (const name in context.spec.components.headers) { + const $ref = `#/components/headers/${name}`; + const headerOrReference = context.spec.components.headers[name]!; + const header = + '$ref' in headerOrReference + ? context.resolveRef(headerOrReference.$ref) + : headerOrReference; + + if (header.schema) { + parseHeader({ + $ref, + context, + schema: header.schema as OpenAPIV3_1.SchemaObject, + }); + } + } + for (const name in context.spec.components.parameters) { const $ref = `#/components/parameters/${name}`; const parameterOrReference = context.spec.components.parameters[name]!; diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index ec595d4ebe..f811c1c279 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -1455,3 +1455,30 @@ export const parseSchema = ({ }, }); }; + +export const parseHeader = ({ + $ref, + context, + schema, +}: { + $ref: string; + context: Context; + schema: OpenAPIV3_1.SchemaObject; +}) => { + if (!context.ir.components) { + context.ir.components = {}; + } + + if (!context.ir.components.headers) { + context.ir.components.headers = {}; + } + + context.ir.components.headers[refToName($ref)] = schemaToIrSchema({ + context, + schema, + state: { + $ref, + circularReferenceTracker: new Set(), + }, + }); +}; diff --git a/specs/3.0.x/components-headers.yaml b/specs/3.0.x/components-headers.yaml new file mode 100644 index 0000000000..46ec6a38bb --- /dev/null +++ b/specs/3.0.x/components-headers.yaml @@ -0,0 +1,27 @@ +openapi: '3.0.3' +info: + title: Test components/headers + version: 0.0.1 +paths: + /foo: + get: + operationId: getFoo + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Foo' +components: + headers: + X-Rate-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + schemas: + Foo: + type: object + properties: + rateLimit: + $ref: '#/components/headers/X-Rate-Limit' From b7b47e90b81260efae959ef6a42a1147d1942167 Mon Sep 17 00:00:00 2001 From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:18:47 +0000 Subject: [PATCH 3/3] fix: add `header` and `response` IR kinds, parse `components/responses`, add 3.1.x test - Add `header` and `response` as separate top-level IR kinds instead of expanding the `schema` regex pattern - Parse `components/responses` in both 3.0.x and 3.1.x parsers - Add `responses` to `IRComponentsObject` type - Update `WalkEvents` union and `forEach` switch for new event types - Subscribe `@hey-api/typescript` plugin to `header` and `response` events - Create `components-headers.yaml` spec for 3.1.x (with both headers and responses) - Update 3.0.x spec to follow naming conventions and include responses - Convert `parseSchema`/`parseHeader` to `function` syntax - Update graph unit tests for new patterns --- .../openapi-ts-tests/main/test/3.0.x.test.ts | 2 +- .../openapi-ts-tests/main/test/3.1.x.test.ts | 7 +++ .../3.0.x/components-headers/index.ts | 2 +- .../3.0.x/components-headers/types.gen.ts | 8 +++ .../__snapshots__/3.0.x/external/index.ts | 2 +- .../__snapshots__/3.0.x/external/types.gen.ts | 25 ++++++++ .../3.1.x/components-headers/index.ts | 3 + .../3.1.x/components-headers/types.gen.ts | 35 +++++++++++ .../__snapshots__/3.1.x/external/index.ts | 2 +- .../__snapshots__/3.1.x/external/types.gen.ts | 25 ++++++++ .../index.ts | 2 +- .../types.gen.ts | 11 +++- .../transforms-read-write-ignore/index.ts | 2 +- .../transforms-read-write-ignore/types.gen.ts | 11 +++- .../3.1.x/transforms-read-write/index.ts | 2 +- .../3.1.x/transforms-read-write/types.gen.ts | 11 +++- .../plugins/@hey-api/typescript/v1/plugin.ts | 28 +++++++++ .../shared/src/ir/__tests__/graph.test.ts | 6 ++ packages/shared/src/ir/graph.ts | 8 ++- packages/shared/src/ir/types.ts | 1 + .../shared/src/openApi/3.0.x/parser/index.ts | 17 +++++- .../shared/src/openApi/3.0.x/parser/schema.ts | 58 +++++++++++++++++-- .../shared/src/openApi/3.1.x/parser/index.ts | 17 +++++- .../shared/src/openApi/3.1.x/parser/schema.ts | 58 +++++++++++++++++-- .../src/plugins/shared/types/instance.ts | 12 ++++ .../src/plugins/shared/utils/instance.ts | 16 +++++ specs/3.0.x/components-headers.yaml | 18 +++++- specs/3.1.x/components-headers.yaml | 39 +++++++++++++ 28 files changed, 394 insertions(+), 34 deletions(-) create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/components-headers/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/components-headers/types.gen.ts create mode 100644 specs/3.1.x/components-headers.yaml diff --git a/packages/openapi-ts-tests/main/test/3.0.x.test.ts b/packages/openapi-ts-tests/main/test/3.0.x.test.ts index 15593d0fde..4cf8cbfef5 100644 --- a/packages/openapi-ts-tests/main/test/3.0.x.test.ts +++ b/packages/openapi-ts-tests/main/test/3.0.x.test.ts @@ -156,7 +156,7 @@ describe(`OpenAPI ${version}`, () => { input: 'components-headers.yaml', output: 'components-headers', }), - description: 'handles reusable header components', + description: 'handles reusable header and response components', }, { config: createConfig({ diff --git a/packages/openapi-ts-tests/main/test/3.1.x.test.ts b/packages/openapi-ts-tests/main/test/3.1.x.test.ts index 2ee3cacfe5..c79c8c89f5 100644 --- a/packages/openapi-ts-tests/main/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/main/test/3.1.x.test.ts @@ -171,6 +171,13 @@ describe(`OpenAPI ${version}`, () => { }), description: 'handles snake_case identifier casing', }, + { + config: createConfig({ + input: 'components-headers.yaml', + output: 'components-headers', + }), + description: 'handles reusable header and response components', + }, { config: createConfig({ input: 'components-request-bodies.json', diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/index.ts index 38b1794ee0..732b5bf93c 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { ClientOptions, Foo, GetFooData, GetFooResponse, GetFooResponses, XRateLimit } from './types.gen'; +export type { ClientOptions, Foo, GetFooData, GetFooResponse, GetFooResponses, NotFound, XRateLimit } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/types.gen.ts index 72f36b930c..2752589da9 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/components-headers/types.gen.ts @@ -6,6 +6,14 @@ export type ClientOptions = { export type XRateLimit = number; +/** + * Resource not found + */ +export type NotFound = { + code?: number; + message?: string; +}; + export type Foo = { rateLimit?: XRateLimit; }; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/index.ts index 5e40b58fbe..f6eec1b375 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { _1, ClientOptions, Deep, ExternalAllOfSchema, ExternalAnyOfSchema, ExternalArraySchema, ExternalDeepHeader, ExternalDeepParam, ExternalDoubleNestedNumeric, ExternalDoubleNestedProp, ExternalIdHeader, ExternalIdParam, ExternalMixedBody, ExternalMixedProperties, ExternalModelBody, ExternalNested, ExternalNestedBody, ExternalNestedNumeric, ExternalNestedNumericObjectA, ExternalNestedNumericObjectB, ExternalNestedObjectA, ExternalNestedObjectB, ExternalNumericParam, ExternalSchemaA, ExternalSchemaB, ExternalSchemaC, ExternalSchemaExternalProp, ExternalSchemaExternalPropAlias, ExternalSchemaPathA, ExternalSchemaPathB, ExternalSchemaPropertyA, ExternalSchemaPropertyB, ExternalSchemaPropertyC, ExternalSchemaPropertyD, ExternalSharedModel, ExternalSharedModelWithUuid, ExternalUnionSchema, ExternalUuidBody, ExternalUuidHeader, ExternalUuidParam, GetExternalArrayData, GetExternalArrayResponse, GetExternalArrayResponses, GetExternalMixedData, GetExternalMixedResponse, GetExternalMixedResponses, GetExternalModelData, GetExternalModelError, GetExternalModelErrors, GetExternalModelResponse, GetExternalModelResponses, GetExternalNestedData, GetExternalNestedResponse, GetExternalNestedResponses, GetExternalPropertiesByIdData, GetExternalPropertiesByIdResponse, GetExternalPropertiesByIdResponses, GetExternalUnionData, GetExternalUnionResponse, GetExternalUnionResponses, GetExternalUuidData, GetExternalUuidResponse, GetExternalUuidResponses, Id, Name, PostExternalArrayData, PostExternalArrayResponse, PostExternalArrayResponses, PostExternalMixedData, PostExternalMixedResponse, PostExternalMixedResponses, PostExternalModelData, PostExternalModelError, PostExternalModelErrors, PostExternalModelResponse, PostExternalModelResponses, PostExternalNestedData, PostExternalNestedResponse, PostExternalNestedResponses, PostExternalUnionData, PostExternalUnionResponse, PostExternalUnionResponses, PutExternalUuidData, PutExternalUuidResponse, PutExternalUuidResponses } from './types.gen'; +export type { _1, ClientOptions, Deep, ExternalAllOfSchema, ExternalAnyOfSchema, ExternalArrayResponse, ExternalArraySchema, ExternalDeepHeader, ExternalDeepParam, ExternalDoubleNestedNumeric, ExternalDoubleNestedProp, ExternalIdHeader, ExternalIdParam, ExternalMixedBody, ExternalMixedProperties, ExternalModelBody, ExternalModelResponse, ExternalNested, ExternalNestedBody, ExternalNestedNumeric, ExternalNestedNumericObjectA, ExternalNestedNumericObjectB, ExternalNestedObjectA, ExternalNestedObjectB, ExternalNestedResponse, ExternalNumericParam, ExternalSchemaA, ExternalSchemaB, ExternalSchemaC, ExternalSchemaExternalProp, ExternalSchemaExternalPropAlias, ExternalSchemaPathA, ExternalSchemaPathB, ExternalSchemaPropertyA, ExternalSchemaPropertyB, ExternalSchemaPropertyC, ExternalSchemaPropertyD, ExternalSharedModel, ExternalSharedModelWithUuid, ExternalUnionResponse, ExternalUnionSchema, ExternalUuidBody, ExternalUuidHeader, ExternalUuidParam, ExternalUuidResponse, GetExternalArrayData, GetExternalArrayResponse, GetExternalArrayResponses, GetExternalMixedData, GetExternalMixedResponse, GetExternalMixedResponses, GetExternalModelData, GetExternalModelError, GetExternalModelErrors, GetExternalModelResponse, GetExternalModelResponses, GetExternalNestedData, GetExternalNestedResponse, GetExternalNestedResponses, GetExternalPropertiesByIdData, GetExternalPropertiesByIdResponse, GetExternalPropertiesByIdResponses, GetExternalUnionData, GetExternalUnionResponse, GetExternalUnionResponses, GetExternalUuidData, GetExternalUuidResponse, GetExternalUuidResponses, Id, Name, PostExternalArrayData, PostExternalArrayResponse, PostExternalArrayResponses, PostExternalMixedData, PostExternalMixedResponse, PostExternalMixedResponses, PostExternalModelData, PostExternalModelError, PostExternalModelErrors, PostExternalModelResponse, PostExternalModelResponses, PostExternalNestedData, PostExternalNestedResponse, PostExternalNestedResponses, PostExternalUnionData, PostExternalUnionResponse, PostExternalUnionResponses, PutExternalUuidData, PutExternalUuidResponse, PutExternalUuidResponses } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/types.gen.ts index 0ff36a2b40..f392137c7e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/external/types.gen.ts @@ -10,6 +10,31 @@ export type ExternalUuidHeader = ExternalSharedModelWithUuid; export type ExternalDeepHeader = Deep; +/** + * Response using external model + */ +export type ExternalModelResponse = ExternalSharedModel; + +/** + * Response using external UUID + */ +export type ExternalUuidResponse = ExternalSharedModelWithUuid; + +/** + * Response using external nested object + */ +export type ExternalNestedResponse = ExternalNested; + +/** + * Response with array of external models + */ +export type ExternalArrayResponse = Array; + +/** + * Response with union of external types + */ +export type ExternalUnionResponse = ExternalSharedModel | ExternalSharedModelWithUuid | ExternalNested; + export type _1 = string; export type ExternalSchemaA = ExternalSharedModel; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/components-headers/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/components-headers/index.ts new file mode 100644 index 0000000000..732b5bf93c --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/components-headers/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { ClientOptions, Foo, GetFooData, GetFooResponse, GetFooResponses, NotFound, XRateLimit } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/components-headers/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/components-headers/types.gen.ts new file mode 100644 index 0000000000..2752589da9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/components-headers/types.gen.ts @@ -0,0 +1,35 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type XRateLimit = number; + +/** + * Resource not found + */ +export type NotFound = { + code?: number; + message?: string; +}; + +export type Foo = { + rateLimit?: XRateLimit; +}; + +export type GetFooData = { + body?: never; + path?: never; + query?: never; + url: '/foo'; +}; + +export type GetFooResponses = { + /** + * OK + */ + 200: Foo; +}; + +export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/index.ts index 5e40b58fbe..f6eec1b375 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { _1, ClientOptions, Deep, ExternalAllOfSchema, ExternalAnyOfSchema, ExternalArraySchema, ExternalDeepHeader, ExternalDeepParam, ExternalDoubleNestedNumeric, ExternalDoubleNestedProp, ExternalIdHeader, ExternalIdParam, ExternalMixedBody, ExternalMixedProperties, ExternalModelBody, ExternalNested, ExternalNestedBody, ExternalNestedNumeric, ExternalNestedNumericObjectA, ExternalNestedNumericObjectB, ExternalNestedObjectA, ExternalNestedObjectB, ExternalNumericParam, ExternalSchemaA, ExternalSchemaB, ExternalSchemaC, ExternalSchemaExternalProp, ExternalSchemaExternalPropAlias, ExternalSchemaPathA, ExternalSchemaPathB, ExternalSchemaPropertyA, ExternalSchemaPropertyB, ExternalSchemaPropertyC, ExternalSchemaPropertyD, ExternalSharedModel, ExternalSharedModelWithUuid, ExternalUnionSchema, ExternalUuidBody, ExternalUuidHeader, ExternalUuidParam, GetExternalArrayData, GetExternalArrayResponse, GetExternalArrayResponses, GetExternalMixedData, GetExternalMixedResponse, GetExternalMixedResponses, GetExternalModelData, GetExternalModelError, GetExternalModelErrors, GetExternalModelResponse, GetExternalModelResponses, GetExternalNestedData, GetExternalNestedResponse, GetExternalNestedResponses, GetExternalPropertiesByIdData, GetExternalPropertiesByIdResponse, GetExternalPropertiesByIdResponses, GetExternalUnionData, GetExternalUnionResponse, GetExternalUnionResponses, GetExternalUuidData, GetExternalUuidResponse, GetExternalUuidResponses, Id, Name, PostExternalArrayData, PostExternalArrayResponse, PostExternalArrayResponses, PostExternalMixedData, PostExternalMixedResponse, PostExternalMixedResponses, PostExternalModelData, PostExternalModelError, PostExternalModelErrors, PostExternalModelResponse, PostExternalModelResponses, PostExternalNestedData, PostExternalNestedResponse, PostExternalNestedResponses, PostExternalUnionData, PostExternalUnionResponse, PostExternalUnionResponses, PutExternalUuidData, PutExternalUuidResponse, PutExternalUuidResponses } from './types.gen'; +export type { _1, ClientOptions, Deep, ExternalAllOfSchema, ExternalAnyOfSchema, ExternalArrayResponse, ExternalArraySchema, ExternalDeepHeader, ExternalDeepParam, ExternalDoubleNestedNumeric, ExternalDoubleNestedProp, ExternalIdHeader, ExternalIdParam, ExternalMixedBody, ExternalMixedProperties, ExternalModelBody, ExternalModelResponse, ExternalNested, ExternalNestedBody, ExternalNestedNumeric, ExternalNestedNumericObjectA, ExternalNestedNumericObjectB, ExternalNestedObjectA, ExternalNestedObjectB, ExternalNestedResponse, ExternalNumericParam, ExternalSchemaA, ExternalSchemaB, ExternalSchemaC, ExternalSchemaExternalProp, ExternalSchemaExternalPropAlias, ExternalSchemaPathA, ExternalSchemaPathB, ExternalSchemaPropertyA, ExternalSchemaPropertyB, ExternalSchemaPropertyC, ExternalSchemaPropertyD, ExternalSharedModel, ExternalSharedModelWithUuid, ExternalUnionResponse, ExternalUnionSchema, ExternalUuidBody, ExternalUuidHeader, ExternalUuidParam, ExternalUuidResponse, GetExternalArrayData, GetExternalArrayResponse, GetExternalArrayResponses, GetExternalMixedData, GetExternalMixedResponse, GetExternalMixedResponses, GetExternalModelData, GetExternalModelError, GetExternalModelErrors, GetExternalModelResponse, GetExternalModelResponses, GetExternalNestedData, GetExternalNestedResponse, GetExternalNestedResponses, GetExternalPropertiesByIdData, GetExternalPropertiesByIdResponse, GetExternalPropertiesByIdResponses, GetExternalUnionData, GetExternalUnionResponse, GetExternalUnionResponses, GetExternalUuidData, GetExternalUuidResponse, GetExternalUuidResponses, Id, Name, PostExternalArrayData, PostExternalArrayResponse, PostExternalArrayResponses, PostExternalMixedData, PostExternalMixedResponse, PostExternalMixedResponses, PostExternalModelData, PostExternalModelError, PostExternalModelErrors, PostExternalModelResponse, PostExternalModelResponses, PostExternalNestedData, PostExternalNestedResponse, PostExternalNestedResponses, PostExternalUnionData, PostExternalUnionResponse, PostExternalUnionResponses, PutExternalUuidData, PutExternalUuidResponse, PutExternalUuidResponses } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/types.gen.ts index 066f39a86a..dd610a6df8 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/external/types.gen.ts @@ -10,6 +10,31 @@ export type ExternalUuidHeader = ExternalSharedModelWithUuid; export type ExternalDeepHeader = Deep; +/** + * Response using external model + */ +export type ExternalModelResponse = ExternalSharedModel; + +/** + * Response using external UUID + */ +export type ExternalUuidResponse = ExternalSharedModelWithUuid; + +/** + * Response using external nested object + */ +export type ExternalNestedResponse = ExternalNested; + +/** + * Response with array of external models + */ +export type ExternalArrayResponse = Array; + +/** + * Response with union of external types + */ +export type ExternalUnionResponse = ExternalSharedModel | ExternalSharedModelWithUuid | ExternalNested; + export type _1 = string; /** diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-custom-name/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-custom-name/index.ts index b9f646d009..2b9a251c76 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-custom-name/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-custom-name/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { Baz, ClientOptions, Foo, Foo2, PostFooReadData, PostFooReadResponse, PostFooReadResponses, PostFooReadWriteData, PostFooReadWriteResponse, PostFooReadWriteResponses, PostFooWriteData, PostFooWriteResponse, PostFooWriteResponses, PutFooWriteData, PutFooWriteResponse, PutFooWriteResponses, QuxAllRead, QuxAllWrite, ReadableBarRead, ReadableBarWrite, ReadableCorge, ReadableFooRead, ReadableFooReadWrite, ReadableFooReadWriteRef, ReadableFooReadWriteRef2, ReadableFooWrite, ReadableQuux, WritableBarRead, WritableBarWrite, WritableCorge, WritableFooRead, WritableFooReadWrite, WritableFooReadWriteRef, WritableFooReadWriteRef2, WritableFooWrite, WritableQuux } from './types.gen'; +export type { Baz, ClientOptions, Foo, Foo2, Foo3, PostFooReadData, PostFooReadResponse, PostFooReadResponses, PostFooReadWriteData, PostFooReadWriteResponse, PostFooReadWriteResponses, PostFooWriteData, PostFooWriteResponse, PostFooWriteResponses, PutFooWriteData, PutFooWriteResponse, PutFooWriteResponses, QuxAllRead, QuxAllWrite, ReadableBarRead, ReadableBarWrite, ReadableCorge, ReadableFooRead, ReadableFooReadWrite, ReadableFooReadWriteRef, ReadableFooReadWriteRef2, ReadableFooWrite, ReadableQuux, WritableBarRead, WritableBarWrite, WritableCorge, WritableFooRead, WritableFooReadWrite, WritableFooReadWriteRef, WritableFooReadWriteRef2, WritableFooWrite, WritableQuux } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-custom-name/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-custom-name/types.gen.ts index 919e7c5dec..8686c9fca6 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-custom-name/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-custom-name/types.gen.ts @@ -4,6 +4,11 @@ export type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); }; +/** + * OK + */ +export type Foo = ReadableFooWrite; + export type Baz = { baz?: string; }; @@ -84,12 +89,12 @@ export type WritableFooReadWriteRef2 = WritableFooReadWrite; /** * Query parameter */ -export type Foo = string; +export type Foo2 = string; /** * PUT /foo-write payload */ -export type Foo2 = { +export type Foo3 = { foo?: WritableBarRead; }; @@ -145,7 +150,7 @@ export type PutFooWriteData = { /** * PUT /foo-write payload */ - body: Foo2; + body: Foo3; path?: never; query?: { /** diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-ignore/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-ignore/index.ts index e1e97a46fc..fac77d22b1 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-ignore/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-ignore/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { BarRead, BarWrite, Baz, ClientOptions, Corge, Foo, Foo2, FooRead, FooReadWrite, FooReadWriteRef, FooReadWriteRef2, FooWrite, PostFooReadData, PostFooReadResponse, PostFooReadResponses, PostFooReadWriteData, PostFooReadWriteResponse, PostFooReadWriteResponses, PostFooWriteData, PostFooWriteResponse, PostFooWriteResponses, PutFooWriteData, PutFooWriteResponse, PutFooWriteResponses, Quux, QuxAllRead, QuxAllWrite } from './types.gen'; +export type { BarRead, BarWrite, Baz, ClientOptions, Corge, Foo, Foo2, Foo3, FooRead, FooReadWrite, FooReadWriteRef, FooReadWriteRef2, FooWrite, PostFooReadData, PostFooReadResponse, PostFooReadResponses, PostFooReadWriteData, PostFooReadWriteResponse, PostFooReadWriteResponses, PostFooWriteData, PostFooWriteResponse, PostFooWriteResponses, PutFooWriteData, PutFooWriteResponse, PutFooWriteResponses, Quux, QuxAllRead, QuxAllWrite } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-ignore/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-ignore/types.gen.ts index 2660fdee60..461ea500f1 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-ignore/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/transforms-read-write-ignore/types.gen.ts @@ -4,6 +4,11 @@ export type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); }; +/** + * OK + */ +export type Foo = FooWrite; + export type FooReadWrite = BarRead & { foo?: string; }; @@ -60,12 +65,12 @@ export type FooReadWriteRef2 = FooReadWrite; /** * Query parameter */ -export type Foo = string; +export type Foo2 = string; /** * PUT /foo-write payload */ -export type Foo2 = { +export type Foo3 = { foo?: BarRead; }; @@ -121,7 +126,7 @@ export type PutFooWriteData = { /** * PUT /foo-write payload */ - body: Foo2; + body: Foo3; path?: never; query?: { /** diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write/index.ts index 60381d055e..d781bad408 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { BarRead, BarReadWritable, BarWrite, BarWriteWritable, Baz, ClientOptions, Corge, CorgeWritable, Foo, Foo2, FooRead, FooReadWritable, FooReadWrite, FooReadWriteRef, FooReadWriteRef2, FooReadWriteRef2Writable, FooReadWriteRefWritable, FooReadWriteWritable, FooWrite, FooWriteWritable, PostFooReadData, PostFooReadResponse, PostFooReadResponses, PostFooReadWriteData, PostFooReadWriteResponse, PostFooReadWriteResponses, PostFooWriteData, PostFooWriteResponse, PostFooWriteResponses, PutFooWriteData, PutFooWriteResponse, PutFooWriteResponses, Quux, QuuxWritable, QuxAllRead, QuxAllWrite } from './types.gen'; +export type { BarRead, BarReadWritable, BarWrite, BarWriteWritable, Baz, ClientOptions, Corge, CorgeWritable, Foo, Foo2, Foo3, FooRead, FooReadWritable, FooReadWrite, FooReadWriteRef, FooReadWriteRef2, FooReadWriteRef2Writable, FooReadWriteRefWritable, FooReadWriteWritable, FooWrite, FooWriteWritable, PostFooReadData, PostFooReadResponse, PostFooReadResponses, PostFooReadWriteData, PostFooReadWriteResponse, PostFooReadWriteResponses, PostFooWriteData, PostFooWriteResponse, PostFooWriteResponses, PutFooWriteData, PutFooWriteResponse, PutFooWriteResponses, Quux, QuuxWritable, QuxAllRead, QuxAllWrite } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write/types.gen.ts index 039f4a8efb..aab64fa868 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write/types.gen.ts @@ -4,6 +4,11 @@ export type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); }; +/** + * OK + */ +export type Foo = FooWrite; + export type FooReadWrite = BarRead; export type FooRead = BarRead & { @@ -84,12 +89,12 @@ export type FooReadWriteRef2Writable = FooReadWriteWritable; /** * Query parameter */ -export type Foo = string; +export type Foo2 = string; /** * PUT /foo-write payload */ -export type Foo2 = { +export type Foo3 = { foo?: BarReadWritable; }; @@ -145,7 +150,7 @@ export type PutFooWriteData = { /** * PUT /foo-write payload */ - body: Foo2; + body: Foo3; path?: never; query?: { /** diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts index 9e40739c9a..2fe32d2200 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts @@ -19,14 +19,29 @@ export const handlerV1: HeyApiTypeScriptPlugin['Handler'] = ({ plugin }) => { const processor = createProcessor(plugin); plugin.forEach( + 'header', 'operation', 'parameter', 'requestBody', + 'response', 'schema', 'server', 'webhook', (event) => { switch (event.type) { + case 'header': + processor.process({ + meta: { + resource: 'definition', + resourceId: pathToJsonPointer(event._path), + }, + naming: plugin.config.definitions, + path: event._path, + plugin, + schema: event.schema, + tags: event.tags, + }); + break; case 'operation': operationToType({ operation: event.operation, @@ -61,6 +76,19 @@ export const handlerV1: HeyApiTypeScriptPlugin['Handler'] = ({ plugin }) => { tags: event.tags, }); break; + case 'response': + processor.process({ + meta: { + resource: 'definition', + resourceId: pathToJsonPointer(event._path), + }, + naming: plugin.config.definitions, + path: event._path, + plugin, + schema: event.response.schema, + tags: event.tags, + }); + break; case 'schema': processor.process({ meta: { diff --git a/packages/shared/src/ir/__tests__/graph.test.ts b/packages/shared/src/ir/__tests__/graph.test.ts index 5668928396..0155676cab 100644 --- a/packages/shared/src/ir/__tests__/graph.test.ts +++ b/packages/shared/src/ir/__tests__/graph.test.ts @@ -5,6 +5,9 @@ describe('matchIrPointerToGroup', () => { const cases: Array< [string, IrTopLevelKind | undefined, { kind?: IrTopLevelKind; matched: boolean }] > = [ + ['#/components/headers/X-Rate-Limit', undefined, { kind: 'header', matched: true }], + ['#/components/headers/X-Rate-Limit', 'header', { kind: 'header', matched: true }], + ['#/components/headers/X-Rate-Limit', 'schema', { matched: false }], ['#/components/schemas/Foo', undefined, { kind: 'schema', matched: true }], ['#/components/schemas/Foo', 'schema', { kind: 'schema', matched: true }], ['#/components/schemas/Foo', 'parameter', { matched: false }], @@ -14,6 +17,9 @@ describe('matchIrPointerToGroup', () => { ['#/components/requestBodies/Baz', undefined, { kind: 'requestBody', matched: true }], ['#/components/requestBodies/Baz', 'requestBody', { kind: 'requestBody', matched: true }], ['#/components/requestBodies/Baz', 'schema', { matched: false }], + ['#/components/responses/NotFound', undefined, { kind: 'response', matched: true }], + ['#/components/responses/NotFound', 'response', { kind: 'response', matched: true }], + ['#/components/responses/NotFound', 'schema', { matched: false }], ['#/servers/0', undefined, { kind: 'server', matched: true }], ['#/servers/foo', undefined, { kind: 'server', matched: true }], ['#/paths/~1users/get', undefined, { kind: 'operation', matched: true }], diff --git a/packages/shared/src/ir/graph.ts b/packages/shared/src/ir/graph.ts index b31182d53a..b96cb71ae4 100644 --- a/packages/shared/src/ir/graph.ts +++ b/packages/shared/src/ir/graph.ts @@ -1,9 +1,11 @@ import type { GetPointerPriorityFn, MatchPointerToGroupFn } from '../graph'; export const irTopLevelKinds = [ + 'header', 'operation', 'parameter', 'requestBody', + 'response', 'schema', 'server', 'webhook', @@ -20,10 +22,12 @@ export type IrTopLevelKind = (typeof irTopLevelKinds)[number]; */ export const matchIrPointerToGroup: MatchPointerToGroupFn = (pointer, kind) => { const patterns: Record = { + header: /^#\/components\/headers\/[^/]+$/, operation: /^#\/paths\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/, parameter: /^#\/components\/parameters\/[^/]+$/, requestBody: /^#\/components\/requestBodies\/[^/]+$/, - schema: /^#\/components\/(headers|schemas)\/[^/]+$/, + response: /^#\/components\/responses\/[^/]+$/, + schema: /^#\/components\/schemas\/[^/]+$/, server: /^#\/servers\/(\d+|[^/]+)$/, webhook: /^#\/webhooks\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/, }; @@ -42,6 +46,8 @@ export const matchIrPointerToGroup: MatchPointerToGroupFn = (poi // default grouping preference (earlier groups emitted first when safe) export const preferGroups = [ 'server', + 'header', + 'response', 'schema', 'parameter', 'requestBody', diff --git a/packages/shared/src/ir/types.ts b/packages/shared/src/ir/types.ts index 45716d4e00..7bc5471387 100644 --- a/packages/shared/src/ir/types.ts +++ b/packages/shared/src/ir/types.ts @@ -20,6 +20,7 @@ interface IRComponentsObject { headers?: Record; parameters?: Record; requestBodies?: Record; + responses?: Record; schemas?: Record; } diff --git a/packages/shared/src/openApi/3.0.x/parser/index.ts b/packages/shared/src/openApi/3.0.x/parser/index.ts index feb4ebd390..e017450e2d 100644 --- a/packages/shared/src/openApi/3.0.x/parser/index.ts +++ b/packages/shared/src/openApi/3.0.x/parser/index.ts @@ -16,7 +16,7 @@ import { filterSpec } from './filter'; import { parsePathOperation } from './operation'; import { parametersArrayToObject, parseParameter } from './parameter'; import { parseRequestBody } from './requestBody'; -import { parseHeader, parseSchema } from './schema'; +import { parseHeader, parseResponse, parseSchema } from './schema'; import { parseServers } from './server'; import { validateOpenApiSpec } from './validate'; @@ -79,6 +79,21 @@ export const parseV3_0_X = (context: Context) => { } } + for (const name in context.spec.components.responses) { + const $ref = `#/components/responses/${name}`; + const responseOrReference = context.spec.components.responses[name]!; + const response = + '$ref' in responseOrReference + ? context.resolveRef(responseOrReference.$ref) + : responseOrReference; + + parseResponse({ + $ref, + context, + response, + }); + } + for (const name in context.spec.components.parameters) { const $ref = `#/components/parameters/${name}`; const parameterOrReference = context.spec.components.parameters[name]!; diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index f7d156ff62..df4d1a3f48 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -14,6 +14,7 @@ import { discriminatorValues, } from '../../../openApi/shared/utils/discriminator'; import { isTopLevelComponent, refToName } from '../../../utils/ref'; +import { contentToSchema, mediaTypeObjects } from './mediaType'; export const getSchemaType = ({ schema, @@ -1321,7 +1322,7 @@ export const schemaToIrSchema = ({ return parseUnknown({ context, schema }); }; -export const parseSchema = ({ +export function parseSchema({ $ref, context, schema, @@ -1329,7 +1330,7 @@ export const parseSchema = ({ $ref: string; context: Context; schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; -}) => { +}) { if (!context.ir.components) { context.ir.components = {}; } @@ -1346,9 +1347,9 @@ export const parseSchema = ({ circularReferenceTracker: new Set(), }, }); -}; +} -export const parseHeader = ({ +export function parseHeader({ $ref, context, schema, @@ -1356,7 +1357,7 @@ export const parseHeader = ({ $ref: string; context: Context; schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; -}) => { +}) { if (!context.ir.components) { context.ir.components = {}; } @@ -1373,4 +1374,49 @@ export const parseHeader = ({ circularReferenceTracker: new Set(), }, }); -}; +} + +export function parseResponse({ + $ref, + context, + response, +}: { + $ref: string; + context: Context; + response: OpenAPIV3.ResponseObject; +}) { + if (!context.ir.components) { + context.ir.components = {}; + } + + if (!context.ir.components.responses) { + context.ir.components.responses = {}; + } + + const contents = mediaTypeObjects({ content: response.content }); + const content = contents.find((content) => content.type === 'json') || contents[0]; + + if (content) { + context.ir.components.responses[refToName($ref)] = { + mediaType: content.mediaType, + schema: schemaToIrSchema({ + context, + schema: { + description: response.description, + ...contentToSchema({ content }), + }, + state: { + $ref, + circularReferenceTracker: new Set(), + }, + }), + }; + } else { + context.ir.components.responses[refToName($ref)] = { + schema: { + description: response.description, + type: 'unknown', + }, + }; + } +} diff --git a/packages/shared/src/openApi/3.1.x/parser/index.ts b/packages/shared/src/openApi/3.1.x/parser/index.ts index c8b767181f..bd4ad05b0a 100644 --- a/packages/shared/src/openApi/3.1.x/parser/index.ts +++ b/packages/shared/src/openApi/3.1.x/parser/index.ts @@ -16,7 +16,7 @@ import { filterSpec } from './filter'; import { parsePathOperation } from './operation'; import { parametersArrayToObject, parseParameter } from './parameter'; import { parseRequestBody } from './requestBody'; -import { parseHeader, parseSchema } from './schema'; +import { parseHeader, parseResponse, parseSchema } from './schema'; import { parseServers } from './server'; import { validateOpenApiSpec } from './validate'; import { parseWebhooks } from './webhook'; @@ -80,6 +80,21 @@ export const parseV3_1_X = (context: Context) => { } } + for (const name in context.spec.components.responses) { + const $ref = `#/components/responses/${name}`; + const responseOrReference = context.spec.components.responses[name]!; + const response = + '$ref' in responseOrReference + ? context.resolveRef(responseOrReference.$ref) + : responseOrReference; + + parseResponse({ + $ref, + context, + response, + }); + } + for (const name in context.spec.components.parameters) { const $ref = `#/components/parameters/${name}`; const parameterOrReference = context.spec.components.parameters[name]!; diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index f811c1c279..ca4d7ac7bc 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -15,6 +15,7 @@ import { discriminatorValues, } from '../../../openApi/shared/utils/discriminator'; import { isTopLevelComponent, refToName } from '../../../utils/ref'; +import { contentToSchema, mediaTypeObjects } from './mediaType'; export const getSchemaTypes = ({ schema, @@ -1429,7 +1430,7 @@ export const schemaToIrSchema = ({ return parseUnknown({ context, schema }); }; -export const parseSchema = ({ +export function parseSchema({ $ref, context, schema, @@ -1437,7 +1438,7 @@ export const parseSchema = ({ $ref: string; context: Context; schema: OpenAPIV3_1.SchemaObject; -}) => { +}) { if (!context.ir.components) { context.ir.components = {}; } @@ -1454,9 +1455,9 @@ export const parseSchema = ({ circularReferenceTracker: new Set(), }, }); -}; +} -export const parseHeader = ({ +export function parseHeader({ $ref, context, schema, @@ -1464,7 +1465,7 @@ export const parseHeader = ({ $ref: string; context: Context; schema: OpenAPIV3_1.SchemaObject; -}) => { +}) { if (!context.ir.components) { context.ir.components = {}; } @@ -1481,4 +1482,49 @@ export const parseHeader = ({ circularReferenceTracker: new Set(), }, }); -}; +} + +export function parseResponse({ + $ref, + context, + response, +}: { + $ref: string; + context: Context; + response: OpenAPIV3_1.ResponseObject; +}) { + if (!context.ir.components) { + context.ir.components = {}; + } + + if (!context.ir.components.responses) { + context.ir.components.responses = {}; + } + + const contents = mediaTypeObjects({ content: response.content }); + const content = contents.find((content) => content.type === 'json') || contents[0]; + + if (content) { + context.ir.components.responses[refToName($ref)] = { + mediaType: content.mediaType, + schema: schemaToIrSchema({ + context, + schema: { + description: response.description, + ...contentToSchema({ content }), + } as OpenAPIV3_1.SchemaObject, + state: { + $ref, + circularReferenceTracker: new Set(), + }, + }), + }; + } else { + context.ir.components.responses[refToName($ref)] = { + schema: { + description: response.description, + type: 'unknown', + }, + }; + } +} diff --git a/packages/shared/src/plugins/shared/types/instance.ts b/packages/shared/src/plugins/shared/types/instance.ts index 8cfc700637..3bba0da3e7 100644 --- a/packages/shared/src/plugins/shared/types/instance.ts +++ b/packages/shared/src/plugins/shared/types/instance.ts @@ -18,6 +18,12 @@ export type BaseEvent = { type WalkEvents = BaseEvent & ( + | { + /** Name of the header (e.g., "X-Rate-Limit" for a header defined as "#/components/headers/X-Rate-Limit"). */ + name: string; + schema: IR.SchemaObject; + type: Extract; + } | { method: keyof IR.PathItemObject; operation: IR.OperationObject; @@ -36,6 +42,12 @@ type WalkEvents = BaseEvent & requestBody: IR.RequestBodyObject; type: Extract; } + | { + /** Name of the response (e.g., "NotFound" for a response defined as "#/components/responses/NotFound"). */ + name: string; + response: IR.ResponseObject; + type: Extract; + } | { /** Name of the schema (e.g., "User" for a schema defined as "#/components/schemas/User"). */ name: string; diff --git a/packages/shared/src/plugins/shared/utils/instance.ts b/packages/shared/src/plugins/shared/utils/instance.ts index d662bf25cb..f6a44b585c 100644 --- a/packages/shared/src/plugins/shared/utils/instance.ts +++ b/packages/shared/src/plugins/shared/utils/instance.ts @@ -204,6 +204,14 @@ export class PluginInstance { tags: nodeInfo.tags ? Array.from(nodeInfo.tags) : undefined, }; switch (result.kind) { + case 'header': + event = { + ...baseEvent, + name: nodeInfo.key as string, + schema: nodeInfo.node as IR.SchemaObject, + type: result.kind, + } satisfies WalkEvent<'header'>; + break; case 'operation': event = { ...baseEvent, @@ -229,6 +237,14 @@ export class PluginInstance { type: result.kind, } satisfies WalkEvent<'requestBody'>; break; + case 'response': + event = { + ...baseEvent, + name: nodeInfo.key as string, + response: nodeInfo.node as IR.ResponseObject, + type: result.kind, + } satisfies WalkEvent<'response'>; + break; case 'schema': event = { ...baseEvent, diff --git a/specs/3.0.x/components-headers.yaml b/specs/3.0.x/components-headers.yaml index 46ec6a38bb..8edcdfce9a 100644 --- a/specs/3.0.x/components-headers.yaml +++ b/specs/3.0.x/components-headers.yaml @@ -1,7 +1,7 @@ -openapi: '3.0.3' +openapi: 3.0.4 info: - title: Test components/headers - version: 0.0.1 + title: OpenAPI 3.0.4 components headers and responses example + version: 1 paths: /foo: get: @@ -19,6 +19,18 @@ components: description: The number of allowed requests in the current period schema: type: integer + responses: + NotFound: + description: Resource not found + content: + application/json: + schema: + type: object + properties: + code: + type: integer + message: + type: string schemas: Foo: type: object diff --git a/specs/3.1.x/components-headers.yaml b/specs/3.1.x/components-headers.yaml new file mode 100644 index 0000000000..a8e353d52f --- /dev/null +++ b/specs/3.1.x/components-headers.yaml @@ -0,0 +1,39 @@ +openapi: 3.1.1 +info: + title: OpenAPI 3.1.1 components headers and responses example + version: 1 +paths: + /foo: + get: + operationId: getFoo + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Foo' +components: + headers: + X-Rate-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + responses: + NotFound: + description: Resource not found + content: + application/json: + schema: + type: object + properties: + code: + type: integer + message: + type: string + schemas: + Foo: + type: object + properties: + rateLimit: + $ref: '#/components/headers/X-Rate-Limit'