@sap/cds-compiler 3.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +19 -0
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +24 -2
- package/doc/CHANGELOG_DEPRECATED.md +21 -1
- package/lib/api/main.js +7 -7
- package/lib/api/options.js +2 -3
- package/lib/base/message-registry.js +17 -5
- package/lib/base/messages.js +18 -39
- package/lib/base/model.js +2 -0
- package/lib/checks/actionsFunctions.js +8 -7
- package/lib/checks/selectItems.js +96 -14
- package/lib/checks/types.js +5 -8
- package/lib/checks/validator.js +1 -2
- package/lib/compiler/assert-consistency.js +64 -12
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +58 -8
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +25 -22
- package/lib/compiler/extend.js +16 -10
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +34 -31
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +14 -15
- package/lib/compiler/shared.js +53 -26
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +13 -4
- package/lib/edm/annotations/preprocessAnnotations.js +8 -4
- package/lib/edm/csn2edm.js +3 -3
- package/lib/edm/edm.js +9 -1
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +295 -638
- package/lib/edm/edmUtils.js +85 -5
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -2
- package/lib/gen/languageLexer.js +3 -0
- package/lib/gen/languageParser.js +4344 -4530
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +8 -6
- package/lib/language/genericAntlrParser.js +121 -63
- package/lib/language/language.g4 +19 -57
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +55 -29
- package/lib/model/csnUtils.js +11 -7
- package/lib/model/revealInternalProperties.js +2 -3
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +27 -0
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toSql.js +24 -8
- package/lib/render/utils/common.js +3 -4
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +0 -1
- package/lib/transform/db/flattening.js +3 -4
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/forHanaNew.js +11 -2
- package/lib/transform/forOdataNew.js +1 -1
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +2 -2
- package/lib/checks/unknownMagic.js +0 -41
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/* eslint max-statements-per-line:off */
|
|
3
3
|
const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
|
|
4
|
-
const {
|
|
5
|
-
|
|
4
|
+
const {
|
|
5
|
+
forEachDefinition, forEachGeneric, forEachMemberRecursively,
|
|
6
|
+
isEdmPropertyRendered, getUtils, cloneCsnNonDict, cloneCsnDictionary,
|
|
7
|
+
isBuiltinType, applyTransformationsOnNonDictionary
|
|
8
|
+
} = require('../model/csnUtils');
|
|
6
9
|
const edmUtils = require('./edmUtils.js');
|
|
10
|
+
const edmAnnoPreproc = require('./edmAnnoPreprocessor.js');
|
|
11
|
+
const { inboundQualificationChecks } = require('./edmInboundChecks.js');
|
|
7
12
|
const typesExposure = require('../transform/odata/typesExposure');
|
|
8
13
|
const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
|
|
9
14
|
|
|
10
|
-
const
|
|
11
|
-
intersect,
|
|
12
|
-
validateOptions,
|
|
13
|
-
foreach,
|
|
14
|
-
isComposition,
|
|
15
|
-
isStructuredArtifact,
|
|
16
|
-
isParameterizedEntity,
|
|
17
|
-
resolveOnConditionAndPrepareConstraints,
|
|
18
|
-
finalizeReferentialConstraints,
|
|
19
|
-
getSchemaPrefix,
|
|
20
|
-
} = require('./edmUtils.js');
|
|
15
|
+
const NavResAnno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
|
|
21
16
|
|
|
22
17
|
/**
|
|
23
18
|
* edmPreprocessor warms up the model so that it can be converted into an EDM document and
|
|
@@ -31,9 +26,9 @@ const {
|
|
|
31
26
|
*/
|
|
32
27
|
function initializeModel(csn, _options, messageFunctions, requestedServiceNames=undefined)
|
|
33
28
|
{
|
|
34
|
-
const { info, warning, error, message
|
|
29
|
+
const { info, warning, error, message } = messageFunctions;
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
const csnUtils = getUtils(csn);
|
|
37
32
|
|
|
38
33
|
// proxies are merged into the final model after all proxy elements are collected
|
|
39
34
|
const proxyCache = [];
|
|
@@ -44,7 +39,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
44
39
|
|
|
45
40
|
|
|
46
41
|
// make sure options are complete
|
|
47
|
-
let options = validateOptions(_options);
|
|
42
|
+
let options = edmUtils.validateOptions(_options);
|
|
48
43
|
|
|
49
44
|
const [ serviceRoots,
|
|
50
45
|
serviceRootNames,
|
|
@@ -66,7 +61,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
// Structural CSN inbound QA checks
|
|
69
|
-
inboundQualificationChecks(
|
|
64
|
+
inboundQualificationChecks(csn, options, messageFunctions,
|
|
65
|
+
serviceRootNames, requestedServiceNames, isMyServiceRequested, whatsMyServiceRootName);
|
|
70
66
|
// not needed at the moment
|
|
71
67
|
// resolveForeignKeyRefs();
|
|
72
68
|
|
|
@@ -124,7 +120,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
124
120
|
if(isMyServiceRequested(defName) && def.kind !== 'aspect')
|
|
125
121
|
reqDefs.definitions[defName] = def;
|
|
126
122
|
},
|
|
127
|
-
linkAssociationTarget
|
|
123
|
+
linkAssociationTarget
|
|
124
|
+
]);
|
|
128
125
|
// initialize requested services
|
|
129
126
|
const skip = { skipArtifact: (_def, defName) => !isMyServiceRequested(defName) };
|
|
130
127
|
forEachDefinition({ definitions: serviceRoots }, initService, skip);
|
|
@@ -139,23 +136,32 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
139
136
|
// Mute V4 elements depending on constraint preparation
|
|
140
137
|
if(options.isV4())
|
|
141
138
|
forEachDefinition(reqDefs, ignoreProperties);
|
|
142
|
-
// calculate constraints based on ignoreProperties and
|
|
139
|
+
// calculate constraints based on ignoreProperties and initConstraints
|
|
143
140
|
forEachDefinition(reqDefs, finalizeConstraints);
|
|
144
141
|
// convert exposed types into cross schema references if required
|
|
145
142
|
// must be run before proxy exposure to avoid potential reference collisions
|
|
146
143
|
convertExposedTypesOfOtherServicesIntoCrossReferences();
|
|
147
|
-
// create association target proxies
|
|
144
|
+
// create association target proxies (v4)
|
|
148
145
|
// Decide if an entity set needs to be constructed or not
|
|
149
|
-
forEachDefinition(reqDefs, [
|
|
146
|
+
forEachDefinition(reqDefs, [
|
|
147
|
+
exposeTargetsAsProxiesOrSchemaRefs,
|
|
148
|
+
determineEntitySet
|
|
149
|
+
]);
|
|
150
150
|
// finalize proxy creation
|
|
151
151
|
mergeProxiesIntoModel();
|
|
152
152
|
|
|
153
|
+
// Calculate NavPropBinding Target paths
|
|
154
|
+
// Rewrite @Capabilities for containment mode
|
|
153
155
|
if(options.isV4())
|
|
154
|
-
forEachDefinition(reqDefs,
|
|
156
|
+
forEachDefinition(reqDefs, [
|
|
157
|
+
initEdmNavPropBindingTargets,
|
|
158
|
+
rewriteContainmentAnnotations,
|
|
159
|
+
annotateOptionalActFuncParams
|
|
160
|
+
]);
|
|
155
161
|
|
|
156
162
|
// Things that can be done in one pass
|
|
157
163
|
// Create edmKeyRefPaths
|
|
158
|
-
// Create NavigationPropertyBindings, requires determineEntitySet
|
|
164
|
+
// Create V4 NavigationPropertyBindings, requires determineEntitySet & initEdmNavPropBindingTargets
|
|
159
165
|
// Map /** doc comments */ to @CoreDescription
|
|
160
166
|
forEachDefinition(reqDefs, [
|
|
161
167
|
initEdmKeyRefPaths,
|
|
@@ -163,7 +169,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
163
169
|
initEdmTypesAndDescription
|
|
164
170
|
]);
|
|
165
171
|
}
|
|
166
|
-
return [
|
|
172
|
+
return [
|
|
173
|
+
serviceRoots,
|
|
174
|
+
schemas,
|
|
175
|
+
reqDefs,
|
|
176
|
+
whatsMyServiceRootName,
|
|
177
|
+
fallBackSchemaName,
|
|
178
|
+
options
|
|
179
|
+
];
|
|
167
180
|
|
|
168
181
|
//////////////////////////////////////////////////////////////////////
|
|
169
182
|
//
|
|
@@ -233,7 +246,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
233
246
|
const rootDef = getRootDef(defName);
|
|
234
247
|
// if this definition has a root def and the root def is not the service/schema name
|
|
235
248
|
// => service C { type D.E }, replace the prefix dots with underscores
|
|
236
|
-
if(rootDef && defName !== rootDef && rootDef !== getSchemaPrefix(defName)) {
|
|
249
|
+
if(rootDef && defName !== rootDef && rootDef !== edmUtils.getSchemaPrefix(defName)) {
|
|
237
250
|
let newDefName = rootDef + '.' + defName.replace(rootDef + '.', '').replace(/\./g, '_');
|
|
238
251
|
// store renamed types in correlation maps for later renaming
|
|
239
252
|
if(def.kind === 'entity')
|
|
@@ -297,7 +310,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
297
310
|
forEachDefinition(csn, (def, defName) => {
|
|
298
311
|
if(def.kind !== 'service') {
|
|
299
312
|
const myServiceRoot = whatsMyServiceRootName(defName);
|
|
300
|
-
const mySchemaPrefix = getSchemaPrefix(defName);
|
|
313
|
+
const mySchemaPrefix = edmUtils.getSchemaPrefix(defName);
|
|
301
314
|
if(myServiceRoot && options.isV4() &&
|
|
302
315
|
/*(options.odataProxies || options.odataXServiceRefs) && options.isStructFormat && */
|
|
303
316
|
defName !== myServiceRoot && myServiceRoot !== mySchemaPrefix) {
|
|
@@ -311,23 +324,23 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
311
324
|
}
|
|
312
325
|
|
|
313
326
|
function attachNameProperty(def, defName) {
|
|
314
|
-
assignProp (def, 'name', defName);
|
|
327
|
+
edmUtils.assignProp (def, 'name', defName);
|
|
315
328
|
// Attach name to bound actions, functions and parameters
|
|
316
329
|
forEachGeneric(def, 'actions', (a, n) => {
|
|
317
|
-
assignProp(a, 'name', n);
|
|
330
|
+
edmUtils.assignProp(a, 'name', n);
|
|
318
331
|
forEachGeneric(a, 'params', (p, n) => {
|
|
319
|
-
assignProp(p, 'name', n);
|
|
332
|
+
edmUtils.assignProp(p, 'name', n);
|
|
320
333
|
});
|
|
321
334
|
});
|
|
322
335
|
// Attach name unbound action parameters
|
|
323
336
|
forEachGeneric(def, 'params', (p,n) => {
|
|
324
|
-
assignProp(p, 'name', n);
|
|
337
|
+
edmUtils.assignProp(p, 'name', n);
|
|
325
338
|
});
|
|
326
339
|
}
|
|
327
340
|
|
|
328
341
|
// initialize the service itself
|
|
329
342
|
function initService(serviceRoot) {
|
|
330
|
-
setSAPSpecificV2AnnotationsToEntityContainer(options, serviceRoot);
|
|
343
|
+
edmAnnoPreproc.setSAPSpecificV2AnnotationsToEntityContainer(options, serviceRoot);
|
|
331
344
|
}
|
|
332
345
|
|
|
333
346
|
// link association target to association and add @odata.contained to compositions in V4
|
|
@@ -338,7 +351,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
338
351
|
if(target) {
|
|
339
352
|
setProp(element, '_target', target);
|
|
340
353
|
// If target has parameters, xref assoc at target for redirection
|
|
341
|
-
if(isParameterizedEntity(target)) {
|
|
354
|
+
if(edmUtils.isParameterizedEntity(target)) {
|
|
342
355
|
if(!target.$sources) {
|
|
343
356
|
setProp(target, '$sources', Object.create(null));
|
|
344
357
|
}
|
|
@@ -352,7 +365,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
352
365
|
// in V4 tag all compositions to be containments
|
|
353
366
|
if(options.odataContainment &&
|
|
354
367
|
options.isV4() &&
|
|
355
|
-
isComposition(element) &&
|
|
368
|
+
edmUtils.isComposition(element) &&
|
|
356
369
|
element['@odata.contained'] === undefined) {
|
|
357
370
|
element['@odata.contained'] = true;
|
|
358
371
|
}
|
|
@@ -362,7 +375,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
362
375
|
// Perform checks and add attributes for "contained" sub-entities:
|
|
363
376
|
// - A container is recognized by having an association/composition annotated with '@odata.contained'.
|
|
364
377
|
// - All targets of such associations ("containees") are marked with a property
|
|
365
|
-
// '
|
|
378
|
+
// '$containerNames: []', having as value an array of container names (i.e. of entities
|
|
366
379
|
// that have a '@odata.contained' association pointing to the containee). Note that this
|
|
367
380
|
// may be multiple entities, possibly including the container itself.
|
|
368
381
|
// - All associations in the containee pointing back to the container are marked with
|
|
@@ -372,35 +385,41 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
372
385
|
// non-containment rendering. If containment rendering is active, the containee has no
|
|
373
386
|
// entity set. Instead try to rewrite the annotation in such a way that it is effective
|
|
374
387
|
// on the containment navigation property.
|
|
388
|
+
// $containeeAssoications stores the containees (children/outbound edges)
|
|
389
|
+
// $containerNames stores the containers (parents/inbound edges)
|
|
390
|
+
|
|
375
391
|
function initContainments(container) {
|
|
376
392
|
if(container.kind === 'entity') {
|
|
393
|
+
if(!container.$containeeAssociations)
|
|
394
|
+
setProp(container, '$containeeAssociations', []);
|
|
377
395
|
forEachMemberRecursively(container, initContainments,
|
|
378
|
-
[], true, { elementsOnly: true });
|
|
396
|
+
[], true, { pathWithoutProp: true, elementsOnly: true });
|
|
379
397
|
}
|
|
380
398
|
|
|
381
|
-
function initContainments(elt,
|
|
399
|
+
function initContainments(elt, _memberName, _prop, path) {
|
|
382
400
|
if(elt.target && elt['@odata.contained'] && !elt._ignore) {
|
|
401
|
+
// store all containment associations, required to create the containment paths later on
|
|
402
|
+
container.$containeeAssociations.push( { assoc: elt, path });
|
|
383
403
|
// Let the containee know its container
|
|
384
404
|
// (array because the contanee may contained more then once)
|
|
385
405
|
let containee = elt._target;
|
|
386
|
-
if (!containee
|
|
387
|
-
setProp(containee, '
|
|
406
|
+
if (!containee.$containerNames)
|
|
407
|
+
setProp(containee, '$containerNames', []);
|
|
388
408
|
// add container only once per containee
|
|
389
|
-
if (!containee.
|
|
390
|
-
containee.
|
|
409
|
+
if (!containee.$containerNames.includes(container.name))
|
|
410
|
+
containee.$containerNames.push(container.name);
|
|
391
411
|
// Mark associations in the containee pointing to the container (i.e. to this entity)
|
|
392
412
|
forEachMemberRecursively(containee, markToContainer,
|
|
393
|
-
[], true, { elementsOnly: true });
|
|
394
|
-
rewriteContainmentAnnotations(container, containee, eltName);
|
|
413
|
+
[ 'definitions', containee.name ], true, { elementsOnly: true });
|
|
395
414
|
}
|
|
396
|
-
else {
|
|
415
|
+
else if(elt.type && !elt.elements) {
|
|
397
416
|
// try to find elements to drill down further
|
|
398
|
-
while(elt && !
|
|
417
|
+
while(elt && !isBuiltinType(elt.type) && !elt.elements) {
|
|
399
418
|
elt = csn.definitions[elt.type];
|
|
400
419
|
}
|
|
401
420
|
if(elt && elt.elements) {
|
|
402
421
|
forEachMemberRecursively(elt, initContainments,
|
|
403
|
-
|
|
422
|
+
path, true, { pathWithoutProp: true, elementsOnly: true });
|
|
404
423
|
}
|
|
405
424
|
}
|
|
406
425
|
}
|
|
@@ -431,13 +450,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
431
450
|
// Containment processing must take place before because it might be that this
|
|
432
451
|
// artifact with parameters is already contained. In such a case the existing
|
|
433
452
|
// containment chain must be propagated and reused. This requires that the
|
|
434
|
-
// containment data structures must be manually added here
|
|
435
|
-
// must be called.
|
|
453
|
+
// containment data structures must be manually added here.
|
|
436
454
|
// As a param entity is a potential proxy candidate, this split must be performed on
|
|
437
455
|
// all definitions
|
|
438
456
|
function initParameterizedEntityOrView(entityCsn, entityName) {
|
|
439
457
|
|
|
440
|
-
if(!isParameterizedEntity(entityCsn))
|
|
458
|
+
if(!edmUtils.isParameterizedEntity(entityCsn))
|
|
441
459
|
return;
|
|
442
460
|
|
|
443
461
|
// Naming rules for aggregated views with parameters
|
|
@@ -454,7 +472,6 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
454
472
|
// this code can be extended for aggregated views
|
|
455
473
|
const typeEntityName = entityName + 'Type';
|
|
456
474
|
const typeEntitySetName = entityName + 'Set';
|
|
457
|
-
const parameterToTypeAssocName = 'Set';
|
|
458
475
|
const typeToParameterAssocName = 'Parameters';
|
|
459
476
|
let hasBacklink = true;
|
|
460
477
|
|
|
@@ -500,7 +517,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
500
517
|
<EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
|
|
501
518
|
sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
|
|
502
519
|
*/
|
|
503
|
-
assignProp(entityCsn, '_SetAttributes',
|
|
520
|
+
edmUtils.assignProp(entityCsn, '_SetAttributes',
|
|
504
521
|
{'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });
|
|
505
522
|
|
|
506
523
|
// redirect inbound associations/compositions to the parameter entity
|
|
@@ -510,7 +527,6 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
510
527
|
entityCsn.$sources[n]._target = parameterCsn;
|
|
511
528
|
entityCsn.$sources[n].target = parameterCsn.name;
|
|
512
529
|
});
|
|
513
|
-
rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToTypeAssocName);
|
|
514
530
|
}
|
|
515
531
|
|
|
516
532
|
function createParameterEntity(entityCsn, entityName, isProxy) {
|
|
@@ -527,7 +543,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
527
543
|
if(!isProxy)
|
|
528
544
|
setProp(parameterCsn, '$entitySetName', entityName);
|
|
529
545
|
if(entityCsn.$location){
|
|
530
|
-
assignProp(parameterCsn, '$location', entityCsn.$location);
|
|
546
|
+
edmUtils.assignProp(parameterCsn, '$location', entityCsn.$location);
|
|
531
547
|
}
|
|
532
548
|
|
|
533
549
|
/*
|
|
@@ -535,21 +551,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
535
551
|
sap:deletable="false" sap:pageable="false" sap:content-version="1"/>
|
|
536
552
|
*/
|
|
537
553
|
|
|
538
|
-
assignProp(parameterCsn, '_SetAttributes',
|
|
554
|
+
edmUtils.assignProp(parameterCsn, '_SetAttributes',
|
|
539
555
|
{'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });
|
|
540
556
|
|
|
541
557
|
setProp(parameterCsn, '$isParamEntity', true);
|
|
542
558
|
setProp(parameterCsn, '$mySchemaName', entityCsn.$mySchemaName);
|
|
543
559
|
|
|
544
|
-
// propagate containment information, if containment is recursive, use parameterCsn.name as _containerEntity
|
|
545
|
-
if(entityCsn._containerEntity) {
|
|
546
|
-
setProp(parameterCsn, '_containerEntity', []);
|
|
547
|
-
for(const c of entityCsn._containerEntity) {
|
|
548
|
-
parameterCsn._containerEntity.push((c === entityCsn.name) ? parameterCsn.name : c);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
entityCsn._containerEntity = [ parameterCsn ];
|
|
552
|
-
|
|
553
560
|
forEachGeneric(entityCsn, 'params', (p,n) => {
|
|
554
561
|
let elt = cloneCsnNonDict(p, options);
|
|
555
562
|
elt.name = n;
|
|
@@ -564,9 +571,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
564
571
|
and so they are in CAP (all view parameters become primary keys which are not null).
|
|
565
572
|
*/
|
|
566
573
|
if(options.isV2())
|
|
567
|
-
assignAnnotation(elt, '@sap.parameter', 'mandatory');
|
|
574
|
+
edmUtils.assignAnnotation(elt, '@sap.parameter', 'mandatory');
|
|
568
575
|
else
|
|
569
|
-
assignAnnotation(elt, '@Common.FieldControl', { '#': 'Mandatory' });
|
|
576
|
+
edmUtils.assignAnnotation(elt, '@Common.FieldControl', { '#': 'Mandatory' });
|
|
570
577
|
parameterCsn.elements[n] = elt;
|
|
571
578
|
});
|
|
572
579
|
linkAssociationTarget(parameterCsn);
|
|
@@ -584,7 +591,26 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
584
591
|
setProp(parameterCsn.elements[parameterToTypeAssocName], '$path',
|
|
585
592
|
[ 'definitions', parameterEntityName, 'elements', parameterToTypeAssocName ] );
|
|
586
593
|
}
|
|
587
|
-
|
|
594
|
+
|
|
595
|
+
// initialize containment
|
|
596
|
+
// propagate containment information, if containment is recursive, use parameterCsn.name as $containerNames
|
|
597
|
+
if(entityCsn.$containerNames) {
|
|
598
|
+
if(!parameterCsn.$containerNames)
|
|
599
|
+
setProp(parameterCsn, '$containerNames', []);
|
|
600
|
+
for(const c of entityCsn.$containerNames) {
|
|
601
|
+
parameterCsn.$containerNames.push((c === entityCsn.name) ? parameterCsn.name : c);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
entityCsn.$containerNames = [ parameterCsn ];
|
|
605
|
+
|
|
606
|
+
if(!parameterCsn.$containeeAssociations)
|
|
607
|
+
setProp(parameterCsn, '$containeeAssociations', [ ]);
|
|
608
|
+
parameterCsn.$containeeAssociations.push(
|
|
609
|
+
{ assoc: parameterCsn.elements[parameterToTypeAssocName],
|
|
610
|
+
path: [ parameterToTypeAssocName ]
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// rewrite $path
|
|
588
614
|
setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
|
|
589
615
|
|
|
590
616
|
// proxies are registered in model separately
|
|
@@ -628,14 +654,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
628
654
|
|
|
629
655
|
// Don't operate on any structured types other than type and entity
|
|
630
656
|
// such as events and aspects
|
|
631
|
-
if(!isStructuredArtifact(def))
|
|
657
|
+
if(!edmUtils.isStructuredArtifact(def))
|
|
632
658
|
return;
|
|
633
659
|
|
|
634
660
|
let keys = Object.create(null);
|
|
635
661
|
let validFrom = [], validKey = [];
|
|
636
662
|
|
|
637
663
|
// Iterate all struct elements
|
|
638
|
-
forEachMemberRecursively(def.items || def, (element, elementName, prop, _path
|
|
664
|
+
forEachMemberRecursively(def.items || def, (element, elementName, prop, _path, construct) => {
|
|
639
665
|
if(prop !== 'elements')
|
|
640
666
|
return;
|
|
641
667
|
|
|
@@ -659,22 +685,22 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
659
685
|
}
|
|
660
686
|
// and eventually remove some afterwards:)
|
|
661
687
|
if(options.isV2())
|
|
662
|
-
setSAPSpecificV2AnnotationsToAssociation(element);
|
|
688
|
+
edmAnnoPreproc.setSAPSpecificV2AnnotationsToAssociation(element);
|
|
663
689
|
|
|
664
690
|
// initialize an association
|
|
665
691
|
if(element.target) {
|
|
666
692
|
// in case this is a forward assoc, store the backlink partners here, _selfReferences.length > 1 => error
|
|
667
|
-
assignProp(element, '_selfReferences', []);
|
|
668
|
-
assignProp(element._target, '$proxies', []);
|
|
693
|
+
edmUtils.assignProp(element, '_selfReferences', []);
|
|
694
|
+
edmUtils.assignProp(element._target, '$proxies', []);
|
|
669
695
|
// $abspath is used as partner path
|
|
670
|
-
assignProp(element, '$abspath', $path2path(element.$path));
|
|
696
|
+
edmUtils.assignProp(element, '$abspath', $path2path(element.$path));
|
|
671
697
|
}
|
|
672
698
|
|
|
673
699
|
// Collect keys
|
|
674
700
|
if (element.key) {
|
|
675
701
|
keys[elementName] = element;
|
|
676
702
|
}
|
|
677
|
-
applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
|
|
703
|
+
edmAnnoPreproc.applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
|
|
678
704
|
}, [], true, { elementsOnly: true });
|
|
679
705
|
|
|
680
706
|
if(!isDeprecatedEnabled(options, '_v1KeysForTemporal')) {
|
|
@@ -682,7 +708,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
682
708
|
if(validKey.length) {
|
|
683
709
|
let altKeys = [{ Key: [] }];
|
|
684
710
|
validKey.forEach(vk => altKeys[0].Key.push( { Name: vk.name, Alias: vk.name } ) );
|
|
685
|
-
assignAnnotation(def, '@Core.AlternateKeys', altKeys);
|
|
711
|
+
edmUtils.assignAnnotation(def, '@Core.AlternateKeys', altKeys);
|
|
686
712
|
}
|
|
687
713
|
}
|
|
688
714
|
else {
|
|
@@ -698,7 +724,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
698
724
|
validFrom.forEach(e => {
|
|
699
725
|
altKeys[0].Key.push( { Name: e.name, Alias: e.name } );
|
|
700
726
|
});
|
|
701
|
-
assignAnnotation(def, '@Core.AlternateKeys', altKeys);
|
|
727
|
+
edmUtils.assignAnnotation(def, '@Core.AlternateKeys', altKeys);
|
|
702
728
|
keys = Object.create(null);
|
|
703
729
|
validKey.forEach(e => {
|
|
704
730
|
e.key = true;
|
|
@@ -715,16 +741,16 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
715
741
|
|
|
716
742
|
// prepare the structure itself
|
|
717
743
|
if(def.kind === 'entity') {
|
|
718
|
-
assignProp(def, '_SetAttributes', Object.create(null));
|
|
719
|
-
assignProp(def, '$keys', keys);
|
|
720
|
-
applyAppSpecificLateCsnTransformationOnStructure(options, def, error);
|
|
721
|
-
setSAPSpecificV2AnnotationsToEntitySet(options, def);
|
|
744
|
+
edmUtils.assignProp(def, '_SetAttributes', Object.create(null));
|
|
745
|
+
edmUtils.assignProp(def, '$keys', keys);
|
|
746
|
+
edmAnnoPreproc.applyAppSpecificLateCsnTransformationOnStructure(options, def, error);
|
|
747
|
+
edmAnnoPreproc.setSAPSpecificV2AnnotationsToEntitySet(options, def);
|
|
722
748
|
}
|
|
723
749
|
}
|
|
724
750
|
|
|
725
751
|
// Prepare the associations for the subsequent steps
|
|
726
752
|
function initConstraints(def) {
|
|
727
|
-
if(!isStructuredArtifact(def))
|
|
753
|
+
if(!edmUtils.isStructuredArtifact(def))
|
|
728
754
|
return;
|
|
729
755
|
|
|
730
756
|
forEachMemberRecursively(def.items || def, initConstraintsOnAssoc, [], true, { elementsOnly: true });
|
|
@@ -734,7 +760,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
734
760
|
// setup the constraints object
|
|
735
761
|
setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
|
|
736
762
|
// and crack the ON condition
|
|
737
|
-
resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
|
|
763
|
+
edmUtils.resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
|
|
738
764
|
}
|
|
739
765
|
}
|
|
740
766
|
|
|
@@ -751,7 +777,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
751
777
|
4) All of this can be revoked with options.renderForeignKeys.
|
|
752
778
|
*/
|
|
753
779
|
function ignoreProperties(struct) {
|
|
754
|
-
if(!isStructuredArtifact(struct))
|
|
780
|
+
if(!edmUtils.isStructuredArtifact(struct))
|
|
755
781
|
return;
|
|
756
782
|
|
|
757
783
|
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
@@ -775,7 +801,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
775
801
|
*/
|
|
776
802
|
if((!isContainerAssoc && !isEdmPropertyRendered(element, options)) ||
|
|
777
803
|
(isContainerAssoc && !options.renderForeignKeys))
|
|
778
|
-
assignAnnotation(element, '@cds.api.ignore', true);
|
|
804
|
+
edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
|
|
779
805
|
// Only in containment:
|
|
780
806
|
// If this element is a foreign key and if it is rendered, remove it from the key ref vector
|
|
781
807
|
else if(options.odataContainment && isContainerAssoc && options.renderForeignKeys) {
|
|
@@ -787,7 +813,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
787
813
|
// Ignore this (foreign key) elment if renderForeignKeys is false
|
|
788
814
|
if(options.odataContainment && element['@odata.containment.ignore']) {
|
|
789
815
|
if(!options.renderForeignKeys)
|
|
790
|
-
assignAnnotation(element, '@cds.api.ignore', true);
|
|
816
|
+
edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
|
|
791
817
|
else
|
|
792
818
|
// If foreign keys shall be rendered, remove it from key ref vector
|
|
793
819
|
delete struct.$keys[element.name];
|
|
@@ -797,7 +823,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
797
823
|
else if(element['@odata.containment.ignore'] && options.odataContainment && !options.renderForeignKeys) {
|
|
798
824
|
// if this is an explicitly containment ignore tagged association,
|
|
799
825
|
// ignore it if option odataContainment is true and no foreign keys should be rendered
|
|
800
|
-
assignAnnotation(element, '@odata.navigable', false);
|
|
826
|
+
edmUtils.assignAnnotation(element, '@odata.navigable', false);
|
|
801
827
|
}
|
|
802
828
|
}, [], true, { elementsOnly: true });
|
|
803
829
|
}
|
|
@@ -808,14 +834,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
808
834
|
in edmUtils
|
|
809
835
|
*/
|
|
810
836
|
function finalizeConstraints(def) {
|
|
811
|
-
if(!isStructuredArtifact(def))
|
|
837
|
+
if(!edmUtils.isStructuredArtifact(def))
|
|
812
838
|
return;
|
|
813
839
|
|
|
814
840
|
forEachMemberRecursively(def.items || def, finalizeConstraintsOnAssoc, [], true, { elementsOnly: true });
|
|
815
841
|
}
|
|
816
842
|
function finalizeConstraintsOnAssoc(element) {
|
|
817
843
|
if (element.target && !element._ignore && element._constraints) {
|
|
818
|
-
finalizeReferentialConstraints(csn, element, options, info);
|
|
844
|
+
edmUtils.finalizeReferentialConstraints(csn, element, options, info);
|
|
819
845
|
|
|
820
846
|
if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
|
|
821
847
|
// if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
|
|
@@ -912,7 +938,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
912
938
|
/*
|
|
913
939
|
if(element._target['@cds.autoexpose'] === false) {
|
|
914
940
|
// :TODO: Also _ignore foreign keys to association?
|
|
915
|
-
foreach(struct.elements,
|
|
941
|
+
edmUtils.foreach(struct.elements,
|
|
916
942
|
e =>
|
|
917
943
|
e['@odata.foreignKey4'] === element.name,
|
|
918
944
|
e => e._ignore = true);
|
|
@@ -940,23 +966,27 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
940
966
|
|
|
941
967
|
const targetSchemaName = element._target.$mySchemaName;
|
|
942
968
|
if(isProxyRequired(element)) {
|
|
943
|
-
if(options.isV4() &&
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
element.
|
|
947
|
-
(element.on && element._constraints.selfs.length === 1 && element._constraints.termCount === 1)
|
|
948
|
-
) {
|
|
969
|
+
if(options.isV4() && (options.odataProxies || options.odataXServiceRefs)) {
|
|
970
|
+
// must be a managed association with keys OR an unambiguous backlink to become a proxy
|
|
971
|
+
let assocOK = element.keys ||
|
|
972
|
+
(element.on && element._constraints.selfs.length === 1 && element._constraints.termCount === 1);
|
|
949
973
|
// reuse proxy if available
|
|
950
974
|
let proxy = getProxyForTargetOf(element);
|
|
951
975
|
if(!proxy) {
|
|
952
976
|
if(targetSchemaName && options.odataXServiceRefs) {
|
|
953
977
|
proxy = createSchemaRefFor(targetSchemaName);
|
|
954
978
|
}
|
|
955
|
-
|
|
979
|
+
// create a proxy for a 'good' association only
|
|
980
|
+
else if(options.odataProxies && assocOK) {
|
|
956
981
|
proxy = createProxyFor(element, targetSchemaName);
|
|
957
982
|
}
|
|
958
983
|
proxy = registerProxy(proxy, element);
|
|
959
|
-
}
|
|
984
|
+
} else if(!assocOK) {
|
|
985
|
+
// if there is already a proxy (generated by a 'good' association)
|
|
986
|
+
// and this association is not a good one, don't expose this association.
|
|
987
|
+
muteNavProp(element, 'oncond');
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
960
990
|
if(proxy) {
|
|
961
991
|
// if a proxy was either already created or could be created and
|
|
962
992
|
// if it's a 'real' proxy, link the _target to it and remove constraints
|
|
@@ -974,24 +1004,22 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
974
1004
|
}
|
|
975
1005
|
}
|
|
976
1006
|
else {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
noNavPropMsg(element);
|
|
1007
|
+
muteNavProp(element, assocOK ? 'std' : 'oncond');
|
|
1008
|
+
return;
|
|
980
1009
|
}
|
|
981
1010
|
}
|
|
982
|
-
// ok schema names are different, now check if external wants to link back into its service schema
|
|
983
1011
|
else {
|
|
984
|
-
|
|
985
|
-
noNavPropMsg(element);
|
|
1012
|
+
muteNavProp(element);
|
|
986
1013
|
return;
|
|
987
1014
|
}
|
|
988
1015
|
}
|
|
989
1016
|
});
|
|
990
1017
|
}
|
|
991
1018
|
|
|
992
|
-
function
|
|
1019
|
+
function muteNavProp(elt, msg='std') {
|
|
1020
|
+
edmUtils.assignAnnotation(elt, '@odata.navigable', false);
|
|
993
1021
|
warning('odata-navigation', ['definitions', struct.name, 'elements', elt.name],
|
|
994
|
-
{ target: elt._target.name, service: globalSchemaPrefix });
|
|
1022
|
+
{ target: elt._target.name, service: globalSchemaPrefix, '#': msg });
|
|
995
1023
|
}
|
|
996
1024
|
|
|
997
1025
|
function createSchemaRefFor(targetSchemaName) {
|
|
@@ -1007,10 +1035,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1007
1035
|
// If target is outside any service expose it in service of source entity
|
|
1008
1036
|
// The proxySchemaName is not prepended with the service schema name to allow to share the proxy
|
|
1009
1037
|
// if it is required in multiple services. The service schema name is prepended upon registration
|
|
1010
|
-
const proxySchemaName = targetSchemaName || getSchemaPrefix(assoc._target.name);
|
|
1038
|
+
const proxySchemaName = targetSchemaName || edmUtils.getSchemaPrefix(assoc._target.name);
|
|
1011
1039
|
|
|
1012
1040
|
// if the target is a parameter entity, it's easy just create the parameter stub
|
|
1013
|
-
const isParamProxy = isParameterizedEntity(assoc._target);
|
|
1041
|
+
const isParamProxy = edmUtils.isParameterizedEntity(assoc._target);
|
|
1014
1042
|
|
|
1015
1043
|
// 1) construct the proxy definition
|
|
1016
1044
|
// proxyDefinitionName: strip the serviceName and replace '.' with '_'
|
|
@@ -1033,7 +1061,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1033
1061
|
setProp(proxy, '$exposedTypes', Object.create(null));
|
|
1034
1062
|
// copy all annotations of the target to the proxy
|
|
1035
1063
|
Object.entries(assoc._target).forEach(([k, v]) => {
|
|
1036
|
-
if(k[0] === '@')
|
|
1064
|
+
if(k[0] === '@' && k !== '@open')
|
|
1037
1065
|
proxy[k] = v;
|
|
1038
1066
|
});
|
|
1039
1067
|
|
|
@@ -1114,7 +1142,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1114
1142
|
// to notNull until the first named type is exposed.
|
|
1115
1143
|
function exposeStructTypeForProxyOf(proxy, node, artificialName,
|
|
1116
1144
|
typeSchemaName=fallBackSchemaName,
|
|
1117
|
-
isKey, forceToNotNull) {
|
|
1145
|
+
isKey = false, forceToNotNull = false) {
|
|
1118
1146
|
|
|
1119
1147
|
if(node.type && isBuiltinType(node.type))
|
|
1120
1148
|
return;
|
|
@@ -1138,7 +1166,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1138
1166
|
let typeId = artificialName; // the artificialName has no namespace, it's the element
|
|
1139
1167
|
if(node.type) {
|
|
1140
1168
|
// same as for proxies, use schema or namespace, 'root' is last resort
|
|
1141
|
-
typeSchemaName = typeDef.$mySchemaName || getSchemaPrefix(node.type);
|
|
1169
|
+
typeSchemaName = typeDef.$mySchemaName || edmUtils.getSchemaPrefix(node.type);
|
|
1142
1170
|
typeId = node.type.replace(typeSchemaName + '.', '').replace(/\./g, '_');
|
|
1143
1171
|
// strip the service root of that type (if any)
|
|
1144
1172
|
const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
|
|
@@ -1146,7 +1174,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1146
1174
|
typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
|
|
1147
1175
|
}
|
|
1148
1176
|
|
|
1149
|
-
if(isStructuredArtifact(typeDef)) {
|
|
1177
|
+
if(edmUtils.isStructuredArtifact(typeDef)) {
|
|
1150
1178
|
// pull forceNotNull to false for named types and non-key nodes
|
|
1151
1179
|
// only toplevel nodes (elements) can be key
|
|
1152
1180
|
forceToNotNull = !!(forceToNotNull && isKey && node.elements && !node.type);
|
|
@@ -1205,6 +1233,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1205
1233
|
};
|
|
1206
1234
|
setProp(type, '$mySchemaName', typeSchemaName);
|
|
1207
1235
|
setProp(type, '$exposedBy', 'proxyExposure');
|
|
1236
|
+
if(typeDef['@open'] !== undefined)
|
|
1237
|
+
type['@open'] = typeDef['@open'];
|
|
1208
1238
|
|
|
1209
1239
|
typeDef.elements && Object.entries(typeDef.elements).forEach( ([elemName, elem]) => {
|
|
1210
1240
|
if(!elem.target) {
|
|
@@ -1281,7 +1311,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1281
1311
|
if(proxy === undefined) {
|
|
1282
1312
|
proxy = e._target;
|
|
1283
1313
|
// no proxy: no navigation
|
|
1284
|
-
assignAnnotation(newElt, '@odata.navigable', false);
|
|
1314
|
+
edmUtils.assignAnnotation(newElt, '@odata.navigable', false);
|
|
1285
1315
|
}
|
|
1286
1316
|
// either the proxy has exposed the type or
|
|
1287
1317
|
// the assoc doesn't need to be exposed, so don't
|
|
@@ -1347,7 +1377,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1347
1377
|
const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
|
|
1348
1378
|
|
|
1349
1379
|
if(!element._target.$cachedProxy)
|
|
1350
|
-
assignProp(element._target, '$cachedProxy', Object.create(null));
|
|
1380
|
+
edmUtils.assignProp(element._target, '$cachedProxy', Object.create(null));
|
|
1351
1381
|
if(getProxyForTargetOf(element)) {
|
|
1352
1382
|
info(null, ['definitions', struct.name, 'elements', element.name],
|
|
1353
1383
|
{ name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
|
|
@@ -1363,10 +1393,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1363
1393
|
|
|
1364
1394
|
function mergeProxiesIntoModel() {
|
|
1365
1395
|
proxyCache.forEach(proxy => {
|
|
1396
|
+
|
|
1366
1397
|
const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
|
|
1367
1398
|
const fqSchemaName = proxy.$globalSchemaPrefix + '.' + proxy.$mySchemaName;
|
|
1368
1399
|
|
|
1369
1400
|
if(proxy.kind === 'entity') {
|
|
1401
|
+
finalizeProxyContainments(proxy);
|
|
1370
1402
|
// collect all schemas even for newly exposed types
|
|
1371
1403
|
// (that may reside in another subcontext schema), but only once
|
|
1372
1404
|
const schemaSet = new Set();
|
|
@@ -1376,7 +1408,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1376
1408
|
// don't forget to prepend the global namespace prefix
|
|
1377
1409
|
// schemas are ordered in csn2edm.js for each service
|
|
1378
1410
|
Object.keys(proxy.$exposedTypes).forEach(t =>
|
|
1379
|
-
schemaSet.add(proxy.$globalSchemaPrefix + '.' + getSchemaPrefix(t)));
|
|
1411
|
+
schemaSet.add(proxy.$globalSchemaPrefix + '.' + edmUtils.getSchemaPrefix(t)));
|
|
1380
1412
|
schemaSet.forEach(schemaName => {
|
|
1381
1413
|
if(!schemas[schemaName]) {
|
|
1382
1414
|
schemas[schemaName] = { kind: 'schema', name: schemaName };
|
|
@@ -1397,6 +1429,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1397
1429
|
setProp(v, '$path', ['definitions', fqtn]);
|
|
1398
1430
|
}
|
|
1399
1431
|
});
|
|
1432
|
+
|
|
1400
1433
|
// default location is not always correct in case proxy has been created by a nested assoc
|
|
1401
1434
|
// as foreign key targeting another proxy association
|
|
1402
1435
|
let loc = ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name];
|
|
@@ -1424,6 +1457,26 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1424
1457
|
});
|
|
1425
1458
|
// sort the global schemaNames array
|
|
1426
1459
|
schemaNames.sort((a,b) => b.length-a.length);
|
|
1460
|
+
|
|
1461
|
+
function finalizeProxyContainments(proxy) {
|
|
1462
|
+
// initialise containments after all exposed types are collected
|
|
1463
|
+
// AND remove unfullfillable NavRestrictions
|
|
1464
|
+
initContainments(proxy);
|
|
1465
|
+
const assocPaths = proxy.$containeeAssociations.map(entry => entry.path.join('.'));
|
|
1466
|
+
const newNpr = [];
|
|
1467
|
+
const npr = proxy[NavResAnno];
|
|
1468
|
+
npr && npr.forEach(np => {
|
|
1469
|
+
const npath = np.NavigationProperty && np.NavigationProperty['='];
|
|
1470
|
+
if(npath && assocPaths.includes(npath))
|
|
1471
|
+
newNpr.push(np);
|
|
1472
|
+
});
|
|
1473
|
+
if(newNpr.length) {
|
|
1474
|
+
proxy[NavResAnno] = newNpr;
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
delete proxy[NavResAnno]
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1427
1480
|
}
|
|
1428
1481
|
|
|
1429
1482
|
/*
|
|
@@ -1443,7 +1496,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1443
1496
|
if(def.$keys) {
|
|
1444
1497
|
setProp(def, '$edmKeyPaths', []);
|
|
1445
1498
|
// for all key elements that shouldn't be ignored produce the paths
|
|
1446
|
-
foreach(def.$keys, k => !(k._isToContainer && k._selfReferences.length), (k, kn) => {
|
|
1499
|
+
edmUtils.foreach(def.$keys, k => !(k._isToContainer && k._selfReferences.length), (k, kn) => {
|
|
1447
1500
|
if(isEdmPropertyRendered(k, options) &&
|
|
1448
1501
|
!(options.isV2() && k['@Core.MediaType'])) {
|
|
1449
1502
|
if(options.isV4() && options.isStructFormat) {
|
|
@@ -1601,7 +1654,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1601
1654
|
if(isEdmPropertyRendered(elt, options)) {
|
|
1602
1655
|
// Assoc can never be a derived TypeDefinition, no need to
|
|
1603
1656
|
// unroll derived type chains for assocs
|
|
1604
|
-
if(elt.target && !elt.$
|
|
1657
|
+
if(elt.target && !elt.$visited) {
|
|
1605
1658
|
if(!elt._target.$edmTgtPaths)
|
|
1606
1659
|
setProp(elt._target, '$edmTgtPaths', []);
|
|
1607
1660
|
// drill into target only if
|
|
@@ -1613,10 +1666,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1613
1666
|
!elt._isToContainer &&
|
|
1614
1667
|
curDef !== elt._target) {
|
|
1615
1668
|
// follow elements in the target but avoid cycles
|
|
1616
|
-
setProp(elt, '$
|
|
1669
|
+
setProp(elt, '$visited', true);
|
|
1617
1670
|
elt._target.$edmTgtPaths.push(newPrefix);
|
|
1618
1671
|
Object.values(elt._target.elements).forEach(e => produceTargetPath(newPrefix, e, elt._target));
|
|
1619
|
-
delete elt.$
|
|
1672
|
+
delete elt.$visited;
|
|
1620
1673
|
}
|
|
1621
1674
|
}
|
|
1622
1675
|
else {
|
|
@@ -1646,7 +1699,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1646
1699
|
if(isEdmPropertyRendered(elt, options)) {
|
|
1647
1700
|
// Assoc can never be a derived TypeDefinition, no need to
|
|
1648
1701
|
// unroll derived type chains for assocs
|
|
1649
|
-
if(elt.target && !elt.$
|
|
1702
|
+
if(elt.target && !elt.$visited) {
|
|
1650
1703
|
// drill into target only if
|
|
1651
1704
|
// 1) target has no entity set and this assoc is not going to the container
|
|
1652
1705
|
// 2) current definition and target are the same (cycle)
|
|
@@ -1656,9 +1709,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1656
1709
|
!elt._isToContainer &&
|
|
1657
1710
|
curDef !== elt._target) {
|
|
1658
1711
|
// follow elements in the target but avoid cycles
|
|
1659
|
-
setProp(elt, '$
|
|
1712
|
+
setProp(elt, '$visited', true);
|
|
1660
1713
|
Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
|
|
1661
|
-
delete elt.$
|
|
1714
|
+
delete elt.$visited;
|
|
1662
1715
|
}
|
|
1663
1716
|
else if(!(options.odataContainment && options.isV4() && elt['@odata.contained'])) {
|
|
1664
1717
|
// end point reached but must not be an external reference nor a proxy nor a composition itself
|
|
@@ -1719,7 +1772,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1719
1772
|
// 1. let all doc props become @Core.Descriptions
|
|
1720
1773
|
// 2. mark a member that will become a collection
|
|
1721
1774
|
// 3. assign the edm primitive type to elements, to be used in the rendering later
|
|
1722
|
-
assignAnnotation(def, '@Core.Description', def.doc);
|
|
1775
|
+
edmUtils.assignAnnotation(def, '@Core.Description', def.doc);
|
|
1723
1776
|
markCollection(def);
|
|
1724
1777
|
mapCdsToEdmProp(def);
|
|
1725
1778
|
if (def.returns) {
|
|
@@ -1727,7 +1780,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1727
1780
|
mapCdsToEdmProp(def.returns);
|
|
1728
1781
|
}
|
|
1729
1782
|
forEachMemberRecursively(def,member => {
|
|
1730
|
-
assignAnnotation(member, '@Core.Description', member.doc);
|
|
1783
|
+
edmUtils.assignAnnotation(member, '@Core.Description', member.doc);
|
|
1731
1784
|
markCollection(member);
|
|
1732
1785
|
mapCdsToEdmProp(member);
|
|
1733
1786
|
ComputedDefaultValue(member);
|
|
@@ -1740,98 +1793,143 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1740
1793
|
function markCollection(obj) {
|
|
1741
1794
|
const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
|
|
1742
1795
|
if (items) {
|
|
1743
|
-
assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
|
|
1744
|
-
assignProp(obj, '_isCollection', true);
|
|
1796
|
+
edmUtils.assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
|
|
1797
|
+
edmUtils.assignProp(obj, '_isCollection', true);
|
|
1745
1798
|
}
|
|
1746
1799
|
}
|
|
1747
1800
|
}
|
|
1748
1801
|
|
|
1802
|
+
// If containment in V4 is active, annotations that would be assigned to the containees
|
|
1803
|
+
// entity set are not renderable anymore. In such a case try to reassign the annotations to
|
|
1804
|
+
// the containment navigation property.
|
|
1805
|
+
// Today only Capabilities.*Restrictions are known to be remapped as there exists a CDS
|
|
1806
|
+
// short cut annotation @readonly that gets expanded and can be safely remapped.
|
|
1807
|
+
function rewriteContainmentAnnotations(rootContainer) {
|
|
1749
1808
|
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
//
|
|
1809
|
+
// meaningless for non-entities and proxies
|
|
1810
|
+
if(rootContainer.kind !== 'entity' || rootContainer.$proxy)
|
|
1811
|
+
return;
|
|
1754
1812
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
forEachMemberRecursively(def, (construct, _constructName, _prop, path) => {
|
|
1760
|
-
if(construct.target && construct.keys) {
|
|
1761
|
-
construct.keys.forEach((fk, i) => {
|
|
1762
|
-
setProp(fk, '_artifact', csnUtils.inspectRef([...path, 'keys', i]).art);
|
|
1763
|
-
});
|
|
1764
|
-
}
|
|
1765
|
-
}, currPath, true, { elementsOnly: true });
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1813
|
+
const isRecursiveContainment =
|
|
1814
|
+
!!(rootContainer.$containerNames && rootContainer.$containeeAssociations &&
|
|
1815
|
+
rootContainer.$containerNames.length === 1 &&
|
|
1816
|
+
rootContainer.$containeeAssociations.some(entry => rootContainer.$containerNames.includes(entry.assoc.target)));
|
|
1768
1817
|
|
|
1818
|
+
// Root nodes are not contained
|
|
1819
|
+
const isRootNode =
|
|
1820
|
+
!!(!rootContainer.$containerNames ||
|
|
1821
|
+
rootContainer.$containerNames && rootContainer.$containerNames.length === 0);
|
|
1769
1822
|
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
checkNestedContextsAndServices();
|
|
1773
|
-
throwWithAnyError();
|
|
1823
|
+
if(!isRecursiveContainment && !isRootNode)
|
|
1824
|
+
return;
|
|
1774
1825
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1826
|
+
const rootRestrictions = [];
|
|
1827
|
+
addContainmentAnnotationsRecursively([], rootContainer);
|
|
1828
|
+
if(rootRestrictions.length)
|
|
1829
|
+
rootContainer[NavResAnno] = rootRestrictions;
|
|
1830
|
+
|
|
1831
|
+
function addContainmentAnnotationsRecursively(prefix, container) {
|
|
1832
|
+
if(container.$containeeAssociations) {
|
|
1833
|
+
// copy or create container restrictions, don't modify original
|
|
1834
|
+
const localRestrictions = container[NavResAnno] ?
|
|
1835
|
+
cloneCsnDictionary(container[NavResAnno], options) : []
|
|
1836
|
+
|
|
1837
|
+
setProp(container, '$visited', true);
|
|
1838
|
+
container.$containeeAssociations.forEach(entry => {
|
|
1839
|
+
const { assoc, path } = entry;
|
|
1840
|
+
const containee = assoc._target;
|
|
1841
|
+
|
|
1842
|
+
if(isMyServiceRequested(containee.name) || containee.$proxy) {
|
|
1843
|
+
const localAssocPath = path.join('.');
|
|
1844
|
+
const props = Object.entries(containee);
|
|
1845
|
+
['@Capabilities.DeleteRestrictions',
|
|
1846
|
+
'@Capabilities.InsertRestrictions',
|
|
1847
|
+
'@Capabilities.UpdateRestrictions',
|
|
1848
|
+
'@Capabilities.ReadRestrictions'].forEach(c =>
|
|
1849
|
+
edmUtils.mergeIntoNavPropRestrictions(c, localAssocPath, props, localRestrictions));
|
|
1850
|
+
|
|
1851
|
+
|
|
1852
|
+
if(!containee.$visited) {
|
|
1853
|
+
addContainmentAnnotationsRecursively(prefix.concat(path), containee);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1783
1857
|
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
message('chained-array-of', path);
|
|
1796
|
-
return;
|
|
1858
|
+
// prefix all paths in the navPropEntry with the NavigationPropertyPath
|
|
1859
|
+
localRestrictions.forEach((p, i) => {
|
|
1860
|
+
if(p.NavigationProperty && p.NavigationProperty['='] && typeof p.NavigationProperty['='] === 'string') {
|
|
1861
|
+
const lp = [ ...prefix, p.NavigationProperty['=']].join('.');
|
|
1862
|
+
applyTransformationsOnNonDictionary(localRestrictions, i, {
|
|
1863
|
+
"=": (parent, prop, value) => {
|
|
1864
|
+
parent[prop] = [lp, value].join('.');
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
// reset NavigationPropertyPath
|
|
1868
|
+
p.NavigationProperty['='] = lp;
|
|
1797
1869
|
}
|
|
1870
|
+
});
|
|
1871
|
+
rootRestrictions.unshift(...localRestrictions);
|
|
1872
|
+
delete container.$visited;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1798
1876
|
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1877
|
+
/*
|
|
1878
|
+
V4 Only:
|
|
1879
|
+
An action/function parameter is optional if
|
|
1880
|
+
1) it is explicitly annotated to be optional
|
|
1881
|
+
2) it has a default value (including null), regardless of it's nullability
|
|
1882
|
+
3) it has NO default value but is nullable (the implicit default value is null)
|
|
1883
|
+
|
|
1884
|
+
If a mandatory parameter (not null no default value) appears after an optional
|
|
1885
|
+
parameter, a warning is raised, Core.OptionalParameter requires that all optional
|
|
1886
|
+
parameters appear rightmost.
|
|
1887
|
+
*/
|
|
1888
|
+
function annotateOptionalActFuncParams(def, defName) {
|
|
1889
|
+
if(!isBetaEnabled(options, 'optionalActionFunctionParameters'))
|
|
1890
|
+
return;
|
|
1891
|
+
// return if there is nothing to do
|
|
1892
|
+
const loc = [ 'definitions', defName ]
|
|
1893
|
+
if(def.kind === 'function' || def.kind === 'action')
|
|
1894
|
+
iterateParams(def, loc.concat('params'));
|
|
1895
|
+
if(def.actions) {
|
|
1896
|
+
for(const an in def.actions) {
|
|
1897
|
+
const a = def.actions[an];
|
|
1898
|
+
iterateParams(a, loc.concat(['actions', an, 'params' ]));
|
|
1803
1899
|
}
|
|
1804
1900
|
}
|
|
1805
1901
|
|
|
1806
|
-
function
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1902
|
+
function iterateParams(def, loc) {
|
|
1903
|
+
let lastOptPos = 0;
|
|
1904
|
+
let i = 0;
|
|
1905
|
+
for(const pn in def.params) {
|
|
1906
|
+
const p = def.params[pn];
|
|
1907
|
+
// user assigned annotation, don't touch it
|
|
1908
|
+
if(Object.keys(p).some(a => a.startsWith('@Core.OptionalParameter') && p[a] !== null)) {
|
|
1909
|
+
lastOptPos = i;
|
|
1812
1910
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
'A context can\'t be nested within a service $(ART)' );
|
|
1821
|
-
}
|
|
1911
|
+
// default value automatically makes param optional
|
|
1912
|
+
else if(p.default) {
|
|
1913
|
+
if(p.default.val)
|
|
1914
|
+
edmUtils.assignAnnotation(p, '@Core.OptionalParameter.DefaultValue', p.default.val);
|
|
1915
|
+
else
|
|
1916
|
+
edmUtils.assignAnnotation(p, '@Core.OptionalParameter.$Type', '');
|
|
1917
|
+
lastOptPos = i;
|
|
1822
1918
|
}
|
|
1823
|
-
|
|
1919
|
+
// if no default is available, nullable makes param optional
|
|
1920
|
+
else if(!p.notNull) {
|
|
1921
|
+
edmUtils.assignAnnotation(p, '@Core.OptionalParameter.$Type', '');
|
|
1922
|
+
lastOptPos = i;
|
|
1923
|
+
}
|
|
1924
|
+
// check if mandatory param follows optional param
|
|
1925
|
+
else if(lastOptPos < i) {
|
|
1926
|
+
warning('odata-parameter-order', loc.concat(pn));
|
|
1927
|
+
}
|
|
1928
|
+
i++;
|
|
1929
|
+
}
|
|
1824
1930
|
}
|
|
1825
1931
|
}
|
|
1826
1932
|
|
|
1827
|
-
//
|
|
1828
|
-
// Checks Secition ends here
|
|
1829
|
-
//
|
|
1830
|
-
//////////////////////////////////////////////////////////////////////
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
1933
|
//////////////////////////////////////////////////////////////////////
|
|
1836
1934
|
//
|
|
1837
1935
|
// Helper section starts here
|
|
@@ -1876,96 +1974,18 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1876
1974
|
}
|
|
1877
1975
|
}
|
|
1878
1976
|
|
|
1879
|
-
|
|
1880
|
-
// If containment in V4 is active, annotations that would be assigned to the containees
|
|
1881
|
-
// entity set are not renderable anymore. In such a case try to reassign the annotations to
|
|
1882
|
-
// the containment navigation property.
|
|
1883
|
-
// Today only Capabilities.*Restrictions are known to be remapped as there exists a CDS
|
|
1884
|
-
// short cut annotation @readonly that gets expanded and can be safely remapped.
|
|
1885
|
-
function rewriteContainmentAnnotations(container, containee, assocName) {
|
|
1886
|
-
// rectify Restrictions to NavigationRestrictions
|
|
1887
|
-
if(options.isV4()) {
|
|
1888
|
-
let navPropEntry;
|
|
1889
|
-
let hasEntry = false;
|
|
1890
|
-
let newEntry = false;
|
|
1891
|
-
const anno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
|
|
1892
|
-
let resProps = container[anno];
|
|
1893
|
-
// merge into existing anno, if available
|
|
1894
|
-
if(resProps) {
|
|
1895
|
-
navPropEntry = resProps.find(p => p.NavigationProperty && p.NavigationProperty['='] === assocName);
|
|
1896
|
-
hasEntry = !!navPropEntry;
|
|
1897
|
-
}
|
|
1898
|
-
if(!navPropEntry) {
|
|
1899
|
-
navPropEntry = { NavigationProperty: { '=': assocName } };
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
const props = Object.entries(containee);
|
|
1903
|
-
|
|
1904
|
-
const merge = (prefix) => {
|
|
1905
|
-
const prop = prefix.split('.')[1];
|
|
1906
|
-
// don't overwrite existing restrictions
|
|
1907
|
-
if(!navPropEntry[prop]) {
|
|
1908
|
-
// Filter properties with prefix and reduce them into a new dictionary
|
|
1909
|
-
const o = props.filter(p => p[0].startsWith(prefix+'.')).reduce((a,c) => {
|
|
1910
|
-
a[c[0].replace(prefix+'.', '')] = c[1];
|
|
1911
|
-
return a;
|
|
1912
|
-
}, { });
|
|
1913
|
-
// if dictionary has entries, add them to navPropEnty
|
|
1914
|
-
if(Object.keys(o).length) {
|
|
1915
|
-
// ReadRestrictions may have sub type ReadByKeyRestrictions { Description, LongDescription }
|
|
1916
|
-
// chop annotations into dictionaries
|
|
1917
|
-
if(prefix === '@Capabilities.ReadRestrictions' &&
|
|
1918
|
-
Object.keys(o).some(k => k.startsWith('ReadByKeyRestrictions.'))) {
|
|
1919
|
-
const no = {};
|
|
1920
|
-
Object.entries(o).forEach(([k,v]) => {
|
|
1921
|
-
const [head, ...tail] = k.split('.');
|
|
1922
|
-
if(head === 'ReadByKeyRestrictions' && tail.length) {
|
|
1923
|
-
if(!no['ReadByKeyRestrictions'])
|
|
1924
|
-
no['ReadByKeyRestrictions'] = {};
|
|
1925
|
-
// Don't try to add entry into non object
|
|
1926
|
-
if(typeof no['ReadByKeyRestrictions'] === 'object')
|
|
1927
|
-
no['ReadByKeyRestrictions'][tail.join('.')] = v;
|
|
1928
|
-
}
|
|
1929
|
-
else {
|
|
1930
|
-
no[k] = v;
|
|
1931
|
-
}
|
|
1932
|
-
});
|
|
1933
|
-
navPropEntry[prop] = no;
|
|
1934
|
-
}
|
|
1935
|
-
else {
|
|
1936
|
-
navPropEntry[prop] = o;
|
|
1937
|
-
}
|
|
1938
|
-
newEntry = true;
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
merge('@Capabilities.DeleteRestrictions');
|
|
1943
|
-
merge('@Capabilities.InsertRestrictions');
|
|
1944
|
-
merge('@Capabilities.UpdateRestrictions');
|
|
1945
|
-
merge('@Capabilities.ReadRestrictions');
|
|
1946
|
-
|
|
1947
|
-
if(newEntry) {
|
|
1948
|
-
if(!hasEntry) {
|
|
1949
|
-
if(!resProps)
|
|
1950
|
-
resProps = container[anno] = [ ];
|
|
1951
|
-
resProps.push(navPropEntry);
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
1977
|
function mapCdsToEdmProp(obj) {
|
|
1958
1978
|
if (obj.type && isBuiltinType(obj.type) && !obj.target && !obj.targetAspect) {
|
|
1959
1979
|
let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
|
|
1960
|
-
assignProp(obj, '_edmType', edmType);
|
|
1980
|
+
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
1961
1981
|
} else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.type)))) {
|
|
1962
1982
|
let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType'], obj.$path);
|
|
1963
|
-
assignProp(obj, '_edmType', edmType);
|
|
1983
|
+
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
1964
1984
|
}
|
|
1965
1985
|
// This is the special case when we have array of array, but will not be supported in the future
|
|
1966
1986
|
else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.items.type))) {
|
|
1967
1987
|
let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
|
|
1968
|
-
assignProp(obj, '_edmType', edmType);
|
|
1988
|
+
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
1969
1989
|
}
|
|
1970
1990
|
}
|
|
1971
1991
|
|
|
@@ -1983,375 +2003,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1983
2003
|
}
|
|
1984
2004
|
// it is a computed value if it is not a simple value or an annotation
|
|
1985
2005
|
if(!((def.val !== undefined && !noTailExpr) || def['#'])) {
|
|
1986
|
-
assignAnnotation(member, '@Core.ComputedDefaultValue', true);
|
|
2006
|
+
edmUtils.assignAnnotation(member, '@Core.ComputedDefaultValue', true);
|
|
1987
2007
|
}
|
|
1988
2008
|
}
|
|
1989
2009
|
}
|
|
1990
2010
|
}
|
|
1991
2011
|
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
/*
|
|
1996
|
-
* Late application specific transformations
|
|
1997
|
-
* At present there are two transformation targets: Structure and Element
|
|
1998
|
-
* These transformations are available today:
|
|
1999
|
-
*
|
|
2000
|
-
* Analytical Scenario:
|
|
2001
|
-
* If a structure is annotated with @Aggregation.ApplySupported.PropertyRestrictions
|
|
2002
|
-
* then a number of annotation rewrites are done to this structure and to the
|
|
2003
|
-
* elements of this structure
|
|
2004
|
-
* Also the key properties of all structure elements are removed and a new
|
|
2005
|
-
* artificial key element 'key _ID : String' is inserted at first position of
|
|
2006
|
-
* the elements dictionary
|
|
2007
|
-
*
|
|
2008
|
-
* PDM (Personal Data Management)
|
|
2009
|
-
* Planned but not yet implemented annotation rewriting (pending to finalization)
|
|
2010
|
-
* /
|
|
2011
|
-
|
|
2012
|
-
/* eslint max-statements-per-line:off */
|
|
2013
|
-
|
|
2014
|
-
function mapAnnotationAssignment(artifact, parent, mappingDictionary)
|
|
2015
|
-
{
|
|
2016
|
-
let props = intersect(Object.keys(mappingDictionary), Object.keys(artifact));
|
|
2017
|
-
// now start the substitution
|
|
2018
|
-
props.forEach(prop => {
|
|
2019
|
-
let [ mapping, value, remove_original ] = mappingDictionary[prop];
|
|
2020
|
-
if(mapping instanceof Function)
|
|
2021
|
-
{
|
|
2022
|
-
mapping(artifact, parent, prop);
|
|
2023
|
-
}
|
|
2024
|
-
else
|
|
2025
|
-
{
|
|
2026
|
-
assignAnnotation(artifact, mapping, value || artifact[prop]['='] || artifact[prop]);
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
if(remove_original)
|
|
2030
|
-
delete artifact[prop];
|
|
2031
|
-
});
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
function applyAppSpecificLateCsnTransformationOnElement(options, element, struct, error)
|
|
2035
|
-
{
|
|
2036
|
-
if(options.isV2())
|
|
2037
|
-
{
|
|
2038
|
-
if(struct['@Aggregation.ApplySupported.PropertyRestrictions'])
|
|
2039
|
-
{
|
|
2040
|
-
mapAnnotationAssignment(element, struct, AnalyticalAnnotations());
|
|
2041
|
-
}
|
|
2042
|
-
mapAnnotationAssignment(element, struct, PDMSemantics());
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
// etag requires Core.OptimisticConcurrency to be set in V4 (cap/issues#2641)
|
|
2046
|
-
// Oliver Heinrich mentions in the issue that the Okra runtime must be set to a
|
|
2047
|
-
// concurrent runtime mode by the caller, if the annotation is added this late,
|
|
2048
|
-
// it doesn't appear in the forOData processed CSN, meaning that the
|
|
2049
|
-
// runtime cannot set that okra flag (alternatively the runtime has to search
|
|
2050
|
-
// for @[odata|cds].etag annotations...
|
|
2051
|
-
if(options.isV4())
|
|
2052
|
-
{
|
|
2053
|
-
if(element['@odata.etag'] == true || element['@cds.etag'] == true) {
|
|
2054
|
-
// don't put element name into collection as per advice from Ralf Handl, as
|
|
2055
|
-
// no runtime is interested in the property itself, it is sufficient to mark
|
|
2056
|
-
// the entity set.
|
|
2057
|
-
assignAnnotation(struct, '@Core.OptimisticConcurrency',
|
|
2058
|
-
(struct['@Core.OptimisticConcurrency'] || [])/*.push(element.name)*/);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
// nested functions begin
|
|
2063
|
-
function PDMSemantics()
|
|
2064
|
-
{
|
|
2065
|
-
/*
|
|
2066
|
-
let dict = Object.create(null);
|
|
2067
|
-
|
|
2068
|
-
dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
|
|
2069
|
-
dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
|
|
2070
|
-
dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
|
|
2071
|
-
dict['@PDM.xxx4'] = [ '@sap.pdm-record-identifier' ];
|
|
2072
|
-
dict['@PDM.xxx5'] = [ '@sap.pdm-field-group' ];
|
|
2073
|
-
dict['@PDM.xxx6'] = [ '@sap.pdm-mask-find-pattern' ];
|
|
2074
|
-
dict['@PDM.xxx7'] = [ '@sap.pdm-mask-replacement-pattern' ];
|
|
2075
|
-
dict['@PDM.xxx8'] = [ '@sap.deletable' ];
|
|
2076
|
-
dict['@PDM.xxx8'] = [ '@sap.updatable' ];
|
|
2077
|
-
|
|
2078
|
-
// respect flattened annotation $value
|
|
2079
|
-
Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
|
|
2080
|
-
*/
|
|
2081
|
-
return Object.create(null);
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
function AnalyticalAnnotations()
|
|
2085
|
-
{
|
|
2086
|
-
function mapCommonAttributes(element, struct, prop)
|
|
2087
|
-
{
|
|
2088
|
-
let CommonAttributes = element[prop];
|
|
2089
|
-
if(!Array.isArray(CommonAttributes)) {
|
|
2090
|
-
error(null, ['definitions', struct.name, 'elements', element.name],
|
|
2091
|
-
{ anno: '@Common.attribute', code: JSON.stringify(CommonAttributes) },
|
|
2092
|
-
`Expect array value for $(ANNOTATION): $(CODE)`);
|
|
2093
|
-
return;
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
let targets = intersect(CommonAttributes, Object.keys(struct.elements));
|
|
2097
|
-
targets.forEach(tgt => {
|
|
2098
|
-
assignAnnotation(struct.elements[tgt], '@sap.attribute-for', element.name);
|
|
2099
|
-
});
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
function mapContextDefiningProperties(element, struct, prop)
|
|
2103
|
-
{
|
|
2104
|
-
let ContextDefiningProperties = element[prop];
|
|
2105
|
-
if(!Array.isArray(ContextDefiningProperties)) {
|
|
2106
|
-
error(null, ['definitions', struct.name, 'elements', element.name],
|
|
2107
|
-
{ anno: '@Aggregation.ContextDefiningProperties', code: JSON.stringify(ContextDefiningProperties) },
|
|
2108
|
-
`Expect array value for $(ANNOTATION): $(CODE)`);
|
|
2109
|
-
return;
|
|
2110
|
-
}
|
|
2111
|
-
if(ContextDefiningProperties.length > 0)
|
|
2112
|
-
assignAnnotation(element, '@sap.super-ordinate', ContextDefiningProperties[ContextDefiningProperties.length-1]);
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
let dict = Object.create(null);
|
|
2116
|
-
//analytics term definition unknown, lower case
|
|
2117
|
-
dict['@Analytics.Measure'] = [ '@sap.aggregation-role', 'measure' ];
|
|
2118
|
-
dict['@Analytics.Dimension'] = [ '@sap.aggregation-role', 'dimension' ];
|
|
2119
|
-
dict['@Semantics.currencyCode'] = [ '@sap.semantics', 'currency-code', true ];
|
|
2120
|
-
dict['@Semantics.unitOfMeasure'] = [ '@sap.semantics', 'unit-of-measure', true ];
|
|
2121
|
-
|
|
2122
|
-
dict['@Measures.ISOCurrency'] = [ '@sap.unit' ];
|
|
2123
|
-
dict['@Measures.Unit'] = [ '@sap.unit' ];
|
|
2124
|
-
|
|
2125
|
-
dict['@Common.Label'] = [ '@sap.label' ];
|
|
2126
|
-
dict['@Common.Text'] = [ '@sap.text' ];
|
|
2127
|
-
dict['@Aggregation.ContextDefiningProperties'] = [ mapContextDefiningProperties ];
|
|
2128
|
-
dict['@Common.Attributes'] = [ mapCommonAttributes ];
|
|
2129
|
-
|
|
2130
|
-
// respect flattened annotation $value
|
|
2131
|
-
Object.entries(dict).forEach(([k, v]) => dict[k+'.$value'] = v);
|
|
2132
|
-
return dict;
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error)
|
|
2137
|
-
{
|
|
2138
|
-
if(options.isV2())
|
|
2139
|
-
{
|
|
2140
|
-
if(struct['@Aggregation.ApplySupported.PropertyRestrictions'])
|
|
2141
|
-
{
|
|
2142
|
-
transformAnalyticalModel(struct);
|
|
2143
|
-
mapAnnotationAssignment(struct, undefined, AnalyticalAnnotations());
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
// nested functions begin
|
|
2148
|
-
function transformAnalyticalModel(struct)
|
|
2149
|
-
{
|
|
2150
|
-
let keyName = 'ID__';
|
|
2151
|
-
if(struct == undefined || struct.elements == undefined || struct.elements[keyName] != undefined)
|
|
2152
|
-
return;
|
|
2153
|
-
|
|
2154
|
-
// remove key prop from elements, add new key to elements
|
|
2155
|
-
let elements = Object.create(null);
|
|
2156
|
-
let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
|
|
2157
|
-
elements[keyName] = key;
|
|
2158
|
-
setProp(struct, '$keys',{ [keyName] : key } );
|
|
2159
|
-
forEachGeneric(struct.items || struct, 'elements', (e,n) =>
|
|
2160
|
-
{
|
|
2161
|
-
if(e.key) delete e.key;
|
|
2162
|
-
elements[n] = e;
|
|
2163
|
-
});
|
|
2164
|
-
struct.elements = elements;
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
function AnalyticalAnnotations()
|
|
2168
|
-
{
|
|
2169
|
-
function mapFilterRestrictions(struct, parent, prop)
|
|
2170
|
-
{
|
|
2171
|
-
let stringDict = Object.create(null);
|
|
2172
|
-
stringDict['SingleValue'] = 'single-value';
|
|
2173
|
-
stringDict['MultiValue'] = 'multi-value';
|
|
2174
|
-
stringDict['SingleRange'] = 'interval';
|
|
2175
|
-
|
|
2176
|
-
let filterRestrictions = struct[prop];
|
|
2177
|
-
if(!Array.isArray(filterRestrictions)) {
|
|
2178
|
-
error(null, ['definitions', struct.name ],
|
|
2179
|
-
{ anno: '@Capabilities.FilterRestrictions.FilterExpressionRestrictions',
|
|
2180
|
-
code: JSON.stringify(filterRestrictions) },
|
|
2181
|
-
`Expect array value for $(ANNOTATION): $(CODE)`);
|
|
2182
|
-
return;
|
|
2183
|
-
}
|
|
2184
|
-
filterRestrictions.forEach(v => {
|
|
2185
|
-
let e = struct.elements[v.Property];
|
|
2186
|
-
if(e)
|
|
2187
|
-
assignAnnotation(e, '@sap.filter-restriction', stringDict[v.AllowedExpressions]);
|
|
2188
|
-
});
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
function mapRequiredProperties(struct, parent, prop)
|
|
2192
|
-
{
|
|
2193
|
-
let requiredProperties = struct[prop];
|
|
2194
|
-
if(!Array.isArray(requiredProperties)) {
|
|
2195
|
-
error(null, ['definitions', struct.name],
|
|
2196
|
-
{ anno: '@Capabilities.FilterRestrictions.RequiredProperties',
|
|
2197
|
-
code: JSON.stringify(requiredProperties) },
|
|
2198
|
-
`Expect array value for $(ANNOTATION): $(CODE)`);
|
|
2199
|
-
return;
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
let props = intersect(Object.keys(struct.elements), requiredProperties)
|
|
2203
|
-
props.forEach(p => {
|
|
2204
|
-
assignAnnotation(struct.elements[p], '@sap.required-in-filter', true);
|
|
2205
|
-
});
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
function mapRequiresFilter(struct, parent, prop)
|
|
2209
|
-
{
|
|
2210
|
-
let requiresFilter = struct[prop];
|
|
2211
|
-
if(requiresFilter)
|
|
2212
|
-
assignAnnotation(struct._SetAttributes, '@sap.requires-filter', requiresFilter);
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
// Entity Props
|
|
2216
|
-
let dict = Object.create(null);
|
|
2217
|
-
dict['@Aggregation.ApplySupported.PropertyRestrictions'] = [ '@sap.semantics', 'aggregate' ];
|
|
2218
|
-
dict['@Common.Label'] = [ '@sap.label' ];
|
|
2219
|
-
dict['@Capabilities.FilterRestrictions.RequiresFilter'] = [ mapRequiresFilter ];
|
|
2220
|
-
dict['@Capabilities.FilterRestrictions.RequiredProperties'] = [ mapRequiredProperties ];
|
|
2221
|
-
dict['@Capabilities.FilterRestrictions.FilterExpressionRestrictions'] = [ mapFilterRestrictions ];
|
|
2222
|
-
|
|
2223
|
-
// respect flattened annotation $value
|
|
2224
|
-
Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
|
|
2225
|
-
|
|
2226
|
-
return dict;
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
|
-
function setSAPSpecificV2AnnotationsToEntityContainer(options, carrier) {
|
|
2231
|
-
if(!options.isV2())
|
|
2232
|
-
return;
|
|
2233
|
-
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntityContainer
|
|
2234
|
-
const SetAttributes = {
|
|
2235
|
-
// EntityContainer only
|
|
2236
|
-
'@sap.supported.formats' : addToSetAttr,
|
|
2237
|
-
'@sap.use.batch': addToSetAttr,
|
|
2238
|
-
'@sap.message.scope.supported': addToSetAttr,
|
|
2239
|
-
};
|
|
2240
|
-
|
|
2241
|
-
Object.entries(carrier).forEach(([p, v]) => {
|
|
2242
|
-
(SetAttributes[p] || function() { /* no-op */ })(carrier, p, v);
|
|
2243
|
-
});
|
|
2244
|
-
|
|
2245
|
-
function addToSetAttr(carrier, propName, propValue, removeFromType=true) {
|
|
2246
|
-
assignProp(carrier, '_SetAttributes', Object.create(null));
|
|
2247
|
-
assignAnnotation(carrier._SetAttributes, propName, propValue);
|
|
2248
|
-
if(removeFromType) {
|
|
2249
|
-
delete carrier[propName];
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
|
|
2255
|
-
if(!options.isV2())
|
|
2256
|
-
return;
|
|
2257
|
-
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntitySet
|
|
2258
|
-
const SetAttributes = {
|
|
2259
|
-
// EntitySet, EntityType
|
|
2260
|
-
'@sap.label' : (s,pn, pv) => { addToSetAttr(s, pn, pv, false); },
|
|
2261
|
-
'@sap.semantics': checkSemantics,
|
|
2262
|
-
// EntitySet only
|
|
2263
|
-
'@sap.creatable' : addToSetAttr,
|
|
2264
|
-
'@sap.updatable' : addToSetAttr,
|
|
2265
|
-
'@sap.deletable': addToSetAttr,
|
|
2266
|
-
'@sap.updatable.path': addToSetAttr,
|
|
2267
|
-
'@sap.deletable.path': addToSetAttr,
|
|
2268
|
-
'@sap.searchable' : addToSetAttr,
|
|
2269
|
-
'@sap.pagable': addToSetAttr,
|
|
2270
|
-
'@sap.topable': addToSetAttr,
|
|
2271
|
-
'@sap.countable': addToSetAttr,
|
|
2272
|
-
'@sap.addressable': addToSetAttr,
|
|
2273
|
-
'@sap.requires.filter': addToSetAttr,
|
|
2274
|
-
'@sap.change.tracking': addToSetAttr,
|
|
2275
|
-
'@sap.maxpagesize': addToSetAttr,
|
|
2276
|
-
'@sap.delta.link.validity': addToSetAttr,
|
|
2277
|
-
};
|
|
2278
|
-
|
|
2279
|
-
Object.entries(carrier).forEach(([p, v]) => {
|
|
2280
|
-
(SetAttributes[p] || function() { /* no-op */ })(carrier, p, v);
|
|
2281
|
-
});
|
|
2282
|
-
|
|
2283
|
-
function addToSetAttr(carrier, propName, propValue, removeFromType=true) {
|
|
2284
|
-
assignProp(carrier, '_SetAttributes', Object.create(null));
|
|
2285
|
-
assignAnnotation(carrier._SetAttributes, propName, propValue);
|
|
2286
|
-
if(removeFromType) {
|
|
2287
|
-
delete carrier[propName];
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
function checkSemantics(struct, propName, propValue) {
|
|
2292
|
-
if(propValue === 'timeseries' || propValue === 'aggregate') {
|
|
2293
|
-
// aggregate is forwarded to Set and must remain on Type
|
|
2294
|
-
addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
function setSAPSpecificV2AnnotationsToAssociation(carrier) {
|
|
2300
|
-
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
|
|
2301
|
-
const SetAttributes = {
|
|
2302
|
-
// Applicable to NavProp and foreign keys, add to AssociationSet
|
|
2303
|
-
'@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
|
|
2304
|
-
// Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
|
|
2305
|
-
'@sap.updatable' : addToAssociationSet,
|
|
2306
|
-
// Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
|
|
2307
|
-
'@sap.deletable': (c, pn, pv) => {
|
|
2308
|
-
addToAssociationSet(c, pn, pv);
|
|
2309
|
-
removeFromForeignKey(c, pn);
|
|
2310
|
-
},
|
|
2311
|
-
// applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
|
|
2312
|
-
'@sap.creatable.path': removeFromForeignKey,
|
|
2313
|
-
'@sap.filterable': removeFromForeignKey,
|
|
2314
|
-
};
|
|
2315
|
-
|
|
2316
|
-
Object.entries(carrier).forEach(([p, v]) => {
|
|
2317
|
-
(SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
|
|
2318
|
-
});
|
|
2319
|
-
|
|
2320
|
-
function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
|
|
2321
|
-
if(carrier.target) {
|
|
2322
|
-
assignProp(carrier, '_SetAttributes', Object.create(null));
|
|
2323
|
-
assignAnnotation(carrier._SetAttributes, propName, propValue);
|
|
2324
|
-
if(removeFromType) {
|
|
2325
|
-
delete carrier[propName];
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
function removeFromForeignKey(carrier, propName) {
|
|
2331
|
-
if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
|
|
2332
|
-
delete carrier[propName];
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
// Assign but not overwrite annotation
|
|
2338
|
-
function assignAnnotation(node, name, value) {
|
|
2339
|
-
if(value !== undefined &&
|
|
2340
|
-
name !== undefined && name[0] === '@' &&
|
|
2341
|
-
(node[name] === undefined ||
|
|
2342
|
-
node[name] && node[name] === null)) {
|
|
2343
|
-
node[name] = value;
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2347
|
-
// Set non enumerable property if it doesn't exist yet
|
|
2348
|
-
function assignProp(obj, prop, value) {
|
|
2349
|
-
if(obj[prop] === undefined) {
|
|
2350
|
-
setProp(obj, prop, value);
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
|
|
2354
2012
|
module.exports = {
|
|
2355
2013
|
initializeModel,
|
|
2356
|
-
assignAnnotation
|
|
2357
2014
|
}
|