From 73fbadbcaa4b816dc2ba3d35c0cb67aa7af97009 Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 13:06:42 -0400 Subject: [PATCH 01/10] feat: support custom codec files in host repositories DX-658 --- packages/openapi-generator/src/project.ts | 45 +++++++++++++++++ .../test/externalModuleApiSpec.test.ts | 49 ++++++++++++++++++- .../sample-types/apiSpecWithCustomCodec.ts | 16 ++++++ .../custom-codecs/openapi-gen.config.js | 14 ++++++ .../@bitgo/custom-codecs/package.json | 17 +++++++ .../@bitgo/custom-codecs/src/index.ts | 12 +++++ .../@bitgo/custom-codecs/tsconfig.json | 7 +++ 7 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 packages/openapi-generator/test/sample-types/apiSpecWithCustomCodec.ts create mode 100644 packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/openapi-gen.config.js create mode 100644 packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/package.json create mode 100644 packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/src/index.ts create mode 100644 packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/tsconfig.json diff --git a/packages/openapi-generator/src/project.ts b/packages/openapi-generator/src/project.ts index fa11ed34..f464ee50 100644 --- a/packages/openapi-generator/src/project.ts +++ b/packages/openapi-generator/src/project.ts @@ -37,6 +37,7 @@ export class Project { async parseEntryPoint(entryPoint: string): Promise> { const queue: string[] = [entryPoint]; let path: string | undefined; + const visitedPackages = new Set(); while (((path = queue.pop()), path !== undefined)) { if (!['.ts', '.js'].includes(p.extname(path))) { continue; @@ -59,6 +60,14 @@ export class Project { // If we are not resolving a relative path, we need to resolve the entry point const baseDir = p.dirname(sourceFile.path); let entryPoint = this.resolveEntryPoint(baseDir, sym.from); + + if (!visitedPackages.has(sym.from)) { + // This is a step that checks if this import has custom codecs, and loads them into known imports + await this.populateCustomCodecs(baseDir, sym.from); + } + + visitedPackages.add(sym.from); + if (E.isLeft(entryPoint)) { continue; } else if (!this.has(entryPoint.right)) { @@ -148,4 +157,40 @@ export class Project { getTypes() { return this.types; } + + private async populateCustomCodecs(basedir: string, packageName: string) { + try { + const packageJson = resolve.sync(`${packageName}/package.json`, { + basedir, + extensions: ['.json'], + }); + const packageInfo = JSON.parse(fs.readFileSync(packageJson, 'utf8')); + + if (packageInfo['customCodecFile']) { + // The package defines their own custom codecs + const customCodecPath = resolve.sync( + `${packageName}/${packageInfo['customCodecFile']}`, + { + basedir, + extensions: ['.ts', '.js'], + }, + ); + const module = await import(customCodecPath); + if (module.default === undefined) { + console.error(`Could not find default export in ${customCodecPath}`); + return; + } + + const customCodecs = module.default(E); + this.knownImports[packageName] = { + ...this.knownImports[packageName], + ...customCodecs, + }; + + console.error(`Loaded custom codecs for ${packageName}`); + } + } catch (e) { + return; + } + } } diff --git a/packages/openapi-generator/test/externalModuleApiSpec.test.ts b/packages/openapi-generator/test/externalModuleApiSpec.test.ts index 05c37e64..ee535964 100644 --- a/packages/openapi-generator/test/externalModuleApiSpec.test.ts +++ b/packages/openapi-generator/test/externalModuleApiSpec.test.ts @@ -126,8 +126,8 @@ async function testCase( components, ); - assert.deepEqual(errors, expectedErrors); - assert.deepEqual(openapi, expected); + assert.deepStrictEqual(errors, expectedErrors); + assert.deepStrictEqual(openapi, expected); }); } @@ -319,3 +319,48 @@ testCase( }, [] ) + +testCase("simple api spec with custom codec", "test/sample-types/apiSpecWithCustomCodec.ts", { + openapi: "3.0.3", + info: { + title: "simple api spec with custom codec", + version: "4.7.4", + description: "simple api spec with custom codec" + }, + paths: { + "/test": { + get: { + parameters: [], + responses: { + 200: { + description: "OK", + content: { + 'application/json': { + schema: { + type: 'string', + description: 'Sample custom codec', + example: 'sample', + format: 'sample' + } + } + } + }, + 201: { + description: 'Created', + content: { + 'application/json': { + schema: { + type: 'number', + description: 'Another sample codec', + } + } + } + } + } + } + } + }, + components: { + schemas: {} + } +}, []); \ No newline at end of file diff --git a/packages/openapi-generator/test/sample-types/apiSpecWithCustomCodec.ts b/packages/openapi-generator/test/sample-types/apiSpecWithCustomCodec.ts new file mode 100644 index 00000000..d708457d --- /dev/null +++ b/packages/openapi-generator/test/sample-types/apiSpecWithCustomCodec.ts @@ -0,0 +1,16 @@ +import { SampleCustomCodec, AnotherSampleCodec } from '@bitgo/custom-codecs'; +import * as h from '@api-ts/io-ts-http'; + +export const apiSpec = h.apiSpec({ + 'api.get.test': { + get: h.httpRoute({ + path: '/test', + method: 'GET', + request: h.httpRequest({}), + response: { + 200: SampleCustomCodec, + 201: AnotherSampleCodec, + }, + }), + }, +}) \ No newline at end of file diff --git a/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/openapi-gen.config.js b/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/openapi-gen.config.js new file mode 100644 index 00000000..d9858cc4 --- /dev/null +++ b/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/openapi-gen.config.js @@ -0,0 +1,14 @@ +module.exports = (E) => { + return { + SampleCustomCodec: () => E.right({ + type: 'string', + description: 'Sample custom codec', + example: 'sample', + format: 'sample' + }), + AnotherSampleCodec: () => E.right({ + type: 'number', + description: 'Another sample codec', + }) + } +} \ No newline at end of file diff --git a/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/package.json b/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/package.json new file mode 100644 index 00000000..9ce999dc --- /dev/null +++ b/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/package.json @@ -0,0 +1,17 @@ +{ + "name": "custom-codecs", + "version": "0.0.1", + "main": "dist/src/index.js", + "types": "src/index.ts", + "files": [ + "dist/src/**/*", + "src/**/*" + ], + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "", + "customCodecFile": "openapi-gen.config.js" + } \ No newline at end of file diff --git a/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/src/index.ts b/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/src/index.ts new file mode 100644 index 00000000..aaaca0d0 --- /dev/null +++ b/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/src/index.ts @@ -0,0 +1,12 @@ +import * as t from 'io-ts'; + +export const SampleCustomCodec: t.Type = new t.Type( + 'SampleCustomCodec', + (u): u is undefined => u === undefined, + (u, c) => + u === null || u === undefined ? t.success(undefined) : t.failure(u, c), + () => undefined, + ); +export type SampleCustomCodec = t.TypeOf; + +export const AnotherSampleCodec = t.number; \ No newline at end of file diff --git a/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/tsconfig.json b/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/tsconfig.json new file mode 100644 index 00000000..6673fe4e --- /dev/null +++ b/packages/openapi-generator/test/sample-types/node_modules/@bitgo/custom-codecs/tsconfig.json @@ -0,0 +1,7 @@ +{ + "include": ["src/**/*.ts"], + "compilerOptions": { + "outDir": "dist" + }, + "references": [] + } \ No newline at end of file From 5e8371fe7992c6d4e244144648b6ce859062b86f Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 13:07:32 -0400 Subject: [PATCH 02/10] docs: update docs on custom codecs in the generator DX-658 --- packages/openapi-generator/README.md | 150 +++++++++++++++++++++------ 1 file changed, 120 insertions(+), 30 deletions(-) diff --git a/packages/openapi-generator/README.md b/packages/openapi-generator/README.md index d262e1e4..60a10bec 100644 --- a/packages/openapi-generator/README.md +++ b/packages/openapi-generator/README.md @@ -5,7 +5,7 @@ API specification into an OpenAPI specification. ## Install -``` +```shell npm install --save-dev @api-ts/openapi-generator ``` @@ -15,7 +15,7 @@ The **openapi-generator** assumes the io-ts-http `apiSpec` is exported in the to of the Typescript file passed as an input parameter. The OpenAPI specification will be written to stdout. -``` +```shell ARGUMENTS: - API route definition file @@ -35,32 +35,122 @@ For example: npx openapi-generator src/index.ts ``` -## Custom codec file - -`openapi-generator` only reads files in the specified package, and stops at the module -boundary. This allows it to work even without `node_modules` installed. It has built-in -support for `io-ts`, `io-ts-types`, and `@api-ts/io-ts-http` imports. If your package -imports codecs from another external library, then you will have to define them in a -custom configuration file so that `openapi-generator` will understand them. To do so, -create a JS file with the following format: - -```typescript -module.exports = (E) => { - return { - 'io-ts-bigint': { - BigIntFromString: () => E.right({ type: 'string' }), - NonZeroBigInt: () => E.right({ type: 'number' }), - NonZeroBigIntFromString: () => E.right({ type: 'string' }), - NegativeBigIntFromString: () => E.right({ type: 'string' }), - NonNegativeBigIntFromString: () => E.right({ type: 'string' }), - PositiveBigIntFromString: () => E.right({ type: 'string' }), - }, - // ... and so on for other packages - }; -}; -``` +## Preparing a types package for reusable codecs + +In order to use types from external `io-ts` types packages, you must ensure two things +are done. + +1. The package source code must be included in the bundle, as the generator is built to + generate specs based from the Typescript AST. It is not set up to work with + transpiled js code. You can do this by modifying your `package.json` to include your + source code in the bundle. For example, if the source code is present in the `src/` + directory, then add `src/` to the files array in the `package.json` of your project. +2. After Step 1, change the `types` field in the `package.json` to be the entry point of + the types in the source code. For example, if the entrypoint is `src/index.ts`, then + set `"types": "src/index.ts"` in the `package.json` + +## Defining Custom Codecs + +When working with `openapi-generator`, you may encounter challenges with handling custom +codecs that require JavaScript interpretation or aren't natively supported by the +generator. These issues typically arise with codecs such as `new t.Type(...)` and other +primitives that aren't directly supported. However, there are two solutions to address +these challenges effectively. Click [here](#list-of-supported-io-ts-primitives) for the +list of supported primitives. + +### Solution 1: Using a Custom Codec Configuration File + +`openapi-generator` supports importing codecs from other packages in `node_modules`, but +it struggles with primitives that need JavaScript interpretation, such as +`new t.Type(...)`. To work around this, you can define schemas for these codecs in a +configuration file within your downstream types package (where you generate the API +docs). This allows the generator to understand and use these schemas where necessary. +Follow these steps to create and use a custom codec configuration file: + +1. Create a JavaScript file with the following format: + + ```javascript + module.exports = (E) => { + return { + 'io-ts-bigint': { + BigIntFromString: () => E.right({ type: 'string' }), + NonZeroBigInt: () => E.right({ type: 'number' }), + NonZeroBigIntFromString: () => E.right({ type: 'string' }), + NegativeBigIntFromString: () => E.right({ type: 'string' }), + NonNegativeBigIntFromString: () => E.right({ type: 'string' }), + PositiveBigIntFromString: () => E.right({ type: 'string' }), + }, + // ... and so on for other packages + }; + }; + ``` + +2. The input parameter `E` is the namespace import of `fp-ts/Either`, which avoids + issues with `require`. The return type should be a `Record` containing AST + definitions for external libraries. For more information on the structure, refer to + [KNOWN_IMPORTS](./src/knownImports.ts). + +### Solution 2: Defining Custom Codec Schemas in the Types Package (recommended) + +`openapi-generator` now offers the ability to define the schema of custom codecs +directly within the types package that defines them, rather than the downstream package +that uses them. This approach is particularly useful for codecs that are used in many +different types packages. Here’s how you can define schemas for your custom codecs in +the upstream repository: + +1. Create a file named `openapi-gen.config.js` in the root of your repository. + +2. Add the following line to the `package.json` of the types package: + + ```json + "customCodecFile": "openapi-gen.config.js" + ``` + +3. In the `openapi-gen.config.js` file, define your custom codecs: + + ```javascript + module.exports = (E) => { + return { + SampleCodecDefinition: () => + E.right({ + type: 'string', + default: 'defaultString', + minLength: 1, + }), + // ... rest of your custom codec definitions + }; + }; + ``` + +By following these steps, the schemas for your custom codecs will be included in the +generated API docs for any endpoints that use the respective codecs. The input parameter +`E` is the namespace import of `fp-ts/Either`, and the return type should be a `Record` +containing AST definitions for external libraries. For more details, see +[KNOWN_IMPORTS](./src/knownImports.ts). + +## List of supported io-ts primitives -The input parameter `E` is the namespace import of `fp-ts/Either` (so that trying to -`require` it from the config file isn't an issue), and the return type is a `Record` -containing AST definitions for external libraries. -[Refer to KNOWN_IMPORTS here for info on the structure](./src/knownImports.ts) +- string +- number +- bigint +- boolean +- null +- nullType +- undefined +- unknown +- any +- array +- readonlyArray +- object +- type +- partial +- exact +- strict +- record +- union +- intersection +- literal +- keyof +- brand +- UnknownRecord +- void From deba7c47307961b087399e537cf7f42976e9adb1 Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 13:15:27 -0400 Subject: [PATCH 03/10] refactor: use new log functions DX-658 --- packages/openapi-generator/src/cli.ts | 8 ++++---- packages/openapi-generator/src/project.ts | 6 +++--- packages/openapi-generator/src/sourceFile.ts | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/openapi-generator/src/cli.ts b/packages/openapi-generator/src/cli.ts index dfd061ef..413a292e 100644 --- a/packages/openapi-generator/src/cli.ts +++ b/packages/openapi-generator/src/cli.ts @@ -171,8 +171,8 @@ const app = command({ const initE = findSymbolInitializer(project.right, sourceFile, ref.name); if (E.isLeft(initE)) { - console.error( - `[ERROR] Could not find symbol '${ref.name}' in '${ref.location}': ${initE.left}`, + logError( + `Could not find symbol '${ref.name}' in '${ref.location}': ${initE.left}`, ); process.exit(1); } @@ -180,8 +180,8 @@ const app = command({ const codecE = parseCodecInitializer(project.right, newSourceFile, init); if (E.isLeft(codecE)) { - console.error( - `[ERROR] Could not parse codec '${ref.name}' in '${ref.location}': ${codecE.left}`, + logError( + `Could not parse codec '${ref.name}' in '${ref.location}': ${codecE.left}`, ); process.exit(1); } diff --git a/packages/openapi-generator/src/project.ts b/packages/openapi-generator/src/project.ts index f464ee50..83ef079f 100644 --- a/packages/openapi-generator/src/project.ts +++ b/packages/openapi-generator/src/project.ts @@ -6,7 +6,7 @@ import resolve from 'resolve'; import { KNOWN_IMPORTS, type KnownCodec } from './knownImports'; import { parseSource, type SourceFile } from './sourceFile'; -import { errorLeft } from './error'; +import { errorLeft, logError, logInfo } from './error'; const readFile = promisify(fs.readFile); @@ -177,7 +177,7 @@ export class Project { ); const module = await import(customCodecPath); if (module.default === undefined) { - console.error(`Could not find default export in ${customCodecPath}`); + logError(`Could not find default export in ${customCodecPath}`); return; } @@ -187,7 +187,7 @@ export class Project { ...customCodecs, }; - console.error(`Loaded custom codecs for ${packageName}`); + logInfo(`Loaded custom codecs for ${packageName}`); } } catch (e) { return; diff --git a/packages/openapi-generator/src/sourceFile.ts b/packages/openapi-generator/src/sourceFile.ts index 9a6c78d5..ad8807d8 100644 --- a/packages/openapi-generator/src/sourceFile.ts +++ b/packages/openapi-generator/src/sourceFile.ts @@ -1,6 +1,7 @@ import * as swc from '@swc/core'; import { parseTopLevelSymbols, type SymbolTable } from './symbol'; +import { logError } from './error'; export type SourceFile = { path: string; @@ -41,7 +42,7 @@ export async function parseSource( span: module.span, }; } catch (e: unknown) { - console.error(`Error parsing source file: ${path}`, e); + logError(`Error parsing source file: ${path}, ${e}`); return undefined; } } From ef7576fe288fa66a5405d024c4fe74e678369eb3 Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 13:48:55 -0400 Subject: [PATCH 04/10] fix: reorder known codecs rewrite DX-658 --- packages/openapi-generator/src/project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-generator/src/project.ts b/packages/openapi-generator/src/project.ts index 83ef079f..df6b30fb 100644 --- a/packages/openapi-generator/src/project.ts +++ b/packages/openapi-generator/src/project.ts @@ -183,8 +183,8 @@ export class Project { const customCodecs = module.default(E); this.knownImports[packageName] = { - ...this.knownImports[packageName], ...customCodecs, + ...this.knownImports[packageName], }; logInfo(`Loaded custom codecs for ${packageName}`); From f3d0be173cdc3082a86c805ec1dc1d530cb5be03 Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 14:12:29 -0400 Subject: [PATCH 05/10] fix: remove extra info comment DX-658 --- packages/openapi-generator/src/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-generator/src/cli.ts b/packages/openapi-generator/src/cli.ts index 413a292e..2b45fdb0 100644 --- a/packages/openapi-generator/src/cli.ts +++ b/packages/openapi-generator/src/cli.ts @@ -125,7 +125,7 @@ const app = command({ } else if (!isApiSpec(entryPoint, symbol.init.callee)) { continue; } - logInfo(`[INFO] Found API spec in ${symbol.name}`); + logInfo(`Found API spec in ${symbol.name}`); const result = parseApiSpec( project.right, From 6b7d7feb8b400a349b7a2fda571329871d7b09ec Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 14:12:43 -0400 Subject: [PATCH 06/10] docs: add more required documentation --- packages/openapi-generator/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/openapi-generator/README.md b/packages/openapi-generator/README.md index 60a10bec..7f4f466c 100644 --- a/packages/openapi-generator/README.md +++ b/packages/openapi-generator/README.md @@ -106,6 +106,9 @@ the upstream repository: "customCodecFile": "openapi-gen.config.js" ``` + You must also add `"openapi-gen.config.js"` to the files field in the package.json, + so that it is included in the final bundle. + 3. In the `openapi-gen.config.js` file, define your custom codecs: ```javascript From bf2aae88986bf0e03f43dd92c212bb7f725e5f9c Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 14:41:14 -0400 Subject: [PATCH 07/10] docs: reorder solutions DX-658 --- packages/openapi-generator/README.md | 66 ++++++++++++++-------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/openapi-generator/README.md b/packages/openapi-generator/README.md index 7f4f466c..2d296808 100644 --- a/packages/openapi-generator/README.md +++ b/packages/openapi-generator/README.md @@ -58,39 +58,7 @@ primitives that aren't directly supported. However, there are two solutions to a these challenges effectively. Click [here](#list-of-supported-io-ts-primitives) for the list of supported primitives. -### Solution 1: Using a Custom Codec Configuration File - -`openapi-generator` supports importing codecs from other packages in `node_modules`, but -it struggles with primitives that need JavaScript interpretation, such as -`new t.Type(...)`. To work around this, you can define schemas for these codecs in a -configuration file within your downstream types package (where you generate the API -docs). This allows the generator to understand and use these schemas where necessary. -Follow these steps to create and use a custom codec configuration file: - -1. Create a JavaScript file with the following format: - - ```javascript - module.exports = (E) => { - return { - 'io-ts-bigint': { - BigIntFromString: () => E.right({ type: 'string' }), - NonZeroBigInt: () => E.right({ type: 'number' }), - NonZeroBigIntFromString: () => E.right({ type: 'string' }), - NegativeBigIntFromString: () => E.right({ type: 'string' }), - NonNegativeBigIntFromString: () => E.right({ type: 'string' }), - PositiveBigIntFromString: () => E.right({ type: 'string' }), - }, - // ... and so on for other packages - }; - }; - ``` - -2. The input parameter `E` is the namespace import of `fp-ts/Either`, which avoids - issues with `require`. The return type should be a `Record` containing AST - definitions for external libraries. For more information on the structure, refer to - [KNOWN_IMPORTS](./src/knownImports.ts). - -### Solution 2: Defining Custom Codec Schemas in the Types Package (recommended) +### Solution 1: Defining Custom Codec Schemas in the Types Package (recommended) `openapi-generator` now offers the ability to define the schema of custom codecs directly within the types package that defines them, rather than the downstream package @@ -131,6 +99,38 @@ generated API docs for any endpoints that use the respective codecs. The input p containing AST definitions for external libraries. For more details, see [KNOWN_IMPORTS](./src/knownImports.ts). +### Solution 2: Using a Custom Codec Configuration File + +`openapi-generator` supports importing codecs from other packages in `node_modules`, but +it struggles with `io-ts` primitives that need JavaScript interpretation, such as +`new t.Type(...)`. To work around this, you can define schemas for these codecs in a +configuration file within your downstream types package (where you generate the API +docs). This allows the generator to understand and use these schemas where necessary. +Follow these steps to create and use a custom codec configuration file: + +1. Create a JavaScript file with the following format: + + ```javascript + module.exports = (E) => { + return { + 'io-ts-bigint': { + BigIntFromString: () => E.right({ type: 'string' }), + NonZeroBigInt: () => E.right({ type: 'number' }), + NonZeroBigIntFromString: () => E.right({ type: 'string' }), + NegativeBigIntFromString: () => E.right({ type: 'string' }), + NonNegativeBigIntFromString: () => E.right({ type: 'string' }), + PositiveBigIntFromString: () => E.right({ type: 'string' }), + }, + // ... and so on for other packages + }; + }; + ``` + +2. The input parameter `E` is the namespace import of `fp-ts/Either`, which avoids + issues with `require`. The return type should be a `Record` containing AST + definitions for external libraries. For more information on the structure, refer to + [KNOWN_IMPORTS](./src/knownImports.ts). + ## List of supported io-ts primitives - string From 443b16fc12a1838d4dca89dd722f5b3974237a8b Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 14:51:45 -0400 Subject: [PATCH 08/10] refactor: logWarn when can't parse sourceFile DX-658 --- packages/openapi-generator/README.md | 2 +- packages/openapi-generator/src/sourceFile.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/openapi-generator/README.md b/packages/openapi-generator/README.md index 2d296808..e8a0640b 100644 --- a/packages/openapi-generator/README.md +++ b/packages/openapi-generator/README.md @@ -102,7 +102,7 @@ containing AST definitions for external libraries. For more details, see ### Solution 2: Using a Custom Codec Configuration File `openapi-generator` supports importing codecs from other packages in `node_modules`, but -it struggles with `io-ts` primitives that need JavaScript interpretation, such as +it struggles with `io-ts`primitives that need JavaScript interpretation, such as `new t.Type(...)`. To work around this, you can define schemas for these codecs in a configuration file within your downstream types package (where you generate the API docs). This allows the generator to understand and use these schemas where necessary. diff --git a/packages/openapi-generator/src/sourceFile.ts b/packages/openapi-generator/src/sourceFile.ts index ad8807d8..4c8c6ff0 100644 --- a/packages/openapi-generator/src/sourceFile.ts +++ b/packages/openapi-generator/src/sourceFile.ts @@ -1,7 +1,7 @@ import * as swc from '@swc/core'; import { parseTopLevelSymbols, type SymbolTable } from './symbol'; -import { logError } from './error'; +import { logWarn } from './error'; export type SourceFile = { path: string; @@ -42,7 +42,7 @@ export async function parseSource( span: module.span, }; } catch (e: unknown) { - logError(`Error parsing source file: ${path}, ${e}`); + logWarn(`Error parsing source file: ${path}, ${e}`); return undefined; } } From 4a569b8b81f7ca57660efd14ef262553e0865e13 Mon Sep 17 00:00:00 2001 From: Aryaman Dhingra Date: Thu, 8 Aug 2024 15:17:34 -0400 Subject: [PATCH 09/10] refactor: refactor custom codecs function to return a either monad DX-658 --- packages/openapi-generator/src/project.ts | 75 ++++++++++++++--------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/packages/openapi-generator/src/project.ts b/packages/openapi-generator/src/project.ts index df6b30fb..788b8e4f 100644 --- a/packages/openapi-generator/src/project.ts +++ b/packages/openapi-generator/src/project.ts @@ -6,7 +6,7 @@ import resolve from 'resolve'; import { KNOWN_IMPORTS, type KnownCodec } from './knownImports'; import { parseSource, type SourceFile } from './sourceFile'; -import { errorLeft, logError, logInfo } from './error'; +import { errorLeft, logInfo } from './error'; const readFile = promisify(fs.readFile); @@ -63,7 +63,19 @@ export class Project { if (!visitedPackages.has(sym.from)) { // This is a step that checks if this import has custom codecs, and loads them into known imports - await this.populateCustomCodecs(baseDir, sym.from); + const codecs = await this.getCustomCodecs(baseDir, sym.from); + if (E.isLeft(codecs)) { + return codecs; + } + + if (Object.keys(codecs.right).length > 0) { + this.knownImports[sym.from] = { + ...codecs.right, + ...this.knownImports[sym.from], + }; + + logInfo(`Loaded custom codecs for ${sym.from}`); + } } visitedPackages.add(sym.from); @@ -158,39 +170,44 @@ export class Project { return this.types; } - private async populateCustomCodecs(basedir: string, packageName: string) { + private async getCustomCodecs( + basedir: string, + packageName: string, + ): Promise>> { + let packageJsonPath = ''; + try { - const packageJson = resolve.sync(`${packageName}/package.json`, { + packageJsonPath = resolve.sync(`${packageName}/package.json`, { basedir, extensions: ['.json'], }); - const packageInfo = JSON.parse(fs.readFileSync(packageJson, 'utf8')); - - if (packageInfo['customCodecFile']) { - // The package defines their own custom codecs - const customCodecPath = resolve.sync( - `${packageName}/${packageInfo['customCodecFile']}`, - { - basedir, - extensions: ['.ts', '.js'], - }, - ); - const module = await import(customCodecPath); - if (module.default === undefined) { - logError(`Could not find default export in ${customCodecPath}`); - return; - } - - const customCodecs = module.default(E); - this.knownImports[packageName] = { - ...customCodecs, - ...this.knownImports[packageName], - }; + } catch (e) { + // This should not lead to the failure of the entire project, so return an empty record + return E.right({}); + } - logInfo(`Loaded custom codecs for ${packageName}`); + const packageInfo = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + if (packageInfo['customCodecFile']) { + // The package defines their own custom codecs + const customCodecPath = resolve.sync( + `${packageName}/${packageInfo['customCodecFile']}`, + { + basedir, + extensions: ['.ts', '.js'], + }, + ); + + const module = await import(customCodecPath); + if (module.default === undefined) { + // Package does not have a default export so we can't use it. Format of the custom codec file is incorrect + return errorLeft(`Could not find default export in ${customCodecPath}`); } - } catch (e) { - return; + + const customCodecs = module.default(E); + return E.right(customCodecs); } + + return E.right({}); } } From 348be34e9188c69a0bc25deca1439a15e0e400e2 Mon Sep 17 00:00:00 2001 From: "Aryaman D." <67097683+ad-world@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:14:05 -0400 Subject: [PATCH 10/10] Update packages/openapi-generator/README.md Co-authored-by: Ansh Chaturvedi <45408169+anshchaturvedi@users.noreply.github.com> --- packages/openapi-generator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-generator/README.md b/packages/openapi-generator/README.md index e8a0640b..2d296808 100644 --- a/packages/openapi-generator/README.md +++ b/packages/openapi-generator/README.md @@ -102,7 +102,7 @@ containing AST definitions for external libraries. For more details, see ### Solution 2: Using a Custom Codec Configuration File `openapi-generator` supports importing codecs from other packages in `node_modules`, but -it struggles with `io-ts`primitives that need JavaScript interpretation, such as +it struggles with `io-ts` primitives that need JavaScript interpretation, such as `new t.Type(...)`. To work around this, you can define schemas for these codecs in a configuration file within your downstream types package (where you generate the API docs). This allows the generator to understand and use these schemas where necessary.