From ac2c97467b3e633735c6948e7638a0abc5c4b821 Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 13 Jun 2024 11:13:03 -0400 Subject: [PATCH] feat: add support for unions that are nullable and optional DX-480 --- packages/openapi-generator/src/openapi.ts | 10 ++- .../openapi-generator/test/openapi.test.ts | 90 +++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/packages/openapi-generator/src/openapi.ts b/packages/openapi-generator/src/openapi.ts index e061ae4e..7eeb6a02 100644 --- a/packages/openapi-generator/src/openapi.ts +++ b/packages/openapi-generator/src/openapi.ts @@ -86,11 +86,17 @@ function schemaToOpenAPI( // If there are two schemas and one of the schemas is undefined, that means the union is a case of `optional` type const undefinedSchema = schema.schemas.find((s) => s.type === 'undefined'); const nonUndefinedSchema = schema.schemas.find((s) => s.type !== 'undefined'); + // If nullSchema exists, that means that the union is also nullable + const nullSchema = schema.schemas.find((s) => s.type === 'null'); // and we can just return the other schema (while attaching the comment to that schema) const isOptional = - schema.schemas.length == 2 && undefinedSchema && nonUndefinedSchema; + schema.schemas.length >= 2 && undefinedSchema && nonUndefinedSchema; if (isOptional) { - return schemaToOpenAPI({ ...nonUndefinedSchema, comment: schema.comment }); + return schemaToOpenAPI({ + ...nonUndefinedSchema, + comment: schema.comment, + ...(nullSchema ? { nullable: true } : {}), + }); } for (const s of schema.schemas) { diff --git a/packages/openapi-generator/test/openapi.test.ts b/packages/openapi-generator/test/openapi.test.ts index 2ad41f4c..3cadde00 100644 --- a/packages/openapi-generator/test/openapi.test.ts +++ b/packages/openapi-generator/test/openapi.test.ts @@ -2736,3 +2736,93 @@ testCase('route with optional array query parameter and documentation', ROUTE_WI schemas: {} } }); + + +const ROUTE_WITH_ARRAY_UNION_NULL_UNDEFINED_QUERY_PARAM = ` +import * as t from 'io-ts'; +import * as h from '@api-ts/io-ts-http'; + +/** + * A simple route with type descriptions for references + * + * @operationId api.v1.test + * @tag Test Routes + */ +export const route = h.httpRoute({ + path: '/foo', + method: 'GET', + request: h.httpRequest({ + query: { + /** + * This is a foo description. + * @example abc + * @pattern ^[a-z]+$ + */ + ipRestrict: t.union([t.array(t.string), t.null, t.undefined]), + }, + }), + response: { + 200: { + test: t.string + } + }, +}); +`; + +testCase('route with array union of null and undefined', ROUTE_WITH_ARRAY_UNION_NULL_UNDEFINED_QUERY_PARAM, { + openapi: '3.0.3', + info: { + title: 'Test', + version: '1.0.0' + }, + paths: { + '/foo': { + get: { + summary: 'A simple route with type descriptions for references', + operationId: 'api.v1.test', + tags: [ + 'Test Routes' + ], + parameters: [ + { + description: 'This is a foo description.', + in: 'query', + name: 'ipRestrict', + schema: { + items: { + description: 'This is a foo description.', + example: 'abc', + type: 'string', + pattern: '^[a-z]+$' + }, + type: 'array' + } + } + ], + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + test: { + type: 'string' + } + }, + required: [ + 'test' + ] + } + } + } + } + } + } + } + }, + components: { + schemas: {} + } +});