@cap-js/ord 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/build.js +4 -3
- package/lib/common/register-compile-targets.js +1 -1
- package/lib/configuration.js +161 -0
- package/lib/constants.js +11 -10
- package/lib/defaults.js +73 -53
- package/lib/integration-dependency.js +3 -4
- package/lib/interop-csn.js +1 -1
- package/lib/ord.js +65 -308
- package/lib/protocol-resolver.js +3 -16
- package/lib/services/mtx-ord-provider-service.js +13 -9
- package/lib/services/ord-service.js +73 -21
- package/lib/templates.js +123 -120
- package/package.json +5 -4
- package/lib/utils.js +0 -12
package/lib/ord.js
CHANGED
|
@@ -1,268 +1,45 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
const cds = require("@sap/cds");
|
|
3
|
+
|
|
4
|
+
const Logger = require("./logger");
|
|
5
|
+
const defaults = require("./defaults");
|
|
6
|
+
const { createAuthConfig } = require("./auth/authentication");
|
|
7
|
+
const Configuration = require("./configuration");
|
|
8
|
+
const { getIntegrationDependencies } = require("./integration-dependency");
|
|
1
9
|
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
ENTITY_RELATIONSHIP_ANNOTATION,
|
|
6
|
-
ORD_ODM_ENTITY_NAME_ANNOTATION,
|
|
7
|
-
} = require("./constants");
|
|
10
|
+
getCustomORDContent,
|
|
11
|
+
compareAndHandleCustomORDContentWithExistingContent,
|
|
12
|
+
} = require("./extend-ord-with-custom");
|
|
8
13
|
const {
|
|
9
14
|
createAPIResourceTemplate,
|
|
10
15
|
createEntityTypeTemplate,
|
|
11
|
-
createEntityTypeMappingsItemTemplate,
|
|
12
16
|
createEventResourceTemplate,
|
|
13
17
|
createGroupsTemplateForService,
|
|
14
18
|
_propagateORDVisibility,
|
|
15
19
|
} = require("./templates");
|
|
16
|
-
const { getIntegrationDependencies } = require("./integration-dependency");
|
|
17
|
-
const {
|
|
18
|
-
getCustomORDContent,
|
|
19
|
-
compareAndHandleCustomORDContentWithExistingContent,
|
|
20
|
-
} = require("./extend-ord-with-custom");
|
|
21
|
-
const { getRFC3339Date } = require("./date");
|
|
22
|
-
const { createAuthConfig } = require("./auth/authentication");
|
|
23
|
-
|
|
24
|
-
const Logger = require("./logger");
|
|
25
|
-
const _ = require("lodash");
|
|
26
|
-
const cds = require("@sap/cds");
|
|
27
|
-
const defaults = require("./defaults");
|
|
28
|
-
const path = require("path");
|
|
29
|
-
|
|
30
|
-
const initializeAppConfig = (csn) => {
|
|
31
|
-
const packageJson = _loadPackageJson();
|
|
32
|
-
const packageName = packageJson.name;
|
|
33
|
-
const appName = _formatAppName(packageName);
|
|
34
|
-
const lastUpdate = getRFC3339Date();
|
|
35
|
-
|
|
36
|
-
const ordNamespace = _getORDNamespace(packageName);
|
|
37
|
-
const eventApplicationNamespace = cds.env?.export?.asyncapi?.applicationNamespace;
|
|
38
|
-
|
|
39
|
-
_validateNamespaces(ordNamespace, eventApplicationNamespace);
|
|
40
|
-
|
|
41
|
-
const { serviceNames, apiResourceNames, apiEndpoints, eventServiceNames, entityTypeTargets } =
|
|
42
|
-
_triageCsnDefinitions(csn);
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
env: cds.env["ord"],
|
|
46
|
-
lastUpdate,
|
|
47
|
-
appName,
|
|
48
|
-
apiEndpoints: Array.from(apiEndpoints),
|
|
49
|
-
eventServiceNames,
|
|
50
|
-
serviceNames,
|
|
51
|
-
apiResourceNames,
|
|
52
|
-
entityTypeTargets: _.uniqBy(entityTypeTargets, CONTENT_MERGE_KEY),
|
|
53
|
-
ordNamespace,
|
|
54
|
-
eventApplicationNamespace,
|
|
55
|
-
packageName,
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
function _loadPackageJson() {
|
|
60
|
-
const packageJsonPath = path.join(cds.root, "package.json");
|
|
61
|
-
if (!cds.utils.exists(packageJsonPath)) {
|
|
62
|
-
throw new Error(`package.json not found in the project root directory`);
|
|
63
|
-
}
|
|
64
|
-
return require(packageJsonPath);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function _formatAppName(packageName) {
|
|
68
|
-
return packageName.replace(/^[@]/, "").replace(/[@/]/g, "-");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function _getORDNamespace(packageName) {
|
|
72
|
-
const vendorNamespace = "customer";
|
|
73
|
-
return cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function _validateNamespaces(ordNamespace, eventApplicationNamespace) {
|
|
77
|
-
if (eventApplicationNamespace && ordNamespace !== eventApplicationNamespace) {
|
|
78
|
-
Logger.warn("ORD and AsyncAPI namespaces should be the same.");
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function _triageCsnDefinitions(csn) {
|
|
83
|
-
const pendingApiResourceNames = [];
|
|
84
|
-
const apiEndpoints = new Set();
|
|
85
|
-
const pendingEventServiceNames = new Set();
|
|
86
|
-
const entityTypeTargets = [];
|
|
87
|
-
const serviceNames = Object.keys(csn.definitions).filter((key) => _isValidService(key, csn.definitions[key]));
|
|
88
|
-
|
|
89
|
-
for (const definitionKey of Object.keys(csn.definitions)) {
|
|
90
|
-
const definitionObj = csn.definitions[definitionKey];
|
|
91
|
-
if (
|
|
92
|
-
definitionKey.includes(BLOCKED_SERVICE_NAME.MTXServices) ||
|
|
93
|
-
definitionKey.includes(BLOCKED_SERVICE_NAME.OpenResourceDiscoveryService)
|
|
94
|
-
) {
|
|
95
|
-
Logger.warn("ORD service name", definitionKey, "is blocked.");
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
switch (definitionObj.kind) {
|
|
99
|
-
case CDS_ELEMENT_KIND.service: {
|
|
100
|
-
const apiResourceName = _handleApiResource(definitionKey, definitionObj);
|
|
101
|
-
if (apiResourceName) pendingApiResourceNames.push(apiResourceName);
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
case CDS_ELEMENT_KIND.entity: {
|
|
105
|
-
const result = _handleEntity(definitionKey, definitionObj);
|
|
106
|
-
if (result) {
|
|
107
|
-
if (result.apiEndpoint) apiEndpoints.add(result.apiEndpoint);
|
|
108
|
-
if (result.entityTypeTarget) entityTypeTargets.push(...result.entityTypeTarget);
|
|
109
|
-
}
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
case CDS_ELEMENT_KIND.event: {
|
|
113
|
-
const event = _handleEvent(serviceNames, definitionKey, definitionObj);
|
|
114
|
-
if (event) pendingEventServiceNames.add(event);
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
case CDS_ELEMENT_KIND.action:
|
|
118
|
-
case CDS_ELEMENT_KIND.function: {
|
|
119
|
-
const apiEndpoint = _handleActionOrFunction(definitionKey, definitionObj);
|
|
120
|
-
if (apiEndpoint) apiEndpoints.add(apiEndpoint);
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
serviceNames,
|
|
128
|
-
apiResourceNames: pendingApiResourceNames,
|
|
129
|
-
apiEndpoints: Array.from(apiEndpoints),
|
|
130
|
-
eventServiceNames: [...pendingEventServiceNames],
|
|
131
|
-
entityTypeTargets,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function _handleApiResource(apiResourceName, serviceDefinition) {
|
|
136
|
-
if (
|
|
137
|
-
_shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) ||
|
|
138
|
-
!_isValidService(apiResourceName, serviceDefinition)
|
|
139
|
-
) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
return apiResourceName;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function _shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) {
|
|
146
|
-
const isActionsNotContained = !serviceDefinition.actions || Object.keys(serviceDefinition.actions).length === 0;
|
|
147
|
-
const isFunctionsNotContained =
|
|
148
|
-
!serviceDefinition.functions || Object.keys(serviceDefinition.functions).length === 0;
|
|
149
|
-
const isEntitiesNotContained = !serviceDefinition.entities || Object.keys(serviceDefinition.entities).length === 0;
|
|
150
|
-
const isEventsContained = serviceDefinition.events && Object.keys(serviceDefinition.events).length > 0;
|
|
151
|
-
if (isActionsNotContained && isFunctionsNotContained && isEntitiesNotContained && isEventsContained) {
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function _shouldNotSkipIfServiceProtocolIsNone(keyDefinition) {
|
|
158
|
-
if (keyDefinition["_service"] && keyDefinition["_service"]["@protocol"] === "none") {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function _isBlockedServiceName(key) {
|
|
165
|
-
const blockedServices = [BLOCKED_SERVICE_NAME.MTXServices, BLOCKED_SERVICE_NAME.OpenResourceDiscoveryService];
|
|
166
|
-
return blockedServices.some((blocked) => key.includes(blocked));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function _isValidService(key, definition) {
|
|
170
|
-
const isExternalService = Object.keys(cds).includes("requires") ? Object.keys(cds.requires).includes(key) : false;
|
|
171
|
-
|
|
172
|
-
return (
|
|
173
|
-
definition.kind === CDS_ELEMENT_KIND.service &&
|
|
174
|
-
!definition["@cds.external"] &&
|
|
175
|
-
definition["@protocol"] !== "none" &&
|
|
176
|
-
!isExternalService &&
|
|
177
|
-
!_isBlockedServiceName(key)
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function _handleEntity(key, keyDefinition) {
|
|
182
|
-
if (!key.includes(".texts") && _shouldNotSkipIfServiceProtocolIsNone(keyDefinition)) {
|
|
183
|
-
const apiEndpoint = key;
|
|
184
|
-
let entityTypeTarget = null;
|
|
185
|
-
if (keyDefinition[ORD_ODM_ENTITY_NAME_ANNOTATION] || keyDefinition[ENTITY_RELATIONSHIP_ANNOTATION]) {
|
|
186
|
-
const mapping = createEntityTypeMappingsItemTemplate(keyDefinition);
|
|
187
|
-
if (Array.isArray(mapping)) entityTypeTarget = mapping;
|
|
188
|
-
else if (mapping) entityTypeTarget = [mapping];
|
|
189
|
-
}
|
|
190
|
-
return { apiEndpoint, entityTypeTarget };
|
|
191
|
-
}
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function _handleEvent(serviceNames, key, keyDefinition) {
|
|
196
|
-
if (_shouldNotSkipIfServiceProtocolIsNone(keyDefinition)) {
|
|
197
|
-
for (const serviceName of serviceNames) {
|
|
198
|
-
if (key.startsWith(serviceName + ".")) {
|
|
199
|
-
return serviceName;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function _handleActionOrFunction(key, keyDefinition) {
|
|
207
|
-
if (_shouldNotSkipIfServiceProtocolIsNone(keyDefinition)) {
|
|
208
|
-
return key;
|
|
209
|
-
}
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const _getPolicyLevels = (appConfig) =>
|
|
214
|
-
appConfig.env?.policyLevels ||
|
|
215
|
-
(appConfig.env?.policyLevel && [appConfig.env?.policyLevel]) ||
|
|
216
|
-
defaults.policyLevels;
|
|
217
|
-
|
|
218
|
-
const _getDescription = (appConfig) => appConfig.env?.description || defaults.description;
|
|
219
20
|
|
|
220
21
|
const _getGroups = (csn, appConfig) => {
|
|
221
22
|
return appConfig.serviceNames
|
|
222
|
-
.
|
|
23
|
+
.map((name) => csn.definitions[name])
|
|
24
|
+
.flatMap((srvDefinition) => createGroupsTemplateForService(srvDefinition, appConfig))
|
|
223
25
|
.filter((resource) => !!resource);
|
|
224
26
|
};
|
|
225
27
|
|
|
226
|
-
const _getPackages = (appConfig) => {
|
|
227
|
-
return defaults.packages(appConfig);
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const _getEntityTypes = (appConfig, packageIds) => {
|
|
231
|
-
if (!appConfig.entityTypeTargets?.length) return [];
|
|
232
|
-
|
|
233
|
-
return appConfig.entityTypeTargets.flatMap((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
|
|
234
|
-
};
|
|
235
|
-
|
|
236
28
|
const _getAPIResources = (csn, appConfig, packageIds, accessStrategies) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
csn.definitions[apiResourceName],
|
|
241
|
-
appConfig,
|
|
242
|
-
packageIds,
|
|
243
|
-
accessStrategies,
|
|
244
|
-
),
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
return apiResources;
|
|
29
|
+
return appConfig.apiResourceNames
|
|
30
|
+
.map((name) => csn.definitions[name])
|
|
31
|
+
.flatMap((srvDefinition) => createAPIResourceTemplate(srvDefinition, appConfig, packageIds, accessStrategies));
|
|
248
32
|
};
|
|
249
33
|
|
|
250
34
|
const _getEventResources = (csn, appConfig, packageIds, accessStrategies) => {
|
|
251
|
-
return appConfig.eventServiceNames
|
|
252
|
-
|
|
253
|
-
|
|
35
|
+
return appConfig.eventServiceNames
|
|
36
|
+
.map((name) => csn.definitions[name])
|
|
37
|
+
.flatMap((srvDefinition) => createEventResourceTemplate(srvDefinition, appConfig, packageIds, accessStrategies));
|
|
254
38
|
};
|
|
255
39
|
|
|
256
|
-
function _getOpenResourceDiscovery(appConfig) {
|
|
257
|
-
return appConfig.env?.openResourceDiscovery || defaults.openResourceDiscovery;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function _getConsumptionBundles(appConfig) {
|
|
261
|
-
return appConfig.env?.consumptionBundles || defaults.consumptionBundles(appConfig);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
40
|
const _getProducts = (appConfig) => {
|
|
265
41
|
const productsObj = defaults.products(appConfig.packageName);
|
|
42
|
+
|
|
266
43
|
if (appConfig.env?.products) {
|
|
267
44
|
const customProducts = appConfig.env.products[0];
|
|
268
45
|
if (customProducts?.ordId?.toLowerCase().startsWith("sap")) {
|
|
@@ -273,40 +50,39 @@ const _getProducts = (appConfig) => {
|
|
|
273
50
|
_.assign(productsObj[0], customProducts);
|
|
274
51
|
}
|
|
275
52
|
}
|
|
276
|
-
|
|
53
|
+
|
|
277
54
|
return productsObj;
|
|
278
55
|
};
|
|
279
56
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
57
|
+
const _createDefaultORDDocument = (linkedCsn, appConfig, authConfig) => {
|
|
58
|
+
const products = _getProducts(appConfig);
|
|
59
|
+
const packages = defaults.packages(appConfig, products);
|
|
60
|
+
const packageIds = packages?.map((pkg) => pkg.ordId) || [];
|
|
61
|
+
const entityTypes = _getEntityTypes(appConfig, packageIds);
|
|
62
|
+
const integrationDependencies = getIntegrationDependencies(linkedCsn, appConfig, packageIds);
|
|
63
|
+
const apiResources = _getAPIResources(linkedCsn, appConfig, packageIds, authConfig.accessStrategies);
|
|
64
|
+
const eventResources = _getEventResources(linkedCsn, appConfig, packageIds, authConfig.accessStrategies);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
// Unconditionally added top-level properties
|
|
283
68
|
$schema: "https://open-resource-discovery.github.io/specification/spec-v1/interfaces/Document.schema.json",
|
|
284
|
-
openResourceDiscovery: _getOpenResourceDiscovery(appConfig),
|
|
285
69
|
policyLevels: appConfig.policyLevels,
|
|
286
|
-
|
|
287
|
-
|
|
70
|
+
packages: packages,
|
|
71
|
+
description: appConfig.env?.description || defaults.description,
|
|
72
|
+
openResourceDiscovery: appConfig.env?.openResourceDiscovery || defaults.openResourceDiscovery,
|
|
73
|
+
|
|
74
|
+
// Conditionally added top-level properties
|
|
75
|
+
...(!entityTypes.length ? {} : { entityTypes: entityTypes }),
|
|
76
|
+
...(!apiResources.length ? {} : { apiResources: apiResources }),
|
|
77
|
+
...(!eventResources.length ? {} : { eventResources: eventResources }),
|
|
78
|
+
...(appConfig.existingProductORDId ? {} : { products: [products[0]] }),
|
|
79
|
+
...(!appConfig.serviceNames.length ? {} : { groups: _getGroups(linkedCsn, appConfig) }),
|
|
80
|
+
...(!integrationDependencies.length ? {} : { integrationDependencies: integrationDependencies }),
|
|
81
|
+
...(!appConfig.env?.consumptionBundles?.length ? {} : { consumptionBundles: appConfig.env.consumptionBundles }),
|
|
288
82
|
};
|
|
83
|
+
};
|
|
289
84
|
|
|
290
|
-
|
|
291
|
-
ordDocument.groups = _getGroups(linkedCsn, appConfig);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (appConfig.env?.existingProductORDId) {
|
|
295
|
-
appConfig.existingProductORDId = appConfig.env.existingProductORDId;
|
|
296
|
-
} else {
|
|
297
|
-
ordDocument.products = [_getProducts(appConfig)[0]];
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
ordDocument.packages = _getPackages(appConfig);
|
|
301
|
-
|
|
302
|
-
return ordDocument;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function extractPackageIds(ordDocument) {
|
|
306
|
-
return ordDocument.packages?.map((pkg) => pkg.ordId) || [];
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function _filterUnusedPackages(ordDocument) {
|
|
85
|
+
const _filterUnusedPackages = (ordDocument) => {
|
|
310
86
|
if (!ordDocument.packages?.length) return [];
|
|
311
87
|
|
|
312
88
|
const usedPackageIds = new Set();
|
|
@@ -322,52 +98,33 @@ function _filterUnusedPackages(ordDocument) {
|
|
|
322
98
|
});
|
|
323
99
|
|
|
324
100
|
return ordDocument.packages.filter((pkg) => usedPackageIds.has(pkg.ordId));
|
|
325
|
-
}
|
|
101
|
+
};
|
|
326
102
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
103
|
+
const _getEntityTypes = (appConfig, packageIds) => {
|
|
104
|
+
return appConfig.entityTypeTargets.flatMap((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
|
|
105
|
+
};
|
|
330
106
|
|
|
331
|
-
|
|
107
|
+
const _createAuthConfig = () => {
|
|
332
108
|
const authConfig = createAuthConfig();
|
|
109
|
+
|
|
110
|
+
// Create auth config and fail-closed on configuration errors
|
|
333
111
|
if (authConfig.error) {
|
|
334
112
|
throw new Error(`Authentication configuration error: ${authConfig.error}`);
|
|
335
113
|
}
|
|
336
|
-
const accessStrategies = authConfig.accessStrategies;
|
|
337
|
-
|
|
338
|
-
let ordDocument = createDefaultORDDocument(linkedCsn, appConfig);
|
|
339
|
-
const packageIds = extractPackageIds(ordDocument);
|
|
340
|
-
const entityTypes = _getEntityTypes(appConfig, packageIds);
|
|
341
114
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (appConfig.apiResourceNames.length) {
|
|
347
|
-
const apiResources = _getAPIResources(linkedCsn, appConfig, packageIds, accessStrategies);
|
|
348
|
-
if (apiResources.length) {
|
|
349
|
-
ordDocument.apiResources = apiResources;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (appConfig.eventServiceNames.length) {
|
|
353
|
-
const eventResources = _getEventResources(linkedCsn, appConfig, packageIds, accessStrategies);
|
|
354
|
-
if (eventResources.length) {
|
|
355
|
-
ordDocument.eventResources = eventResources;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const integrationDependencies = getIntegrationDependencies(linkedCsn, appConfig, packageIds);
|
|
360
|
-
if (integrationDependencies.length) {
|
|
361
|
-
ordDocument.integrationDependencies = integrationDependencies;
|
|
362
|
-
}
|
|
115
|
+
return authConfig;
|
|
116
|
+
};
|
|
363
117
|
|
|
364
|
-
|
|
118
|
+
module.exports = (csn, extensions = []) => {
|
|
119
|
+
const authConfig = _createAuthConfig();
|
|
120
|
+
const linkedCsn = _propagateORDVisibility(cds.linked(csn));
|
|
121
|
+
const appConfig = new Configuration(linkedCsn);
|
|
122
|
+
const ordDocument = [...(extensions || []), getCustomORDContent(appConfig)]
|
|
365
123
|
.filter((extension) => !!extension)
|
|
366
|
-
.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
ordDocument.packages = _filterUnusedPackages(ordDocument);
|
|
124
|
+
.reduce(
|
|
125
|
+
(document, extension) => compareAndHandleCustomORDContentWithExistingContent(document, extension),
|
|
126
|
+
_createDefaultORDDocument(linkedCsn, appConfig, authConfig),
|
|
127
|
+
);
|
|
371
128
|
|
|
372
|
-
return ordDocument;
|
|
129
|
+
return Object.assign(ordDocument, { packages: _filterUnusedPackages(ordDocument) });
|
|
373
130
|
};
|
package/lib/protocol-resolver.js
CHANGED
|
@@ -7,18 +7,6 @@ const {
|
|
|
7
7
|
} = require("./constants");
|
|
8
8
|
const Logger = require("./logger");
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Gets CAP endpoints for a service using CDS endpoints4().
|
|
12
|
-
*
|
|
13
|
-
* @param {string} serviceName The service name.
|
|
14
|
-
* @param {Object} srvDefinition The service definition object.
|
|
15
|
-
* @returns {Array} Raw endpoints from CDS.
|
|
16
|
-
*/
|
|
17
|
-
function _getCapEndpoints(serviceName, srvDefinition) {
|
|
18
|
-
const srvObj = { name: serviceName, definition: srvDefinition };
|
|
19
|
-
return cds.service.protocols.endpoints4(srvObj);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
10
|
/**
|
|
23
11
|
* Reads the explicit @protocol annotation from service definition.
|
|
24
12
|
*
|
|
@@ -42,13 +30,12 @@ function _getExplicitProtocol(srvDefinition) {
|
|
|
42
30
|
* - Rule B: Only fallback to OData when no explicit protocol
|
|
43
31
|
* - Rule C: Never produce [null] in entryPoints
|
|
44
32
|
*
|
|
45
|
-
* @param {string} serviceName The service name.
|
|
46
33
|
* @param {Object} srvDefinition The service definition object.
|
|
47
34
|
* @param {Object} options Configuration options.
|
|
48
35
|
* @param {Function} options.isPrimaryDataProduct Strategy function to check if service is primary data product.
|
|
49
36
|
* @returns {Array} Array with single {apiProtocol, entryPoints, hasResourceDefinitions} object, or empty array.
|
|
50
37
|
*/
|
|
51
|
-
function resolveApiResourceProtocol(
|
|
38
|
+
function resolveApiResourceProtocol(srvDefinition, options = {}) {
|
|
52
39
|
const { isPrimaryDataProduct = () => false } = options;
|
|
53
40
|
|
|
54
41
|
// 1. Primary Data Product - early return
|
|
@@ -62,7 +49,7 @@ function resolveApiResourceProtocol(serviceName, srvDefinition, options = {}) {
|
|
|
62
49
|
];
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
const capEndpoints =
|
|
52
|
+
const capEndpoints = cds.service.protocols.endpoints4({ name: srvDefinition.name, definition: srvDefinition });
|
|
66
53
|
const ordProtocols = [];
|
|
67
54
|
for (const endpoint of capEndpoints) {
|
|
68
55
|
if (PLUGIN_UNSUPPORTED_PROTOCOLS.includes(endpoint.kind)) {
|
|
@@ -107,7 +94,7 @@ function resolveApiResourceProtocol(serviceName, srvDefinition, options = {}) {
|
|
|
107
94
|
|
|
108
95
|
// 4. Handle explicit protocol with no CAP endpoint (Rule A)
|
|
109
96
|
if (!ordProtocols.some((p) => p.apiProtocol === protocol) && !CAP_TO_ORD_PROTOCOL_MAP[protocol]) {
|
|
110
|
-
Logger.warn(`Unknown protocol '${protocol}' is not supported, and skipped for service '${
|
|
97
|
+
Logger.warn(`Unknown protocol '${protocol}' is not supported, and skipped for service '${srvDefinition.name}'.`);
|
|
111
98
|
}
|
|
112
99
|
}
|
|
113
100
|
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
const cds = require("@sap/cds/lib");
|
|
2
2
|
const { ord, getMetadata } = require("@cap-js/ord/lib");
|
|
3
3
|
|
|
4
|
+
const defaults = require("../defaults");
|
|
5
|
+
const { DOCUMENT_PERSPECTIVES } = require("../constants");
|
|
6
|
+
|
|
4
7
|
module.exports = class MtxOrdProviderService extends cds.ApplicationService {
|
|
5
8
|
init() {
|
|
6
9
|
this.on("getOrdDocument", async (req) => {
|
|
7
10
|
req._?.res?.set("Content-Type", "application/json");
|
|
8
11
|
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
await
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
return defaults.adjustForPerspective(
|
|
13
|
+
ord(
|
|
14
|
+
await (
|
|
15
|
+
await cds.connect.to("cds.xt.ModelProviderService")
|
|
16
|
+
).getCsn({
|
|
17
|
+
tenant: req.data.tenant,
|
|
18
|
+
toggles: req.data.toggles,
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
DOCUMENT_PERSPECTIVES.SystemInstance,
|
|
17
22
|
);
|
|
18
23
|
});
|
|
19
24
|
|
|
@@ -23,7 +28,6 @@ module.exports = class MtxOrdProviderService extends cds.ApplicationService {
|
|
|
23
28
|
await (
|
|
24
29
|
await cds.connect.to("cds.xt.ModelProviderService")
|
|
25
30
|
).getCsn({
|
|
26
|
-
for: req.data.for,
|
|
27
31
|
tenant: req.data.tenant,
|
|
28
32
|
toggles: req.data.toggles,
|
|
29
33
|
}),
|
|
@@ -3,9 +3,61 @@ const cds = require("@sap/cds");
|
|
|
3
3
|
const ord = require("../ord.js");
|
|
4
4
|
const Logger = require("../logger.js");
|
|
5
5
|
const defaults = require("../defaults.js");
|
|
6
|
+
const { LOCAL_TENANT_ID_HEADER_KEY, DOCUMENT_PERSPECTIVES } = require("../constants");
|
|
6
7
|
const compileMetadata = require("../meta-data.js");
|
|
7
8
|
const { createAuthConfig, createAuthMiddleware } = require("../auth/authentication.js");
|
|
8
9
|
|
|
10
|
+
const validationMiddleware = (req, res, next) => {
|
|
11
|
+
const toggles = cds.env.requires.toggles;
|
|
12
|
+
const perspective = req.query.perspective;
|
|
13
|
+
const extensibility = cds.env.requires.extensibility;
|
|
14
|
+
const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
|
|
15
|
+
|
|
16
|
+
if (perspective && ![DOCUMENT_PERSPECTIVES.SystemVersion, DOCUMENT_PERSPECTIVES.SystemInstance].includes(perspective)) {
|
|
17
|
+
return res.status(400).send(`Required query parameter 'perspective' is invalid`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (perspective === DOCUMENT_PERSPECTIVES.SystemInstance && !(toggles || extensibility)) {
|
|
21
|
+
return res.status(400).send(`Unsupported query parameter 'perspective=${perspective}'`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!tenant && perspective === DOCUMENT_PERSPECTIVES.SystemInstance) {
|
|
25
|
+
return res.status(400).send(`Missing required tenant context`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return next();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const metadataResponseHandler = async (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const perspective = req.query.perspective;
|
|
34
|
+
const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
|
|
35
|
+
const model = await resolveCdsModel(perspective, tenant);
|
|
36
|
+
const { contentType, response } = await compileMetadata(req.path, model);
|
|
37
|
+
|
|
38
|
+
return res.status(200).contentType(contentType).send(response);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
Logger.error(error, "Error while processing the resource definition document");
|
|
41
|
+
return res.status(500).send(error.message);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const resolveCdsModel = async (perspective, tenant) => {
|
|
46
|
+
if (!tenant || perspective !== DOCUMENT_PERSPECTIVES.SystemInstance) {
|
|
47
|
+
Logger.info("Retrieving static CDS model...");
|
|
48
|
+
return cds.model;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Logger.info(`Retrieving dynamic CDS model for tenant ${tenant}...`);
|
|
52
|
+
|
|
53
|
+
return await (
|
|
54
|
+
await cds.connect.to("cds.xt.ModelProviderService")
|
|
55
|
+
).getCsn({
|
|
56
|
+
tenant: tenant,
|
|
57
|
+
toggles: OpenResourceDiscoveryService.resolveFeatureToggles(tenant),
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
9
61
|
class OpenResourceDiscoveryService extends cds.ApplicationService {
|
|
10
62
|
async init() {
|
|
11
63
|
this.extensions = {};
|
|
@@ -26,38 +78,38 @@ class OpenResourceDiscoveryService extends cds.ApplicationService {
|
|
|
26
78
|
// Create authentication middleware
|
|
27
79
|
const authMiddleware = createAuthMiddleware(authConfig);
|
|
28
80
|
|
|
29
|
-
|
|
30
|
-
|
|
81
|
+
// Default: /.well-known/open-resource-discovery
|
|
82
|
+
cds.app.get(`${this.path}`, (req, res) => {
|
|
83
|
+
const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
|
|
84
|
+
|
|
85
|
+
return res.status(200).send(defaults.baseTemplate(authConfig, tenant));
|
|
31
86
|
});
|
|
32
87
|
|
|
33
|
-
cds.app.get(`/ord/v1/documents/ord-document`, authMiddleware, async (
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
88
|
+
cds.app.get(`/ord/v1/documents/ord-document`, [authMiddleware, validationMiddleware], async (req, res) => {
|
|
89
|
+
const perspective = req.query.perspective || DOCUMENT_PERSPECTIVES.SystemVersion;
|
|
90
|
+
const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
|
|
91
|
+
const model = await resolveCdsModel(perspective, tenant);
|
|
92
|
+
const extensions = Array.from(Object.values(this.extensions));
|
|
93
|
+
|
|
94
|
+
return res.status(200).send(defaults.adjustForPerspective(ord(model, extensions), perspective));
|
|
37
95
|
});
|
|
38
96
|
|
|
39
|
-
cds.app.get(`/ord/v1/documents/:id`, authMiddleware, async (_, res) => {
|
|
97
|
+
cds.app.get(`/ord/v1/documents/:id`, [authMiddleware, validationMiddleware], async (_, res) => {
|
|
40
98
|
return res.status(404).send("404 Not Found");
|
|
41
99
|
});
|
|
42
100
|
|
|
43
|
-
// Handler for metadata requests (oas3, edmx, csn, etc.)
|
|
44
|
-
const metadataHandler = async (req, res) => {
|
|
45
|
-
try {
|
|
46
|
-
const { contentType, response } = await compileMetadata(req.url);
|
|
47
|
-
return res.status(200).contentType(contentType).send(response);
|
|
48
|
-
} catch (error) {
|
|
49
|
-
Logger.error(error, "Error while processing the resource definition document");
|
|
50
|
-
return res.status(500).send(error.message);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
101
|
// Use separate routes instead of optional parameters for path-to-regexp v8 compatibility
|
|
55
|
-
cds.app.get(`/ord/v1/:ordId/:service`, authMiddleware,
|
|
56
|
-
cds.app.get(`/ord/v1/:ordId`, authMiddleware,
|
|
57
|
-
cds.app.get(`/ord/v1`, authMiddleware,
|
|
102
|
+
cds.app.get(`/ord/v1/:ordId/:service`, [authMiddleware, validationMiddleware], metadataResponseHandler);
|
|
103
|
+
cds.app.get(`/ord/v1/:ordId`, [authMiddleware, validationMiddleware], metadataResponseHandler);
|
|
104
|
+
cds.app.get(`/ord/v1`, [authMiddleware, validationMiddleware], metadataResponseHandler);
|
|
58
105
|
|
|
59
106
|
return super.init();
|
|
60
107
|
}
|
|
108
|
+
|
|
109
|
+
// eslint-disable-next-line no-unused-vars
|
|
110
|
+
static resolveFeatureToggles(tenant) {
|
|
111
|
+
return ["*"];
|
|
112
|
+
}
|
|
61
113
|
}
|
|
62
114
|
|
|
63
115
|
module.exports = { OpenResourceDiscoveryService };
|