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