From 96b5e1a6d41fd57584ccc80650df5ca6cdb00c36 Mon Sep 17 00:00:00 2001 From: Yan Date: Thu, 23 Oct 2025 14:59:37 -0700 Subject: [PATCH 1/2] refactor: improve the error message for missing external codecs in openapi-generator following principles of 1. What went wrong and 2. how to solve it --- packages/openapi-generator/src/codec.ts | 6 +++++- packages/openapi-generator/test/apiSpec.test.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/openapi-generator/src/codec.ts b/packages/openapi-generator/src/codec.ts index e3eb58a4..e909c8f6 100644 --- a/packages/openapi-generator/src/codec.ts +++ b/packages/openapi-generator/src/codec.ts @@ -528,7 +528,11 @@ export function parseCodecInitializer( // schema.location might be a package name -> need to resolve the path from the project types const path = project.getTypes()[schema.name]; if (path === undefined) - return errorLeft(`Cannot find module '${schema.location}' in the project`); + return errorLeft( + `Cannot find external codec '${schema.name}' from module '${schema.location}'. ` + + `To fix this, add the codec definition to your codec config file. ` + + `See: https://github.com/BitGo/api-ts/tree/master/packages/openapi-generator#4-defining-custom-codecs`, + ); refSource = project.get(path); if (refSource === undefined) { return errorLeft(`Cannot find '${schema.name}' from '${schema.location}'`); diff --git a/packages/openapi-generator/test/apiSpec.test.ts b/packages/openapi-generator/test/apiSpec.test.ts index 52a7030b..4c4bb424 100644 --- a/packages/openapi-generator/test/apiSpec.test.ts +++ b/packages/openapi-generator/test/apiSpec.test.ts @@ -290,5 +290,5 @@ const MISSING_REFERENCE = { }; testCase('missing reference', MISSING_REFERENCE, '/index.ts', {}, [ - "Cannot find module 'foo' in the project", + "Cannot find external codec 'Foo' from module 'foo'. To fix this, add the codec definition to your codec config file. See: https://github.com/BitGo/api-ts/tree/master/packages/openapi-generator#4-defining-custom-codecs", ]); From 81ac730826b4763c0de9555d7389b65c13602f66 Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 24 Oct 2025 11:36:19 -0700 Subject: [PATCH 2/2] 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, + }, +});