@@ -12,12 +12,13 @@ import forEach from 'lodash/forEach';
12
12
import { OpenApi } from '../types/openapi' ;
13
13
import set from 'lodash/set' ;
14
14
import { map , omit , isEqual , reduce } from 'lodash' ;
15
- import { attributeValidations } from './utils' ;
15
+ import { attributeValidations , resolveRef } from './utils' ;
16
16
17
17
18
18
/**
19
19
* Generate Swagger schema content describing the specified Sails attribute.
20
- * TODO: add test to this function
20
+ *
21
+ * XXX TODO: add test to this function
21
22
*
22
23
* @see https://swagger.io/docs/specification/data-models/
23
24
* @param {Record<string, any> } attribute Sails model attribute specification as per `Model.js` file
@@ -155,31 +156,119 @@ export const generateAttributeSchema = (attribute: Sails.AttributeDefinition): O
155
156
* Generate the OpenAPI schemas for the foreign key values used to reference
156
157
* ORM records for the associations of the specified Sails Model.
157
158
*
159
+ * Used for 'replace' REST blueprint.
160
+ *
158
161
* @param model
159
162
* @param models
160
163
*/
161
- export const generateModelAssociationFKAttributeSchemas = ( model : SwaggerSailsModel , models : NameKeyMap < SwaggerSailsModel > ) : OpenApi . UpdatedSchema [ ] => {
164
+ export const generateModelAssociationFKAttributeSchemas = ( model : SwaggerSailsModel ,
165
+ aliasesToInclude : string [ ] | undefined , models : NameKeyMap < SwaggerSailsModel > ) : OpenApi . UpdatedSchema [ ] => {
162
166
163
167
if ( ! model . associations ) { return [ ] ; }
164
168
165
- const ret = model . associations . map ( association => {
169
+ return model . associations . map ( association => {
170
+
171
+ if ( ! aliasesToInclude || aliasesToInclude . indexOf ( association . alias ) < 0 ) return ;
166
172
167
173
const targetModelIdentity = association . type === 'model' ? association . model : association . collection ;
168
174
const targetModel = models [ targetModelIdentity ! ] ;
169
175
170
176
if ( ! targetModel ) {
171
- return { } ; // data structure integrity issue should not occur
177
+ return ; // data structure integrity issue should not occur
172
178
}
173
179
180
+ const description = association . type === 'model' ?
181
+ `**${ model . globalId } ** record's foreign key value to use as the replacement for this attribute`
182
+ : `**${ model . globalId } ** record's foreign key values to use as the replacement for this collection` ;
183
+
174
184
const targetFKAttribute = targetModel . attributes [ targetModel . primaryKey ] ;
175
185
return generateAttributeSchema ( {
176
186
...targetFKAttribute ,
177
- description : `**${ model . globalId } ** record's foreign key value (**${ association . alias } ** association${ targetFKAttribute . description ? '; ' + targetFKAttribute . description : '' } )`
187
+ autoMigrations : {
188
+ ...( targetFKAttribute . autoMigrations || { } ) ,
189
+ autoIncrement : false , // autoIncrement not relevant for FK parameter
190
+ } ,
191
+ description : `${ description } (**${ association . alias } ** association${ targetFKAttribute . description ? '; ' + targetFKAttribute . description : '' } )`
178
192
} ) ;
179
193
180
- } ) ;
194
+ } )
195
+ . filter ( parameter => parameter ) as OpenApi . UpdatedSchema [ ] ;
196
+
197
+ }
198
+
199
+ /**
200
+ * Generate the OpenAPI parameters for the foreign key values used to reference
201
+ * ORM records for the associations of the specified Sails Model.
202
+ *
203
+ * Used for 'replace' shortcut blueprint.
204
+ *
205
+ * @param model
206
+ * @param aliasesToInclude
207
+ * @param models
208
+ */
209
+ export const generateModelAssociationFKAttributeParameters = ( model : SwaggerSailsModel ,
210
+ aliasesToInclude : string [ ] | undefined , models : NameKeyMap < SwaggerSailsModel > ) : OpenApi . Parameter [ ] => {
211
+
212
+ if ( ! model . associations ) { return [ ] ; }
213
+
214
+ return model . associations . map ( association => {
215
+
216
+ if ( ! aliasesToInclude || aliasesToInclude . indexOf ( association . alias ) < 0 ) return ;
217
+
218
+ const targetModelIdentity = association . type === 'model' ? association . model : association . collection ;
219
+ const targetModel = models [ targetModelIdentity ! ] ;
220
+
221
+ if ( ! targetModel ) {
222
+ return ; // data structure integrity issue should not occur
223
+ }
224
+
225
+ const description = association . type === 'model' ?
226
+ `**${ model . globalId } ** record's foreign key value to use as the replacement for this attribute`
227
+ : `**${ model . globalId } ** record's foreign key values to use as the replacement for this collection` ;
228
+
229
+ const targetFKAttribute = targetModel . attributes [ targetModel . primaryKey ] ;
230
+ const targetFKAttributeSchema = generateAttributeSchema ( {
231
+ ...targetFKAttribute ,
232
+ autoMigrations : {
233
+ ...( targetFKAttribute . autoMigrations || { } ) ,
234
+ autoIncrement : false , // autoIncrement not relevant for FK parameter
235
+ } ,
236
+ description : `${ description } (**${ association . alias } ** association${ targetFKAttribute . description ? '; ' + targetFKAttribute . description : '' } )`
237
+ } ) ;
181
238
182
- return ret ;
239
+ return {
240
+ in : 'query' as OpenApi . ParameterLocation ,
241
+ name : association . alias ,
242
+ description : targetFKAttributeSchema . description ,
243
+ schema : {
244
+ type : 'array' ,
245
+ items : targetFKAttributeSchema ,
246
+ } ,
247
+ } as OpenApi . Parameter ;
248
+
249
+ } )
250
+ . filter ( parameter => parameter ) as OpenApi . Parameter [ ] ;
251
+
252
+ }
253
+
254
+ export const generateSchemaAsQueryParameters = ( schema : OpenApi . UpdatedSchema ) : OpenApi . Parameter [ ] => {
255
+
256
+ const required = schema . required || [ ] ;
257
+
258
+ return map ( schema . properties || { } , ( property , name ) => {
259
+ const parameter = {
260
+ in : 'query' as OpenApi . ParameterLocation ,
261
+ name : name ,
262
+ schema : property ,
263
+ } as OpenApi . Parameter ;
264
+ if ( ( property as OpenApi . UpdatedSchema ) . description ) {
265
+ parameter . description = ( property as OpenApi . UpdatedSchema ) . description ;
266
+ }
267
+ if ( required . indexOf ( name ) >= 0 ) {
268
+ parameter . required = true ;
269
+ }
270
+ return parameter ;
271
+ } ) ;
183
272
184
273
}
185
274
@@ -237,7 +326,7 @@ export const generateSchemas = (models: NameKeyMap<SwaggerSailsModel>): NameKeyM
237
326
* @param models
238
327
*/
239
328
export const generatePaths = ( routes : SwaggerRouteInfo [ ] , templates : BlueprintActionTemplates ,
240
- defaultsValues : Defaults , specification : Omit < OpenApi . OpenApi , 'paths' > ,
329
+ defaultsValues : Defaults , specification : OpenApi . OpenApi ,
241
330
models : NameKeyMap < SwaggerSailsModel > ) : OpenApi . Paths => {
242
331
243
332
const paths = { } ;
@@ -359,16 +448,29 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
359
448
} ,
360
449
361
450
addModelBodyParam : ( ) => {
362
- if ( pathEntry . requestBody ) return ;
363
- pathEntry . requestBody = {
364
- description : subst ( 'JSON dictionary representing the {globalId} instance to create.' ) ,
365
- required : true ,
366
- content : {
367
- 'application/json' : {
368
- schema : { "$ref" : "#/components/schemas/" + route . model ! . identity }
451
+ if ( route . actionType === 'shortcutBlueprint' ) {
452
+ const schema = specification ! . components ! . schemas ?. [ route . model ! . identity ] ;
453
+ if ( schema ) {
454
+ const resolvedSchema = resolveRef ( specification , schema ) ;
455
+ if ( resolvedSchema ) {
456
+ generateSchemaAsQueryParameters ( resolvedSchema as OpenApi . UpdatedSchema ) . map ( p => {
457
+ if ( isParam ( 'query' , p . name ) ) return ;
458
+ pathEntry . parameters . push ( p ) ;
459
+ } ) ;
460
+ }
461
+ }
462
+ } else {
463
+ if ( pathEntry . requestBody ) return ;
464
+ pathEntry . requestBody = {
465
+ description : subst ( 'JSON dictionary representing the {globalId} instance to create.' ) ,
466
+ required : true ,
467
+ content : {
468
+ 'application/json' : {
469
+ schema : { "$ref" : "#/components/schemas/" + route . model ! . identity }
470
+ } ,
369
471
} ,
370
- } ,
371
- } ;
472
+ } ;
473
+ }
372
474
} ,
373
475
374
476
addResultOfArrayOfModels : ( ) => {
@@ -400,13 +502,13 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
400
502
} ,
401
503
402
504
addAssociationFKPathParam : ( ) => {
403
- if ( isParam ( 'path' , 'fk ' ) ) return ; // pre-defined/pre-configured --> skip
505
+ if ( isParam ( 'path' , 'childid ' ) ) return ; // pre-defined/pre-configured --> skip
404
506
pathEntry . parameters . push ( {
405
507
in : 'path' ,
406
- name : 'fk ' ,
508
+ name : 'childid ' ,
407
509
required : true ,
408
510
schema : {
409
- oneOf : generateModelAssociationFKAttributeSchemas ( route . model ! , models ) ,
511
+ oneOf : generateModelAssociationFKAttributeSchemas ( route . model ! , route . associationAliases , models ) ,
410
512
} as OpenApi . UpdatedSchema ,
411
513
description : 'The desired target association record\'s foreign key value'
412
514
} ) ;
@@ -460,26 +562,37 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
460
562
} ,
461
563
462
564
addFksBodyParam : ( ) => {
463
- if ( pathEntry . requestBody ) return ;
464
- pathEntry . requestBody = {
465
- description : 'The primary key values (usually IDs) of the child records to use as the new members of this collection' ,
466
- required : true ,
467
- content : {
468
- 'application/json' : {
469
- schema : {
470
- type : 'array' ,
471
- items : {
472
- oneOf : generateModelAssociationFKAttributeSchemas ( route . model ! , models ) ,
473
- }
474
- } as OpenApi . UpdatedSchema ,
565
+ if ( route . actionType === 'shortcutBlueprint' ) {
566
+ generateModelAssociationFKAttributeParameters ( route . model ! , route . associationAliases , models ) . map ( p => {
567
+ if ( ! route . associationAliases || route . associationAliases . indexOf ( p . name ) < 0 ) return ;
568
+ if ( isParam ( 'query' , p . name ) ) return ;
569
+ pathEntry . parameters . push ( p ) ;
570
+ } ) ;
571
+ } else {
572
+ if ( pathEntry . requestBody ) return ;
573
+ pathEntry . requestBody = {
574
+ description : 'The primary key values (usually IDs) of the child records to use as the new members of this collection' ,
575
+ required : true ,
576
+ content : {
577
+ 'application/json' : {
578
+ schema : {
579
+ type : 'array' ,
580
+ items : {
581
+ oneOf : generateModelAssociationFKAttributeSchemas ( route . model ! , route . associationAliases , models ) ,
582
+ }
583
+ } as OpenApi . UpdatedSchema ,
584
+ } ,
475
585
} ,
476
- } ,
477
- } ;
586
+ } ;
587
+ }
478
588
} ,
479
589
480
590
addShortCutBlueprintRouteNote : ( ) => {
481
591
if ( route . actionType !== 'shortcutBlueprint' ) { return ; }
482
592
pathEntry . summary += ' *' ;
593
+ if ( route . action === 'replace' ) {
594
+ pathEntry . description += `\n\nOnly one of the query parameters, that matches the **association** path parameter, should be specified.` ;
595
+ }
483
596
pathEntry . description += `\n\n(\\*) Note that this is a`
484
597
+ ` [Sails blueprint shortcut route](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes#?shortcut-blueprint-routes)`
485
598
+ ` (recommended for **development-mode only**)` ;
@@ -650,7 +763,7 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
650
763
const dereferenced = components . parameters ! [ ref ] ;
651
764
return dereferenced ? dereferenced : parameter ;
652
765
}
653
- } ) ;
766
+ } ) as OpenApi . Parameter [ ] ;
654
767
655
768
// now add patternVariables that don't already exist
656
769
route . variables . map ( v => {
0 commit comments