@sap/cds-compiler 4.1.2 → 4.2.2
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/CHANGELOG.md +101 -1
- package/bin/cdsc.js +6 -3
- package/doc/CHANGELOG_BETA.md +5 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +2 -2
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +24 -24
- package/lib/base/message-registry.js +41 -6
- package/lib/base/messages.js +7 -0
- package/lib/base/model.js +37 -8
- package/lib/checks/elements.js +11 -10
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +2 -3
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/utils.js +3 -2
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +27 -24
- package/lib/compiler/base.js +6 -2
- package/lib/compiler/builtins.js +34 -34
- package/lib/compiler/checks.js +179 -208
- package/lib/compiler/classes.js +2 -2
- package/lib/compiler/cycle-detector.js +6 -6
- package/lib/compiler/define.js +66 -45
- package/lib/compiler/extend.js +81 -72
- package/lib/compiler/finalize-parse-cdl.js +26 -26
- package/lib/compiler/generate.js +61 -45
- package/lib/compiler/index.js +47 -49
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +42 -35
- package/lib/compiler/propagator.js +6 -6
- package/lib/compiler/resolve.js +170 -126
- package/lib/compiler/shared.js +122 -45
- package/lib/compiler/tweak-assocs.js +93 -40
- package/lib/compiler/utils.js +15 -12
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +678 -772
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +686 -646
- package/lib/edm/edmUtils.js +277 -296
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1253 -1276
- package/lib/json/from-csn.js +34 -4
- package/lib/json/to-csn.js +4 -4
- package/lib/language/language.g4 +2 -5
- package/lib/main.d.ts +61 -1
- package/lib/model/csnUtils.js +31 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +37 -2
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +15 -3
- package/lib/render/toCdl.js +30 -4
- package/lib/render/toSql.js +5 -9
- package/lib/render/utils/common.js +8 -6
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +121 -47
- package/lib/transform/db/flattening.js +75 -7
- package/lib/transform/forOdata.js +4 -1
- package/lib/transform/forRelationalDB.js +80 -62
- package/lib/transform/localized.js +91 -54
- package/lib/transform/transformUtils.js +9 -10
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
|
|
2
3
|
const { isEdmPropertyRendered, isBuiltinType } = require('../../model/csnUtils');
|
|
3
4
|
const edmUtils = require('../edmUtils.js');
|
|
4
5
|
const oDataDictionary = require('../../gen/Dictionary.json');
|
|
@@ -37,115 +38,115 @@ const { isBetaEnabled, setProp } = require('../../base/model.js');
|
|
|
37
38
|
*/
|
|
38
39
|
|
|
39
40
|
const vocabularyDefinitions = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
Aggregation: {
|
|
42
|
+
ref: { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Aggregation.V1.xml' },
|
|
43
|
+
inc: { Alias: 'Aggregation', Namespace: 'Org.OData.Aggregation.V1' },
|
|
44
|
+
int: { filename: 'Aggregation.xml' },
|
|
44
45
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
Analytics: {
|
|
47
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Analytics.xml' },
|
|
48
|
+
inc: { Alias: 'Analytics', Namespace: 'com.sap.vocabularies.Analytics.v1' },
|
|
49
|
+
int: { filename: 'Analytics.xml' },
|
|
49
50
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
Authorization: {
|
|
52
|
+
ref: { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Authorization.V1.xml' },
|
|
53
|
+
inc: { Alias: 'Authorization', Namespace: 'Org.OData.Authorization.V1' },
|
|
54
|
+
int: { filename: 'Authorization.xml' },
|
|
54
55
|
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
Capabilities: {
|
|
57
|
+
ref: { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Capabilities.V1.xml' },
|
|
58
|
+
inc: { Alias: 'Capabilities', Namespace: 'Org.OData.Capabilities.V1' },
|
|
59
|
+
int: { filename: 'Capabilities.xml' },
|
|
59
60
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
CodeList: {
|
|
62
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/CodeList.xml' },
|
|
63
|
+
inc: { Alias: 'CodeList', Namespace: 'com.sap.vocabularies.CodeList.v1' },
|
|
64
|
+
int: { filename: 'CodeList.xml' },
|
|
64
65
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
Common: {
|
|
67
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Common.xml' },
|
|
68
|
+
inc: { Alias: 'Common', Namespace: 'com.sap.vocabularies.Common.v1' },
|
|
69
|
+
int: { filename: 'Common.xml' },
|
|
69
70
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
Communication: {
|
|
72
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Communication.xml' },
|
|
73
|
+
inc: { Alias: 'Communication', Namespace: 'com.sap.vocabularies.Communication.v1' },
|
|
74
|
+
int: { filename: 'Communication.xml' },
|
|
74
75
|
},
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
Core: {
|
|
77
|
+
ref: { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml' },
|
|
78
|
+
inc: { Alias: 'Core', Namespace: 'Org.OData.Core.V1' },
|
|
79
|
+
int: { filename: 'Core.xml' },
|
|
79
80
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
DataIntegration: {
|
|
82
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/DataIntegration.xml' },
|
|
83
|
+
inc: { Alias: 'DataIntegration', Namespace: 'com.sap.vocabularies.DataIntegration.v1' },
|
|
84
|
+
int: { filename: 'DataIntegration.xml' },
|
|
84
85
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
Graph: {
|
|
87
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Graph.xml' },
|
|
88
|
+
inc: { Alias: 'Graph', Namespace: 'com.sap.vocabularies.Graph.v1' },
|
|
89
|
+
int: { filename: 'Graph.xml' },
|
|
89
90
|
},
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
Hierarchy: {
|
|
92
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Hierarchy.xml' },
|
|
93
|
+
inc: { Alias: 'Hierarchy', Namespace: 'com.sap.vocabularies.Hierarchy.v1' },
|
|
94
|
+
int: { filename: 'Hierarchy.xml' },
|
|
94
95
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
HTML5: {
|
|
97
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/HTML5.xml' },
|
|
98
|
+
inc: { Alias: 'HTML5', Namespace: 'com.sap.vocabularies.HTML5.v1' },
|
|
99
|
+
int: { filename: 'HTML5.xml' },
|
|
99
100
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
JSON: {
|
|
102
|
+
ref: { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.JSON.V1.xml' },
|
|
103
|
+
inc: { Alias: 'JSON', Namespace: 'Org.OData.JSON.V1' },
|
|
104
|
+
int: { filename: 'JSON.xml' },
|
|
104
105
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
Measures: {
|
|
107
|
+
ref: { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Measures.V1.xml' },
|
|
108
|
+
inc: { Alias: 'Measures', Namespace: 'Org.OData.Measures.V1' },
|
|
109
|
+
int: { filename: 'Measures.xml' },
|
|
109
110
|
},
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
ODM: {
|
|
112
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/ODM.xml' },
|
|
113
|
+
inc: { Alias: 'ODM', Namespace: 'com.sap.vocabularies.ODM.v1' },
|
|
114
|
+
int: { filename: 'ODM.xml' },
|
|
114
115
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
Offline: {
|
|
117
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Offline.xml' },
|
|
118
|
+
inc: { Alias: 'Offline', Namespace: 'com.sap.vocabularies.Offline.v1' },
|
|
119
|
+
int: { filename: 'Offline.xml' },
|
|
119
120
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
PDF: {
|
|
122
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/PDF.xml' },
|
|
123
|
+
inc: { Alias: 'PDF', Namespace: 'com.sap.vocabularies.PDF.v1' },
|
|
124
|
+
int: { filename: 'PDF.xml' },
|
|
124
125
|
},
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
PersonalData: {
|
|
127
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/PersonalData.xml' },
|
|
128
|
+
inc: { Alias: 'PersonalData', Namespace: 'com.sap.vocabularies.PersonalData.v1' },
|
|
129
|
+
int: { filename: 'PersonalData.xml' },
|
|
129
130
|
},
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
Repeatability: {
|
|
132
|
+
ref: { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Repeatability.V1.xml' },
|
|
133
|
+
inc: { Alias: 'Repeatability', Namespace: 'Org.OData.Repeatability.V1' },
|
|
134
|
+
int: { filename: 'Repeatability.xml' },
|
|
134
135
|
},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
Session: {
|
|
137
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Session.xml' },
|
|
138
|
+
inc: { Alias: 'Session', Namespace: 'com.sap.vocabularies.Session.v1' },
|
|
139
|
+
int: { filename: 'Session.xml' },
|
|
139
140
|
},
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
UI: {
|
|
142
|
+
ref: { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/UI.xml' },
|
|
143
|
+
inc: { Alias: 'UI', Namespace: 'com.sap.vocabularies.UI.v1' },
|
|
144
|
+
int: { filename: 'UI.xml' },
|
|
144
145
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
Validation: {
|
|
147
|
+
ref: { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Validation.V1.xml' },
|
|
148
|
+
inc: { Alias: 'Validation', Namespace: 'Org.OData.Validation.V1' },
|
|
149
|
+
int: { filename: 'Validation.xml' },
|
|
149
150
|
},
|
|
150
151
|
/* unvalidated vocabularies below here:
|
|
151
152
|
A vocabulary is unvalidated if it doesn't have an int.filename property as this indicates that
|
|
@@ -164,29 +165,27 @@ Object.entries(vocabularyDefinitions).forEach(([n, v]) => {
|
|
|
164
165
|
vocabularyDefinitions[v.inc.Namespace] = vocabularyDefinitions[n];
|
|
165
166
|
});
|
|
166
167
|
*/
|
|
167
|
-
|
|
168
|
+
/** ************************************************************************************************
|
|
168
169
|
* csn2annotationEdm
|
|
169
170
|
*
|
|
170
171
|
* options:
|
|
171
172
|
* v - array with two boolean entries, first is for v2, second is for v4
|
|
172
173
|
* dictReplacement: for test purposes, replaces the standard oDataDictionary
|
|
173
174
|
*/
|
|
174
|
-
function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
175
|
-
|
|
175
|
+
function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
176
|
+
Edm = undefined, options = undefined, messageFunctions = undefined, mergedVocDefs = vocabularyDefinitions ) {
|
|
176
177
|
// global variable where we store all the generated annotations
|
|
177
|
-
const
|
|
178
|
+
const gAnnosArray = [];
|
|
178
179
|
|
|
179
180
|
const { message } = messageFunctions;
|
|
180
181
|
|
|
181
182
|
const [ userDefinedTermDict, allKnownVocabularies ] = createUserDefinedTermDictionary();
|
|
182
183
|
|
|
183
184
|
allKnownVocabularies.push(...Object.keys(mergedVocDefs));
|
|
184
|
-
allKnownVocabularies.sort((a,b) => b.length-a.length);
|
|
185
|
-
const whatsMyTermNamespace =
|
|
186
|
-
return allKnownVocabularies.reduce((rc, ns) => !rc && anno && anno.startsWith('@' + ns + '.') ? ns : rc, undefined);
|
|
187
|
-
}
|
|
185
|
+
allKnownVocabularies.sort((a, b) => b.length - a.length);
|
|
186
|
+
const whatsMyTermNamespace = anno => allKnownVocabularies.reduce((rc, ns) => (!rc && anno && anno.startsWith(`@${ns}.`) ? ns : rc), undefined);
|
|
188
187
|
|
|
189
|
-
|
|
188
|
+
// Static dynamic expression dictionary, loaded with Edm creators
|
|
190
189
|
const [ dynamicExpressions, dynamicExpressionNames ] = initEdmJson();
|
|
191
190
|
|
|
192
191
|
// annotation preprocessing
|
|
@@ -194,25 +193,25 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
194
193
|
|
|
195
194
|
// we take note of which vocabularies are actually used in a service in order to avoid
|
|
196
195
|
// producing useless references; reset everything to "unused"
|
|
197
|
-
for(const n in mergedVocDefs) {
|
|
196
|
+
for (const n in mergedVocDefs) {
|
|
198
197
|
mergedVocDefs[n].used = false;
|
|
199
198
|
delete mergedVocDefs[n].$ignore;
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
// These vocabularies are always added for the runtimes
|
|
203
|
-
mergedVocDefs
|
|
204
|
-
mergedVocDefs
|
|
202
|
+
mergedVocDefs.Common.used = true;
|
|
203
|
+
mergedVocDefs.Core.used = true;
|
|
205
204
|
|
|
206
205
|
const vocDef = mergedVocDefs[serviceName];
|
|
207
|
-
if(vocDef && vocDef.$optVocRef) {
|
|
206
|
+
if (vocDef && vocDef.$optVocRef) {
|
|
208
207
|
setProp(vocDef, '$ignore', true);
|
|
209
208
|
message('odata-anno-vocref', null,
|
|
210
|
-
|
|
209
|
+
{ name: serviceName, '#': 'service' } );
|
|
211
210
|
}
|
|
212
211
|
|
|
213
212
|
// provide functions for dictionary lookup
|
|
214
213
|
// use closure to avoid making "dict" and "experimental" global variables
|
|
215
|
-
const { getDictTerm, getDictType } = function(){
|
|
214
|
+
const { getDictTerm, getDictType } = (function createDictGetters() {
|
|
216
215
|
const dict = options.dictReplacement || oDataDictionary; // tests can set different dictionary via options
|
|
217
216
|
const experimental = {}; // take note of all experimental annos that have been used
|
|
218
217
|
const deprecated = {}; // take note of all deprecated annos that have been used
|
|
@@ -221,27 +220,27 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
221
220
|
// called to look-up a term in the dictionary
|
|
222
221
|
// in addition: - note usage of the respective vocabulary
|
|
223
222
|
// - issue a warning if the term is flagged as "experimental"
|
|
224
|
-
getDictTerm
|
|
223
|
+
getDictTerm(termName, msg) {
|
|
225
224
|
const dictTerm = (dict.terms[termName] ||
|
|
226
|
-
userDefinedTermDict.terms[serviceName
|
|
225
|
+
userDefinedTermDict.terms[`${serviceName}.${termName}`] ||
|
|
227
226
|
userDefinedTermDict.terms[termName]);
|
|
228
227
|
// register vocabulary usage if possible
|
|
229
228
|
const vocName = termName.slice(0, termName.indexOf('.'));
|
|
230
|
-
const
|
|
231
|
-
if(
|
|
232
|
-
|
|
233
|
-
else if(dictTerm?.$myServiceRoot &&
|
|
229
|
+
const myVocDef = mergedVocDefs[vocName];
|
|
230
|
+
if (myVocDef && !myVocDef.$ignore)
|
|
231
|
+
myVocDef.used = true;
|
|
232
|
+
else if (dictTerm?.$myServiceRoot &&
|
|
234
233
|
userDefinedTermDict.xrefs[dictTerm?.$myServiceRoot])
|
|
235
234
|
userDefinedTermDict.xrefs[dictTerm.$myServiceRoot].used = true;
|
|
236
235
|
if (dictTerm) {
|
|
237
236
|
// issue message for usage of experimental Terms, but only once per Term
|
|
238
|
-
if (dictTerm
|
|
237
|
+
if (dictTerm.$experimental && !experimental[termName]) {
|
|
239
238
|
message('odata-anno-dict', msg.location, { anno: msg.anno(), '#': 'experimental' });
|
|
240
239
|
experimental[termName] = true;
|
|
241
240
|
}
|
|
242
|
-
if (dictTerm
|
|
241
|
+
if (dictTerm.$deprecated && !deprecated[termName]) {
|
|
243
242
|
message('odata-anno-def', msg.location,
|
|
244
|
-
|
|
243
|
+
{ anno: msg.anno(), depr: dictTerm.$deprecationText, '#': 'deprecated' });
|
|
245
244
|
deprecated[termName] = true;
|
|
246
245
|
}
|
|
247
246
|
}
|
|
@@ -249,44 +248,44 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
249
248
|
},
|
|
250
249
|
// called to look-up a type in the dictionary
|
|
251
250
|
// in addition, note usage of the respective vocabulary
|
|
252
|
-
getDictType
|
|
253
|
-
|
|
254
|
-
userDefinedTermDict.types[serviceName
|
|
251
|
+
getDictType(typeName) {
|
|
252
|
+
const dictType = (dict.types[typeName] ||
|
|
253
|
+
userDefinedTermDict.types[`${serviceName}.${typeName}`] ||
|
|
255
254
|
userDefinedTermDict.types[typeName]);
|
|
256
255
|
if (dictType) {
|
|
257
256
|
// register usage of vocabulary
|
|
258
257
|
const vocName = typeName.slice(0, typeName.indexOf('.'));
|
|
259
|
-
const
|
|
260
|
-
if(
|
|
261
|
-
|
|
258
|
+
const myVocDef = mergedVocDefs[vocName];
|
|
259
|
+
if (myVocDef && !myVocDef.$ignore)
|
|
260
|
+
myVocDef.used = true;
|
|
262
261
|
}
|
|
263
262
|
return dictType;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}();
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}());
|
|
267
266
|
|
|
268
|
-
const v = options
|
|
267
|
+
const { v } = options;
|
|
269
268
|
|
|
270
269
|
// Copy annotations from origin to parameter entity if it's
|
|
271
270
|
// qualified with #$parameters or if its applicable to an EntitySet or Singleton
|
|
272
271
|
forEachDefinition(reqDefs, (object) => {
|
|
273
|
-
if(object.$isParamEntity && object._origin) {
|
|
274
|
-
for(const attr in object._origin) {
|
|
272
|
+
if (object.$isParamEntity && object._origin) {
|
|
273
|
+
for (const attr in object._origin) {
|
|
275
274
|
if (attr[0] === '@') {
|
|
276
275
|
const [ prefix, innerAnnotation ] = attr.split('.@');
|
|
277
276
|
const ns = whatsMyTermNamespace(prefix);
|
|
278
|
-
if(ns) {
|
|
279
|
-
const steps = prefix.replace(
|
|
277
|
+
if (ns) {
|
|
278
|
+
const steps = prefix.replace(`@${ns}.`, '').split('.');
|
|
280
279
|
const paramAnnoParts = steps[0].split('#$parameters');
|
|
281
|
-
const dictTerm = getDictTerm(ns
|
|
282
|
-
if(paramAnnoParts.length > 1 || ['Singleton', 'EntitySet'].some(y => dictTerm?.AppliesTo?.includes(y))) {
|
|
283
|
-
steps[0] =
|
|
280
|
+
const dictTerm = getDictTerm(`${ns}.${paramAnnoParts[0]}`, options);
|
|
281
|
+
if (paramAnnoParts.length > 1 || [ 'Singleton', 'EntitySet' ].some(y => dictTerm?.AppliesTo?.includes(y))) {
|
|
282
|
+
steps[0] = `@${ns}.${paramAnnoParts.join('')}`;
|
|
284
283
|
let newAnno = steps.join('.');
|
|
285
|
-
if(innerAnnotation)
|
|
286
|
-
newAnno +=
|
|
284
|
+
if (innerAnnotation)
|
|
285
|
+
newAnno += `.@${innerAnnotation}`;
|
|
287
286
|
edmUtils.assignAnnotation(object, newAnno, object._origin[attr]);
|
|
288
287
|
// delete original annotation only if it was qualified with $parameters
|
|
289
|
-
if(paramAnnoParts.length > 1)
|
|
288
|
+
if (paramAnnoParts.length > 1)
|
|
290
289
|
delete object._origin[attr];
|
|
291
290
|
}
|
|
292
291
|
}
|
|
@@ -301,8 +300,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
301
300
|
// Note: we assume that all objects ly flat in the service, i.e. objName always
|
|
302
301
|
// looks like <service name, can contain dots>.<id>
|
|
303
302
|
forEachDefinition(reqDefs, (object, objName) => {
|
|
304
|
-
if (objName === serviceName || objName.startsWith(serviceName
|
|
305
|
-
|
|
303
|
+
if (objName === serviceName || objName.startsWith(`${serviceName}.`)) {
|
|
306
304
|
const location = [ 'definitions', objName ];
|
|
307
305
|
if (object.kind === 'action' || object.kind === 'function') {
|
|
308
306
|
handleAction(objName, object, null, location);
|
|
@@ -322,14 +320,14 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
322
320
|
// filter out empty <Annotations...> elements
|
|
323
321
|
// add references for the used vocabularies
|
|
324
322
|
return {
|
|
325
|
-
annos:
|
|
326
|
-
usedVocabularies: Object.values(mergedVocDefs).filter(
|
|
327
|
-
xrefs: Object.values(userDefinedTermDict.xrefs).filter(
|
|
323
|
+
annos: gAnnosArray,
|
|
324
|
+
usedVocabularies: Object.values(mergedVocDefs).filter(voc => voc.used),
|
|
325
|
+
xrefs: Object.values(userDefinedTermDict.xrefs).filter(voc => voc.used).map(voc => voc.$myServiceRoot),
|
|
328
326
|
};
|
|
329
327
|
|
|
330
|
-
//-------------------------------------------------------------------------------------------------
|
|
331
|
-
//-------------------------------------------------------------------------------------------------
|
|
332
|
-
//-------------------------------------------------------------------------------------------------
|
|
328
|
+
//-------------------------------------------------------------------------------------------------
|
|
329
|
+
//-------------------------------------------------------------------------------------------------
|
|
330
|
+
//-------------------------------------------------------------------------------------------------
|
|
333
331
|
|
|
334
332
|
// helper to determine the OData version
|
|
335
333
|
// TODO: improve option handling
|
|
@@ -337,7 +335,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
337
335
|
return v && v[0];
|
|
338
336
|
}
|
|
339
337
|
|
|
340
|
-
/*
|
|
338
|
+
/*
|
|
341
339
|
Mapping annotated thing in cds/csn => annotated thing in edmx:
|
|
342
340
|
|
|
343
341
|
carrier: the annotated thing in cds, can be: service, entity, structured type, element of entity or structured type,
|
|
@@ -375,17 +373,16 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
375
373
|
*/
|
|
376
374
|
|
|
377
375
|
|
|
378
|
-
|
|
379
|
-
|
|
380
376
|
// handle the annotations of the elements of an object
|
|
381
377
|
// in: objname : name of the object
|
|
382
378
|
// object : the object itself
|
|
383
|
-
function handleElements(objname, object, location) {
|
|
384
|
-
if (!object.elements)
|
|
385
|
-
|
|
379
|
+
function handleElements( objname, object, location ) {
|
|
380
|
+
if (!object.elements)
|
|
381
|
+
return;
|
|
382
|
+
Object.entries(object.elements).forEach(([ elemName, element ]) => {
|
|
386
383
|
// determine the name of the target in the resulting edm
|
|
387
384
|
// for non-assoc element, this simply is "<objectName>/<elementName>"
|
|
388
|
-
const edmTargetName = objname
|
|
385
|
+
const edmTargetName = `${objname}/${elemName}`;
|
|
389
386
|
const eLocation = [ ...location, 'elements', elemName ];
|
|
390
387
|
handleAnnotations(edmTargetName, element, eLocation);
|
|
391
388
|
});
|
|
@@ -402,16 +399,16 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
402
399
|
// handle the annotations of cObject's (an entity) bound actions/functions and their parameters
|
|
403
400
|
// in: cObjectname : qualified name of the object that holds the actions
|
|
404
401
|
// cObject : the object itself
|
|
405
|
-
function handleBoundActions(cObjectname, cObject, location) {
|
|
406
|
-
if(!cObject.actions)
|
|
402
|
+
function handleBoundActions( cObjectname, cObject, location ) {
|
|
403
|
+
if (!cObject.actions)
|
|
404
|
+
return;
|
|
407
405
|
// get service name: remove last part of the object name
|
|
408
406
|
// only works if all objects ly flat in the service
|
|
409
|
-
const nameParts = cObjectname.split('.')
|
|
407
|
+
const nameParts = cObjectname.split('.');
|
|
410
408
|
const entityName = nameParts.pop();
|
|
411
|
-
const serviceName = nameParts.join('.');
|
|
412
409
|
|
|
413
|
-
Object.entries(cObject.actions).forEach(([n, action]) => {
|
|
414
|
-
const actionName = serviceName
|
|
410
|
+
Object.entries(cObject.actions).forEach(([ n, action ]) => {
|
|
411
|
+
const actionName = `${serviceName}.${isV2() ? `${entityName}_` : ''}${n}`;
|
|
415
412
|
handleAction(actionName, action, cObjectname, [ ...location, 'actions', n ]);
|
|
416
413
|
});
|
|
417
414
|
}
|
|
@@ -421,12 +418,12 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
421
418
|
// in: cActionName : qualified name of the action
|
|
422
419
|
// cAction : the action object
|
|
423
420
|
// entityNameIfBound : qualified name of entity if bound action/function
|
|
424
|
-
function handleAction(cActionName, cAction, entityNameIfBound, location) {
|
|
421
|
+
function handleAction( cActionName, cAction, entityNameIfBound, location ) {
|
|
425
422
|
let actionName = cActionName;
|
|
426
423
|
if (isV2()) { // Replace up to last dot with <serviceName>.EntityContainer
|
|
427
424
|
const lastDotIndex = actionName.lastIndexOf('.');
|
|
428
425
|
if (lastDotIndex > -1)
|
|
429
|
-
actionName = serviceName
|
|
426
|
+
actionName = `${serviceName}.EntityContainer/${actionName.substring(lastDotIndex + 1)}`;
|
|
430
427
|
}
|
|
431
428
|
else { // add parameter type list
|
|
432
429
|
actionName += relParList();
|
|
@@ -434,17 +431,17 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
434
431
|
|
|
435
432
|
handleAnnotations(actionName, cAction, location);
|
|
436
433
|
|
|
437
|
-
if(cAction.params) {
|
|
438
|
-
Object.entries(cAction.params).forEach(([n, p]) => {
|
|
439
|
-
const edmTargetName = actionName
|
|
434
|
+
if (cAction.params) {
|
|
435
|
+
Object.entries(cAction.params).forEach(([ n, p ]) => {
|
|
436
|
+
const edmTargetName = `${actionName}/${n}`;
|
|
440
437
|
handleAnnotations(edmTargetName, p, [ ...location, 'params', n ]);
|
|
441
438
|
});
|
|
442
439
|
}
|
|
443
|
-
if(cAction.returns) {
|
|
444
|
-
const edmTargetName = actionName
|
|
440
|
+
if (cAction.returns) {
|
|
441
|
+
const edmTargetName = `${actionName}/$ReturnType`;
|
|
445
442
|
setProp(cAction.returns, '$appliesToReturnType', true);
|
|
446
443
|
handleAnnotations(edmTargetName, cAction.returns, [ ...location, 'returns' ]);
|
|
447
|
-
delete cAction.returns
|
|
444
|
+
delete cAction.returns.$appliesToReturnType;
|
|
448
445
|
}
|
|
449
446
|
|
|
450
447
|
function relParList() {
|
|
@@ -452,40 +449,37 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
452
449
|
const params = [];
|
|
453
450
|
if (entityNameIfBound) {
|
|
454
451
|
// If this is an action and has an explicit binding parameter add it here
|
|
455
|
-
if(cAction.$bindingParam && cAction.kind === 'action')
|
|
456
|
-
params.push(cAction.$bindingParam.items ?
|
|
457
|
-
|
|
452
|
+
if (cAction.$bindingParam && cAction.kind === 'action')
|
|
453
|
+
params.push(cAction.$bindingParam.items ? `Collection(${entityNameIfBound})` : entityNameIfBound);
|
|
454
|
+
|
|
458
455
|
// If action/function has no explicit binding parameter add it here
|
|
459
|
-
else if (!cAction.$bindingParam)
|
|
460
|
-
params.push(cAction['@cds.odata.bindingparameter.collection'] ?
|
|
461
|
-
}
|
|
456
|
+
else if (!cAction.$bindingParam)
|
|
457
|
+
params.push(cAction['@cds.odata.bindingparameter.collection'] ? `Collection(${entityNameIfBound})` : entityNameIfBound);
|
|
462
458
|
}
|
|
463
459
|
// In case this is a function the explicit binding parameter is part of
|
|
464
460
|
// the functions params dictionary. Only for functions all parameters must
|
|
465
461
|
// be listed in the annotation target
|
|
466
|
-
if (cAction.kind === 'function') {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
});
|
|
472
|
-
}
|
|
462
|
+
if (cAction.kind === 'function' && cAction.params) {
|
|
463
|
+
Object.values(cAction.params).forEach((p) => {
|
|
464
|
+
const isArrayType = !p.type && p.items && p.items.type;
|
|
465
|
+
params.push(isArrayType ? `Collection(${mapType(p.items)})` : mapType(p));
|
|
466
|
+
});
|
|
473
467
|
}
|
|
474
|
-
return
|
|
468
|
+
return `(${params.join(',')})`;
|
|
475
469
|
|
|
476
|
-
function mapType(p) {
|
|
477
|
-
if(isBuiltinType(p.type))
|
|
478
|
-
return edmUtils.mapCdsToEdmType(p, messageFunctions, false /*is only called for v4*/)
|
|
479
|
-
|
|
470
|
+
function mapType( p ) {
|
|
471
|
+
if (isBuiltinType(p.type)) {
|
|
472
|
+
return edmUtils.mapCdsToEdmType(p, messageFunctions, false /* is only called for v4 */);
|
|
473
|
+
}
|
|
474
|
+
else if (options.whatsMySchemaName) {
|
|
480
475
|
const schemaName = options.whatsMySchemaName(p.type);
|
|
481
|
-
|
|
482
|
-
if(schemaName && schemaName !== options.serviceName)
|
|
483
|
-
return p.type.replace(options.serviceName
|
|
476
|
+
// strip the service namespace of from a parameter type
|
|
477
|
+
if (schemaName && schemaName !== options.serviceName)
|
|
478
|
+
return p.type.replace(`${options.serviceName}.`, '');
|
|
484
479
|
}
|
|
485
480
|
return p.type;
|
|
486
481
|
}
|
|
487
482
|
}
|
|
488
|
-
|
|
489
483
|
}
|
|
490
484
|
|
|
491
485
|
|
|
@@ -493,7 +487,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
493
487
|
// edmTargetName : string, name of the target in edm
|
|
494
488
|
// carrier: object, the annotated cds thing, contains all the annotations
|
|
495
489
|
// as properties with names starting with @
|
|
496
|
-
function handleAnnotations(edmTargetName, carrier, location) {
|
|
490
|
+
function handleAnnotations( edmTargetName, carrier, location ) {
|
|
497
491
|
// collect the names of the carrier's annotation properties
|
|
498
492
|
// keep only those annotations that - start with a known vocabulary name
|
|
499
493
|
// - have a value other than null
|
|
@@ -503,20 +497,22 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
503
497
|
// if the carrier is a media stream element in V2
|
|
504
498
|
// do nothing
|
|
505
499
|
|
|
506
|
-
if(!isEdmPropertyRendered(carrier, options) ||
|
|
507
|
-
(isV2() && (edmUtils.isDerivedType(carrier))))
|
|
500
|
+
if (!isEdmPropertyRendered(carrier, options) ||
|
|
501
|
+
(isV2() && (edmUtils.isDerivedType(carrier))))
|
|
508
502
|
return;
|
|
509
|
-
|
|
503
|
+
|
|
510
504
|
|
|
511
505
|
// Filter unknown toplevel annotations
|
|
512
506
|
// Final filtering of all annotations is done in handleTerm
|
|
513
507
|
|
|
514
508
|
let knownAnnos = filterKnownAnnotations();
|
|
515
|
-
if (knownAnnos.length === 0)
|
|
509
|
+
if (knownAnnos.length === 0)
|
|
510
|
+
return;
|
|
516
511
|
|
|
517
|
-
if(rewriteInnerAnnotations()) {
|
|
512
|
+
if (rewriteInnerAnnotations()) {
|
|
518
513
|
knownAnnos = filterKnownAnnotations();
|
|
519
|
-
if (knownAnnos.length === 0)
|
|
514
|
+
if (knownAnnos.length === 0)
|
|
515
|
+
return;
|
|
520
516
|
}
|
|
521
517
|
|
|
522
518
|
const prefixTree = createPrefixTree();
|
|
@@ -529,12 +525,12 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
529
525
|
// later when looking at single annotations
|
|
530
526
|
|
|
531
527
|
const [
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
528
|
+
stdEdmTargetName, // either the schema path or the EntityContainer itself
|
|
529
|
+
hasAlternativeCarrier, // is the alternative annotation target available in the EDM?
|
|
530
|
+
alternativeEdmTargetName, // EntitySet path name
|
|
531
|
+
testToStandardEdmTarget, // if true, assign to standard Edm Target
|
|
532
|
+
testToAlternativeEdmTarget, // if true, assign to alternative Edm Target
|
|
533
|
+
] = initCarrierControlVars();
|
|
538
534
|
|
|
539
535
|
// collect produced Edm.Annotation nodes for various carriers
|
|
540
536
|
const serviceAnnotations = [];
|
|
@@ -546,66 +542,66 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
546
542
|
|
|
547
543
|
// Produce Edm.Annotations and attach collected Edm.Annotation(s) to the
|
|
548
544
|
// envelope (or directly to the Schema)
|
|
549
|
-
if(serviceAnnotations.length)
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if(stdAnnotations.length) {
|
|
545
|
+
if (serviceAnnotations.length)
|
|
546
|
+
gAnnosArray.push(...serviceAnnotations.filter(a => a));
|
|
547
|
+
|
|
548
|
+
if (stdAnnotations.length) {
|
|
553
549
|
const annotations = new Edm.Annotations(v, stdEdmTargetName); // used in closure
|
|
554
550
|
annotations.append(...stdAnnotations);
|
|
555
|
-
|
|
551
|
+
gAnnosArray.push(annotations);
|
|
556
552
|
}
|
|
557
|
-
if(alternativeAnnotations.length) {
|
|
553
|
+
if (alternativeAnnotations.length) {
|
|
558
554
|
const annotations = new Edm.Annotations(v, alternativeEdmTargetName);
|
|
559
555
|
annotations.append(...alternativeAnnotations);
|
|
560
|
-
|
|
556
|
+
gAnnosArray.push(annotations);
|
|
561
557
|
}
|
|
562
558
|
|
|
563
559
|
function filterKnownAnnotations() {
|
|
564
560
|
const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
565
561
|
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
566
|
-
const
|
|
562
|
+
const knownAnnosP = annoNames.filter((n) => {
|
|
567
563
|
const tns = whatsMyTermNamespace(n);
|
|
568
564
|
return tns &&
|
|
569
565
|
(mergedVocDefs[tns] && !mergedVocDefs[tns].$ignore ||
|
|
570
566
|
!mergedVocDefs[tns]);
|
|
571
567
|
}).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
572
|
-
if(isBetaEnabled(options, 'odataTerms')) {
|
|
568
|
+
if (isBetaEnabled(options, 'odataTerms')) {
|
|
573
569
|
// Extend knownAnnos with the in-service term definitions
|
|
574
|
-
annoNames.forEach(an => {
|
|
570
|
+
annoNames.forEach((an) => {
|
|
575
571
|
const paths = an.slice(1).split('.');
|
|
576
572
|
const hasNSPrefix = paths[0] === serviceName;
|
|
577
|
-
if(!hasNSPrefix)
|
|
573
|
+
if (!hasNSPrefix)
|
|
578
574
|
paths.splice(0, 0, serviceName);
|
|
579
|
-
|
|
580
|
-
const fqName =
|
|
575
|
+
|
|
576
|
+
const fqName = `@${paths.join('.')}`;
|
|
581
577
|
const i = paths[1].indexOf('#');
|
|
582
578
|
const termNameWithoutQualifiers = i > 0 ? paths[1].substring(0, i) : paths[1];
|
|
583
|
-
const def = reqDefs.definitions[paths[0]
|
|
579
|
+
const def = reqDefs.definitions[`${paths[0]}.${termNameWithoutQualifiers}`];
|
|
584
580
|
// if there is a term definition inside the service and the
|
|
585
581
|
// annotation value is != null, then add the annotation to the list
|
|
586
582
|
// of known annotations
|
|
587
|
-
if(def?.kind === 'annotation' && carrier[an] !== null) {
|
|
583
|
+
if (def?.kind === 'annotation' && carrier[an] !== null) {
|
|
588
584
|
// Subsequent annotation handler code expects that first path segment
|
|
589
585
|
// is the Vocabulary namespace. The ad-hoc namespace is the service
|
|
590
586
|
// name itself.
|
|
591
587
|
// For service S an annotation assignment could be addressed
|
|
592
588
|
// relative or absolute to the service @S.foo or @foo
|
|
593
|
-
if(!hasNSPrefix) {
|
|
589
|
+
if (!hasNSPrefix) {
|
|
594
590
|
carrier[fqName] = carrier[an];
|
|
595
591
|
delete carrier[an];
|
|
596
592
|
}
|
|
597
|
-
|
|
593
|
+
knownAnnosP.push(fqName);
|
|
598
594
|
}
|
|
599
595
|
});
|
|
600
596
|
}
|
|
601
|
-
return
|
|
597
|
+
return knownAnnosP;
|
|
602
598
|
}
|
|
603
599
|
|
|
604
600
|
// construct a function that is used to add an <Annotation ...> to the
|
|
605
601
|
// respective collector array
|
|
606
602
|
// this function is specific to the actual carrier, following the mapping rules given above
|
|
607
|
-
function addAnnotation(annotation, appliesTo) {
|
|
608
|
-
let rc=false;
|
|
603
|
+
function addAnnotation( annotation, appliesTo ) {
|
|
604
|
+
let rc = false;
|
|
609
605
|
if (testToAlternativeEdmTarget && appliesTo && testToAlternativeEdmTarget(appliesTo)) {
|
|
610
606
|
if (carrier.kind === 'service') {
|
|
611
607
|
if (isV2()) {
|
|
@@ -614,98 +610,98 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
614
610
|
}
|
|
615
611
|
serviceAnnotations.push(annotation); // for target Schema: no <Annotations> element
|
|
616
612
|
}
|
|
617
|
-
else if(hasAlternativeCarrier) {
|
|
613
|
+
else if (hasAlternativeCarrier) {
|
|
618
614
|
alternativeAnnotations.push(annotation);
|
|
619
615
|
}
|
|
620
|
-
rc=true;
|
|
616
|
+
rc = true;
|
|
621
617
|
}
|
|
622
|
-
if(testToStandardEdmTarget(appliesTo)) {
|
|
618
|
+
if (testToStandardEdmTarget(appliesTo)) {
|
|
623
619
|
stdAnnotations.push(annotation);
|
|
624
|
-
rc=true;
|
|
620
|
+
rc = true;
|
|
625
621
|
}
|
|
626
622
|
// Another crazy hack due to this crazy function:
|
|
627
623
|
// If carrier is a managed association (has keys) and rc is false (annotation was not applicable)
|
|
628
624
|
// return true to NOT trigger 'unapplicable' message message
|
|
629
|
-
if(rc === false && carrier.target && carrier.keys && appliesTo.includes('Property'))
|
|
625
|
+
if (rc === false && carrier.target && carrier.keys && appliesTo.includes('Property'))
|
|
630
626
|
rc = true;
|
|
631
627
|
return rc;
|
|
632
628
|
}
|
|
633
629
|
|
|
634
630
|
function initCarrierControlVars() {
|
|
635
631
|
// eslint-disable-next-line no-unused-vars
|
|
636
|
-
let
|
|
637
|
-
let
|
|
638
|
-
let
|
|
639
|
-
let
|
|
640
|
-
let
|
|
632
|
+
let testToStandardEdmTargetP = () => true; // if true, assign to standard Edm Target
|
|
633
|
+
let stdEdmTargetNameP = edmTargetName;
|
|
634
|
+
let alternativeEdmTargetNameP = null;
|
|
635
|
+
let hasAlternativeCarrierP = false; // is the alternative annotation target available in the EDM?
|
|
636
|
+
let testToAlternativeEdmTargetP = null; // if true, assign to alternative Edm Target
|
|
641
637
|
|
|
642
638
|
if (carrier.kind === 'entity') {
|
|
643
639
|
// If AppliesTo=[EntitySet/Singleton/Collection, EntityType], EntitySet/Singleton/Collection has precedence
|
|
644
|
-
|
|
645
|
-
if(options.isV2())
|
|
646
|
-
return ['Singleton', 'EntitySet', 'Collection'].some(y => x.includes(y));
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
640
|
+
testToAlternativeEdmTargetP = ((x) => {
|
|
641
|
+
if (options.isV2())
|
|
642
|
+
return [ 'Singleton', 'EntitySet', 'Collection' ].some(y => x.includes(y));
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
return edmUtils.isSingleton(carrier)
|
|
646
|
+
? x.includes('Singleton')
|
|
647
|
+
: [ 'EntitySet', 'Collection' ].some(y => x.includes(y));
|
|
653
648
|
});
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const lastDotIndex =
|
|
649
|
+
testToStandardEdmTargetP = (x => (x ? x.includes('EntityType') : true));
|
|
650
|
+
// if carrier has an alternate 'entitySetName' use this instead of EdmTargetName
|
|
651
|
+
// (see edmPreprocessor.initializeParameterizedEntityOrView(), where parameterized artifacts
|
|
652
|
+
// are split into *Parameter and *Type entities and their respective EntitySets are eventually
|
|
653
|
+
// renamed.
|
|
654
|
+
// (which is the definition key in the CSN and usually the name of the EntityType)
|
|
655
|
+
// Replace up to last dot with <serviceName>.EntityContainer/
|
|
656
|
+
alternativeEdmTargetNameP = carrier.$entitySetName || edmTargetName;
|
|
657
|
+
const lastDotIndex = alternativeEdmTargetNameP.lastIndexOf('.');
|
|
663
658
|
if (lastDotIndex > -1)
|
|
664
|
-
|
|
665
|
-
|
|
659
|
+
alternativeEdmTargetNameP = `${serviceName}.EntityContainer/${alternativeEdmTargetNameP.substring(lastDotIndex + 1)}`;
|
|
660
|
+
hasAlternativeCarrierP = carrier.$hasEntitySet;
|
|
666
661
|
}
|
|
667
|
-
else if(carrier.kind === 'type') {
|
|
668
|
-
|
|
662
|
+
else if (carrier.kind === 'type') {
|
|
663
|
+
testToStandardEdmTargetP = (x => (x ? x.includes(carrier.elements ? 'ComplexType' : 'TypeDefinition') : true));
|
|
669
664
|
}
|
|
670
665
|
else if (carrier.kind === 'service') {
|
|
671
666
|
// if annotated object is a service, annotation goes to EntityContainer,
|
|
672
667
|
// except if AppliesTo contains Schema but not EntityContainer, then annotation goes to Schema
|
|
673
|
-
|
|
674
|
-
|
|
668
|
+
testToAlternativeEdmTargetP = (x => x.includes('Schema') && !x.includes('EntityContainer'));
|
|
669
|
+
testToStandardEdmTargetP = ( x => (x ? (
|
|
675
670
|
// either only AppliesTo=[EntityContainer]
|
|
676
|
-
|
|
671
|
+
(!x.includes('Schema') && x.includes('EntityContainer')) ||
|
|
677
672
|
// or AppliesTo=[Schema, EntityContainer]
|
|
678
673
|
(x.includes('Schema') && x.includes('EntityContainer')))
|
|
679
|
-
: true );
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
674
|
+
: true) );
|
|
675
|
+
stdEdmTargetNameP = `${edmTargetName}.EntityContainer`;
|
|
676
|
+
alternativeEdmTargetNameP = edmTargetName;
|
|
677
|
+
hasAlternativeCarrierP = true; // EntityContainer is always available
|
|
683
678
|
}
|
|
684
|
-
//element => decide if navprop or normal property
|
|
685
|
-
else if(!carrier.kind) {
|
|
679
|
+
// element => decide if navprop or normal property
|
|
680
|
+
else if (!carrier.kind) {
|
|
686
681
|
// if appliesTo is undefined, return true
|
|
687
|
-
if(carrier.target) {
|
|
688
|
-
|
|
682
|
+
if (carrier.target) {
|
|
683
|
+
testToStandardEdmTargetP = (x => (x
|
|
689
684
|
? x.includes('NavigationProperty') ||
|
|
690
685
|
carrier.cardinality && carrier.cardinality.max === '*' && x.includes('Collection')
|
|
691
|
-
: true);
|
|
686
|
+
: true));
|
|
687
|
+
}
|
|
688
|
+
else if (carrier.$appliesToReturnType) {
|
|
689
|
+
testToStandardEdmTargetP = (x => (x ? x.includes('ReturnType') : true));
|
|
692
690
|
}
|
|
693
|
-
else if(carrier.$appliesToReturnType)
|
|
694
|
-
testToStandardEdmTarget = (x => x ? x.includes('ReturnType') : true);
|
|
695
691
|
else {
|
|
696
692
|
// this might be more precise if handleAnnotation would know more about the carrier
|
|
697
|
-
|
|
698
|
-
? ['Parameter', 'Property'].some(y => x.includes(y) ||
|
|
693
|
+
testToStandardEdmTargetP = (x => (x
|
|
694
|
+
? [ 'Parameter', 'Property' ].some(y => x.includes(y) ||
|
|
699
695
|
carrier._isCollection && x.includes('Collection'))
|
|
700
|
-
: true);
|
|
696
|
+
: true));
|
|
701
697
|
}
|
|
702
698
|
}
|
|
703
699
|
return [
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
700
|
+
stdEdmTargetNameP,
|
|
701
|
+
hasAlternativeCarrierP,
|
|
702
|
+
alternativeEdmTargetNameP,
|
|
703
|
+
testToStandardEdmTargetP,
|
|
704
|
+
testToAlternativeEdmTargetP,
|
|
709
705
|
];
|
|
710
706
|
/* all AppliesTo entries:
|
|
711
707
|
"Action",
|
|
@@ -759,17 +755,17 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
759
755
|
|
|
760
756
|
Insert $value into $edmJson with inner annotation as well.
|
|
761
757
|
*/
|
|
762
|
-
if(innerAnnotation) {
|
|
758
|
+
if (innerAnnotation) {
|
|
763
759
|
// != null => also != undefined
|
|
764
|
-
if(carrier[prefix] != null) {
|
|
765
|
-
const valPrefix = prefix
|
|
760
|
+
if (carrier[prefix] != null) {
|
|
761
|
+
const valPrefix = `${prefix}.$value`;
|
|
766
762
|
carrier[valPrefix] = carrier[prefix];
|
|
767
763
|
delete carrier[prefix];
|
|
768
764
|
rc = true;
|
|
769
765
|
}
|
|
770
|
-
const edmJsonPrefix = prefix
|
|
771
|
-
if(carrier[edmJsonPrefix] != null) {
|
|
772
|
-
const valPrefix = prefix
|
|
766
|
+
const edmJsonPrefix = `${prefix}.$edmJson`;
|
|
767
|
+
if (carrier[edmJsonPrefix] != null) {
|
|
768
|
+
const valPrefix = `${prefix}.$value.$edmJson`;
|
|
773
769
|
carrier[valPrefix] = carrier[edmJsonPrefix];
|
|
774
770
|
delete carrier[edmJsonPrefix];
|
|
775
771
|
rc = true;
|
|
@@ -787,7 +783,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
787
783
|
// in OData, there are "structured" annotations -> we first need to regroup the cds annotations
|
|
788
784
|
// by building a "prefix tree" for the annotations attached to the carrier
|
|
789
785
|
// see example at definition of function mergePathStepsIntoPrefixTree
|
|
790
|
-
const
|
|
786
|
+
const prefixTreeP = {};
|
|
791
787
|
|
|
792
788
|
for (const a of knownAnnos) {
|
|
793
789
|
// remove leading @ and split at "."
|
|
@@ -798,27 +794,26 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
798
794
|
// takes care of assigning these annotations to the record members
|
|
799
795
|
const [ prefix, innerAnnotation ] = a.split('.@');
|
|
800
796
|
const ns = whatsMyTermNamespace(prefix);
|
|
801
|
-
const steps = prefix.replace(
|
|
797
|
+
const steps = prefix.replace(`@${ns}.`, '').split('.');
|
|
802
798
|
steps.splice(0, 0, ns);
|
|
803
799
|
let i = steps.lastIndexOf('$edmJson');
|
|
804
|
-
if(i > -1) {
|
|
805
|
-
i = steps.findIndex(s => s.includes('@'), i+1);
|
|
806
|
-
if(i > -1)
|
|
807
|
-
steps.splice(i, steps.length-i, steps.slice(i).join('.'));
|
|
808
|
-
}
|
|
800
|
+
if (i > -1) {
|
|
801
|
+
i = steps.findIndex(s => s.includes('@'), i + 1);
|
|
802
|
+
if (i > -1)
|
|
803
|
+
steps.splice(i, steps.length - i, steps.slice(i).join('.'));
|
|
809
804
|
}
|
|
810
805
|
if (innerAnnotation) {
|
|
811
806
|
// A voc annotation has two steps (Namespace+Name),
|
|
812
807
|
// any further steps need to be rendered separately
|
|
813
808
|
const innerAnnoSteps = innerAnnotation.split('.');
|
|
814
|
-
const tailSteps = innerAnnoSteps.splice(2, innerAnnoSteps.length-2);
|
|
815
|
-
|
|
816
|
-
tailSteps.splice(0, 0,
|
|
809
|
+
const tailSteps = innerAnnoSteps.splice(2, innerAnnoSteps.length - 2);
|
|
810
|
+
// prepend annotation prefix (path) to tail steps
|
|
811
|
+
tailSteps.splice(0, 0, `@${innerAnnoSteps.join('.')}`);
|
|
817
812
|
steps.push(...tailSteps);
|
|
818
813
|
}
|
|
819
|
-
mergePathStepsIntoPrefixTree(
|
|
814
|
+
mergePathStepsIntoPrefixTree(prefixTreeP, steps, 0);
|
|
820
815
|
}
|
|
821
|
-
return
|
|
816
|
+
return prefixTreeP;
|
|
822
817
|
|
|
823
818
|
// tree: object where to put the next level of names
|
|
824
819
|
// path: the parts of the annotation name
|
|
@@ -843,17 +838,17 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
843
838
|
// q2 : ... }
|
|
844
839
|
// t3#y : { q1 : ...,
|
|
845
840
|
// q2 : ... } } }
|
|
846
|
-
function mergePathStepsIntoPrefixTree(tree, pathSteps, index
|
|
841
|
+
function mergePathStepsIntoPrefixTree( tree, pathSteps, index ) {
|
|
847
842
|
// TODO check nesting level > 3
|
|
848
843
|
const name = pathSteps[index];
|
|
849
|
-
if (index+1 < pathSteps.length ) {
|
|
850
|
-
if (!tree[name])
|
|
844
|
+
if (index + 1 < pathSteps.length ) {
|
|
845
|
+
if (!tree[name])
|
|
851
846
|
tree[name] = {};
|
|
852
|
-
|
|
853
|
-
mergePathStepsIntoPrefixTree(tree[name], pathSteps, index+1
|
|
847
|
+
|
|
848
|
+
mergePathStepsIntoPrefixTree(tree[name], pathSteps, index + 1);
|
|
854
849
|
}
|
|
855
|
-
else if(typeof tree === 'object' ){
|
|
856
|
-
tree[name] = carrier[
|
|
850
|
+
else if (typeof tree === 'object' ) {
|
|
851
|
+
tree[name] = carrier[`@${pathSteps.join('.')}`];
|
|
857
852
|
}
|
|
858
853
|
}
|
|
859
854
|
}
|
|
@@ -864,34 +859,33 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
864
859
|
// addAnnotationFunc: a function that adds the <Annotation ...> tags created here into the
|
|
865
860
|
// correct parent tag (see handleAnnotations())
|
|
866
861
|
// prefixTree: the annotations
|
|
867
|
-
function handleAnno2(addAnnotationFunc, prefixTree, location) {
|
|
862
|
+
function handleAnno2( addAnnotationFunc, prefixTree, location ) {
|
|
868
863
|
// first level names of prefix tree are the vocabulary names
|
|
869
864
|
// second level names are the term names
|
|
870
865
|
// create an annotation tag <Annotation ...> for each term
|
|
871
866
|
for (const voc of Object.keys(prefixTree)) {
|
|
872
867
|
for (const term of Object.keys(prefixTree[voc])) {
|
|
873
|
-
const fullTermName = voc
|
|
868
|
+
const fullTermName = `${voc}.${term}`;
|
|
874
869
|
|
|
875
870
|
// msg is "semantic" location message used for messages
|
|
876
871
|
const msg = {
|
|
877
872
|
fullTermName,
|
|
878
873
|
stack: [],
|
|
879
|
-
location: [ ...location,
|
|
874
|
+
location: [ ...location, `@${fullTermName}` ],
|
|
880
875
|
};
|
|
881
|
-
msg.anno = () =>
|
|
882
|
-
return msg.fullTermName + msg.stack.join('');
|
|
883
|
-
}
|
|
876
|
+
msg.anno = () => msg.fullTermName + msg.stack.join('');
|
|
884
877
|
|
|
885
878
|
// anno is the full <Annotation Term=...>
|
|
886
879
|
const anno = handleTerm(fullTermName, prefixTree[voc][term], msg);
|
|
887
|
-
if(anno !== undefined) {
|
|
880
|
+
if (anno !== undefined) {
|
|
888
881
|
// addAnnotationFunc needs AppliesTo message from dictionary to decide where to put the anno
|
|
889
882
|
const termName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
|
|
890
883
|
const dictTerm = getDictTerm(termName, msg); // message for unknown term was already issued in handleTerm
|
|
891
|
-
|
|
892
|
-
|
|
884
|
+
// eslint-disable-next-line sonarjs/no-collapsible-if
|
|
885
|
+
if (!addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo)) {
|
|
886
|
+
if (dictTerm && dictTerm.AppliesTo) {
|
|
893
887
|
message('odata-anno-def', location,
|
|
894
|
-
|
|
888
|
+
{ anno: termName, rawvalues: dictTerm.AppliesTo, '#': 'notapplied' });
|
|
895
889
|
}
|
|
896
890
|
}
|
|
897
891
|
}
|
|
@@ -905,30 +899,30 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
905
899
|
// they are regrouped here
|
|
906
900
|
// msg : for messages
|
|
907
901
|
// return : object that represents the annotation in the result edmx
|
|
908
|
-
function handleTerm(termName, annoValue, msg) {
|
|
902
|
+
function handleTerm( termName, annoValue, msg ) {
|
|
909
903
|
/**
|
|
910
904
|
* create the <Annotation ...> tag
|
|
911
905
|
* @type {object}
|
|
912
906
|
* */
|
|
913
|
-
let newAnno
|
|
914
|
-
const omissions = { 'Aggregation.default':1 };
|
|
915
|
-
const nullList = { 'Core.OperationAvailable':1 };
|
|
916
|
-
if(annoValue !== null && !omissions[termName]|| nullList[termName]) {
|
|
907
|
+
let newAnno;
|
|
908
|
+
const omissions = { 'Aggregation.default': 1 };
|
|
909
|
+
const nullList = { 'Core.OperationAvailable': 1 };
|
|
910
|
+
if (annoValue !== null && !omissions[termName] || nullList[termName]) {
|
|
917
911
|
// termName may contain a qualifier: @UI.FieldGroup#shippingStatus
|
|
918
912
|
// -> remove qualifier from termName and set Qualifier attribute in newAnno
|
|
919
913
|
const i = termName.indexOf('#');
|
|
920
914
|
const termNameWithoutQualifiers = i > 0 ? termName.substring(0, i) : termName;
|
|
921
|
-
const qualifier = i >= 0 ? termName.substring(i+1) : undefined;
|
|
915
|
+
const qualifier = i >= 0 ? termName.substring(i + 1) : undefined;
|
|
922
916
|
|
|
923
917
|
termNameWithoutQualifiers.split('.').forEach((id) => {
|
|
924
|
-
if(!edmUtils.isODataSimpleIdentifier(id))
|
|
918
|
+
if (!edmUtils.isODataSimpleIdentifier(id))
|
|
925
919
|
message('odata-spec-violation-id', msg.location, { id });
|
|
926
|
-
})
|
|
920
|
+
});
|
|
927
921
|
newAnno = new Edm.Annotation(v, termNameWithoutQualifiers);
|
|
928
922
|
if (qualifier?.length) {
|
|
929
923
|
if (!edmUtils.isODataSimpleIdentifier(qualifier)) {
|
|
930
924
|
message('odata-spec-violation-id', msg.location,
|
|
931
|
-
|
|
925
|
+
{ id: qualifier, '#': 'qualifier' });
|
|
932
926
|
}
|
|
933
927
|
newAnno.setEdmAttribute('Term', termNameWithoutQualifiers);
|
|
934
928
|
newAnno.setEdmAttribute('Qualifier', qualifier);
|
|
@@ -941,9 +935,9 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
941
935
|
}
|
|
942
936
|
else {
|
|
943
937
|
// message if term is completely unknown or if vocabulary is unchecked
|
|
944
|
-
const
|
|
945
|
-
if((
|
|
946
|
-
message('odata-anno-def', msg.location,{ anno: termNameWithoutQualifiers });
|
|
938
|
+
const myVocDef = mergedVocDefs[whatsMyTermNamespace(`@${termNameWithoutQualifiers}`)];
|
|
939
|
+
if ((myVocDef?.int && myVocDef?.int?.filename) || !myVocDef)
|
|
940
|
+
message('odata-anno-def', msg.location, { anno: termNameWithoutQualifiers });
|
|
947
941
|
}
|
|
948
942
|
|
|
949
943
|
// handle the annotation value and put the result into the <Annotation ...> tag just created above
|
|
@@ -957,131 +951,143 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
957
951
|
// cAnnoValue: the annotation value (c : csn)
|
|
958
952
|
// oTarget: the result object (o: odata)
|
|
959
953
|
// oTermName: current term
|
|
960
|
-
//
|
|
961
|
-
function handleValue(cAnnoValue, oTarget, oTermName,
|
|
954
|
+
// dTypeNameArg: expected type of cAnnoValue according to dictionary, may be null (d: dictionary)
|
|
955
|
+
function handleValue( cAnnoValue, oTarget, oTermName, dTypeNameArg, msg ) {
|
|
962
956
|
// this function basically only figures out what kind of annotation value we have
|
|
963
957
|
// (can be: array, expression, enum, pseudo-record, record, simple value),
|
|
964
958
|
// then calls a more specific function to deal with it and puts
|
|
965
959
|
// the result into the oTarget object
|
|
966
960
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
{
|
|
961
|
+
const [ dTypeName, dTypeIsACollection ] = stripCollection(dTypeNameArg);
|
|
962
|
+
|
|
963
|
+
if (Array.isArray(cAnnoValue)) {
|
|
964
|
+
if (isEnumType(dTypeName)) {
|
|
971
965
|
// if we find an array although we expect an enum, this may be a "flag enum"
|
|
972
|
-
checkMultiEnumValue(
|
|
973
|
-
oTarget.setJSON({
|
|
974
|
-
oTarget.setXml( {
|
|
966
|
+
checkMultiEnumValue();
|
|
967
|
+
oTarget.setJSON({ EnumMember: generateMultiEnumValue(false), 'EnumMember@odata.type': `#${dTypeName}` });
|
|
968
|
+
oTarget.setXml( { EnumMember: generateMultiEnumValue(true) });
|
|
975
969
|
}
|
|
976
|
-
else
|
|
977
|
-
|
|
978
|
-
oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName, msg));
|
|
970
|
+
else {
|
|
971
|
+
oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
|
|
979
972
|
}
|
|
980
973
|
}
|
|
981
974
|
else if (cAnnoValue && typeof cAnnoValue === 'object') {
|
|
982
975
|
// an empty record is rendered as <Record/>
|
|
983
976
|
if ('=' in cAnnoValue) {
|
|
977
|
+
if (dTypeIsACollection) {
|
|
978
|
+
message('odata-anno-value', msg.location,
|
|
979
|
+
{ anno: msg.anno(), str: 'path', '#': 'incompval' });
|
|
980
|
+
}
|
|
984
981
|
// expression
|
|
985
982
|
const res = handleExpression(cAnnoValue['='], dTypeName);
|
|
986
|
-
oTarget.setXml( { [res.name]
|
|
987
|
-
oTarget.setJSON( { [res.name]
|
|
983
|
+
oTarget.setXml( { [res.name]: res.value });
|
|
984
|
+
oTarget.setJSON( { [res.name]: res.value });
|
|
988
985
|
}
|
|
989
986
|
else if (cAnnoValue['#'] !== undefined) {
|
|
990
987
|
const enumSymbol = cAnnoValue['#'];
|
|
991
988
|
// enum
|
|
992
989
|
if (dTypeName) {
|
|
993
|
-
const typeDef = getDictType(
|
|
994
|
-
if(typeDef && typeDef.$Allowed && !typeDef.Members) {
|
|
990
|
+
const typeDef = getDictType(dTypeName);
|
|
991
|
+
if (typeDef && typeDef.$Allowed && !typeDef.Members) {
|
|
995
992
|
const allowedValue = typeDef.$Allowed.Symbols[enumSymbol];
|
|
996
|
-
if(!allowedValue) {
|
|
993
|
+
if (!allowedValue) {
|
|
997
994
|
message('odata-anno-value', msg.location,
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
995
|
+
{
|
|
996
|
+
anno: msg.anno(),
|
|
997
|
+
type: dTypeName,
|
|
998
|
+
value: `"#${enumSymbol}"`,
|
|
999
|
+
rawvalues: Object.keys(typeDef.$Allowed.Symbols).map(m => `"#${m}"`),
|
|
1000
|
+
'#': 'enum',
|
|
1001
|
+
});
|
|
1004
1002
|
}
|
|
1005
1003
|
else {
|
|
1006
1004
|
oTarget.setXml( { [typeDef.UnderlyingType?.replace('Edm.', '') || 'String']: allowedValue.Value || enumSymbol });
|
|
1007
1005
|
}
|
|
1008
1006
|
}
|
|
1009
|
-
else if(checkEnumValue(enumSymbol))
|
|
1010
|
-
oTarget.setXml( {
|
|
1011
|
-
|
|
1012
|
-
|
|
1007
|
+
else if (checkEnumValue(enumSymbol)) {
|
|
1008
|
+
oTarget.setXml( { EnumMember: `${dTypeName}/${enumSymbol}` });
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
oTarget.setXml( { String: enumSymbol });
|
|
1012
|
+
}
|
|
1013
1013
|
}
|
|
1014
1014
|
else {
|
|
1015
|
-
oTarget.setXml( {
|
|
1015
|
+
oTarget.setXml( { EnumMember: `${oTermName}Type/${enumSymbol}` });
|
|
1016
1016
|
}
|
|
1017
1017
|
oTarget.setJSON({ 'Edm.String': enumSymbol });
|
|
1018
1018
|
}
|
|
1019
|
-
else if (cAnnoValue
|
|
1019
|
+
else if (cAnnoValue.$value !== undefined) {
|
|
1020
1020
|
// "pseudo-structure" used for annotating scalar annotations
|
|
1021
|
-
handleValue(cAnnoValue
|
|
1021
|
+
handleValue(cAnnoValue.$value, oTarget, oTermName, dTypeNameArg, msg);
|
|
1022
1022
|
|
|
1023
1023
|
const k = Object.keys(cAnnoValue).filter( x => x[0] === '@');
|
|
1024
1024
|
if (!k || k.length === 0) {
|
|
1025
1025
|
message('odata-anno-value', msg.location,
|
|
1026
|
-
|
|
1026
|
+
{ anno: msg.anno(), str: 'nested', '#': 'nested' });
|
|
1027
1027
|
}
|
|
1028
1028
|
for (const nestedAnnoName of k) {
|
|
1029
1029
|
const nestedAnno = handleTerm(nestedAnnoName.slice(1), cAnnoValue[nestedAnnoName], msg);
|
|
1030
1030
|
oTarget.append(nestedAnno);
|
|
1031
1031
|
}
|
|
1032
1032
|
}
|
|
1033
|
-
else if (cAnnoValue
|
|
1033
|
+
else if (cAnnoValue.$edmJson) {
|
|
1034
1034
|
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
|
|
1035
|
-
oTarget.append(handleEdmJson(cAnnoValue
|
|
1035
|
+
oTarget.append(handleEdmJson(cAnnoValue.$edmJson, msg));
|
|
1036
1036
|
}
|
|
1037
1037
|
else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
1038
1038
|
// object consists only of properties starting with "@", no $value
|
|
1039
1039
|
message('odata-anno-value', msg.location,
|
|
1040
|
-
|
|
1040
|
+
{ anno: msg.anno(), str: 'base', '#': 'nested' } );
|
|
1041
1041
|
}
|
|
1042
1042
|
else {
|
|
1043
1043
|
// regular record
|
|
1044
|
-
|
|
1044
|
+
if (dTypeIsACollection) {
|
|
1045
|
+
message('odata-anno-value', msg.location,
|
|
1046
|
+
{ anno: msg.anno(), str: 'structured', '#': 'incompval' });
|
|
1047
|
+
}
|
|
1048
|
+
oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
|
|
1045
1049
|
}
|
|
1046
1050
|
}
|
|
1047
1051
|
else {
|
|
1048
1052
|
const res = handleSimpleValue(cAnnoValue, dTypeName, msg);
|
|
1049
|
-
if(((oTermName === 'Core.OperationAvailable' && dTypeName === 'Edm.Boolean') ||
|
|
1050
|
-
(oTermName === 'Validation.AllowedValues' && dTypeName === 'Edm.PrimitiveType'))
|
|
1051
|
-
|
|
1053
|
+
if (((oTermName === 'Core.OperationAvailable' && dTypeName === 'Edm.Boolean') ||
|
|
1054
|
+
(oTermName === 'Validation.AllowedValues' && dTypeName === 'Edm.PrimitiveType')) &&
|
|
1055
|
+
cAnnoValue === null) {
|
|
1052
1056
|
oTarget.append(new Edm.ValueThing(v, 'Null'));
|
|
1053
1057
|
oTarget._ignoreChildren = true;
|
|
1054
1058
|
}
|
|
1055
1059
|
else {
|
|
1056
|
-
oTarget.setXml( { [res.name]
|
|
1060
|
+
oTarget.setXml( { [res.name]: res.value });
|
|
1057
1061
|
}
|
|
1058
|
-
oTarget.setJSON( { [res.jsonName]
|
|
1062
|
+
oTarget.setJSON( { [res.jsonName]: res.value });
|
|
1059
1063
|
}
|
|
1060
|
-
|
|
1061
1064
|
// found an enum value ("#"), check whether this fits
|
|
1062
1065
|
// the expected type "dTypeName"
|
|
1063
|
-
function checkEnumValue(value) {
|
|
1066
|
+
function checkEnumValue( value ) {
|
|
1064
1067
|
let rc = true;
|
|
1065
1068
|
const expectedType = getDictType(dTypeName);
|
|
1066
1069
|
if (!expectedType && !isPrimitiveType(dTypeName)) {
|
|
1067
1070
|
message('odata-anno-dict', msg.location,
|
|
1068
|
-
|
|
1071
|
+
{ anno: msg.anno(), type: dTypeName });
|
|
1069
1072
|
}
|
|
1070
|
-
else if (isComplexType(dTypeName) || isPrimitiveType(dTypeName) || expectedType
|
|
1073
|
+
else if (isComplexType(dTypeName) || isPrimitiveType(dTypeName) || expectedType.$kind !== 'EnumType') {
|
|
1071
1074
|
message('odata-anno-value', msg.location,
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
+
{
|
|
1076
|
+
anno: msg.anno(),
|
|
1077
|
+
type: dTypeName,
|
|
1078
|
+
value: `"#${value}"`,
|
|
1079
|
+
});
|
|
1075
1080
|
rc = false;
|
|
1076
1081
|
}
|
|
1077
1082
|
else if (!expectedType.Members.includes(value)) {
|
|
1078
1083
|
message('odata-anno-value', msg.location,
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1084
|
+
{
|
|
1085
|
+
anno: msg.anno(),
|
|
1086
|
+
type: dTypeName,
|
|
1087
|
+
value: `"#${value}"`,
|
|
1088
|
+
rawvalues: expectedType.Members.map(m => `"#${m}"`),
|
|
1089
|
+
'#': 'enum',
|
|
1090
|
+
});
|
|
1085
1091
|
}
|
|
1086
1092
|
return rc;
|
|
1087
1093
|
}
|
|
@@ -1089,64 +1095,65 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1089
1095
|
// cAnnoValue: array
|
|
1090
1096
|
// dTypeName: expected type, already identified as enum type
|
|
1091
1097
|
// array is expected to contain enum values
|
|
1092
|
-
function checkMultiEnumValue(
|
|
1093
|
-
|
|
1098
|
+
function checkMultiEnumValue( ) {
|
|
1099
|
+
// we know that dTypeName is not null
|
|
1094
1100
|
const type = getDictType(dTypeName);
|
|
1095
|
-
if (!type || type
|
|
1101
|
+
if (!type || type.IsFlags !== 'true') {
|
|
1096
1102
|
message('odata-anno-value', msg.location,
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1103
|
+
{
|
|
1104
|
+
anno: msg.anno(),
|
|
1105
|
+
str: 'collection',
|
|
1106
|
+
type: dTypeName,
|
|
1107
|
+
'#': 'incompval',
|
|
1108
|
+
});
|
|
1101
1109
|
}
|
|
1102
1110
|
|
|
1103
1111
|
let index = 0;
|
|
1104
|
-
for (
|
|
1105
|
-
msg.stack.push(
|
|
1112
|
+
for (const value of cAnnoValue) {
|
|
1113
|
+
msg.stack.push(`[${index}]`);
|
|
1106
1114
|
index++;
|
|
1107
1115
|
if (value['#']) {
|
|
1108
1116
|
checkEnumValue(value['#']);
|
|
1109
1117
|
}
|
|
1110
1118
|
else {
|
|
1111
1119
|
message('odata-anno-value', msg.location,
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1120
|
+
{
|
|
1121
|
+
anno: msg.anno(),
|
|
1122
|
+
type: dTypeName,
|
|
1123
|
+
value: value['='] || value,
|
|
1124
|
+
rawvalues: type.Members.map(m => `"#${m}"`),
|
|
1125
|
+
'#': 'enum',
|
|
1126
|
+
});
|
|
1118
1127
|
}
|
|
1119
1128
|
msg.stack.pop();
|
|
1120
1129
|
}
|
|
1121
1130
|
}
|
|
1122
1131
|
|
|
1123
|
-
function generateMultiEnumValue(
|
|
1132
|
+
function generateMultiEnumValue( forXml ) {
|
|
1124
1133
|
// remove all invalid entries (warnining message has already been issued)
|
|
1125
1134
|
// replace short enum name by the full name
|
|
1126
1135
|
// concatenate all the enums to a string, separated by spaces
|
|
1127
|
-
return cAnnoValue.filter( x => x['#']
|
|
1136
|
+
return cAnnoValue.filter( x => x['#']).map( x => (forXml ? `${dTypeName}/` : '') + x['#'] ).join(forXml ? ' ' : ',');
|
|
1128
1137
|
}
|
|
1129
1138
|
}
|
|
1130
1139
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
1140
|
// found an expression value ("=") "expr"
|
|
1134
1141
|
// expected type is dTypeName
|
|
1135
1142
|
// note: expr can also be provided if an enum/complex type/collection is expected
|
|
1136
|
-
function handleExpression(value, dTypeName) {
|
|
1143
|
+
function handleExpression( value, dTypeName ) {
|
|
1137
1144
|
let typeName = 'Path';
|
|
1138
|
-
if( ['Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(dTypeName) )
|
|
1145
|
+
if ( [ 'Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(dTypeName) )
|
|
1139
1146
|
typeName = dTypeName.split('.')[1];
|
|
1140
1147
|
|
|
1141
|
-
if(value) {
|
|
1148
|
+
if (value) {
|
|
1142
1149
|
// replace all occurrences of '.' by '/' up to first '@'
|
|
1143
|
-
value = value.split('@').map((o,i) => (i === 0 ? o.replace(/\./g, '/') : o)).join('@');
|
|
1150
|
+
value = value.split('@').map((o, i) => (i === 0 ? o.replace(/\./g, '/') : o)).join('@');
|
|
1144
1151
|
}
|
|
1145
1152
|
|
|
1146
1153
|
return {
|
|
1147
|
-
name
|
|
1148
|
-
value
|
|
1149
|
-
}
|
|
1154
|
+
name: typeName,
|
|
1155
|
+
value,
|
|
1156
|
+
};
|
|
1150
1157
|
}
|
|
1151
1158
|
|
|
1152
1159
|
|
|
@@ -1157,7 +1164,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1157
1164
|
// floating point type except Edm.Decimal -> Float
|
|
1158
1165
|
// Edm.Decimal -> Decimal
|
|
1159
1166
|
// integer type -> Int
|
|
1160
|
-
function handleSimpleValue(value, dTypeName, msg) {
|
|
1167
|
+
function handleSimpleValue( value, dTypeName, msg ) {
|
|
1161
1168
|
// these types must be represented as "String" values in XML:
|
|
1162
1169
|
const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
|
|
1163
1170
|
// caller already made sure that val is neither object nor array
|
|
@@ -1172,66 +1179,72 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1172
1179
|
const type = getDictType(resolvedType);
|
|
1173
1180
|
const expected = type.Members.map(m => `"#${m}"`);
|
|
1174
1181
|
message('odata-anno-value', msg.location,
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1182
|
+
{
|
|
1183
|
+
anno: msg.anno(),
|
|
1184
|
+
value,
|
|
1185
|
+
rawvalues: expected,
|
|
1186
|
+
type: resolvedType,
|
|
1187
|
+
'#': 'enum',
|
|
1188
|
+
});
|
|
1180
1189
|
}
|
|
1181
1190
|
|
|
1182
1191
|
let typeName = 'String';
|
|
1183
|
-
if(Allowed && !Allowed.Values[value])
|
|
1192
|
+
if (Allowed && !Allowed.Values[value]) {
|
|
1184
1193
|
message('odata-anno-value', msg.location,
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1194
|
+
{
|
|
1195
|
+
anno: msg.anno(),
|
|
1196
|
+
value,
|
|
1197
|
+
rawvalues: Object.keys(Allowed.Values),
|
|
1198
|
+
type: resolvedType,
|
|
1199
|
+
'#': 'enum',
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1190
1202
|
|
|
1191
1203
|
if (typeof value === 'string') {
|
|
1192
1204
|
if (resolvedType === 'Edm.Boolean') {
|
|
1193
1205
|
typeName = 'Bool';
|
|
1194
1206
|
if (value !== 'true' && value !== 'false') {
|
|
1195
1207
|
message('odata-anno-value', msg.location,
|
|
1196
|
-
|
|
1208
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1197
1209
|
}
|
|
1198
1210
|
}
|
|
1199
1211
|
else if (resolvedType === 'Edm.Decimal') {
|
|
1200
1212
|
typeName = 'Decimal';
|
|
1213
|
+
// eslint-disable-next-line no-restricted-globals
|
|
1201
1214
|
if (isNaN(Number(value)) || isNaN(parseFloat(value))) {
|
|
1202
1215
|
message('odata-anno-value', msg.location,
|
|
1203
|
-
|
|
1216
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1204
1217
|
}
|
|
1205
1218
|
}
|
|
1206
1219
|
else if (resolvedType === 'Edm.Double' || resolvedType === 'Edm.Single') {
|
|
1207
1220
|
typeName = 'Float';
|
|
1221
|
+
// eslint-disable-next-line no-restricted-globals
|
|
1208
1222
|
if (isNaN(Number(value)) || isNaN(parseFloat(value))) {
|
|
1209
1223
|
message('odata-anno-value', msg.location,
|
|
1210
|
-
|
|
1224
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1211
1225
|
}
|
|
1212
1226
|
}
|
|
1213
1227
|
else if (isComplexType(resolvedType)) {
|
|
1214
1228
|
message('odata-anno-value', msg.location,
|
|
1215
|
-
|
|
1229
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1216
1230
|
}
|
|
1217
1231
|
else if (isEnumType(resolvedType)) {
|
|
1218
1232
|
message('odata-anno-value', msg.location,
|
|
1219
|
-
|
|
1233
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1220
1234
|
typeName = 'EnumMember';
|
|
1221
1235
|
}
|
|
1222
1236
|
else if (resolvedType && resolvedType.startsWith('Edm.') && !castToXmlString.includes(resolvedType)) {
|
|
1223
1237
|
// this covers also all paths
|
|
1224
1238
|
typeName = resolvedType.substring(4);
|
|
1225
1239
|
}
|
|
1226
|
-
else {
|
|
1227
|
-
|
|
1228
|
-
resolvedType = 'Edm.String';
|
|
1240
|
+
else if (!resolvedType || castToXmlString.some(t => t === resolvedType)) {
|
|
1241
|
+
resolvedType = 'Edm.String';
|
|
1229
1242
|
// TODO
|
|
1230
|
-
//message(message, msg, "type is not yet handled: found String, expected type: " + dTypeName);
|
|
1243
|
+
// message(message, msg, "type is not yet handled: found String, expected type: " + dTypeName);
|
|
1231
1244
|
}
|
|
1232
1245
|
}
|
|
1233
1246
|
else if (typeof value === 'boolean') {
|
|
1234
|
-
if(resolvedType
|
|
1247
|
+
if (!resolvedType || resolvedType === 'Edm.Boolean' || resolvedType === 'Edm.PrimitiveType') {
|
|
1235
1248
|
typeName = 'Bool';
|
|
1236
1249
|
resolvedType = 'Edm.Boolean';
|
|
1237
1250
|
}
|
|
@@ -1243,7 +1256,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1243
1256
|
}
|
|
1244
1257
|
else {
|
|
1245
1258
|
message('odata-anno-value', msg.location,
|
|
1246
|
-
|
|
1259
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1247
1260
|
}
|
|
1248
1261
|
}
|
|
1249
1262
|
else if (typeof value === 'number') {
|
|
@@ -1251,7 +1264,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1251
1264
|
resolvedType === 'Edm.PropertyPath' ||
|
|
1252
1265
|
resolvedType === 'Edm.Boolean') {
|
|
1253
1266
|
message('odata-anno-value', msg.location,
|
|
1254
|
-
|
|
1267
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1255
1268
|
}
|
|
1256
1269
|
else if (resolvedType === 'Edm.String') {
|
|
1257
1270
|
typeName = 'String';
|
|
@@ -1262,36 +1275,35 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1262
1275
|
else if (resolvedType === 'Edm.Double') {
|
|
1263
1276
|
typeName = 'Float';
|
|
1264
1277
|
}
|
|
1278
|
+
else if (Number.isInteger(value)) {
|
|
1279
|
+
// typeName = Number.isInteger(val) ? 'Int' : 'Float';
|
|
1280
|
+
typeName = 'Int';
|
|
1281
|
+
if (resolvedType == null || resolvedType === 'Edm.PrimitiveType' || !resolvedType.startsWith('Edm.'))
|
|
1282
|
+
resolvedType = 'Edm.Int64';
|
|
1283
|
+
}
|
|
1265
1284
|
else {
|
|
1266
|
-
|
|
1267
|
-
if(
|
|
1268
|
-
|
|
1269
|
-
if(resolvedType == null || resolvedType === 'Edm.PrimitiveType' || !resolvedType.startsWith('Edm.'))
|
|
1270
|
-
resolvedType = 'Edm.Int64';
|
|
1271
|
-
}
|
|
1272
|
-
else {
|
|
1273
|
-
typeName = 'Float';
|
|
1274
|
-
if(resolvedType == null || resolvedType === 'Edm.PrimitiveType'|| !resolvedType.startsWith('Edm.'))
|
|
1275
|
-
resolvedType = 'Edm.Double';
|
|
1276
|
-
}
|
|
1285
|
+
typeName = 'Float';
|
|
1286
|
+
if (resolvedType == null || resolvedType === 'Edm.PrimitiveType' || !resolvedType.startsWith('Edm.'))
|
|
1287
|
+
resolvedType = 'Edm.Double';
|
|
1277
1288
|
}
|
|
1278
1289
|
}
|
|
1279
|
-
else if (value === null)
|
|
1280
|
-
if((resolvedType == null || resolvedType === 'Edm.PrimitiveType') && typeName === 'String') {
|
|
1290
|
+
else if (value === null) {
|
|
1291
|
+
if ((resolvedType == null || resolvedType === 'Edm.PrimitiveType') && typeName === 'String') {
|
|
1281
1292
|
resolvedType = 'Edm.String';
|
|
1282
1293
|
}
|
|
1283
1294
|
else {
|
|
1284
1295
|
message('odata-anno-value', msg.location,
|
|
1285
|
-
|
|
1296
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1286
1297
|
}
|
|
1298
|
+
}
|
|
1287
1299
|
|
|
1288
|
-
if( ['Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(resolvedType) )
|
|
1300
|
+
if ( [ 'Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(resolvedType) )
|
|
1289
1301
|
resolvedType = resolvedType.split('.')[1];
|
|
1290
1302
|
|
|
1291
1303
|
return {
|
|
1292
|
-
name
|
|
1304
|
+
name: typeName,
|
|
1293
1305
|
jsonName: resolvedType,
|
|
1294
|
-
value
|
|
1306
|
+
value,
|
|
1295
1307
|
};
|
|
1296
1308
|
}
|
|
1297
1309
|
|
|
@@ -1300,59 +1312,67 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1300
1312
|
// dTypeName : name of the expected record type according to vocabulary, may be null
|
|
1301
1313
|
//
|
|
1302
1314
|
// can be called for a record directly below a term, or at a deeper level
|
|
1303
|
-
function generateRecord(obj, termName, dTypeName, msg) {
|
|
1315
|
+
function generateRecord( obj, termName, dTypeName, dTypeIsACollection, msg ) {
|
|
1304
1316
|
/** @type {object} */
|
|
1305
1317
|
const newRecord = new Edm.Record(v);
|
|
1306
1318
|
|
|
1307
1319
|
// first determine what is the actual type to be used for the record
|
|
1308
1320
|
if (dTypeName && !isComplexType(dTypeName)) {
|
|
1309
|
-
if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !
|
|
1321
|
+
if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !dTypeIsACollection) {
|
|
1310
1322
|
message('odata-anno-dict', msg.location,
|
|
1311
|
-
|
|
1312
|
-
|
|
1323
|
+
{ anno: msg.anno(), type: dTypeName });
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1313
1326
|
message('odata-anno-value', msg.location,
|
|
1314
|
-
|
|
1327
|
+
{
|
|
1328
|
+
anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'incompval',
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1315
1331
|
return newRecord;
|
|
1316
1332
|
}
|
|
1317
1333
|
|
|
1318
1334
|
let actualTypeName = null;
|
|
1319
|
-
if (obj
|
|
1320
|
-
actualTypeName = obj
|
|
1335
|
+
if (obj.$Type) { // type is explicitly specified
|
|
1336
|
+
actualTypeName = obj.$Type;
|
|
1321
1337
|
if (!getDictType(actualTypeName)) {
|
|
1322
1338
|
// this type doesn't exist
|
|
1323
1339
|
message('odata-anno-type', msg.location,
|
|
1324
|
-
|
|
1340
|
+
{ anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
|
|
1325
1341
|
// explicitly mentioned type, render in XML and JSON
|
|
1326
|
-
newRecord.setXml({
|
|
1342
|
+
newRecord.setXml({ Type: actualTypeName });
|
|
1327
1343
|
// unknown dictionary type: can't fully qualify it
|
|
1328
|
-
newRecord.setJSON({
|
|
1344
|
+
newRecord.setJSON({ Type: actualTypeName });
|
|
1329
1345
|
}
|
|
1330
1346
|
else {
|
|
1331
1347
|
if (isAbstractType(actualTypeName)) {
|
|
1332
1348
|
// this type is abstract
|
|
1333
1349
|
message('odata-anno-type', msg.location,
|
|
1334
|
-
|
|
1335
|
-
|
|
1350
|
+
{
|
|
1351
|
+
anno: msg.anno(), type: actualTypeName, code: '$Type', '#': 'abstract',
|
|
1352
|
+
});
|
|
1353
|
+
if (dTypeName)
|
|
1336
1354
|
actualTypeName = dTypeName;
|
|
1337
1355
|
}
|
|
1338
1356
|
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
|
|
1339
1357
|
// this type doesn't fit the expected one
|
|
1340
1358
|
message('odata-anno-type', msg.location,
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1359
|
+
{
|
|
1360
|
+
anno: msg.anno(),
|
|
1361
|
+
type: actualTypeName,
|
|
1362
|
+
name: dTypeName,
|
|
1363
|
+
code: '$Type',
|
|
1364
|
+
'#': 'derived',
|
|
1365
|
+
});
|
|
1346
1366
|
actualTypeName = dTypeName;
|
|
1347
1367
|
}
|
|
1348
1368
|
// Dictionary Type, render in XML only for backward compatibility
|
|
1349
1369
|
newRecord.setXml( { Type: actualTypeName });
|
|
1350
1370
|
const vocName = actualTypeName.slice(0, actualTypeName.indexOf('.'));
|
|
1351
|
-
const
|
|
1371
|
+
const myVocDef = mergedVocDefs[vocName];
|
|
1352
1372
|
// Set full qualified type in JSON
|
|
1353
1373
|
// TODO: Adhoc type x-ref URIs (only if abstract types are allowed in CDS)
|
|
1354
|
-
if(
|
|
1355
|
-
newRecord.setJSON( {
|
|
1374
|
+
if (myVocDef)
|
|
1375
|
+
newRecord.setJSON( { Type: `${myVocDef.ref.Uri}#${actualTypeName}` });
|
|
1356
1376
|
// don't add short actualTypeName into JSON as this would be wrong for a resolved! type.
|
|
1357
1377
|
// A $Type w/o vocDef can only occur for adhoc type defs and these can't be abstract but
|
|
1358
1378
|
// are fully resolvable due to their term usage via schema x-ref.
|
|
@@ -1361,15 +1381,18 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1361
1381
|
else if (dTypeName) { // there is an expected type name according to dictionary
|
|
1362
1382
|
// convenience for common situation:
|
|
1363
1383
|
// if DataFieldAbstract is expected and no explicit type is provided, automatically choose DataField
|
|
1364
|
-
if (dTypeName === 'UI.DataFieldAbstract')
|
|
1384
|
+
if (dTypeName === 'UI.DataFieldAbstract')
|
|
1365
1385
|
actualTypeName = 'UI.DataField';
|
|
1366
|
-
|
|
1367
|
-
else
|
|
1386
|
+
|
|
1387
|
+
else
|
|
1368
1388
|
actualTypeName = dTypeName;
|
|
1369
|
-
|
|
1370
|
-
if (isAbstractType(actualTypeName))
|
|
1389
|
+
|
|
1390
|
+
if (isAbstractType(actualTypeName)) {
|
|
1371
1391
|
message('odata-anno-type', msg.location,
|
|
1372
|
-
|
|
1392
|
+
{
|
|
1393
|
+
anno: msg.anno(), type: dTypeName, code: '$Type', '#': 'abstract',
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1373
1396
|
|
|
1374
1397
|
// Dictionary Type, render in XML only for backward compatibility
|
|
1375
1398
|
newRecord.setXml( { Type: actualTypeName });
|
|
@@ -1383,7 +1406,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1383
1406
|
|
|
1384
1407
|
// loop over elements
|
|
1385
1408
|
for (const name of Object.keys(obj)) {
|
|
1386
|
-
msg.stack.push(
|
|
1409
|
+
msg.stack.push(`.${name}`);
|
|
1387
1410
|
|
|
1388
1411
|
if (name === '$Type') {
|
|
1389
1412
|
// ignore, this is an "artificial" property used to indicate the type
|
|
@@ -1400,11 +1423,11 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1400
1423
|
dictPropertyTypeName = dictProperties[name];
|
|
1401
1424
|
if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType) {
|
|
1402
1425
|
message('odata-anno-type', msg.location,
|
|
1403
|
-
|
|
1426
|
+
{ name, anno: termName, type: dTypeName });
|
|
1404
1427
|
}
|
|
1405
1428
|
}
|
|
1406
1429
|
|
|
1407
|
-
|
|
1430
|
+
const newPropertyValue = new Edm.PropertyValue(v, name);
|
|
1408
1431
|
// property value can be anything, so delegate handling to handleValue
|
|
1409
1432
|
handleValue(obj[name], newPropertyValue, termName, dictPropertyTypeName, msg);
|
|
1410
1433
|
newRecord.append(newPropertyValue);
|
|
@@ -1419,55 +1442,50 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1419
1442
|
|
|
1420
1443
|
// annoValue is an array
|
|
1421
1444
|
// dTypeName : Collection(...) according to dictionary
|
|
1422
|
-
function generateCollection(annoValue, termName, dTypeName, msg) {
|
|
1445
|
+
function generateCollection( annoValue, termName, dTypeName, dTypeIsACollection, msg ) {
|
|
1423
1446
|
const newCollection = new Edm.Collection(v);
|
|
1424
1447
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
}
|
|
1431
|
-
else {
|
|
1432
|
-
message('odata-anno-value', msg.location,
|
|
1433
|
-
{ anno: msg.anno(), str: 'collection', type: dTypeName, '#': 'struct' });
|
|
1434
|
-
}
|
|
1448
|
+
if (dTypeName && !dTypeIsACollection) {
|
|
1449
|
+
message('odata-anno-value', msg.location,
|
|
1450
|
+
{
|
|
1451
|
+
anno: msg.anno(), str: 'collection', type: dTypeName, '#': 'incompval',
|
|
1452
|
+
});
|
|
1435
1453
|
}
|
|
1436
1454
|
|
|
1437
1455
|
let index = 0;
|
|
1438
1456
|
for (const value of annoValue) {
|
|
1439
|
-
msg.stack.push(
|
|
1440
|
-
index
|
|
1457
|
+
msg.stack.push(`[${index}]`);
|
|
1458
|
+
index++;
|
|
1441
1459
|
|
|
1442
1460
|
// for dealing with the single array entries we unfortunately cannot call handleValue(),
|
|
1443
1461
|
// as the values inside an array are represented differently from the values
|
|
1444
1462
|
// in a record or term
|
|
1445
1463
|
if (Array.isArray(value)) {
|
|
1446
1464
|
message('odata-anno-value', msg.location,
|
|
1447
|
-
|
|
1465
|
+
{ anno: msg.anno(), '#': 'nestedcollection' });
|
|
1448
1466
|
}
|
|
1449
1467
|
else if (value && typeof value === 'object') {
|
|
1450
1468
|
if (value['=']) {
|
|
1451
|
-
const res = handleExpression(value['='],
|
|
1469
|
+
const res = handleExpression(value['='], dTypeName);
|
|
1452
1470
|
const newPropertyPath = new Edm.ValueThing(v, res.name, res.value );
|
|
1453
|
-
newPropertyPath.setJSON( { [res.name]
|
|
1471
|
+
newPropertyPath.setJSON( { [res.name]: res.value } );
|
|
1454
1472
|
newCollection.append(newPropertyPath);
|
|
1455
1473
|
}
|
|
1456
1474
|
else if (value['#']) {
|
|
1457
1475
|
message('odata-anno-value', msg.location,
|
|
1458
|
-
|
|
1476
|
+
{ anno: msg.anno(), '#': 'enumincollection' });
|
|
1459
1477
|
}
|
|
1460
|
-
else if(value
|
|
1461
|
-
newCollection.append(handleEdmJson(value
|
|
1478
|
+
else if (value.$edmJson) {
|
|
1479
|
+
newCollection.append(handleEdmJson(value.$edmJson, msg));
|
|
1462
1480
|
}
|
|
1463
1481
|
else {
|
|
1464
|
-
newCollection.append(generateRecord(value, termName,
|
|
1482
|
+
newCollection.append(generateRecord(value, termName, dTypeName, dTypeIsACollection, msg));
|
|
1465
1483
|
}
|
|
1466
1484
|
}
|
|
1467
1485
|
else {
|
|
1468
|
-
const res = handleSimpleValue(value,
|
|
1469
|
-
const newThing = (value === null) ?new Edm.ValueThing(v, 'Null') : new Edm.ValueThing(v, res.name, value );
|
|
1470
|
-
newThing.setJSON( { [res.jsonName]
|
|
1486
|
+
const res = handleSimpleValue(value, dTypeName, msg);
|
|
1487
|
+
const newThing = (value === null) ? new Edm.ValueThing(v, 'Null') : new Edm.ValueThing(v, res.name, value );
|
|
1488
|
+
newThing.setJSON( { [res.jsonName]: res.value });
|
|
1471
1489
|
newCollection.append(newThing);
|
|
1472
1490
|
}
|
|
1473
1491
|
|
|
@@ -1485,17 +1503,16 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1485
1503
|
// See example in test/odataAnnotations/smallTests/edmJson_noReverse_ok
|
|
1486
1504
|
// and test3/ODataBackends/DynExpr
|
|
1487
1505
|
|
|
1488
|
-
function handleEdmJson(obj, msg, exprDef=undefined) {
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
if(obj == null)
|
|
1506
|
+
function handleEdmJson( obj, msg, exprDef = undefined ) {
|
|
1507
|
+
let edmNode;
|
|
1508
|
+
if (obj == null)
|
|
1492
1509
|
return edmNode;
|
|
1493
1510
|
|
|
1494
1511
|
const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
|
|
1495
1512
|
|
|
1496
|
-
if(dynExprs.length > 1) {
|
|
1513
|
+
if (dynExprs.length > 1) {
|
|
1497
1514
|
message('odata-anno-value', msg.location,
|
|
1498
|
-
|
|
1515
|
+
{ anno: msg.anno(), rawvalues: dynExprs, '#': 'multexpr' });
|
|
1499
1516
|
return edmNode;
|
|
1500
1517
|
}
|
|
1501
1518
|
|
|
@@ -1506,61 +1523,60 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1506
1523
|
edmNode = new Edm.ValueThing(v, k[0] === '$' ? k.slice(1) : k, val );
|
|
1507
1524
|
edmNode.setJSON( { [edmNode.kind]: val } );
|
|
1508
1525
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1526
|
+
// This thing is either a record or a collection or a literal
|
|
1527
|
+
else if (Array.isArray(obj)) {
|
|
1528
|
+
// EDM JSON doesn't mention annotations on collections
|
|
1529
|
+
edmNode = new Edm.Collection(v);
|
|
1530
|
+
obj.forEach(o => edmNode.append(handleEdmJson(o, msg)));
|
|
1531
|
+
}
|
|
1532
|
+
else if (typeof obj === 'object') {
|
|
1533
|
+
edmNode = new Edm.Record(v);
|
|
1534
|
+
const annos = Object.create(null);
|
|
1535
|
+
const props = Object.create(null);
|
|
1536
|
+
Object.entries(obj).forEach(([ k, val ]) => {
|
|
1537
|
+
if (k === '@type') {
|
|
1538
|
+
edmNode.setJSON({ Type: val });
|
|
1539
|
+
// try to shorten full qualified type URI to short type name
|
|
1540
|
+
const parts = val.split('#');
|
|
1541
|
+
const shortTypeName = parts[parts.length - 1];
|
|
1542
|
+
edmNode.setXml({ Type: shortTypeName });
|
|
1543
|
+
}
|
|
1544
|
+
else {
|
|
1545
|
+
let child;
|
|
1546
|
+
const [ head, tail ] = k.split('@');
|
|
1547
|
+
if (tail) {
|
|
1548
|
+
child = handleTerm(tail, val, msg);
|
|
1527
1549
|
}
|
|
1528
1550
|
else {
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1551
|
+
child = new Edm.PropertyValue(v, head);
|
|
1552
|
+
child.append(handleEdmJson(val, msg));
|
|
1553
|
+
}
|
|
1554
|
+
if (child) {
|
|
1555
|
+
if (tail && head.length) {
|
|
1556
|
+
if (!annos[head])
|
|
1557
|
+
annos[head] = [ child ];
|
|
1558
|
+
else
|
|
1559
|
+
annos[head].push(child);
|
|
1533
1560
|
}
|
|
1534
1561
|
else {
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
if(child) {
|
|
1539
|
-
if(tail && head.length) {
|
|
1540
|
-
if(!annos[head])
|
|
1541
|
-
annos[head] = [ child ];
|
|
1542
|
-
else
|
|
1543
|
-
annos[head].push(child);
|
|
1544
|
-
}
|
|
1545
|
-
else {
|
|
1546
|
-
if(head.length)
|
|
1547
|
-
props[head] = child;
|
|
1548
|
-
edmNode.append(child);
|
|
1549
|
-
}
|
|
1562
|
+
if (head.length)
|
|
1563
|
+
props[head] = child;
|
|
1564
|
+
edmNode.append(child);
|
|
1550
1565
|
}
|
|
1551
1566
|
}
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
// add collected annotations to record members
|
|
1570
|
+
Object.entries(annos).forEach(([ n, val ]) => {
|
|
1571
|
+
if (props[n])
|
|
1572
|
+
props[n].prepend(...val);
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
else { // literal
|
|
1576
|
+
edmNode = new Edm.ValueThing(v,
|
|
1577
|
+
exprDef && exprDef.valueThingName || getXmlTypeName(obj), obj);
|
|
1578
|
+
// typename for static expression rendering
|
|
1579
|
+
edmNode.setJSON( { [getJsonTypeName(obj)]: obj } );
|
|
1564
1580
|
}
|
|
1565
1581
|
}
|
|
1566
1582
|
else {
|
|
@@ -1570,23 +1586,21 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1570
1586
|
|
|
1571
1587
|
// iterate over each obj.property and translate expression into EDM
|
|
1572
1588
|
forEach(obj, (name, val) => {
|
|
1573
|
-
if(exprDef) {
|
|
1574
|
-
if(exprDef.anno && name[0] === '@') {
|
|
1589
|
+
if (exprDef) {
|
|
1590
|
+
if (exprDef.anno && name[0] === '@') {
|
|
1575
1591
|
edmNode.append(handleTerm(name.slice(1), val, msg));
|
|
1576
1592
|
}
|
|
1577
1593
|
else if (exprDef.attr && exprDef.attr.includes(name)) {
|
|
1578
|
-
if (name[0] === '$')
|
|
1594
|
+
if (name[0] === '$')
|
|
1579
1595
|
edmNode.setEdmAttribute(name.slice(1), val);
|
|
1580
|
-
}
|
|
1581
1596
|
}
|
|
1582
1597
|
else if (exprDef.jsonAttr && exprDef.jsonAttr.includes(name)) {
|
|
1583
|
-
if (name[0] === '$')
|
|
1584
|
-
edmNode.setJSON( { [name.slice(1)]: val })
|
|
1585
|
-
}
|
|
1598
|
+
if (name[0] === '$')
|
|
1599
|
+
edmNode.setJSON( { [name.slice(1)]: val });
|
|
1586
1600
|
}
|
|
1587
|
-
else if(exprDef.children) {
|
|
1601
|
+
else if (exprDef.children) {
|
|
1588
1602
|
if (Array.isArray(val)) {
|
|
1589
|
-
val.forEach(a => {
|
|
1603
|
+
val.forEach((a) => {
|
|
1590
1604
|
edmNode.append(handleEdmJson(a, msg, exprDef));
|
|
1591
1605
|
});
|
|
1592
1606
|
}
|
|
@@ -1599,23 +1613,22 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1599
1613
|
}
|
|
1600
1614
|
return edmNode;
|
|
1601
1615
|
|
|
1602
|
-
function getXmlTypeName(val) {
|
|
1616
|
+
function getXmlTypeName( val ) {
|
|
1603
1617
|
let typeName = 'String';
|
|
1604
|
-
if (typeof val === 'boolean')
|
|
1618
|
+
if (typeof val === 'boolean')
|
|
1605
1619
|
typeName = 'Bool';
|
|
1606
|
-
|
|
1607
|
-
else if (typeof val === 'number')
|
|
1620
|
+
|
|
1621
|
+
else if (typeof val === 'number')
|
|
1608
1622
|
typeName = Number.isInteger(val) ? 'Int' : 'Decimal';
|
|
1609
|
-
|
|
1623
|
+
|
|
1610
1624
|
return typeName;
|
|
1611
1625
|
}
|
|
1612
1626
|
|
|
1613
|
-
function getJsonTypeName(val) {
|
|
1614
|
-
|
|
1615
|
-
if(typeName === 'Int')
|
|
1616
|
-
return 'Edm.Int32'
|
|
1617
|
-
|
|
1618
|
-
return 'Edm.'+typeName;
|
|
1627
|
+
function getJsonTypeName( val ) {
|
|
1628
|
+
const typeName = getXmlTypeName(val);
|
|
1629
|
+
if (typeName === 'Int')
|
|
1630
|
+
return 'Edm.Int32';
|
|
1631
|
+
return `Edm.${typeName}`;
|
|
1619
1632
|
}
|
|
1620
1633
|
}
|
|
1621
1634
|
|
|
@@ -1630,73 +1643,75 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1630
1643
|
* @returns [object, Array<object>]
|
|
1631
1644
|
*/
|
|
1632
1645
|
function createUserDefinedTermDictionary() {
|
|
1633
|
-
const
|
|
1646
|
+
const allKnownVocabulariesP = [];
|
|
1634
1647
|
const dict = { terms: {}, types: {}, xrefs: {} };
|
|
1635
1648
|
|
|
1636
|
-
if(!isBetaEnabled(options, 'odataTerms'))
|
|
1637
|
-
return [ dict,
|
|
1649
|
+
if (!isBetaEnabled(options, 'odataTerms'))
|
|
1650
|
+
return [ dict, allKnownVocabulariesP ];
|
|
1638
1651
|
|
|
1639
|
-
for(
|
|
1652
|
+
for (const termName in csnVocabularies) {
|
|
1640
1653
|
let dictDef = oDataDictionary.terms[termName];
|
|
1641
|
-
if(dictDef) {
|
|
1642
|
-
message('odata-anno-dict', ['vocabularies', termName],
|
|
1643
|
-
|
|
1654
|
+
if (dictDef) {
|
|
1655
|
+
message('odata-anno-dict', [ 'vocabularies', termName ],
|
|
1656
|
+
{ anno: termName, string: 'annotation', '#': 'redefinition' } );
|
|
1644
1657
|
}
|
|
1645
|
-
else if(!dictDef) {
|
|
1658
|
+
else if (!dictDef) {
|
|
1646
1659
|
const annoDef = csnVocabularies[termName];
|
|
1647
|
-
if(annoDef?.$mySchemaName) {
|
|
1648
|
-
if(!
|
|
1649
|
-
|
|
1660
|
+
if (annoDef?.$mySchemaName) {
|
|
1661
|
+
if (!allKnownVocabulariesP.includes[annoDef.$mySchemaName])
|
|
1662
|
+
allKnownVocabulariesP.push(annoDef.$mySchemaName);
|
|
1650
1663
|
const myServiceRoot = options.whatsMyServiceRootName(annoDef.$mySchemaName);
|
|
1651
|
-
if(!dict.xrefs[myServiceRoot])
|
|
1664
|
+
if (!dict.xrefs[myServiceRoot])
|
|
1652
1665
|
dict.xrefs[myServiceRoot] = { $myServiceRoot: myServiceRoot, used: false };
|
|
1653
1666
|
const edmType = new Edm.TypeBase(options.v, {}, annoDef);
|
|
1654
1667
|
dictDef = edmType._edmAttributes;
|
|
1655
|
-
dictDef
|
|
1668
|
+
dictDef.$myServiceRoot = myServiceRoot;
|
|
1656
1669
|
let val = annoDef['@odata.term.AppliesTo'];
|
|
1657
|
-
if(val != null)
|
|
1658
|
-
dictDef.AppliesTo = Array.isArray(val) ? val.map(
|
|
1659
|
-
val
|
|
1660
|
-
if(val != null)
|
|
1661
|
-
dictDef
|
|
1670
|
+
if (val != null)
|
|
1671
|
+
dictDef.AppliesTo = Array.isArray(val) ? val.map(av => av['='] || av) : [ val['='] || val ];
|
|
1672
|
+
val = annoDef['@odata.term.Experimental'];
|
|
1673
|
+
if (val != null)
|
|
1674
|
+
dictDef.$experimental = !!val;
|
|
1662
1675
|
val = annoDef['@odata.term.Deprecated'];
|
|
1663
|
-
if(val != null) {
|
|
1664
|
-
dictDef
|
|
1665
|
-
if(typeof val === 'string')
|
|
1666
|
-
dictDef
|
|
1676
|
+
if (val != null) {
|
|
1677
|
+
dictDef.$deprecated = !!val;
|
|
1678
|
+
if (typeof val === 'string')
|
|
1679
|
+
dictDef.$deprecationText = val;
|
|
1667
1680
|
}
|
|
1668
1681
|
dict.terms[termName] = dictDef;
|
|
1669
1682
|
|
|
1670
|
-
if((annoDef.items?.enum || annoDef.enum) && isBuiltinType(annoDef.items?.type || annoDef.type)) {
|
|
1671
|
-
const enumType = createTypeDefWithAllowedValues(annoDef, annoDef, dictDef.Type, ['vocabularies', termName ]);
|
|
1672
|
-
const tName = termName
|
|
1683
|
+
if ((annoDef.items?.enum || annoDef.enum) && isBuiltinType(annoDef.items?.type || annoDef.type)) {
|
|
1684
|
+
const enumType = createTypeDefWithAllowedValues(annoDef, annoDef, dictDef.Type, [ 'vocabularies', termName ]);
|
|
1685
|
+
const tName = `${termName}_$$$EnumType$$$$`;
|
|
1673
1686
|
dict.types[tName] = enumType;
|
|
1674
1687
|
dictDef.Type = tName;
|
|
1675
1688
|
}
|
|
1676
|
-
else
|
|
1677
|
-
|
|
1689
|
+
else {
|
|
1690
|
+
addTypesToDictionary(annoDef);
|
|
1691
|
+
}
|
|
1678
1692
|
}
|
|
1679
1693
|
}
|
|
1680
1694
|
}
|
|
1681
|
-
return [ dict,
|
|
1695
|
+
return [ dict, allKnownVocabulariesP ];
|
|
1682
1696
|
|
|
1683
|
-
function addTypesToDictionary(node) {
|
|
1697
|
+
function addTypesToDictionary( node ) {
|
|
1684
1698
|
const typeName = node.items?.type || node.type;
|
|
1685
1699
|
// for type reuse in x-ref mode, the definition has already been
|
|
1686
1700
|
// replaced by a reference object in edmPreprocessor.
|
|
1687
1701
|
// Fall back to original type (the one of the other service).
|
|
1688
|
-
const typeDef = reqDefs.definitions[typeName] || reqDefs.definitions[typeName.replace(serviceName
|
|
1689
|
-
if(typeDef) {
|
|
1702
|
+
const typeDef = reqDefs.definitions[typeName] || reqDefs.definitions[typeName.replace(`${serviceName}.`, '')];
|
|
1703
|
+
if (typeDef) {
|
|
1690
1704
|
let dictDef = { };
|
|
1691
1705
|
const elements = typeDef.items?.elements || typeDef.elements;
|
|
1692
|
-
if(elements) {
|
|
1693
|
-
|
|
1694
|
-
dictDef
|
|
1706
|
+
if (elements) {
|
|
1707
|
+
// complex type
|
|
1708
|
+
dictDef.$kind = 'ComplexType';
|
|
1709
|
+
// eslint-disable-next-line no-new-object
|
|
1695
1710
|
dictDef.Properties = new Object(null);
|
|
1696
1711
|
|
|
1697
|
-
for(
|
|
1712
|
+
for (const en in elements) {
|
|
1698
1713
|
const elt = elements[en];
|
|
1699
|
-
if(isEdmPropertyRendered(elt, options)) {
|
|
1714
|
+
if (isEdmPropertyRendered(elt, options)) {
|
|
1700
1715
|
const edmType = new Edm.TypeBase(options.v, {}, elt);
|
|
1701
1716
|
dictDef.Properties[en] = edmType._edmAttributes[edmType._typeName];
|
|
1702
1717
|
addTypesToDictionary(elt);
|
|
@@ -1706,39 +1721,42 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1706
1721
|
else {
|
|
1707
1722
|
// type definition
|
|
1708
1723
|
const edmType = new Edm.TypeBase(options.v, {}, typeDef);
|
|
1709
|
-
dictDef = createTypeDefWithAllowedValues(node, typeDef, edmType._edmAttributes[edmType._typeName], ['definitions', typeName ]);
|
|
1710
|
-
if(!typeDef.enum)
|
|
1724
|
+
dictDef = createTypeDefWithAllowedValues(node, typeDef, edmType._edmAttributes[edmType._typeName], [ 'definitions', typeName ]);
|
|
1725
|
+
if (!typeDef.enum)
|
|
1711
1726
|
delete dictDef.$Allowed;
|
|
1712
1727
|
}
|
|
1713
1728
|
dict.types[typeName] = dictDef;
|
|
1714
1729
|
}
|
|
1715
1730
|
}
|
|
1716
1731
|
|
|
1717
|
-
function createTypeDefWithAllowedValues(node, typeDef, UnderlyingType, path) {
|
|
1732
|
+
function createTypeDefWithAllowedValues( node, typeDef, UnderlyingType, path ) {
|
|
1718
1733
|
const dictTypeDef = { $kind: 'TypeDefinition', UnderlyingType, $Allowed: { Values: {}, Symbols: {} } };
|
|
1719
1734
|
// create an artificial type that holds the $Allowed enum symbols and values
|
|
1720
|
-
if(node.items && typeDef.enum || typeDef.items?.enum)
|
|
1735
|
+
if (node.items && typeDef.enum || typeDef.items?.enum) {
|
|
1721
1736
|
message('odata-anno-dict-enum', [ 'vocabularies', node.name ],
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1737
|
+
{
|
|
1738
|
+
name: node.name,
|
|
1739
|
+
type: typeDef.name,
|
|
1740
|
+
'#': node.name === typeDef.name ? 'std' : 'type',
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1726
1743
|
const enumDic = (typeDef.items?.enum || typeDef.enum);
|
|
1727
1744
|
const baseType = (typeDef.items || typeDef).type;
|
|
1728
|
-
if(baseType !== 'cds.String' && Object.values(enumDic).some(
|
|
1745
|
+
if (baseType !== 'cds.String' && Object.values(enumDic).some(av => !av.val)) {
|
|
1729
1746
|
message('odata-anno-dict-enum', path,
|
|
1730
|
-
|
|
1747
|
+
{ name: node.name, type: baseType, '#': 'value' });
|
|
1731
1748
|
}
|
|
1732
1749
|
else {
|
|
1733
|
-
for(const symbol in enumDic) {
|
|
1750
|
+
for (const symbol in enumDic) {
|
|
1734
1751
|
const valDic = { '#SymbolicName': symbol };
|
|
1735
1752
|
const enumDef = enumDic[symbol];
|
|
1736
1753
|
// <Null/> values can't be rendered
|
|
1737
|
-
if(enumDef.val === undefined)
|
|
1754
|
+
if (enumDef.val === undefined)
|
|
1738
1755
|
valDic.Value = symbol;
|
|
1739
|
-
else if(valDic.val !== null)
|
|
1756
|
+
else if (valDic.val !== null)
|
|
1740
1757
|
valDic.Value = enumDef.val;
|
|
1741
|
-
dictTypeDef.$Allowed.
|
|
1758
|
+
dictTypeDef.$Allowed.Values[symbol] = valDic;
|
|
1759
|
+
dictTypeDef.$Allowed.Symbols[symbol] = valDic;
|
|
1742
1760
|
}
|
|
1743
1761
|
}
|
|
1744
1762
|
return dictTypeDef;
|
|
@@ -1747,127 +1765,126 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1747
1765
|
|
|
1748
1766
|
function initEdmJson() {
|
|
1749
1767
|
// Static dynamic expression dictionary, loaded with Edm creators
|
|
1750
|
-
const
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
//valueThingName: 'EnumMember' Implicit Cast Rule String => Primitive Type is OK
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
create: () =>
|
|
1768
|
+
const dynamicExpressionsP = {
|
|
1769
|
+
$And: { create: () => new Edm.Expr(v, 'And'), anno: true },
|
|
1770
|
+
$Or: { create: () => new Edm.Expr(v, 'Or'), anno: true },
|
|
1771
|
+
$Not: { create: () => new Edm.Expr(v, 'Not'), anno: true },
|
|
1772
|
+
$Eq: { create: () => new Edm.Expr(v, 'Eq'), anno: true },
|
|
1773
|
+
$Ne: { create: () => new Edm.Expr(v, 'Ne'), anno: true },
|
|
1774
|
+
$Gt: { create: () => new Edm.Expr(v, 'Gt'), anno: true },
|
|
1775
|
+
$Ge: { create: () => new Edm.Expr(v, 'Ge'), anno: true },
|
|
1776
|
+
$Lt: { create: () => new Edm.Expr(v, 'Lt'), anno: true },
|
|
1777
|
+
$Le: { create: () => new Edm.Expr(v, 'Le'), anno: true },
|
|
1778
|
+
// valueThingName: 'EnumMember' Implicit Cast Rule String => Primitive Type is OK
|
|
1779
|
+
$Has: { create: () => new Edm.Expr(v, 'Has'), anno: true },
|
|
1780
|
+
$In: { create: () => new Edm.Expr(v, 'In'), anno: true },
|
|
1781
|
+
$Add: { create: () => new Edm.Expr(v, 'Add'), anno: true },
|
|
1782
|
+
$Sub: { create: () => new Edm.Expr(v, 'Sub'), anno: true },
|
|
1783
|
+
$Neg: { create: () => new Edm.Expr(v, 'Neg'), anno: true },
|
|
1784
|
+
$Mul: { create: () => new Edm.Expr(v, 'Mul'), anno: true },
|
|
1785
|
+
$Div: { create: () => new Edm.Expr(v, 'Div'), anno: true },
|
|
1786
|
+
$DivBy: { create: () => new Edm.Expr(v, 'DivBy'), anno: true },
|
|
1787
|
+
$Mod: { create: () => new Edm.Expr(v, 'Mod'), anno: true },
|
|
1788
|
+
$Apply: {
|
|
1789
|
+
create: () => new Edm.Apply(v),
|
|
1772
1790
|
attr: [ '$Function' ],
|
|
1773
|
-
anno: true
|
|
1791
|
+
anno: true,
|
|
1774
1792
|
},
|
|
1775
|
-
|
|
1776
|
-
create: () =>
|
|
1793
|
+
$Cast: {
|
|
1794
|
+
create: () => new Edm.Cast(v),
|
|
1777
1795
|
attr: [ '$Type' ],
|
|
1778
1796
|
jsonAttr: [ '$Collection' ],
|
|
1779
|
-
anno: true
|
|
1797
|
+
anno: true,
|
|
1780
1798
|
},
|
|
1781
|
-
|
|
1782
|
-
create: () =>
|
|
1799
|
+
$IsOf: {
|
|
1800
|
+
create: () => new Edm.IsOf(v),
|
|
1783
1801
|
attr: [ '$Type' ],
|
|
1784
|
-
anno: true
|
|
1802
|
+
anno: true,
|
|
1785
1803
|
},
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
create: () =>
|
|
1804
|
+
$If: { create: () => new Edm.If(v), anno: true },
|
|
1805
|
+
$LabeledElement: {
|
|
1806
|
+
create: () => new Edm.LabeledElement(v),
|
|
1789
1807
|
attr: [ '$Name' ],
|
|
1790
|
-
anno: true
|
|
1808
|
+
anno: true,
|
|
1791
1809
|
},
|
|
1792
|
-
|
|
1793
|
-
create:
|
|
1810
|
+
$LabeledElementReference: {
|
|
1811
|
+
create: obj => new Edm.LabeledElementReference(v, obj.$LabeledElementReference),
|
|
1794
1812
|
},
|
|
1795
|
-
|
|
1796
|
-
|
|
1813
|
+
$UrlRef: { create: () => new Edm.UrlRef(v), anno: true },
|
|
1814
|
+
$Null: { create: () => new Edm.Null(v), anno: true, children: false },
|
|
1797
1815
|
};
|
|
1798
1816
|
|
|
1799
|
-
Object.entries(
|
|
1800
|
-
if(!
|
|
1801
|
-
|
|
1802
|
-
if(
|
|
1803
|
-
|
|
1817
|
+
Object.entries(dynamicExpressionsP).forEach(([ k, dv ]) => {
|
|
1818
|
+
if (!dv.name)
|
|
1819
|
+
dv.name = k.slice(1);
|
|
1820
|
+
if (dv.children === undefined)
|
|
1821
|
+
dv.children = true;
|
|
1804
1822
|
});
|
|
1805
|
-
return [
|
|
1823
|
+
return [ dynamicExpressionsP, Object.keys(dynamicExpressionsP) ];
|
|
1806
1824
|
}
|
|
1807
1825
|
//-------------------------------------------------------------------------------------------------
|
|
1808
1826
|
//-------------------------------------------------------------------------------------------------
|
|
1809
1827
|
//-------------------------------------------------------------------------------------------------
|
|
1810
1828
|
|
|
1811
|
-
|
|
1829
|
+
// resolve "derived types"
|
|
1812
1830
|
// -> if dTypeName is a TypeDefinition, replace by
|
|
1813
1831
|
// underlying type
|
|
1814
|
-
function resolveTypeDefinition(dTypeName) {
|
|
1832
|
+
function resolveTypeDefinition( dTypeName ) {
|
|
1815
1833
|
const type = getDictType(dTypeName);
|
|
1816
|
-
if (type && type.UnderlyingType && type
|
|
1834
|
+
if (type && type.UnderlyingType && type.$kind === 'TypeDefinition')
|
|
1817
1835
|
return type.UnderlyingType;
|
|
1818
|
-
|
|
1836
|
+
|
|
1819
1837
|
return dTypeName;
|
|
1820
1838
|
}
|
|
1821
1839
|
|
|
1822
|
-
function stripCollection(typeName) {
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1840
|
+
function stripCollection( typeName ) {
|
|
1841
|
+
if (typeName) {
|
|
1842
|
+
const match = typeName.match(/^Collection\((.+)\)/);
|
|
1843
|
+
if (match)
|
|
1844
|
+
return [ match[1], true ];
|
|
1826
1845
|
}
|
|
1827
|
-
return typeName;
|
|
1828
|
-
}
|
|
1829
1846
|
|
|
1830
|
-
|
|
1831
|
-
return typeName.split('.')[0] === 'Edm';
|
|
1847
|
+
return [ typeName, false ];
|
|
1832
1848
|
}
|
|
1833
1849
|
|
|
1834
|
-
function
|
|
1835
|
-
return typeName.
|
|
1850
|
+
function isPrimitiveType( typeName ) {
|
|
1851
|
+
return typeName.split('.')[0] === 'Edm';
|
|
1836
1852
|
}
|
|
1837
1853
|
|
|
1838
|
-
function isEnumType(dTypeName) {
|
|
1854
|
+
function isEnumType( dTypeName ) {
|
|
1839
1855
|
const type = getDictType(dTypeName);
|
|
1840
|
-
return type && type
|
|
1856
|
+
return type && type.$kind === 'EnumType';
|
|
1841
1857
|
}
|
|
1842
1858
|
|
|
1843
|
-
function isComplexType(dTypeName) {
|
|
1859
|
+
function isComplexType( dTypeName ) {
|
|
1844
1860
|
const type = getDictType(dTypeName);
|
|
1845
|
-
return dTypeName === 'Edm.ComplexType' || type && type
|
|
1861
|
+
return dTypeName === 'Edm.ComplexType' || type && type.$kind === 'ComplexType';
|
|
1846
1862
|
}
|
|
1847
1863
|
|
|
1848
|
-
function isAbstractType(dTypeName) {
|
|
1864
|
+
function isAbstractType( dTypeName ) {
|
|
1849
1865
|
const type = getDictType(dTypeName);
|
|
1850
|
-
return type && type
|
|
1866
|
+
return type && type.Abstract === 'true';
|
|
1851
1867
|
}
|
|
1852
1868
|
|
|
1853
1869
|
// return true if derived has baseCandidate as direct or indirect base type
|
|
1854
|
-
function isDerivedFrom(derived, baseCandidate) {
|
|
1870
|
+
function isDerivedFrom( derived, baseCandidate ) {
|
|
1855
1871
|
while (derived) {
|
|
1856
|
-
if (derived
|
|
1872
|
+
if (derived === baseCandidate)
|
|
1873
|
+
return true;
|
|
1857
1874
|
derived = getDictType(derived).BaseType;
|
|
1858
1875
|
}
|
|
1859
1876
|
return false;
|
|
1860
1877
|
}
|
|
1861
1878
|
|
|
1862
1879
|
// return dictionary of all properties of typeName, including those of base types
|
|
1863
|
-
function getAllProperties(typeName) {
|
|
1864
|
-
if (!typeName || !getDictType(typeName))
|
|
1880
|
+
function getAllProperties( typeName ) {
|
|
1881
|
+
if (!typeName || !getDictType(typeName))
|
|
1882
|
+
return null;
|
|
1865
1883
|
return getDictType(typeName).Properties;
|
|
1866
1884
|
}
|
|
1867
|
-
|
|
1868
1885
|
}
|
|
1869
1886
|
|
|
1870
|
-
function mergeOdataVocabularies(options, message) {
|
|
1887
|
+
function mergeOdataVocabularies( options, message ) {
|
|
1871
1888
|
/* Merge options.odataVocabularies into vocabularyDefinitions and
|
|
1872
1889
|
create a csn2edm stack local dictionary.
|
|
1873
1890
|
odataVocabularies is an object, each property is the
|
|
@@ -1878,43 +1895,40 @@ function mergeOdataVocabularies(options, message) {
|
|
|
1878
1895
|
inverted index of mergedVocDefs above)
|
|
1879
1896
|
*/
|
|
1880
1897
|
const mergedVocDefs = Object.assign({}, vocabularyDefinitions);
|
|
1881
|
-
const reqProps = ['Alias', 'Namespace', 'Uri'];
|
|
1882
|
-
if(options.odataVocabularies) {
|
|
1898
|
+
const reqProps = [ 'Alias', 'Namespace', 'Uri' ];
|
|
1899
|
+
if (options.odataVocabularies) {
|
|
1883
1900
|
const vocRefs = options.odataVocabularies;
|
|
1884
1901
|
if (typeof vocRefs === 'object' && !Array.isArray(vocRefs)) {
|
|
1885
|
-
Object.entries(vocRefs).forEach(([id, def]) => {
|
|
1902
|
+
Object.entries(vocRefs).forEach(([ id, def ]) => {
|
|
1886
1903
|
let defOk = true;
|
|
1887
|
-
reqProps.forEach(name => {
|
|
1888
|
-
if(!def[name] || typeof def[name] !== 'string') {
|
|
1904
|
+
reqProps.forEach((name) => {
|
|
1905
|
+
if (!def[name] || typeof def[name] !== 'string') {
|
|
1889
1906
|
message('odata-anno-vocref', null,
|
|
1890
|
-
|
|
1907
|
+
{ id, name, '#': 'malformed' } );
|
|
1891
1908
|
defOk = false;
|
|
1892
1909
|
}
|
|
1893
|
-
else if(name === 'Alias' && !edmUtils.isODataSimpleIdentifier(def[name])) {
|
|
1894
|
-
message('odata-spec-violation-id', null, { id:name, value: def[name], '#': 'vocrefalias' });
|
|
1910
|
+
else if (name === 'Alias' && !edmUtils.isODataSimpleIdentifier(def[name])) {
|
|
1911
|
+
message('odata-spec-violation-id', null, { id: name, value: def[name], '#': 'vocrefalias' });
|
|
1895
1912
|
defOk = false;
|
|
1896
1913
|
}
|
|
1897
1914
|
});
|
|
1898
|
-
if(defOk) {
|
|
1915
|
+
if (defOk) {
|
|
1899
1916
|
const vocDef = mergedVocDefs[id];
|
|
1900
|
-
if(vocDef && !vocDef.$optVocRef) {
|
|
1917
|
+
if (vocDef && !vocDef.$optVocRef) {
|
|
1918
|
+
message('odata-anno-vocref', null,
|
|
1919
|
+
{ id, type: mergedVocDefs[id].inc.Namespace, '#': 'redef' } );
|
|
1920
|
+
}
|
|
1921
|
+
else if (id !== def.Alias) {
|
|
1901
1922
|
message('odata-anno-vocref', null,
|
|
1902
|
-
|
|
1923
|
+
{ id, name: def.Alias } );
|
|
1903
1924
|
}
|
|
1904
1925
|
else {
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
const vocDef = {
|
|
1912
|
-
ref: { Uri: def.Uri },
|
|
1913
|
-
inc: { Alias: def.Alias, Namespace: def.Namespace }
|
|
1914
|
-
};
|
|
1915
|
-
setProp(vocDef, '$optVocRef', true);
|
|
1916
|
-
mergedVocDefs[id] = vocDef;
|
|
1917
|
-
}
|
|
1926
|
+
// no int.filename => no validation
|
|
1927
|
+
mergedVocDefs[id] = {
|
|
1928
|
+
ref: { Uri: def.Uri },
|
|
1929
|
+
inc: { Alias: def.Alias, Namespace: def.Namespace },
|
|
1930
|
+
};
|
|
1931
|
+
setProp(mergedVocDefs[id], '$optVocRef', true);
|
|
1918
1932
|
}
|
|
1919
1933
|
}
|
|
1920
1934
|
});
|