Skip to content

Commit 16535cb

Browse files
committed
feat: allow overriding internal custom codecs
1 parent fe2967e commit 16535cb

File tree

4 files changed

+106
-5
lines changed

4 files changed

+106
-5
lines changed

packages/openapi-generator/src/codec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function codecIdentifier(
3232
} else if (imp.type === 'star') {
3333
return E.left(`Tried to use star import as codec ${id.value}`);
3434
}
35-
const knownImport = project.knownImports[imp.from]?.[imp.importedName];
35+
const knownImport = project.resolveKnownImport(imp.from, imp.importedName);
3636
if (knownImport !== undefined) {
3737
return E.right({ type: 'codec', schema: knownImport });
3838
}
@@ -67,7 +67,7 @@ function codecIdentifier(
6767
}
6868

6969
const name = id.property.value;
70-
const knownImport = project.knownImports[objectSym.from]?.[name];
70+
const knownImport = project.resolveKnownImport(objectSym.from, name);
7171
if (knownImport !== undefined) {
7272
return E.right({ type: 'codec', schema: knownImport });
7373
}

packages/openapi-generator/src/project.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { parseSource, type SourceFile } from './sourceFile';
1010
const readFile = promisify(fs.readFile);
1111

1212
export class Project {
13-
readonly knownImports: Record<string, Record<string, KnownCodec>>;
13+
private readonly knownImports: Record<string, Record<string, KnownCodec>>;
1414

1515
private files: Record<string, SourceFile>;
1616

@@ -90,4 +90,9 @@ export class Project {
9090
}
9191
}
9292
}
93+
94+
resolveKnownImport(path: string, name: string): KnownCodec | undefined {
95+
const baseKey = path.startsWith('.') ? '.' : path;
96+
return this.knownImports[baseKey]?.[name];
97+
}
9398
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import * as E from 'fp-ts/lib/Either';
2+
import assert from 'node:assert';
3+
import test from 'node:test';
4+
5+
import { TestProject } from './testProject';
6+
import { parsePlainInitializer, type Schema } from '../src';
7+
import { KNOWN_IMPORTS, type KnownImports } from '../src/knownImports';
8+
9+
async function testCase(
10+
description: string,
11+
src: string,
12+
knownImports: KnownImports,
13+
expected: Record<string, Schema>,
14+
expectedErrors: string[] = [],
15+
) {
16+
test(description, async () => {
17+
const project = new TestProject(
18+
{ '/index.ts': src },
19+
{ ...KNOWN_IMPORTS, ...knownImports },
20+
);
21+
await project.parseEntryPoint('/index.ts');
22+
const sourceFile = project.get('/index.ts');
23+
if (sourceFile === undefined) {
24+
throw new Error('Source file not found');
25+
}
26+
27+
const actual: Record<string, Schema> = {};
28+
const errors: string[] = [];
29+
for (const symbol of sourceFile.symbols.declarations) {
30+
if (symbol.init !== undefined) {
31+
const result = parsePlainInitializer(project, sourceFile, symbol.init);
32+
if (E.isLeft(result)) {
33+
errors.push(result.left);
34+
} else {
35+
if (symbol.comment !== undefined) {
36+
result.right.comment = symbol.comment;
37+
}
38+
actual[symbol.name] = result.right;
39+
}
40+
}
41+
}
42+
43+
assert.deepStrictEqual(errors, expectedErrors);
44+
assert.deepStrictEqual(actual, expected);
45+
});
46+
}
47+
48+
const EXTERNAL_CUSTOM_CODEC: KnownImports = {
49+
foo: {
50+
bar: () => E.right({ type: 'primitive', value: 'string' }),
51+
},
52+
};
53+
54+
const EXTERNAL_CUSTOM_CODEC_SRC = `
55+
import * as f from 'foo';
56+
57+
export const FOO = f.bar;
58+
`;
59+
60+
testCase(
61+
'External custom codecs are parsed',
62+
EXTERNAL_CUSTOM_CODEC_SRC,
63+
EXTERNAL_CUSTOM_CODEC,
64+
{
65+
FOO: { type: 'primitive', value: 'string' },
66+
},
67+
);
68+
69+
const INTERNAL_CODEC_OVERRIDE: KnownImports = {
70+
'.': {
71+
bar: () => E.right({ type: 'primitive', value: 'string' }),
72+
},
73+
};
74+
75+
const INTERNAL_CODEC_OVERRIDE_SRC = `
76+
import * as t from 'io-ts';
77+
import { bar } from './bar';
78+
79+
export const FOO = t.type({ bar: bar });
80+
`;
81+
82+
testCase(
83+
'Internal codec overrides are parsed',
84+
INTERNAL_CODEC_OVERRIDE_SRC,
85+
INTERNAL_CODEC_OVERRIDE,
86+
{
87+
FOO: {
88+
type: 'object',
89+
properties: {
90+
bar: { type: 'primitive', value: 'string' },
91+
},
92+
required: ['bar'],
93+
},
94+
},
95+
);

packages/openapi-generator/test/testProject.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import resolve from 'resolve';
44
import { promisify } from 'util';
55

66
import { Project } from '../src';
7+
import type { KnownImports } from '../src/knownImports';
78

89
export class TestProject extends Project {
910
private volume: ReturnType<(typeof Volume)['fromJSON']>;
1011

11-
constructor(files: NestedDirectoryJSON) {
12-
super();
12+
constructor(files: NestedDirectoryJSON, knownImports?: KnownImports) {
13+
super({}, knownImports);
1314
this.volume = Volume.fromNestedJSON(files, '/');
1415
}
1516

0 commit comments

Comments
 (0)