diff --git a/packages/openapi-generator/src/codec.ts b/packages/openapi-generator/src/codec.ts index 79e39dda..2bcce2d3 100644 --- a/packages/openapi-generator/src/codec.ts +++ b/packages/openapi-generator/src/codec.ts @@ -327,6 +327,65 @@ function parseArrayExpression( return E.right({ type: 'tuple', schemas: result }); } +function parseTemplateLiteral( + project: Project, + source: SourceFile, + template: swc.TemplateLiteral, +): E.Either { + const expressions: E.Either[] = template.expressions.map((expr) => + parsePlainInitializer(project, source, expr), + ); + + return pipe( + expressions, + E.sequenceArray, + E.flatMap((schemas) => { + const literals: string[] = []; + for (const schema of schemas) { + if ( + schema.type === 'string' && + schema.enum !== undefined && + schema.enum.length === 1 + ) { + literals.push(String(schema.enum[0])); + } else if (schema.type === 'ref') { + const realInitE = findSymbolInitializer(project, source, schema.name); + if (E.isLeft(realInitE)) { + return realInitE; + } + const schemaE = parsePlainInitializer( + project, + realInitE.right[0], + realInitE.right[1], + ); + if (E.isLeft(schemaE)) { + return schemaE; + } + if (schemaE.right.type !== 'string' || schemaE.right.enum === undefined) { + return errorLeft('Template element must be string literal'); + } + literals.push(String(schemaE.right.enum[0])); + } + } + + const quasis = template.quasis.map((quasi) => quasi.cooked); + const result = quasis.reduce((acc, quasi, index) => { + if (index < literals.length) { + acc.push(quasi ?? '', literals[index]!); + } else { + acc.push(quasi ?? ''); + } + return acc; + }, [] as (string | Schema)[]); + + return E.right({ + type: 'string', + enum: [result.join('')], + }); + }), + ); +} + export function parsePlainInitializer( project: Project, source: SourceFile, @@ -348,6 +407,8 @@ export function parsePlainInitializer( return E.right({ type: 'undefined' }); } else if (init.type === 'TsConstAssertion' || init.type === 'TsAsExpression') { return parsePlainInitializer(project, source, init.expression); + } else if (init.type === 'TemplateLiteral') { + return parseTemplateLiteral(project, source, init); } else if ( init.type === 'Identifier' || init.type === 'MemberExpression' || diff --git a/packages/openapi-generator/test/codec.test.ts b/packages/openapi-generator/test/codec.test.ts index 6955c7d3..afe505a0 100644 --- a/packages/openapi-generator/test/codec.test.ts +++ b/packages/openapi-generator/test/codec.test.ts @@ -870,3 +870,42 @@ testCase('computed property is parsed', COMPUTED_PROPERTY, { enum: ['foo'], } }); + +const TEMPLATE_LITERAL = ` +import * as t from 'io-ts'; +const test = 'foo'; +export const FOO = \`\${test}bar\`; +`; + +testCase('basic template literal is parsed', TEMPLATE_LITERAL, { + FOO: { + type: 'string', + enum: ['foobar'], + }, + test: { + type: 'string', + enum: ['foo'], + }, +}); + +const MULTI_TEMPLATE_LITERAL = ` +import * as t from 'io-ts'; +const test = 'foo'; +const test2 = 'baz'; +export const FOO = \`aaa\${test}bar\${test2}bat\`; +`; + +testCase('compound template literal is parsed', MULTI_TEMPLATE_LITERAL, { + FOO: { + type: 'string', + enum: ['aaafoobarbazbat'], + }, + test: { + type: 'string', + enum: ['foo'], + }, + test2: { + type: 'string', + enum: ['baz'], + }, +});