@cap-js/ord 1.3.10 → 1.3.12
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/interopCsn.js +128 -0
- package/lib/metaData.js +4 -2
- package/lib/ord-service.js +2 -1
- package/lib/ord.js +7 -5
- package/lib/templates.js +13 -7
- package/package.json +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
.map(([locale, value]) => [locale.replaceAll('_', '-'), value]) // CSN interop uses '-' as separator
|
|
23
|
+
.reduce((all, [locale, value]) => ({ ...all, [locale]: value }), {});
|
|
24
|
+
|
|
25
|
+
// get all i18n keys referenced in the csn
|
|
26
|
+
let i18n_keys = new Set();
|
|
27
|
+
for (let n1 in csn.definitions) {
|
|
28
|
+
let def = csn.definitions[n1];
|
|
29
|
+
collect_i18n(def, i18n_keys);
|
|
30
|
+
if (def.kind === "entity")
|
|
31
|
+
for (let n2 in def.elements) {
|
|
32
|
+
let el = def.elements[n2];
|
|
33
|
+
collect_i18n(el, i18n_keys);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// delete from i18n array all entries not occuring in i18n_keys and add result to csn
|
|
38
|
+
for (let locale in i18n) {
|
|
39
|
+
let texts = i18n[locale];
|
|
40
|
+
for (let k in texts) {
|
|
41
|
+
if (!i18n_keys.has(k)) delete texts[k];
|
|
42
|
+
}
|
|
43
|
+
if (Object.keys(texts).length === 0) delete i18n[locale];
|
|
44
|
+
}
|
|
45
|
+
csn["i18n"] = i18n;
|
|
46
|
+
|
|
47
|
+
// helper function: find all i18n keys referenced in annotations
|
|
48
|
+
// for an entity or element
|
|
49
|
+
// to be improved: currently only considers annotations with scalar, string-like value;
|
|
50
|
+
// doesn't drill into structured or array-like annotations
|
|
51
|
+
function collect_i18n(o, keys) {
|
|
52
|
+
for (let n in o) {
|
|
53
|
+
if (n.startsWith("@")) {
|
|
54
|
+
let annoVal = o[n];
|
|
55
|
+
if (typeof annoVal === "string" && annoVal.startsWith("{i18n>")) {
|
|
56
|
+
let [, x] = annoVal.match(/^\{i18n>(.*)\}$/) || [];
|
|
57
|
+
if (x) keys.add(x);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//
|
|
65
|
+
// annotation mapping/replacement
|
|
66
|
+
//
|
|
67
|
+
function map_annotations(csn) {
|
|
68
|
+
for (let n1 in csn.definitions) {
|
|
69
|
+
let def = csn.definitions[n1];
|
|
70
|
+
replaceAnnos(def);
|
|
71
|
+
if (def.kind === "entity")
|
|
72
|
+
for (let n2 in def.elements) {
|
|
73
|
+
let el = def.elements[n2];
|
|
74
|
+
replaceAnnos(el);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// helper function: do the actual anno replacement
|
|
79
|
+
function replaceAnnos(o) {
|
|
80
|
+
// rhs null => anno is removed
|
|
81
|
+
const annoReplacement = {
|
|
82
|
+
"@Common.Label": "@EndUserText.label",
|
|
83
|
+
"@title": "@EndUserText.label",
|
|
84
|
+
"@label": "@EndUserText.label",
|
|
85
|
+
"@description": "@EndUserText.quickInfo",
|
|
86
|
+
"@cds.autoexpose": null,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
for (const [oldA, newA] of Object.entries(annoReplacement)) {
|
|
90
|
+
if (o[oldA]) {
|
|
91
|
+
if (newA) o[newA] ??= o[oldA];
|
|
92
|
+
delete o[oldA];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//
|
|
99
|
+
// add meta information
|
|
100
|
+
//
|
|
101
|
+
function add_meta_info(csn) {
|
|
102
|
+
if (typeof csn != "object") return csn; // needed to make tests pass
|
|
103
|
+
csn["csnInteropEffective"] = "1.0";
|
|
104
|
+
csn.meta ??= {};
|
|
105
|
+
csn.meta.flavor = "effective";
|
|
106
|
+
|
|
107
|
+
let services = Object.entries(csn.definitions).filter(([, def]) => def.kind === "service");
|
|
108
|
+
if (services.length === 1) {
|
|
109
|
+
// assumption: "short" service name contains no dots
|
|
110
|
+
let segments = services[0][0].split(".");
|
|
111
|
+
let v = "1";
|
|
112
|
+
let srv = segments.pop();
|
|
113
|
+
let m = srv.match(/^v(\d+)$/);
|
|
114
|
+
if (m) {
|
|
115
|
+
srv = segments.pop();
|
|
116
|
+
v = m[1];
|
|
117
|
+
}
|
|
118
|
+
csn.meta.document = { version: `${v}.0.0` };
|
|
119
|
+
csn.meta.__name = srv;
|
|
120
|
+
if (segments.length > 0) csn.meta.__namespace = segments.join(".");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return csn;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
interopCSN,
|
|
128
|
+
};
|
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
|
|
40
|
-
|
|
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;
|
package/lib/ord-service.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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)
|
|
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).
|
|
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)
|