diff --git a/packages/openapi-generator/src/apiSpec.ts b/packages/openapi-generator/src/apiSpec.ts index 3caf5eee..d04514d3 100644 --- a/packages/openapi-generator/src/apiSpec.ts +++ b/packages/openapi-generator/src/apiSpec.ts @@ -1,11 +1,13 @@ import * as swc from '@swc/core'; import * as E from 'fp-ts/Either'; +import type { Block } from 'comment-parser'; import { parsePlainInitializer } from './codec'; import type { Project } from './project'; import { resolveLiteralOrIdentifier } from './resolveInit'; import { parseRoute, type Route } from './route'; import { SourceFile } from './sourceFile'; +import { OpenAPIV3 } from 'openapi-types'; export function parseApiSpec( project: Project, @@ -83,3 +85,14 @@ export function parseApiSpec( return E.right(result); } + +export function parseApiSpecComment( + comment: Block | undefined, +): OpenAPIV3.ServerObject | undefined { + if (comment === undefined) { + return undefined; + } + const description = comment.description; + const url = comment.tags.find((tag) => tag.tag === 'url')?.name; + return url !== undefined ? { url, description } : undefined; +} diff --git a/packages/openapi-generator/src/cli.ts b/packages/openapi-generator/src/cli.ts index 0bcb269c..fbd1039f 100644 --- a/packages/openapi-generator/src/cli.ts +++ b/packages/openapi-generator/src/cli.ts @@ -5,8 +5,9 @@ import * as E from 'fp-ts/Either'; import * as fs from 'fs'; import * as p from 'path'; import type { Expression } from '@swc/core'; +import type { OpenAPIV3 } from 'openapi-types'; -import { parseApiSpec } from './apiSpec'; +import { parseApiSpec, parseApiSpecComment } from './apiSpec'; import { getRefs } from './ref'; import { convertRoutesToOpenAPI } from './openapi'; import type { Route } from './route'; @@ -106,6 +107,7 @@ const app = command({ } let apiSpec: Route[] = []; + let servers: OpenAPIV3.ServerObject[] = []; for (const symbol of Object.values(entryPoint.symbols.declarations)) { if (symbol.init === undefined) { continue; @@ -136,6 +138,10 @@ const app = command({ process.exit(1); } + const server = parseApiSpecComment(symbol.comment); + if (server !== undefined) { + servers.push(server); + } apiSpec.push(...result.right); } if (apiSpec.length === 0) { @@ -189,6 +195,7 @@ const app = command({ version, description, }, + servers, apiSpec, components, ); diff --git a/packages/openapi-generator/src/index.ts b/packages/openapi-generator/src/index.ts index a6598c81..aa1a4b01 100644 --- a/packages/openapi-generator/src/index.ts +++ b/packages/openapi-generator/src/index.ts @@ -1,4 +1,4 @@ -export { parseApiSpec } from './apiSpec'; +export { parseApiSpec, parseApiSpecComment } from './apiSpec'; export { parseCodecInitializer, parsePlainInitializer } from './codec'; export { parseCommentBlock, type JSDoc } from './jsdoc'; export { convertRoutesToOpenAPI } from './openapi'; diff --git a/packages/openapi-generator/src/openapi.ts b/packages/openapi-generator/src/openapi.ts index 6fc04216..d511fd28 100644 --- a/packages/openapi-generator/src/openapi.ts +++ b/packages/openapi-generator/src/openapi.ts @@ -174,6 +174,7 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec export function convertRoutesToOpenAPI( info: OpenAPIV3.InfoObject, + servers: OpenAPIV3.ServerObject[], routes: Route[], schemas: Record, ): OpenAPIV3.Document { @@ -215,6 +216,7 @@ export function convertRoutesToOpenAPI( return { openapi: '3.0.3', info, + ...(servers.length > 0 ? { servers } : {}), paths, components: { schemas: openapiSchemas, diff --git a/packages/openapi-generator/test/apiSpec.test.ts b/packages/openapi-generator/test/apiSpec.test.ts index a4f0600d..e4e14f61 100644 --- a/packages/openapi-generator/test/apiSpec.test.ts +++ b/packages/openapi-generator/test/apiSpec.test.ts @@ -4,7 +4,7 @@ import test from 'node:test'; import type { NestedDirectoryJSON } from 'memfs'; import { TestProject } from './testProject'; -import { parseApiSpec, type Route } from '../src'; +import { parseApiSpec, parseApiSpecComment, type Route } from '../src'; async function testCase( description: string, @@ -219,3 +219,49 @@ testCase('computed property api spec', COMPUTED_PROPERTY, '/index.ts', { }, ], }); + +const BASE_URL = { + '/index.ts': ` + import * as t from 'io-ts'; + import * as h from '@api-ts/io-ts-http'; + + /** An API spec + * + * @url /api/v1 + **/ + export const test = h.apiSpec({ + 'api.test': { + get: h.httpRoute({ + path: '/test', + method: 'GET', + request: h.httpRequest({}), + response: { + 200: t.string, + }, + }) + } + });`, +}; + +test('api spec comment parser', async () => { + const project = new TestProject(BASE_URL); + + await project.parseEntryPoint('/index.ts'); + const sourceFile = project.get('/index.ts'); + if (sourceFile === undefined) { + throw new Error('could not find source file /index.ts'); + } + + let commentParsed = false; + sourceFile.symbols.declarations.forEach((symbol) => { + if (symbol.name === 'test') { + assert.deepEqual(parseApiSpecComment(symbol.comment), { + description: 'An API spec', + url: '/api/v1', + }); + commentParsed = true; + } + }); + + assert(commentParsed); +}); diff --git a/packages/openapi-generator/test/openapi.test.ts b/packages/openapi-generator/test/openapi.test.ts index a2e9c862..c2ce86a9 100644 --- a/packages/openapi-generator/test/openapi.test.ts +++ b/packages/openapi-generator/test/openapi.test.ts @@ -47,6 +47,7 @@ async function testCase( const actual = convertRoutesToOpenAPI( { title: 'Test', version: '1.0.0' }, + [], routes, schemas, );