Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/openapi-ts-tests/main/test/3.0.x.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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];
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/ir/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const matchIrPointerToGroup: MatchPointerToGroupFn<IrTopLevelKind> = (poi
operation: /^#\/paths\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/,
parameter: /^#\/components\/parameters\/[^/]+$/,
requestBody: /^#\/components\/requestBodies\/[^/]+$/,
schema: /^#\/components\/schemas\/[^/]+$/,
schema: /^#\/components\/(headers|schemas)\/[^/]+$/,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because pathToName's ROOT_CONTEXT for components skips 2 segments and collects 1, both #/components/headers/Foo and #/components/schemas/Foo derive the base name "Foo". The planner's assignSymbolName will silently rename one (e.g., Foo2), which is non-obvious for users.

This is an edge case — most real-world specs won't have colliding header and schema names — but it's worth calling out. Consider whether the ROOT_CONTEXT should be adjusted to include the component category in the name for headers (e.g., prefix with the header name), or at minimum add a test case to graph.test.ts that asserts #/components/headers/Foo matches 'schema' so the behaviour is documented.

server: /^#\/servers\/(\d+|[^/]+)$/,
webhook: /^#\/webhooks\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/,
};
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/ir/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface IRBodyObject {
}

interface IRComponentsObject {
headers?: Record<string, IRSchemaObject>;
parameters?: Record<string, IRParameterObject>;
requestBodies?: Record<string, IRRequestBodyObject>;
schemas?: Record<string, IRSchemaObject>;
Expand Down
19 changes: 18 additions & 1 deletion packages/shared/src/openApi/3.0.x/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -62,6 +62,23 @@ export const parseV3_0_X = (context: Context<OpenAPIV3.Document>) => {
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<OpenAPIV3.HeaderObject>(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]!;
Expand Down
27 changes: 27 additions & 0 deletions packages/shared/src/openApi/3.0.x/parser/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
});
};
19 changes: 18 additions & 1 deletion packages/shared/src/openApi/3.1.x/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -63,6 +63,23 @@ export const parseV3_1_X = (context: Context<OpenAPIV3_1.Document>) => {
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<OpenAPIV3_1.HeaderObject>(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]!;
Expand Down
27 changes: 27 additions & 0 deletions packages/shared/src/openApi/3.1.x/parser/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
});
};
27 changes: 27 additions & 0 deletions specs/3.0.x/components-headers.yaml
Original file line number Diff line number Diff line change
@@ -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'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test spec only covers the happy path (header with a schema that is $ref'd from a schema property). Consider adding a case where a header and a schema share the same name to verify the collision-resolution behaviour, and a header whose schema is itself a $ref to another schema (to confirm schemaToIrSchema handles the transitive reference).

Loading