@cap-js/ord 1.1.0 → 1.3.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/ord.js CHANGED
@@ -1,86 +1,175 @@
1
- const path = require("path");
2
- const cds = require("@sap/cds");
3
- const { exists } = cds.utils;
4
- const _ = require("lodash");
5
- const defaults = require("./defaults");
1
+ const {
2
+ BLOCKED_SERVICE_NAME,
3
+ CDS_ELEMENT_KIND,
4
+ CONTENT_MERGE_KEY,
5
+ ENTITY_RELATIONSHIP_ANNOTATION,
6
+ ORD_ODM_ENTITY_NAME_ANNOTATION,
7
+ } = require("./constants");
6
8
  const {
7
9
  createAPIResourceTemplate,
10
+ createEntityTypeTemplate,
11
+ createEntityTypeMappingsItemTemplate,
8
12
  createEventResourceTemplate,
9
13
  createGroupsTemplateForService,
10
- createEntityTypeTemplate
11
- } = require('./templates');
12
- const { extendCustomORDContentIfExists } = require('./extendOrdWithCustom');
13
- const { ORD_RESOURCE_TYPE } = require('./constants');
14
+ _propagateORDVisibility,
15
+ } = require("./templates");
16
+ const { extendCustomORDContentIfExists } = require("./extendOrdWithCustom");
17
+ const { getRFC3339Date } = require("./date");
18
+ const { getAuthConfig } = require("./authentication");
14
19
 
15
- const logger = cds.log('ord-plugin');
20
+ const { Logger } = require("./logger");
21
+ const _ = require("lodash");
22
+ const cds = require("@sap/cds");
23
+ const defaults = require("./defaults");
24
+ const path = require("path");
16
25
 
17
26
  const initializeAppConfig = (csn) => {
18
- let packagejsonPath = path.join(cds.root, 'package.json')
19
- let packageJson;
20
- if (exists(packagejsonPath)) {
21
- packageJson = require(packagejsonPath);
22
- } else {
27
+ const packageJson = _loadPackageJson();
28
+ const packageName = packageJson.name;
29
+ const appName = _formatAppName(packageName);
30
+ const lastUpdate = getRFC3339Date();
31
+
32
+ const ordNamespace = _getORDNamespace(packageName);
33
+ const eventApplicationNamespace = cds.env?.export?.asyncapi?.applicationNamespace;
34
+
35
+ _validateNamespaces(ordNamespace, eventApplicationNamespace);
36
+
37
+ const { serviceNames, apiResourceNames, apiEndpoints, eventNames, entityTypeTargets } = _triageCsnDefinitions(csn);
38
+
39
+ return {
40
+ env: cds.env["ord"],
41
+ lastUpdate,
42
+ appName,
43
+ apiEndpoints: Array.from(apiEndpoints),
44
+ eventNames,
45
+ serviceNames,
46
+ apiResourceNames,
47
+ entityTypeTargets: _.uniqBy(entityTypeTargets, CONTENT_MERGE_KEY),
48
+ ordNamespace,
49
+ eventApplicationNamespace,
50
+ packageName,
51
+ };
52
+ };
53
+
54
+ function _loadPackageJson() {
55
+ const packageJsonPath = path.join(cds.root, "package.json");
56
+ if (!cds.utils.exists(packageJsonPath)) {
23
57
  throw new Error(`package.json not found in the project root directory`);
24
58
  }
25
- const packageName = packageJson.name;
26
- const appName = packageJson.name.replace(/^[@]/, "").replace(/[@/]/g, "-");
27
- const modelKeys = Object.keys(csn.definitions);
28
- const events = [];
29
- const serviceNames = [];
30
- let odmEntity = [];
31
-
32
- // namespace variable value if present in cdsrc.json take it there or else from package.json
33
- //if cdsrc.json does not have applicationNamespace, then use just the namespace
59
+ return require(packageJsonPath);
60
+ }
61
+
62
+ function _formatAppName(packageName) {
63
+ return packageName.replace(/^[@]/, "").replace(/[@/]/g, "-");
64
+ }
65
+
66
+ function _getORDNamespace(packageName) {
34
67
  const vendorNamespace = "customer";
35
- const ordNamespace = cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`;
36
- const eventApplicationNamespace = cds.env?.export?.asyncapi?.applicationNamespace;
68
+ return cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`;
69
+ }
37
70
 
71
+ function _validateNamespaces(ordNamespace, eventApplicationNamespace) {
38
72
  if (eventApplicationNamespace && ordNamespace !== eventApplicationNamespace) {
39
- console.warn("ORD and AsyncAPI namespaces should be the same.");
73
+ Logger.warn("ORD and AsyncAPI namespaces should be the same.");
40
74
  }
75
+ }
41
76
 
42
- for (const key of modelKeys) {
43
- const keyDefinition = csn.definitions[key];
44
- switch (keyDefinition.kind) {
45
- case ORD_RESOURCE_TYPE.service:
46
- if (!keyDefinition["@cds.external"]) {
47
- serviceNames.push(key);
48
- }
77
+ function _triageCsnDefinitions(csn) {
78
+ const pendingApiResourceNames = [];
79
+ const apiEndpoints = new Set();
80
+ const pendingEventNames = [];
81
+ const entityTypeTargets = [];
82
+ const pendingServiceNames = [];
83
+
84
+ for (const definitionKey of Object.keys(csn.definitions)) {
85
+ const definitionObj = csn.definitions[definitionKey];
86
+ if (definitionKey.includes(BLOCKED_SERVICE_NAME.MTXServices)) {
87
+ Logger.warn(`ORD service name "${definitionKey}" is blocked.`);
88
+ continue;
89
+ }
90
+ switch (definitionObj.kind) {
91
+ case CDS_ELEMENT_KIND.service: {
92
+ pendingServiceNames.push(definitionKey);
93
+ const apiResourceName = _handleApiResource(definitionKey, definitionObj);
94
+ if (apiResourceName) pendingApiResourceNames.push(apiResourceName);
49
95
  break;
50
- // TODO: should be rewritten
51
- case ORD_RESOURCE_TYPE.entity:
52
- if (!key.includes(".texts") && keyDefinition["@ODM.entityName"]) {
53
- odmEntity.push(createEntityTypeTemplate(keyDefinition));
96
+ }
97
+ case CDS_ELEMENT_KIND.entity: {
98
+ const result = _handleEntity(definitionKey, definitionObj);
99
+ if (result) {
100
+ if (result.apiEndpoint) apiEndpoints.add(result.apiEndpoint);
101
+ if (result.entityTypeTarget) entityTypeTargets.push(result.entityTypeTarget);
54
102
  }
55
103
  break;
56
- case ORD_RESOURCE_TYPE.event:
57
- events.push(key);
104
+ }
105
+ case CDS_ELEMENT_KIND.event: {
106
+ const event = _handleEvent(definitionKey);
107
+ if (event) pendingEventNames.push(event);
108
+ break;
109
+ }
110
+ case CDS_ELEMENT_KIND.action:
111
+ case CDS_ELEMENT_KIND.function: {
112
+ const apiEndpoint = _handleActionOrFunction(definitionKey);
113
+ if (apiEndpoint) apiEndpoints.add(apiEndpoint);
58
114
  break;
115
+ }
59
116
  }
60
117
  }
61
118
 
62
- odmEntity = _.uniqBy(odmEntity, "ordId");
63
-
64
119
  return {
65
- env: cds.env["ord"],
66
- appName,
67
- events,
68
- serviceNames,
69
- odmEntity,
70
- ordNamespace,
71
- eventApplicationNamespace,
72
- packageName,
120
+ serviceNames: pendingServiceNames,
121
+ apiResourceNames: pendingApiResourceNames,
122
+ apiEndpoints: Array.from(apiEndpoints),
123
+ eventNames: pendingEventNames,
124
+ entityTypeTargets,
73
125
  };
74
- };
126
+ }
127
+
128
+ function _handleApiResource(apiResourceName, serviceDefinition) {
129
+ if (_shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) || serviceDefinition["@cds.external"]) {
130
+ return null;
131
+ }
132
+ return apiResourceName;
133
+ }
134
+
135
+ function _shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) {
136
+ const isActionsNotContained = !serviceDefinition.actions || Object.keys(serviceDefinition.actions).length === 0;
137
+ const isFunctionsNotContained =
138
+ !serviceDefinition.functions || Object.keys(serviceDefinition.functions).length === 0;
139
+ const isEntitiesNotContained = !serviceDefinition.entities || Object.keys(serviceDefinition.entities).length === 0;
140
+ const isEventsContained = serviceDefinition.events && Object.keys(serviceDefinition.events).length > 0;
141
+ if (isActionsNotContained && isFunctionsNotContained && isEntitiesNotContained && isEventsContained) {
142
+ return true;
143
+ }
144
+ return false;
145
+ }
146
+
147
+ function _handleEntity(key, keyDefinition) {
148
+ if (!key.includes(".texts")) {
149
+ const apiEndpoint = key;
150
+ const entityTypeTarget =
151
+ keyDefinition[ORD_ODM_ENTITY_NAME_ANNOTATION] || keyDefinition[ENTITY_RELATIONSHIP_ANNOTATION]
152
+ ? createEntityTypeMappingsItemTemplate(keyDefinition)
153
+ : null;
154
+ return { apiEndpoint, entityTypeTarget };
155
+ }
156
+ return null;
157
+ }
75
158
 
76
- const _getPolicyLevel = (appConfig) =>
77
- appConfig.env?.policyLevel || defaults.policyLevel;
159
+ function _handleEvent(key) {
160
+ return key;
161
+ }
78
162
 
79
- const _getDescription = (appConfig) =>
80
- appConfig.env?.description || defaults.description;
163
+ function _handleActionOrFunction(key) {
164
+ return key;
165
+ }
81
166
 
82
- const _getProducts = (appConfig) =>
83
- appConfig.env?.products || defaults.products(appConfig.packageName);
167
+ const _getPolicyLevels = (appConfig) =>
168
+ appConfig.env?.policyLevels ||
169
+ (appConfig.env?.policyLevel && [appConfig.env?.policyLevel]) ||
170
+ defaults.policyLevels;
171
+
172
+ const _getDescription = (appConfig) => appConfig.env?.description || defaults.description;
84
173
 
85
174
  const _getGroups = (csn, appConfig) => {
86
175
  return appConfig.serviceNames
@@ -88,79 +177,132 @@ const _getGroups = (csn, appConfig) => {
88
177
  .filter((resource) => !!resource);
89
178
  };
90
179
 
91
- const _getPackages = (policyLevel, appConfig) =>
92
- appConfig.env?.packages || appConfig.events.length
93
- ? defaults.packages(appConfig.appName, policyLevel, appConfig.ordNamespace)
94
- : defaults
95
- .packages(appConfig.appName, policyLevel, appConfig.ordNamespace)
96
- .slice(0, 1);
180
+ const _getPackages = (policyLevels, appConfig) => {
181
+ return defaults.packages(appConfig, policyLevels);
182
+ };
97
183
 
98
- const _getAPIResources = (csn, appConfig, packageIds) => {
99
- return appConfig.serviceNames
100
- .flatMap((serviceName) => createAPIResourceTemplate(serviceName, csn.definitions[serviceName], appConfig, packageIds) || [])
101
- .filter((resource) => !!resource);
184
+ const _getEntityTypes = (appConfig, packageIds) => {
185
+ if (!appConfig.entityTypeTargets?.length) return [];
186
+
187
+ return appConfig.entityTypeTargets.map((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
102
188
  };
103
189
 
104
- const _getEventResources = (csn, appConfig, packageIds) => {
105
- if (appConfig.events.length === 0) return [];
106
- return appConfig.serviceNames
107
- .filter((serviceName) => appConfig.events.some((eventName) => eventName.startsWith(serviceName)))
108
- .flatMap((serviceName) => createEventResourceTemplate(serviceName, csn.definitions[serviceName], appConfig, packageIds) || []);
190
+ const _getAPIResources = (csn, appConfig, packageIds, accessStrategies) => {
191
+ return appConfig.apiResourceNames.flatMap((apiResourceName) =>
192
+ createAPIResourceTemplate(
193
+ apiResourceName,
194
+ csn.definitions[apiResourceName],
195
+ appConfig,
196
+ packageIds,
197
+ accessStrategies,
198
+ ),
199
+ );
109
200
  };
110
201
 
111
- function validateNamespace(appConfig) {
112
- const validateSystemNamespace = new RegExp(`^${appConfig.eventApplicationNamespace}\\.[^.]+\\..+$`);
113
- if (
114
- appConfig.ordNamespace === undefined &&
115
- !validateSystemNamespace.test(appConfig.ordNamespace)
116
- ) {
117
- let error = new Error(
118
- `Namespace is not defined in cdsrc.json or it is not in the format of ${appConfig.eventApplicationNamespace}.<appName>.<service>`
119
- );
120
- console.error("Namespace error:", error.message);
121
- throw error;
122
- }
202
+ const _getEventResources = (csn, appConfig, packageIds, accessStrategies) => {
203
+ return appConfig.serviceNames.flatMap((serviceName) =>
204
+ createEventResourceTemplate(serviceName, csn.definitions[serviceName], appConfig, packageIds, accessStrategies),
205
+ );
206
+ };
207
+
208
+ function _getOpenResourceDiscovery(appConfig) {
209
+ return appConfig.env?.openResourceDiscovery || defaults.openResourceDiscovery;
210
+ }
211
+
212
+ function _getConsumptionBundles(appConfig) {
213
+ return appConfig.env?.consumptionBundles || defaults.consumptionBundles(appConfig);
123
214
  }
124
215
 
216
+ const _getProducts = (appConfig) => {
217
+ const productsObj = defaults.products(appConfig.packageName);
218
+ if (appConfig.env?.products) {
219
+ const customProducts = appConfig.env.products[0];
220
+ if (customProducts?.ordId?.toLowerCase().startsWith("sap")) {
221
+ Logger.error(
222
+ "Detected sap product ordId, which should not be defined for custom products, use default value instead. Please check ord global registry.",
223
+ );
224
+ } else {
225
+ _.assign(productsObj[0], customProducts);
226
+ }
227
+ }
228
+ appConfig.products = productsObj;
229
+ return productsObj;
230
+ };
231
+
125
232
  function createDefaultORDDocument(linkedCsn, appConfig) {
126
233
  let ordDocument = {
127
- $schema: "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json",
128
- openResourceDiscovery: "1.9",
129
- policyLevel: _getPolicyLevel(appConfig),
234
+ $schema: "https://open-resource-discovery.github.io/specification/spec-v1/interfaces/Document.schema.json",
235
+ openResourceDiscovery: _getOpenResourceDiscovery(appConfig),
236
+ policyLevels: _getPolicyLevels(appConfig),
130
237
  description: _getDescription(appConfig),
131
- products: _getProducts(appConfig),
132
- groups: _getGroups(linkedCsn, appConfig),
238
+ consumptionBundles: _getConsumptionBundles(appConfig),
133
239
  };
134
240
 
135
- if (_getAPIResources(linkedCsn, appConfig).length && _getEventResources(linkedCsn, appConfig).length) {
136
- ordDocument.packages = _getPackages(ordDocument.policyLevel, appConfig);
241
+ if (appConfig.serviceNames.length) {
242
+ ordDocument.groups = _getGroups(linkedCsn, appConfig);
137
243
  }
244
+
245
+ if (appConfig.env?.existingProductORDId) {
246
+ appConfig.existingProductORDId = appConfig.env.existingProductORDId;
247
+ } else {
248
+ ordDocument.products = [_getProducts(appConfig)[0]];
249
+ }
250
+
251
+ ordDocument.packages = _getPackages(ordDocument.policyLevels, appConfig);
252
+
138
253
  return ordDocument;
139
254
  }
140
255
 
141
256
  function extractPackageIds(ordDocument) {
142
- const packageIds = new Set();
143
- if (ordDocument.packages) {
144
- ordDocument.packages.map((pkg) => packageIds.add(pkg.ordId));
145
- }
146
- return packageIds;
257
+ return ordDocument.packages?.map((pkg) => pkg.ordId) || [];
258
+ }
259
+
260
+ function _filterUnusedPackages(ordDocument) {
261
+ if (!ordDocument.packages?.length) return [];
262
+
263
+ const usedPackageIds = new Set();
264
+
265
+ ordDocument.apiResources?.forEach((api) => usedPackageIds?.add(api.partOfPackage));
266
+ ordDocument.eventResources?.forEach((event) => usedPackageIds?.add(event.partOfPackage));
267
+ ordDocument.dataProducts?.forEach((dp) => usedPackageIds?.add(dp.partOfPackage));
268
+ ordDocument.entityTypes?.forEach((et) => {
269
+ if (et && et.partOfPackage) {
270
+ usedPackageIds.add(et.partOfPackage);
271
+ }
272
+ });
273
+
274
+ const filteredPackages = ordDocument.packages.filter((pkg) => usedPackageIds.has(pkg.ordId));
275
+
276
+ return filteredPackages;
147
277
  }
148
278
 
149
279
  module.exports = (csn) => {
150
- const linkedCsn = cds.linked(csn);
280
+ const linkedCsn = _propagateORDVisibility(cds.linked(csn));
151
281
  const appConfig = initializeAppConfig(linkedCsn);
152
- validateNamespace(appConfig);
282
+ const accessStrategies = getAuthConfig().accessStrategies;
153
283
 
154
284
  let ordDocument = createDefaultORDDocument(linkedCsn, appConfig);
155
285
  const packageIds = extractPackageIds(ordDocument);
156
- // TODO: add testcase without apiResources or event, no empty package
157
- ordDocument.apiResources = _getAPIResources(linkedCsn, appConfig, packageIds);
158
- const eventResources = _getEventResources(linkedCsn, appConfig, packageIds);
159
- if (eventResources.length) {
160
- ordDocument.eventResources = eventResources;
286
+ const entityTypes = _getEntityTypes(appConfig, packageIds);
287
+
288
+ if (entityTypes.length) {
289
+ ordDocument.entityTypes = entityTypes;
161
290
  }
162
291
 
163
- ordDocument = extendCustomORDContentIfExists(appConfig, ordDocument, logger);
292
+ if (appConfig.apiResourceNames.length) {
293
+ const apiResources = _getAPIResources(linkedCsn, appConfig, packageIds, accessStrategies);
294
+ if (apiResources.length) {
295
+ ordDocument.apiResources = apiResources;
296
+ }
297
+ }
298
+ if (appConfig.eventNames.length) {
299
+ const eventResources = _getEventResources(linkedCsn, appConfig, packageIds, accessStrategies);
300
+ if (eventResources.length) {
301
+ ordDocument.eventResources = eventResources;
302
+ }
303
+ }
304
+ ordDocument = extendCustomORDContentIfExists(appConfig, ordDocument);
305
+ ordDocument.packages = _filterUnusedPackages(ordDocument);
164
306
 
165
307
  return ordDocument;
166
308
  };