11import IMountableItem from './IMountableItem' ;
2+ import describeRouteVariables from './describeRouteVariables' ;
23import Router from './Router' ;
34import Route , { IOperationVariable } from './Route' ;
45import express from 'express' ;
56
7+ interface IOpenApiOptions {
8+ title : string ;
9+ version : string ;
10+ termsOfService ?: string ;
11+ license ?: string ;
12+ basePath ?: string ;
13+ host ?: string ;
14+ }
15+
616interface IParameter {
717 name : string ;
818 required : boolean ;
919 in : string ;
10- type : string ;
20+ type ?: string ;
21+ schema ?: IParameterArraySchema | IParameterItemTypeOrRef ;
1122 default ?: string | boolean | number ;
1223}
1324
25+ interface IParameterArraySchema {
26+ type : string ;
27+ items : IParameterItemTypeOrRef ;
28+ }
29+
30+ interface IParameterItemTypeOrRef {
31+ type ?: string ;
32+ $ref ?: string ;
33+ }
34+
1435interface IBuildParametersArguments {
1536 variableDefinitions : IOperationVariable [ ] ;
1637 variableLocation : string ;
@@ -22,24 +43,55 @@ function openApiPath(path: string): string {
2243 return path . replace ( PATH_VARIABLES_REGEX , '{$1}' ) ;
2344}
2445
46+ function resolveRefOrType ( variableType : string ) : IParameterItemTypeOrRef {
47+ switch ( variableType ) {
48+ case 'String' :
49+ return { type : 'string' } ;
50+ case 'Boolean' :
51+ return { type : 'boolean' } ;
52+ case 'Int' :
53+ return { type : 'number' } ;
54+ default :
55+ return { '$ref' : `#/definitions/${ variableType } ` } ;
56+ }
57+ }
58+
2559// TODO: Return Type and Attempt to get description from graphql
2660function buildParametersArray ( { variableDefinitions, variableLocation } : IBuildParametersArguments ) : IParameter [ ] {
2761 return variableDefinitions . map (
28- ( variableDefinition : IOperationVariable ) : IParameter => ( {
29- name : variableDefinition . name ,
30- required : variableDefinition . required ,
31- default : variableDefinition . defaultValue ,
32- in : variableLocation ,
33- type : variableDefinition . type ,
34- } )
62+ ( variableDefinition : IOperationVariable ) : IParameter => {
63+ const parameter : IParameter = {
64+ name : variableDefinition . name ,
65+ required : variableDefinition . required ,
66+ default : variableDefinition . defaultValue ,
67+ in : variableLocation ,
68+ } ;
69+
70+ if ( variableDefinition . array ) {
71+ parameter . schema = {
72+ type : 'array' ,
73+ items : resolveRefOrType ( variableDefinition . type ) ,
74+ } ;
75+ } else {
76+ const refOrType = resolveRefOrType ( variableDefinition . type ) ;
77+
78+ if ( refOrType . type ) {
79+ parameter . type = refOrType . type ;
80+ } else {
81+ parameter . schema = refOrType ;
82+ }
83+ }
84+
85+ return parameter ;
86+ }
3587 ) ;
3688}
3789
3890export class V2 implements IMountableItem {
3991 public path : string = '/docs/openapi/v2' ;
4092 public httpMethod : string = 'get' ;
4193
42- constructor ( options : any , private router ?: Router ) {
94+ constructor ( private options : IOpenApiOptions , private router ?: Router ) {
4395 }
4496
4597 setRouter ( router : Router ) : this {
@@ -54,7 +106,7 @@ export class V2 implements IMountableItem {
54106 return this ;
55107 }
56108
57- private generateDocumentation ( ) : any {
109+ private async generateDocumentation ( ) : Promise < any > {
58110 const { router } = this ;
59111
60112 if ( ! router ) {
@@ -65,78 +117,157 @@ export class V2 implements IMountableItem {
65117 ` ) ;
66118 }
67119
68- const page : any = {
69- swagger : '2.0' ,
70- info : { } ,
71- paths : { } ,
72- } ;
120+ try {
121+ const {
122+ title,
123+ version,
124+ termsOfService,
125+ license,
126+ host,
127+ basePath,
128+ } = this . options ;
73129
74- router . routes . forEach ( ( route ) => {
75- const { path, httpMethod } = route ;
130+ const page : any = {
131+ swagger : '2.0' ,
132+ info : { } ,
133+ paths : { } ,
134+ produces : [ 'application/json' ] ,
135+ definitions : { } ,
136+ } ;
76137
77- const docPath = openApiPath ( path ) ;
138+ page . info . title = title ;
139+ page . info . version = version ;
78140
79- if ( ! page . paths [ docPath ] ) {
80- page . paths [ docPath ] = { } ;
141+ if ( termsOfService ) {
142+ page . info . termsOfService = termsOfService ;
81143 }
82144
83- const routeDoc : any = page . paths [ docPath ] [ httpMethod ] = { } ;
84-
85- routeDoc . parameters = [ ] ;
86- routeDoc . consumes = [ ] ;
87- routeDoc . produces = [ 'application/json' ] ;
88- routeDoc . responses = {
89- 200 : {
90- description : 'Server alive. This does not mean that the query was completed succesfully. Check the errors object in the response' ,
91- } ,
92- 400 : {
93- description : 'Authentication Required' ,
94- } ,
95- 500 : {
96- description : 'Server error.' ,
97- }
98- } ;
145+ if ( license ) {
146+ page . info . license = { name : license } ;
147+ }
99148
100- if ( httpMethod === 'post' ) {
101- routeDoc . consumes . push ( 'application/json' ) ;
149+ if ( host ) {
150+ page . host = host ;
102151 }
103152
104- routeDoc . parameters . push (
105- ...buildParametersArray ( {
106- variableDefinitions : route . queryVariables ,
107- variableLocation : 'query'
108- } )
109- ) ;
153+ if ( basePath ) {
154+ page . basePath = basePath ;
155+ }
110156
111- routeDoc . parameters . push (
112- ...buildParametersArray ( {
113- variableDefinitions : route . pathVariables ,
114- variableLocation : 'path'
115- } )
116- ) ;
157+ router . routes . forEach ( ( route ) => {
158+ const { path, httpMethod } = route ;
117159
118- routeDoc . parameters . push (
119- ...buildParametersArray ( {
120- variableDefinitions : route . bodyVariables ,
121- variableLocation : 'body'
122- } )
123- ) ;
124- } ) ;
160+ const docPath = openApiPath ( path ) ;
125161
126- return page ;
162+ if ( ! page . paths [ docPath ] ) {
163+ page . paths [ docPath ] = { } ;
164+ }
165+
166+ const routeDoc : any = page . paths [ docPath ] [ httpMethod ] = { } ;
167+
168+ routeDoc . parameters = [ ] ;
169+ routeDoc . consumes = [ ] ;
170+ routeDoc . produces = [ 'application/json' ] ;
171+ routeDoc . responses = {
172+ 200 : {
173+ description : 'Server alive. This does not mean that the query was completed succesfully. Check the errors object in the response' ,
174+ } ,
175+ 400 : {
176+ description : 'Authentication Required' ,
177+ } ,
178+ 500 : {
179+ description : 'Server error.' ,
180+ }
181+ } ;
182+
183+ if ( httpMethod === 'post' ) {
184+ routeDoc . consumes . push ( 'application/json' ) ;
185+ }
186+
187+ // TODO: 'push in parameters for header'
188+
189+ routeDoc . parameters . push (
190+ ...buildParametersArray ( {
191+ variableDefinitions : route . queryVariables ,
192+ variableLocation : 'query'
193+ } )
194+ ) ;
195+
196+ routeDoc . parameters . push (
197+ ...buildParametersArray ( {
198+ variableDefinitions : route . pathVariables ,
199+ variableLocation : 'path'
200+ } )
201+ ) ;
202+
203+ routeDoc . parameters . push (
204+ ...buildParametersArray ( {
205+ variableDefinitions : route . bodyVariables ,
206+ variableLocation : 'body'
207+ } )
208+ ) ;
209+ } ) ;
210+
211+ const introspectedVariableDefinitions : any = await describeRouteVariables ( router ) ;
212+
213+ Object . keys ( introspectedVariableDefinitions ) . forEach ( ( variableName ) => {
214+ const variableDefinition : any = introspectedVariableDefinitions [ variableName ] ;
215+
216+ const definitionDoc : any = page . definitions [ variableName ] = { } ;
217+
218+ if ( variableDefinition . kind === 'INPUT_OBJECT' ) {
219+ definitionDoc . type = 'object' ;
220+ definitionDoc . properties = { } ;
221+
222+ variableDefinition . inputFields . forEach ( ( field : any ) => {
223+ const inputDoc : any = definitionDoc . properties [ field . name ] = { } ;
224+
225+ inputDoc . type = 'string' ;
226+ inputDoc . description = field . type . description ;
227+
228+ if ( field . type . kind === 'ENUM' ) {
229+ inputDoc . enum = [ ] ;
230+
231+ field . type . enumValues . forEach ( ( enumValue : any ) => {
232+ inputDoc . enum . push ( enumValue . name ) ;
233+ } ) ;
234+ }
235+ } ) ;
236+ }
237+
238+ if ( variableDefinition . kind === 'ENUM' ) {
239+ definitionDoc . type = 'string' ;
240+ definitionDoc . enum = [ ] ;
241+
242+ variableDefinition . enumValues . forEach ( ( enumValue : any ) => {
243+ definitionDoc . enum . push ( enumValue . name ) ;
244+ } ) ;
245+ }
246+ } ) ;
247+
248+ return page ;
249+ } catch ( error ) {
250+ return {
251+ error : error . message ,
252+ } ;
253+ }
127254 }
128255
129256 withOptions ( options : { } ) : this {
130257 return this ;
131258 }
132259
133260 asExpressRoute ( ) : ( req : express . Request , res : express . Response ) => void {
134- const doc = this . generateDocumentation ( ) ;
261+ const generateDoc = this . generateDocumentation ( ) ;
135262
136263 return ( req : express . Request , res : express . Response ) => {
137- res
138- . status ( 200 )
139- . json ( doc ) ;
264+ generateDoc . then (
265+ ( doc ) => {
266+ res
267+ . status ( 200 )
268+ . json ( doc ) ;
269+ }
270+ ) ;
140271 }
141272 }
142273
0 commit comments