import express from 'express' ; import describeRouteVariables from './describeRouteVariables' ; import Router from './Router' ; import Route from './Route' ; import { IMountableItem , IOperationVariable , IOpenApiOptions , IGlobalConfiguration } from './types' ; interface IParameter { name : string ; required : boolean ; in : string ; type ? : string ; schema ? : IParameterArraySchema | IParameterItemTypeOrRef ; default ? : string | boolean | number ; } interface IParameterArraySchema { type : string ; items : IParameterItemTypeOrRef ; } interface IParameterItemTypeOrRef { type ? : string ; $ref ? : string ; } interface IBuildParametersArguments { variableDefinitions : IOperationVariable [ ] ; variableLocation : string ; refLocation : string ; } const PATH_VARIABLES_REGEX = /:([A-Za-z]+)/g ; function resolveBodySchemaName ( route ) { return `${ route . operationName }Body` ; } function translateScalarType ( scalarType ) { switch ( scalarType ) { case 'Int' : return 'number' ; case 'Boolean' : return 'boolean' ; case 'String' : default : return 'string' ; } } function buildScalarDefinition ( node ) { const scalarType = translateScalarType ( node . name ) ; const scalarDoc = { type : scalarType , } ; if ( node . description ) { scalarDoc . description = node . description ; } return scalarDoc ; } function buildObjectDefinition ( node ) { const objectDoc = { } ; objectDoc . type = 'object' ; objectDoc . properties = { } ; if ( node . inputFields ) { node . inputFields . forEach ( ( field ) => { const { type : fieldNode } = field ; objectDoc . properties [ field . name ] = buildDefinition ( fieldNode ) ; } ) ; } return objectDoc ; } function buildBodyDefinition ( variables , refLocation ) { const bodyDoc = { } ; bodyDoc . type = 'object' ; bodyDoc . properties = { } ; variables . forEach ( ( field ) => { bodyDoc . properties [ field . name ] = buildSchemaParameter ( field , refLocation ) ; } ) ; return bodyDoc ; } function buildDefinition ( node ) { switch ( node . kind ) { case 'INPUT_OBJECT' : return buildObjectDefinition ( node ) ; case 'ENUM' : return buildEnumDefinition ( node ) ; case 'SCALAR' : default : return buildScalarDefinition ( node ) ; } } function buildEnumDefinition ( node ) { const enumDoc = { type : 'string' , enum : [ ] , } ; if ( node . description ) { enumDoc . description = node . description ; } node . enumValues . forEach ( ( enumValue ) => { enumDoc . enum . push ( enumValue . name ) ; } ) ; return enumDoc ; } function openApiPath ( path ) { return path . replace ( PATH_VARIABLES_REGEX , '{$1}' ) ; } function buildSchemaParameter ( variableDefinition , refLocation ) { if ( variableDefinition . array ) { return { type : 'array' , items : { '$ref' : `${ refLocation }/${ variableDefinition . type }` } , } ; } return { '$ref' : `${ refLocation }/${ variableDefinition . type }` } ; } function buildParametersArray ( { variableDefinitions , variableLocation , refLocation } ) { return variableDefinitions . map ( ( variableDefinition ) : IParameter => ( { name : variableDefinition . name , required : variableDefinition . required , in : variableLocation , schema : buildSchemaParameter ( variableDefinition , refLocation ) , } ) ) ; } class MountableDocument implements IMountableItem { public path = '/docs/openapi' ; public httpMethod = 'get' ; protected router ? : Router ; constructor ( protected options ) { } onMount ( router ) { this . router = router ; return this ; } at ( path ) { this . path = path ; return this ; } protected async generateDocumentation ( ) { return '' ; } withOptions ( options ) { return this ; } asExpressRoute ( ) { const generateDoc = this . generateDocumentation ( ) ; return async ( req , res ) => { const doc = await generateDoc ; res . status ( 200 ) . json ( doc ) ; } ; } asKoaRoute ( ) { throw new Error ( 'not yet implemented' ) ; } asMetal ( ) { throw new Error ( 'not yet implemented' ) ; } protected getRouter ( ) { const { router } = this ; if ( ! router ) { throw new Error ( `
        Router must be set in order to generate documentation. If you are using router.mount(), the router will automatically be set.
        If you are using this outside of a Router instance, please leverage #setRouter() to select a class that adheres to the router
        interface.
      ` ) ; } return router ; } } export class V2 extends MountableDocument { public path = '/docs/swagger' ; protected async generateDocumentation ( ) { const router = this . getRouter ( ) ; try { const { title , version , termsOfService , license , host , basePath , } = this . options ; const page = { swagger : '2.0' , info : { } , paths : { } , produces : [ 'application/json' ] , definitions : { } , } ; page . info . title = title ; page . info . version = version ; if ( termsOfService ) { page . info . termsOfService = termsOfService ; } if ( license ) { page . info . license = { name : license } ; } if ( host ) { page . host = host ; } if ( basePath ) { page . basePath = basePath ; } router . routes . forEach ( ( route ) => { const { path , httpMethod } = route ; const docPath = openApiPath ( path ) ; if ( ! page . paths [ docPath ] ) { page . paths [ docPath ] = { } ; } const routeDoc = page . paths [ docPath ] [ httpMethod ] = { } ; routeDoc . parameters = [ ] ; routeDoc . consumes = [ ] ; routeDoc . produces = [ 'application/json' ] ; routeDoc . responses = { 200 : { description : 'Server alive. This does not mean that the query was completed succesfully. Check the errors object in the response' , } , 400 : { description : 'Authentication Required' , } , 500 : { description : 'Server error.' , } } ; if ( httpMethod === 'post' ) { routeDoc . consumes . push ( 'application/json' ) ; } routeDoc . parameters . push ( ... buildParametersArray ( { variableDefinitions : route . queryVariables , variableLocation : 'query' , refLocation : '#/definitions' , } ) ) ; routeDoc . parameters . push ( ... buildParametersArray ( { variableDefinitions : route . pathVariables , variableLocation : 'path' , refLocation : '#/definitions' , } ) ) ; routeDoc . parameters . push ( ... buildParametersArray ( { variableDefinitions : route . bodyVariables , variableLocation : 'body' , refLocation : '#/definitions' , } ) ) ; } ) ; const introspectedVariableDefinitions = await describeRouteVariables ( router ) ; Object . keys ( introspectedVariableDefinitions ) . forEach ( ( variableName ) => { const variableDefinition = introspectedVariableDefinitions [ variableName ] ; page . definitions [ variableName ] = buildDefinition ( variableDefinition ) ; } ) ; return page ; } catch ( error ) { return { error : error . message , } ; } } } export class V3 extends MountableDocument { protected async generateDocumentation ( ) { const { options } = this ; const doc = { } ; const refLocation = '#/components/schemas' ; doc . openapi = '3.0.0' ; doc . info = { } ; doc . info . title = options . title ; doc . info . version = options . version ; doc . paths = { } ; doc . components = { } ; if ( options . license ) { doc . license = { } ; doc . license . name = options . license ; } if ( options . host ) { doc . servers = [ ] ; doc . servers . push ( { url : `${ options . host }${ options . basePath || '' }` , } ) ; } const router = this . getRouter ( ) ; router . routes . forEach ( ( route ) => { const { path , httpMethod } = route ; const docPath = openApiPath ( path ) ; if ( ! doc . paths [ docPath ] ) { doc . paths [ docPath ] = { } ; } const routeDoc = doc . paths [ docPath ] [ httpMethod ] = { } ; routeDoc . responses = { } ; routeDoc . responses . default = { } ; routeDoc . responses . default . description = 'OK' ; routeDoc . parameters = [ ] ; routeDoc . parameters . push ( ... buildParametersArray ( { variableDefinitions : route . queryVariables , variableLocation : 'query' , refLocation , } ) ) ; routeDoc . parameters . push ( ... buildParametersArray ( { variableDefinitions : route . pathVariables , variableLocation : 'path' , refLocation , } ) ) ; if ( route . bodyVariables . length ) { const schemaName = resolveBodySchemaName ( route ) ; routeDoc . requestBody = { content : { 'application/json' : { schema : { '$ref' : `${ refLocation }/${ schemaName }` } , } , } , } ; } } ) ; doc . components = { } ; doc . components . schemas = { } ; try { const introspectedVariableDefinitions = await describeRouteVariables ( router ) ; Object . keys ( introspectedVariableDefinitions ) . forEach ( ( variableName ) => { const variableDefinition = introspectedVariableDefinitions [ variableName ] ; doc . components . schemas [ variableName ] = buildDefinition ( variableDefinition ) ; } ) ; router . routes . forEach ( ( route ) => { const schemaName = resolveBodySchemaName ( route ) ; doc . components . schemas [ schemaName ] = buildBodyDefinition ( route . bodyVariables , refLocation ) ; } ) ; return doc ; } catch ( error ) { return { error : error . message , } ; } } }