@sap/cds-compiler 3.4.2 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +15 -16
- package/bin/cdshi.js +19 -6
- package/doc/CHANGELOG_ARCHIVE.md +2 -2
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +61 -59
- package/lib/api/options.js +4 -2
- package/lib/api/validate.js +2 -2
- package/lib/base/cleanSymbols.js +2 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +6 -6
- package/lib/base/location.js +11 -12
- package/lib/base/message-registry.js +177 -58
- package/lib/base/messages.js +252 -180
- package/lib/base/model.js +14 -11
- package/lib/base/node-helpers.js +9 -10
- package/lib/base/optionProcessorHelper.js +138 -129
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +5 -5
- package/lib/checks/annotationsOData.js +4 -4
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +3 -3
- package/lib/checks/defaultValues.js +3 -3
- package/lib/checks/elements.js +7 -7
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +4 -4
- package/lib/checks/managedInType.js +1 -1
- package/lib/checks/managedWithoutKeys.js +1 -1
- package/lib/checks/nonexpandableStructured.js +5 -3
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +5 -6
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -2
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +4 -4
- package/lib/checks/types.js +7 -7
- package/lib/checks/utils.js +4 -4
- package/lib/checks/validator.js +16 -13
- package/lib/compiler/.eslintrc.json +4 -1
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/builtins.js +14 -14
- package/lib/compiler/checks.js +123 -48
- package/lib/compiler/define.js +12 -13
- package/lib/compiler/extend.js +266 -60
- package/lib/compiler/finalize-parse-cdl.js +10 -5
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +14 -6
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +27 -16
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +20 -0
- package/lib/edm/annotations/genericTranslation.js +604 -358
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +275 -222
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +6 -6
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +107 -77
- package/lib/edm/edmUtils.js +44 -5
- package/lib/gen/Dictionary.json +210 -8
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +67 -63
- package/lib/gen/language.tokens +81 -81
- package/lib/gen/languageLexer.interp +4 -10
- package/lib/gen/languageLexer.js +854 -869
- package/lib/gen/languageLexer.tokens +79 -81
- package/lib/gen/languageParser.js +14309 -13832
- package/lib/inspect/inspectModelStatistics.js +2 -2
- package/lib/inspect/inspectPropagation.js +6 -6
- package/lib/inspect/inspectUtils.js +2 -2
- package/lib/json/from-csn.js +102 -55
- package/lib/json/to-csn.js +119 -198
- package/lib/language/antlrParser.js +5 -2
- package/lib/language/docCommentParser.js +6 -6
- package/lib/language/errorStrategy.js +43 -23
- package/lib/language/genericAntlrParser.js +113 -133
- package/lib/language/language.g4 +1550 -1506
- package/lib/language/multiLineStringParser.js +3 -3
- package/lib/language/textUtils.js +2 -2
- package/lib/main.js +3 -3
- package/lib/model/csnRefs.js +5 -0
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/compare.js +2 -2
- package/lib/modelCompare/utils/.eslintrc.json +22 -0
- package/lib/modelCompare/utils/filter.js +100 -0
- package/lib/optionProcessor.js +5 -0
- package/lib/render/.eslintrc.json +1 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +311 -276
- package/lib/render/toHdbcds.js +97 -94
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +127 -223
- package/lib/render/utils/common.js +141 -108
- package/lib/render/utils/delta.js +227 -0
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/assertUnique.js +13 -12
- package/lib/transform/db/associations.js +5 -5
- package/lib/transform/db/cdsPersistence.js +10 -8
- package/lib/transform/db/constraints.js +14 -14
- package/lib/transform/db/expansion.js +20 -22
- package/lib/transform/db/flattening.js +24 -42
- package/lib/transform/db/groupByOrderBy.js +3 -3
- package/lib/transform/db/temporal.js +6 -6
- package/lib/transform/db/transformExists.js +23 -23
- package/lib/transform/db/views.js +16 -16
- package/lib/transform/draft/.eslintrc.json +1 -35
- package/lib/transform/draft/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +8 -29
- package/lib/transform/forRelationalDB.js +16 -6
- package/lib/transform/localized.js +11 -10
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +113 -47
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +17 -10
- package/lib/transform/translateAssocsToJoins.js +24 -19
- package/lib/transform/universalCsn/coreComputed.js +10 -10
- package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
- package/lib/transform/universalCsn/utils.js +3 -3
- package/lib/utils/file.js +5 -5
- package/lib/utils/moduleResolve.js +13 -13
- package/lib/utils/objectUtils.js +6 -6
- package/lib/utils/term.js +5 -2
- package/lib/utils/timetrace.js +51 -24
- package/package.json +5 -8
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/redirected-to-complex.md +4 -4
- package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
- package/lib/modelCompare/filter.js +0 -83
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { isEdmPropertyRendered, isBuiltinType } = require('../../model/csnUtils');
|
|
3
3
|
const edmUtils = require('../edmUtils.js');
|
|
4
|
-
const preprocessAnnotations = require('./preprocessAnnotations.js');
|
|
5
4
|
const oDataDictionary = require('../../gen/Dictionary.json');
|
|
5
|
+
const preprocessAnnotations = require('./preprocessAnnotations.js');
|
|
6
6
|
const { forEachDefinition } = require('../../model/csnUtils');
|
|
7
|
-
const { forEach } = require(
|
|
7
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
8
|
+
const { isBetaEnabled } = require('../../base/model.js');
|
|
8
9
|
|
|
9
10
|
/*
|
|
10
11
|
OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies
|
|
@@ -15,19 +16,21 @@ const { forEach } = require("../../utils/objectUtils");
|
|
|
15
16
|
JSON (published)
|
|
16
17
|
Measures (published)
|
|
17
18
|
Repeatability (published)
|
|
18
|
-
Temporal (
|
|
19
|
+
Temporal (published)
|
|
19
20
|
Validation (published)
|
|
20
21
|
|
|
21
22
|
SAP: https://github.com/SAP/odata-vocabularies/tree/master/vocabularies
|
|
22
23
|
Analytics (published)
|
|
23
24
|
CodeList (published)
|
|
24
|
-
Common (
|
|
25
|
+
Common (published)
|
|
25
26
|
Communication (published)
|
|
26
27
|
DataIntegration (published)
|
|
27
28
|
Graph (published, experimental)
|
|
28
29
|
Hierarchy (published, experimental)
|
|
29
30
|
HTML5 (published, experimental)
|
|
30
31
|
ODM (published, experimental)
|
|
32
|
+
Offline (experimental)
|
|
33
|
+
PDF (published)
|
|
31
34
|
PersonalData (published)
|
|
32
35
|
Session (published)
|
|
33
36
|
UI (published)
|
|
@@ -109,6 +112,16 @@ const vocabularyDefinitions = {
|
|
|
109
112
|
'inc': { Alias: 'ODM', Namespace: 'com.sap.vocabularies.ODM.v1' },
|
|
110
113
|
'int': { filename: 'ODM.xml' }
|
|
111
114
|
},
|
|
115
|
+
'Offline': {
|
|
116
|
+
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Offline.xml' },
|
|
117
|
+
'inc': { Alias: 'Offline', Namespace: 'com.sap.vocabularies.Offline.v1' },
|
|
118
|
+
'int': { filename: 'Offline.xml' }
|
|
119
|
+
},
|
|
120
|
+
'PDF': {
|
|
121
|
+
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/PDF.xml' },
|
|
122
|
+
'inc': { Alias: 'PDF', Namespace: 'com.sap.vocabularies.PDF.v1' },
|
|
123
|
+
'int': { filename: 'PDF.xml' }
|
|
124
|
+
},
|
|
112
125
|
'PersonalData': {
|
|
113
126
|
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/PersonalData.xml' },
|
|
114
127
|
'inc': { Alias: 'PersonalData', Namespace: 'com.sap.vocabularies.PersonalData.v1' },
|
|
@@ -138,7 +151,6 @@ const vocabularyDefinitions = {
|
|
|
138
151
|
|
|
139
152
|
const knownVocabularies = Object.keys(vocabularyDefinitions);
|
|
140
153
|
|
|
141
|
-
|
|
142
154
|
/**************************************************************************************************
|
|
143
155
|
* csn2annotationEdm
|
|
144
156
|
*
|
|
@@ -146,8 +158,8 @@ const knownVocabularies = Object.keys(vocabularyDefinitions);
|
|
|
146
158
|
* v - array with two boolean entries, first is for v2, second is for v4
|
|
147
159
|
* dictReplacement: for test purposes, replaces the standard oDataDictionary
|
|
148
160
|
*/
|
|
149
|
-
function csn2annotationEdm(
|
|
150
|
-
|
|
161
|
+
function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefined, options=undefined, messageFunctions=undefined) {
|
|
162
|
+
|
|
151
163
|
if(!Edm)
|
|
152
164
|
throw new Error('Please debug me: csn2annotationsEdm must be invoked with Edm');
|
|
153
165
|
if(!options)
|
|
@@ -157,13 +169,30 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
157
169
|
// global variable where we store all the generated annotations
|
|
158
170
|
const g_annosArray = [];
|
|
159
171
|
|
|
160
|
-
const {
|
|
172
|
+
const { message } = messageFunctions;
|
|
173
|
+
|
|
174
|
+
const [ adhocDictionary, allKnownVocabularies ] = createAdhocDictionary();
|
|
161
175
|
|
|
162
|
-
|
|
176
|
+
/*
|
|
177
|
+
allKnownVocabularies is the union of knownVocabularies & adhoc $mySchemaName annotation definition
|
|
178
|
+
this allows to identify namespace prefixes of arbitrary length.
|
|
179
|
+
|
|
180
|
+
If in the future, the full Oasis vocabulary names shall be made usable for assignments
|
|
181
|
+
eg. @com.sap.vocabularies.Common.v1.Label, all that needs to be done is to x-link the
|
|
182
|
+
vocabulary dictionary whith the full ns as key:
|
|
183
|
+
vocabularyDefinitions['@com.sap.vocabularies.Common.v1'] = vocabularyDefinitions['Common']
|
|
184
|
+
*/
|
|
185
|
+
allKnownVocabularies.push(...knownVocabularies);
|
|
186
|
+
allKnownVocabularies.sort((a,b) => b.length-a.length);
|
|
187
|
+
const whatsMyTermNamespace = function(anno) {
|
|
188
|
+
return allKnownVocabularies.reduce((rc, ns) => !rc && anno && anno.startsWith('@' + ns + '.') ? ns : rc, undefined);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Static dynamic expression dictionary, loaded with Edm creators
|
|
163
192
|
const [ dynamicExpressions, dynamicExpressionNames ] = initEdmJson();
|
|
164
193
|
|
|
165
194
|
// annotation preprocessing
|
|
166
|
-
preprocessAnnotations.preprocessAnnotations(
|
|
195
|
+
preprocessAnnotations.preprocessAnnotations(reqDefs, serviceName, options);
|
|
167
196
|
|
|
168
197
|
// we take note of which vocabularies are actually used in a service in order to avoid
|
|
169
198
|
// producing useless references; reset everything to "unused"
|
|
@@ -171,32 +200,41 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
171
200
|
vocabularyDefinitions[n].used = false;
|
|
172
201
|
});
|
|
173
202
|
|
|
203
|
+
// These vocabularies are always added for the runtimes
|
|
204
|
+
vocabularyDefinitions['Common'].used = true;
|
|
205
|
+
vocabularyDefinitions['Core'].used = true;
|
|
206
|
+
|
|
174
207
|
// provide functions for dictionary lookup
|
|
175
208
|
// use closure to avoid making "dict" and "experimental" global variables
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
209
|
+
const { getDictTerm, getDictType } = function(){
|
|
210
|
+
const dict = options.dictReplacement || oDataDictionary; // tests can set different dictionary via options
|
|
211
|
+
const experimental = {}; // take note of all experimental annos that have been used
|
|
212
|
+
const deprecated = {}; // take note of all deprecated annos that have been used
|
|
180
213
|
|
|
181
214
|
return {
|
|
182
215
|
// called to look-up a term in the dictionary
|
|
183
216
|
// in addition: - note usage of the respective vocabulary
|
|
184
217
|
// - issue a warning if the term is flagged as "experimental"
|
|
185
|
-
getDictTerm: function(termName,
|
|
186
|
-
const dictTerm = dict.terms[termName]
|
|
218
|
+
getDictTerm: function(termName, msg) {
|
|
219
|
+
const dictTerm = (dict.terms[termName] ||
|
|
220
|
+
adhocDictionary.terms[serviceName + '.' + termName] ||
|
|
221
|
+
adhocDictionary.terms[termName]);
|
|
187
222
|
// register vocabulary usage if possible
|
|
188
223
|
const vocName = termName.slice(0, termName.indexOf('.'));
|
|
189
224
|
if(vocabularyDefinitions[vocName])
|
|
190
225
|
vocabularyDefinitions[vocName].used = true;
|
|
191
|
-
|
|
226
|
+
else if(dictTerm?.$myServiceRoot &&
|
|
227
|
+
adhocDictionary.xrefs[dictTerm?.$myServiceRoot])
|
|
228
|
+
adhocDictionary.xrefs[dictTerm.$myServiceRoot].used = true;
|
|
192
229
|
if (dictTerm) {
|
|
193
|
-
// issue
|
|
230
|
+
// issue message for usage of experimental Terms, but only once per Term
|
|
194
231
|
if (dictTerm['$experimental'] && !experimental[termName]) {
|
|
195
|
-
message(
|
|
232
|
+
message('odata-anno-dict', msg.location, { anno: msg.anno(), '#': 'experimental' });
|
|
196
233
|
experimental[termName] = true;
|
|
197
234
|
}
|
|
198
235
|
if (dictTerm['$deprecated'] && !deprecated[termName]) {
|
|
199
|
-
message(
|
|
236
|
+
message('odata-anno-def', msg.location,
|
|
237
|
+
{ anno: msg.anno(), depr: dictTerm['$deprecationText'], '#': 'deprecated' });
|
|
200
238
|
deprecated[termName] = true;
|
|
201
239
|
}
|
|
202
240
|
}
|
|
@@ -205,10 +243,14 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
205
243
|
// called to look-up a type in the dictionary
|
|
206
244
|
// in addition, note usage of the respective vocabulary
|
|
207
245
|
getDictType: function (typeName) {
|
|
208
|
-
let dictType = dict.types[typeName]
|
|
246
|
+
let dictType = (dict.types[typeName] ||
|
|
247
|
+
adhocDictionary.types[serviceName + '.' + typeName] ||
|
|
248
|
+
adhocDictionary.types[typeName]);
|
|
209
249
|
if (dictType) {
|
|
210
250
|
// register usage of vocabulary
|
|
211
|
-
vocabularyDefinitions[typeName.slice(0, typeName.indexOf('.'))]
|
|
251
|
+
const voc = vocabularyDefinitions[typeName.slice(0, typeName.indexOf('.'))];
|
|
252
|
+
if(voc)
|
|
253
|
+
voc.used = true;
|
|
212
254
|
}
|
|
213
255
|
return dictType;
|
|
214
256
|
}
|
|
@@ -222,18 +264,20 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
222
264
|
// Note: only works for single service
|
|
223
265
|
// Note: we assume that all objects ly flat in the service, i.e. objName always
|
|
224
266
|
// looks like <service name, can contain dots>.<id>
|
|
225
|
-
forEachDefinition(
|
|
267
|
+
forEachDefinition(reqDefs, (object, objName) => {
|
|
226
268
|
if (objName === serviceName || objName.startsWith(serviceName + '.')) {
|
|
269
|
+
|
|
270
|
+
const location = [ 'definitions', objName ];
|
|
227
271
|
if (object.kind === 'action' || object.kind === 'function') {
|
|
228
|
-
handleAction(objName, object, null);
|
|
272
|
+
handleAction(objName, object, null, location);
|
|
229
273
|
}
|
|
230
274
|
else { // service, entity, anything else?
|
|
231
275
|
// handle the annotations directly tied to the object
|
|
232
|
-
handleAnnotations(objName, object);
|
|
276
|
+
handleAnnotations(objName, object, location);
|
|
233
277
|
// handle the annotations of the object's elements
|
|
234
|
-
handleElements(objName, object);
|
|
278
|
+
handleElements(objName, object, location);
|
|
235
279
|
// handle the annotations of the object's actions
|
|
236
|
-
handleBoundActions(objName, object);
|
|
280
|
+
handleBoundActions(objName, object, location);
|
|
237
281
|
}
|
|
238
282
|
}
|
|
239
283
|
});
|
|
@@ -241,7 +285,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
241
285
|
// filter out empty <Annotations...> elements
|
|
242
286
|
// add references for the used vocabularies
|
|
243
287
|
return {
|
|
244
|
-
annos: g_annosArray,
|
|
288
|
+
annos: g_annosArray,
|
|
289
|
+
usedVocabularies: Object.values(vocabularyDefinitions).filter(v => v.used),
|
|
290
|
+
xrefs: Object.values(adhocDictionary.xrefs).filter(v => v.used).map(v => v.$myServiceRoot)
|
|
245
291
|
};
|
|
246
292
|
|
|
247
293
|
//-------------------------------------------------------------------------------------------------
|
|
@@ -254,21 +300,6 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
254
300
|
return v && v[0];
|
|
255
301
|
}
|
|
256
302
|
|
|
257
|
-
// this function is called in the translation code to issue an info/warning/error message
|
|
258
|
-
// messages are reported via the severity function
|
|
259
|
-
// context contains "semantic location"
|
|
260
|
-
function message(severity, context, message) {
|
|
261
|
-
let fullMessage = 'In annotation translation: ' + message;
|
|
262
|
-
if (context) {
|
|
263
|
-
let loc = 'target: ' + context.target + ', annotation: ' + context.term;
|
|
264
|
-
if (context.stack.length > 0) {
|
|
265
|
-
loc += context.stack.join('');
|
|
266
|
-
}
|
|
267
|
-
fullMessage += ', ' + loc;
|
|
268
|
-
}
|
|
269
|
-
severity(null, null, `${fullMessage}`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
303
|
/*
|
|
273
304
|
Mapping annotated thing in cds/csn => annotated thing in edmx:
|
|
274
305
|
|
|
@@ -312,37 +343,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
312
343
|
// handle the annotations of the elements of an object
|
|
313
344
|
// in: objname : name of the object
|
|
314
345
|
// object : the object itself
|
|
315
|
-
function handleElements(objname, object) {
|
|
346
|
+
function handleElements(objname, object, location) {
|
|
316
347
|
if (!object.elements) return;
|
|
317
348
|
Object.entries(object.elements).forEach(([elemName, element]) => {
|
|
318
349
|
// determine the name of the target in the resulting edm
|
|
319
350
|
// for non-assoc element, this simply is "<objectName>/<elementName>"
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
// handle sub elements
|
|
324
|
-
if (element.elements) {
|
|
325
|
-
handleNestedElements(objname, elemName, element.elements);
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// handling annotations at nested elements is not yet supported
|
|
331
|
-
// => issue a warning, but only if there actually are annotations
|
|
332
|
-
function handleNestedElements(objname, baseElemName, elementsObj) {
|
|
333
|
-
if(!elementsObj) return;
|
|
334
|
-
Object.entries(elementsObj).forEach(([elemName, element]) => {
|
|
335
|
-
if (Object.keys(element).filter( x => x[0] === '@' ).filter(filterKnownVocabularies).length > 0) {
|
|
336
|
-
message(warning, null, `annotations at nested elements are not yet supported, object ${objname}, element ${baseElemName}.${elemName}`);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (element.elements) {
|
|
340
|
-
handleNestedElements(objname, baseElemName + '.' + elemName, element.elements);
|
|
341
|
-
}
|
|
351
|
+
const edmTargetName = objname + '/' + elemName;
|
|
352
|
+
const eLocation = [ ...location, 'elements', elemName ];
|
|
353
|
+
handleAnnotations(edmTargetName, element, eLocation);
|
|
342
354
|
});
|
|
343
355
|
}
|
|
344
356
|
|
|
345
|
-
|
|
346
357
|
// Annotations for actions and functions (and their parameters)
|
|
347
358
|
// v2, unbound: Target = <service>.EntityContainer/<action/function>
|
|
348
359
|
// v2, bound: Target = <service>.EntityContainer/<entity>_<action/function>
|
|
@@ -354,17 +365,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
354
365
|
// handle the annotations of cObject's (an entity) bound actions/functions and their parameters
|
|
355
366
|
// in: cObjectname : qualified name of the object that holds the actions
|
|
356
367
|
// cObject : the object itself
|
|
357
|
-
function handleBoundActions(cObjectname, cObject) {
|
|
368
|
+
function handleBoundActions(cObjectname, cObject, location) {
|
|
358
369
|
if(!cObject.actions) return;
|
|
359
370
|
// get service name: remove last part of the object name
|
|
360
371
|
// only works if all objects ly flat in the service
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
372
|
+
const nameParts = cObjectname.split('.')
|
|
373
|
+
const entityName = nameParts.pop();
|
|
374
|
+
const serviceName = nameParts.join('.');
|
|
364
375
|
|
|
365
376
|
Object.entries(cObject.actions).forEach(([n, action]) => {
|
|
366
|
-
|
|
367
|
-
handleAction(actionName, action, cObjectname);
|
|
377
|
+
const actionName = serviceName + '.' + (isV2() ? entityName + '_' : '') + n;
|
|
378
|
+
handleAction(actionName, action, cObjectname, [ ...location, 'actions', n ]);
|
|
368
379
|
});
|
|
369
380
|
}
|
|
370
381
|
|
|
@@ -373,63 +384,63 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
373
384
|
// in: cActionName : qualified name of the action
|
|
374
385
|
// cAction : the action object
|
|
375
386
|
// entityNameIfBound : qualified name of entity if bound action/function
|
|
376
|
-
function handleAction(cActionName, cAction, entityNameIfBound) {
|
|
387
|
+
function handleAction(cActionName, cAction, entityNameIfBound, location) {
|
|
377
388
|
let actionName = cActionName;
|
|
378
389
|
if (isV2()) { // Replace up to last dot with <serviceName>.EntityContainer
|
|
379
390
|
const lastDotIndex = actionName.lastIndexOf('.');
|
|
380
391
|
if (lastDotIndex > -1)
|
|
381
|
-
actionName = serviceName + '.EntityContainer/' + actionName.
|
|
392
|
+
actionName = serviceName + '.EntityContainer/' + actionName.substring(lastDotIndex + 1);
|
|
382
393
|
}
|
|
383
394
|
else { // add parameter type list
|
|
384
|
-
actionName += relParList(
|
|
395
|
+
actionName += relParList();
|
|
385
396
|
}
|
|
386
397
|
|
|
387
|
-
handleAnnotations(actionName, cAction);
|
|
398
|
+
handleAnnotations(actionName, cAction, location);
|
|
399
|
+
|
|
388
400
|
if(cAction.params) {
|
|
389
401
|
Object.entries(cAction.params).forEach(([n, p]) => {
|
|
390
|
-
|
|
391
|
-
handleAnnotations(edmTargetName, p);
|
|
402
|
+
const edmTargetName = actionName + '/' + n;
|
|
403
|
+
handleAnnotations(edmTargetName, p, [ ...location, 'params', n ]);
|
|
392
404
|
});
|
|
393
405
|
}
|
|
394
|
-
}
|
|
395
406
|
|
|
396
|
-
|
|
407
|
+
function relParList() {
|
|
397
408
|
// we rely on the order of params in the csn being the correct one
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
409
|
+
const params = [];
|
|
410
|
+
if (entityNameIfBound) {
|
|
411
|
+
params.push(cAction['@cds.odata.bindingparameter.collection'] ? 'Collection(' + entityNameIfBound + ')' : entityNameIfBound);
|
|
412
|
+
}
|
|
413
|
+
if (cAction.kind === 'function') {
|
|
414
|
+
if(cAction.params) {
|
|
415
|
+
cAction.params && Object.values(cAction.params).forEach(p => {
|
|
416
|
+
const isArrayType = !p.type && p.items && p.items.type;
|
|
417
|
+
params.push(isArrayType ? 'Collection(' + mapType(p.items) + ')' : mapType(p));
|
|
418
|
+
});
|
|
419
|
+
}
|
|
408
420
|
}
|
|
409
|
-
|
|
410
|
-
return '(' + params.join(',') + ')';
|
|
421
|
+
return '(' + params.join(',') + ')';
|
|
411
422
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
423
|
+
function mapType(p) {
|
|
424
|
+
if(isBuiltinType(p.type))
|
|
425
|
+
return edmUtils.mapCdsToEdmType(p, messageFunctions, false /*is only called for v4*/)
|
|
426
|
+
else if(options.whatsMySchemaName) {
|
|
427
|
+
const schemaName = options.whatsMySchemaName(p.type);
|
|
417
428
|
// strip the service namespace of from a parameter type
|
|
418
|
-
|
|
419
|
-
|
|
429
|
+
if(schemaName && schemaName !== options.serviceName)
|
|
430
|
+
return p.type.replace(options.serviceName + '.', '');
|
|
431
|
+
}
|
|
432
|
+
return p.type;
|
|
420
433
|
}
|
|
421
|
-
return p.type;
|
|
422
434
|
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
435
|
|
|
436
|
+
}
|
|
426
437
|
|
|
427
438
|
|
|
428
439
|
// handle all the annotations for a given cds thing, here called carrier
|
|
429
440
|
// edmTargetName : string, name of the target in edm
|
|
430
441
|
// carrier: object, the annotated cds thing, contains all the annotations
|
|
431
442
|
// as properties with names starting with @
|
|
432
|
-
function handleAnnotations(edmTargetName, carrier) {
|
|
443
|
+
function handleAnnotations(edmTargetName, carrier, location) {
|
|
433
444
|
// collect the names of the carrier's annotation properties
|
|
434
445
|
// keep only those annotations that - start with a known vocabulary name
|
|
435
446
|
// - have a value other than null
|
|
@@ -447,22 +458,20 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
447
458
|
// Filter unknown toplevel annotations
|
|
448
459
|
// Final filtering of all annotations is done in handleTerm
|
|
449
460
|
|
|
450
|
-
let
|
|
451
|
-
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
452
|
-
let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
461
|
+
let knownAnnos = filterKnownAnnotations();
|
|
453
462
|
if (knownAnnos.length === 0) return;
|
|
454
463
|
|
|
455
464
|
if(rewriteInnerAnnotations()) {
|
|
456
|
-
|
|
457
|
-
knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
465
|
+
knownAnnos = filterKnownAnnotations();
|
|
458
466
|
if (knownAnnos.length === 0) return;
|
|
459
467
|
}
|
|
468
|
+
|
|
460
469
|
const prefixTree = createPrefixTree();
|
|
461
470
|
|
|
462
471
|
// usually, for a given carrier there is one target
|
|
463
472
|
// for some carriers (service, entity), there can be an alternative target (usually the EntitySet)
|
|
464
473
|
// alternativeEdmTargetName: name of alternative target
|
|
465
|
-
// which one to choose depends on the "AppliesTo"
|
|
474
|
+
// which one to choose depends on the "AppliesTo" message of the single annotations, so we have
|
|
466
475
|
// to defer this decision; this is why we here construct a function that can make the decision
|
|
467
476
|
// later when looking at single annotations
|
|
468
477
|
|
|
@@ -480,7 +489,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
480
489
|
const alternativeAnnotations = [];
|
|
481
490
|
|
|
482
491
|
// now create annotation objects for all the annotations of carrier
|
|
483
|
-
handleAnno2(addAnnotation,
|
|
492
|
+
handleAnno2(addAnnotation, prefixTree, location);
|
|
484
493
|
|
|
485
494
|
// Produce Edm.Annotations and attach collected Edm.Annotation(s) to the
|
|
486
495
|
// envelope (or directly to the Schema)
|
|
@@ -498,6 +507,44 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
498
507
|
g_annosArray.push(annotations);
|
|
499
508
|
}
|
|
500
509
|
|
|
510
|
+
function filterKnownAnnotations() {
|
|
511
|
+
const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
512
|
+
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
513
|
+
const knownAnnos = annoNames.filter(whatsMyTermNamespace).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
514
|
+
if(isBetaEnabled(options, 'odataTerms')) {
|
|
515
|
+
// Extend knownAnnos with the in-service term definitions
|
|
516
|
+
const adhoc = [];
|
|
517
|
+
annoNames.forEach(an => {
|
|
518
|
+
const paths = an.slice(1).split('.');
|
|
519
|
+
const hasNSPrefix = paths[0] === serviceName;
|
|
520
|
+
if(!hasNSPrefix) {
|
|
521
|
+
paths.splice(0, 0, serviceName);
|
|
522
|
+
}
|
|
523
|
+
const fqName = '@' + paths.join('.');
|
|
524
|
+
const i = paths[1].indexOf('#');
|
|
525
|
+
const termNameWithoutQualifiers = i > 0 ? paths[1].substring(0, i) : paths[1];
|
|
526
|
+
const def = reqDefs.definitions[paths[0] + '.' + termNameWithoutQualifiers];
|
|
527
|
+
// if there is a term definition inside the service and the
|
|
528
|
+
// annotation value is != null, then add the annotation to the list
|
|
529
|
+
// of known annotations
|
|
530
|
+
if(def?.kind === 'annotation' && carrier[an] !== null) {
|
|
531
|
+
// Subsequent annotation handler code expects that first path segment
|
|
532
|
+
// is the Vocabulary namespace. The ad-hoc namespace is the service
|
|
533
|
+
// name itself.
|
|
534
|
+
// For service S an annotation assignment could be addressed
|
|
535
|
+
// relative or absolute to the service @S.foo or @foo
|
|
536
|
+
if(!hasNSPrefix) {
|
|
537
|
+
carrier[fqName] = carrier[an];
|
|
538
|
+
delete carrier[an];
|
|
539
|
+
}
|
|
540
|
+
adhoc.push(fqName);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
knownAnnos.push(...adhoc);
|
|
544
|
+
}
|
|
545
|
+
return knownAnnos;
|
|
546
|
+
}
|
|
547
|
+
|
|
501
548
|
// construct a function that is used to add an <Annotation ...> to the
|
|
502
549
|
// respective collector array
|
|
503
550
|
// this function is specific to the actual carrier, following the mapping rules given above
|
|
@@ -522,7 +569,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
522
569
|
}
|
|
523
570
|
// Another crazy hack due to this crazy function:
|
|
524
571
|
// If carrier is a managed association (has keys) and rc is false (annotation was not applicable)
|
|
525
|
-
// return true to NOT trigger 'unapplicable'
|
|
572
|
+
// return true to NOT trigger 'unapplicable' message message
|
|
526
573
|
if(rc === false && carrier.target && carrier.keys && appliesTo.includes('Property'))
|
|
527
574
|
rc = true;
|
|
528
575
|
return rc;
|
|
@@ -558,7 +605,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
558
605
|
alternativeEdmTargetName = carrier.$entitySetName || edmTargetName
|
|
559
606
|
const lastDotIndex = alternativeEdmTargetName.lastIndexOf('.');
|
|
560
607
|
if (lastDotIndex > -1)
|
|
561
|
-
alternativeEdmTargetName = serviceName + '.EntityContainer/' + alternativeEdmTargetName.
|
|
608
|
+
alternativeEdmTargetName = serviceName + '.EntityContainer/' + alternativeEdmTargetName.substring(lastDotIndex + 1);
|
|
562
609
|
hasAlternativeCarrier = carrier.$hasEntitySet;
|
|
563
610
|
}
|
|
564
611
|
else if(carrier.kind === 'type') {
|
|
@@ -630,7 +677,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
630
677
|
|
|
631
678
|
function rewriteInnerAnnotations() {
|
|
632
679
|
let rc = false;
|
|
633
|
-
for (
|
|
680
|
+
for (const a of knownAnnos) {
|
|
634
681
|
const [ prefix, innerAnnotation ] = a.split('.@');
|
|
635
682
|
/*
|
|
636
683
|
New inner annotation (de-)structuring of the core compiler to make
|
|
@@ -684,7 +731,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
684
731
|
// see example at definition of function mergePathStepsIntoPrefixTree
|
|
685
732
|
const prefixTree = {};
|
|
686
733
|
|
|
687
|
-
for (
|
|
734
|
+
for (const a of knownAnnos) {
|
|
688
735
|
// remove leading @ and split at "."
|
|
689
736
|
// stop splitting at ".@" (used for nested annotations)
|
|
690
737
|
// Inline JSON EDM allows to add annotations to record members
|
|
@@ -692,7 +739,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
692
739
|
// The splitter should leave such annotations alone, handleEdmJson
|
|
693
740
|
// takes care of assigning these annotations to the record members
|
|
694
741
|
const [ prefix, innerAnnotation ] = a.split('.@');
|
|
695
|
-
const
|
|
742
|
+
const ns = whatsMyTermNamespace(prefix);
|
|
743
|
+
const steps = prefix.replace('@' + ns + '.', '').split('.');
|
|
744
|
+
steps.splice(0,0, ns);
|
|
696
745
|
let i = steps.lastIndexOf('$edmJson');
|
|
697
746
|
if(i > -1) {
|
|
698
747
|
i = steps.findIndex(s => s.includes('@'), i+1);
|
|
@@ -738,7 +787,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
738
787
|
// q2 : ... } } }
|
|
739
788
|
function mergePathStepsIntoPrefixTree(tree, pathSteps, index, carrier) {
|
|
740
789
|
// TODO check nesting level > 3
|
|
741
|
-
|
|
790
|
+
const name = pathSteps[index];
|
|
742
791
|
if (index+1 < pathSteps.length ) {
|
|
743
792
|
if (!tree[name]) {
|
|
744
793
|
tree[name] = {};
|
|
@@ -756,27 +805,35 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
756
805
|
// handle all the annotations for a given carrier
|
|
757
806
|
// addAnnotationFunc: a function that adds the <Annotation ...> tags created here into the
|
|
758
807
|
// correct parent tag (see handleAnnotations())
|
|
759
|
-
// edmTargetName: name of the edmx target, only used for messages
|
|
760
808
|
// prefixTree: the annotations
|
|
761
|
-
function handleAnno2(addAnnotationFunc,
|
|
809
|
+
function handleAnno2(addAnnotationFunc, prefixTree, location) {
|
|
762
810
|
// first level names of prefix tree are the vocabulary names
|
|
763
811
|
// second level names are the term names
|
|
764
812
|
// create an annotation tag <Annotation ...> for each term
|
|
765
|
-
for (
|
|
766
|
-
for (
|
|
767
|
-
|
|
813
|
+
for (const voc of Object.keys(prefixTree)) {
|
|
814
|
+
for (const term of Object.keys(prefixTree[voc])) {
|
|
815
|
+
const fullTermName = voc + '.' + term;
|
|
816
|
+
|
|
817
|
+
// msg is "semantic" location message used for messages
|
|
818
|
+
const msg = {
|
|
819
|
+
fullTermName,
|
|
820
|
+
stack: [],
|
|
821
|
+
location: [ ...location, '@' + fullTermName ],
|
|
822
|
+
};
|
|
823
|
+
msg.anno = () => {
|
|
824
|
+
return msg.fullTermName + msg.stack.join('');
|
|
825
|
+
}
|
|
768
826
|
|
|
769
|
-
// context is "semantic" location info used for messages
|
|
770
|
-
let context = { target: edmTargetName, term: fullTermName, stack: [] };
|
|
771
827
|
// anno is the full <Annotation Term=...>
|
|
772
|
-
|
|
828
|
+
const anno = handleTerm(fullTermName, prefixTree[voc][term], msg);
|
|
773
829
|
if(anno !== undefined) {
|
|
774
|
-
// addAnnotationFunc needs AppliesTo
|
|
775
|
-
|
|
776
|
-
|
|
830
|
+
// addAnnotationFunc needs AppliesTo message from dictionary to decide where to put the anno
|
|
831
|
+
const termName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
|
|
832
|
+
const dictTerm = getDictTerm(termName, msg); // message for unknown term was already issued in handleTerm
|
|
777
833
|
if(!addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo)) {
|
|
778
834
|
if(dictTerm && dictTerm.AppliesTo) {
|
|
779
|
-
message(
|
|
835
|
+
message('odata-anno-def', location,
|
|
836
|
+
{ anno: termName, rawvalues: dictTerm.AppliesTo, '#': 'notapplied' });
|
|
780
837
|
}
|
|
781
838
|
}
|
|
782
839
|
}
|
|
@@ -788,9 +845,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
788
845
|
// annoValue : the annotation value from the csn
|
|
789
846
|
// if the csn contains flattened out elements of a structured annotation,
|
|
790
847
|
// they are regrouped here
|
|
791
|
-
//
|
|
848
|
+
// msg : for messages
|
|
792
849
|
// return : object that represents the annotation in the result edmx
|
|
793
|
-
function handleTerm(termName, annoValue,
|
|
850
|
+
function handleTerm(termName, annoValue, msg) {
|
|
794
851
|
/**
|
|
795
852
|
* create the <Annotation ...> tag
|
|
796
853
|
* @type {object}
|
|
@@ -798,52 +855,40 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
798
855
|
let newAnno = undefined;
|
|
799
856
|
const omissions = { 'Aggregation.default':1 };
|
|
800
857
|
const nullList = { 'Core.OperationAvailable':1 };
|
|
801
|
-
|
|
802
|
-
if(vocabularyDefinitions[voc] && annoValue !== null && !omissions[termName]|| nullList[termName]) {
|
|
803
|
-
newAnno = new Edm.Annotation(v, termName);
|
|
804
|
-
|
|
858
|
+
if(annoValue !== null && !omissions[termName]|| nullList[termName]) {
|
|
805
859
|
// termName may contain a qualifier: @UI.FieldGroup#shippingStatus
|
|
806
860
|
// -> remove qualifier from termName and set Qualifier attribute in newAnno
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
861
|
+
const i = termName.indexOf('#');
|
|
862
|
+
const termNameWithoutQualifiers = i > 0 ? termName.substring(0, i) : termName;
|
|
863
|
+
const qualifier = i >= 0 ? termName.substring(i+1) : undefined;
|
|
864
|
+
|
|
865
|
+
termNameWithoutQualifiers.split('.').forEach((id) => {
|
|
866
|
+
if(!edmUtils.isODataSimpleIdentifier(id))
|
|
867
|
+
message('odata-spec-violation-id', msg.location, { id });
|
|
868
|
+
})
|
|
869
|
+
newAnno = new Edm.Annotation(v, termNameWithoutQualifiers);
|
|
870
|
+
if (qualifier?.length) {
|
|
871
|
+
if (!edmUtils.isODataSimpleIdentifier(qualifier)) {
|
|
872
|
+
message('odata-spec-violation-id', msg.location,
|
|
873
|
+
{ id: qualifier, '#': 'qualifier' });
|
|
814
874
|
}
|
|
815
875
|
newAnno.setEdmAttribute('Term', termNameWithoutQualifiers);
|
|
816
|
-
newAnno.setEdmAttribute('Qualifier',
|
|
876
|
+
newAnno.setEdmAttribute('Qualifier', qualifier);
|
|
817
877
|
}
|
|
818
|
-
if (p.length>2) {
|
|
819
|
-
message(warning, context, `multiple qualifiers (${ p[1] },${ p[2] }${ p.length > 3 ? ',...' : '' })`);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
878
|
// get the type of the term from the dictionary
|
|
823
879
|
let termTypeName = null;
|
|
824
|
-
|
|
880
|
+
const dictTerm = getDictTerm(termNameWithoutQualifiers, msg);
|
|
825
881
|
if (dictTerm) {
|
|
826
882
|
termTypeName = dictTerm.Type;
|
|
827
883
|
}
|
|
828
884
|
else {
|
|
829
|
-
message(
|
|
885
|
+
message('odata-anno-def', msg.location,{ anno: termNameWithoutQualifiers });
|
|
830
886
|
}
|
|
831
887
|
|
|
832
888
|
// handle the annotation value and put the result into the <Annotation ...> tag just created above
|
|
833
|
-
handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName,
|
|
889
|
+
handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName, msg);
|
|
834
890
|
}
|
|
835
891
|
return newAnno;
|
|
836
|
-
|
|
837
|
-
function checkOdataTerm(ns) {
|
|
838
|
-
const simpleIdentifiers = ns.split('.');
|
|
839
|
-
simpleIdentifiers.forEach((identifier) => {
|
|
840
|
-
if(!edmUtils.isODataSimpleIdentifier(identifier)){
|
|
841
|
-
message(error, context,
|
|
842
|
-
`OData annotation term "${ identifier }" must consist of one or more dot separated simple identifiers (each starting with a letter or underscore, followed by at most 127 letters)`);
|
|
843
|
-
}
|
|
844
|
-
})
|
|
845
|
-
}
|
|
846
|
-
|
|
847
892
|
}
|
|
848
893
|
|
|
849
894
|
|
|
@@ -852,7 +897,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
852
897
|
// oTarget: the result object (o: odata)
|
|
853
898
|
// oTermName: current term
|
|
854
899
|
// dTypeName: expected type of cAnnoValue according to dictionary, may be null (d: dictionary)
|
|
855
|
-
function handleValue(cAnnoValue, oTarget, oTermName, dTypeName,
|
|
900
|
+
function handleValue(cAnnoValue, oTarget, oTermName, dTypeName, msg) {
|
|
856
901
|
// this function basically only figures out what kind of annotation value we have
|
|
857
902
|
// (can be: array, expression, enum, pseudo-record, record, simple value),
|
|
858
903
|
// then calls a more specific function to deal with it and puts
|
|
@@ -863,65 +908,86 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
863
908
|
if (isEnumType(dTypeName))
|
|
864
909
|
{
|
|
865
910
|
// if we find an array although we expect an enum, this may be a "flag enum"
|
|
866
|
-
checkMultiEnumValue(cAnnoValue
|
|
867
|
-
oTarget.setJSON({ 'EnumMember': generateMultiEnumValue(cAnnoValue,
|
|
868
|
-
oTarget.setXml( { 'EnumMember': generateMultiEnumValue(cAnnoValue,
|
|
911
|
+
checkMultiEnumValue(cAnnoValue);
|
|
912
|
+
oTarget.setJSON({ 'EnumMember': generateMultiEnumValue(cAnnoValue, false), 'EnumMember@odata.type' : '#'+dTypeName });
|
|
913
|
+
oTarget.setXml( { 'EnumMember': generateMultiEnumValue(cAnnoValue, true) });
|
|
869
914
|
}
|
|
870
915
|
else
|
|
871
916
|
{
|
|
872
|
-
oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName,
|
|
917
|
+
oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName, msg));
|
|
873
918
|
}
|
|
874
919
|
}
|
|
875
920
|
else if (cAnnoValue && typeof cAnnoValue === 'object') {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
}
|
|
879
|
-
else if ('=' in cAnnoValue) {
|
|
921
|
+
// an empty record is rendered as <Record/>
|
|
922
|
+
if ('=' in cAnnoValue) {
|
|
880
923
|
// expression
|
|
881
|
-
|
|
924
|
+
const res = handleExpression(cAnnoValue['='], dTypeName);
|
|
882
925
|
oTarget.setXml( { [res.name] : res.value });
|
|
883
926
|
oTarget.setJSON( { [res.name] : res.value });
|
|
884
927
|
}
|
|
885
928
|
else if (cAnnoValue['#'] !== undefined) {
|
|
929
|
+
const enumSymbol = cAnnoValue['#'];
|
|
886
930
|
// enum
|
|
887
931
|
if (dTypeName) {
|
|
888
|
-
|
|
889
|
-
|
|
932
|
+
const typeDef = getDictType(stripCollection(dTypeName));
|
|
933
|
+
if(typeDef && typeDef.$Allowed && !typeDef.Members) {
|
|
934
|
+
const allowedValue = typeDef.$Allowed.Symbols[enumSymbol];
|
|
935
|
+
if(!allowedValue) {
|
|
936
|
+
message('odata-anno-value', msg.location,
|
|
937
|
+
{ anno: msg.anno(),
|
|
938
|
+
type: dTypeName,
|
|
939
|
+
value: `"#${enumSymbol}"`,
|
|
940
|
+
rawvalues: Object.keys(typeDef.$Allowed.Symbols).map(m => `"#${m}"`),
|
|
941
|
+
'#': 'enum'
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
oTarget.setXml( { [typeDef.UnderlyingType?.replace('Edm.', '') || 'String']: allowedValue.Value || enumSymbol });
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
else if(checkEnumValue(enumSymbol))
|
|
949
|
+
oTarget.setXml( { 'EnumMember': dTypeName + '/' + enumSymbol });
|
|
950
|
+
else
|
|
951
|
+
oTarget.setXml( { 'String': enumSymbol });
|
|
890
952
|
}
|
|
891
953
|
else {
|
|
892
|
-
oTarget.setXml( { 'EnumMember': oTermName + 'Type/' +
|
|
954
|
+
oTarget.setXml( { 'EnumMember': oTermName + 'Type/' + enumSymbol });
|
|
893
955
|
}
|
|
894
|
-
oTarget.setJSON({ 'Edm.String':
|
|
956
|
+
oTarget.setJSON({ 'Edm.String': enumSymbol });
|
|
895
957
|
}
|
|
896
958
|
else if (cAnnoValue['$value'] !== undefined) {
|
|
897
959
|
// "pseudo-structure" used for annotating scalar annotations
|
|
898
|
-
handleValue(cAnnoValue['$value'], oTarget, oTermName, dTypeName,
|
|
960
|
+
handleValue(cAnnoValue['$value'], oTarget, oTermName, dTypeName, msg);
|
|
899
961
|
|
|
900
|
-
|
|
962
|
+
const k = Object.keys(cAnnoValue).filter( x => x[0] === '@');
|
|
901
963
|
if (!k || k.length === 0) {
|
|
902
|
-
message(
|
|
964
|
+
message('odata-anno-value', msg.location,
|
|
965
|
+
{ anno: msg.anno(), str: 'nested', '#': 'nested' });
|
|
903
966
|
}
|
|
904
|
-
for (
|
|
905
|
-
|
|
967
|
+
for (const nestedAnnoName of k) {
|
|
968
|
+
const nestedAnno = handleTerm(nestedAnnoName.slice(1), cAnnoValue[nestedAnnoName], msg);
|
|
906
969
|
oTarget.append(nestedAnno);
|
|
907
970
|
}
|
|
908
971
|
}
|
|
909
972
|
else if (cAnnoValue['$edmJson']) {
|
|
910
973
|
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
|
|
911
|
-
oTarget.append(handleEdmJson(cAnnoValue['$edmJson'],
|
|
974
|
+
oTarget.append(handleEdmJson(cAnnoValue['$edmJson'], msg));
|
|
912
975
|
}
|
|
913
976
|
else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
914
977
|
// object consists only of properties starting with "@", no $value
|
|
915
|
-
message(
|
|
978
|
+
message('odata-anno-value', msg.location,
|
|
979
|
+
{ anno: msg.anno(), str: 'base', '#': 'nested'} );
|
|
916
980
|
}
|
|
917
981
|
else {
|
|
918
982
|
// regular record
|
|
919
|
-
oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName,
|
|
983
|
+
oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, msg));
|
|
920
984
|
}
|
|
921
985
|
}
|
|
922
986
|
else {
|
|
923
|
-
|
|
924
|
-
if(oTermName === 'Core.OperationAvailable' && dTypeName === 'Edm.Boolean'
|
|
987
|
+
const res = handleSimpleValue(cAnnoValue, dTypeName, msg);
|
|
988
|
+
if(((oTermName === 'Core.OperationAvailable' && dTypeName === 'Edm.Boolean') ||
|
|
989
|
+
(oTermName === 'Validation.AllowedValues' && dTypeName === 'Edm.PrimitiveType'))
|
|
990
|
+
&& cAnnoValue === null) {
|
|
925
991
|
oTarget.append(new Edm.ValueThing(v, 'Null'));
|
|
926
992
|
oTarget._ignoreChildren = true;
|
|
927
993
|
}
|
|
@@ -930,81 +996,95 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
930
996
|
}
|
|
931
997
|
oTarget.setJSON( { [res.jsonName] : res.value });
|
|
932
998
|
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
999
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1000
|
+
// found an enum value ("#"), check whether this fits
|
|
1001
|
+
// the expected type "dTypeName"
|
|
1002
|
+
function checkEnumValue(value) {
|
|
1003
|
+
let rc = true;
|
|
1004
|
+
const expectedType = getDictType(dTypeName);
|
|
1005
|
+
if (!expectedType && !isPrimitiveType(dTypeName)) {
|
|
1006
|
+
message('odata-anno-dict', msg.location,
|
|
1007
|
+
{ anno: msg.anno(), type: dTypeName });
|
|
1008
|
+
}
|
|
1009
|
+
else if (isComplexType(dTypeName) || isPrimitiveType(dTypeName) || expectedType['$kind'] !== 'EnumType') {
|
|
1010
|
+
message('odata-anno-value', msg.location,
|
|
1011
|
+
{ anno: msg.anno(),
|
|
1012
|
+
type: dTypeName,
|
|
1013
|
+
value: `"#${value}"` });
|
|
1014
|
+
rc = false;
|
|
1015
|
+
}
|
|
1016
|
+
else if (!expectedType.Members.includes(value)) {
|
|
1017
|
+
message('odata-anno-value', msg.location,
|
|
1018
|
+
{ anno: msg.anno(),
|
|
1019
|
+
type: dTypeName,
|
|
1020
|
+
value: `"#${value}"`,
|
|
1021
|
+
rawvalues: expectedType.Members.map(m => `"#${m}"`),
|
|
1022
|
+
'#': 'enum'
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
return rc;
|
|
951
1026
|
}
|
|
952
|
-
}
|
|
953
1027
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1028
|
+
// cAnnoValue: array
|
|
1029
|
+
// dTypeName: expected type, already identified as enum type
|
|
1030
|
+
// array is expected to contain enum values
|
|
1031
|
+
function checkMultiEnumValue(cAnnoValue) {
|
|
958
1032
|
// we know that dTypeName is not null
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1033
|
+
const type = getDictType(dTypeName);
|
|
1034
|
+
if (!type || type['IsFlags'] !== 'true') {
|
|
1035
|
+
message('odata-anno-value', msg.location,
|
|
1036
|
+
{ anno: msg.anno(),
|
|
1037
|
+
str: 'collection',
|
|
1038
|
+
type: dTypeName,
|
|
1039
|
+
'#': 'struct' });
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
let index = 0;
|
|
1043
|
+
for (let value of cAnnoValue) {
|
|
1044
|
+
msg.stack.push('[' + index + ']');
|
|
1045
|
+
index++;
|
|
1046
|
+
if (value['#']) {
|
|
1047
|
+
checkEnumValue(value['#']);
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
message('odata-anno-value', msg.location,
|
|
1051
|
+
{ anno: msg.anno(),
|
|
1052
|
+
type: dTypeName,
|
|
1053
|
+
value: value['='] || value,
|
|
1054
|
+
rawvalues: type.Members.map(m => `"#${m}"`),
|
|
1055
|
+
'#': 'enum'
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
msg.stack.pop();
|
|
1059
|
+
}
|
|
962
1060
|
}
|
|
963
1061
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
checkEnumValue(e['#'], dTypeName, context);
|
|
970
|
-
}
|
|
971
|
-
else {
|
|
972
|
-
// TODO improve message: but found ...
|
|
973
|
-
message(warning, context, 'expected an enum value');
|
|
974
|
-
}
|
|
975
|
-
context.stack.pop();
|
|
1062
|
+
function generateMultiEnumValue(cAnnoValue, forXml) {
|
|
1063
|
+
// remove all invalid entries (warnining message has already been issued)
|
|
1064
|
+
// replace short enum name by the full name
|
|
1065
|
+
// concatenate all the enums to a string, separated by spaces
|
|
1066
|
+
return cAnnoValue.filter( x => x['#'] != undefined ).map( x => (forXml ? dTypeName + '/' : '') + x['#'] ).join(forXml ? ' ' : ',');
|
|
976
1067
|
}
|
|
977
1068
|
}
|
|
978
1069
|
|
|
979
|
-
function generateMultiEnumValue(cAnnoValue, dTypeName, forXml)
|
|
980
|
-
{
|
|
981
|
-
// remove all invalid entries (warnining message has already been issued)
|
|
982
|
-
// replace short enum name by the full name
|
|
983
|
-
// concatenate all the enums to a string, separated by spaces
|
|
984
|
-
return cAnnoValue.filter( x => x['#'] != undefined ).map( x => (forXml ? dTypeName + '/' : '') + x['#'] ).join(forXml ? ' ' : ',');
|
|
985
|
-
}
|
|
986
1070
|
|
|
987
1071
|
|
|
988
1072
|
// found an expression value ("=") "expr"
|
|
989
1073
|
// expected type is dTypeName
|
|
990
1074
|
// note: expr can also be provided if an enum/complex type/collection is expected
|
|
991
|
-
function handleExpression(
|
|
1075
|
+
function handleExpression(value, dTypeName) {
|
|
992
1076
|
let typeName = 'Path';
|
|
993
1077
|
if( ['Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(dTypeName) )
|
|
994
1078
|
typeName = dTypeName.split('.')[1];
|
|
995
1079
|
|
|
996
|
-
|
|
997
|
-
if (!expr) {
|
|
998
|
-
message(warning, context, 'empty expression value');
|
|
999
|
-
}
|
|
1000
|
-
else {
|
|
1080
|
+
if(value) {
|
|
1001
1081
|
// replace all occurrences of '.' by '/' up to first '@'
|
|
1002
|
-
|
|
1082
|
+
value = value.split('@').map((o,i) => (i === 0 ? o.replace(/\./g, '/') : o)).join('@');
|
|
1003
1083
|
}
|
|
1004
1084
|
|
|
1005
1085
|
return {
|
|
1006
1086
|
name : typeName,
|
|
1007
|
-
value
|
|
1087
|
+
value
|
|
1008
1088
|
}
|
|
1009
1089
|
}
|
|
1010
1090
|
|
|
@@ -1016,119 +1096,147 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1016
1096
|
// floating point type except Edm.Decimal -> Float
|
|
1017
1097
|
// Edm.Decimal -> Decimal
|
|
1018
1098
|
// integer tpye -> Int
|
|
1019
|
-
function handleSimpleValue(
|
|
1099
|
+
function handleSimpleValue(value, dTypeName, msg) {
|
|
1020
1100
|
// these types must be represented as "String" values in XML:
|
|
1021
1101
|
const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
|
|
1022
1102
|
// caller already made sure that val is neither object nor array
|
|
1023
|
-
dTypeName = resolveType(dTypeName);
|
|
1024
1103
|
|
|
1025
|
-
if
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1104
|
+
// check if type has allowed values
|
|
1105
|
+
const typeDef = getDictType(dTypeName);
|
|
1106
|
+
const Allowed = typeDef?.$Allowed;
|
|
1107
|
+
|
|
1108
|
+
let resolvedType = resolveTypeDefinition(dTypeName);
|
|
1109
|
+
|
|
1110
|
+
if (isEnumType(resolvedType)) {
|
|
1111
|
+
const type = getDictType(resolvedType);
|
|
1112
|
+
const expected = type.Members.map(m => `"#${m}"`);
|
|
1113
|
+
message('odata-anno-value', msg.location,
|
|
1114
|
+
{ anno: msg.anno(),
|
|
1115
|
+
value,
|
|
1116
|
+
rawvalues: expected,
|
|
1117
|
+
type: resolvedType,
|
|
1118
|
+
'#': 'enum' });
|
|
1029
1119
|
}
|
|
1030
1120
|
|
|
1031
1121
|
let typeName = 'String';
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1122
|
+
if(Allowed && !Allowed.Values[value])
|
|
1123
|
+
message('odata-anno-value', msg.location,
|
|
1124
|
+
{ anno: msg.anno(),
|
|
1125
|
+
value,
|
|
1126
|
+
rawvalues: Object.keys(Allowed.Values),
|
|
1127
|
+
type: resolvedType,
|
|
1128
|
+
'#': 'enum' });
|
|
1129
|
+
|
|
1130
|
+
if (typeof value === 'string') {
|
|
1131
|
+
if (resolvedType === 'Edm.Boolean') {
|
|
1035
1132
|
typeName = 'Bool';
|
|
1036
|
-
if (
|
|
1037
|
-
message(
|
|
1133
|
+
if (value !== 'true' && value !== 'false') {
|
|
1134
|
+
message('odata-anno-value', msg.location,
|
|
1135
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1038
1136
|
}
|
|
1039
1137
|
}
|
|
1040
|
-
else if (
|
|
1138
|
+
else if (resolvedType === 'Edm.Decimal') {
|
|
1041
1139
|
typeName = 'Decimal';
|
|
1042
|
-
if (isNaN(Number(
|
|
1043
|
-
message(
|
|
1140
|
+
if (isNaN(Number(value)) || isNaN(parseFloat(value))) {
|
|
1141
|
+
message('odata-anno-value', msg.location,
|
|
1142
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1044
1143
|
}
|
|
1045
1144
|
}
|
|
1046
|
-
else if (
|
|
1145
|
+
else if (resolvedType === 'Edm.Double' || resolvedType === 'Edm.Single') {
|
|
1047
1146
|
typeName = 'Float';
|
|
1048
|
-
if (isNaN(Number(
|
|
1049
|
-
message(
|
|
1147
|
+
if (isNaN(Number(value)) || isNaN(parseFloat(value))) {
|
|
1148
|
+
message('odata-anno-value', msg.location,
|
|
1149
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1050
1150
|
}
|
|
1051
1151
|
}
|
|
1052
|
-
else if (isComplexType(
|
|
1053
|
-
message(
|
|
1152
|
+
else if (isComplexType(resolvedType)) {
|
|
1153
|
+
message('odata-anno-value', msg.location,
|
|
1154
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1054
1155
|
}
|
|
1055
|
-
else if (isEnumType(
|
|
1056
|
-
message(
|
|
1156
|
+
else if (isEnumType(resolvedType)) {
|
|
1157
|
+
message('odata-anno-value', msg.location,
|
|
1158
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1057
1159
|
typeName = 'EnumMember';
|
|
1058
1160
|
}
|
|
1059
|
-
else if (
|
|
1161
|
+
else if (resolvedType && resolvedType.startsWith('Edm.') && !castToXmlString.includes(resolvedType)) {
|
|
1060
1162
|
// this covers also all paths
|
|
1061
|
-
typeName =
|
|
1163
|
+
typeName = resolvedType.substring(4);
|
|
1062
1164
|
}
|
|
1063
1165
|
else {
|
|
1064
|
-
if(
|
|
1065
|
-
|
|
1166
|
+
if(resolvedType == undefined || castToXmlString.some(t => t === resolvedType))
|
|
1167
|
+
resolvedType = 'Edm.String';
|
|
1066
1168
|
// TODO
|
|
1067
|
-
//message(
|
|
1169
|
+
//message(message, msg, "type is not yet handled: found String, expected type: " + dTypeName);
|
|
1068
1170
|
}
|
|
1069
1171
|
}
|
|
1070
|
-
else if (typeof
|
|
1071
|
-
if(
|
|
1172
|
+
else if (typeof value === 'boolean') {
|
|
1173
|
+
if(resolvedType == undefined || resolvedType === 'Edm.Boolean' || resolvedType === 'Edm.PrimitiveType') {
|
|
1072
1174
|
typeName = 'Bool';
|
|
1073
|
-
|
|
1175
|
+
resolvedType = 'Edm.Boolean';
|
|
1074
1176
|
}
|
|
1075
|
-
if (
|
|
1076
|
-
|
|
1177
|
+
if (resolvedType === 'Edm.Boolean') {
|
|
1178
|
+
value = value ? 'true' : 'false';
|
|
1077
1179
|
}
|
|
1078
|
-
else if (
|
|
1180
|
+
else if (resolvedType === 'Edm.String') {
|
|
1079
1181
|
typeName = 'String';
|
|
1080
1182
|
}
|
|
1081
1183
|
else {
|
|
1082
|
-
message(
|
|
1184
|
+
message('odata-anno-value', msg.location,
|
|
1185
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1083
1186
|
}
|
|
1084
1187
|
}
|
|
1085
|
-
else if (typeof
|
|
1086
|
-
if (isComplexType(
|
|
1087
|
-
message(
|
|
1188
|
+
else if (typeof value === 'number') {
|
|
1189
|
+
if (isComplexType(resolvedType)) {
|
|
1190
|
+
message('odata-anno-value', msg.location,
|
|
1191
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1088
1192
|
}
|
|
1089
|
-
else if (
|
|
1193
|
+
else if (resolvedType === 'Edm.String') {
|
|
1090
1194
|
typeName = 'String';
|
|
1091
1195
|
}
|
|
1092
|
-
else if (
|
|
1093
|
-
message(
|
|
1196
|
+
else if (resolvedType === 'Edm.PropertyPath') {
|
|
1197
|
+
message('odata-anno-value', msg.location,
|
|
1198
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1094
1199
|
}
|
|
1095
|
-
else if (
|
|
1096
|
-
message(
|
|
1200
|
+
else if (resolvedType === 'Edm.Boolean') {
|
|
1201
|
+
message('odata-anno-value', msg.location,
|
|
1202
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1097
1203
|
}
|
|
1098
|
-
else if (
|
|
1204
|
+
else if (resolvedType === 'Edm.Decimal') {
|
|
1099
1205
|
typeName = 'Decimal';
|
|
1100
1206
|
}
|
|
1101
|
-
else if (
|
|
1207
|
+
else if (resolvedType === 'Edm.Double') {
|
|
1102
1208
|
typeName = 'Float';
|
|
1103
1209
|
}
|
|
1104
1210
|
else {
|
|
1105
1211
|
//typeName = Number.isInteger(val) ? 'Int' : 'Float';
|
|
1106
|
-
if(Number.isInteger(
|
|
1212
|
+
if(Number.isInteger(value)) {
|
|
1107
1213
|
typeName = 'Int';
|
|
1108
|
-
if(
|
|
1109
|
-
|
|
1214
|
+
if(resolvedType == undefined || resolvedType === 'Edm.PrimitiveType' || !resolvedType.startsWith('Edm.'))
|
|
1215
|
+
resolvedType = 'Edm.Int64';
|
|
1110
1216
|
}
|
|
1111
1217
|
else {
|
|
1112
1218
|
typeName = 'Float';
|
|
1113
|
-
if(
|
|
1114
|
-
|
|
1219
|
+
if(resolvedType == undefined || resolvedType === 'Edm.PrimitiveType'|| !resolvedType.startsWith('Edm.'))
|
|
1220
|
+
resolvedType = 'Edm.Double';
|
|
1115
1221
|
}
|
|
1116
1222
|
}
|
|
1117
1223
|
}
|
|
1118
|
-
else if (
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1224
|
+
else if (value === null)
|
|
1225
|
+
if((resolvedType == null || resolvedType == 'Edm.PrimitiveType') && typeName === 'String') {
|
|
1226
|
+
resolvedType = 'Edm.String';
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
message('odata-anno-value', msg.location,
|
|
1230
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1231
|
+
}
|
|
1124
1232
|
|
|
1125
|
-
if( ['Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(
|
|
1126
|
-
|
|
1233
|
+
if( ['Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(resolvedType) )
|
|
1234
|
+
resolvedType = resolvedType.split('.')[1];
|
|
1127
1235
|
|
|
1128
1236
|
return {
|
|
1129
1237
|
name : typeName,
|
|
1130
|
-
jsonName:
|
|
1131
|
-
value :
|
|
1238
|
+
jsonName: resolvedType,
|
|
1239
|
+
value : value
|
|
1132
1240
|
};
|
|
1133
1241
|
}
|
|
1134
1242
|
|
|
@@ -1137,16 +1245,18 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1137
1245
|
// dTypeName : name of the expected record type according to vocabulary, may be null
|
|
1138
1246
|
//
|
|
1139
1247
|
// can be called for a record directly below a term, or at a deeper level
|
|
1140
|
-
function generateRecord(obj, termName, dTypeName,
|
|
1248
|
+
function generateRecord(obj, termName, dTypeName, msg) {
|
|
1141
1249
|
/** @type {object} */
|
|
1142
|
-
|
|
1250
|
+
const newRecord = new Edm.Record(v);
|
|
1143
1251
|
|
|
1144
1252
|
// first determine what is the actual type to be used for the record
|
|
1145
1253
|
if (dTypeName && !isComplexType(dTypeName)) {
|
|
1146
1254
|
if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !isCollection(dTypeName))
|
|
1147
|
-
message(
|
|
1255
|
+
message('odata-anno-dict', msg.location,
|
|
1256
|
+
{ anno: msg.anno(), type: dTypeName, '#': 'std' });
|
|
1148
1257
|
else
|
|
1149
|
-
message(
|
|
1258
|
+
message('odata-anno-value', msg.location,
|
|
1259
|
+
{ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'struct' });
|
|
1150
1260
|
return newRecord;
|
|
1151
1261
|
}
|
|
1152
1262
|
|
|
@@ -1155,26 +1265,32 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1155
1265
|
actualTypeName = obj['$Type'];
|
|
1156
1266
|
if (!getDictType(actualTypeName)) {
|
|
1157
1267
|
// this type doesn't exist
|
|
1158
|
-
message(
|
|
1159
|
-
|
|
1160
|
-
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1161
|
-
}
|
|
1162
|
-
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
|
|
1163
|
-
// this type doesn't fit the expected one
|
|
1164
|
-
message(warning, context, `explicitly specified type '${ actualTypeName
|
|
1165
|
-
}' is not derived from expected type '${ dTypeName }'`);
|
|
1166
|
-
actualTypeName = dTypeName;
|
|
1268
|
+
message('odata-anno-type', msg.location,
|
|
1269
|
+
{ anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
|
|
1167
1270
|
// explicitly mentioned type, render in XML and JSON
|
|
1168
1271
|
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1169
1272
|
}
|
|
1170
1273
|
else if (isAbstractType(actualTypeName)) {
|
|
1171
1274
|
// this type is abstract
|
|
1172
|
-
message(
|
|
1275
|
+
message('odata-anno-type', msg.location,
|
|
1276
|
+
{ anno: msg.anno(), type: actualTypeName, '#': 'abstract' });
|
|
1173
1277
|
if(dTypeName)
|
|
1174
1278
|
actualTypeName = dTypeName;
|
|
1175
1279
|
// set to definition name and render in XML and JSON
|
|
1176
1280
|
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1177
1281
|
}
|
|
1282
|
+
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
|
|
1283
|
+
// this type doesn't fit the expected one
|
|
1284
|
+
message('odata-anno-type', msg.location,
|
|
1285
|
+
{ anno: msg.anno(),
|
|
1286
|
+
type: actualTypeName,
|
|
1287
|
+
name: dTypeName,
|
|
1288
|
+
code: '$Type',
|
|
1289
|
+
'#': 'derived' });
|
|
1290
|
+
actualTypeName = dTypeName;
|
|
1291
|
+
// explicitly mentioned type, render in XML and JSON
|
|
1292
|
+
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1293
|
+
}
|
|
1178
1294
|
else {
|
|
1179
1295
|
// ok
|
|
1180
1296
|
// Dictionary Type, render in XML only for backward compatibility
|
|
@@ -1191,7 +1307,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1191
1307
|
actualTypeName = dTypeName;
|
|
1192
1308
|
}
|
|
1193
1309
|
if (isAbstractType(actualTypeName))
|
|
1194
|
-
message(
|
|
1310
|
+
message('odata-anno-type', msg.location,
|
|
1311
|
+
{ anno: msg.anno(), type: dTypeName, '#': 'abstract' });
|
|
1195
1312
|
|
|
1196
1313
|
// Dictionary Type, render in XML only for backward compatibility
|
|
1197
1314
|
newRecord.setXml( { Type: actualTypeName });
|
|
@@ -1201,37 +1318,38 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1201
1318
|
}
|
|
1202
1319
|
|
|
1203
1320
|
// now the type is clear, so look ath the value
|
|
1204
|
-
|
|
1321
|
+
const dictProperties = getAllProperties(actualTypeName);
|
|
1205
1322
|
|
|
1206
1323
|
// loop over elements
|
|
1207
|
-
for (
|
|
1208
|
-
|
|
1324
|
+
for (const name of Object.keys(obj)) {
|
|
1325
|
+
msg.stack.push('.' + name);
|
|
1209
1326
|
|
|
1210
|
-
if (
|
|
1327
|
+
if (name === '$Type') {
|
|
1211
1328
|
// ignore, this is an "artificial" property used to indicate the type
|
|
1212
1329
|
}
|
|
1213
|
-
else if (
|
|
1330
|
+
else if (name[0] === '@') {
|
|
1214
1331
|
// not a regular property, but a nested annotation
|
|
1215
|
-
|
|
1332
|
+
const newAnno = handleTerm(name.substring(1, name.length), obj[name], msg);
|
|
1216
1333
|
newRecord.append(newAnno);
|
|
1217
1334
|
}
|
|
1218
1335
|
else {
|
|
1219
1336
|
// regular property
|
|
1220
1337
|
let dictPropertyTypeName = null;
|
|
1221
1338
|
if (dictProperties) {
|
|
1222
|
-
dictPropertyTypeName = dictProperties[
|
|
1223
|
-
if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType){
|
|
1224
|
-
message(
|
|
1339
|
+
dictPropertyTypeName = dictProperties[name];
|
|
1340
|
+
if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType) {
|
|
1341
|
+
message('odata-anno-type', msg.location,
|
|
1342
|
+
{ name, anno: termName, type: dTypeName });
|
|
1225
1343
|
}
|
|
1226
1344
|
}
|
|
1227
1345
|
|
|
1228
|
-
let newPropertyValue = new Edm.PropertyValue(v,
|
|
1346
|
+
let newPropertyValue = new Edm.PropertyValue(v, name);
|
|
1229
1347
|
// property value can be anything, so delegate handling to handleValue
|
|
1230
|
-
handleValue(obj[
|
|
1348
|
+
handleValue(obj[name], newPropertyValue, termName, dictPropertyTypeName, msg);
|
|
1231
1349
|
newRecord.append(newPropertyValue);
|
|
1232
1350
|
}
|
|
1233
1351
|
|
|
1234
|
-
|
|
1352
|
+
msg.stack.pop();
|
|
1235
1353
|
}
|
|
1236
1354
|
|
|
1237
1355
|
return newRecord;
|
|
@@ -1240,56 +1358,59 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1240
1358
|
|
|
1241
1359
|
// annoValue is an array
|
|
1242
1360
|
// dTypeName : Collection(...) according to dictionary
|
|
1243
|
-
function generateCollection(annoValue, termName, dTypeName,
|
|
1244
|
-
|
|
1361
|
+
function generateCollection(annoValue, termName, dTypeName, msg) {
|
|
1362
|
+
const newCollection = new Edm.Collection(v);
|
|
1245
1363
|
|
|
1246
1364
|
let innerTypeName = null;
|
|
1247
1365
|
if (dTypeName) {
|
|
1248
|
-
|
|
1366
|
+
const match = dTypeName.match(/^Collection\((.+)\)/);
|
|
1249
1367
|
if (match) {
|
|
1250
1368
|
innerTypeName = match[1];
|
|
1251
1369
|
}
|
|
1252
1370
|
else {
|
|
1253
|
-
message(
|
|
1371
|
+
message('odata-anno-value', msg.location,
|
|
1372
|
+
{ anno: msg.anno(), str: 'collection', type: dTypeName, '#': 'struct' });
|
|
1254
1373
|
}
|
|
1255
1374
|
}
|
|
1256
1375
|
|
|
1257
1376
|
let index = 0;
|
|
1258
1377
|
for (const value of annoValue) {
|
|
1259
|
-
|
|
1378
|
+
msg.stack.push('[' + index + ']');
|
|
1260
1379
|
index++
|
|
1261
1380
|
|
|
1262
1381
|
// for dealing with the single array entries we unfortunately cannot call handleValue(),
|
|
1263
1382
|
// as the values inside an array are represented differently from the values
|
|
1264
1383
|
// in a record or term
|
|
1265
1384
|
if (Array.isArray(value)) {
|
|
1266
|
-
message(
|
|
1385
|
+
message('odata-anno-value', msg.location,
|
|
1386
|
+
{ anno: msg.anno(), '#': 'nestedcollection' });
|
|
1267
1387
|
}
|
|
1268
1388
|
else if (value && typeof value === 'object') {
|
|
1269
1389
|
if (value['=']) {
|
|
1270
|
-
|
|
1271
|
-
|
|
1390
|
+
const res = handleExpression(value['='], innerTypeName);
|
|
1391
|
+
const newPropertyPath = new Edm.ValueThing(v, res.name, res.value );
|
|
1272
1392
|
newPropertyPath.setJSON( { [res.name] : res.value } );
|
|
1273
1393
|
newCollection.append(newPropertyPath);
|
|
1274
1394
|
}
|
|
1275
1395
|
else if (value['#']) {
|
|
1276
|
-
message(
|
|
1396
|
+
message('odata-anno-value', msg.location,
|
|
1397
|
+
{ anno: msg.anno(), '#': 'enumincollection' });
|
|
1277
1398
|
}
|
|
1278
1399
|
else if(value['$edmJson']) {
|
|
1279
|
-
newCollection.append(handleEdmJson(value['$edmJson'],
|
|
1400
|
+
newCollection.append(handleEdmJson(value['$edmJson'], msg));
|
|
1280
1401
|
}
|
|
1281
1402
|
else {
|
|
1282
|
-
newCollection.append(generateRecord(value, termName, innerTypeName,
|
|
1403
|
+
newCollection.append(generateRecord(value, termName, innerTypeName, msg));
|
|
1283
1404
|
}
|
|
1284
1405
|
}
|
|
1285
1406
|
else {
|
|
1286
|
-
|
|
1287
|
-
|
|
1407
|
+
const res = handleSimpleValue(value, innerTypeName, msg);
|
|
1408
|
+
const newThing = (value === null) ?new Edm.ValueThing(v, 'Null') : new Edm.ValueThing(v, res.name, value );
|
|
1288
1409
|
newThing.setJSON( { [res.jsonName] : res.value });
|
|
1289
1410
|
newCollection.append(newThing);
|
|
1290
1411
|
}
|
|
1291
1412
|
|
|
1292
|
-
|
|
1413
|
+
msg.stack.pop();
|
|
1293
1414
|
}
|
|
1294
1415
|
|
|
1295
1416
|
return newCollection;
|
|
@@ -1303,7 +1424,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1303
1424
|
// See example in test/odataAnnotations/smallTests/edmJson_noReverse_ok
|
|
1304
1425
|
// and test3/ODataBackends/DynExpr
|
|
1305
1426
|
|
|
1306
|
-
function handleEdmJson(obj,
|
|
1427
|
+
function handleEdmJson(obj, msg, exprDef=undefined) {
|
|
1307
1428
|
|
|
1308
1429
|
let edmNode = undefined;
|
|
1309
1430
|
if(obj === undefined || obj === null)
|
|
@@ -1312,7 +1433,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1312
1433
|
const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
|
|
1313
1434
|
|
|
1314
1435
|
if(dynExprs.length > 1) {
|
|
1315
|
-
message(
|
|
1436
|
+
message('odata-anno-value', msg.location,
|
|
1437
|
+
{ anno: msg.anno(), rawvalues: dynExprs, '#': 'multexpr' });
|
|
1316
1438
|
return edmNode;
|
|
1317
1439
|
}
|
|
1318
1440
|
|
|
@@ -1328,7 +1450,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1328
1450
|
if(Array.isArray(obj)) {
|
|
1329
1451
|
// EDM JSON doesn't mention annotations on collections
|
|
1330
1452
|
edmNode = new Edm.Collection(v);
|
|
1331
|
-
obj.forEach(o => edmNode.append(handleEdmJson(o,
|
|
1453
|
+
obj.forEach(o => edmNode.append(handleEdmJson(o, msg)));
|
|
1332
1454
|
}
|
|
1333
1455
|
else if(typeof obj === 'object') {
|
|
1334
1456
|
edmNode = new Edm.Record(v);
|
|
@@ -1342,11 +1464,11 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1342
1464
|
let child = undefined;
|
|
1343
1465
|
const [ head, tail ] = k.split('@');
|
|
1344
1466
|
if(tail) {
|
|
1345
|
-
child = handleTerm(tail, val,
|
|
1467
|
+
child = handleTerm(tail, val, msg);
|
|
1346
1468
|
}
|
|
1347
1469
|
else {
|
|
1348
1470
|
child = new Edm.PropertyValue(v, head);
|
|
1349
|
-
child.append(handleEdmJson(val,
|
|
1471
|
+
child.append(handleEdmJson(val, msg));
|
|
1350
1472
|
}
|
|
1351
1473
|
if(child) {
|
|
1352
1474
|
if(tail && head.length) {
|
|
@@ -1385,7 +1507,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1385
1507
|
forEach(obj, (name, val) => {
|
|
1386
1508
|
if(exprDef) {
|
|
1387
1509
|
if(exprDef.anno && name[0] === '@') {
|
|
1388
|
-
edmNode.append(handleTerm(name.slice(1), val,
|
|
1510
|
+
edmNode.append(handleTerm(name.slice(1), val, msg));
|
|
1389
1511
|
}
|
|
1390
1512
|
else if (exprDef.attr && exprDef.attr.includes(name)) {
|
|
1391
1513
|
if (name[0] === '$') {
|
|
@@ -1400,11 +1522,11 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1400
1522
|
else if(exprDef.children) {
|
|
1401
1523
|
if (Array.isArray(val)) {
|
|
1402
1524
|
val.forEach(a => {
|
|
1403
|
-
edmNode.append(handleEdmJson(a,
|
|
1525
|
+
edmNode.append(handleEdmJson(a, msg, exprDef));
|
|
1404
1526
|
});
|
|
1405
1527
|
}
|
|
1406
1528
|
else {
|
|
1407
|
-
edmNode.append(handleEdmJson(val,
|
|
1529
|
+
edmNode.append(handleEdmJson(val, msg, exprDef));
|
|
1408
1530
|
}
|
|
1409
1531
|
}
|
|
1410
1532
|
}
|
|
@@ -1432,6 +1554,130 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1432
1554
|
}
|
|
1433
1555
|
}
|
|
1434
1556
|
|
|
1557
|
+
/*
|
|
1558
|
+
translate vocabulary definitions into an adhoc dictionary
|
|
1559
|
+
with the same structure as the gobal jsonDictionary that
|
|
1560
|
+
contains all official term and type definitions.
|
|
1561
|
+
|
|
1562
|
+
Return the dictionary and an array of schemas to which
|
|
1563
|
+
the vocabulary definitions belong
|
|
1564
|
+
*/
|
|
1565
|
+
function createAdhocDictionary() {
|
|
1566
|
+
const allKnownVocabularies = [];
|
|
1567
|
+
const dict = { terms: {}, types: {}, xrefs: {} };
|
|
1568
|
+
|
|
1569
|
+
if(!isBetaEnabled(options, 'odataTerms'))
|
|
1570
|
+
return [ dict, allKnownVocabularies ];
|
|
1571
|
+
|
|
1572
|
+
for(let termName in csnVocabularies) {
|
|
1573
|
+
let dictDef = oDataDictionary.terms[termName];
|
|
1574
|
+
if(dictDef) {
|
|
1575
|
+
message('odata-anno-dict', ['vocabularies', termName],
|
|
1576
|
+
{ anno: termName, '#': 'redefinition' } );
|
|
1577
|
+
}
|
|
1578
|
+
else if(!dictDef) {
|
|
1579
|
+
const annoDef = csnVocabularies[termName];
|
|
1580
|
+
if(annoDef?.$mySchemaName) {
|
|
1581
|
+
if(!allKnownVocabularies.includes[annoDef.$mySchemaName])
|
|
1582
|
+
allKnownVocabularies.push(annoDef.$mySchemaName);
|
|
1583
|
+
const myServiceRoot = options.whatsMyServiceRootName(annoDef.$mySchemaName);
|
|
1584
|
+
if(!dict.xrefs[myServiceRoot])
|
|
1585
|
+
dict.xrefs[myServiceRoot] = { $myServiceRoot: myServiceRoot, used: false };
|
|
1586
|
+
const edmType = new Edm.TypeBase(options.v, {}, annoDef);
|
|
1587
|
+
dictDef = edmType._edmAttributes;
|
|
1588
|
+
dictDef['$myServiceRoot'] = myServiceRoot;
|
|
1589
|
+
let val = annoDef['@odata.term.AppliesTo'];
|
|
1590
|
+
if(val != null)
|
|
1591
|
+
dictDef.AppliesTo = Array.isArray(val) ? val.map(v=>v['='] || v) : [val['='] || val];
|
|
1592
|
+
val = annoDef['@odata.term.Experimental'];
|
|
1593
|
+
if(val != null)
|
|
1594
|
+
dictDef['$experimental'] = !!val;
|
|
1595
|
+
val = annoDef['@odata.term.Deprecated'];
|
|
1596
|
+
if(val != null) {
|
|
1597
|
+
dictDef['$deprecated'] = !!val;
|
|
1598
|
+
if(typeof val === 'string')
|
|
1599
|
+
dictDef['$deprecationText'] = val;
|
|
1600
|
+
}
|
|
1601
|
+
dict.terms[termName] = dictDef;
|
|
1602
|
+
|
|
1603
|
+
if((annoDef.items?.enum || annoDef.enum) && isBuiltinType(annoDef.items?.type || annoDef.type)) {
|
|
1604
|
+
const enumType = createTypeDefWithAllowedValues(annoDef, annoDef, dictDef.Type, ['vocabularies', termName ]);
|
|
1605
|
+
const tName = termName + '_$$$EnumType$$$$';
|
|
1606
|
+
dict.types[tName] = enumType;
|
|
1607
|
+
dictDef.Type = tName;
|
|
1608
|
+
}
|
|
1609
|
+
else
|
|
1610
|
+
addTypesToDictionary(annoDef);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return [ dict, allKnownVocabularies ];
|
|
1615
|
+
|
|
1616
|
+
function addTypesToDictionary(node) {
|
|
1617
|
+
const typeName = node.items?.type || node.type;
|
|
1618
|
+
// for type reuse in x-ref mode, the definition has already been
|
|
1619
|
+
// replaced by a reference object in edmPreprocessor.
|
|
1620
|
+
// Fall back to original type (the one of the other service).
|
|
1621
|
+
const typeDef = reqDefs.definitions[typeName] || reqDefs.definitions[typeName.replace(serviceName + '.', '')];
|
|
1622
|
+
if(typeDef) {
|
|
1623
|
+
let dictDef = { };
|
|
1624
|
+
const elements = typeDef.items?.elements || typeDef.elements;
|
|
1625
|
+
if(elements) {
|
|
1626
|
+
// complex type
|
|
1627
|
+
dictDef['$kind'] = 'ComplexType';
|
|
1628
|
+
dictDef.Properties = new Object(null);
|
|
1629
|
+
|
|
1630
|
+
for(let en in elements) {
|
|
1631
|
+
const elt = elements[en];
|
|
1632
|
+
if(isEdmPropertyRendered(elt, options)) {
|
|
1633
|
+
const edmType = new Edm.TypeBase(options.v, {}, elt);
|
|
1634
|
+
dictDef.Properties[en] = edmType._edmAttributes[edmType._typeName];
|
|
1635
|
+
addTypesToDictionary(elt);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
else {
|
|
1640
|
+
// type definition
|
|
1641
|
+
const edmType = new Edm.TypeBase(options.v, {}, typeDef);
|
|
1642
|
+
dictDef = createTypeDefWithAllowedValues(node, typeDef, edmType._edmAttributes[edmType._typeName], ['definitions', typeName ]);
|
|
1643
|
+
if(!typeDef.enum)
|
|
1644
|
+
delete dictDef.$Allowed;
|
|
1645
|
+
}
|
|
1646
|
+
dict.types[typeName] = dictDef;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function createTypeDefWithAllowedValues(node, typeDef, UnderlyingType, path) {
|
|
1651
|
+
const dictTypeDef = { $kind: 'TypeDefinition', UnderlyingType, $Allowed: { Values: {}, Symbols: {} } };
|
|
1652
|
+
// create an artificial type that holds the $Allowed enum symbols and values
|
|
1653
|
+
if(node.items && typeDef.enum || typeDef.items?.enum)
|
|
1654
|
+
message('odata-anno-dict-enum', [ 'vocabularies', node.name ],
|
|
1655
|
+
{ name: node.name,
|
|
1656
|
+
type: typeDef.name,
|
|
1657
|
+
'#': node.name === typeDef.name ? 'std' : 'type'
|
|
1658
|
+
});
|
|
1659
|
+
const enumDic = (typeDef.items?.enum || typeDef.enum);
|
|
1660
|
+
const baseType = (typeDef.items || typeDef).type;
|
|
1661
|
+
if(baseType !== 'cds.String' && Object.values(enumDic).some(v => !v.val)) {
|
|
1662
|
+
message('odata-anno-dict-enum', path,
|
|
1663
|
+
{ name: node.name, type: baseType, '#': 'value' });
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
for(const symbol in enumDic) {
|
|
1667
|
+
const valDic = { '#SymbolicName': symbol };
|
|
1668
|
+
const enumDef = enumDic[symbol];
|
|
1669
|
+
// <Null/> values can't be rendered
|
|
1670
|
+
if(enumDef.val === undefined)
|
|
1671
|
+
valDic.Value = symbol;
|
|
1672
|
+
else if(valDic.val !== null)
|
|
1673
|
+
valDic.Value = enumDef.val;
|
|
1674
|
+
dictTypeDef.$Allowed.Symbols[symbol] = dictTypeDef.$Allowed.Values[symbol] = valDic;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
return dictTypeDef;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1435
1681
|
function initEdmJson() {
|
|
1436
1682
|
// Static dynamic expression dictionary, loaded with Edm creators
|
|
1437
1683
|
const dynamicExpressions = {
|
|
@@ -1495,25 +1741,25 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1495
1741
|
//-------------------------------------------------------------------------------------------------
|
|
1496
1742
|
//-------------------------------------------------------------------------------------------------
|
|
1497
1743
|
|
|
1498
|
-
|
|
1499
|
-
// accepts those strings that start with a known vocabulary name
|
|
1500
|
-
function filterKnownVocabularies(name) {
|
|
1501
|
-
var match = name.match(/^(@)(\w+)/);
|
|
1502
|
-
if (match == null) return false;
|
|
1503
|
-
return knownVocabularies.includes(match[2]); // second match group
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// resolve "derived types"
|
|
1744
|
+
// resolve "derived types"
|
|
1507
1745
|
// -> if dTypeName is a TypeDefinition, replace by
|
|
1508
1746
|
// underlying type
|
|
1509
|
-
function
|
|
1510
|
-
|
|
1747
|
+
function resolveTypeDefinition(dTypeName) {
|
|
1748
|
+
const type = getDictType(dTypeName);
|
|
1511
1749
|
if (type && type.UnderlyingType && type['$kind'] === 'TypeDefinition') {
|
|
1512
1750
|
return type.UnderlyingType;
|
|
1513
1751
|
}
|
|
1514
1752
|
return dTypeName;
|
|
1515
1753
|
}
|
|
1516
1754
|
|
|
1755
|
+
function stripCollection(typeName) {
|
|
1756
|
+
const match = typeName.match(/^Collection\((.+)\)/);
|
|
1757
|
+
if (match) {
|
|
1758
|
+
typeName = match[1];
|
|
1759
|
+
}
|
|
1760
|
+
return typeName;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1517
1763
|
function isPrimitiveType(typeName) {
|
|
1518
1764
|
return typeName.split('.')[0] === 'Edm';
|
|
1519
1765
|
}
|
|
@@ -1523,17 +1769,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1523
1769
|
}
|
|
1524
1770
|
|
|
1525
1771
|
function isEnumType(dTypeName) {
|
|
1526
|
-
|
|
1772
|
+
const type = getDictType(dTypeName);
|
|
1527
1773
|
return type && type['$kind'] === 'EnumType';
|
|
1528
1774
|
}
|
|
1529
1775
|
|
|
1530
1776
|
function isComplexType(dTypeName) {
|
|
1531
|
-
|
|
1777
|
+
const type = getDictType(dTypeName);
|
|
1532
1778
|
return dTypeName === 'Edm.ComplexType' || type && type['$kind'] === 'ComplexType';
|
|
1533
1779
|
}
|
|
1534
1780
|
|
|
1535
1781
|
function isAbstractType(dTypeName) {
|
|
1536
|
-
|
|
1782
|
+
const type = getDictType(dTypeName);
|
|
1537
1783
|
return type && type['Abstract'] === 'true';
|
|
1538
1784
|
}
|
|
1539
1785
|
|