import { RouterContext , RouterOptions } from '@koa/router' ; import Router from '@koa/router' ; import R from 'ramda' ; import is from 'is-type-of' ; import validate from './validate' ; import { swaggerHTML } from './swaggerHTML' ; import { swaggerJSON } from './swaggerJSON' ; import swaggerObject from './swaggerObject' ; import { convertPath , getPath , loadSwaggerClasses , reservedMethodNames , allowedMethods } from './utils' ; import { Data } from './types' ; import { writeFileSync } from 'fs' ; import path from 'path' ; import compose from 'koa-compose' ; export interface Context extends RouterContext { validatedQuery : any ; validatedBody : any ; validatedParams : any ; } const validator = ( parameters ) => async ( ctx , next ) => { if ( ! parameters ) { await next ( ) ; return ; } if ( parameters . query ) { ctx . validatedQuery = validate ( ctx . request . query , parameters . query ) ; } if ( parameters . path ) { ctx . validatedParams = validate ( ctx . params , parameters . path ) ; } if ( parameters . body ) { ctx . validatedBody = validate ( ( ctx . request as any ) . body , parameters . body ) ; } await next ( ) ; } ; export interface SwaggerDisplayConfiguration { deepLinking ? : boolean ; displayOperationId ? : boolean ; defaultModelsExpandDepth ? : number ; defaultModelExpandDepth ? : number ; defaultModelRendering ? : 'example' | 'model' ; displayRequestDuration ? : boolean ; docExpansion ? : 'list' | 'full' | 'none' ; filter ? : boolean | string ; maxDisplayedTags ? : number ; showExtensions ? : boolean ; showCommonExtensions ? : boolean ; } export interface SwaggerConfiguration { display ? : SwaggerDisplayConfiguration ; } export interface SwaggerOptions { title ? : string ; description ? : string ; version ? : string ; swaggerJsonEndpoint ? : string ; swaggerHtmlEndpoint ? : string ; prefix ? : string ; swaggerOptions ? : any ; swaggerConfiguration ? : SwaggerConfiguration ; [ name ] : any ; } export interface DumpOptions { dir : string ; filename : string ; } const handleDumpSwaggerJSON = ( router , dumpOptions , options = { } ) => { let data = { } ; const { dir = process . cwd ( ) , filename = 'swagger.json' } = dumpOptions ; Object . keys ( swaggerObject . data ) . forEach ( ( k ) => { if ( router . swaggerKeys . has ( k ) ) { data [ k ] = swaggerObject . data [ k ] ; } } ) ; console . log ( path . resolve ( dir , filename ) ) ; const jsonData = swaggerJSON ( options , data ) ; writeFileSync ( path . resolve ( dir , filename ) , JSON . stringify ( jsonData , null , 2 ) ) ; } ; const handleSwagger = ( router , options ) => { const { swaggerJsonEndpoint = '/swagger-json' , swaggerHtmlEndpoint = '/swagger-html' , prefix = '' , swaggerConfiguration = { } , } = options ; router . get ( swaggerJsonEndpoint , async ( ctx ) => { let data = { } ; if ( router instanceof SwaggerRouter ) { Object . keys ( swaggerObject . data ) . forEach ( k => { if ( router . swaggerKeys . has ( k ) ) { data [ k ] = swaggerObject . data [ k ] ; } } ) ; } else { data = swaggerObject . data ; } ctx . body = swaggerJSON ( options , data ) ; } ) ; router . get ( swaggerHtmlEndpoint , async ( ctx ) => { ctx . body = swaggerHTML ( getPath ( prefix , swaggerJsonEndpoint ) , swaggerConfiguration ) ; } ) ; } ; const handleMap = ( router , SwaggerClass , { doValidation = true } ) => { if ( ! SwaggerClass ) return ; const classMiddlewares = SwaggerClass . middlewares || [ ] ; const classPrefix = SwaggerClass . prefix || '' ; const classParameters = SwaggerClass . parameters || { } ; const classParametersFilters = SwaggerClass . parameters ? SwaggerClass . parameters . filters : [ 'ALL' ] ; classParameters . query = classParameters . query ? classParameters . query : { } ; const staticMethods = Object . getOwnPropertyNames ( SwaggerClass ) . filter ( method => ! reservedMethodNames . includes ( method ) ) . map ( ( method ) => { const func = SwaggerClass [ method ] ; if ( typeof func !== 'object' && typeof func !== 'function' ) { return { } ; } func [ 'fnName' ] = method ; return func ; } ) ; const SwaggerClassPrototype = SwaggerClass . prototype ; const methods = Object . getOwnPropertyNames ( SwaggerClassPrototype ) . filter ( ( method ) => ! reservedMethodNames . includes ( method ) ) . map ( ( method ) => { const wrapperMethod = async ( ctx ) => { const c = new SwaggerClass ( ctx ) ; await c [ method ] ( ctx ) ; } ; const descriptors = Object . getOwnPropertyDescriptors ( SwaggerClassPrototype [ method ] ) ; Object . defineProperties ( wrapperMethod , { fnName : { value : method , enumerable : true , writable : true , configurable : true , } , ... descriptors } ) ; return wrapperMethod ; } ) ; [ ... staticMethods , ... methods ] . filter ( ( item ) => { const { path , method } = item ; if ( ! path && ! method ) { return false ; } return true ; } ) . forEach ( ( item ) => { router . _addKey ( `${ SwaggerClass . name }-${ item . fnName }` ) ; const { path , method } = item ; let { middlewares = [ ] } = item ; const localParams = item . parameters || { } ; if ( classParametersFilters . includes ( 'ALL' ) || classParametersFilters . map ( i => i . toLowerCase ( ) ) . includes ( method ) ) { const globalQuery = R . clone ( classParameters . query ) ; localParams . query = localParams . query ? localParams . query : { } ; localParams . query = Object . assign ( globalQuery , localParams . query ) ; } if ( is . function ( middlewares ) ) { middlewares = [ middlewares ] ; } if ( ! is . array ( middlewares ) ) { throw new Error ( 'middlewares params must be an array or function' ) ; } middlewares . forEach ( ( item ) => { if ( ! is . function ( item ) ) { throw new Error ( 'item in middlewares must be a function' ) ; } } ) ; if ( ! allowedMethods . hasOwnProperty ( method . toUpperCase ( ) ) ) { throw new Error ( `illegal API: ${ method } ${ path } at [${ item }]` ) ; } const chain = [ `${ convertPath ( `${ classPrefix }${ path }` ) }` ] ; if ( doValidation ) { chain . push ( validator ( localParams ) ) ; } chain . push ( ... classMiddlewares ) ; chain . push ( ... middlewares ) ; chain . push ( item ) ; ( router as any ) [ method ] ( ... chain ) ; } ) ; } ; const handleMapDir = ( router , dir , options ) => { loadSwaggerClasses ( dir , options ) . forEach ( ( c ) => { router . map ( c , options ) ; } ) ; } ; const handleBuildMiddleware = ( router , SwaggerClass , { doValidation = true } ) => { const meta = [ ] ; if ( ! SwaggerClass ) return meta ; const classMiddlewares = SwaggerClass . middlewares || [ ] ; const classPrefix = SwaggerClass . prefix || '' ; const classParameters = SwaggerClass . parameters || { } ; const classParametersFilters = SwaggerClass . parameters ? SwaggerClass . parameters . filters : [ 'ALL' ] ; classParameters . query = classParameters . query ? classParameters . query : { } ; const staticMethods = Object . getOwnPropertyNames ( SwaggerClass ) . filter ( method => ! reservedMethodNames . includes ( method ) ) . map ( ( method ) => { const func = SwaggerClass [ method ] ; func [ 'fnName' ] = method ; return func ; } ) ; const SwaggerClassPrototype = SwaggerClass . prototype ; const methods = Object . getOwnPropertyNames ( SwaggerClassPrototype ) . filter ( ( method ) => ! reservedMethodNames . includes ( method ) ) . map ( ( method ) => { const wrapperMethod = async ( ctx ) => { const c = new SwaggerClass ( ctx ) ; await c [ method ] ( ctx ) ; } ; const descriptors = Object . getOwnPropertyDescriptors ( SwaggerClassPrototype [ method ] ) ; Object . defineProperties ( wrapperMethod , { fnName : { value : method , enumerable : true , writable : true , configurable : true , } , ... descriptors } ) ; return wrapperMethod ; } ) ; [ ... staticMethods , ... methods ] . filter ( ( item ) => { const { path , method } = item ; if ( ! path && ! method ) { return false ; } return true ; } ) . forEach ( ( item ) => { router . _addKey ( `${ SwaggerClass . name }-${ item . fnName }` ) ; const { path , method } = item ; let { middlewares = [ ] } = item ; const localParams = item . parameters || { } ; if ( classParametersFilters . includes ( 'ALL' ) || classParametersFilters . map ( i => i . toLowerCase ( ) ) . includes ( method ) ) { const globalQuery = R . clone ( classParameters . query ) ; localParams . query = localParams . query ? localParams . query : { } ; localParams . query = Object . assign ( globalQuery , localParams . query ) ; } if ( is . function ( middlewares ) ) { middlewares = [ middlewares ] ; } if ( ! is . array ( middlewares ) ) { throw new Error ( 'middlewares params must be an array or function' ) ; } middlewares . forEach ( ( item ) => { if ( ! is . function ( item ) ) { throw new Error ( 'item in middlewares must be a function' ) ; } } ) ; if ( ! allowedMethods . hasOwnProperty ( method . toUpperCase ( ) ) ) { throw new Error ( `illegal API: ${ method } ${ path } at [${ item }]` ) ; } const endpoint = `${ convertPath ( `${ classPrefix }${ path }` ) }` ; const chain = [ ] ; if ( doValidation ) { chain . push ( validator ( localParams ) ) ; } chain . push ( ... classMiddlewares ) ; chain . push ( ... middlewares ) ; meta . push ( { path : endpoint , method : method . toLowerCase ( ) , middlewares : chain , name : item . name } ) ; } ) ; return meta . map ( m => ( { path : m . path , method : m . method , middleware : compose ( m . middlewares ) , name : m . name } ) ) as { path : string ; method : string ; middleware : any ; name : string ; } [ ] ; } ; export interface MapOptions { doValidation ? : boolean ; recursive ? : boolean ; [ name ] : any ; ignore ? : string [ ] ; } const wrapper = ( router ) => { router . swagger = ( options = { } ) => { handleSwagger ( router , options ) ; } ; router . map = ( SwaggerClass , options = { } ) => { handleMap ( router , SwaggerClass , options ) ; } ; router . mapDir = ( dir , options = { } ) => { handleMapDir ( router , dir , options ) ; } ; } ; class SwaggerRouter < StateT = any , CustomT = { } > extends Router < StateT , CustomT > { public swaggerKeys : Set < String > ; public opts : RouterOptions ; public swaggerOpts : SwaggerOptions ; constructor ( opts = { } , swaggerOpts = { } ) { super ( opts ) ; this . opts = opts || { } ; this . swaggerKeys = new Set ( ) ; this . swaggerOpts = swaggerOpts || { } ; if ( this . opts . prefix && ! this . swaggerOpts . prefix ) { this . swaggerOpts . prefix = this . opts . prefix ; } } _addKey ( str ) { this . swaggerKeys . add ( str ) ; } swagger ( options = { } ) { const opts = Object . assign ( options , this . swaggerOpts ) ; handleSwagger ( this , opts ) ; } dumpSwaggerJson ( dumpOptions , options = { } ) { const opts = Object . assign ( options , this . swaggerOpts ) ; handleDumpSwaggerJSON ( this , dumpOptions , opts ) ; } map ( SwaggerClass , options ) { handleMap ( this , SwaggerClass , options ) ; } mapDir ( dir , options = { } ) { handleMapDir ( this , dir , options ) ; } buildMiddleware ( SwaggerClass , options ) { return handleBuildMiddleware ( this , SwaggerClass , options ) ; } } export { wrapper , SwaggerRouter } ;