From 2d004560fd574bef7a5abcf726b93423e596159b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:36:03 +0000 Subject: [PATCH 1/3] fix(openapi): widen global header type to string when schemas are incompatible Co-Authored-By: thomas@buildwithfern.com --- .../src/buildGlobalHeaders.ts | 50 ++++++++++- .../apis/openapi-accept-header/generators.yml | 3 + .../apis/openapi-accept-header/openapi.yml | 89 +++++++++++++++++++ 3 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 test-definitions/fern/apis/openapi-accept-header/generators.yml create mode 100644 test-definitions/fern/apis/openapi-accept-header/openapi.yml diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildGlobalHeaders.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildGlobalHeaders.ts index a64d6ca582a1..9dc68f084e0c 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildGlobalHeaders.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildGlobalHeaders.ts @@ -12,8 +12,9 @@ import { getTypeFromTypeReference } from "./utils/getTypeFromTypeReference"; import { wrapTypeReferenceAsOptional } from "./utils/wrapTypeReferenceAsOptional"; class HeaderWithCount { - public readonly schema: RawSchemas.HttpHeaderSchema; + public schema: RawSchemas.HttpHeaderSchema; public count = 0; + private hasIncompatibleSchemas = false; constructor(schema: RawSchemas.HttpHeaderSchema) { this.schema = schema; @@ -22,6 +23,14 @@ class HeaderWithCount { public increment(): void { this.count += 1; } + + public markIncompatible(): void { + this.hasIncompatibleSchemas = true; + } + + public isIncompatible(): boolean { + return this.hasIncompatibleSchemas; + } } /* 75% of endpoints must have header present, for it to be considered a global header*/ @@ -101,6 +110,20 @@ export function buildGlobalHeaders(context: OpenApiIrConverterContext): void { }); headerWithCount = new HeaderWithCount(convertedHeader); globalHeaders[header.name] = headerWithCount; + } else { + // Check if the current header schema is compatible with the existing one + const currentConvertedHeader = buildHeader({ + header, + fileContainingReference: RelativeFilePath.of(ROOT_API_FILENAME), + context, + namespace: undefined + }); + const existingType = getTypeFromHttpHeaderSchema(headerWithCount.schema); + const currentType = getTypeFromHttpHeaderSchema(currentConvertedHeader); + if (existingType !== currentType) { + // Schemas are incompatible, widen the type to string + headerWithCount.markIncompatible(); + } } headerWithCount.increment(); } @@ -115,16 +138,37 @@ export function buildGlobalHeaders(context: OpenApiIrConverterContext): void { if (predefinedHeader != null) { continue; // already added } else if (isRequired) { + // If schemas are incompatible, widen the type to string + const schema = header.isIncompatible() ? widenToString(header.schema) : header.schema; context.builder.addGlobalHeader({ name: headerName, - schema: header.schema + schema }); } else if (isOptional) { + // If schemas are incompatible, widen the type to string + const schema = header.isIncompatible() ? widenToString(header.schema) : header.schema; context.builder.addGlobalHeader({ name: headerName, - schema: wrapTypeReferenceAsOptional(header.schema) + schema: wrapTypeReferenceAsOptional(schema) }); } } } } + +function getTypeFromHttpHeaderSchema(schema: RawSchemas.HttpHeaderSchema): string { + if (typeof schema === "string") { + return schema; + } + return schema.type; +} + +function widenToString(schema: RawSchemas.HttpHeaderSchema): RawSchemas.HttpHeaderSchema { + if (typeof schema === "string") { + return "string"; + } + return { + ...schema, + type: "string" + }; +} diff --git a/test-definitions/fern/apis/openapi-accept-header/generators.yml b/test-definitions/fern/apis/openapi-accept-header/generators.yml new file mode 100644 index 000000000000..d8bd49b048ba --- /dev/null +++ b/test-definitions/fern/apis/openapi-accept-header/generators.yml @@ -0,0 +1,3 @@ +api: + specs: + - openapi: ./openapi.yml diff --git a/test-definitions/fern/apis/openapi-accept-header/openapi.yml b/test-definitions/fern/apis/openapi-accept-header/openapi.yml new file mode 100644 index 000000000000..e98903d6bb48 --- /dev/null +++ b/test-definitions/fern/apis/openapi-accept-header/openapi.yml @@ -0,0 +1,89 @@ +openapi: 3.0.3 +info: + title: Accept Header Test API + version: 1.0.0 + description: Test API for Accept header with text/event-stream default +paths: + /events: + get: + operationId: getEvents + summary: Get events stream + description: Returns a stream of events + parameters: + - $ref: '#/components/parameters/AcceptEventStream' + responses: + '200': + description: Successful response + content: + text/event-stream: + schema: + type: string + /data: + get: + operationId: getData + summary: Get data as JSON + description: Returns data as JSON + parameters: + - $ref: '#/components/parameters/AcceptJson' + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + id: + type: string + /other: + get: + operationId: getOther + summary: Get other data as JSON + description: Returns other data as JSON + parameters: + - $ref: '#/components/parameters/AcceptJson' + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + name: + type: string + /more: + get: + operationId: getMore + summary: Get more data as JSON + description: Returns more data as JSON + parameters: + - $ref: '#/components/parameters/AcceptJson' + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + value: + type: string +components: + parameters: + AcceptEventStream: + name: Accept + in: header + required: true + description: The MIME type of the response body. + schema: + type: string + default: 'text/event-stream' + AcceptJson: + name: Accept + in: header + required: true + description: The MIME type of the response body. + schema: + type: string + const: 'application/json' From f128f969fa82bdd96b084999e692f0698db866c8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:42:18 +0000 Subject: [PATCH 2/3] chore(openapi): add JSON schema snapshots for openapi-accept-header test fixture Co-Authored-By: thomas@buildwithfern.com --- .../type__GetDataResponse.json | 17 +++++++++++++++++ .../type__GetMoreResponse.json | 17 +++++++++++++++++ .../type__GetOtherResponse.json | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetDataResponse.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetMoreResponse.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetOtherResponse.json diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetDataResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetDataResponse.json new file mode 100644 index 000000000000..05f2b9661f64 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetDataResponse.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetMoreResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetMoreResponse.json new file mode 100644 index 000000000000..8b118f7aa83f --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetMoreResponse.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetOtherResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetOtherResponse.json new file mode 100644 index 000000000000..005c29438195 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/openapi-accept-header/type__GetOtherResponse.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file From 2cf0c31ad10c87344f006ff7f723f840e9d39c4e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:47:06 +0000 Subject: [PATCH 3/3] chore(openapi): add Swift SDK snapshot for openapi-accept-header test fixture Co-Authored-By: thomas@buildwithfern.com --- .../formatted-endpoint-paths/openapi-accept-header.swift | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/openapi-accept-header.swift diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/openapi-accept-header.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/openapi-accept-header.swift new file mode 100644 index 000000000000..2fae599f9ebc --- /dev/null +++ b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/openapi-accept-header.swift @@ -0,0 +1,5 @@ +// service_ +"/events" +"/data" +"/other" +"/more" \ No newline at end of file