diff --git a/packages/openapi-generator/src/codec.ts b/packages/openapi-generator/src/codec.ts index 175abbd4..e3eb58a4 100644 --- a/packages/openapi-generator/src/codec.ts +++ b/packages/openapi-generator/src/codec.ts @@ -450,6 +450,20 @@ export function parsePlainInitializer( } } +function parseFunctionBody( + project: Project, + source: SourceFile, + func: swc.ArrowFunctionExpression, +): E.Either { + if (func.body === undefined) { + return errorLeft('Function body is undefined'); + } + if (func.body.type === 'BlockStatement') { + return errorLeft('BlockStatement arrow functions are not yet supported'); + } + return parseCodecInitializer(project, source, func.body); +} + export function parseCodecInitializer( project: Project, source: SourceFile, @@ -471,8 +485,29 @@ export function parseCodecInitializer( } else if (init.type === 'CallExpression') { const callee = init.callee; if (callee.type !== 'Identifier' && callee.type !== 'MemberExpression') { - return errorLeft(`Unimplemented callee type ${init.callee.type}`); + return errorLeft(`Unimplemented callee type ${callee.type}`); + } + + let calleeName: string | [string, string] | undefined; + if (callee.type === 'Identifier') { + calleeName = callee.value; + } else if ( + callee.object.type === 'Identifier' && + callee.property.type === 'Identifier' + ) { + calleeName = [callee.object.value, callee.property.value]; + } + + if (calleeName !== undefined) { + const calleeInitE = findSymbolInitializer(project, source, calleeName); + if (E.isRight(calleeInitE)) { + const [calleeSourceFile, calleeInit] = calleeInitE.right; + if (calleeInit !== null && calleeInit.type === 'ArrowFunctionExpression') { + return parseFunctionBody(project, calleeSourceFile, calleeInit); + } + } } + const identifierE = codecIdentifier(project, source, callee); if (E.isLeft(identifierE)) { return identifierE; diff --git a/packages/openapi-generator/test/externalModuleApiSpec.test.ts b/packages/openapi-generator/test/externalModuleApiSpec.test.ts index 08837ced..7522a169 100644 --- a/packages/openapi-generator/test/externalModuleApiSpec.test.ts +++ b/packages/openapi-generator/test/externalModuleApiSpec.test.ts @@ -368,3 +368,46 @@ testCase( }, [], ); + +testCase( + 'simple api spec with util type functions', + 'test/sample-types/apiSpecWithArrow.ts', + { + openapi: '3.0.3', + info: { + title: 'simple api spec with util type functions', + version: '1.0.0', + description: 'simple api spec with util type functions', + }, + paths: { + '/test': { + get: { + parameters: [], + responses: { + 200: { + description: 'OK', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + hasLargeNumberOfAddresses: { + nullable: true, + type: 'boolean', + }, + }, + required: ['hasLargeNumberOfAddresses'], + }, + }, + }, + }, + }, + }, + }, + }, + components: { + schemas: {}, + }, + }, + [], +); diff --git a/packages/openapi-generator/test/sample-types/apiSpecWithArrow.ts b/packages/openapi-generator/test/sample-types/apiSpecWithArrow.ts new file mode 100644 index 00000000..edb6a807 --- /dev/null +++ b/packages/openapi-generator/test/sample-types/apiSpecWithArrow.ts @@ -0,0 +1,23 @@ +import * as h from '@api-ts/io-ts-http'; +import * as t from 'io-ts'; +import { BooleanFromString, fromNullable } from 'io-ts-types'; + +const BooleanFromNullableWithFallback = () => + fromNullable(t.union([BooleanFromString, t.boolean]), false); + +export const TEST_ROUTE = h.httpRoute({ + path: '/test', + method: 'GET', + request: h.httpRequest({}), + response: { + 200: t.type({ + hasLargeNumberOfAddresses: BooleanFromNullableWithFallback(), + }), + }, +}); + +export const apiSpec = h.apiSpec({ + 'api.test': { + get: TEST_ROUTE, + }, +});