@sap/cds-compiler 4.1.2 → 4.2.4
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 +107 -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 +38 -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 +133 -50
- 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
package/lib/edm/csn2edm.js
CHANGED
|
@@ -6,32 +6,36 @@
|
|
|
6
6
|
const NAVPROP_TRENNER = '_';
|
|
7
7
|
const VALUELIST_NAVPROP_PREFIX = '';
|
|
8
8
|
|
|
9
|
-
const edmUtils = require('./edmUtils.js')
|
|
9
|
+
const edmUtils = require('./edmUtils.js');
|
|
10
10
|
const { initializeModel } = require('./edmPreprocessor.js');
|
|
11
11
|
const translate = require('./annotations/genericTranslation.js');
|
|
12
12
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
13
13
|
const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
|
|
14
14
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
15
15
|
const { makeMessageFunction } = require('../base/messages');
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm,
|
|
18
|
+
} = require('./edm.js');
|
|
17
19
|
|
|
18
20
|
/*
|
|
19
21
|
OData V2 spec 06/01/2017 PDF version is available from here:
|
|
20
22
|
https://msdn.microsoft.com/en-us/library/dd541474.aspx
|
|
21
23
|
*/
|
|
22
24
|
|
|
23
|
-
function csn2edm(_csn, serviceName, _options) {
|
|
24
|
-
return csn2edmAll(_csn, _options, [ serviceName ])[
|
|
25
|
+
function csn2edm( _csn, serviceName, _options ) {
|
|
26
|
+
return csn2edmAll(_csn, _options, [ serviceName ])[serviceName];
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
29
|
+
function csn2edmAll( _csn, _options, serviceNames = undefined ) {
|
|
28
30
|
// get us a fresh model copy that we can work with
|
|
29
31
|
const csn = cloneCsnNonDict(_csn, _options);
|
|
30
32
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
31
33
|
|
|
32
34
|
// use original options for messages; cloned CSN for semantic location
|
|
33
35
|
const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
|
|
34
|
-
const {
|
|
36
|
+
const {
|
|
37
|
+
info, warning, error, message, throwWithError,
|
|
38
|
+
} = messageFunctions;
|
|
35
39
|
checkCSNVersion(csn, _options);
|
|
36
40
|
|
|
37
41
|
let rc = Object.create(null);
|
|
@@ -40,7 +44,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
40
44
|
// There is the need to assign the odata options because we would like to determine
|
|
41
45
|
// whether to execute toFinalBaseType in the edmPreprocessor or not
|
|
42
46
|
if (_csn.meta && _csn.meta.transformation === 'odata' && _csn.meta.options) {
|
|
43
|
-
if (!csn.meta)
|
|
47
|
+
if (!csn.meta)
|
|
48
|
+
setProp(csn, 'meta', Object.create(null));
|
|
44
49
|
setProp(csn.meta, 'options', _csn.meta.options);
|
|
45
50
|
}
|
|
46
51
|
|
|
@@ -50,35 +55,36 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
50
55
|
reqDefs,
|
|
51
56
|
whatsMyServiceRootName,
|
|
52
57
|
fallBackSchemaName,
|
|
53
|
-
options
|
|
58
|
+
options,
|
|
59
|
+
] = initializeModel(csn, _options, messageFunctions, serviceNames);
|
|
54
60
|
|
|
55
61
|
const mergedVocabularies = translate.mergeOdataVocabularies(options, message);
|
|
56
62
|
|
|
57
63
|
const Edm = getEdm(options, messageFunctions);
|
|
58
64
|
|
|
59
|
-
const v = options
|
|
60
|
-
if(Object.keys(allServices).length === 0) {
|
|
65
|
+
const { v } = options;
|
|
66
|
+
if (Object.keys(allServices).length === 0) {
|
|
61
67
|
info(null, null, 'No Services in model');
|
|
62
68
|
return rc;
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
if(serviceNames === undefined)
|
|
71
|
+
if (serviceNames === undefined)
|
|
66
72
|
serviceNames = options.serviceNames;
|
|
67
|
-
if(serviceNames) {
|
|
68
|
-
serviceNames.forEach(name => {
|
|
69
|
-
|
|
70
|
-
if(serviceCsn
|
|
73
|
+
if (serviceNames) {
|
|
74
|
+
serviceNames.forEach((name) => {
|
|
75
|
+
const serviceCsn = allServices[name];
|
|
76
|
+
if (!serviceCsn)
|
|
71
77
|
warning(null, null, { name }, 'No service definition with name $(NAME) found in the model');
|
|
72
|
-
|
|
73
|
-
else
|
|
78
|
+
|
|
79
|
+
else
|
|
74
80
|
rc[name] = createEdm(serviceCsn);
|
|
75
|
-
}
|
|
76
81
|
});
|
|
77
82
|
}
|
|
78
83
|
else {
|
|
79
84
|
rc = Object.values(allServices).reduce((services, serviceCsn) => {
|
|
80
85
|
services[serviceCsn.name] = createEdm(serviceCsn);
|
|
81
|
-
return services;
|
|
86
|
+
return services;
|
|
87
|
+
}, rc);
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
throwWithError();
|
|
@@ -87,16 +93,20 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
87
93
|
//--------------------------------------------------------------------------------
|
|
88
94
|
// embedded functions
|
|
89
95
|
//--------------------------------------------------------------------------------
|
|
90
|
-
function createEdm(serviceCsn) {
|
|
91
|
-
|
|
92
|
-
function baseName(str, del) {
|
|
93
|
-
|
|
96
|
+
function createEdm( serviceCsn ) {
|
|
97
|
+
// eslint-disable-next-line no-unused-vars
|
|
98
|
+
function baseName( str, del ) {
|
|
99
|
+
const l = str.lastIndexOf(del);
|
|
100
|
+
return (l >= 0) ? str.slice(l + del.length, str.length) : str;
|
|
101
|
+
}
|
|
94
102
|
|
|
95
103
|
// if we have a real alias take it, otherwise use basename of service
|
|
96
104
|
// let alias = serviceCsn.alias || baseName(baseName(serviceCsn.name, '::'), '.');
|
|
97
105
|
// FIXME: UI5 cannot deal with spec conforming simpleid alias names
|
|
98
106
|
|
|
99
|
-
function markRendered(
|
|
107
|
+
function markRendered( def ) {
|
|
108
|
+
setProp(def, '$isRendered', true);
|
|
109
|
+
}
|
|
100
110
|
|
|
101
111
|
const service = new Edm.DataServices(v);
|
|
102
112
|
/** @type {object} */
|
|
@@ -137,26 +147,24 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
137
147
|
entries.
|
|
138
148
|
-----------------------------------------------*/
|
|
139
149
|
let LeadSchema;
|
|
140
|
-
const fqSchemaXRef = [serviceCsn.name];
|
|
141
|
-
const whatsMySchemaName =
|
|
142
|
-
return fqSchemaXRef.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? sn : rc, undefined);
|
|
143
|
-
}
|
|
150
|
+
const fqSchemaXRef = [ serviceCsn.name ];
|
|
151
|
+
const whatsMySchemaName = n => fqSchemaXRef.reduce((acc, sn) => (!acc && n && n.startsWith(`${sn}.`) ? sn : acc), undefined);
|
|
144
152
|
|
|
145
153
|
// tunnel schema xref and servicename in options to edm.Typebase to rectify
|
|
146
154
|
// type references that are eventually also prefixed with the service schema name.
|
|
147
155
|
options.serviceName = serviceCsn.name;
|
|
148
|
-
|
|
156
|
+
// List of all schema names in this service, including the service itself
|
|
149
157
|
options.whatsMySchemaName = whatsMySchemaName;
|
|
150
158
|
options.whatsMyServiceRootName = whatsMyServiceRootName;
|
|
151
159
|
|
|
152
160
|
let xServiceRefs = {};
|
|
153
161
|
const UsedTypes = {};
|
|
154
|
-
function collectUsedType(
|
|
155
|
-
if(typeName) {
|
|
156
|
-
if(UsedTypes[typeName])
|
|
157
|
-
UsedTypes[typeName].push(
|
|
162
|
+
function collectUsedType( def, typeName = (def.items?.type || def.type) ) {
|
|
163
|
+
if (typeName) {
|
|
164
|
+
if (UsedTypes[typeName])
|
|
165
|
+
UsedTypes[typeName].push(def);
|
|
158
166
|
else
|
|
159
|
-
UsedTypes[typeName] = [
|
|
167
|
+
UsedTypes[typeName] = [ def ];
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
|
|
@@ -167,34 +175,34 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
167
175
|
fqName: serviceCsn.name,
|
|
168
176
|
_csn: serviceCsn,
|
|
169
177
|
container: true,
|
|
170
|
-
definitions: Object.create(null)
|
|
171
|
-
}
|
|
178
|
+
definitions: Object.create(null),
|
|
179
|
+
},
|
|
172
180
|
};
|
|
173
181
|
|
|
174
|
-
if(options.isV4()) {
|
|
182
|
+
if (options.isV4()) {
|
|
175
183
|
// Add additional schema containers as sub contexts to the service
|
|
176
|
-
Object.entries(allSchemas).forEach(([fqName, art]) => {
|
|
177
|
-
if(serviceCsn.name === whatsMyServiceRootName(fqName) &&
|
|
178
|
-
fqName.startsWith(serviceCsn.name
|
|
179
|
-
if(art.kind === 'reference')
|
|
184
|
+
Object.entries(allSchemas).forEach(([ fqName, art ]) => {
|
|
185
|
+
if (serviceCsn.name === whatsMyServiceRootName(fqName) &&
|
|
186
|
+
fqName.startsWith(`${serviceCsn.name}.`)) {
|
|
187
|
+
if (art.kind === 'reference')
|
|
180
188
|
fqSchemaXRef.push(fqName);
|
|
181
|
-
if(art.kind === 'schema') {
|
|
189
|
+
if (art.kind === 'schema') {
|
|
182
190
|
fqSchemaXRef.push(fqName);
|
|
183
191
|
// Strip the toplevel service schema name (see comment above)
|
|
184
|
-
const name = fqName.replace(serviceCsn.name
|
|
192
|
+
const name = fqName.replace(`${serviceCsn.name}.`, '');
|
|
185
193
|
subSchemaDictionary[name] = {
|
|
186
194
|
name,
|
|
187
195
|
fqName,
|
|
188
196
|
_csn: art,
|
|
189
197
|
container: false,
|
|
190
|
-
definitions: Object.create(null)
|
|
198
|
+
definitions: Object.create(null),
|
|
191
199
|
};
|
|
192
200
|
}
|
|
193
201
|
}
|
|
194
202
|
}, subSchemaDictionary);
|
|
195
203
|
|
|
196
204
|
// Sort schema names in reverse order to allow longest match
|
|
197
|
-
fqSchemaXRef.sort((a,b) => b.length-a.length);
|
|
205
|
+
fqSchemaXRef.sort((a, b) => b.length - a.length);
|
|
198
206
|
|
|
199
207
|
// Fill the schemas and references, fqSchemaXRef must be complete
|
|
200
208
|
populateSchemas(subSchemaDictionary);
|
|
@@ -202,14 +210,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
202
210
|
|
|
203
211
|
// Bring the schemas in alphabetical order, service first, root last
|
|
204
212
|
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== fallBackSchemaName && n !== serviceCsn.name).sort();
|
|
205
|
-
if(subSchemaDictionary[fallBackSchemaName])
|
|
213
|
+
if (subSchemaDictionary[fallBackSchemaName])
|
|
206
214
|
sortedSchemaNames.push(fallBackSchemaName);
|
|
207
215
|
|
|
208
216
|
// Finally create the schemas and register them in the service.
|
|
209
217
|
LeadSchema = createSchema(subSchemaDictionary[serviceCsn.name]);
|
|
210
218
|
service.registerSchema(serviceCsn.name, LeadSchema);
|
|
211
219
|
|
|
212
|
-
sortedSchemaNames.forEach(name => {
|
|
220
|
+
sortedSchemaNames.forEach((name) => {
|
|
213
221
|
const schema = subSchemaDictionary[name];
|
|
214
222
|
service.registerSchema(schema.fqName, createSchema(schema));
|
|
215
223
|
});
|
|
@@ -223,30 +231,32 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
223
231
|
/*
|
|
224
232
|
EntityContainer duplicate check
|
|
225
233
|
*/
|
|
226
|
-
service._children.forEach(c => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
service._children.forEach((c) => {
|
|
235
|
+
if (c._ec) {
|
|
236
|
+
Object.entries(c._ec._registry).forEach((([ setName, arr ]) => {
|
|
237
|
+
if (arr.length > 1) {
|
|
238
|
+
error(null, null, {
|
|
239
|
+
name: c._edmAttributes.Namespace,
|
|
240
|
+
id: setName,
|
|
241
|
+
names: arr.map(a => a.getDuplicateMessage()),
|
|
242
|
+
}, 'Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for $(NAMES)');
|
|
243
|
+
}
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
236
246
|
});
|
|
237
247
|
// Create annotations and distribute into Schemas, merge vocabulary cross refs into xServiceRefs
|
|
238
|
-
|
|
248
|
+
addAnnotations2XServiceRefs();
|
|
239
249
|
|
|
240
250
|
// Finally add cross service references into the EDM and extract the targetSchemaNames
|
|
241
251
|
// for the type cross check
|
|
242
|
-
Object.values(xServiceRefs).forEach(ref => {
|
|
243
|
-
|
|
244
|
-
r.append(new Edm.Include(v, ref.inc))
|
|
252
|
+
Object.values(xServiceRefs).forEach((ref) => {
|
|
253
|
+
const r = new Edm.Reference(v, ref.ref);
|
|
254
|
+
r.append(new Edm.Include(v, ref.inc));
|
|
245
255
|
edm._defaultRefs.push(r);
|
|
246
256
|
});
|
|
247
257
|
|
|
248
|
-
for(
|
|
249
|
-
if(!isBuiltinType(typeName)) {
|
|
258
|
+
for (const typeName in UsedTypes) {
|
|
259
|
+
if (!isBuiltinType(typeName)) {
|
|
250
260
|
let iTypeName = typeName;
|
|
251
261
|
/*
|
|
252
262
|
Report type ref, if the type is
|
|
@@ -255,31 +265,30 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
255
265
|
- not a type clash (reported in type exposure),
|
|
256
266
|
- a @cds.external service member but can't be rendered
|
|
257
267
|
*/
|
|
258
|
-
if(!typeName.startsWith(serviceCsn.name
|
|
259
|
-
iTypeName = serviceCsn.name
|
|
268
|
+
if (!typeName.startsWith(`${serviceCsn.name}.`))
|
|
269
|
+
iTypeName = `${serviceCsn.name}.${typeName}`;
|
|
260
270
|
const def = reqDefs.definitions[iTypeName];
|
|
261
271
|
|
|
262
272
|
const usages = UsedTypes[typeName].filter(u => !u.$NameClashReported);
|
|
263
|
-
if(usages.length > 0 && def && !def.$isRendered && def['@cds.external']) {
|
|
273
|
+
if (usages.length > 0 && def && !def.$isRendered && def['@cds.external']) {
|
|
264
274
|
message('odata-spec-violation-type', usages[0].$location,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
);
|
|
275
|
+
{
|
|
276
|
+
type: typeName,
|
|
277
|
+
anno: '@cds.external',
|
|
278
|
+
name: serviceCsn.name,
|
|
279
|
+
code: def.elements ? 'Edm.ComplexType' : 'Edm.TypeDefinition',
|
|
280
|
+
version: options.isV4() ? '4.0' : '2.0',
|
|
281
|
+
'#': 'external',
|
|
282
|
+
});
|
|
274
283
|
}
|
|
275
284
|
}
|
|
276
285
|
}
|
|
277
286
|
|
|
278
|
-
return edm
|
|
287
|
+
return edm;
|
|
279
288
|
|
|
280
289
|
// Sort definitions into their schema container
|
|
281
|
-
function populateSchemas(schemas) {
|
|
282
|
-
Object.entries(reqDefs.definitions).forEach(([fqName, art]) => {
|
|
290
|
+
function populateSchemas( schemas ) {
|
|
291
|
+
Object.entries(reqDefs.definitions).forEach(([ fqName, art ]) => {
|
|
283
292
|
// Identify service members by their definition name only, this allows
|
|
284
293
|
// to let the internal object.name have the sub-schema name.
|
|
285
294
|
// With nested services we must do a longest path match and check whether
|
|
@@ -292,16 +301,16 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
292
301
|
// Add this definition to a (sub) schema, if it is not
|
|
293
302
|
// a container (context, service) and
|
|
294
303
|
// not marked to be ignored as schema member
|
|
295
|
-
if(mySchemaName &&
|
|
304
|
+
if (mySchemaName &&
|
|
296
305
|
serviceCsn.name === whatsMyServiceRootName(fqName, false) &&
|
|
297
306
|
art.kind !== 'context' && art.kind !== 'service') {
|
|
298
|
-
|
|
299
307
|
// Strip the toplevel serviceName from object.name
|
|
300
308
|
// except if the schema name is the service name itself.
|
|
301
309
|
// Proxy names are not prefixed, as they need to be reused.
|
|
302
|
-
if(mySchemaName !== serviceCsn.name) {
|
|
303
|
-
|
|
304
|
-
|
|
310
|
+
if (mySchemaName !== serviceCsn.name) {
|
|
311
|
+
art.name = fqName.replace(`${serviceCsn.name}.`, '');
|
|
312
|
+
fqName = art.name;
|
|
313
|
+
mySchemaName = mySchemaName.replace(`${serviceCsn.name}.`, '');
|
|
305
314
|
}
|
|
306
315
|
schemas[mySchemaName].definitions[fqName] = art;
|
|
307
316
|
}
|
|
@@ -332,30 +341,31 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
332
341
|
};
|
|
333
342
|
*/
|
|
334
343
|
|
|
335
|
-
return Object.entries(allSchemas).reduce((references, [fqName, art]) => {
|
|
344
|
+
return Object.entries(allSchemas).reduce((references, [ fqName, art ]) => {
|
|
336
345
|
// add references
|
|
337
|
-
if(art.kind === 'reference' &&
|
|
346
|
+
if (art.kind === 'reference' &&
|
|
338
347
|
whatsMySchemaName(fqName) &&
|
|
339
|
-
serviceCsn.name === whatsMyServiceRootName(fqName, false))
|
|
348
|
+
serviceCsn.name === whatsMyServiceRootName(fqName, false))
|
|
340
349
|
references[art.inc.Namespace] = art;
|
|
341
|
-
|
|
350
|
+
|
|
342
351
|
return references;
|
|
343
352
|
}, {});
|
|
344
353
|
}
|
|
345
354
|
|
|
346
355
|
// Main schema creator function
|
|
347
|
-
function createSchema(schema) {
|
|
356
|
+
function createSchema( schema ) {
|
|
348
357
|
/** @type {object} */
|
|
349
358
|
|
|
350
359
|
// Same check for alias (if supported by us)
|
|
351
|
-
const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
|
|
352
|
-
const loc = ['definitions', schema.name];
|
|
353
|
-
if(reservedNames.includes(schema.name))
|
|
360
|
+
const reservedNames = [ 'Edm', 'odata', 'System', 'Transient' ];
|
|
361
|
+
const loc = [ 'definitions', schema.name ];
|
|
362
|
+
if (reservedNames.includes(schema.name))
|
|
354
363
|
message('odata-spec-violation-namespace', loc, { names: reservedNames });
|
|
355
|
-
if (schema.name.length > 511)
|
|
364
|
+
if (schema.name.length > 511) {
|
|
356
365
|
message('odata-spec-violation-namespace', loc, { '#': 'length' });
|
|
366
|
+
}
|
|
357
367
|
else {
|
|
358
|
-
schema.name.split('.').forEach(id => {
|
|
368
|
+
schema.name.split('.').forEach((id) => {
|
|
359
369
|
if (!edmUtils.isODataSimpleIdentifier(id))
|
|
360
370
|
message('odata-spec-violation-id', loc, { id });
|
|
361
371
|
});
|
|
@@ -365,7 +375,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
365
375
|
const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
|
|
366
376
|
const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
|
|
367
377
|
// now namespace and alias are used to create the fullQualified(name)
|
|
368
|
-
const schemaNamePrefix = schema.name
|
|
378
|
+
const schemaNamePrefix = `${schema.name}.`;
|
|
369
379
|
const schemaAliasPrefix = schemaNamePrefix;
|
|
370
380
|
const schemaCsn = schema;
|
|
371
381
|
const navigationProperties = [];
|
|
@@ -376,49 +386,48 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
376
386
|
Entity starts with 'localserviceNameized.' or ends with '_localized'
|
|
377
387
|
*/
|
|
378
388
|
edmUtils.foreach(schemaCsn.definitions,
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
);
|
|
389
|
+
a => a.kind === 'entity' && !a.abstract && a.name.startsWith(schemaNamePrefix),
|
|
390
|
+
[ createEntityTypeAndSet, markRendered ]);
|
|
382
391
|
// create unbound actions/functions
|
|
383
392
|
edmUtils.foreach(schemaCsn.definitions,
|
|
384
|
-
|
|
385
|
-
|
|
393
|
+
a => (a.kind === 'action' || a.kind === 'function') && a.name.startsWith(schemaNamePrefix),
|
|
394
|
+
[ (options.isV4()) ? createActionV4 : createActionV2, markRendered ]);
|
|
386
395
|
|
|
387
|
-
|
|
396
|
+
// create the complex types
|
|
388
397
|
edmUtils.foreach(schemaCsn.definitions,
|
|
389
|
-
|
|
390
|
-
|
|
398
|
+
a => edmUtils.isStructuredType(a) && a.name.startsWith(schemaNamePrefix),
|
|
399
|
+
[ createComplexType, markRendered ]);
|
|
391
400
|
|
|
392
|
-
if(options.isV4())
|
|
393
|
-
{
|
|
401
|
+
if (options.isV4()) {
|
|
394
402
|
edmUtils.foreach(schemaCsn.definitions,
|
|
395
|
-
|
|
396
|
-
!artifact.target&&
|
|
403
|
+
artifact => edmUtils.isDerivedType(artifact) &&
|
|
404
|
+
!artifact.target &&
|
|
397
405
|
artifact.name.startsWith(schemaNamePrefix),
|
|
398
|
-
|
|
406
|
+
[ createTypeDefinitionV4, markRendered ]);
|
|
399
407
|
}
|
|
400
408
|
|
|
401
|
-
if(isBetaEnabled(options, 'odataTerms')) {
|
|
409
|
+
if (isBetaEnabled(options, 'odataTerms')) {
|
|
402
410
|
edmUtils.foreach(schemaCsn.definitions,
|
|
403
|
-
|
|
404
|
-
|
|
411
|
+
a => a.kind === 'annotation' && a.name.startsWith(schemaNamePrefix),
|
|
412
|
+
createTerm);
|
|
405
413
|
}
|
|
406
414
|
|
|
407
415
|
// fetch all existing children names in a map
|
|
408
416
|
const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
|
|
409
417
|
const name = cur._edmAttributes.Name;
|
|
410
|
-
if(acc[name] === undefined)
|
|
418
|
+
if (acc[name] === undefined)
|
|
411
419
|
acc[name] = [ cur ];
|
|
412
|
-
|
|
420
|
+
|
|
421
|
+
else
|
|
413
422
|
acc[name].push(cur);
|
|
414
|
-
|
|
423
|
+
|
|
415
424
|
return acc;
|
|
416
425
|
}, Object.create(null) );
|
|
417
426
|
|
|
418
|
-
navigationProperties.forEach(np => {
|
|
419
|
-
if(options.isV4()) {
|
|
427
|
+
navigationProperties.forEach((np) => {
|
|
428
|
+
if (options.isV4()) {
|
|
420
429
|
// V4: No referential constraints for Containment Relationships
|
|
421
|
-
if((!np.isContainment() || (options.renderForeignKeys)) && !np.isToMany())
|
|
430
|
+
if ((!np.isContainment() || (options.renderForeignKeys)) && !np.isToMany())
|
|
422
431
|
np.addReferentialConstraintNodes();
|
|
423
432
|
}
|
|
424
433
|
else {
|
|
@@ -440,122 +449,126 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
440
449
|
This sentence expresses that an OData SERVICE must contain an entity container, but an EDMX is not required to have a container.
|
|
441
450
|
Therefore it is absolutely legal and necessary to remove an empty container from the IR!
|
|
442
451
|
*/
|
|
443
|
-
if(Schema._ec && Schema._ec._children.length === 0)
|
|
452
|
+
if (Schema._ec && Schema._ec._children.length === 0)
|
|
444
453
|
Schema._children.splice(Schema._children.indexOf(Schema._ec), 1);
|
|
445
|
-
}
|
|
446
454
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
'
|
|
455
|
+
|
|
456
|
+
Object.entries(NamesInSchemaXRef).forEach(([ name, refs ]) => {
|
|
457
|
+
if (refs.length > 1) {
|
|
458
|
+
error(null, [ 'definitions', `${Schema._edmAttributes.Namespace}.${name}` ], { name: Schema._edmAttributes.Namespace },
|
|
459
|
+
'Duplicate name in Schema $(NAME)');
|
|
451
460
|
}
|
|
452
461
|
});
|
|
453
462
|
|
|
454
463
|
return Schema;
|
|
455
464
|
|
|
456
|
-
function createEntityTypeAndSet(entityCsn)
|
|
457
|
-
{
|
|
465
|
+
function createEntityTypeAndSet( entityCsn ) {
|
|
458
466
|
const EntityTypeName = entityCsn.name.replace(schemaNamePrefix, '');
|
|
459
467
|
const EntitySetName = edmUtils.getBaseName(entityCsn.$entitySetName || entityCsn.name);
|
|
460
468
|
const isSingleton = edmUtils.isSingleton(entityCsn) && options.isV4();
|
|
461
469
|
const [ properties, hasStream ] = createProperties(entityCsn);
|
|
462
470
|
|
|
463
|
-
const
|
|
471
|
+
const location = [ 'definitions', entityCsn.name ];
|
|
464
472
|
const type = `${schema.name}.${EntityTypeName}`;
|
|
465
|
-
if(properties.length === 0)
|
|
466
|
-
warning(null,
|
|
467
|
-
else if(entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
|
|
468
|
-
message('odata-spec-violation-no-key',
|
|
473
|
+
if (properties.length === 0)
|
|
474
|
+
warning(null, location, { type }, 'EDM EntityType $(TYPE) has no properties');
|
|
475
|
+
else if (entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
|
|
476
|
+
message('odata-spec-violation-no-key', location);
|
|
469
477
|
|
|
470
|
-
if(!edmUtils.isODataSimpleIdentifier(EntityTypeName))
|
|
471
|
-
message('odata-spec-violation-id',
|
|
478
|
+
if (!edmUtils.isODataSimpleIdentifier(EntityTypeName))
|
|
479
|
+
message('odata-spec-violation-id', location, { id: EntityTypeName });
|
|
472
480
|
|
|
473
|
-
properties.forEach(p => {
|
|
474
|
-
const pLoc = [...
|
|
481
|
+
properties.forEach((p) => {
|
|
482
|
+
const pLoc = [ ...location, 'elements', p._edmAttributes.Name ];
|
|
475
483
|
edmTypeCompatibilityCheck(p, pLoc);
|
|
476
|
-
if(p._edmAttributes.Name === EntityTypeName)
|
|
484
|
+
if (p._edmAttributes.Name === EntityTypeName)
|
|
477
485
|
message('odata-spec-violation-property-name', pLoc, { meta: entityCsn.kind });
|
|
478
486
|
|
|
479
|
-
if(options.isV2() && p._isCollection && !p._csn.target)
|
|
487
|
+
if (options.isV2() && p._isCollection && !p._csn.target)
|
|
480
488
|
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
481
489
|
|
|
482
|
-
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
490
|
+
if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name)) {
|
|
483
491
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
492
|
+
}
|
|
484
493
|
else if (options.isV2() && /^(_|\d)/.test(p._edmAttributes.Name)) {
|
|
485
494
|
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
486
495
|
message('odata-spec-violation-id', pLoc,
|
|
487
|
-
|
|
496
|
+
{
|
|
497
|
+
prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar',
|
|
498
|
+
});
|
|
488
499
|
}
|
|
489
500
|
});
|
|
490
501
|
|
|
491
502
|
// construct EntityType attributes
|
|
492
|
-
const attributes = { Name
|
|
503
|
+
const attributes = { Name: EntityTypeName };
|
|
493
504
|
|
|
494
505
|
// CDXCORE-CDXCORE-173
|
|
495
|
-
if(options.isV2() && hasStream) {
|
|
506
|
+
if (options.isV2() && hasStream) {
|
|
496
507
|
attributes['m:HasStream'] = true;
|
|
497
508
|
edmUtils.assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
|
|
498
509
|
}
|
|
499
510
|
|
|
500
511
|
Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));
|
|
501
512
|
|
|
502
|
-
if (EntityContainer && entityCsn.$hasEntitySet)
|
|
503
|
-
{
|
|
513
|
+
if (EntityContainer && entityCsn.$hasEntitySet) {
|
|
504
514
|
/** @type {object} */
|
|
505
515
|
let containerEntry;
|
|
506
516
|
|
|
507
|
-
if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
|
|
517
|
+
if (edmUtils.isSingleton(entityCsn) && options.isV4()) {
|
|
508
518
|
containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
|
|
509
|
-
if(entityCsn['@odata.singleton.nullable'])
|
|
510
|
-
containerEntry._edmAttributes.Nullable= true;
|
|
519
|
+
if (entityCsn['@odata.singleton.nullable'])
|
|
520
|
+
containerEntry._edmAttributes.Nullable = true;
|
|
511
521
|
}
|
|
512
522
|
else {
|
|
513
523
|
containerEntry = new Edm.EntitySet(v, { Name: EntitySetName, EntityType: fullQualified(EntityTypeName) }, entityCsn);
|
|
514
524
|
}
|
|
515
525
|
|
|
516
526
|
// V4: Create NavigationPropertyBinding in EntitySet
|
|
517
|
-
if(options.isV4()) {
|
|
518
|
-
entityCsn.$edmNPBs.forEach(npb => {
|
|
519
|
-
containerEntry.append(new Edm.NavigationPropertyBinding(v, npb))
|
|
527
|
+
if (options.isV4()) {
|
|
528
|
+
entityCsn.$edmNPBs.forEach((npb) => {
|
|
529
|
+
containerEntry.append(new Edm.NavigationPropertyBinding(v, npb));
|
|
520
530
|
});
|
|
521
531
|
}
|
|
522
532
|
EntityContainer.register(containerEntry);
|
|
523
533
|
}
|
|
524
534
|
|
|
525
535
|
// put actions behind entity types in Schema/EntityContainer
|
|
526
|
-
|
|
527
|
-
(
|
|
528
|
-
|
|
529
|
-
|
|
536
|
+
if (entityCsn.actions) {
|
|
537
|
+
Object.entries(entityCsn.actions).forEach(([ n, a ]) => {
|
|
538
|
+
if (options.isV4())
|
|
539
|
+
createActionV4(a, n, entityCsn);
|
|
540
|
+
else
|
|
541
|
+
createActionV2(a, n, entityCsn);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
530
544
|
}
|
|
531
545
|
|
|
532
|
-
function createComplexType(structuredTypeCsn)
|
|
533
|
-
{
|
|
546
|
+
function createComplexType( structuredTypeCsn ) {
|
|
534
547
|
// V4 attributes: Name, BaseType, Abstract, OpenType
|
|
535
548
|
const attributes = { Name: structuredTypeCsn.name.replace(schemaNamePrefix, '') };
|
|
536
549
|
|
|
537
550
|
const complexType = new Edm.ComplexType(v, attributes, structuredTypeCsn);
|
|
538
551
|
const elementsCsn = structuredTypeCsn.items || structuredTypeCsn;
|
|
539
552
|
const properties = createProperties(elementsCsn, structuredTypeCsn)[0];
|
|
540
|
-
const
|
|
553
|
+
const location = [ 'definitions', structuredTypeCsn.name ];
|
|
541
554
|
|
|
542
|
-
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
543
|
-
message('odata-spec-violation-id',
|
|
555
|
+
if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
556
|
+
message('odata-spec-violation-id', location, { id: attributes.Name });
|
|
544
557
|
|
|
545
|
-
properties.forEach(p => {
|
|
546
|
-
const pLoc = [ ...
|
|
558
|
+
properties.forEach((p) => {
|
|
559
|
+
const pLoc = [ ...location, ...(structuredTypeCsn.items ? [ 'items', 'elements' ] : [ 'elements' ]), p._edmAttributes.Name ];
|
|
547
560
|
edmTypeCompatibilityCheck(p, pLoc);
|
|
548
|
-
if(p._edmAttributes.Name === complexType._edmAttributes.Name)
|
|
561
|
+
if (p._edmAttributes.Name === complexType._edmAttributes.Name)
|
|
549
562
|
message('odata-spec-violation-property-name', pLoc, { meta: structuredTypeCsn.kind });
|
|
550
563
|
|
|
551
|
-
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
564
|
+
if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
552
565
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
553
566
|
|
|
554
|
-
if(options.isV2()) {
|
|
555
|
-
if(p._isCollection && !p._csn.target)
|
|
567
|
+
if (options.isV2()) {
|
|
568
|
+
if (p._isCollection && !p._csn.target)
|
|
556
569
|
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
557
570
|
|
|
558
|
-
if(p._csn.target)
|
|
571
|
+
if (p._csn.target)
|
|
559
572
|
message('odata-spec-violation-assoc', pLoc, { version: '2.0' });
|
|
560
573
|
}
|
|
561
574
|
});
|
|
@@ -573,86 +586,82 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
573
586
|
* array of Edm Properties
|
|
574
587
|
* hasStream : value of @Core.MediaType assignment
|
|
575
588
|
*/
|
|
576
|
-
function createProperties(elementsCsn, edmParentCsn=elementsCsn)
|
|
577
|
-
{
|
|
589
|
+
function createProperties( elementsCsn, edmParentCsn = elementsCsn ) {
|
|
578
590
|
const props = [];
|
|
579
591
|
let hasStream = false;
|
|
580
592
|
const streamProps = [];
|
|
581
593
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
594
|
+
if (elementsCsn.elements) {
|
|
595
|
+
Object.entries(elementsCsn.elements).forEach(([ elementName, elementCsn ]) => {
|
|
596
|
+
if (!elementCsn._edmParentCsn)
|
|
597
|
+
setProp(elementCsn, '_edmParentCsn', edmParentCsn);
|
|
586
598
|
|
|
587
|
-
|
|
588
|
-
|
|
599
|
+
if (elementCsn.target) {
|
|
600
|
+
// Foreign keys are part of the generic elementCsn.elements property creation
|
|
589
601
|
|
|
590
602
|
// This is the V4 edmx:NavigationProperty
|
|
591
603
|
// gets rewritten for V2 in addAssociations()
|
|
592
604
|
|
|
593
605
|
// suppress navprop creation only if @odata.navigable:false is not annotated.
|
|
594
606
|
// (undefined !== false) still evaluates to true
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
props.push(navProp);
|
|
607
|
+
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false) {
|
|
608
|
+
const navProp = new Edm.NavigationProperty(v, {
|
|
609
|
+
Name: elementName,
|
|
610
|
+
Type: elementCsn._target.name,
|
|
611
|
+
}, elementCsn);
|
|
612
|
+
collectUsedType(elementCsn, elementCsn._target.name);
|
|
613
|
+
props.push(navProp);
|
|
603
614
|
// save the navProp in the global array for late constraint building
|
|
604
|
-
|
|
615
|
+
navigationProperties.push(navProp);
|
|
616
|
+
}
|
|
605
617
|
}
|
|
606
|
-
}
|
|
607
618
|
// render ordinary property if element is NOT ...
|
|
608
619
|
// 1) ... annotated @cds.api.ignore
|
|
609
620
|
// 2) ... annotated @odata.foreignKey4 and odataFormat: structured
|
|
610
621
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
delete elementCsn['@Core.MediaType'];
|
|
622
|
+
else if (isEdmPropertyRendered(elementCsn, options)) {
|
|
623
|
+
// CDXCORE-CDXCORE-173
|
|
624
|
+
// V2: filter @Core.MediaType
|
|
625
|
+
if ( options.isV2() && elementCsn['@Core.MediaType']) {
|
|
626
|
+
hasStream = elementCsn['@Core.MediaType'];
|
|
627
|
+
delete elementCsn['@Core.MediaType'];
|
|
618
628
|
// CDXCORE-CDXCORE-177:
|
|
619
629
|
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType
|
|
620
630
|
// V4: render property type 'Edm.Stream'
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
631
|
+
streamProps.push(elementName);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
collectUsedType(elementCsn);
|
|
635
|
+
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
|
|
636
|
+
}
|
|
626
637
|
}
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (options.isV2()) {
|
|
641
|
+
if (streamProps.length > 1) { // TODO: why not mention 2.0 in text?
|
|
642
|
+
error(null, [ 'definitions', elementsCsn.name ], { names: streamProps, version: '2.0', anno: '@Core.MediaType' },
|
|
643
|
+
'Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)');
|
|
627
644
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if(streamProps.length > 1) { // TODO: why not mention 2.0 in text?
|
|
632
|
-
error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' },
|
|
633
|
-
'Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)');
|
|
634
|
-
}
|
|
635
|
-
else if(streamProps.length === 1) {
|
|
636
|
-
info(null, ['definitions', elementsCsn.name], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' },
|
|
637
|
-
'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)');
|
|
645
|
+
else if (streamProps.length === 1) {
|
|
646
|
+
info(null, [ 'definitions', elementsCsn.name ], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' },
|
|
647
|
+
'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)');
|
|
638
648
|
}
|
|
639
649
|
}
|
|
640
650
|
return [ props, hasStream ];
|
|
641
651
|
}
|
|
642
652
|
|
|
643
|
-
function createTerm(termCsn) {
|
|
653
|
+
function createTerm( termCsn ) {
|
|
644
654
|
const attributes = { Name: termCsn.name.replace(schemaNamePrefix, '') };
|
|
645
655
|
const term = new Edm.Term(v, attributes, termCsn);
|
|
646
656
|
Schema.append(term);
|
|
647
657
|
}
|
|
648
658
|
|
|
649
659
|
// V4 <TypeDefintion>
|
|
650
|
-
function createTypeDefinitionV4(typeCsn)
|
|
651
|
-
{
|
|
660
|
+
function createTypeDefinitionV4( typeCsn ) {
|
|
652
661
|
// derived types are already resolved to base types
|
|
653
662
|
const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
|
|
654
|
-
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
655
|
-
message('odata-spec-violation-id', ['definitions', typeCsn.name], { id: attributes.Name });
|
|
663
|
+
if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
664
|
+
message('odata-spec-violation-id', [ 'definitions', typeCsn.name ], { id: attributes.Name });
|
|
656
665
|
|
|
657
666
|
const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn );
|
|
658
667
|
edmTypeCompatibilityCheck(typeDef, [ 'definitions', typeCsn.name ]);
|
|
@@ -660,25 +669,24 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
660
669
|
}
|
|
661
670
|
|
|
662
671
|
// add bound/unbound actions/functions for V4
|
|
663
|
-
function createActionV4(actionCsn, _name, entityCsn=undefined)
|
|
664
|
-
{
|
|
672
|
+
function createActionV4( actionCsn, _name, entityCsn = undefined ) {
|
|
665
673
|
const iAmAnAction = actionCsn.kind === 'action';
|
|
666
674
|
const actionName = edmUtils.getBaseName(actionCsn.name);
|
|
667
|
-
const attributes = { Name: actionName, IsBound
|
|
675
|
+
const attributes = { Name: actionName, IsBound: false };
|
|
668
676
|
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
677
|
+
const location = entityCsn
|
|
678
|
+
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
|
|
679
|
+
: [ 'definitions', actionCsn.name ];
|
|
672
680
|
|
|
673
|
-
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
674
|
-
message('odata-spec-violation-id',
|
|
681
|
+
if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
682
|
+
message('odata-spec-violation-id', location, { id: attributes.Name });
|
|
675
683
|
|
|
676
|
-
if(!iAmAnAction)
|
|
684
|
+
if (!iAmAnAction)
|
|
677
685
|
attributes.IsComposable = false;
|
|
678
686
|
|
|
679
687
|
/** @type {object} */
|
|
680
688
|
const actionNode = (iAmAnAction) ? new Edm.Action(v, attributes)
|
|
681
|
-
|
|
689
|
+
: new Edm.FunctionDefinition(v, attributes);
|
|
682
690
|
|
|
683
691
|
const bpType = entityCsn ? fullQualified(entityCsn.name) : undefined;
|
|
684
692
|
/*
|
|
@@ -689,97 +697,91 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
689
697
|
*/
|
|
690
698
|
|
|
691
699
|
let bpName = 'in';
|
|
692
|
-
if(actionCsn.params) {
|
|
693
|
-
const entries =
|
|
700
|
+
if (actionCsn.params) {
|
|
701
|
+
const entries = Object.entries(actionCsn.params);
|
|
694
702
|
const firstParam = entries[0][1];
|
|
695
703
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
696
|
-
if(type === special$self) {
|
|
704
|
+
if (type === special$self) {
|
|
697
705
|
bpName = entries[0][0];
|
|
698
706
|
setProp(actionCsn, '$bindingParam', firstParam);
|
|
699
|
-
if(bpType) {
|
|
700
|
-
if(firstParam.items?.type)
|
|
707
|
+
if (bpType) {
|
|
708
|
+
if (firstParam.items?.type)
|
|
701
709
|
firstParam.items.type = bpType;
|
|
702
|
-
if(firstParam.type)
|
|
710
|
+
if (firstParam.type)
|
|
703
711
|
firstParam.type = bpType;
|
|
704
712
|
}
|
|
705
|
-
if(!edmUtils.isODataSimpleIdentifier(bpName))
|
|
706
|
-
message('odata-spec-violation-id', [ ...
|
|
713
|
+
if (!edmUtils.isODataSimpleIdentifier(bpName))
|
|
714
|
+
message('odata-spec-violation-id', [ ...location, 'params', bpName ], { id: bpName });
|
|
707
715
|
}
|
|
708
716
|
}
|
|
709
717
|
|
|
710
718
|
// bpName is eventually used later for EntitySetPath
|
|
711
719
|
// No explicit binding parameter, check (user defined) annotation value)
|
|
712
|
-
if(!actionCsn.$bindingParam) {
|
|
720
|
+
if (!actionCsn.$bindingParam) {
|
|
713
721
|
const bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
|
|
714
|
-
if(bpNameAnno != null) {
|
|
715
|
-
if(typeof bpNameAnno === 'string')
|
|
722
|
+
if (bpNameAnno != null) {
|
|
723
|
+
if (typeof bpNameAnno === 'string')
|
|
716
724
|
bpName = bpNameAnno;
|
|
717
|
-
if(typeof bpNameAnno === 'object' && bpNameAnno['='])
|
|
725
|
+
if (typeof bpNameAnno === 'object' && bpNameAnno['='])
|
|
718
726
|
bpName = bpNameAnno['='];
|
|
719
727
|
}
|
|
720
|
-
if(!edmUtils.isODataSimpleIdentifier(bpName))
|
|
721
|
-
message('odata-spec-violation-id', [...
|
|
722
|
-
if(actionCsn.params && actionCsn.params[bpName])
|
|
723
|
-
error('duplicate-definition', [...
|
|
724
|
-
}
|
|
728
|
+
if (!edmUtils.isODataSimpleIdentifier(bpName))
|
|
729
|
+
message('odata-spec-violation-id', [ ...location, '@cds.odata.bindingparameter.name' ], { id: bpName });
|
|
730
|
+
if (actionCsn.params && actionCsn.params[bpName])
|
|
731
|
+
error('duplicate-definition', [ ...location, '@cds.odata.bindingparameter.name' ], { '#': 'param', name: bpName });
|
|
725
732
|
}
|
|
726
|
-
if(entityCsn
|
|
727
|
-
{
|
|
733
|
+
if (entityCsn) {
|
|
728
734
|
actionNode.setEdmAttribute('IsBound', true);
|
|
729
|
-
if(!actionCsn.$bindingParam) {
|
|
735
|
+
if (!actionCsn.$bindingParam) {
|
|
730
736
|
// Binding Parameter: 'in' at first position in sequence, this is decisive!
|
|
731
|
-
if(actionCsn['@cds.odata.bindingparameter.collection'])
|
|
732
|
-
actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType, Collection:true
|
|
737
|
+
if (actionCsn['@cds.odata.bindingparameter.collection'])
|
|
738
|
+
actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType, Collection: true/* , Nullable: false */ } ));
|
|
733
739
|
else
|
|
734
|
-
|
|
740
|
+
actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType } ));
|
|
735
741
|
}
|
|
736
742
|
}
|
|
737
|
-
else if(EntityContainer)// unbound => produce Action/FunctionImport
|
|
738
|
-
{
|
|
743
|
+
else if (EntityContainer) { // unbound => produce Action/FunctionImport
|
|
739
744
|
/** @type {object} */
|
|
740
745
|
const actionImport = iAmAnAction
|
|
741
|
-
? new Edm.ActionImport(v, { Name: actionName, Action
|
|
742
|
-
: new Edm.FunctionImport(v, { Name: actionName, Function
|
|
746
|
+
? new Edm.ActionImport(v, { Name: actionName, Action: fullQualified(actionName) })
|
|
747
|
+
: new Edm.FunctionImport(v, { Name: actionName, Function: fullQualified(actionName) });
|
|
743
748
|
|
|
744
749
|
const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
|
|
745
|
-
if(rt) // add EntitySet attribute only if return type is a non abstract entity
|
|
746
|
-
{
|
|
750
|
+
if (rt) { // add EntitySet attribute only if return type is a non abstract entity
|
|
747
751
|
const definition = schemaCsn.definitions[rt];
|
|
748
|
-
if(definition && definition.kind === 'entity' && !definition.abstract)
|
|
749
|
-
{
|
|
752
|
+
if (definition && definition.kind === 'entity' && !definition.abstract)
|
|
750
753
|
actionImport.setEdmAttribute('EntitySet', edmUtils.getBaseName(rt));
|
|
751
|
-
}
|
|
752
754
|
}
|
|
753
755
|
EntityContainer.register(actionImport);
|
|
754
756
|
}
|
|
755
757
|
|
|
756
758
|
// Parameter Nodes
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
759
|
+
if (actionCsn.params) {
|
|
760
|
+
Object.entries(actionCsn.params).forEach(([ parameterName, parameterCsn ]) => {
|
|
761
|
+
const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
|
|
762
|
+
const pLoc = [ ...location, 'params', p._edmAttributes.Name ];
|
|
763
|
+
if (!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
764
|
+
message('odata-spec-violation-id', pLoc, { id: parameterName });
|
|
765
|
+
collectUsedType(parameterCsn);
|
|
766
|
+
edmTypeCompatibilityCheck(p, pLoc);
|
|
767
|
+
actionNode.append(p);
|
|
768
|
+
});
|
|
769
|
+
}
|
|
766
770
|
|
|
767
771
|
// return type if any
|
|
768
|
-
if(actionCsn.returns) {
|
|
772
|
+
if (actionCsn.returns) {
|
|
769
773
|
actionNode._returnType = new Edm.ReturnType(v, actionCsn.returns);
|
|
770
774
|
collectUsedType(actionCsn.returns);
|
|
771
|
-
edmTypeCompatibilityCheck(actionNode._returnType, [ ...
|
|
775
|
+
edmTypeCompatibilityCheck(actionNode._returnType, [ ...location, 'returns' ]);
|
|
772
776
|
// if binding type matches return type add attribute EntitySetPath
|
|
773
|
-
if(entityCsn
|
|
777
|
+
if (entityCsn && fullQualified(entityCsn.name) === actionNode._returnType._type)
|
|
774
778
|
actionNode.setEdmAttribute('EntitySetPath', bpName);
|
|
775
|
-
}
|
|
776
779
|
}
|
|
777
780
|
Schema.addAction(actionNode);
|
|
778
781
|
}
|
|
779
782
|
|
|
780
783
|
// add bound/unbound actions/functions for V2
|
|
781
|
-
function createActionV2(actionCsn, name, entityCsn=undefined)
|
|
782
|
-
{
|
|
784
|
+
function createActionV2( actionCsn, name, entityCsn = undefined ) {
|
|
783
785
|
/** @type {object} */
|
|
784
786
|
const attributes = { Name: name.replace(schemaNamePrefix, '') };
|
|
785
787
|
const functionImport = new Edm.FunctionImport(v, attributes );
|
|
@@ -795,117 +797,113 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
795
797
|
in the spec and advised mention it as in V4
|
|
796
798
|
*/
|
|
797
799
|
|
|
798
|
-
const
|
|
800
|
+
const location = entityCsn
|
|
799
801
|
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
|
|
800
802
|
: [ 'definitions', actionCsn.name ];
|
|
801
803
|
|
|
802
|
-
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
803
|
-
message('odata-spec-violation-id',
|
|
804
|
+
if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
805
|
+
message('odata-spec-violation-id', location, { id: attributes.Name });
|
|
804
806
|
|
|
805
807
|
const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
|
|
806
|
-
if(rt) // add EntitySet attribute only if return type is an entity
|
|
807
|
-
{
|
|
808
|
+
if (rt) { // add EntitySet attribute only if return type is an entity
|
|
808
809
|
const definition = schemaCsn.definitions[rt];
|
|
809
|
-
if(definition && definition.kind === 'entity')
|
|
810
|
-
{
|
|
810
|
+
if (definition && definition.kind === 'entity')
|
|
811
811
|
functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
|
|
812
|
-
}
|
|
813
812
|
}
|
|
814
813
|
|
|
815
|
-
if(actionCsn.returns)
|
|
814
|
+
if (actionCsn.returns)
|
|
816
815
|
functionImport.setEdmAttribute('ReturnType', getReturnType(actionCsn));
|
|
817
816
|
|
|
818
|
-
if(actionCsn.kind === 'function')
|
|
819
|
-
functionImport.setXml( {'m:HttpMethod': 'GET' });
|
|
820
|
-
else if(actionCsn.kind === 'action')
|
|
821
|
-
functionImport.setXml( {'m:HttpMethod': 'POST'});
|
|
817
|
+
if (actionCsn.kind === 'function')
|
|
818
|
+
functionImport.setXml( { 'm:HttpMethod': 'GET' });
|
|
819
|
+
else if (actionCsn.kind === 'action')
|
|
820
|
+
functionImport.setXml( { 'm:HttpMethod': 'POST' });
|
|
822
821
|
|
|
823
|
-
if(entityCsn
|
|
824
|
-
{
|
|
822
|
+
if (entityCsn) {
|
|
825
823
|
// Make bound function names always unique as per Ralf's recommendation
|
|
826
|
-
functionImport.setXml( {'sap:action-for':
|
|
827
|
-
const
|
|
828
|
-
functionImport.setEdmAttribute('Name',
|
|
824
|
+
functionImport.setXml( { 'sap:action-for': fullQualified(entityCsn.name) } );
|
|
825
|
+
const entityName = `${entityCsn.name.replace(schemaNamePrefix, '')}_${functionImport._edmAttributes.Name}`;
|
|
826
|
+
functionImport.setEdmAttribute('Name', entityName);
|
|
829
827
|
|
|
830
828
|
// Binding Parameter: Primary Keys at first position in sequence, this is decisive!
|
|
831
829
|
// V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
|
|
832
830
|
edmUtils.foreach(entityCsn.elements,
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
);
|
|
831
|
+
elementCsn => elementCsn.key && !elementCsn.target,
|
|
832
|
+
(elementCsn, elementName) => {
|
|
833
|
+
functionImport.append(new Edm.Parameter(v, { Name: elementName }, elementCsn, 'In' ));
|
|
834
|
+
});
|
|
838
835
|
}
|
|
839
836
|
|
|
840
837
|
// is this still required?
|
|
841
|
-
Object.entries(actionCsn).forEach(([
|
|
842
|
-
if (
|
|
843
|
-
functionImport.setXml( { [
|
|
838
|
+
Object.entries(actionCsn).forEach(([ key, val ]) => {
|
|
839
|
+
if (key.match(/^@sap\./))
|
|
840
|
+
functionImport.setXml( { [`sap:${key.slice(5).replace(/\./g, '-')}`]: val });
|
|
844
841
|
});
|
|
845
842
|
// then append all other parameters
|
|
846
843
|
// V2 XML: Parameters that are not explicitly marked as Nullable or NotNullable in the CSN must become Nullable=true
|
|
847
844
|
// V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
|
|
848
845
|
// the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
846
|
+
if (actionCsn.params) {
|
|
847
|
+
Object.entries(actionCsn.params).forEach(([ parameterName, parameterCsn ], i) => {
|
|
848
|
+
const type = parameterCsn?.items?.type || parameterCsn?.type;
|
|
849
|
+
if (i === 0 && type === special$self) {
|
|
852
850
|
// skip and remove the first parameter if it is a $self binding parameter to
|
|
853
851
|
// omit annotation rendering later on
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
852
|
+
delete actionCsn.params[parameterName];
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
const pLoc = [ ...location, 'params', parameterName ];
|
|
856
|
+
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
|
|
857
|
+
collectUsedType(parameterCsn);
|
|
858
|
+
edmTypeCompatibilityCheck(param, pLoc);
|
|
859
|
+
if (!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
860
|
+
message('odata-spec-violation-id', pLoc, { id: parameterName });
|
|
861
|
+
|
|
862
|
+
// only scalar or structured type in V2 (not entity)
|
|
863
|
+
if (param._type &&
|
|
866
864
|
!param._type.startsWith('Edm.') &&
|
|
867
865
|
csn.definitions[param._type] &&
|
|
868
866
|
!edmUtils.isStructuredType(csn.definitions[param._type]))
|
|
869
|
-
|
|
867
|
+
message('odata-spec-violation-param', pLoc, { version: '2.0' });
|
|
870
868
|
|
|
871
|
-
|
|
872
|
-
|
|
869
|
+
if (param._isCollection)
|
|
870
|
+
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
873
871
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
872
|
+
functionImport.append(param);
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
877
876
|
|
|
878
|
-
if(EntityContainer)
|
|
877
|
+
if (EntityContainer)
|
|
879
878
|
EntityContainer.register(functionImport);
|
|
880
879
|
|
|
881
|
-
function getReturnType(action)
|
|
882
|
-
{
|
|
880
|
+
function getReturnType( action ) {
|
|
883
881
|
// it is safe to assume that either type or items.type are set
|
|
884
|
-
const returnsLoc = [ ...
|
|
882
|
+
const returnsLoc = [ ...location, 'returns' ];
|
|
885
883
|
const returns = action.returns.items || action.returns;
|
|
886
884
|
let type = returns['@odata.Type'];
|
|
887
|
-
if(!type) {
|
|
885
|
+
if (!type) {
|
|
888
886
|
type = returns.type;
|
|
889
887
|
if (type) {
|
|
890
888
|
collectUsedType(action.returns);
|
|
891
889
|
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
|
|
892
890
|
message('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
|
|
893
891
|
}
|
|
894
|
-
else if(isBuiltinType(type)) {
|
|
892
|
+
else if (isBuiltinType(type)) {
|
|
895
893
|
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
|
|
896
|
-
if(type) {
|
|
894
|
+
if (type) {
|
|
897
895
|
const td = EdmPrimitiveTypeMap[type];
|
|
898
|
-
if(td && !td.v2) {
|
|
896
|
+
if (td && !td.v2) {
|
|
899
897
|
message('odata-spec-violation-type', returnsLoc,
|
|
900
|
-
|
|
898
|
+
{ type, version: '2.0', '#': 'incompatible' });
|
|
901
899
|
}
|
|
902
900
|
}
|
|
903
901
|
else {
|
|
904
902
|
message('odata-spec-violation-type-unknown', returnsLoc, { type });
|
|
905
903
|
}
|
|
906
904
|
}
|
|
907
|
-
if(action.returns._isCollection)
|
|
908
|
-
type = `Collection(${type})
|
|
905
|
+
if (action.returns._isCollection)
|
|
906
|
+
type = `Collection(${type})`;
|
|
909
907
|
}
|
|
910
908
|
else {
|
|
911
909
|
// type is missing
|
|
@@ -930,16 +928,15 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
930
928
|
add <End>, <ReferentialConstraint>, <Dependent> and <Principal> sub elements to <Association>
|
|
931
929
|
add <AssociationSet> to the EntityContainer for each <Association>
|
|
932
930
|
*/
|
|
933
|
-
function addAssociationV2(navigationProperty)
|
|
934
|
-
{
|
|
931
|
+
function addAssociationV2( navigationProperty ) {
|
|
935
932
|
let constraints = navigationProperty._csn._constraints;
|
|
936
933
|
let parentName = navigationProperty._csn._edmParentCsn.name.replace(schemaNamePrefix, '');
|
|
937
934
|
let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty._edmAttributes.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
|
|
938
935
|
let assocName = plainAssocName;
|
|
939
936
|
let i = 1;
|
|
940
|
-
while(NamesInSchemaXRef[assocName] !== undefined)
|
|
941
|
-
assocName = plainAssocName
|
|
942
|
-
|
|
937
|
+
while (NamesInSchemaXRef[assocName] !== undefined)
|
|
938
|
+
assocName = `${plainAssocName}_${i++}`;
|
|
939
|
+
|
|
943
940
|
|
|
944
941
|
let fromRole = parentName;
|
|
945
942
|
let toRole = navigationProperty._edmAttributes.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
|
|
@@ -954,8 +951,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
954
951
|
|
|
955
952
|
// from and to roles must be distinguishable (in case of self association entity E { toE: association to E; ... })
|
|
956
953
|
|
|
957
|
-
if(fromRole === toRole) {
|
|
958
|
-
if(constraints._partnerCsn)
|
|
954
|
+
if (fromRole === toRole) {
|
|
955
|
+
if (constraints._partnerCsn)
|
|
959
956
|
fromRole += '1';
|
|
960
957
|
else
|
|
961
958
|
toRole += '1';
|
|
@@ -980,20 +977,20 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
980
977
|
*/
|
|
981
978
|
|
|
982
979
|
let reuseAssoc = false;
|
|
983
|
-
|
|
984
|
-
if(forwardAssocCsn)
|
|
985
|
-
{
|
|
980
|
+
const forwardAssocCsn = constraints._partnerCsn;
|
|
981
|
+
if (forwardAssocCsn) {
|
|
986
982
|
// This is a backlink, swap the roles and types, rewrite assocName
|
|
987
983
|
[ fromRole, toRole ] = [ toRole, fromRole ];
|
|
988
984
|
[ fromEntityType, toEntityType ] = [ toEntityType, fromEntityType ];
|
|
989
985
|
[ fromEntitySet, toEntitySet ] = [ toEntitySet, fromEntitySet ];
|
|
990
986
|
|
|
991
987
|
parentName = forwardAssocCsn._edmParentCsn.name.replace(schemaNamePrefix, '');
|
|
992
|
-
|
|
988
|
+
plainAssocName = parentName + NAVPROP_TRENNER + forwardAssocCsn.name.replace(VALUELIST_NAVPROP_PREFIX, '');
|
|
989
|
+
assocName = plainAssocName;
|
|
993
990
|
i = 1;
|
|
994
|
-
while(NamesInSchemaXRef[assocName] !== undefined && !(NamesInSchemaXRef[assocName][0] instanceof Edm.Association))
|
|
995
|
-
assocName = plainAssocName
|
|
996
|
-
|
|
991
|
+
while (NamesInSchemaXRef[assocName] !== undefined && !(NamesInSchemaXRef[assocName][0] instanceof Edm.Association))
|
|
992
|
+
assocName = `${plainAssocName}_${i++}`;
|
|
993
|
+
|
|
997
994
|
|
|
998
995
|
navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
|
|
999
996
|
|
|
@@ -1002,111 +999,125 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
1002
999
|
constraints._multiplicity = edmUtils.determineMultiplicity(forwardAssocCsn);
|
|
1003
1000
|
}
|
|
1004
1001
|
|
|
1005
|
-
if(reuseAssoc)
|
|
1002
|
+
if (reuseAssoc)
|
|
1006
1003
|
return;
|
|
1007
1004
|
|
|
1008
1005
|
// Create Association and AssociationSet if this is not a backlink association.
|
|
1009
1006
|
// Store association at navigation property because in case the Ends must be modified
|
|
1010
1007
|
// later by the partner (backlink) association
|
|
1011
1008
|
const edmAssociation = new Edm.Association(v, { Name: assocName }, navigationProperty,
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
if(NamesInSchemaXRef[assocName] === undefined)
|
|
1009
|
+
[ fromRole, fullQualified(fromEntityType) ],
|
|
1010
|
+
[ toRole, fullQualified(toEntityType) ],
|
|
1011
|
+
constraints._multiplicity );
|
|
1012
|
+
if (NamesInSchemaXRef[assocName] === undefined)
|
|
1016
1013
|
NamesInSchemaXRef[assocName] = [ edmAssociation ];
|
|
1017
|
-
|
|
1018
|
-
else
|
|
1014
|
+
|
|
1015
|
+
else
|
|
1019
1016
|
NamesInSchemaXRef[assocName].push(edmAssociation);
|
|
1020
|
-
|
|
1017
|
+
|
|
1021
1018
|
// Add ReferentialConstraints if any
|
|
1022
|
-
if(!navigationProperty._isCollection && Object.keys(constraints.constraints).length > 0) {
|
|
1019
|
+
if (!navigationProperty._isCollection && Object.keys(constraints.constraints).length > 0) {
|
|
1023
1020
|
// A managed composition is treated as association
|
|
1024
|
-
if(navigationProperty._csn.type === 'cds.Composition' && navigationProperty._csn.on) {
|
|
1021
|
+
if (navigationProperty._csn.type === 'cds.Composition' && navigationProperty._csn.on) {
|
|
1025
1022
|
edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
|
|
1026
|
-
|
|
1023
|
+
toRole, fromRole, constraints.constraints));
|
|
1027
1024
|
}
|
|
1028
1025
|
else {
|
|
1029
1026
|
edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
|
|
1030
|
-
|
|
1027
|
+
fromRole, toRole, constraints.constraints));
|
|
1031
1028
|
}
|
|
1032
1029
|
}
|
|
1033
1030
|
|
|
1034
1031
|
Schema.append(edmAssociation);
|
|
1035
|
-
if(EntityContainer && !navigationProperty._targetCsn.$proxy) {
|
|
1036
|
-
const assocSet =
|
|
1037
|
-
|
|
1038
|
-
if(navigationProperty._csn._SetAttributes)
|
|
1032
|
+
if (EntityContainer && !navigationProperty._targetCsn.$proxy) {
|
|
1033
|
+
const assocSet = new Edm.AssociationSet(v, { Name: assocName, Association: fullQualified(assocName) },
|
|
1034
|
+
fromRole, toRole, fromEntitySet, toEntitySet);
|
|
1035
|
+
if (navigationProperty._csn._SetAttributes)
|
|
1039
1036
|
assocSet.setSapVocabularyAsAttributes(navigationProperty._csn._SetAttributes);
|
|
1040
1037
|
EntityContainer.register(assocSet);
|
|
1041
1038
|
}
|
|
1042
1039
|
}
|
|
1043
1040
|
|
|
1044
1041
|
// produce a full qualified name replacing the namespace with the alias (if provided)
|
|
1045
|
-
function fullQualified(name)
|
|
1046
|
-
|
|
1047
|
-
return schemaAliasPrefix + name.replace(schemaNamePrefix, '')
|
|
1042
|
+
function fullQualified( name ) {
|
|
1043
|
+
return schemaAliasPrefix + name.replace(schemaNamePrefix, '');
|
|
1048
1044
|
}
|
|
1049
1045
|
}
|
|
1050
1046
|
|
|
1051
1047
|
// generate the Edm.Annotations tree and append it to the corresponding schema
|
|
1052
|
-
function
|
|
1053
|
-
|
|
1048
|
+
function addAnnotations2XServiceRefs( ) {
|
|
1049
|
+
const { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
|
|
1054
1050
|
// distribute edm:Annotations into the schemas
|
|
1055
1051
|
// Distribute each anno into Schema
|
|
1056
|
-
annos.forEach(anno => {
|
|
1052
|
+
annos.forEach((anno) => {
|
|
1057
1053
|
let targetSchema = whatsMySchemaName(anno._edmAttributes.Target);
|
|
1058
1054
|
// if no target schema has been found, it's a service annotation that applies to the service schema
|
|
1059
|
-
if(targetSchema === undefined)
|
|
1055
|
+
if (targetSchema === undefined)
|
|
1060
1056
|
targetSchema = serviceCsn.name;
|
|
1061
|
-
if(targetSchema !== serviceCsn.name) {
|
|
1062
|
-
const newTarget = anno._edmAttributes.Target.replace(serviceCsn.name
|
|
1057
|
+
if (targetSchema !== serviceCsn.name) {
|
|
1058
|
+
const newTarget = anno._edmAttributes.Target.replace(`${serviceCsn.name}.`, '');
|
|
1063
1059
|
anno.setEdmAttribute('Target', newTarget);
|
|
1064
1060
|
}
|
|
1065
1061
|
edm._service._schemas[targetSchema]._annotations.push(anno);
|
|
1066
1062
|
});
|
|
1067
1063
|
|
|
1068
1064
|
// create service cross reference and merge it into xServiceRefs
|
|
1069
|
-
xrefs.forEach(xr => {
|
|
1070
|
-
if(xr !== serviceCsn.name) {
|
|
1065
|
+
xrefs.forEach((xr) => {
|
|
1066
|
+
if (xr !== serviceCsn.name) {
|
|
1071
1067
|
const art = edmUtils.createSchemaRef(allServices, xr);
|
|
1072
|
-
if(xServiceRefs[art.inc.Namespace] === undefined)
|
|
1068
|
+
if (xServiceRefs[art.inc.Namespace] === undefined)
|
|
1073
1069
|
xServiceRefs[art.inc.Namespace] = art;
|
|
1074
1070
|
}
|
|
1075
1071
|
});
|
|
1076
1072
|
// merge vocabulary cross references into xServiceRefs
|
|
1077
|
-
usedVocabularies.forEach(art =>
|
|
1073
|
+
usedVocabularies.forEach((art) => {
|
|
1074
|
+
xServiceRefs[art.inc.Namespace] = art;
|
|
1075
|
+
} );
|
|
1078
1076
|
}
|
|
1079
1077
|
|
|
1080
|
-
function edmTypeCompatibilityCheck(p, pLoc) {
|
|
1078
|
+
function edmTypeCompatibilityCheck( p, pLoc ) {
|
|
1081
1079
|
const edmType = p._type;
|
|
1082
|
-
if(!edmType) {
|
|
1080
|
+
if (!edmType) {
|
|
1083
1081
|
message('odata-spec-violation-type', pLoc);
|
|
1084
1082
|
}
|
|
1085
|
-
else if(p._scalarType) {
|
|
1083
|
+
else if (p._scalarType) {
|
|
1086
1084
|
const td = EdmPrimitiveTypeMap[edmType];
|
|
1087
|
-
if(td) {
|
|
1085
|
+
if (td) {
|
|
1088
1086
|
// The renderer/type mapper doesn't/shouldn't produce incompatible types and facets.
|
|
1089
1087
|
// Only the unknown type warning may be triggered by an unknown @odata.Type override.
|
|
1090
|
-
if(td.v2 !== p.v2 && td.v4 !== p.v4)
|
|
1088
|
+
if (td.v2 !== p.v2 && td.v4 !== p.v4) {
|
|
1091
1089
|
message('odata-spec-violation-type', pLoc,
|
|
1092
|
-
|
|
1093
|
-
|
|
1090
|
+
{ type: edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
|
|
1091
|
+
}
|
|
1092
|
+
EdmTypeFacetNames.forEach((name) => {
|
|
1094
1093
|
const facet = EdmTypeFacetMap[name];
|
|
1095
|
-
const optional
|
|
1096
|
-
(facet.optional !== undefined)
|
|
1094
|
+
const optional
|
|
1095
|
+
= (facet.optional !== undefined)
|
|
1097
1096
|
? (Array.isArray(facet.optional)
|
|
1098
1097
|
? facet.optional.includes(edmType)
|
|
1099
1098
|
: facet.optional)
|
|
1100
|
-
|
|
1099
|
+
: false;
|
|
1101
1100
|
|
|
1102
1101
|
// facet is not in attributes
|
|
1103
1102
|
// facet is member of type definition and mandatory
|
|
1104
1103
|
// node and facet version match
|
|
1105
|
-
if(!p._edmAttributes[name] && td[name] && !optional && (p.v2 === facet.v2 || p.v4 === facet.v4)) {
|
|
1104
|
+
if (!p._edmAttributes[name] && td[name] && !optional && (p.v2 === facet.v2 || p.v4 === facet.v4)) {
|
|
1106
1105
|
message('odata-spec-violation-type', pLoc,
|
|
1107
|
-
|
|
1106
|
+
{
|
|
1107
|
+
type: edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet',
|
|
1108
|
+
});
|
|
1108
1109
|
}
|
|
1109
1110
|
});
|
|
1111
|
+
if (edmType === 'Edm.Decimal') {
|
|
1112
|
+
const precision = Number.parseInt(p._edmAttributes.Precision, 10);
|
|
1113
|
+
const scale = Number.parseInt(p._edmAttributes.Scale, 10);
|
|
1114
|
+
if (!Number.isNaN(precision) && !Number.isNaN(scale) && scale > precision) {
|
|
1115
|
+
message('odata-spec-violation-type', pLoc,
|
|
1116
|
+
{
|
|
1117
|
+
type: edmType, number: scale, rawvalue: precision, ersion: (p.v4 ? '4.0' : '2.0'), '#': 'scale',
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1110
1121
|
}
|
|
1111
1122
|
else {
|
|
1112
1123
|
message('odata-spec-violation-type-unknown', pLoc, { type: edmType });
|