From 81ac730826b4763c0de9555d7389b65c13602f66 Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 24 Oct 2025 11:36:19 -0700 Subject: [PATCH] fix: support BlockStatement arrow functions in codec parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, only implicit-return arrow functions were supported: ```typescript const factory = () => t.string; // ✅ Worked BlockStatement arrow functions with explicit returns would fail: const factory = () => { return t.string; // ❌ Error: "BlockStatement arrow functions are not yet supported" }; ``` This fix: - Searches BlockStatement.stmts for ReturnStatement nodes - Extracts the return value expression (returnStmt.argument) - Passes it to parseCodecInitializer() for recursive parsing - Validates that a return statement exists with a non-undefined argument Both styles now produce identical schemas since they converge on the same parseCodecInitializer() call with the return value expression. Test case added using BooleanFromNullableWithFallback() with explicit block syntax to verify the parser handles both arrow function styles. --- packages/openapi-generator/src/codec.ts | 9 +++- .../test/externalModuleApiSpec.test.ts | 43 +++++++++++++++++++ .../sample-types/apiSpecWithBlockArrow.ts | 24 +++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 packages/openapi-generator/test/sample-types/apiSpecWithBlockArrow.ts diff --git a/packages/openapi-generator/src/codec.ts b/packages/openapi-generator/src/codec.ts index e909c8f6..b33a8491 100644 --- a/packages/openapi-generator/src/codec.ts +++ b/packages/openapi-generator/src/codec.ts @@ -459,7 +459,14 @@ function parseFunctionBody( return errorLeft('Function body is undefined'); } if (func.body.type === 'BlockStatement') { - return errorLeft('BlockStatement arrow functions are not yet supported'); + const returnStmt = func.body.stmts.find((s) => s.type === 'ReturnStatement'); + if (!returnStmt || returnStmt.type !== 'ReturnStatement') { + return errorLeft('BlockStatement must contain a return statement'); + } + if (!returnStmt.argument) { + return errorLeft('Return statement must have an argument'); + } + return parseCodecInitializer(project, source, returnStmt.argument); } return parseCodecInitializer(project, source, func.body); } diff --git a/packages/openapi-generator/test/externalModuleApiSpec.test.ts b/packages/openapi-generator/test/externalModuleApiSpec.test.ts index 7522a169..46c961a1 100644 --- a/packages/openapi-generator/test/externalModuleApiSpec.test.ts +++ b/packages/openapi-generator/test/externalModuleApiSpec.test.ts @@ -411,3 +411,46 @@ testCase( }, [], ); + +testCase( + 'simple api spec with block statement arrow functions', + 'test/sample-types/apiSpecWithBlockArrow.ts', + { + openapi: '3.0.3', + info: { + title: 'simple api spec with block statement arrow functions', + version: '1.0.0', + description: 'simple api spec with block statement arrow 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/apiSpecWithBlockArrow.ts b/packages/openapi-generator/test/sample-types/apiSpecWithBlockArrow.ts new file mode 100644 index 00000000..74074ad0 --- /dev/null +++ b/packages/openapi-generator/test/sample-types/apiSpecWithBlockArrow.ts @@ -0,0 +1,24 @@ +import * as h from '@api-ts/io-ts-http'; +import * as t from 'io-ts'; +import { BooleanFromString, fromNullable } from 'io-ts-types'; + +const BooleanFromNullableWithFallback = () => { + return 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, + }, +});