From 1d3660ffb63ac4f6b8f41ab3ba0e829dde402a7c Mon Sep 17 00:00:00 2001 From: Alex McKinney Date: Tue, 19 Mar 2024 17:36:23 -0700 Subject: [PATCH] (fix): Handle optional multipart references (#3218) * (fix): Handle optional multipart references * allow one ofs to be optional too * add test --------- Co-authored-by: armandobelardo --- .../optional-multipart.test.ts.snap | 667 ++++++++++++++++++ .../__snapshots__/vellum.test.ts.snap | 69 +- .../fixtures/optional-multipart/openapi.json | 203 ++++++ .../src/__test__/optional-multipart.test.ts | 5 + .../v3/converters/endpoint/convertRequest.ts | 14 +- .../utils/convertSchemaWithExampleToSchema.ts | 41 ++ 6 files changed, 971 insertions(+), 28 deletions(-) create mode 100644 packages/cli/openapi-parser/src/__test__/__snapshots__/optional-multipart.test.ts.snap create mode 100644 packages/cli/openapi-parser/src/__test__/fixtures/optional-multipart/openapi.json create mode 100644 packages/cli/openapi-parser/src/__test__/optional-multipart.test.ts diff --git a/packages/cli/openapi-parser/src/__test__/__snapshots__/optional-multipart.test.ts.snap b/packages/cli/openapi-parser/src/__test__/__snapshots__/optional-multipart.test.ts.snap new file mode 100644 index 00000000000..86861fcca5f --- /dev/null +++ b/packages/cli/openapi-parser/src/__test__/__snapshots__/optional-multipart.test.ts.snap @@ -0,0 +1,667 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`optional-multipart optional-multipart parse open api 1`] = ` +{ + "channel": [], + "description": null, + "endpoints": [ + { + "audiences": [], + "authed": false, + "availability": null, + "description": "speech", + "errorStatusCode": [], + "examples": [], + "generatedRequestName": "SpeechOperationRequest", + "headers": [ + { + "description": "Your API key.", + "env": null, + "name": "an-api-key", + "parameterNameOverride": null, + "schema": { + "description": "Your API key.", + "generatedName": "SpeechOperationRequestAnApiKey", + "groupName": null, + "nameOverride": null, + "type": "nullable", + "value": { + "description": "Your API key.", + "generatedName": "SpeechOperationRequestAnApiKey", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + ], + "internal": null, + "method": "POST", + "operationId": "speech_operation", + "path": "/v1/speech/{voice_id}", + "pathParameters": [ + { + "description": null, + "name": "voice_id", + "schema": { + "description": null, + "generatedName": "SpeechOperationRequestVoiceId", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + "variableReference": null, + }, + ], + "queryParameters": [ + { + "description": null, + "name": "some_query_param", + "parameterNameOverride": null, + "schema": { + "description": null, + "generatedName": "SpeechOperationRequestSomeQueryParam", + "groupName": null, + "nameOverride": null, + "type": "nullable", + "value": { + "description": null, + "generatedName": "SpeechOperationRequestSomeQueryParam", + "groupName": null, + "nameOverride": null, + "schema": { + "type": "int", + }, + "type": "primitive", + }, + }, + }, + ], + "request": { + "description": null, + "name": "SpeechBody", + "properties": [ + { + "description": null, + "key": "audio", + "schema": { + "isArray": false, + "isOptional": false, + "type": "file", + }, + }, + { + "description": null, + "key": "model_id", + "schema": { + "type": "json", + "value": { + "description": null, + "generatedName": "SpeechOperationRequestModelId", + "groupName": null, + "nameOverride": null, + "type": "optional", + "value": { + "description": null, + "generatedName": "SpeechOperationRequestModelId", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + }, + { + "description": null, + "key": "voice_settings", + "schema": { + "type": "json", + "value": { + "description": "Voice settings.", + "generatedName": "SpeechOperationRequestVoiceSettings", + "groupName": null, + "nameOverride": null, + "type": "optional", + "value": { + "description": "Voice settings.", + "generatedName": "SpeechOperationRequestVoiceSettings", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + }, + ], + "type": "multipart", + }, + "requestNameOverride": null, + "response": { + "description": "Successful Response", + "type": "file", + }, + "sdkName": null, + "server": [], + "summary": "speech", + "tags": [], + }, + { + "audiences": [], + "authed": false, + "availability": null, + "description": "speech_stream", + "errorStatusCode": [], + "examples": [], + "generatedRequestName": "SpeechStreamOperationRequest", + "headers": [ + { + "description": "Your API key.", + "env": null, + "name": "an-api-key", + "parameterNameOverride": null, + "schema": { + "description": "Your API key.", + "generatedName": "SpeechStreamOperationRequestAnApiKey", + "groupName": null, + "nameOverride": null, + "type": "nullable", + "value": { + "description": "Your API key.", + "generatedName": "SpeechStreamOperationRequestAnApiKey", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + ], + "internal": null, + "method": "POST", + "operationId": "speech_stream_operation", + "path": "/v1/speech/{voice_id}/stream", + "pathParameters": [ + { + "description": null, + "name": "voice_id", + "schema": { + "description": null, + "generatedName": "SpeechStreamOperationRequestVoiceId", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + "variableReference": null, + }, + ], + "queryParameters": [ + { + "description": null, + "name": "some_query_param", + "parameterNameOverride": null, + "schema": { + "description": null, + "generatedName": "SpeechStreamOperationRequestSomeQueryParam", + "groupName": null, + "nameOverride": null, + "type": "nullable", + "value": { + "description": null, + "generatedName": "SpeechStreamOperationRequestSomeQueryParam", + "groupName": null, + "nameOverride": null, + "schema": { + "type": "int", + }, + "type": "primitive", + }, + }, + }, + ], + "request": { + "description": null, + "name": "SpeechBodyStream", + "properties": [ + { + "description": null, + "key": "audio", + "schema": { + "isArray": false, + "isOptional": false, + "type": "file", + }, + }, + { + "description": null, + "key": "model_id", + "schema": { + "type": "json", + "value": { + "description": null, + "generatedName": "SpeechStreamOperationRequestModelId", + "groupName": null, + "nameOverride": null, + "type": "optional", + "value": { + "description": null, + "generatedName": "SpeechStreamOperationRequestModelId", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + }, + { + "description": null, + "key": "voice_settings", + "schema": { + "type": "json", + "value": { + "description": "Voice settings.", + "generatedName": "SpeechStreamOperationRequestVoiceSettings", + "groupName": null, + "nameOverride": null, + "type": "optional", + "value": { + "description": "Voice settings.", + "generatedName": "SpeechStreamOperationRequestVoiceSettings", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + }, + { + "description": null, + "key": "my_enum", + "schema": { + "type": "json", + "value": { + "description": null, + "generatedName": "SpeechStreamOperationRequestMyEnum", + "groupName": null, + "nameOverride": null, + "type": "optional", + "value": { + "description": null, + "generatedName": "SpeechStreamOperationRequestMyEnum", + "groupName": null, + "nameOverride": null, + "type": "enum", + "values": [ + { + "casing": { + "camel": null, + "pascal": null, + "screamingSnake": null, + "snake": null, + }, + "description": null, + "generatedName": "default", + "nameOverride": null, + "value": "default", + }, + { + "casing": { + "camel": null, + "pascal": null, + "screamingSnake": null, + "snake": null, + }, + "description": null, + "generatedName": "converting", + "nameOverride": null, + "value": "converting", + }, + ], + }, + }, + }, + }, + { + "description": null, + "key": "my_ref", + "schema": { + "type": "json", + "value": { + "description": null, + "generatedName": "SpeechStreamOperationRequestMyRef", + "groupName": null, + "nameOverride": null, + "type": "optional", + "value": { + "description": null, + "generatedName": "SpeechStreamOperationRequestMyRef", + "groupName": null, + "nameOverride": null, + "schema": "request-model", + "type": "reference", + }, + }, + }, + }, + ], + "type": "multipart", + }, + "requestNameOverride": null, + "response": null, + "sdkName": null, + "server": [], + "summary": "speech_stream", + "tags": [], + }, + ], + "errors": {}, + "globalHeaders": [], + "hasEndpointsMarkedInternal": false, + "nonRequestReferencedSchemas": [ + "request-model", + ], + "schemas": { + "SpeechBody": { + "allOf": [], + "allOfPropertyConflicts": [], + "description": null, + "generatedName": "SpeechBody", + "groupName": null, + "nameOverride": "SpeechBody", + "properties": [ + { + "audiences": [], + "conflict": {}, + "generatedName": "speechBodyAudio", + "key": "audio", + "schema": { + "description": "The audio file.", + "generatedName": "SpeechBodyAudio", + "groupName": null, + "nameOverride": "Audio", + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + { + "audiences": [], + "conflict": {}, + "generatedName": "speechBodyModelId", + "key": "model_id", + "schema": { + "description": null, + "generatedName": "speechBodyModelId", + "groupName": null, + "nameOverride": "SpeechBody", + "type": "optional", + "value": { + "description": null, + "generatedName": "SpeechBodyModelId", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + { + "audiences": [], + "conflict": {}, + "generatedName": "speechBodyVoiceSettings", + "key": "voice_settings", + "schema": { + "description": null, + "generatedName": "speechBodyVoiceSettings", + "groupName": null, + "nameOverride": "SpeechBody", + "type": "optional", + "value": { + "description": "Voice settings.", + "generatedName": "SpeechBodyVoiceSettings", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + ], + "type": "object", + }, + "SpeechBodyStream": { + "allOf": [], + "allOfPropertyConflicts": [], + "description": null, + "generatedName": "SpeechBodyStream", + "groupName": null, + "nameOverride": "SpeechBodyStream", + "properties": [ + { + "audiences": [], + "conflict": {}, + "generatedName": "speechBodyStreamAudio", + "key": "audio", + "schema": { + "description": "The audio file.", + "generatedName": "SpeechBodyStreamAudio", + "groupName": null, + "nameOverride": "Audio", + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + { + "audiences": [], + "conflict": {}, + "generatedName": "speechBodyStreamModelId", + "key": "model_id", + "schema": { + "description": null, + "generatedName": "speechBodyStreamModelId", + "groupName": null, + "nameOverride": "SpeechBodyStream", + "type": "optional", + "value": { + "description": null, + "generatedName": "SpeechBodyStreamModelId", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + { + "audiences": [], + "conflict": {}, + "generatedName": "speechBodyStreamVoiceSettings", + "key": "voice_settings", + "schema": { + "description": null, + "generatedName": "speechBodyStreamVoiceSettings", + "groupName": null, + "nameOverride": "SpeechBodyStream", + "type": "optional", + "value": { + "description": "Voice settings.", + "generatedName": "SpeechBodyStreamVoiceSettings", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + }, + { + "audiences": [], + "conflict": {}, + "generatedName": "speechBodyStreamMyEnum", + "key": "my_enum", + "schema": { + "description": null, + "generatedName": "speechBodyStreamMyEnum", + "groupName": null, + "nameOverride": "SpeechBodyStream", + "type": "optional", + "value": { + "description": null, + "generatedName": "SpeechBodyStreamMyEnum", + "groupName": null, + "nameOverride": null, + "type": "enum", + "values": [ + { + "casing": { + "camel": null, + "pascal": null, + "screamingSnake": null, + "snake": null, + }, + "description": null, + "generatedName": "default", + "nameOverride": null, + "value": "default", + }, + { + "casing": { + "camel": null, + "pascal": null, + "screamingSnake": null, + "snake": null, + }, + "description": null, + "generatedName": "converting", + "nameOverride": null, + "value": "converting", + }, + ], + }, + }, + }, + { + "audiences": [], + "conflict": {}, + "generatedName": "speechBodyStreamMyRef", + "key": "my_ref", + "schema": { + "description": null, + "generatedName": "speechBodyStreamMyRef", + "groupName": null, + "nameOverride": "SpeechBodyStream", + "type": "optional", + "value": { + "description": null, + "generatedName": "SpeechBodyStreamMyRef", + "groupName": null, + "nameOverride": null, + "schema": "request-model", + "type": "reference", + }, + }, + }, + ], + "type": "object", + }, + "request-model": { + "allOf": [], + "allOfPropertyConflicts": [], + "description": null, + "generatedName": "RequestModel", + "groupName": null, + "nameOverride": null, + "properties": [ + { + "audiences": [], + "conflict": {}, + "generatedName": "requestModelVoiceId", + "key": "voice_id", + "schema": { + "description": null, + "generatedName": "RequestModelVoiceId", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": null, + "type": "string", + }, + "type": "primitive", + }, + }, + ], + "type": "object", + }, + }, + "securitySchemes": {}, + "servers": [], + "tags": { + "orderedTagIds": null, + "tagsById": {}, + }, + "title": "API Documentation", + "variables": {}, + "webhooks": [], +} +`; diff --git a/packages/cli/openapi-parser/src/__test__/__snapshots__/vellum.test.ts.snap b/packages/cli/openapi-parser/src/__test__/__snapshots__/vellum.test.ts.snap index 8777890e282..931c1db1560 100644 --- a/packages/cli/openapi-parser/src/__test__/__snapshots__/vellum.test.ts.snap +++ b/packages/cli/openapi-parser/src/__test__/__snapshots__/vellum.test.ts.snap @@ -2583,24 +2583,31 @@ Upload a document to be indexed and used for search. "generatedName": "DocumentsUploadRequestAddToIndexNames", "groupName": null, "nameOverride": null, - "type": "nullable", + "type": "optional", "value": { "description": "Optionally include the names of all indexes that you'd like this document to be included in", "generatedName": "DocumentsUploadRequestAddToIndexNames", "groupName": null, "nameOverride": null, - "type": "array", + "type": "nullable", "value": { - "description": null, - "generatedName": "DocumentsUploadRequestAddToIndexNamesItem", + "description": "Optionally include the names of all indexes that you'd like this document to be included in", + "generatedName": "DocumentsUploadRequestAddToIndexNames", "groupName": null, "nameOverride": null, - "schema": { - "maxLength": null, - "minLength": 1, - "type": "string", + "type": "array", + "value": { + "description": null, + "generatedName": "DocumentsUploadRequestAddToIndexNamesItem", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": 1, + "type": "string", + }, + "type": "primitive", }, - "type": "primitive", }, }, }, @@ -2616,18 +2623,25 @@ Upload a document to be indexed and used for search. "generatedName": "DocumentsUploadRequestExternalId", "groupName": null, "nameOverride": null, - "type": "nullable", + "type": "optional", "value": { "description": "Optionally include an external ID for this document. This is useful if you want to re-upload the same document later when its contents change and would like it to be re-indexed.", "generatedName": "DocumentsUploadRequestExternalId", "groupName": null, "nameOverride": null, - "schema": { - "maxLength": null, - "minLength": 1, - "type": "string", + "type": "nullable", + "value": { + "description": "Optionally include an external ID for this document. This is useful if you want to re-upload the same document later when its contents change and would like it to be re-indexed.", + "generatedName": "DocumentsUploadRequestExternalId", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": 1, + "type": "string", + }, + "type": "primitive", }, - "type": "primitive", }, }, }, @@ -2670,24 +2684,31 @@ Upload a document to be indexed and used for search. "generatedName": "DocumentsUploadRequestKeywords", "groupName": null, "nameOverride": null, - "type": "nullable", + "type": "optional", "value": { "description": "Optionally include a list of keywords that'll be associated with this document. Used when performing keyword searches.", "generatedName": "DocumentsUploadRequestKeywords", "groupName": null, "nameOverride": null, - "type": "array", + "type": "nullable", "value": { - "description": null, - "generatedName": "DocumentsUploadRequestKeywordsItem", + "description": "Optionally include a list of keywords that'll be associated with this document. Used when performing keyword searches.", + "generatedName": "DocumentsUploadRequestKeywords", "groupName": null, "nameOverride": null, - "schema": { - "maxLength": null, - "minLength": 1, - "type": "string", + "type": "array", + "value": { + "description": null, + "generatedName": "DocumentsUploadRequestKeywordsItem", + "groupName": null, + "nameOverride": null, + "schema": { + "maxLength": null, + "minLength": 1, + "type": "string", + }, + "type": "primitive", }, - "type": "primitive", }, }, }, diff --git a/packages/cli/openapi-parser/src/__test__/fixtures/optional-multipart/openapi.json b/packages/cli/openapi-parser/src/__test__/fixtures/optional-multipart/openapi.json new file mode 100644 index 00000000000..de058cb59f3 --- /dev/null +++ b/packages/cli/openapi-parser/src/__test__/fixtures/optional-multipart/openapi.json @@ -0,0 +1,203 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "API Documentation", + "version": "1.0" + }, + "paths": { + "/v1/speech/{voice_id}": { + "post": { + "tags": [], + "summary": "speech", + "description": "speech", + "operationId": "speech_operation", + "parameters": [ + { + "required": true, + "schema": { + "type": "string", + "title": "Voice Id" + }, + "example": "1234", + "name": "voice_id", + "in": "path" + }, + { + "required": false, + "schema": { + "type": "integer", + "title": "An optional query param", + "default": 0 + }, + "name": "some_query_param", + "in": "query" + }, + { + "description": "Your API key.", + "required": false, + "schema": { + "type": "string", + "title": "an-api-key", + "description": "Your API key." + }, + "name": "an-api-key", + "in": "header" + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/SpeechBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "audio/mpeg": {} + } + } + } + } + }, + "/v1/speech/{voice_id}/stream": { + "post": { + "tags": [], + "summary": "speech_stream", + "description": "speech_stream", + "operationId": "speech_stream_operation", + "parameters": [ + { + "required": true, + "schema": { + "type": "string", + "title": "Voice Id" + }, + "example": "1234", + "name": "voice_id", + "in": "path" + }, + { + "required": false, + "schema": { + "type": "integer", + "title": "An optional query param", + "default": 0 + }, + "name": "some_query_param", + "in": "query" + }, + { + "description": "Your API key.", + "required": false, + "schema": { + "type": "string", + "title": "an-api-key", + "description": "Your API key." + }, + "name": "an-api-key", + "in": "header" + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/SpeechBodyStream" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response" + } + } + } + }, + "tags": [] + }, + "components": { + "schemas": { + "SpeechBody": { + "properties": { + "audio": { + "type": "string", + "format": "binary", + "title": "Audio", + "description": "The audio file." + }, + "model_id": { + "type": "string", + "title": "Model Id", + "default": "model-1" + }, + "voice_settings": { + "type": "string", + "title": "Voice Settings", + "description": "Voice settings." + } + }, + "type": "object", + "required": [ + "audio" + ], + "title": "SpeechBody" + }, + "SpeechBodyStream": { + "properties": { + "audio": { + "type": "string", + "format": "binary", + "title": "Audio", + "description": "The audio file." + }, + "model_id": { + "type": "string", + "title": "Model Id", + "default": "model-1" + }, + "voice_settings": { + "type": "string", + "title": "Voice Settings", + "description": "Voice settings." + }, + "my_enum": { + "type": "string", + "enum": [ + "default", + "converting" + ], + "title": "my_enum" + }, + "my_ref": { + "$ref": "#/components/schemas/request-model" + } + }, + "type": "object", + "required": [ + "audio" + ], + "title": "SpeechBodyStream" + }, + "request-model": { + "properties": { + "voice_id": { + "type": "string", + "title": "Voice Id" + } + }, + "type": "object", + "required": [ + "voice_id" + ], + "title": "request-model" + } + } + } +} \ No newline at end of file diff --git a/packages/cli/openapi-parser/src/__test__/optional-multipart.test.ts b/packages/cli/openapi-parser/src/__test__/optional-multipart.test.ts new file mode 100644 index 00000000000..4f3074b3964 --- /dev/null +++ b/packages/cli/openapi-parser/src/__test__/optional-multipart.test.ts @@ -0,0 +1,5 @@ +import { testParseOpenAPI } from "./testParseOpenApi"; + +describe("optional-multipart", () => { + testParseOpenAPI("optional-multipart", "openapi.json"); +}); diff --git a/packages/cli/openapi-parser/src/openapi/v3/converters/endpoint/convertRequest.ts b/packages/cli/openapi-parser/src/openapi/v3/converters/endpoint/convertRequest.ts index 7fd3c8089c6..74681ad06e5 100644 --- a/packages/cli/openapi-parser/src/openapi/v3/converters/endpoint/convertRequest.ts +++ b/packages/cli/openapi-parser/src/openapi/v3/converters/endpoint/convertRequest.ts @@ -2,7 +2,10 @@ import { MultipartSchema, RequestWithExample } from "@fern-api/openapi-ir-sdk"; import { OpenAPIV3 } from "openapi-types"; import { getExtension } from "../../../../getExtension"; import { convertSchema, getSchemaIdFromReference, SCHEMA_REFERENCE_PREFIX } from "../../../../schema/convertSchemas"; -import { convertSchemaWithExampleToSchema } from "../../../../schema/utils/convertSchemaWithExampleToSchema"; +import { + convertSchemaWithExampleToOptionalSchema, + convertSchemaWithExampleToSchema +} from "../../../../schema/utils/convertSchemaWithExampleToSchema"; import { isReferenceObject } from "../../../../schema/utils/isReferenceObject"; import { AbstractOpenAPIV3ParserContext } from "../../AbstractOpenAPIV3ParserContext"; import { FernOpenAPIExtension } from "../../extensions/fernExtensions"; @@ -86,6 +89,8 @@ export function convertRequest({ : undefined, description: undefined, properties: Object.entries(resolvedMultipartSchema.schema.properties ?? {}).map(([key, definition]) => { + const required: string[] | undefined = resolvedMultipartSchema.schema.required; + const isRequired = required !== undefined && required.includes(key); if ( !isReferenceObject(definition) && ((definition.type === "string" && definition.format === "binary") || @@ -94,8 +99,6 @@ export function convertRequest({ definition.items.type === "string" && definition.items.format === "binary")) ) { - const required: string[] | undefined = resolvedMultipartSchema.schema.required; - const isRequired = required !== undefined && required.includes(key); return { key, schema: MultipartSchema.file({ isOptional: !isRequired, isArray: definition.type === "array" }), @@ -104,10 +107,13 @@ export function convertRequest({ } const schemaWithExample = convertSchema(definition, false, context, [...requestBreadcrumbs, key]); const audiences = getExtension(definition, FernOpenAPIExtension.AUDIENCES) ?? []; + const schema = isRequired + ? convertSchemaWithExampleToSchema(schemaWithExample) + : convertSchemaWithExampleToOptionalSchema(schemaWithExample); return { key, audiences, - schema: MultipartSchema.json(convertSchemaWithExampleToSchema(schemaWithExample)), + schema: MultipartSchema.json(schema), description: undefined }; }) diff --git a/packages/cli/openapi-parser/src/schema/utils/convertSchemaWithExampleToSchema.ts b/packages/cli/openapi-parser/src/schema/utils/convertSchemaWithExampleToSchema.ts index 75fa196121d..971a0fc0382 100644 --- a/packages/cli/openapi-parser/src/schema/utils/convertSchemaWithExampleToSchema.ts +++ b/packages/cli/openapi-parser/src/schema/utils/convertSchemaWithExampleToSchema.ts @@ -102,6 +102,47 @@ export function convertSchemaWithExampleToSchema(schema: SchemaWithExample): Sch } } +export function convertSchemaWithExampleToOptionalSchema(schema: SchemaWithExample): Schema { + switch (schema.type) { + case "object": + case "array": + case "enum": + case "literal": + case "nullable": + case "primitive": + case "map": + case "reference": + case "unknown": + return Schema.optional({ + generatedName: schema.generatedName, + nameOverride: schema.nameOverride, + description: schema.description, + value: convertSchemaWithExampleToSchema(schema), + groupName: schema.groupName + }); + case "optional": + return Schema.optional({ + generatedName: schema.generatedName, + nameOverride: schema.nameOverride, + description: schema.description, + value: convertSchemaWithExampleToSchema(schema.value), + groupName: schema.groupName + }); + case "oneOf": { + const oneOfSchema = convertToOneOf(schema.value); + return Schema.optional({ + generatedName: oneOfSchema.generatedName, + nameOverride: oneOfSchema.nameOverride, + description: oneOfSchema.description, + value: Schema.oneOf(convertToOneOf(schema.value)), + groupName: oneOfSchema.groupName + }); + } + default: + assertNever(schema); + } +} + function convertToOneOf(oneOfSchema: OneOfSchemaWithExample): OneOfSchema { switch (oneOfSchema.type) { case "discriminated":