@sap/cds-compiler 2.13.8 → 2.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +109 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +20 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +89 -14
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +6 -5
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +33 -15
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +298 -225
- package/lib/edm/edmPreprocessor.js +486 -415
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +90 -16
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +94 -64
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +1 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* eslint max-statements-per-line:off */
|
|
3
3
|
const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
|
|
4
4
|
const { forEachDefinition, forEachGeneric, forEachMemberRecursively,
|
|
5
|
-
isEdmPropertyRendered, getUtils,
|
|
5
|
+
isEdmPropertyRendered, getUtils, cloneCsnNonDict, isBuiltinType } = require('../model/csnUtils');
|
|
6
6
|
const edmUtils = require('./edmUtils.js');
|
|
7
7
|
const typesExposure = require('../transform/odata/typesExposure');
|
|
8
8
|
const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
|
|
@@ -18,10 +18,8 @@ const {
|
|
|
18
18
|
isParameterizedEntity,
|
|
19
19
|
resolveOnConditionAndPrepareConstraints,
|
|
20
20
|
finalizeReferentialConstraints,
|
|
21
|
-
isODataSimpleIdentifier,
|
|
22
21
|
isEntity,
|
|
23
22
|
getSchemaPrefix,
|
|
24
|
-
isActionOrFunction
|
|
25
23
|
} = require('./edmUtils.js');
|
|
26
24
|
|
|
27
25
|
/**
|
|
@@ -34,62 +32,46 @@ const {
|
|
|
34
32
|
* @param {CSN.Model} csn
|
|
35
33
|
* @param {object} _options
|
|
36
34
|
*/
|
|
37
|
-
function initializeModel(csn, _options, messageFunctions)
|
|
35
|
+
function initializeModel(csn, _options, messageFunctions, requestedServiceNames=undefined)
|
|
38
36
|
{
|
|
39
|
-
|
|
40
|
-
throw Error('Please debug me: initializeModel must be invoked with options');
|
|
37
|
+
const { info, warning, error, message, throwWithAnyError } = messageFunctions;
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const csnUtils = getUtils(csn);
|
|
45
|
-
const {
|
|
46
|
-
inspectRef,
|
|
47
|
-
getCsnDef,
|
|
48
|
-
getFinalTypeDef,
|
|
49
|
-
isStructured,
|
|
50
|
-
isAssocOrComposition,
|
|
51
|
-
} = getUtils(csn);
|
|
39
|
+
let csnUtils = getUtils(csn);
|
|
52
40
|
|
|
53
41
|
// proxies are merged into the final model after all proxy elements are collected
|
|
54
42
|
const proxyCache = [];
|
|
43
|
+
// iterarte only over those definitions that need to be preprocessed
|
|
44
|
+
// instead of mangling through the whole model each time
|
|
45
|
+
// preprocess steps removing adding to the model must co-modify this map
|
|
46
|
+
const reqDefs = { definitions: Object.create(null) };
|
|
47
|
+
|
|
55
48
|
|
|
56
49
|
// make sure options are complete
|
|
57
50
|
let options = validateOptions(_options);
|
|
58
51
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if(art.kind === 'service') {
|
|
64
|
-
serviceRoots[artName] = Object.assign(art, { name: artName });
|
|
65
|
-
}
|
|
66
|
-
return serviceRoots;
|
|
67
|
-
}, Object.create(null) );
|
|
52
|
+
const [ serviceRoots,
|
|
53
|
+
serviceRootNames,
|
|
54
|
+
fallBackSchemaName,
|
|
55
|
+
whatsMyServiceRootName ] = getAnOverviewOnTheServices(csn);
|
|
68
56
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
|
|
57
|
+
if(requestedServiceNames === undefined)
|
|
58
|
+
requestedServiceNames = options.serviceNames;
|
|
59
|
+
if(requestedServiceNames === undefined) {
|
|
60
|
+
requestedServiceNames = serviceRootNames;
|
|
74
61
|
}
|
|
75
62
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
let autoexposeSchemaName = 'root';
|
|
79
|
-
let i = 1;
|
|
80
|
-
while (allDefs.some(([artName, _art]) => {
|
|
81
|
-
const p = artName.split('.');
|
|
82
|
-
return p.length === 2 && p[0] === autoexposeSchemaName;
|
|
83
|
-
})) {
|
|
84
|
-
autoexposeSchemaName = 'root' + i++;
|
|
63
|
+
function isMyServiceRequested(n) {
|
|
64
|
+
return requestedServiceNames.includes(whatsMyServiceRootName(n));
|
|
85
65
|
}
|
|
86
66
|
|
|
87
67
|
if(serviceRootNames.length === 0) {
|
|
88
|
-
return [serviceRoots, Object.create(null), whatsMyServiceRootName,
|
|
68
|
+
return [serviceRoots, Object.create(null), reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
|
|
89
69
|
}
|
|
90
70
|
|
|
91
71
|
// Structural CSN inbound QA checks
|
|
92
72
|
inboundQualificationChecks();
|
|
73
|
+
// not needed at the moment
|
|
74
|
+
// resolveForeignKeyRefs();
|
|
93
75
|
|
|
94
76
|
if(isBetaEnabled(options, undefined)) {
|
|
95
77
|
splitDottedDefinitionsIntoSeparateServices();
|
|
@@ -102,10 +84,13 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
102
84
|
renameDottedDefinitionsInsideServiceOrContext();
|
|
103
85
|
|
|
104
86
|
/*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
87
|
+
Final base type expansion is required here when:
|
|
88
|
+
1) The input CSN was already transformed for V4 but shall be rendered in V2 and the
|
|
89
|
+
edmx generator is called directly (bypassing OData transformation)
|
|
90
|
+
2) The input CSN was already transformed for V4 and persisted (all non-enumerables are
|
|
91
|
+
stripped of)
|
|
92
|
+
3) call via cdsc
|
|
93
|
+
|
|
109
94
|
At the end of the day, this module must be called only here, in the renderer and removed
|
|
110
95
|
as a step in the OData transformer with the goal to have a protocol agnostic OData CSN.
|
|
111
96
|
*/
|
|
@@ -113,20 +98,18 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
113
98
|
const { toFinalBaseType }= require('../transform/transformUtilsNew').getTransformers(csn, options);
|
|
114
99
|
expandCSNToFinalBaseType(csn, { toFinalBaseType }, csnUtils, serviceRootNames, options);
|
|
115
100
|
}
|
|
101
|
+
|
|
116
102
|
/*
|
|
117
|
-
Enrich the CSN by de-anonymizing and exposing
|
|
103
|
+
Enrich the CSN by de-anonymizing and exposing types that are required to make the service self contained.
|
|
118
104
|
Type exposure will add additional schema contexts and group the exposed types in these contexts.
|
|
119
105
|
contexts either represent another service (if the type to be exposed resides in that
|
|
120
106
|
service), the namespace (including (sub-)contexts) or as last resort (if the type name
|
|
121
107
|
has no prefix path) a 'root' namespace.
|
|
122
108
|
*/
|
|
123
|
-
const schemas = typesExposure(csn, whatsMyServiceRootName,
|
|
109
|
+
const schemas = typesExposure(csn, whatsMyServiceRootName, requestedServiceNames,
|
|
110
|
+
fallBackSchemaName, options, csnUtils, { error });
|
|
124
111
|
|
|
125
|
-
//
|
|
126
|
-
// elements are done in initializeStruct
|
|
127
|
-
forEachDefinition(csn, attachNameProperty);
|
|
128
|
-
|
|
129
|
-
// next, we must get an overview about all schemas (including the services)
|
|
112
|
+
// Get an overview about all schemas (including the services)
|
|
130
113
|
const schemaNames = [...serviceRootNames];
|
|
131
114
|
schemaNames.push(...Object.keys(schemas));
|
|
132
115
|
// sort schemas in reverse order to allow longest match in whatsMySchemaName function
|
|
@@ -136,53 +119,99 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
136
119
|
}
|
|
137
120
|
|
|
138
121
|
if(schemaNames.length) {
|
|
139
|
-
|
|
122
|
+
// First attach names to all definitions (and actions/params) in the model
|
|
123
|
+
// elements are done in initializeStruct
|
|
140
124
|
// Set myServiceName for later reference and indication of a service member
|
|
141
|
-
// First attach names to all definitions in the model
|
|
125
|
+
// First attach names to all definitions in the model and fill reqDefs
|
|
142
126
|
// Link association targets and spray @odata.contained over untagged compositions
|
|
143
|
-
forEachDefinition(csn, [
|
|
144
|
-
|
|
145
|
-
|
|
127
|
+
forEachDefinition(csn, [
|
|
128
|
+
attachNameProperty,
|
|
129
|
+
(def, defName) => {
|
|
130
|
+
const mySchemaName = whatsMySchemaName(defName);
|
|
131
|
+
mySchemaName && setProp(def, '$mySchemaName', mySchemaName);
|
|
132
|
+
if(isMyServiceRequested(defName))
|
|
133
|
+
reqDefs.definitions[defName] = def;
|
|
134
|
+
},
|
|
135
|
+
linkAssociationTarget ]);
|
|
136
|
+
// initialize requested services
|
|
137
|
+
const skip = { skipArtifact: (_def, defName) => !isMyServiceRequested(defName) };
|
|
138
|
+
forEachDefinition({ definitions: serviceRoots }, initService, skip);
|
|
146
139
|
// Create data structures for containments
|
|
147
|
-
forEachDefinition(
|
|
140
|
+
forEachDefinition(reqDefs, initContainments);
|
|
148
141
|
// Initialize entities with parameters (add Parameter entity)
|
|
149
|
-
forEachDefinition(
|
|
142
|
+
forEachDefinition(reqDefs, initParameterizedEntityOrView);
|
|
150
143
|
// Initialize structures
|
|
151
|
-
forEachDefinition(csn,
|
|
144
|
+
forEachDefinition(csn, initStructure);
|
|
152
145
|
// Initialize associations after _parent linking
|
|
153
|
-
forEachDefinition(
|
|
146
|
+
forEachDefinition(reqDefs, initConstraints);
|
|
154
147
|
// Mute V4 elements depending on constraint preparation
|
|
155
148
|
if(options.isV4())
|
|
156
|
-
forEachDefinition(
|
|
149
|
+
forEachDefinition(reqDefs, ignoreProperties);
|
|
157
150
|
// calculate constraints based on ignoreProperties and prepareConstraints
|
|
158
|
-
forEachDefinition(
|
|
151
|
+
forEachDefinition(reqDefs, finalizeConstraints);
|
|
159
152
|
// convert exposed types into cross schema references if required
|
|
160
153
|
// must be run before proxy exposure to avoid potential reference collisions
|
|
161
154
|
convertExposedTypesOfOtherServicesIntoCrossReferences();
|
|
162
155
|
// create association target proxies
|
|
163
156
|
// Decide if an entity set needs to be constructed or not
|
|
164
|
-
forEachDefinition(
|
|
157
|
+
forEachDefinition(reqDefs, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
|
|
165
158
|
// finalize proxy creation
|
|
166
159
|
mergeProxiesIntoModel();
|
|
167
160
|
|
|
168
161
|
if(options.isV4())
|
|
169
|
-
forEachDefinition(
|
|
162
|
+
forEachDefinition(reqDefs, initEdmNavPropBindingTargets);
|
|
170
163
|
|
|
171
164
|
// Things that can be done in one pass
|
|
172
165
|
// Create edmKeyRefPaths
|
|
173
166
|
// Create NavigationPropertyBindings, requires determineEntitySet
|
|
174
167
|
// Map /** doc comments */ to @CoreDescription
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
168
|
+
forEachDefinition(reqDefs, [
|
|
169
|
+
initEdmKeyRefPaths,
|
|
170
|
+
initEdmNavPropBindingPaths,
|
|
171
|
+
initEdmTypesAndDescription
|
|
172
|
+
]);
|
|
178
173
|
}
|
|
179
|
-
return [serviceRoots, schemas, whatsMyServiceRootName,
|
|
174
|
+
return [serviceRoots, schemas, reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
|
|
180
175
|
|
|
181
176
|
//////////////////////////////////////////////////////////////////////
|
|
182
177
|
//
|
|
183
178
|
// Service initialization starts here
|
|
184
179
|
//
|
|
185
180
|
|
|
181
|
+
function getAnOverviewOnTheServices(csn) {
|
|
182
|
+
const defs = csn.definitions || {};
|
|
183
|
+
const serviceRoots = Object.create(null);
|
|
184
|
+
for(const defName in defs) {
|
|
185
|
+
const def = defs[defName];
|
|
186
|
+
if(def && def.kind === 'service')
|
|
187
|
+
serviceRoots[defName] = Object.assign(def, { name: defName });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// first of all we need to know about all 'real' user defined services
|
|
191
|
+
const serviceRootNames = Object.keys(serviceRoots).sort((a,b)=>b.length-a.length);
|
|
192
|
+
|
|
193
|
+
function whatsMyServiceRootName(n, self=true) {
|
|
194
|
+
return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// find a globally unambiguous schema name to collect all top level 'root' types
|
|
198
|
+
// TODO: work on service basis (this requires post exposure renaming)
|
|
199
|
+
let fallBackSchemaName = 'root';
|
|
200
|
+
let i = 1;
|
|
201
|
+
while (Object.keys(defs).some(artName => {
|
|
202
|
+
const p = artName.split('.');
|
|
203
|
+
return p.length === 2 && p[0] === fallBackSchemaName;
|
|
204
|
+
})) {
|
|
205
|
+
fallBackSchemaName = 'root' + i++;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return [
|
|
209
|
+
serviceRoots,
|
|
210
|
+
serviceRootNames,
|
|
211
|
+
fallBackSchemaName,
|
|
212
|
+
whatsMyServiceRootName ];
|
|
213
|
+
}
|
|
214
|
+
|
|
186
215
|
/*
|
|
187
216
|
Replace dots in sub-service and sub-context definitions with underscores to be
|
|
188
217
|
Odata ID compliant.
|
|
@@ -194,9 +223,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
194
223
|
// Find the first definition above the current definition or undefined otherwise.
|
|
195
224
|
// Definition can either be a context or a service
|
|
196
225
|
function getRootDef(name) {
|
|
226
|
+
const scopeKinds = {'service':1, 'context':1};
|
|
197
227
|
let pos = name.lastIndexOf('.');
|
|
198
228
|
name = pos < 0 ? undefined : name.substring(0, pos);
|
|
199
|
-
while (name && !
|
|
229
|
+
while (name && !((csn.definitions[name] && csn.definitions[name].kind) in scopeKinds)) {
|
|
200
230
|
pos = name.lastIndexOf('.');
|
|
201
231
|
name = pos < 0 ? undefined : name.substring(0, pos);
|
|
202
232
|
}
|
|
@@ -205,8 +235,9 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
205
235
|
|
|
206
236
|
const dotEntityNameMap = Object.create(null);
|
|
207
237
|
const dotTypeNameMap = Object.create(null);
|
|
238
|
+
const kinds = {'entity':1, 'type':1, 'action':1, 'function':1};
|
|
208
239
|
forEachDefinition(csn, (def, defName) => {
|
|
209
|
-
if(
|
|
240
|
+
if(def.kind in kinds) {
|
|
210
241
|
const rootDef = getRootDef(defName);
|
|
211
242
|
// if this definition has a root def and the root def is not the service/schema name
|
|
212
243
|
// => service C { type D.E }, replace the prefix dots with underscores
|
|
@@ -303,18 +334,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
303
334
|
}
|
|
304
335
|
|
|
305
336
|
// initialize the service itself
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
if (service.name.length > 511) {
|
|
309
|
-
error(null, ['definitions', service.name], 'OData namespace must not exceed 511 characters' );
|
|
310
|
-
}
|
|
311
|
-
const simpleIdentifiers = service.name.split('.');
|
|
312
|
-
simpleIdentifiers.forEach((identifier) => {
|
|
313
|
-
if (!isODataSimpleIdentifier(identifier)) {
|
|
314
|
-
signalIllegalIdentifier(identifier, ['definitions', service.name]);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
setSAPSpecificV2AnnotationsToEntityContainer(options, service);
|
|
337
|
+
function initService(serviceRoot) {
|
|
338
|
+
setSAPSpecificV2AnnotationsToEntityContainer(options, serviceRoot);
|
|
318
339
|
}
|
|
319
340
|
|
|
320
341
|
// link association target to association and add @odata.contained to compositions in V4
|
|
@@ -361,7 +382,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
361
382
|
// non-containment rendering. If containment rendering is active, the containee has no
|
|
362
383
|
// entity set. Instead try to rewrite the annotation in such a way that it is effective
|
|
363
384
|
// on the containment navigation property.
|
|
364
|
-
function
|
|
385
|
+
function initContainments(container) {
|
|
365
386
|
if(container.kind === 'entity') {
|
|
366
387
|
forEachMemberRecursively(container, initContainments,
|
|
367
388
|
[], true, { elementsOnly: true });
|
|
@@ -424,7 +445,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
424
445
|
// must be called.
|
|
425
446
|
// As a param entity is a potential proxy candidate, this split must be performed on
|
|
426
447
|
// all definitions
|
|
427
|
-
function
|
|
448
|
+
function initParameterizedEntityOrView(entityCsn, entityName) {
|
|
428
449
|
|
|
429
450
|
if(!isParameterizedEntity(entityCsn))
|
|
430
451
|
return;
|
|
@@ -441,13 +462,71 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
441
462
|
// Backlink Navigation Property "Parameters" to <ViewName>Parameters
|
|
442
463
|
|
|
443
464
|
// this code can be extended for aggregated views
|
|
444
|
-
const
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
const backlinkAssocName = 'Parameters';
|
|
465
|
+
const typeEntityName = entityName + 'Type';
|
|
466
|
+
const typeEntitySetName = entityName + 'Set';
|
|
467
|
+
const parameterToTypeAssocName = 'Set';
|
|
468
|
+
const typeToParameterAssocName = 'Parameters';
|
|
449
469
|
let hasBacklink = true;
|
|
450
470
|
|
|
471
|
+
|
|
472
|
+
// create the Parameter Definition
|
|
473
|
+
const parameterCsn = createParameterEntity(entityCsn, entityName, false);
|
|
474
|
+
|
|
475
|
+
// create the Type Definition
|
|
476
|
+
// modify the original parameter entity with backlink and new name
|
|
477
|
+
if(csn.definitions[typeEntityName])
|
|
478
|
+
error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: typeEntityName });
|
|
479
|
+
else {
|
|
480
|
+
csn.definitions[typeEntityName] = entityCsn;
|
|
481
|
+
reqDefs.definitions[typeEntityName] = entityCsn;
|
|
482
|
+
delete csn.definitions[entityCsn.name];
|
|
483
|
+
delete reqDefs.definitions[entityCsn.name];
|
|
484
|
+
entityCsn.name = typeEntityName;
|
|
485
|
+
}
|
|
486
|
+
setProp(entityCsn, '$entitySetName', typeEntitySetName);
|
|
487
|
+
// add backlink association
|
|
488
|
+
if(hasBacklink) {
|
|
489
|
+
entityCsn.elements[typeToParameterAssocName] = {
|
|
490
|
+
name: typeToParameterAssocName,
|
|
491
|
+
target: parameterCsn.name,
|
|
492
|
+
type: 'cds.Association',
|
|
493
|
+
on: [ { ref: [ 'Parameters', 'Set' ] }, '=', { ref: [ '$self' ] } ]
|
|
494
|
+
};
|
|
495
|
+
setProp(entityCsn.elements[typeToParameterAssocName], '_selfReferences', []);
|
|
496
|
+
setProp(entityCsn.elements[typeToParameterAssocName], '_target', parameterCsn);
|
|
497
|
+
setProp(entityCsn.elements[typeToParameterAssocName], '$path',
|
|
498
|
+
[ 'definitions', typeEntityName, 'elements', typeToParameterAssocName ] );
|
|
499
|
+
|
|
500
|
+
// rewrite $path
|
|
501
|
+
if(entityCsn.$path)
|
|
502
|
+
entityCsn.$path[1] = typeEntityName;
|
|
503
|
+
forEachMemberRecursively(entityCsn, (member) => {
|
|
504
|
+
if(member.$path)
|
|
505
|
+
member.$path[1] = typeEntityName;
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/*
|
|
510
|
+
<EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
|
|
511
|
+
sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
|
|
512
|
+
*/
|
|
513
|
+
assignProp(entityCsn, '_SetAttributes',
|
|
514
|
+
{'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });
|
|
515
|
+
|
|
516
|
+
// redirect inbound associations/compositions to the parameter entity
|
|
517
|
+
Object.keys(entityCsn.$sources || {}).forEach(n => {
|
|
518
|
+
// preserve the original target for constraint calculation
|
|
519
|
+
setProp(entityCsn.$sources[n], '_originalTarget', entityCsn.$sources[n]._target);
|
|
520
|
+
entityCsn.$sources[n]._target = parameterCsn;
|
|
521
|
+
entityCsn.$sources[n].target = parameterCsn.name;
|
|
522
|
+
});
|
|
523
|
+
rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToTypeAssocName);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function createParameterEntity(entityCsn, entityName, isProxy) {
|
|
527
|
+
const parameterEntityName = entityName + 'Parameters';
|
|
528
|
+
const parameterToTypeAssocName = 'Set';
|
|
529
|
+
|
|
451
530
|
// Construct the parameter entity
|
|
452
531
|
const parameterCsn = {
|
|
453
532
|
name: parameterEntityName,
|
|
@@ -455,8 +534,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
455
534
|
elements: Object.create(null),
|
|
456
535
|
'@sap.semantics': 'parameters',
|
|
457
536
|
};
|
|
458
|
-
|
|
459
|
-
|
|
537
|
+
if(!isProxy)
|
|
538
|
+
setProp(parameterCsn, '$entitySetName', entityName);
|
|
460
539
|
if(entityCsn.$location){
|
|
461
540
|
assignProp(parameterCsn, '$location', entityCsn.$location);
|
|
462
541
|
}
|
|
@@ -482,9 +561,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
482
561
|
entityCsn._containerEntity = [ parameterCsn ];
|
|
483
562
|
|
|
484
563
|
forEachGeneric(entityCsn, 'params', (p,n) => {
|
|
485
|
-
let elt =
|
|
564
|
+
let elt = cloneCsnNonDict(p, options);
|
|
486
565
|
elt.name = n;
|
|
487
566
|
delete elt.kind;
|
|
567
|
+
setProp(elt, '$path', [ 'definitions', parameterEntityName, 'elements', n ]);
|
|
488
568
|
elt.key = true; // params become primary key in parameter entity
|
|
489
569
|
/*
|
|
490
570
|
Spec meeting decision 28.02.22:
|
|
@@ -500,81 +580,35 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
500
580
|
parameterCsn.elements[n] = elt;
|
|
501
581
|
});
|
|
502
582
|
linkAssociationTarget(parameterCsn);
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
cardinality: { src: 1, min: 0, max: '*' }
|
|
511
|
-
};
|
|
512
|
-
setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn);
|
|
513
|
-
setProp(parameterCsn.elements[parameterToOriginalAssocName], '$path',
|
|
514
|
-
[ 'definitions', parameterEntityName, 'elements', parameterToOriginalAssocName ] );
|
|
515
|
-
|
|
516
|
-
// rewrite $path
|
|
517
|
-
setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
|
|
518
|
-
forEachMemberRecursively(parameterCsn, (member) => {
|
|
519
|
-
if(member.$path)
|
|
520
|
-
member.$path[1] = parameterEntityName;
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
if(csn.definitions[parameterCsn.name])
|
|
524
|
-
error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
|
|
525
|
-
else
|
|
526
|
-
csn.definitions[parameterCsn.name] = parameterCsn;
|
|
527
|
-
// modify the original parameter entity with backlink and new name
|
|
528
|
-
if(csn.definitions[originalEntityName])
|
|
529
|
-
error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: originalEntityName });
|
|
530
|
-
else {
|
|
531
|
-
csn.definitions[originalEntityName] = entityCsn;
|
|
532
|
-
delete csn.definitions[entityCsn.name];
|
|
533
|
-
entityCsn.name = originalEntityName;
|
|
534
|
-
}
|
|
535
|
-
setProp(entityCsn, '$entitySetName', originalEntitySetName);
|
|
536
|
-
// add backlink association
|
|
537
|
-
if(hasBacklink) {
|
|
538
|
-
entityCsn.elements[backlinkAssocName] = {
|
|
539
|
-
name: backlinkAssocName,
|
|
540
|
-
target: parameterCsn.name,
|
|
583
|
+
initContainments(parameterCsn);
|
|
584
|
+
// add assoc to result set, FIXME: is the cardinality correct?
|
|
585
|
+
if(!isProxy) {
|
|
586
|
+
parameterCsn.elements[parameterToTypeAssocName] = {
|
|
587
|
+
'@odata.contained': true,
|
|
588
|
+
name: parameterToTypeAssocName,
|
|
589
|
+
target: entityCsn.name,
|
|
541
590
|
type: 'cds.Association',
|
|
542
|
-
|
|
591
|
+
cardinality: { src: 1, min: 0, max: '*' }
|
|
543
592
|
};
|
|
544
|
-
setProp(
|
|
545
|
-
setProp(
|
|
546
|
-
|
|
547
|
-
[ 'definitions', originalEntityName, 'elements', backlinkAssocName ] );
|
|
548
|
-
|
|
549
|
-
// rewrite $path
|
|
550
|
-
if(entityCsn.$path)
|
|
551
|
-
entityCsn.$path[1] = originalEntityName;
|
|
552
|
-
forEachMemberRecursively(entityCsn, (member) => {
|
|
553
|
-
if(member.$path)
|
|
554
|
-
member.$path[1] = originalEntityName;
|
|
555
|
-
});
|
|
593
|
+
setProp(parameterCsn.elements[parameterToTypeAssocName], '_target', entityCsn);
|
|
594
|
+
setProp(parameterCsn.elements[parameterToTypeAssocName], '$path',
|
|
595
|
+
[ 'definitions', parameterEntityName, 'elements', parameterToTypeAssocName ] );
|
|
556
596
|
}
|
|
597
|
+
// rewrite $path
|
|
598
|
+
setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
|
|
557
599
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
Object.keys(entityCsn.$sources || {}).forEach(n => {
|
|
569
|
-
// preserve the original target for constraint calculation
|
|
570
|
-
setProp(entityCsn.$sources[n], '_originalTarget', entityCsn.$sources[n]._target);
|
|
571
|
-
entityCsn.$sources[n]._target = parameterCsn;
|
|
572
|
-
entityCsn.$sources[n].target = parameterCsn.name;
|
|
573
|
-
});
|
|
574
|
-
rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToOriginalAssocName);
|
|
600
|
+
// proxies are registered in model separately
|
|
601
|
+
if(!isProxy) {
|
|
602
|
+
if(csn.definitions[parameterCsn.name])
|
|
603
|
+
error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
|
|
604
|
+
else {
|
|
605
|
+
csn.definitions[parameterCsn.name] = parameterCsn;
|
|
606
|
+
reqDefs.definitions[parameterCsn.name] = parameterCsn;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return parameterCsn;
|
|
575
610
|
}
|
|
576
611
|
|
|
577
|
-
|
|
578
612
|
function initElement(element, name, struct) {
|
|
579
613
|
setProp(element, 'name', name)
|
|
580
614
|
setProp(element, '_parent', struct);
|
|
@@ -600,7 +634,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
600
634
|
}
|
|
601
635
|
|
|
602
636
|
// Initialize a structured artifact
|
|
603
|
-
function
|
|
637
|
+
function initStructure(def) {
|
|
604
638
|
|
|
605
639
|
// Don't operate on any structured types other than type and entity
|
|
606
640
|
// such as events and aspects
|
|
@@ -611,24 +645,13 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
611
645
|
let validFrom = [], validKey = [];
|
|
612
646
|
|
|
613
647
|
// Iterate all struct elements
|
|
614
|
-
forEachMemberRecursively(def.items || def, (element, elementName, prop,
|
|
648
|
+
forEachMemberRecursively(def.items || def, (element, elementName, prop, _path = [], construct) => {
|
|
615
649
|
if(prop !== 'elements')
|
|
616
650
|
return;
|
|
617
651
|
|
|
618
652
|
initElement(element, elementName, construct);
|
|
619
653
|
|
|
620
|
-
|
|
621
|
-
if(element._parent && element._parent.$mySchemaName) {
|
|
622
|
-
if(!isODataSimpleIdentifier(elementName)) {
|
|
623
|
-
signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
|
|
624
|
-
} else if (options.isV2() && /^(_|[0-9])/.test(elementName) && element._parent.kind === 'entity') {
|
|
625
|
-
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
626
|
-
error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
|
|
627
|
-
'Element names must not start with $(PROP) for OData V2');
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
// collect temporal information
|
|
654
|
+
// collect temporal information
|
|
632
655
|
if(element['@cds.valid.key']) {
|
|
633
656
|
validKey.push(element);
|
|
634
657
|
}
|
|
@@ -708,18 +731,19 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
708
731
|
}
|
|
709
732
|
|
|
710
733
|
// Prepare the associations for the subsequent steps
|
|
711
|
-
function
|
|
712
|
-
if(!isStructuredArtifact(
|
|
734
|
+
function initConstraints(def) {
|
|
735
|
+
if(!isStructuredArtifact(def))
|
|
713
736
|
return;
|
|
714
737
|
|
|
715
|
-
forEachMemberRecursively(
|
|
716
|
-
|
|
738
|
+
forEachMemberRecursively(def.items || def, initConstraintsOnAssoc, [], true, { elementsOnly: true });
|
|
739
|
+
}
|
|
740
|
+
function initConstraintsOnAssoc(element) {
|
|
741
|
+
if (isAssociationOrComposition(element) && !element._ignore && !element._constraints) {
|
|
717
742
|
// setup the constraints object
|
|
718
|
-
|
|
743
|
+
setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
|
|
719
744
|
// and crack the ON condition
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}, [], true, { elementsOnly: true });
|
|
745
|
+
resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
|
|
746
|
+
}
|
|
723
747
|
}
|
|
724
748
|
|
|
725
749
|
/*
|
|
@@ -791,38 +815,39 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
791
815
|
It may be that now a number of properties are not rendered and cannot act as constraints (see isConstraintCandidate())
|
|
792
816
|
in edmUtils
|
|
793
817
|
*/
|
|
794
|
-
function finalizeConstraints(
|
|
795
|
-
if(!isStructuredArtifact(
|
|
818
|
+
function finalizeConstraints(def) {
|
|
819
|
+
if(!isStructuredArtifact(def))
|
|
796
820
|
return;
|
|
797
821
|
|
|
798
|
-
forEachMemberRecursively(
|
|
799
|
-
|
|
800
|
-
|
|
822
|
+
forEachMemberRecursively(def.items || def, finalizeConstraintsOnAssoc, [], true, { elementsOnly: true });
|
|
823
|
+
}
|
|
824
|
+
function finalizeConstraintsOnAssoc(element) {
|
|
825
|
+
if (isAssociationOrComposition(element) && !element._ignore && element._constraints) {
|
|
826
|
+
finalizeReferentialConstraints(csn, element, options, info);
|
|
801
827
|
|
|
802
|
-
|
|
828
|
+
if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
|
|
803
829
|
// if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
|
|
804
|
-
|
|
830
|
+
if(element._constraints._partnerCsn.cardinality) {
|
|
805
831
|
// if the forward association has set a src cardinality and it deviates from the backlink target cardinality raise a warning
|
|
806
832
|
// in V2 only, in V4 the source cardinality is rendered implicitly at the Type property
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
833
|
+
if(element._constraints._partnerCsn.cardinality.src) {
|
|
834
|
+
let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
|
|
835
|
+
let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
|
|
836
|
+
if(options.isV2() && srcMult !== newMult) {
|
|
811
837
|
// Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
else {
|
|
816
|
-
// .. but only if the original assoc hasn't set src yet
|
|
817
|
-
element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
|
|
838
|
+
warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
|
|
818
839
|
}
|
|
819
840
|
}
|
|
820
841
|
else {
|
|
821
|
-
|
|
842
|
+
// .. but only if the original assoc hasn't set src yet
|
|
843
|
+
element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
|
|
822
844
|
}
|
|
823
845
|
}
|
|
846
|
+
else {
|
|
847
|
+
element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
|
|
848
|
+
}
|
|
824
849
|
}
|
|
825
|
-
}
|
|
850
|
+
}
|
|
826
851
|
}
|
|
827
852
|
|
|
828
853
|
/*
|
|
@@ -838,8 +863,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
838
863
|
if(serviceRootNames.includes(targetSchemaName)) {
|
|
839
864
|
// remove all definitions starting with < fqSchemaName >. and add a schema reference
|
|
840
865
|
Object.keys(csn.definitions).forEach(dn => {
|
|
841
|
-
if(dn.startsWith(fqSchemaName)) // this includes the fqSchemaName context
|
|
866
|
+
if(dn.startsWith(fqSchemaName)) {// this includes the fqSchemaName context
|
|
842
867
|
delete csn.definitions[dn];
|
|
868
|
+
delete reqDefs.definitions[dn];
|
|
869
|
+
}
|
|
843
870
|
});
|
|
844
871
|
if(!schemas[fqSchemaName])
|
|
845
872
|
schemaNames.push(fqSchemaName);
|
|
@@ -870,10 +897,9 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
870
897
|
|
|
871
898
|
If option odataExtReferences is used, 'root' proxies are still created.
|
|
872
899
|
|
|
873
|
-
If
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
proxies.
|
|
900
|
+
If the association leading to the proxy candidate refers to associations either directly
|
|
901
|
+
or indirectly (via structured elements), these dependent entity types are (recursively) exposed
|
|
902
|
+
(or referenced) as well to keep the navigation graph in tact.
|
|
877
903
|
*/
|
|
878
904
|
function exposeTargetsAsProxiesOrSchemaRefs(struct) {
|
|
879
905
|
if(struct.kind === 'context' || struct.kind === 'service' || struct.$proxy)
|
|
@@ -923,14 +949,16 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
923
949
|
const targetSchemaName = element._target.$mySchemaName;
|
|
924
950
|
if(isProxyRequired(element)) {
|
|
925
951
|
if(options.isV4() &&
|
|
926
|
-
|
|
927
|
-
// must be a managed association with keys
|
|
928
|
-
|
|
952
|
+
(options.toOdata.odataProxies || options.toOdata.odataXServiceRefs) &&
|
|
953
|
+
// must be a managed association with keys OR an unambiguous backlink
|
|
954
|
+
element.keys ||
|
|
955
|
+
(element.on && element._constraints.selfs.length === 1 && element._constraints.termCount === 1)
|
|
956
|
+
) {
|
|
929
957
|
// reuse proxy if available
|
|
930
958
|
let proxy = getProxyForTargetOf(element);
|
|
931
959
|
if(!proxy) {
|
|
932
960
|
if(targetSchemaName && options.toOdata.odataXServiceRefs) {
|
|
933
|
-
proxy = createSchemaRefFor(
|
|
961
|
+
proxy = createSchemaRefFor(targetSchemaName);
|
|
934
962
|
}
|
|
935
963
|
else if(options.toOdata.odataProxies) {
|
|
936
964
|
proxy = createProxyFor(element, targetSchemaName);
|
|
@@ -941,14 +969,16 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
941
969
|
// if a proxy was either already created or could be created and
|
|
942
970
|
// if it's a 'real' proxy, link the _target to it and remove constraints
|
|
943
971
|
// otherwise proxy is a schema reference, then do nothing
|
|
972
|
+
setProp(element, '$noPartner', true);
|
|
944
973
|
element._constraints.constraints = Object.create(null);
|
|
945
974
|
if(proxy.kind === 'entity') {
|
|
975
|
+
if(!proxy.$isParamEntity)
|
|
976
|
+
populateProxyElements(element, proxy, getForeignKeyDefinitions(element));
|
|
946
977
|
element._target = proxy;
|
|
947
|
-
populateProxyElements(element, proxy, getForeignKeyDefinitions(element, ['definitions', struct.name, 'elements', element.name]));
|
|
948
978
|
}
|
|
949
979
|
else {
|
|
950
|
-
//
|
|
951
|
-
setProp(element
|
|
980
|
+
// No navigation property bindings on external references
|
|
981
|
+
setProp(element, '$externalRef', true);
|
|
952
982
|
}
|
|
953
983
|
}
|
|
954
984
|
else {
|
|
@@ -968,11 +998,11 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
968
998
|
}
|
|
969
999
|
|
|
970
1000
|
function noNavPropMsg(elt) {
|
|
971
|
-
warning(
|
|
972
|
-
{ target: elt._target.name, service: globalSchemaPrefix }
|
|
1001
|
+
warning('odata-navigation', ['definitions', struct.name, 'elements', elt.name],
|
|
1002
|
+
{ target: elt._target.name, service: globalSchemaPrefix });
|
|
973
1003
|
}
|
|
974
1004
|
|
|
975
|
-
function createSchemaRefFor(
|
|
1005
|
+
function createSchemaRefFor(targetSchemaName) {
|
|
976
1006
|
let ref = csn.definitions[globalSchemaPrefix + '.' + targetSchemaName];
|
|
977
1007
|
if(!ref) {
|
|
978
1008
|
ref = createSchemaRef(targetSchemaName);
|
|
@@ -987,11 +1017,23 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
987
1017
|
// if it is required in multiple services. The service schema name is prepended upon registration
|
|
988
1018
|
const proxySchemaName = targetSchemaName || getSchemaPrefix(assoc._target.name);
|
|
989
1019
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1020
|
+
// if the target is a parameter entity, it's easy just create the parameter stub
|
|
1021
|
+
const isParamProxy = isParameterizedEntity(assoc._target);
|
|
1022
|
+
|
|
1023
|
+
// 1) construct the proxy definition
|
|
1024
|
+
// proxyDefinitionName: strip the serviceName and replace '.' with '_'
|
|
1025
|
+
let defName =
|
|
1026
|
+
`${assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_')}`;
|
|
1027
|
+
|
|
993
1028
|
// fullName: Prepend serviceName and if in same service add '_proxy'
|
|
994
|
-
const proxy =
|
|
1029
|
+
const proxy = isParamProxy
|
|
1030
|
+
? createParameterEntity(assoc._target, proxySchemaName + '.' + defName, true)
|
|
1031
|
+
: { name: proxySchemaName + '.' + defName, kind: 'entity', elements: Object.create(null) };
|
|
1032
|
+
|
|
1033
|
+
// Final proxyShortName for all further processing
|
|
1034
|
+
const proxyShortName = defName + (isParamProxy ? 'Parameters' : '');
|
|
1035
|
+
|
|
1036
|
+
setProp(proxy, '$proxy', true);
|
|
995
1037
|
setProp(proxy, '$mySchemaName', proxySchemaName);
|
|
996
1038
|
setProp(proxy, '$proxyShortName', proxyShortName);
|
|
997
1039
|
setProp(proxy, '$keys', Object.create(null));
|
|
@@ -1004,53 +1046,66 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1004
1046
|
});
|
|
1005
1047
|
|
|
1006
1048
|
// 2) create the elements and $keys
|
|
1007
|
-
|
|
1049
|
+
if(isParamProxy) {
|
|
1050
|
+
// Reset param proxy elements to expose element tree
|
|
1051
|
+
const elements = proxy.elements;
|
|
1052
|
+
proxy.elements = Object.create(null);
|
|
1053
|
+
populateProxyElements(assoc, proxy, elements);
|
|
1054
|
+
}
|
|
1055
|
+
else
|
|
1056
|
+
populateProxyElements(assoc, proxy, assoc._target.$keys);
|
|
1008
1057
|
return proxy;
|
|
1009
1058
|
|
|
1010
1059
|
}
|
|
1011
1060
|
|
|
1012
|
-
//
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}).filter(fk=>fk) : [];
|
|
1061
|
+
// Return top level foreign key element definitions. The full top level
|
|
1062
|
+
// element is exposed instead of merging partial trees into the exposed type
|
|
1063
|
+
// def structure.
|
|
1064
|
+
function getForeignKeyDefinitions(e) {
|
|
1065
|
+
return e.keys ? e.keys.map(fk => e._target.elements[fk.ref[0]]) : [];
|
|
1018
1066
|
}
|
|
1019
|
-
|
|
1067
|
+
|
|
1068
|
+
// copy over the primary keys of the target and trigger the type exposure
|
|
1069
|
+
// if the element already exists we assume it was fully exposed
|
|
1020
1070
|
function populateProxyElements(assoc, proxy, elements) {
|
|
1021
1071
|
forAll(elements, e => {
|
|
1022
|
-
if (isEdmPropertyRendered(e, options)
|
|
1023
|
-
let newElt =
|
|
1024
|
-
if(
|
|
1025
|
-
if(
|
|
1026
|
-
if(
|
|
1027
|
-
|
|
1028
|
-
|
|
1072
|
+
if (isEdmPropertyRendered(e, options)) {
|
|
1073
|
+
let newElt = proxy.elements[e.name];
|
|
1074
|
+
if(!newElt) {
|
|
1075
|
+
if(csnUtils.isAssocOrComposition(e.type)) {
|
|
1076
|
+
if(!e.on && e.keys) {
|
|
1077
|
+
if(options.toOdata.odataNoTransitiveProxies)
|
|
1078
|
+
newElt = convertManagedAssocIntoStruct(e);
|
|
1079
|
+
else
|
|
1029
1080
|
newElt = createProxyOrSchemaRefForManagedAssoc(e);
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
info(null, ['definitions', struct.name, 'elements', assoc.name],
|
|
1033
1084
|
{ name: proxy.nname, target: assoc._target.name },
|
|
1034
1085
|
'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
|
|
1086
|
+
}
|
|
1035
1087
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1088
|
+
else {
|
|
1089
|
+
newElt = Object.create(null);
|
|
1090
|
+
Object.keys(e).forEach(prop => newElt[prop] = e[prop])
|
|
1091
|
+
}
|
|
1092
|
+
if(newElt) {
|
|
1093
|
+
initElement(newElt, e.name, proxy);
|
|
1094
|
+
proxy.elements[newElt.name] = newElt;
|
|
1095
|
+
|
|
1096
|
+
if(csnUtils.isStructured(newElt)) {
|
|
1043
1097
|
// argument proxySchemaName forces an anonymous type definition for newElt into the
|
|
1044
|
-
// proxy schema. If omitted, this exposure defaults to 'root', in case API flavor
|
|
1045
|
-
// changes...
|
|
1046
|
-
|
|
1047
|
-
|
|
1098
|
+
// proxy schema. If omitted, this exposure defaults to 'root', in case API flavor
|
|
1099
|
+
// of the day changes...
|
|
1100
|
+
exposeStructTypeForProxyOf(proxy, newElt, proxy.$proxyShortName + '_' + newElt.name,
|
|
1101
|
+
proxy.$mySchemaName, newElt.key, !!(newElt.key && newElt.elements));
|
|
1102
|
+
}
|
|
1103
|
+
if(newElt.key)
|
|
1104
|
+
proxy.$keys[newElt.name] = newElt;
|
|
1048
1105
|
}
|
|
1049
|
-
proxy.elements[newElt.name] = newElt;
|
|
1050
|
-
if(newElt.key)
|
|
1051
|
-
proxy.$keys[newElt.name] = newElt;
|
|
1052
1106
|
}
|
|
1053
1107
|
}
|
|
1108
|
+
|
|
1054
1109
|
});
|
|
1055
1110
|
// 3) sort the exposed types so that they appear lexicographically ordered in the EDM
|
|
1056
1111
|
proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
|
|
@@ -1062,78 +1117,90 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1062
1117
|
// anonymous or has a definition outside of 'service'), create an equivalent type in 'service', either
|
|
1063
1118
|
// using the type's name or (if anonymous) 'artificialName', and make 'node' use that type instead.
|
|
1064
1119
|
// Complain if there is an error.
|
|
1065
|
-
|
|
1066
|
-
|
|
1120
|
+
// isKey: Indicates top level element is key or not
|
|
1121
|
+
// forceToNotNull: if top level element is key, recursively set all anonymously exposed elements
|
|
1122
|
+
// to notNull until the first named type is exposed.
|
|
1123
|
+
function exposeStructTypeForProxyOf(proxy, node, artificialName,
|
|
1124
|
+
typeSchemaName=fallBackSchemaName,
|
|
1125
|
+
isKey, forceToNotNull) {
|
|
1126
|
+
|
|
1127
|
+
if(node.type && isBuiltinType(node.type))
|
|
1128
|
+
return;
|
|
1129
|
+
|
|
1067
1130
|
// Always expose types referred to by a proxy, never reuse an eventually existing type
|
|
1068
1131
|
// as the nested elements must all be not nullable
|
|
1069
|
-
|
|
1070
|
-
|
|
1132
|
+
// elements have precedence over type
|
|
1133
|
+
const typeDef = !node.elements && node.type ? csn.definitions[node.type] : node;
|
|
1071
1134
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1135
|
+
if (typeDef) {
|
|
1136
|
+
let typeClone;
|
|
1074
1137
|
// the type clone must be produced for each service as this type may
|
|
1075
1138
|
// produce references and/or proxies into multiple services
|
|
1076
1139
|
// (but only once per service, therefore cache it).
|
|
1077
|
-
|
|
1140
|
+
if(typeDef.$proxyTypes && typeDef.$proxyTypes[globalSchemaPrefix]) {
|
|
1078
1141
|
// if type has been exposed in a schema use this type
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1142
|
+
typeClone = typeDef.$proxyTypes[globalSchemaPrefix];
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1082
1145
|
// Set the correct name
|
|
1083
|
-
|
|
1084
|
-
|
|
1146
|
+
let typeId = artificialName; // the artificialName has no namespace, it's the element
|
|
1147
|
+
if(node.type) {
|
|
1085
1148
|
// same as for proxies, use schema or namespace, 'root' is last resort
|
|
1086
|
-
|
|
1087
|
-
|
|
1149
|
+
typeSchemaName = typeDef.$mySchemaName || getSchemaPrefix(node.type);
|
|
1150
|
+
typeId = node.type.replace(typeSchemaName + '.', '').replace(/\./g, '_');
|
|
1088
1151
|
// strip the service root of that type (if any)
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1152
|
+
const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
|
|
1153
|
+
if(myServiceRootName)
|
|
1154
|
+
typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
if(isStructuredArtifact(typeDef)) {
|
|
1158
|
+
// pull forceNotNull to false for named types and non-key nodes
|
|
1159
|
+
// only toplevel nodes (elements) can be key
|
|
1160
|
+
forceToNotNull = !!(forceToNotNull && isKey && node.elements && !node.type);
|
|
1093
1161
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
if(typeClone) {
|
|
1162
|
+
typeClone = cloneStructTypeForProxy(typeSchemaName, `${typeSchemaName}.${typeId}`, typeDef);
|
|
1163
|
+
if(typeClone) {
|
|
1097
1164
|
// Recurse into elements of 'type' (if any)
|
|
1098
|
-
|
|
1165
|
+
typeClone.elements && Object.entries(typeClone.elements).forEach(([elemName, elem]) => {
|
|
1099
1166
|
// if this is a foreign key elment, we must check whether or not the association
|
|
1100
1167
|
// has been exposed as proxy. If it has not been exposed, no further structured
|
|
1101
1168
|
// types must be exposed as 'Proxy_' types.
|
|
1102
1169
|
|
|
1103
1170
|
// TODO: expose types of assoc.keys and don't rely on exposed foreign keys
|
|
1104
|
-
|
|
1171
|
+
if(!elem['@odata.foreignKey4'] ||
|
|
1105
1172
|
(elem['@odata.foreignKey4'] && !typeClone.elements[elem['@odata.foreignKey4']].$exposed))
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
typeDef.$proxyTypes
|
|
1111
|
-
|
|
1173
|
+
exposeStructTypeForProxyOf(proxy, elem, `${typeId}_${elemName}`,
|
|
1174
|
+
typeSchemaName, isKey, forceToNotNull);
|
|
1175
|
+
});
|
|
1176
|
+
if(!typeDef.$proxyTypes)
|
|
1177
|
+
typeDef.$proxyTypes = Object.create(null);
|
|
1178
|
+
typeDef.$proxyTypes[globalSchemaPrefix] = typeClone;
|
|
1112
1179
|
}
|
|
1113
|
-
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1114
1182
|
// FUTURE: expose scalar type definition as well
|
|
1115
|
-
}
|
|
1116
1183
|
}
|
|
1117
|
-
|
|
1184
|
+
}
|
|
1185
|
+
if(typeClone) {
|
|
1118
1186
|
// register the type clone at the proxy
|
|
1119
1187
|
// Reminder: Each proxy receives a full set of type clones, even if the types are shared
|
|
1120
1188
|
// (no scattered type clone caching). registerProxy() checks if a clone needs to be added to
|
|
1121
1189
|
// csn.definitions.
|
|
1122
|
-
|
|
1190
|
+
proxy.$exposedTypes[typeClone.name] = typeClone;
|
|
1123
1191
|
|
|
1124
1192
|
// set the node's new type name
|
|
1125
|
-
|
|
1193
|
+
node.type = typeClone.name;
|
|
1126
1194
|
// the key path generator must use the type clone directly, because it can't resolve
|
|
1127
1195
|
// the type clone in the CSN (its name is the final name and not the definition name).
|
|
1128
|
-
|
|
1196
|
+
setProp(node, '_type', typeClone);
|
|
1129
1197
|
// Hack alert:
|
|
1130
1198
|
// beta feature 'subElemRedirections' (now the default in v2) adds elements to the node by
|
|
1131
1199
|
// default, without we must do it to get the primary key tuple calculation correct.
|
|
1132
1200
|
// Remember: node.type is the service local type name (not prepended by the service name),
|
|
1133
1201
|
// so it can't be resolved in definitions later on
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
}
|
|
1202
|
+
if(typeClone.elements)
|
|
1203
|
+
node.elements = typeClone.elements;
|
|
1137
1204
|
}
|
|
1138
1205
|
}
|
|
1139
1206
|
|
|
@@ -1151,12 +1218,13 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1151
1218
|
if(!elem.target) {
|
|
1152
1219
|
type.elements[elemName] = Object.create(null);
|
|
1153
1220
|
Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
|
|
1154
|
-
type.elements[elemName].notNull = true;
|
|
1155
1221
|
}
|
|
1156
1222
|
else if(elem.keys && !elem.on) {
|
|
1157
1223
|
// a primary key can never be an unmanaged association
|
|
1158
1224
|
type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
|
|
1159
1225
|
}
|
|
1226
|
+
if(forceToNotNull)
|
|
1227
|
+
type.elements[elemName].notNull = true;
|
|
1160
1228
|
setProp(type.elements[elemName], 'name', elem.name);
|
|
1161
1229
|
});
|
|
1162
1230
|
return type;
|
|
@@ -1166,7 +1234,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1166
1234
|
// Convert a managed association into a structured type and
|
|
1167
1235
|
// eliminate nested foreign key associations
|
|
1168
1236
|
function convertManagedAssocIntoStruct(e) {
|
|
1169
|
-
let newElt =
|
|
1237
|
+
let newElt = cloneCsnNonDict(e, options);
|
|
1170
1238
|
newElt.elements = Object.create(null);
|
|
1171
1239
|
// remove all unwanted garbage
|
|
1172
1240
|
delete newElt.keys;
|
|
@@ -1177,14 +1245,15 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1177
1245
|
let keys = (!e._target.$isParamEntity && e.keys) ||
|
|
1178
1246
|
Object.keys(e._target.$keys).map(k => { return { ref: [k] } });
|
|
1179
1247
|
keys.forEach(k => {
|
|
1180
|
-
let art = e._target || getCsnDef(e.target);
|
|
1248
|
+
let art = e._target || csnUtils.getCsnDef(e.target);
|
|
1181
1249
|
for(let ps of k.ref) {
|
|
1182
1250
|
art = art.elements[ps];
|
|
1183
1251
|
}
|
|
1184
1252
|
// art is in the target side, clone it and remove key property
|
|
1185
|
-
let cloneArt =
|
|
1253
|
+
let cloneArt = cloneCsnNonDict(art, options);
|
|
1186
1254
|
setProp(cloneArt, 'name', art.name);
|
|
1187
|
-
|
|
1255
|
+
if(e.key)
|
|
1256
|
+
cloneArt.notNull = true;
|
|
1188
1257
|
delete cloneArt.key;
|
|
1189
1258
|
newElt.elements[art.name] = cloneArt;
|
|
1190
1259
|
});
|
|
@@ -1200,18 +1269,19 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1200
1269
|
function createProxyOrSchemaRefForManagedAssoc(e) {
|
|
1201
1270
|
|
|
1202
1271
|
let proxy = e._target;
|
|
1203
|
-
let newElt =
|
|
1272
|
+
let newElt = cloneCsnNonDict(e, options);
|
|
1204
1273
|
|
|
1205
1274
|
if(isProxyRequired(e)) {
|
|
1206
1275
|
proxy = getProxyForTargetOf(e);
|
|
1207
1276
|
if(!proxy) {
|
|
1208
1277
|
// option odataXServiceRefs has precedence over odataProxies
|
|
1209
1278
|
if(e._target.$mySchemaName && options.toOdata.odataXServiceRefs) {
|
|
1210
|
-
proxy = createSchemaRefFor(e
|
|
1279
|
+
proxy = createSchemaRefFor(e._target.$mySchemaName);
|
|
1211
1280
|
}
|
|
1212
1281
|
else if(options.toOdata.odataProxies) {
|
|
1213
1282
|
proxy = createProxyFor(e, e._target.$mySchemaName);
|
|
1214
|
-
|
|
1283
|
+
if(!e._target.$isParamEntity)
|
|
1284
|
+
populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
|
|
1215
1285
|
}
|
|
1216
1286
|
proxy = registerProxy(proxy, e);
|
|
1217
1287
|
}
|
|
@@ -1227,7 +1297,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1227
1297
|
setProp(newElt, '$exposed', true);
|
|
1228
1298
|
// _target must be set with (original) in case
|
|
1229
1299
|
// a schema ref has been created
|
|
1300
|
+
setProp(newElt, '$noPartner', true);
|
|
1230
1301
|
setProp(newElt, '_target', e._target);
|
|
1302
|
+
initConstraintsOnAssoc(e);
|
|
1303
|
+
finalizeConstraintsOnAssoc(e);
|
|
1231
1304
|
setProp(newElt, '_constraints', e._constraints);
|
|
1232
1305
|
setProp(newElt, '_selfReferences', []);
|
|
1233
1306
|
if(proxy.kind === 'entity') {
|
|
@@ -1322,11 +1395,13 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1322
1395
|
const alreadyRegistered = csn.definitions[fqProxyName];
|
|
1323
1396
|
if(!alreadyRegistered) {
|
|
1324
1397
|
csn.definitions[fqProxyName] = proxy;
|
|
1398
|
+
reqDefs.definitions[fqProxyName] = proxy;
|
|
1325
1399
|
setProp(proxy, '$path', ['definitions', fqProxyName]);
|
|
1326
1400
|
Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
|
|
1327
1401
|
const fqtn = proxy.$globalSchemaPrefix + '.' + tn;
|
|
1328
1402
|
if(csn.definitions[fqtn] === undefined) {
|
|
1329
1403
|
csn.definitions[fqtn] = v;
|
|
1404
|
+
reqDefs.definitions[fqtn] = v;
|
|
1330
1405
|
setProp(v, '$path', ['definitions', fqtn]);
|
|
1331
1406
|
}
|
|
1332
1407
|
});
|
|
@@ -1372,28 +1447,28 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1372
1447
|
and do not render associations (this will include the foreign keys of
|
|
1373
1448
|
the _isToContainer association).
|
|
1374
1449
|
*/
|
|
1375
|
-
function
|
|
1376
|
-
if(
|
|
1377
|
-
setProp(
|
|
1450
|
+
function initEdmKeyRefPaths(def) {
|
|
1451
|
+
if(def.$keys) {
|
|
1452
|
+
setProp(def, '$edmKeyPaths', []);
|
|
1378
1453
|
// for all key elements that shouldn't be ignored produce the paths
|
|
1379
|
-
foreach(
|
|
1454
|
+
foreach(def.$keys, k => !k._ignore && !(k._isToContainer && k._selfReferences.length), (k, kn) => {
|
|
1380
1455
|
if(isEdmPropertyRendered(k, options) &&
|
|
1381
1456
|
!(options.isV2() && k['@Core.MediaType'])) {
|
|
1382
1457
|
if(options.isV4() && options.isStructFormat) {
|
|
1383
1458
|
// This is structured OData ONLY
|
|
1384
1459
|
// if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
|
|
1385
1460
|
if(options.renderForeignKeys && !k.target)
|
|
1386
|
-
|
|
1461
|
+
def.$edmKeyPaths.push([kn]);
|
|
1387
1462
|
// else produce paths (isEdmPropertyRendered() has filtered @odata.foreignKey4 already)
|
|
1388
1463
|
else if(!options.renderForeignKeys)
|
|
1389
|
-
|
|
1464
|
+
def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
|
|
1390
1465
|
}
|
|
1391
1466
|
// In v2/v4 flat, associations are never rendered
|
|
1392
1467
|
else if(!k.target) {
|
|
1393
|
-
|
|
1468
|
+
def.$edmKeyPaths.push([kn]);
|
|
1394
1469
|
}
|
|
1395
1470
|
// check toplevel key for spec violations
|
|
1396
|
-
checkKeySpecViolations(k, ['definitions',
|
|
1471
|
+
checkKeySpecViolations(k, ['definitions', def.name, 'elements', k.name]);
|
|
1397
1472
|
}
|
|
1398
1473
|
});
|
|
1399
1474
|
}
|
|
@@ -1410,6 +1485,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1410
1485
|
*/
|
|
1411
1486
|
function produceKeyRefPaths(eltCsn, prefix) {
|
|
1412
1487
|
const keyPaths = [];
|
|
1488
|
+
// we want to point to the element in the entity which is the first path step
|
|
1489
|
+
const location = def.$path.concat(['elements']).concat(prefix.split('/')[0]);
|
|
1413
1490
|
if(!isEdmPropertyRendered(eltCsn, options)) {
|
|
1414
1491
|
// let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
|
|
1415
1492
|
// warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
|
|
@@ -1418,7 +1495,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1418
1495
|
}
|
|
1419
1496
|
// OData requires all elements along the path to be nullable: false (that is either key or notNull)
|
|
1420
1497
|
|
|
1421
|
-
const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
|
|
1498
|
+
const finalType = csnUtils.getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
|
|
1422
1499
|
const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
|
|
1423
1500
|
(finalType && (finalType.elements || finalType.items && finalType.items.elements));
|
|
1424
1501
|
if(elements) {
|
|
@@ -1428,8 +1505,6 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1428
1505
|
keyPaths.push(...newRefs);
|
|
1429
1506
|
// check path step key for spec violations
|
|
1430
1507
|
const pathSegment = `${prefix}/${eltName}`;
|
|
1431
|
-
// we want to point to the element in the entity which is the first path step
|
|
1432
|
-
const location = struct.$path.concat(['elements']).concat(pathSegment.split('/')[0]);
|
|
1433
1508
|
checkKeySpecViolations(elt, location, pathSegment);
|
|
1434
1509
|
}
|
|
1435
1510
|
});
|
|
@@ -1444,12 +1519,15 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1444
1519
|
// use the primary keys of the target
|
|
1445
1520
|
let keys = (!eltCsn._target.$isParamEntity && eltCsn.keys) ||
|
|
1446
1521
|
Object.keys(eltCsn._target.$keys).map(k => { return { ref: [k] } });
|
|
1522
|
+
let pathSegment = prefix
|
|
1447
1523
|
keys.forEach(k => {
|
|
1448
|
-
let art = eltCsn._target || getCsnDef(eltCsn.target);
|
|
1524
|
+
let art = eltCsn._target || csnUtils.getCsnDef(eltCsn.target);
|
|
1449
1525
|
for(let ps of k.ref) {
|
|
1450
1526
|
art = art.elements[ps];
|
|
1527
|
+
pathSegment += '/' + art.name
|
|
1528
|
+
checkKeySpecViolations(art, location, pathSegment);
|
|
1451
1529
|
if(art.type && !isBuiltinType(art.type)) {
|
|
1452
|
-
art = art._type || getCsnDef(art.type);
|
|
1530
|
+
art = art._type || csnUtils.getCsnDef(art.type);
|
|
1453
1531
|
}
|
|
1454
1532
|
}
|
|
1455
1533
|
keyPaths.push(...produceKeyRefPaths(art, prefix + options.pathDelimiter + k.ref.join(options.pathDelimiter)));
|
|
@@ -1469,7 +1547,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1469
1547
|
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
|
1470
1548
|
}
|
|
1471
1549
|
// many
|
|
1472
|
-
let type = elt.items || elt.type && !isBuiltinType(elt.type) && getFinalTypeDef(elt.type).items;
|
|
1550
|
+
let type = elt.items || elt.type && !isBuiltinType(elt.type) && csnUtils.getFinalTypeDef(elt.type).items;
|
|
1473
1551
|
if(type) {
|
|
1474
1552
|
error('odata-spec-violation-key-array', location,
|
|
1475
1553
|
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
|
@@ -1483,10 +1561,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1483
1561
|
// V2 allows any Edm.PrimitiveType (even Double and Binary), V4 is more specific:
|
|
1484
1562
|
if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
|
|
1485
1563
|
const edmType = edmUtils.mapCdsToEdmType(type);
|
|
1486
|
-
const legalEdmTypes =
|
|
1487
|
-
'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
|
|
1488
|
-
'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay'
|
|
1489
|
-
if(!
|
|
1564
|
+
const legalEdmTypes = {
|
|
1565
|
+
'Edm.Boolean':1, 'Edm.Byte':1, 'Edm.Date':1, 'Edm.DateTimeOffset':1, 'Edm.Decimal':1, 'Edm.Duration':1,
|
|
1566
|
+
'Edm.Guid':1, 'Edm.Int16':1, 'Edm.Int32':1, 'Edm.Int64':1, 'Edm.SByte':1, 'Edm.String':1, 'Edm.TimeOfDay':1 };
|
|
1567
|
+
if(!(edmType in legalEdmTypes)) {
|
|
1490
1568
|
warning('odata-spec-violation-key-type', location,
|
|
1491
1569
|
{name: pathSegment, type: type.type, id: edmType, '#': pathSegment ? 'std' : 'scalar'});
|
|
1492
1570
|
}
|
|
@@ -1522,10 +1600,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1522
1600
|
Path="items/subitems/subitems/up_" Target="Header/items/subitems"/>
|
|
1523
1601
|
Path="items/subitems/subitems/toG" Target="G"/>
|
|
1524
1602
|
*/
|
|
1525
|
-
function
|
|
1526
|
-
if(
|
|
1527
|
-
forEachGeneric(
|
|
1528
|
-
produceTargetPath([edmUtils.getBaseName(
|
|
1603
|
+
function initEdmNavPropBindingTargets(def) {
|
|
1604
|
+
if(def.$hasEntitySet) {
|
|
1605
|
+
forEachGeneric(def.items || def, 'elements', (element) => {
|
|
1606
|
+
produceTargetPath([edmUtils.getBaseName(def.name)], element, def);
|
|
1529
1607
|
});
|
|
1530
1608
|
}
|
|
1531
1609
|
|
|
@@ -1556,13 +1634,13 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1556
1634
|
}
|
|
1557
1635
|
}
|
|
1558
1636
|
|
|
1559
|
-
function
|
|
1560
|
-
if(options.isV4() &&
|
|
1637
|
+
function initEdmNavPropBindingPaths(def) {
|
|
1638
|
+
if(options.isV4() &&def.$hasEntitySet) {
|
|
1561
1639
|
let npbs = [];
|
|
1562
|
-
forEachGeneric(
|
|
1563
|
-
npbs = npbs.concat(produceNavigationPath(element,
|
|
1640
|
+
forEachGeneric(def.items || def, 'elements', (element) => {
|
|
1641
|
+
npbs = npbs.concat(produceNavigationPath(element, def));
|
|
1564
1642
|
});
|
|
1565
|
-
setProp(
|
|
1643
|
+
setProp(def, '$edmNPBs', npbs);
|
|
1566
1644
|
}
|
|
1567
1645
|
|
|
1568
1646
|
// collect all paths originating from this element that end up in an entity set
|
|
@@ -1576,7 +1654,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1576
1654
|
// drill into target only if
|
|
1577
1655
|
// 1) target has no entity set and this assoc is not going to the container
|
|
1578
1656
|
// 2) current definition and target are the same (cycle)
|
|
1579
|
-
if(!elt
|
|
1657
|
+
if(!elt.$externalRef &&
|
|
1658
|
+
!elt._target.$hasEntitySet &&
|
|
1659
|
+
!elt._isToContainer &&
|
|
1660
|
+
curDef !== elt._target) {
|
|
1580
1661
|
// follow elements in the target but avoid cycles
|
|
1581
1662
|
setProp(elt, '$touched', true);
|
|
1582
1663
|
Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
|
|
@@ -1586,16 +1667,18 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1586
1667
|
// end point reached but must not be an external reference nor a proxy nor a composition itself
|
|
1587
1668
|
// last assoc step must not be to-n and target a singleton
|
|
1588
1669
|
let p = undefined;
|
|
1589
|
-
if (!elt
|
|
1590
|
-
|
|
1670
|
+
if (!elt.$externalRef &&
|
|
1671
|
+
!(edmUtils.isToMany(elt) &&
|
|
1672
|
+
edmUtils.isSingleton(elt._target) &&
|
|
1673
|
+
options.isV4())) {
|
|
1591
1674
|
if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
|
|
1592
|
-
p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(
|
|
1675
|
+
p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(def.name)) || elt._target.$edmTgtPaths[0];
|
|
1593
1676
|
}
|
|
1594
1677
|
else if(elt._target.$hasEntitySet) {
|
|
1595
1678
|
const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
|
|
1596
1679
|
// if own struct and target have a set they either are in the same $mySchemaName or not
|
|
1597
1680
|
// if target is in another schema, target the full qualified entity set
|
|
1598
|
-
p = (elt._target.$mySchemaName ===
|
|
1681
|
+
p = (elt._target.$mySchemaName === def.$mySchemaName) ?
|
|
1599
1682
|
[ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
|
|
1600
1683
|
}
|
|
1601
1684
|
if(p) {
|
|
@@ -1625,28 +1708,28 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1625
1708
|
}
|
|
1626
1709
|
}
|
|
1627
1710
|
|
|
1628
|
-
function determineEntitySet(
|
|
1711
|
+
function determineEntitySet(def) {
|
|
1629
1712
|
// if this is an entity or a view, determine if an entity set is required or not
|
|
1630
1713
|
// 1) must not be a proxy and not a containee in V4
|
|
1631
1714
|
// No annos are rendered for non-existing EntitySet targets.
|
|
1632
|
-
if(
|
|
1633
|
-
const hasEntitySet = isEntity(
|
|
1634
|
-
setProp(
|
|
1715
|
+
if(def.$hasEntitySet === undefined) {
|
|
1716
|
+
const hasEntitySet = isEntity(def) && !(options.isV4() && edmUtils.isContainee(def)) && !def.$proxy;
|
|
1717
|
+
setProp(def, '$hasEntitySet', hasEntitySet);
|
|
1635
1718
|
}
|
|
1636
1719
|
}
|
|
1637
1720
|
|
|
1638
|
-
function
|
|
1721
|
+
function initEdmTypesAndDescription(def) {
|
|
1639
1722
|
// 1. let all doc props become @Core.Descriptions
|
|
1640
1723
|
// 2. mark a member that will become a collection
|
|
1641
1724
|
// 3. assign the edm primitive type to elements, to be used in the rendering later
|
|
1642
|
-
assignAnnotation(
|
|
1643
|
-
markCollection(
|
|
1644
|
-
mapCdsToEdmProp(
|
|
1645
|
-
if (
|
|
1646
|
-
markCollection(
|
|
1647
|
-
mapCdsToEdmProp(
|
|
1648
|
-
}
|
|
1649
|
-
forEachMemberRecursively(
|
|
1725
|
+
assignAnnotation(def, '@Core.Description', def.doc);
|
|
1726
|
+
markCollection(def);
|
|
1727
|
+
mapCdsToEdmProp(def);
|
|
1728
|
+
if (def.returns) {
|
|
1729
|
+
markCollection(def.returns);
|
|
1730
|
+
mapCdsToEdmProp(def.returns);
|
|
1731
|
+
}
|
|
1732
|
+
forEachMemberRecursively(def,member => {
|
|
1650
1733
|
assignAnnotation(member, '@Core.Description', member.doc);
|
|
1651
1734
|
markCollection(member);
|
|
1652
1735
|
mapCdsToEdmProp(member);
|
|
@@ -1656,7 +1739,6 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1656
1739
|
mapCdsToEdmProp(member.returns);
|
|
1657
1740
|
}
|
|
1658
1741
|
});
|
|
1659
|
-
|
|
1660
1742
|
// mark members that need to be rendered as collections
|
|
1661
1743
|
function markCollection(obj) {
|
|
1662
1744
|
const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
|
|
@@ -1673,19 +1755,37 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1673
1755
|
// Checks section starts here
|
|
1674
1756
|
//
|
|
1675
1757
|
|
|
1758
|
+
// eslint-disable-next-line no-unused-vars
|
|
1759
|
+
function resolveForeignKeyRefs() {
|
|
1760
|
+
forEachDefinition(csn, (def, defName) => {
|
|
1761
|
+
let currPath = ['definitions', defName ];
|
|
1762
|
+
forEachMemberRecursively(def, (construct, _constructName, _prop, path) => {
|
|
1763
|
+
if(construct.target && construct.keys) {
|
|
1764
|
+
construct.keys.forEach((fk, i) => {
|
|
1765
|
+
setProp(fk, '_artifact', csnUtils.inspectRef([...path, 'keys', i]).art);
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
}, currPath, true, { elementsOnly: true });
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
|
|
1676
1773
|
function inboundQualificationChecks() {
|
|
1677
|
-
forEachDefinition(csn, [ checkChainedArray ]);
|
|
1774
|
+
forEachDefinition(csn, [ attach$path, checkChainedArray ]);
|
|
1678
1775
|
checkNestedContextsAndServices();
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
//
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1776
|
+
throwWithAnyError();
|
|
1777
|
+
|
|
1778
|
+
// attach $path to all
|
|
1779
|
+
function attach$path(def, defName) {
|
|
1780
|
+
setProp(def, '$path', [ 'definitions', defName ]);
|
|
1781
|
+
forEachMemberRecursively(def,
|
|
1782
|
+
(member, _memberName, _prop, path) => {
|
|
1783
|
+
setProp(member, '$path', path);
|
|
1784
|
+
}, [ 'definitions', defName ]);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1687
1787
|
function checkChainedArray(def, defName) {
|
|
1688
|
-
if (!
|
|
1788
|
+
if (!isMyServiceRequested(defName))
|
|
1689
1789
|
return;
|
|
1690
1790
|
let currPath = ['definitions', defName];
|
|
1691
1791
|
checkIfItemsOfItems(def, undefined, undefined, currPath);
|
|
@@ -1695,13 +1795,13 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1695
1795
|
const constructType = csnUtils.effectiveType(construct);
|
|
1696
1796
|
if (constructType.items) {
|
|
1697
1797
|
if (constructType.items.items) {
|
|
1698
|
-
|
|
1798
|
+
message('chained-array-of', path);
|
|
1699
1799
|
return;
|
|
1700
1800
|
}
|
|
1701
1801
|
|
|
1702
1802
|
const itemsType = csnUtils.effectiveType(constructType.items);
|
|
1703
1803
|
if (itemsType.items)
|
|
1704
|
-
|
|
1804
|
+
message('chained-array-of', path);
|
|
1705
1805
|
}
|
|
1706
1806
|
}
|
|
1707
1807
|
}
|
|
@@ -1709,7 +1809,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1709
1809
|
function checkNestedContextsAndServices() {
|
|
1710
1810
|
!isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
|
|
1711
1811
|
const parent = whatsMyServiceRootName(sn, false);
|
|
1712
|
-
if(parent && parent !== sn) {
|
|
1812
|
+
if(parent && requestedServiceNames.includes(parent) && parent !== sn) {
|
|
1713
1813
|
message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
|
|
1714
1814
|
'A service can\'t be nested within a service $(ART)' );
|
|
1715
1815
|
}
|
|
@@ -1718,7 +1818,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1718
1818
|
Object.entries(csn.definitions).forEach(([fqName, art]) => {
|
|
1719
1819
|
if(art.kind === 'context') {
|
|
1720
1820
|
const parent = whatsMyServiceRootName(fqName);
|
|
1721
|
-
if(parent) {
|
|
1821
|
+
if(requestedServiceNames.includes(parent)) {
|
|
1722
1822
|
message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
|
|
1723
1823
|
'A context can\'t be nested within a service $(ART)' );
|
|
1724
1824
|
}
|
|
@@ -1727,54 +1827,6 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1727
1827
|
}
|
|
1728
1828
|
}
|
|
1729
1829
|
|
|
1730
|
-
/**
|
|
1731
|
-
*
|
|
1732
|
-
* @param {String} identifier the illegal identifier
|
|
1733
|
-
* @param {CSN.Path} path
|
|
1734
|
-
*/
|
|
1735
|
-
function signalIllegalIdentifier(identifier, path) {
|
|
1736
|
-
error(null, path, { id: identifier },
|
|
1737
|
-
'OData identifier $(ID) must start with a letter or underscore, followed by at most 127 letters, underscores or digits'
|
|
1738
|
-
);
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
// { '#': this.csnUtils.isComposition(member.type) ? 'cmp' : 'std' },
|
|
1743
|
-
// {
|
|
1744
|
-
// std: 'An association can\'t have cardinality "to many" without an ON-condition',
|
|
1745
|
-
// cmp: 'A composition can\'t have cardinality "to many" without an ON-condition',
|
|
1746
|
-
// }
|
|
1747
|
-
|
|
1748
|
-
// Check the artifact identifier for compliance with the odata specification
|
|
1749
|
-
function checkArtifactIdentifierAndBoundActions(artifact) {
|
|
1750
|
-
if(artifact.$mySchemaName) {
|
|
1751
|
-
const artifactName = artifact.name.replace(`${artifact.$mySchemaName }.`, '');
|
|
1752
|
-
// if the artifact has bound actions, check the action identifiers and their param identifiers to be OData compliant
|
|
1753
|
-
if(artifact.actions) {
|
|
1754
|
-
Object.keys(artifact.actions).forEach(identifier => checkActionOrFunctionIdentifier(artifact.actions[identifier], identifier))
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
// if the artifact is an unbound function check it's identifer
|
|
1758
|
-
if(isActionOrFunction(artifact)){
|
|
1759
|
-
checkActionOrFunctionIdentifier(artifact, artifactName);
|
|
1760
|
-
} else if(![ 'service', 'context', 'event', 'aspect' ].includes(artifact.kind) && !isODataSimpleIdentifier(artifactName)) {
|
|
1761
|
-
signalIllegalIdentifier(artifactName, ['definitions', artifact.name]);
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
function checkActionOrFunctionIdentifier(actionOrFunction, actionOrFunctionName) {
|
|
1766
|
-
if(!isODataSimpleIdentifier(actionOrFunctionName)){
|
|
1767
|
-
signalIllegalIdentifier(actionOrFunctionName, actionOrFunction.$path);
|
|
1768
|
-
}
|
|
1769
|
-
if(actionOrFunction.params) {
|
|
1770
|
-
forEachGeneric(actionOrFunction, 'params', (param) => {
|
|
1771
|
-
if(!isODataSimpleIdentifier(param.name)){
|
|
1772
|
-
signalIllegalIdentifier(param.name, param.$path);
|
|
1773
|
-
}
|
|
1774
|
-
});
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
1830
|
//
|
|
1779
1831
|
// Checks Secition ends here
|
|
1780
1832
|
//
|
|
@@ -1863,7 +1915,29 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1863
1915
|
}, { });
|
|
1864
1916
|
// if dictionary has entries, add them to navPropEnty
|
|
1865
1917
|
if(Object.keys(o).length) {
|
|
1866
|
-
|
|
1918
|
+
// ReadRestrictions may have sub type ReadByKeyRestrictions { Description, LongDescription }
|
|
1919
|
+
// chop annotations into dictionaries
|
|
1920
|
+
if(prefix === '@Capabilities.ReadRestrictions' &&
|
|
1921
|
+
Object.keys(o).some(k => k.startsWith('ReadByKeyRestrictions.'))) {
|
|
1922
|
+
const no = {};
|
|
1923
|
+
Object.entries(o).forEach(([k,v]) => {
|
|
1924
|
+
const [head, ...tail] = k.split('.');
|
|
1925
|
+
if(head === 'ReadByKeyRestrictions' && tail.length) {
|
|
1926
|
+
if(!no['ReadByKeyRestrictions'])
|
|
1927
|
+
no['ReadByKeyRestrictions'] = {};
|
|
1928
|
+
// Don't try to add entry into non object
|
|
1929
|
+
if(typeof no['ReadByKeyRestrictions'] === 'object')
|
|
1930
|
+
no['ReadByKeyRestrictions'][tail.join('.')] = v;
|
|
1931
|
+
}
|
|
1932
|
+
else {
|
|
1933
|
+
no[k] = v;
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
navPropEntry[prop] = no;
|
|
1937
|
+
}
|
|
1938
|
+
else {
|
|
1939
|
+
navPropEntry[prop] = o;
|
|
1940
|
+
}
|
|
1867
1941
|
newEntry = true;
|
|
1868
1942
|
}
|
|
1869
1943
|
}
|
|
@@ -1887,19 +1961,15 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1887
1961
|
if (obj.type && isBuiltinType(obj.type) && !isAssociationOrComposition(obj) && !obj.targetAspect) {
|
|
1888
1962
|
let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
|
|
1889
1963
|
assignProp(obj, '_edmType', edmType);
|
|
1890
|
-
} else if (obj._isCollection && (obj.items && isBuiltinType(getFinalTypeDef(obj.items.type)))) {
|
|
1891
|
-
let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
|
|
1964
|
+
} else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.type)))) {
|
|
1965
|
+
let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType'], obj.$path);
|
|
1892
1966
|
assignProp(obj, '_edmType', edmType);
|
|
1893
1967
|
}
|
|
1894
1968
|
// This is the special case when we have array of array, but will not be supported in the future
|
|
1895
|
-
else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(getFinalTypeDef(obj.items.items.type))) {
|
|
1969
|
+
else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.items.type))) {
|
|
1896
1970
|
let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
|
|
1897
1971
|
assignProp(obj, '_edmType', edmType);
|
|
1898
1972
|
}
|
|
1899
|
-
|
|
1900
|
-
// check against the value of the @odata.Type annotation
|
|
1901
|
-
if (obj['@odata.Type'] && !['Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.String'].includes(obj['@odata.Type']))
|
|
1902
|
-
info(null, obj.$location, { type: obj['@odata.Type'] }, "@odata.Type: $(TYPE) is ignored, only Edm.String and Edm.Int[16,32,64] are allowed");
|
|
1903
1973
|
}
|
|
1904
1974
|
|
|
1905
1975
|
function ComputedDefaultValue(member) {
|
|
@@ -2286,4 +2356,5 @@ function assignProp(obj, prop, value) {
|
|
|
2286
2356
|
|
|
2287
2357
|
module.exports = {
|
|
2288
2358
|
initializeModel,
|
|
2359
|
+
assignAnnotation
|
|
2289
2360
|
}
|