Skip to content

Commit 7e72538

Browse files
author
Dekel Barzilay
committed
Added support for array service.id
1 parent 776cf64 commit 7e72538

File tree

9 files changed

+138
-91
lines changed

9 files changed

+138
-91
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ __Options:__
119119
- `tags` (*optional*) - Give multiple tags
120120
- `model` (*optional*) - Override model that is parsed from path
121121
- `modelName` (*optional*) - Override modelName that is parsed from path
122-
- `idType` (*optional*) - The swagger type of ids used in paths for this service
122+
- `idType` (*optional*) - The swagger type of ids used in paths for this service. value can be an array of types when `service.id` is set to array.
123123
- `idNames` (*optional*) - Object with path parameter names, to customize the idName on operation / method level
124124
- `get`|`update`|`patch`|`remove` - name of the path parameter for the specific method, use service.id to change it for all
125125
- `definition`(also `schema` for openapi v3) (*optional*) - Swagger definition of the model of the service, will be merged into global definitions (with all additional generated definitions)

lib/openapi.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class OpenApiGenerator {
168168
const pathObj = this.specs.paths;
169169
const basePath = `/${path}`;
170170
const multiOperations = doc.multi || this.config.defaults.multi;
171+
const idSeparator = (service.options && service.options.idSeparator) || ',';
171172

172173
this.applyDefinitionsToSpecs(service, model, modelName);
173174

@@ -187,12 +188,12 @@ class OpenApiGenerator {
187188

188189
let swaggerPath = path;
189190
if (methodIdName) {
190-
swaggerPath += `/{${methodIdName}}`;
191+
swaggerPath += `/${utils.idPathParameters(methodIdName, idSeparator)}`;
191192
}
192193

193194
if (customMethod) {
194195
const withId = path.includes(':__feathersId');
195-
swaggerPath = swaggerPath.replace(':__feathersId', `{${idName}}`);
196+
swaggerPath = swaggerPath.replace(':__feathersId', utils.idPathParameters(idName, idSeparator));
196197
const generator = typeof this.config.defaults.operationGenerators.custom === 'function'
197198
? this.config.defaults.operationGenerators.custom
198199
: this.operationDefaults.custom;

lib/utils.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ exports.security = function security (method, securities, security) {
4444

4545
return [];
4646
};
47+
48+
exports.idPathParameters = function idPathParameters (idName, idSeparator) {
49+
return `{${Array.isArray(idName) ? idName.join(`}${idSeparator}{`) : idName}}`;
50+
};

lib/v2/generator.js

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
const AbstractApiGenerator = require('../openapi');
22
const utils = require('../utils');
33

4+
function idPathParameters (idName, idType, description) {
5+
const idNames = Array.isArray(idName) ? idName : [idName];
6+
const idTypes = Array.isArray(idType) ? idType : [idType];
7+
const params = [];
8+
9+
for (let i = 0; i < idNames.length; i++) {
10+
const name = idNames[i];
11+
12+
params.push({
13+
in: 'path',
14+
name,
15+
description,
16+
type: idTypes[i] || idTypes[0],
17+
required: true
18+
});
19+
}
20+
21+
return params;
22+
}
23+
424
class OpenApiV2Generator extends AbstractApiGenerator {
525
getDefaultSpecs () {
626
return {
@@ -63,13 +83,7 @@ class OpenApiV2Generator extends AbstractApiGenerator {
6383
return {
6484
tags,
6585
description: 'Retrieves a single resource with the given id from the service.',
66-
parameters: [{
67-
description: `ID of ${modelName} to return`,
68-
in: 'path',
69-
required: true,
70-
name: idName,
71-
type: idType
72-
}],
86+
parameters: idPathParameters(idName, idType, `ID of ${modelName} to return`),
7387
responses: {
7488
200: {
7589
description: 'success',
@@ -127,20 +141,14 @@ class OpenApiV2Generator extends AbstractApiGenerator {
127141
return {
128142
tags,
129143
description: 'Updates the resource identified by id using data.',
130-
parameters: [{
131-
description: `ID of ${modelName} to return`,
132-
in: 'path',
133-
required: true,
134-
name: idName,
135-
type: idType
136-
}, {
144+
parameters: idPathParameters(idName, idType, `ID of ${modelName} to return`).concat([{
137145
in: 'body',
138146
name: 'body',
139147
required: true,
140148
schema: {
141149
$ref: `#/definitions/${refs.updateRequest}`
142150
}
143-
}],
151+
}]),
144152
responses: {
145153
200: {
146154
description: 'success',
@@ -198,20 +206,14 @@ class OpenApiV2Generator extends AbstractApiGenerator {
198206
return {
199207
tags,
200208
description: 'Updates the resource identified by id using data.',
201-
parameters: [{
202-
description: `ID of ${modelName} to update`,
203-
in: 'path',
204-
required: true,
205-
name: idName,
206-
type: idType
207-
}, {
209+
parameters: idPathParameters(idName, idType, `ID of ${modelName} to update`).concat([{
208210
in: 'body',
209211
name: 'body',
210212
required: true,
211213
schema: {
212214
$ref: `#/definitions/${refs.patchRequest}`
213215
}
214-
}],
216+
}]),
215217
responses: {
216218
200: {
217219
description: 'success',
@@ -271,13 +273,7 @@ class OpenApiV2Generator extends AbstractApiGenerator {
271273
return {
272274
tags,
273275
description: 'Removes the resource with id.',
274-
parameters: [{
275-
description: `ID of ${modelName} to remove`,
276-
in: 'path',
277-
required: true,
278-
name: idName,
279-
type: idType
280-
}],
276+
parameters: idPathParameters(idName, idType, `ID of ${modelName} to remove`),
281277
responses: {
282278
200: {
283279
description: 'success',
@@ -328,7 +324,6 @@ class OpenApiV2Generator extends AbstractApiGenerator {
328324
const customDoc = {
329325
tags,
330326
description: `A custom ${method} method.`,
331-
parameters: [],
332327
responses: {
333328
200: {
334329
description: 'success'
@@ -345,15 +340,7 @@ class OpenApiV2Generator extends AbstractApiGenerator {
345340
security: utils.security(method, securities, security)
346341
};
347342

348-
if (withId) {
349-
customDoc.parameters[0] = {
350-
description: `ID of ${modelName}`,
351-
in: 'path',
352-
required: true,
353-
name: idName,
354-
type: idType
355-
};
356-
}
343+
customDoc.parameters = withId ? idPathParameters(idName, idType, `ID of ${modelName}`) : [];
357344

358345
if (['post', 'put', 'patch'].includes(httpMethod)) {
359346
const refRequestName = `${method}Request`;

lib/v3/generator.js

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@ function jsonSchemaRef (ref) {
3131
};
3232
}
3333

34+
function idPathParameters (idName, idType, description) {
35+
const idNames = Array.isArray(idName) ? idName : [idName];
36+
const idTypes = Array.isArray(idType) ? idType : [idType];
37+
const params = [];
38+
39+
for (let i = 0; i < idNames.length; i++) {
40+
const name = idNames[i];
41+
42+
params.push({
43+
in: 'path',
44+
name,
45+
description,
46+
schema: {
47+
type: idTypes[i] || idTypes[0]
48+
},
49+
required: true
50+
});
51+
}
52+
53+
return params;
54+
}
55+
3456
class OpenApiV3Generator extends AbstractApiGenerator {
3557
getDefaultSpecs () {
3658
return {
@@ -106,15 +128,7 @@ class OpenApiV3Generator extends AbstractApiGenerator {
106128
return {
107129
tags,
108130
description: 'Retrieves a single resource with the given id from the service.',
109-
parameters: [{
110-
description: `ID of ${modelName} to return`,
111-
in: 'path',
112-
required: true,
113-
name: idName,
114-
schema: {
115-
type: idType
116-
}
117-
}],
131+
parameters: idPathParameters(idName, idType, `ID of ${modelName} to return`),
118132
responses: {
119133
200: {
120134
description: 'success',
@@ -160,15 +174,7 @@ class OpenApiV3Generator extends AbstractApiGenerator {
160174
return {
161175
tags,
162176
description: 'Updates the resource identified by id using data.',
163-
parameters: [{
164-
description: `ID of ${modelName} to update`,
165-
in: 'path',
166-
required: true,
167-
name: idName,
168-
schema: {
169-
type: idType
170-
}
171-
}],
177+
parameters: idPathParameters(idName, idType, `ID of ${modelName} to update`),
172178
requestBody: {
173179
required: true,
174180
content: jsonSchemaRef(refs.updateRequest)
@@ -219,15 +225,7 @@ class OpenApiV3Generator extends AbstractApiGenerator {
219225
return {
220226
tags,
221227
description: 'Updates the resource identified by id using data.',
222-
parameters: [{
223-
description: `ID of ${modelName} to update`,
224-
in: 'path',
225-
required: true,
226-
name: idName,
227-
schema: {
228-
type: idType
229-
}
230-
}],
228+
parameters: idPathParameters(idName, idType, `ID of ${modelName} to update`),
231229
requestBody: {
232230
required: true,
233231
content: jsonSchemaRef(refs.patchRequest)
@@ -278,15 +276,7 @@ class OpenApiV3Generator extends AbstractApiGenerator {
278276
return {
279277
tags,
280278
description: 'Removes the resource with id.',
281-
parameters: [{
282-
description: `ID of ${modelName} to remove`,
283-
in: 'path',
284-
required: true,
285-
name: idName,
286-
schema: {
287-
type: idType
288-
}
289-
}],
279+
parameters: idPathParameters(idName, idType, `ID of ${modelName} to remove`),
290280
responses: {
291281
200: {
292282
description: 'success',
@@ -329,7 +319,6 @@ class OpenApiV3Generator extends AbstractApiGenerator {
329319
const customDoc = {
330320
tags,
331321
description: `A custom ${method} method.`,
332-
parameters: [],
333322
responses: {
334323
200: {
335324
description: 'success'
@@ -345,15 +334,7 @@ class OpenApiV3Generator extends AbstractApiGenerator {
345334
};
346335

347336
if (withId) {
348-
customDoc.parameters[0] = {
349-
description: `ID of ${modelName}`,
350-
in: 'path',
351-
required: true,
352-
name: idName,
353-
schema: {
354-
type: idType
355-
}
356-
};
337+
customDoc.parameters = withId ? idPathParameters(idName, idType, `ID of ${modelName}`) : [];
357338
}
358339

359340
if (['post', 'put', 'patch'].includes(httpMethod)) {

test/utils.test.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-unused-expressions */
22
const { expect } = require('chai');
3-
const { operation, tag, security } = require('../lib/utils');
3+
const { operation, tag, security, idPathParameters } = require('../lib/utils');
44

55
describe('util tests', () => {
66
describe('operation', () => {
@@ -131,4 +131,22 @@ describe('util tests', () => {
131131
expect(security('create', ['all'], securityDefinitions)).to.equals(securityDefinitions);
132132
});
133133
});
134+
135+
describe('idPathParameters', () => {
136+
it('should return id path parameters when idName is string', () => {
137+
expect(idPathParameters('id', ',')).to.deep.equals('{id}');
138+
});
139+
140+
it('should return id path parameters when idName is array', () => {
141+
expect(idPathParameters(['id'], ',')).to.deep.equals('{id}');
142+
});
143+
144+
it('should return id path parameters when idName is array with multiple items', () => {
145+
expect(idPathParameters(['firstId', 'secondId'], ',')).to.deep.equals('{firstId},{secondId}');
146+
});
147+
148+
it('should return id path parameters when idName is array with multiple items and custom idSeparator', () => {
149+
expect(idPathParameters(['firstId', 'secondId'], '|')).to.deep.equals('{firstId}|{secondId}');
150+
});
151+
});
134152
});

test/v3/generator.test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,55 @@ describe('openopi v3 generator', function () {
974974
expect(specs.paths['/message/{rid}'].delete.parameters[0].name).to.equal('rid');
975975
});
976976

977+
describe('array service.id', function () {
978+
it('array service.id should be consumed', function () {
979+
const specs = {};
980+
const gen = new OpenApi3Generator(specs, swaggerOptions);
981+
const service = memory();
982+
service.options.id = ['firstId', 'secondId'];
983+
service.docs = {
984+
definition: messageDefinition
985+
};
986+
987+
gen.addService(service, 'message');
988+
989+
expect(specs.paths['/message/{firstId},{secondId}'].get.parameters[0].schema.type).to.equal('integer');
990+
expect(specs.paths['/message/{firstId},{secondId}'].get.parameters[1].schema.type).to.equal('integer');
991+
});
992+
993+
it('array service.id should be consumed', function () {
994+
const specs = {};
995+
const gen = new OpenApi3Generator(specs, swaggerOptions);
996+
const service = memory();
997+
service.options.id = ['firstId', 'secondId'];
998+
service.docs = {
999+
idType: ['string', 'integer'],
1000+
definition: messageDefinition
1001+
};
1002+
1003+
gen.addService(service, 'message');
1004+
1005+
expect(specs.paths['/message/{firstId},{secondId}'].get.parameters[0].schema.type).to.equal('string');
1006+
expect(specs.paths['/message/{firstId},{secondId}'].get.parameters[1].schema.type).to.equal('integer');
1007+
});
1008+
1009+
it('array service.id with custom service.idSeparator should be consumed', function () {
1010+
const specs = {};
1011+
const gen = new OpenApi3Generator(specs, swaggerOptions);
1012+
const service = memory();
1013+
service.options.id = ['firstId', 'secondId'];
1014+
service.options.idSeparator = '|';
1015+
service.docs = {
1016+
definition: messageDefinition
1017+
};
1018+
1019+
gen.addService(service, 'message');
1020+
1021+
expect(specs.paths['/message/{firstId}|{secondId}'].get.parameters[0].schema.type).to.equal('integer');
1022+
expect(specs.paths['/message/{firstId}|{secondId}'].get.parameters[1].schema.type).to.equal('integer');
1023+
});
1024+
});
1025+
9771026
describe('overwriteTagSpec', function () {
9781027
it('nothing should be overwritten by default', function () {
9791028
specs.tags = [

types/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ declare namespace feathersSwagger {
159159
tags?: string[];
160160
model?: string;
161161
modelName?: string;
162-
idType?: string;
162+
idType?: string | string[];
163163
idNames?: {
164164
get?: string;
165165
update?: string;

types/test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,13 @@ const serviceEmptyRefs: ServiceSwaggerAddon = {
252252
}
253253
};
254254

255+
// array idType
256+
const serviceIdTypeArray: ServiceSwaggerAddon = {
257+
docs: {
258+
idType: ['string'],
259+
}
260+
};
261+
255262
// empty docs object
256263
const serviceEmpty: ServiceSwaggerAddon = {
257264
docs: {}

0 commit comments

Comments
 (0)