UNPKG

routing-controllers-openapi

Version:

Runtime OpenAPI v3 spec generation for routing-controllers

154 lines (136 loc) 4.68 kB
import _merge from 'lodash.merge' import { OperationObject, ReferenceObject, ResponsesObject } from 'openapi3-ts' import 'reflect-metadata' import { getContentType, getStatusCode, IRoute } from './index' const OPEN_API_KEY = Symbol('routing-controllers-openapi:OpenAPI') export type OpenAPIParam = | Partial<OperationObject> | ((source: OperationObject, route: IRoute) => OperationObject) /** * Supplement action with additional OpenAPI Operation keywords. * * @param spec OpenAPI Operation object that is merged into the schema derived * from routing-controllers decorators. In case of conflicts, keywords defined * here overwrite the existing ones. Alternatively you can supply a function * that receives as parameters the existing Operation and target route, * returning an updated Operation. */ export function OpenAPI(spec: OpenAPIParam) { // tslint:disable-next-line:ban-types return (...args: [Function] | [object, string, PropertyDescriptor]) => { if (args.length === 1) { const [target] = args const currentMeta = getOpenAPIMetadata(target) setOpenAPIMetadata([spec, ...currentMeta], target) } else { const [target, key] = args const currentMeta = getOpenAPIMetadata(target, key) setOpenAPIMetadata([spec, ...currentMeta], target, key) } } } /** * Apply the keywords defined in @OpenAPI decorator to its target route. */ export function applyOpenAPIDecorator( originalOperation: OperationObject, route: IRoute ): OperationObject { const { action } = route const openAPIParams = [ ...getOpenAPIMetadata(action.target), ...getOpenAPIMetadata(action.target.prototype, action.method), ] return openAPIParams.reduce((acc: OperationObject, oaParam: OpenAPIParam) => { return typeof oaParam === 'function' ? oaParam(acc, route) : _merge({}, acc, oaParam) }, originalOperation) as OperationObject } /** * Get the OpenAPI Operation object stored in given target property's metadata. */ export function getOpenAPIMetadata( target: object, key?: string ): OpenAPIParam[] { return ( (key ? Reflect.getMetadata(OPEN_API_KEY, target.constructor, key) : Reflect.getMetadata(OPEN_API_KEY, target)) || [] ) } /** * Store given OpenAPI Operation object into target property's metadata. */ export function setOpenAPIMetadata( value: OpenAPIParam[], target: object, key?: string ) { return key ? Reflect.defineMetadata(OPEN_API_KEY, value, target.constructor, key) : Reflect.defineMetadata(OPEN_API_KEY, value, target) } /** * Supplement action with response body type annotation. */ export function ResponseSchema( responseClass: Function | string, // tslint:disable-line options: { contentType?: string description?: string statusCode?: string | number isArray?: boolean } = {} ) { const setResponseSchema = (source: OperationObject, route: IRoute) => { const contentType = options.contentType || getContentType(route) const description = options.description || '' const isArray = options.isArray || false const statusCode = (options.statusCode || getStatusCode(route)) + '' let responseSchemaName = '' if (typeof responseClass === 'function' && responseClass.name) { responseSchemaName = responseClass.name } else if (typeof responseClass === 'string') { responseSchemaName = responseClass } if (responseSchemaName) { const reference: ReferenceObject = { $ref: `#/components/schemas/${responseSchemaName}`, } const schema = isArray ? { items: reference, type: 'array' } : reference const responses: ResponsesObject = { [statusCode]: { content: { [contentType]: { schema, }, }, description, }, } const oldSchema = source.responses[statusCode]?.content[contentType].schema if (oldSchema?.$ref || oldSchema?.items || oldSchema?.oneOf) { // case where we're adding multiple schemas under single statuscode/contentType const newStatusCodeResponse = _merge( {}, source.responses[statusCode], responses[statusCode] ) const newSchema = oldSchema.oneOf ? { oneOf: [...oldSchema.oneOf, schema], } : { oneOf: [oldSchema, schema] } newStatusCodeResponse.content[contentType].schema = newSchema source.responses[statusCode] = newStatusCodeResponse return source } return _merge({}, source, { responses }) } return source } return OpenAPI(setResponseSchema) }