@cap-js/ord 1.3.10 → 1.3.11

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.
@@ -0,0 +1,127 @@
1
+ const localize = require("@sap/cds/lib/i18n/localize");
2
+
3
+ // turn effective CSN into interop CSN
4
+ function interopCSN(csn) {
5
+ if (typeof csn != "object" || csn === null) return csn; // handle non-object inputs early
6
+ add_i18n_texts(csn);
7
+ map_annotations(csn);
8
+ add_meta_info(csn);
9
+ return csn;
10
+ }
11
+
12
+ //
13
+ // add i18n texts
14
+ //
15
+ // First fetch all texts defined in the app,
16
+ // then remove those that are not referenced in the csn.
17
+ //
18
+ function add_i18n_texts(csn) {
19
+ // get all texts of the app
20
+ const i18n = [...(localize.bundles4(csn) || [])]
21
+ .filter(([locale]) => !!locale)
22
+ .reduce((all, [locale, value]) => ({ ...all, [locale]: value }), {});
23
+
24
+ // get all i18n keys referenced in the csn
25
+ let i18n_keys = new Set();
26
+ for (let n1 in csn.definitions) {
27
+ let def = csn.definitions[n1];
28
+ collect_i18n(def, i18n_keys);
29
+ if (def.kind === "entity")
30
+ for (let n2 in def.elements) {
31
+ let el = def.elements[n2];
32
+ collect_i18n(el, i18n_keys);
33
+ }
34
+ }
35
+
36
+ // delete from i18n array all entries not occuring in i18n_keys and add result to csn
37
+ for (let locale in i18n) {
38
+ let texts = i18n[locale];
39
+ for (let k in texts) {
40
+ if (!i18n_keys.has(k)) delete texts[k];
41
+ }
42
+ if (Object.keys(texts).length === 0) delete i18n[locale];
43
+ }
44
+ csn["i18n"] = i18n;
45
+
46
+ // helper function: find all i18n keys referenced in annotations
47
+ // for an entity or element
48
+ // to be improved: currently only considers annotations with scalar, string-like value;
49
+ // doesn't drill into structured or array-like annotations
50
+ function collect_i18n(o, keys) {
51
+ for (let n in o) {
52
+ if (n.startsWith("@")) {
53
+ let annoVal = o[n];
54
+ if (typeof annoVal === "string" && annoVal.startsWith("{i18n>")) {
55
+ let [, x] = annoVal.match(/^\{i18n>(.*)\}$/) || [];
56
+ if (x) keys.add(x);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ //
64
+ // annotation mapping/replacement
65
+ //
66
+ function map_annotations(csn) {
67
+ for (let n1 in csn.definitions) {
68
+ let def = csn.definitions[n1];
69
+ replaceAnnos(def);
70
+ if (def.kind === "entity")
71
+ for (let n2 in def.elements) {
72
+ let el = def.elements[n2];
73
+ replaceAnnos(el);
74
+ }
75
+ }
76
+
77
+ // helper function: do the actual anno replacement
78
+ function replaceAnnos(o) {
79
+ // rhs null => anno is removed
80
+ const annoReplacement = {
81
+ "@Common.Label": "@EndUserText.label",
82
+ "@title": "@EndUserText.label",
83
+ "@label": "@EndUserText.label",
84
+ "@description": "@EndUserText.quickInfo",
85
+ "@cds.autoexpose": null,
86
+ };
87
+
88
+ for (const [oldA, newA] of Object.entries(annoReplacement)) {
89
+ if (o[oldA]) {
90
+ if (newA) o[newA] ??= o[oldA];
91
+ delete o[oldA];
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ //
98
+ // add meta information
99
+ //
100
+ function add_meta_info(csn) {
101
+ if (typeof csn != "object") return csn; // needed to make tests pass
102
+ csn["csnInteropEffective"] = "1.0";
103
+ csn.meta ??= {};
104
+ csn.meta.flavor = "effective";
105
+
106
+ let services = Object.entries(csn.definitions).filter(([, def]) => def.kind === "service");
107
+ if (services.length === 1) {
108
+ // assumption: "short" service name contains no dots
109
+ let segments = services[0][0].split(".");
110
+ let v = "1";
111
+ let srv = segments.pop();
112
+ let m = srv.match(/^v(\d+)$/);
113
+ if (m) {
114
+ srv = segments.pop();
115
+ v = m[1];
116
+ }
117
+ csn.meta.document = { version: `${v}.0.0` };
118
+ csn.meta.__name = srv;
119
+ if (segments.length > 0) csn.meta.__namespace = segments.join(".");
120
+ }
121
+
122
+ return csn;
123
+ }
124
+
125
+ module.exports = {
126
+ interopCSN,
127
+ };
package/lib/metaData.js CHANGED
@@ -3,6 +3,7 @@ const { compile: openapi } = require("@cap-js/openapi");
3
3
  const { compile: asyncapi } = require("@cap-js/asyncapi");
4
4
  const { COMPILER_TYPES } = require("./constants");
5
5
  const { Logger } = require("./logger");
6
+ const { interopCSN } = require("./interopCsn.js");
6
7
  const cdsc = require("@sap/cds-compiler/lib/main");
7
8
 
8
9
  module.exports = async (url, model = null) => {
@@ -36,8 +37,9 @@ module.exports = async (url, model = null) => {
36
37
  break;
37
38
  case COMPILER_TYPES.csn:
38
39
  try {
39
- const opt2 = { beta: { effectiveCsn: true }, effectiveServiceName: serviceName };
40
- responseFile = cdsc.for.effective(csn, opt2);
40
+ const opt_eff = { beta: { effectiveCsn: true }, effectiveServiceName: serviceName };
41
+ let effCsn = cdsc.for.effective(csn, opt_eff);
42
+ responseFile = interopCSN(effCsn);
41
43
  } catch (error) {
42
44
  Logger.error("Csn error:", error.message);
43
45
  throw error;
@@ -3,6 +3,7 @@ const { ord, getMetadata, defaults, authentication, Logger } = require("./index.
3
3
 
4
4
  class OpenResourceDiscoveryService extends cds.ApplicationService {
5
5
  init() {
6
+ const logger = Logger.Logger;
6
7
  cds.app.get(`${this.path}`, cds.middlewares.before, (_, res) => {
7
8
  return res.status(200).send(defaults.baseTemplate);
8
9
  });
@@ -22,7 +23,7 @@ class OpenResourceDiscoveryService extends cds.ApplicationService {
22
23
  const { contentType, response } = await getMetadata(req.url);
23
24
  return res.status(200).contentType(contentType).send(response);
24
25
  } catch (error) {
25
- Logger.error(error, "Error while processing the resource definition document");
26
+ logger.error(error, "Error while processing the resource definition document");
26
27
  return res.status(500).send(error.message);
27
28
  }
28
29
  });
package/lib/ord.js CHANGED
@@ -101,7 +101,7 @@ function _triageCsnDefinitions(csn) {
101
101
  const result = _handleEntity(definitionKey, definitionObj);
102
102
  if (result) {
103
103
  if (result.apiEndpoint) apiEndpoints.add(result.apiEndpoint);
104
- if (result.entityTypeTarget) entityTypeTargets.push(result.entityTypeTarget);
104
+ if (result.entityTypeTarget) entityTypeTargets.push(...result.entityTypeTarget);
105
105
  }
106
106
  break;
107
107
  }
@@ -177,10 +177,12 @@ function _isValidService(key, definition) {
177
177
  function _handleEntity(key, keyDefinition) {
178
178
  if (!key.includes(".texts") && _shouldNotSkipIfServiceProtocolIsNone(keyDefinition)) {
179
179
  const apiEndpoint = key;
180
- const entityTypeTarget =
181
- keyDefinition[ORD_ODM_ENTITY_NAME_ANNOTATION] || keyDefinition[ENTITY_RELATIONSHIP_ANNOTATION]
182
- ? createEntityTypeMappingsItemTemplate(keyDefinition)
183
- : null;
180
+ let entityTypeTarget = null;
181
+ if (keyDefinition[ORD_ODM_ENTITY_NAME_ANNOTATION] || keyDefinition[ENTITY_RELATIONSHIP_ANNOTATION]) {
182
+ const mapping = createEntityTypeMappingsItemTemplate(keyDefinition);
183
+ if (Array.isArray(mapping)) entityTypeTarget = mapping;
184
+ else if (mapping) entityTypeTarget = [mapping];
185
+ }
184
186
  return { apiEndpoint, entityTypeTarget };
185
187
  }
186
188
  return null;
package/lib/templates.js CHANGED
@@ -80,24 +80,28 @@ const _generatePaths = (srv, srvDefinition) => {
80
80
  * @returns {Object} An entry of the entityTypeMappings array.
81
81
  */
82
82
  const createEntityTypeMappingsItemTemplate = (entity) => {
83
+ const results = [];
83
84
  if (entity[ORD_ODM_ENTITY_NAME_ANNOTATION]) {
84
- return {
85
+ results.push({
85
86
  ordId: `sap.odm:entityType:${entity[ORD_ODM_ENTITY_NAME_ANNOTATION]}:v1`,
86
87
  entityName: entity[ORD_ODM_ENTITY_NAME_ANNOTATION],
87
88
  isODMMapping: true,
88
89
  ...entity,
89
- };
90
- } else if (entity[ENTITY_RELATIONSHIP_ANNOTATION]) {
90
+ });
91
+ }
92
+ if (entity[ENTITY_RELATIONSHIP_ANNOTATION]) {
91
93
  const ordIdParts = entity[ENTITY_RELATIONSHIP_ANNOTATION].split(":");
92
94
  const namespace = ordIdParts[0];
93
95
  const entityName = ordIdParts[1];
94
96
  const version = ordIdParts[2] || "v1";
95
- return {
97
+ results.push({
96
98
  ordId: `${namespace}:entityType:${entityName}:${version}`,
97
99
  entityName,
98
100
  ...entity,
99
- };
101
+ });
100
102
  }
103
+ if (results.length === 0) return;
104
+ return results;
101
105
  };
102
106
 
103
107
  function _getGroupID(serviceDefinition, groupTypeId = defaults.groupTypeId, appConfig) {
@@ -468,7 +472,9 @@ function _getEntityTypeMappings(definitionObj) {
468
472
  return;
469
473
  }
470
474
  const entities = Object.values(definitionObj.entities).flatMap((entity) => {
471
- const entityData = _flattenEntityGraph(entity).map(createEntityTypeMappingsItemTemplate);
475
+ const entityData = _flattenEntityGraph(entity)
476
+ .flatMap(createEntityTypeMappingsItemTemplate) // now returns arrays
477
+ .filter(Boolean);
472
478
  return _.uniqBy(entityData, CONTENT_MERGE_KEY);
473
479
  });
474
480
  const entityTypeTargets = _.uniqBy(entities, CONTENT_MERGE_KEY)
@@ -488,7 +494,7 @@ function _getExposedEntityTypes(definitionObj) {
488
494
  return;
489
495
  }
490
496
  const entities = Object.values(definitionObj.entities).flatMap((entity) => {
491
- const entityData = _flattenEntityGraph(entity).map(createEntityTypeMappingsItemTemplate);
497
+ const entityData = _flattenEntityGraph(entity).flatMap(createEntityTypeMappingsItemTemplate).filter(Boolean);
492
498
  return _.uniqBy(entityData, CONTENT_MERGE_KEY);
493
499
  });
494
500
  const exposedEntityTypes = _.uniqBy(entities, CONTENT_MERGE_KEY)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/ord",
3
- "version": "1.3.10",
3
+ "version": "1.3.11",
4
4
  "description": "CAP Plugin for generating ORD document.",
5
5
  "repository": "cap-js/ord",
6
6
  "author": "SAP SE (https://www.sap.com)",