Skip to content

Commit 6b12cc3

Browse files
authored
[CDSADAPTERS-2186] Fixed openapi filename issue when there's only one service in the CDL source (#60)
* Fixed openapi filename issue when there's only one service in the CDL source file * Update test cases to handle generator function in openAPI * Add changelog entry * Add version in changelog * Fixed openapi filename issue when there's only one service in the CDL source * Fix and add comment
1 parent e4a25cd commit 6b12cc3

File tree

3 files changed

+68
-130
lines changed

3 files changed

+68
-130
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
44
This project adheres to [Semantic Versioning](http://semver.org/).
55
The format is based on [Keep a Changelog](http://keepachangelog.com/).
66

7+
## Version 1.1.2 - tbd
78

89
### Fixed
910

lib/compile/index.js

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@ module.exports = function processor(csn, options = {}) {
1414
let openApiDocs = {};
1515

1616
if (csdl[Symbol.iterator]) { // generator function means multiple services
17-
openApiDocs = _getOpenApiForMultipleServices(csdl, csn, options)
17+
openApiDocs = _getOpenApiForMultipleServices(csdl, csn, options);
18+
return _iterate(openApiDocs);
1819
} else {
19-
const openApiOptions = toOpenApiOptions(csdl, csn, options)
20-
openApiDocs = _getOpenApi(csdl, openApiOptions);
20+
const openApiOptions = toOpenApiOptions(csdl, csn, options);
21+
const serviceName = csdl.$EntityContainer.replace(/\.[^.]+$/, "");
22+
openApiDocs = _getOpenApi(csdl, openApiOptions,serviceName);
23+
if(Object.keys(openApiDocs).length===1){
24+
return openApiDocs[serviceName];
25+
}else{
26+
return _iterate(openApiDocs);
27+
}
2128
}
22-
23-
return _iterate(openApiDocs);
2429
}
2530

2631
function _getOpenApiForMultipleServices(csdl, csn, options) {
@@ -46,12 +51,6 @@ function* _iterate(openApiDocs) {
4651
}
4752
}
4853

49-
function nameParts(qualifiedName) {
50-
const pos = qualifiedName.lastIndexOf('.');
51-
console.assert(pos > 0, 'Invalid qualified name ' + qualifiedName);
52-
return qualifiedName.substring(0, pos);
53-
}
54-
5554
function _getOpenApi(csdl, options, serviceName = "") {
5655
const openApiDocs = {};
5756
let filename;
@@ -70,10 +69,6 @@ function _getOpenApi(csdl, options, serviceName = "") {
7069

7170
const openapi = csdl2openapi.csdl2openapi(csdl, sOptions);
7271

73-
if(!serviceName) {
74-
serviceName= nameParts(csdl.$EntityContainer);
75-
}
76-
7772
if (protocols.length > 1) {
7873
filename = serviceName + "." + protocol;
7974
} else {

test/lib/compile/openapi.test.js

Lines changed: 57 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ const SCENARIO = Object.freeze({
1212

1313
function checkAnnotations(csn, annotations, scenario = SCENARIO.positive, property = '') {
1414
const openApi = toOpenApi(csn);
15-
if (openApi.next) {
16-
const { value } = openApi.next();
17-
const schemas = Object.entries(value[0].components.schemas).filter(([key]) => key.startsWith('sap.odm.test.A.E1'))
15+
const schemas = Object.entries(openApi.components.schemas).filter(([key]) => key.startsWith('sap.odm.test.A.E1'))
1816
// Test if the openAPI document was generated with some schemas.
19-
expect(value[0].components.schemas).toBeDefined()
20-
expect(value[0]).toBeDefined()
17+
expect(openApi.components.schemas).toBeDefined()
18+
expect(openApi).toBeDefined()
2119
expect(schemas.length > 0).toBeTruthy()
2220

2321
// Expect that not-allowed ODM annotations are unavailable in the schema.
@@ -59,7 +57,7 @@ function checkAnnotations(csn, annotations, scenario = SCENARIO.positive, proper
5957
// Test that no other places contain the ODM extensions in the OpenAPI document.
6058

6159
// components.schemas where the schemas are not from entity E1.
62-
const notE1 = Object.entries(value[0].components.schemas).filter(([key]) => !key.startsWith('sap.odm.test.A.E1'))
60+
const notE1 = Object.entries(openApi.components.schemas).filter(([key]) => !key.startsWith('sap.odm.test.A.E1'))
6361
for (const [, schema] of notE1) {
6462
const schemaString = JSON.stringify(schema)
6563
for (const [annKey] of annotations) {
@@ -68,11 +66,11 @@ function checkAnnotations(csn, annotations, scenario = SCENARIO.positive, proper
6866
}
6967

7068
// all other components of the OpenAPI document except the schemas.
71-
const openApiNoSchemas = JSON.stringify({ ...value[0], components: { parameters: { ...value[0].components.parameters }, responses: { ...value[0].components.responses } } })
69+
const openApiNoSchemas = JSON.stringify({ ...openApi, components: { parameters: { ...openApi.components.parameters }, responses: { ...openApi.components.responses } } })
7270
for (const [annKey] of annotations) {
7371
expect(openApiNoSchemas).not.toContain(annKey)
7472
}
75-
}
73+
7674
}
7775

7876
describe('OpenAPI export', () => {
@@ -90,12 +88,9 @@ describe('OpenAPI export', () => {
9088
service A {entity E { key ID : UUID; };};`
9189
);
9290
const openapi = toOpenApi(csn);
93-
if (openapi.next) {
94-
const { value } = openapi.next();
95-
expect(value[0]).toMatchObject(someOpenApi);
96-
// UUID elements are not required
97-
expect(value[0].components.schemas['A.E-create']).not.toHaveProperty('required');
98-
}
91+
expect(openapi).toMatchObject(someOpenApi);
92+
// UUID elements are not required
93+
expect(openapi.components.schemas['A.E-create']).not.toHaveProperty('required');
9994
});
10095

10196
test('one service, namespace', () => {
@@ -104,10 +99,7 @@ describe('OpenAPI export', () => {
10499
service A {entity E { key ID : UUID; };};`
105100
);
106101
const openapi = toOpenApi(csn);
107-
if (openapi.next) {
108-
const { value } = openapi.next();
109-
expect(value[0]).toMatchObject(someOpenApi);
110-
}
102+
expect(openapi).toMatchObject(someOpenApi);
111103
});
112104

113105
test('one service, multiple protocols', () => {
@@ -154,11 +146,8 @@ service CatalogService {
154146
`);
155147

156148
const openAPI = toOpenApi(csn);
157-
if (openAPI.next) {
158-
const { value } = openAPI.next();
159-
expect(value[0]).toBeDefined();
160-
expect(value[0].tags.length).toBe(1);
161-
}
149+
expect(openAPI).toBeDefined();
150+
expect(openAPI.tags.length).toBe(1);
162151
});
163152

164153
test('multiple services', () => {
@@ -169,16 +158,10 @@ service CatalogService {
169158
expect(() => toOpenApi(csn, { service: 'foo' })).toThrowError(/no service/si)
170159

171160
let openapi = toOpenApi(csn, { service: 'A' });
172-
if (openapi.next) {
173-
const { value } = openapi.next();
174-
expect(value[0]).toMatchObject(someOpenApi);
175-
}
161+
expect(openapi).toMatchObject(someOpenApi);
176162

177163
openapi = toOpenApi(csn, { service: 'B' });
178-
if (openapi.next) {
179-
const { value } = openapi.next();
180-
expect(value[0]).toMatchObject(someOpenApi);
181-
}
164+
expect(openapi).toMatchObject(someOpenApi);
182165

183166
openapi = toOpenApi(csn, { service: 'all' });
184167
const filesFound = new Set();
@@ -198,16 +181,11 @@ service CatalogService {
198181
expect(() => toOpenApi(csn, { service: 'foo' })).toThrowError(/no service/si)
199182

200183
let openapi = toOpenApi(csn, { service: 'com.sap.A' });
201-
if (openapi.next) {
202-
const { value } = openapi.next();
203-
expect(value[0]).toMatchObject(someOpenApi);
204-
}
184+
expect(openapi).toMatchObject(someOpenApi);
205185

206186
openapi = toOpenApi(csn, { service: 'com.sap.B' });
207-
if (openapi.next) {
208-
const { value } = openapi.next();
209-
expect(value[0]).toMatchObject(someOpenApi);
210-
}
187+
expect(openapi).toMatchObject(someOpenApi);
188+
211189
openapi = toOpenApi(csn, { service: 'all' });
212190
const filesFound = new Set();
213191
for (const [content, metadata] of openapi) {
@@ -240,62 +218,44 @@ service CatalogService {
240218
service B {entity F { key ID : UUID; };};`
241219
);
242220
let openapi = toOpenApi(csn, { service: 'A' });
243-
if (openapi.next) {
244-
const { value } = openapi.next();
245-
expect(value[0]).toMatchObject({ servers: [{ url: '/a' }] });
246-
expect(value[0].info.description).toBe("Use @Core.LongDescription: '...' on your CDS service to provide a meaningful description.")
247-
}
221+
expect(openapi).toMatchObject({ servers: [{ url: '/a' }] });
222+
expect(openapi.info.description).toBe("Use @Core.LongDescription: '...' on your CDS service to provide a meaningful description.")
248223

249224
openapi = toOpenApi(csn, { service: 'A', 'openapi:url': 'http://foo.bar:8080' });
250-
if (openapi.next) {
251-
const { value } = openapi.next();
252-
expect(value[0]).toMatchObject({ servers: [{ url: 'http://foo.bar:8080' }] });
253-
}
225+
expect(openapi).toMatchObject({ servers: [{ url: 'http://foo.bar:8080' }] });
226+
254227

255228
openapi = toOpenApi(csn, { service: 'A', 'openapi:url': 'http://foo.bar:8080//${service-path}/foo' });
256-
if (openapi.next) {
257-
const { value } = openapi.next();
258-
expect(value[0]).toMatchObject({ servers: [{ url: 'http://foo.bar:8080/a/foo' }] });
259-
}
229+
expect(openapi).toMatchObject({ servers: [{ url: 'http://foo.bar:8080/a/foo' }] });
230+
260231
});
261232

262233
test('options: diagram', () => {
263234
const csn = cds.compile.to.csn(`
264235
service A {entity E { key ID : UUID; };};`
265236
);
266237
let openapi = toOpenApi(csn);
267-
if (openapi.next) {
268-
const { value } = openapi.next();
269-
expect(value[0].info.description).not.toMatch(/yuml.*diagram/i);
270-
}
238+
expect(openapi.info.description).not.toMatch(/yuml.*diagram/i);
239+
271240
openapi = toOpenApi(csn, { 'openapi:diagram': true });
272-
if (openapi.next) {
273-
const { value } = openapi.next();
274-
expect(value[0].info.description).toMatch(/yuml.*diagram/i);
275-
}
241+
expect(openapi.info.description).toMatch(/yuml.*diagram/i);
276242
});
277243

278244
test('options: servers', () => {
279245
const csn = cds.compile.to.csn(`
280246
service A {entity E { key ID : UUID; };};`
281247
);
282248
const serverObj = "[{\n \"url\": \"https://{customerId}.saas-app.com:{port}/v2\",\n \"variables\": {\n \"customerId\": \"demo\",\n \"description\": \"Customer ID assigned by the service provider\"\n }\n}]"
283-
const openapi = toOpenApi(csn, { 'openapi:servers': serverObj });
284-
if (openapi.next) {
285-
const { value } = openapi.next();
286-
expect(value[0].servers).toBeTruthy();
287-
}
249+
const openapi = toOpenApi(csn, { 'openapi:servers': serverObj })
250+
expect(openapi.servers).toBeTruthy();
288251
});
289252

290253
test('options: odata-version check server URL', () => {
291254
const csn = cds.compile.to.csn(`
292255
service A {entity E { key ID : UUID; };};`
293256
);
294257
const openapi = toOpenApi(csn, { 'odata-version': '4.0' });
295-
if (openapi.next) {
296-
const { value } = openapi.next();
297-
expect(value[0].servers[0].url).toMatch('odata');
298-
}
258+
expect(openapi.servers[0].url).toMatch('odata');
299259
});
300260

301261
test('options: Multiple servers', () => {
@@ -304,11 +264,8 @@ service CatalogService {
304264
);
305265
const serverObj = "[{\n \"url\": \"https://{customer1Id}.saas-app.com:{port}/v2\",\n \"variables\": {\n \"customer1Id\": \"demo\",\n \"description\": \"Customer1 ID assigned by the service provider\"\n }\n}, {\n \"url\": \"https://{customer2Id}.saas-app.com:{port}/v2\",\n \"variables\": {\n \"customer2Id\": \"demo\",\n \"description\": \"Customer2 ID assigned by the service provider\"\n }\n}]"
306266
const openapi = toOpenApi(csn, { 'openapi:servers': serverObj });
307-
if (openapi.next) {
308-
const { value } = openapi.next();
309-
expect(value[0].servers).toBeTruthy();
310-
expect(value[0].servers[0].url).toMatch('https://{customer1Id}.saas-app.com:{port}/v2')
311-
}
267+
expect(openapi.servers).toBeTruthy();
268+
expect(openapi.servers[0].url).toMatch('https://{customer1Id}.saas-app.com:{port}/v2')
312269
});
313270

314271

@@ -330,13 +287,10 @@ service CatalogService {
330287
service A {entity E { key ID : UUID; };};`
331288
);
332289
const openapi = toOpenApi(csn, { 'openapi:config-file': path.resolve("./test/lib/compile/data/configFile.json") });
333-
if (openapi.next) {
334-
const { value } = openapi.next();
335-
expect(value[0].servers).toBeTruthy();
336-
expect(value[0]).toMatchObject({ servers: [{ url: 'http://foo.bar:8080' }, { url: "http://foo.bar:8080/a/foo" }] });
337-
expect(value[0].info.description).toMatch(/yuml.*diagram/i);
338-
expect(value[0]['x-odata-version']).toMatch('4.1');
339-
}
290+
expect(openapi.servers).toBeTruthy();
291+
expect(openapi).toMatchObject({ servers: [{ url: 'http://foo.bar:8080' }, { url: "http://foo.bar:8080/a/foo" }] });
292+
expect(openapi.info.description).toMatch(/yuml.*diagram/i);
293+
expect(openapi['x-odata-version']).toMatch('4.1');
340294
});
341295

342296
test('options: config-file - with inline options, inline options given precedence', () => {
@@ -350,13 +304,10 @@ service CatalogService {
350304
'openapi:diagram': "false"
351305
}
352306
const openapi = toOpenApi(csn, options);
353-
if (openapi.next) {
354-
const { value } = openapi.next();
355-
expect(value[0].info.title).toMatch(/http:\/\/example.com:8080/i)
356-
expect(value[0].info.description).not.toMatch(/yuml.*diagram/i);
357-
expect(value[0]['x-odata-version']).toMatch('4.0');
358-
expect(value[0]).toMatchObject({ servers: [{ url: 'http://foo.bar:8080' }, { url: "http://foo.bar:8080/a/foo" }] });
359-
}
307+
expect(openapi.info.title).toMatch(/http:\/\/example.com:8080/i)
308+
expect(openapi.info.description).not.toMatch(/yuml.*diagram/i);
309+
expect(openapi['x-odata-version']).toMatch('4.0');
310+
expect(openapi).toMatchObject({ servers: [{ url: 'http://foo.bar:8080' }, { url: "http://foo.bar:8080/a/foo" }] });
360311
});
361312

362313
test('annotations: root entity property', () => {
@@ -370,13 +321,10 @@ service CatalogService {
370321
`)
371322

372323
const openAPI = toOpenApi(csn)
373-
if (openAPI.next) {
374-
const { value } = openAPI.next();
375-
expect(value[0]).toBeDefined()
376-
expect(value[0].components.schemas["sap.odm.test.A.E1"]).toMatchObject({ "x-sap-root-entity": true })
377-
expect(value[0].components.schemas["sap.odm.test.A.E1-create"]["x-sap-root-entity"]).toBeUndefined()
378-
expect(value[0].components.schemas["sap.odm.test.A.E1-update"]["x-sap-root-entity"]).toBeUndefined()
379-
}
324+
expect(openAPI).toBeDefined()
325+
expect(openAPI.components.schemas["sap.odm.test.A.E1"]).toMatchObject({ "x-sap-root-entity": true })
326+
expect(openAPI.components.schemas["sap.odm.test.A.E1-create"]["x-sap-root-entity"]).toBeUndefined()
327+
expect(openAPI.components.schemas["sap.odm.test.A.E1-update"]["x-sap-root-entity"]).toBeUndefined()
380328
});
381329

382330
test('odm annotations: entity name and oid property', () => {
@@ -482,12 +430,9 @@ service CatalogService {
482430
}
483431
}`);
484432
const openAPI = toOpenApi(csn);
485-
if (openAPI.next) {
486-
const { value } = openAPI.next();
487-
expect(value[0].externalDocs).toBeDefined();
488-
expect(value[0].externalDocs.description).toBe('API Guide');
489-
expect(value[0].externalDocs.url).toBe('https://help.sap.com/docs/product/123.html');
490-
}
433+
expect(openAPI.externalDocs).toBeDefined();
434+
expect(openAPI.externalDocs.description).toBe('API Guide');
435+
expect(openAPI.externalDocs.url).toBe('https://help.sap.com/docs/product/123.html');
491436
}
492437
);
493438

@@ -530,19 +475,16 @@ service CatalogService {
530475
531476
}`);
532477
const openAPI = toOpenApi(csn);
533-
if(openAPI.next){
534-
const {value}=openAPI.next();
535-
expect(value[0]).toBeDefined();
536-
expect(value[0]['x-sap-compliance-level']).toBe('sap:base:v1');
537-
expect(value[0]['x-sap-ext-overview'].name).toBe('Communication Scenario');
538-
expect(value[0]['x-sap-ext-overview'].values.text).toBe('Planning Calendar API Integration');
539-
expect(value[0]['x-sap-ext-overview'].values.format).toBe('plain');
540-
expect(value[0].components.schemas["sap.OpenAPI.test.A.E1"]["x-sap-dpp-is-potentially-sensitive"]).toBe('true');
541-
expect(value[0].paths["/F1"].get["x-sap-operation-intent"]).toBe('read-collection for function');
542-
expect(value[0].paths["/A1"].post["x-sap-operation-intent"]).toBe('read-collection for action');
543-
expect(value[0].paths["/F1"].get["x-sap-deprecated-operation"].deprecationDate).toBe('2022-12-31');
544-
expect(value[0].paths["/F1"].get["x-sap-deprecated-operation"].successorOperationId).toBe('successorOperation');
545-
expect(value[0].paths["/F1"].get["x-sap-deprecated-operation"].notValidKey).toBeUndefined();
546-
}
478+
expect(openAPI).toBeDefined();
479+
expect(openAPI['x-sap-compliance-level']).toBe('sap:base:v1');
480+
expect(openAPI['x-sap-ext-overview'].name).toBe('Communication Scenario');
481+
expect(openAPI['x-sap-ext-overview'].values.text).toBe('Planning Calendar API Integration');
482+
expect(openAPI['x-sap-ext-overview'].values.format).toBe('plain');
483+
expect(openAPI.components.schemas["sap.OpenAPI.test.A.E1"]["x-sap-dpp-is-potentially-sensitive"]).toBe('true');
484+
expect(openAPI.paths["/F1"].get["x-sap-operation-intent"]).toBe('read-collection for function');
485+
expect(openAPI.paths["/A1"].post["x-sap-operation-intent"]).toBe('read-collection for action');
486+
expect(openAPI.paths["/F1"].get["x-sap-deprecated-operation"].deprecationDate).toBe('2022-12-31');
487+
expect(openAPI.paths["/F1"].get["x-sap-deprecated-operation"].successorOperationId).toBe('successorOperation');
488+
expect(openAPI.paths["/F1"].get["x-sap-deprecated-operation"].notValidKey).toBeUndefined();
547489
});
548490
});

0 commit comments

Comments
 (0)