diff --git a/README.md b/README.md index 1943142d..5ea65753 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ import * as swaggerUi from 'swagger-ui-express' import { prefix } from 'typera-openapi' import myRoutes from './my-routes' -import myRouteDefs from './my-routes.openapi' +import openapi from './openapi' const openapiDoc: OpenAPIV3.Document = { openapi: '3.0.0', @@ -91,9 +91,7 @@ const openapiDoc: OpenAPIV3.Document = { title: 'My cool API', version: '0.1.0', }, - paths: { - ...prefix('/api', myRouteDefs.paths), - }, + ...openapi, } const app = express() @@ -102,9 +100,6 @@ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openapiDoc)) app.listen(3000) ``` -The `prefix` function is used to move OpenAPI path definitions to a different -prefix, because the `myRoutes` are served from the `/api` prefix. - ## CLI ``` diff --git a/src/cli.ts b/src/cli.ts index 741a2711..c7f21848 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,11 +1,9 @@ -#!/usr/bin/env node import * as fs from 'fs' import * as path from 'path' -import { OpenAPIV3 } from 'openapi-types' import * as ts from 'typescript' import * as yargs from 'yargs' -import { generate } from '.' +import { GenerateResult, generate } from '.' import { Logger } from './context' import { runPrettier } from './prettify' @@ -22,10 +20,15 @@ const parseArgs = () => type: 'boolean', default: false, }) - .option('format', { - description: 'Output file format', - choices: ['ts' as const, 'json' as const], - default: 'ts' as Format, + .option('outfile', { + alias: 'o', + description: 'Output file. Must end in `.ts` or `.json`.', + default: 'openapi.ts', + coerce: (arg: string): [string, Format] => { + if (arg.endsWith('.ts')) return [arg, 'ts'] + if (arg.endsWith('.json')) return [arg, 'json'] + throw new Error('outfile must end in `.ts` or `.json`') + }, }) .option('tsconfig', { description: 'Which tsconfig.json to use', @@ -33,26 +36,23 @@ const parseArgs = () => }) .option('prettify', { alias: 'p', - description: 'Apply prettier to output files', + description: 'Apply prettier to the output file', type: 'boolean', default: false, }) .option('check', { alias: 'c', description: - 'Exit with an error if output files are not up-to-date (useful for CI)', + 'Exit with an error if the output file is not up-to-date (useful for CI)', type: 'boolean', default: false, }).argv -const outputFileName = (sourceFileName: string, ext: string): string => - sourceFileName.slice(0, -path.extname(sourceFileName).length) + ext - -const main = async () => { +const main = async (): Promise => { const args = parseArgs() const sourceFiles = args._.map((x) => path.resolve(x.toString())) - const ext = `.openapi.${args.format}` + const [outfile, format] = args.outfile const compilerOptions = readCompilerOptions(args.tsconfig) if (!compilerOptions) process.exit(1) @@ -61,32 +61,18 @@ const main = async () => { console.log(`Compiler options: ${JSON.stringify(compilerOptions, null, 2)}`) } - const results = generate(sourceFiles, compilerOptions, { - log, - }).map((result) => ({ - ...result, - outputFileName: outputFileName(result.fileName, ext), - })) - - let success = true - for (const { outputFileName, paths, components } of results) { - let content = - args.format === 'ts' - ? tsString(paths, components) - : jsonString(paths, components) - if (args.prettify) { - content = await runPrettier(outputFileName, content) - } - if (args.check) { - if (!checkOutput(outputFileName, content)) success = false - } else { - writeOutput(outputFileName, content) - } + const result = generate(sourceFiles, compilerOptions, { log }) + let content = format === 'ts' ? tsString(result) : jsonString(result) + if (args.prettify) { + content = await runPrettier(outfile, content) } - - if (!success) { - process.exit(1) + if (args.check) { + if (!checkOutput(outfile, content)) return 1 + } else { + writeOutput(outfile, content) } + + return 0 } const readCompilerOptions = ( @@ -136,22 +122,18 @@ const writeOutput = (fileName: string, content: string): void => { fs.writeFileSync(fileName, content) } -const tsString = ( - paths: OpenAPIV3.PathsObject, - components: OpenAPIV3.ComponentsObject -): string => `\ +const tsString = (result: GenerateResult): string => `\ import { OpenAPIV3 } from 'openapi-types' const spec: { paths: OpenAPIV3.PathsObject, components: OpenAPIV3.ComponentsObject } = ${JSON.stringify( - { paths, components } + result )}; export default spec; ` -const jsonString = ( - paths: OpenAPIV3.PathsObject, - components: OpenAPIV3.ComponentsObject -): string => JSON.stringify({ paths, components }) +const jsonString = (result: GenerateResult): string => JSON.stringify(result) -main() +main().then((status) => { + if (status !== 0) process.exit(status) +}) diff --git a/src/generate.ts b/src/generate.ts index a1802802..60399bc0 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -27,8 +27,7 @@ interface GenerateOptions { log: Logger } -interface Result { - fileName: string +export interface GenerateResult { paths: OpenAPIV3.PathsObject components: OpenAPIV3.ComponentsObject } @@ -37,35 +36,32 @@ export const generate = ( fileNames: string[], compilerOptions: ts.CompilerOptions, options?: GenerateOptions -): Result[] => { +): GenerateResult => { const log = options?.log || (() => undefined) const program = ts.createProgram(fileNames, compilerOptions) const checker = program.getTypeChecker() - const result: Result[] = [] + const components = new Components() + let paths: OpenAPIV3.PathsObject = {} for (const sourceFile of program.getSourceFiles()) { if (!fileNames.includes(sourceFile.fileName)) continue if (sourceFile.isDeclarationFile) continue ts.forEachChild(sourceFile, (node) => { - const components = new Components() - const paths = visitTopLevelNode( + const newPaths = visitTopLevelNode( context(checker, sourceFile, log, node), components, node ) - if (paths) { - result.push({ - fileName: sourceFile.fileName, - paths, - components: components.build(), - }) + if (newPaths) { + // TODO: What if a route is defined multiple times? + paths = { ...paths, ...newPaths } } }) } - return result + return { paths, components: components.build() } } const visitTopLevelNode = ( diff --git a/src/index.ts b/src/index.ts index 561a2698..eb851fc5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { generate } from './generate' +export { GenerateResult, generate } from './generate' export { LogLevel } from './context' import { OpenAPIV3 } from 'openapi-types' diff --git a/tests/__snapshots__/generate.spec.ts.snap b/tests/__snapshots__/generate.spec.ts.snap index 64be5ee9..75733dde 100644 --- a/tests/__snapshots__/generate.spec.ts.snap +++ b/tests/__snapshots__/generate.spec.ts.snap @@ -11,277 +11,226 @@ Array [ `; exports[`generate works 1`] = ` -Array [ - Object { - "components": Object { - "schemas": Object { - "DirectRecursiveIntersection": Object { - "properties": Object { - "children": Object { - "$ref": "#/components/schemas/DirectRecursiveIntersection", - }, - "id": Object { - "type": "string", - }, +Object { + "components": Object { + "schemas": Object { + "DirectRecursiveIntersection": Object { + "properties": Object { + "children": Object { + "$ref": "#/components/schemas/DirectRecursiveIntersection", + }, + "id": Object { + "type": "string", }, - "required": Array [ - "id", - "children", - ], - "type": "object", }, - "DirectRecursiveType": Object { - "properties": Object { - "children": Object { - "items": Object { - "$ref": "#/components/schemas/DirectRecursiveType", - }, - "type": "array", - }, - "id": Object { - "type": "string", + "required": Array [ + "id", + "children", + ], + "type": "object", + }, + "DirectRecursiveType": Object { + "properties": Object { + "children": Object { + "items": Object { + "$ref": "#/components/schemas/DirectRecursiveType", }, + "type": "array", + }, + "id": Object { + "type": "string", }, - "required": Array [ - "id", - "children", - ], - "type": "object", }, - "DocumentedInterface": Object { - "properties": Object { - "outputField": Object { - "description": "Output field description here", - "type": "string", - }, + "required": Array [ + "id", + "children", + ], + "type": "object", + }, + "DocumentedInterface": Object { + "properties": Object { + "outputField": Object { + "description": "Output field description here", + "type": "string", }, - "required": Array [ - "outputField", - ], - "type": "object", }, - "IndirectRecursiveType": Object { - "properties": Object { - "hello": Object { - "type": "string", - }, + "required": Array [ + "outputField", + ], + "type": "object", + }, + "IndirectRecursiveType": Object { + "properties": Object { + "hello": Object { + "type": "string", + }, + "items": Object { "items": Object { - "items": Object { - "$ref": "#/components/schemas/MutuallyRecursive", - }, - "type": "array", + "$ref": "#/components/schemas/MutuallyRecursive", }, + "type": "array", }, - "required": Array [ - "hello", - "items", - ], - "type": "object", }, - "MutuallyRecursive": Object { - "properties": Object { - "other": Object { - "$ref": "#/components/schemas/IndirectRecursiveType", - }, + "required": Array [ + "hello", + "items", + ], + "type": "object", + }, + "MutuallyRecursive": Object { + "properties": Object { + "other": Object { + "$ref": "#/components/schemas/IndirectRecursiveType", }, - "required": Array [ - "other", - ], - "type": "object", }, - "User": Object { - "properties": Object { - "petName": Object { - "nullable": true, - "type": "string", - }, - "shoeSize": Object { - "type": "number", - }, - "updated": Object { - "format": "date-time", - "type": "string", - }, + "required": Array [ + "other", + ], + "type": "object", + }, + "User": Object { + "properties": Object { + "petName": Object { + "nullable": true, + "type": "string", }, - "required": Array [ - "shoeSize", - "petName", - "updated", - ], - "type": "object", - }, - "User2": Object { - "properties": Object { - "name": Object { - "type": "string", - }, + "shoeSize": Object { + "type": "number", + }, + "updated": Object { + "format": "date-time", + "type": "string", }, - "required": Array [ - "name", - ], - "type": "object", }, + "required": Array [ + "shoeSize", + "petName", + "updated", + ], + "type": "object", }, - }, - "fileName": "tests/test-routes.ts", - "paths": Object { - "/binary-response": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "application/octet-stream": Object { - "schema": Object { - "format": "binary", - "type": "string", - }, - }, - }, - "description": "OK", - }, + "User2": Object { + "properties": Object { + "name": Object { + "type": "string", }, }, + "required": Array [ + "name", + ], + "type": "object", }, - "/branded-request-body": Object { - "post": Object { - "requestBody": Object { + }, + }, + "paths": Object { + "/binary-response": Object { + "get": Object { + "responses": Object { + "200": Object { "content": Object { - "application/json": Object { + "application/octet-stream": Object { "schema": Object { - "properties": Object { - "int": Object { - "type": "integer", - }, - "nonEmptyString": Object { - "type": "string", - }, - }, - "required": Array [ - "int", - "nonEmptyString", - ], - "type": "object", + "format": "binary", + "type": "string", }, }, }, - }, - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "number", - }, - }, - }, - "description": "OK", - }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, - }, - }, - "description": "Bad Request", - }, + "description": "OK", }, }, }, - "/constant": Object { - "get": Object { - "description": "No input, static output, has a tag", - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { + }, + "/branded-request-body": Object { + "post": Object { + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "properties": Object { + "int": Object { + "type": "integer", + }, + "nonEmptyString": Object { "type": "string", }, }, + "required": Array [ + "int", + "nonEmptyString", + ], + "type": "object", }, - "description": "Successful result", }, }, - "summary": "This is a summary", - "tags": Array [ - "Tag", - ], }, - }, - "/cookies": Object { - "get": Object { - "parameters": Array [ - Object { - "in": "cookie", - "name": "foo", - "required": true, - }, - Object { - "in": "cookie", - "name": "bar", - "required": false, - }, - ], - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { - "properties": Object { - "bar": Object { - "type": "number", - }, - "foo": Object { - "type": "string", - }, - }, - "required": Array [ - "foo", - ], - "type": "object", - }, + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "number", }, }, - "description": "OK", }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + "description": "OK", + }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "Bad Request", }, + "description": "Bad Request", }, }, }, - "/direct-route-call": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + }, + "/constant": Object { + "get": Object { + "description": "No input, static output, has a tag", + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, + "description": "Successful result", }, }, + "summary": "This is a summary", + "tags": Array [ + "Tag", + ], }, - "/handler-not-inline": Object { - "post": Object { - "requestBody": Object { + }, + "/cookies": Object { + "get": Object { + "parameters": Array [ + Object { + "in": "cookie", + "name": "foo", + "required": true, + }, + Object { + "in": "cookie", + "name": "bar", + "required": false, + }, + ], + "responses": Object { + "200": Object { "content": Object { "application/json": Object { "schema": Object { "properties": Object { + "bar": Object { + "type": "number", + }, "foo": Object { "type": "string", }, @@ -293,584 +242,648 @@ Array [ }, }, }, + "description": "OK", }, - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + "description": "Bad Request", + }, + }, + }, + }, + "/direct-route-call": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "Bad Request", }, + "description": "OK", }, }, }, - "/interface-array-response": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { - "items": Object { - "$ref": "#/components/schemas/User", - }, - "type": "array", + }, + "/handler-not-inline": Object { + "post": Object { + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "properties": Object { + "foo": Object { + "type": "string", }, }, + "required": Array [ + "foo", + ], + "type": "object", + }, + }, + }, + }, + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", + }, + }, + }, + "description": "OK", + }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", + }, }, - "description": "OK", }, + "description": "Bad Request", }, }, }, - "/interface-response": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { + }, + "/interface-array-response": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "items": Object { "$ref": "#/components/schemas/User", }, + "type": "array", }, }, - "description": "OK", }, + "description": "OK", }, }, }, - "/no-explicit-route-type": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + }, + "/interface-response": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/User", }, }, - "description": "OK", }, + "description": "OK", }, }, }, - "/other-file-default-export": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + }, + "/no-explicit-route-type": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, + "description": "OK", }, }, }, - "/other-file-export": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { - "$ref": "#/components/schemas/User2", - }, + }, + "/other-file-default-export": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, + "description": "OK", }, }, }, - "/query": Object { - "get": Object { - "parameters": Array [ - Object { - "in": "query", - "name": "str", - "required": true, - }, - Object { - "in": "query", - "name": "num", - "required": false, - }, - ], - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + }, + "/other-file-export": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/User2", }, }, - "description": "OK", }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + "description": "OK", + }, + }, + }, + }, + "/other-route": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "Bad Request", }, + "description": "OK", }, }, }, - "/recursive-types": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { - "$ref": "#/components/schemas/IndirectRecursiveType", - }, + }, + "/query": Object { + "get": Object { + "parameters": Array [ + Object { + "in": "query", + "name": "str", + "required": true, + }, + Object { + "in": "query", + "name": "num", + "required": false, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, + "description": "OK", + }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", + }, + }, + }, + "description": "Bad Request", }, }, }, - "/request-body": Object { - "post": Object { - "description": "This one has request body and two possible successful responses and multiple tags", - "requestBody": Object { + }, + "/recursive-types": Object { + "get": Object { + "responses": Object { + "200": Object { "content": Object { "application/json": Object { "schema": Object { - "properties": Object { - "date": Object { - "format": "date-time", - "type": "string", - }, - "nullableNum": Object { - "nullable": true, - "type": "number", - }, - "nullableObj": Object { - "nullable": true, - "properties": Object { - "foo": Object { - "type": "number", - }, - }, - "required": Array [ - "foo", - ], - "type": "object", - }, - "numLit": Object { - "enum": Array [ - 42, - ], - "type": "number", - }, - "numLits": Object { - "enum": Array [ - 42, - 123, - ], - "type": "number", - }, - "optionalBool": Object { - "type": "boolean", - }, - "requiredBool": Object { - "type": "boolean", - }, - "str": Object { - "type": "string", - }, - "strLit": Object { - "enum": Array [ - "foo", - ], - "type": "string", - }, - "strLits": Object { - "enum": Array [ - "foo", - "bar", - ], - "type": "string", - }, - }, - "required": Array [ - "str", - "requiredBool", - "nullableNum", - "nullableObj", - "numLit", - "numLits", - "strLit", - "strLits", - "date", - ], - "type": "object", + "$ref": "#/components/schemas/IndirectRecursiveType", }, }, }, + "description": "OK", }, - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { + }, + }, + }, + "/request-body": Object { + "post": Object { + "description": "This one has request body and two possible successful responses and multiple tags", + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "properties": Object { + "date": Object { + "format": "date-time", "type": "string", }, - }, - }, - "description": "Successful result", - }, - "201": Object { - "description": "A new resource was created", - }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { + "nullableNum": Object { + "nullable": true, + "type": "number", + }, + "nullableObj": Object { + "nullable": true, + "properties": Object { + "foo": Object { + "type": "number", + }, + }, + "required": Array [ + "foo", + ], + "type": "object", + }, + "numLit": Object { + "enum": Array [ + 42, + ], + "type": "number", + }, + "numLits": Object { + "enum": Array [ + 42, + 123, + ], + "type": "number", + }, + "optionalBool": Object { + "type": "boolean", + }, + "requiredBool": Object { + "type": "boolean", + }, + "str": Object { + "type": "string", + }, + "strLit": Object { + "enum": Array [ + "foo", + ], + "type": "string", + }, + "strLits": Object { + "enum": Array [ + "foo", + "bar", + ], "type": "string", }, }, + "required": Array [ + "str", + "requiredBool", + "nullableNum", + "nullableObj", + "numLit", + "numLits", + "strLit", + "strLits", + "date", + ], + "type": "object", }, - "description": "Validation error", }, }, - "tags": Array [ - "Tag1", - "Tag2", - "Tag3", - "Tag4", - "Tag5", - ], }, - }, - "/request-headers": Object { - "get": Object { - "parameters": Array [ - Object { - "in": "header", - "name": "API-KEY", - "required": true, - }, - Object { - "in": "header", - "name": "X-Forwarded-For", - "required": false, - }, - ], - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + "description": "Successful result", + }, + "201": Object { + "description": "A new resource was created", + }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "Bad Request", }, + "description": "Validation error", }, }, + "tags": Array [ + "Tag1", + "Tag2", + "Tag3", + "Tag4", + "Tag5", + ], }, - "/response-headers": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + }, + "/request-headers": Object { + "get": Object { + "parameters": Array [ + Object { + "in": "header", + "name": "API-KEY", + "required": true, + }, + Object { + "in": "header", + "name": "X-Forwarded-For", + "required": false, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", - "headers": Object { - "X-Bar": Object { - "required": false, - }, - "X-Foo": Object { - "required": true, + }, + "description": "OK", + }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, }, + "description": "Bad Request", }, }, }, - "/same-path-route": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { - "properties": Object { - "foo": Object { - "type": "string", - }, - }, - "required": Array [ - "foo", - ], - "type": "object", - }, + }, + "/response-headers": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", + }, + "description": "OK", + "headers": Object { + "X-Bar": Object { + "required": false, + }, + "X-Foo": Object { + "required": true, + }, }, }, }, - "post": Object { - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { - "properties": Object { - "bar": Object { - "type": "number", - }, + }, + }, + "/same-path-route": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "properties": Object { + "foo": Object { + "type": "string", }, - "required": Array [ - "bar", - ], - "type": "object", }, + "required": Array [ + "foo", + ], + "type": "object", }, }, - "description": "OK", }, + "description": "OK", }, }, }, - "/schema-docstrings": Object { - "get": Object { - "parameters": Array [ - Object { - "description": "Foo bar baz", - "in": "query", - "name": "param", - "required": true, - }, - ], - "requestBody": Object { + "post": Object { + "responses": Object { + "200": Object { "content": Object { "application/json": Object { "schema": Object { "properties": Object { - "inputField": Object { - "description": "Input field description", + "bar": Object { "type": "number", }, }, "required": Array [ - "inputField", + "bar", ], "type": "object", }, }, }, + "description": "OK", }, - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { - "$ref": "#/components/schemas/DocumentedInterface", - }, - }, - }, - "description": "OK", - }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", + }, + }, + }, + "/schema-docstrings": Object { + "get": Object { + "parameters": Array [ + Object { + "description": "Foo bar baz", + "in": "query", + "name": "param", + "required": true, + }, + ], + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "properties": Object { + "inputField": Object { + "description": "Input field description", + "type": "number", }, }, + "required": Array [ + "inputField", + ], + "type": "object", }, - "description": "Bad Request", }, }, }, - }, - "/type-alias": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/DocumentedInterface", }, }, - "description": "OK", }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + "description": "OK", + }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "Bad Request", }, + "description": "Bad Request", }, }, }, - "/unused-request": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + }, + "/type-alias": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, + "description": "OK", }, - }, - }, - "/user/{id}/{other}": Object { - "get": Object { - "parameters": Array [ - Object { - "in": "path", - "name": "id", - "required": true, - }, - Object { - "in": "path", - "name": "other", - "required": true, - }, - ], - "responses": Object { - "200": Object { - "content": Object { - "application/json": Object { - "schema": Object { - "properties": Object { - "id": Object { - "type": "number", - }, - "other": Object { - "type": "string", - }, - }, - "required": Array [ - "id", - "other", - ], - "type": "object", - }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, + "description": "Bad Request", }, }, }, - "/uses-custom-route": Object { - "get": Object { - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + }, + "/unused-request": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, + "description": "OK", }, }, }, - "/with-content-type-middleware": Object { - "post": Object { - "requestBody": Object { + }, + "/user/{id}/{other}": Object { + "get": Object { + "parameters": Array [ + Object { + "in": "path", + "name": "id", + "required": true, + }, + Object { + "in": "path", + "name": "other", + "required": true, + }, + ], + "responses": Object { + "200": Object { "content": Object { - "application/x-www-form-urlencoded": Object { + "application/json": Object { "schema": Object { "properties": Object { - "a": Object { + "id": Object { + "type": "number", + }, + "other": Object { "type": "string", }, }, "required": Array [ - "a", + "id", + "other", ], "type": "object", }, }, }, + "description": "OK", }, - "responses": Object { - "200": Object { - "content": Object { - "text/plain": Object { - "schema": Object { - "type": "string", - }, + }, + }, + }, + "/uses-custom-route": Object { + "get": Object { + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", }, }, - "description": "OK", }, - "400": Object { - "content": Object { - "text/plain": Object { - "schema": Object { + "description": "OK", + }, + }, + }, + }, + "/with-content-type-middleware": Object { + "post": Object { + "requestBody": Object { + "content": Object { + "application/x-www-form-urlencoded": Object { + "schema": Object { + "properties": Object { + "a": Object { "type": "string", }, }, + "required": Array [ + "a", + ], + "type": "object", }, - "description": "Bad Request", }, }, }, + "responses": Object { + "200": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", + }, + }, + }, + "description": "OK", + }, + "400": Object { + "content": Object { + "text/plain": Object { + "schema": Object { + "type": "string", + }, + }, + }, + "description": "Bad Request", + }, + }, }, }, }, -] +} `; diff --git a/tests/generate.spec.ts b/tests/generate.spec.ts index 7b85b7b3..e2dea2d6 100644 --- a/tests/generate.spec.ts +++ b/tests/generate.spec.ts @@ -5,11 +5,12 @@ const relativePath = (fileName: string): string => path.relative(process.cwd(), __dirname + '/' + fileName) const testRoutes = relativePath('test-routes.ts') +const otherRoutes = relativePath('other-routes.ts') const warningRoutes = relativePath('warning-routes.ts') describe('generate', () => { it('works', () => { - const result = generate([testRoutes], { strict: true }) + const result = generate([testRoutes, otherRoutes], { strict: true }) expect(result).toMatchSnapshot() }) diff --git a/tests/other-routes.ts b/tests/other-routes.ts new file mode 100644 index 00000000..0b6587bb --- /dev/null +++ b/tests/other-routes.ts @@ -0,0 +1,5 @@ +import { Response, route, Route, router } from 'typera-express' + +const otherRoute: Route> = route.get('/other-route').handler(() => Response.ok('hello')) + +export default router(otherRoute)