@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +19 -0
  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 +7 -7
  9. package/lib/api/options.js +2 -3
  10. package/lib/base/message-registry.js +17 -5
  11. package/lib/base/messages.js +18 -39
  12. package/lib/base/model.js +2 -0
  13. package/lib/checks/actionsFunctions.js +8 -7
  14. package/lib/checks/selectItems.js +96 -14
  15. package/lib/checks/types.js +5 -8
  16. package/lib/checks/validator.js +1 -2
  17. package/lib/compiler/assert-consistency.js +64 -12
  18. package/lib/compiler/base.js +6 -4
  19. package/lib/compiler/builtins.js +58 -8
  20. package/lib/compiler/checks.js +1 -1
  21. package/lib/compiler/define.js +25 -22
  22. package/lib/compiler/extend.js +16 -10
  23. package/lib/compiler/finalize-parse-cdl.js +5 -9
  24. package/lib/compiler/index.js +2 -0
  25. package/lib/compiler/populate.js +34 -31
  26. package/lib/compiler/propagator.js +11 -6
  27. package/lib/compiler/resolve.js +14 -15
  28. package/lib/compiler/shared.js +53 -26
  29. package/lib/compiler/tweak-assocs.js +5 -11
  30. package/lib/compiler/utils.js +13 -4
  31. package/lib/edm/annotations/preprocessAnnotations.js +8 -4
  32. package/lib/edm/csn2edm.js +3 -3
  33. package/lib/edm/edm.js +9 -1
  34. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  35. package/lib/edm/edmInboundChecks.js +85 -0
  36. package/lib/edm/edmPreprocessor.js +295 -638
  37. package/lib/edm/edmUtils.js +85 -5
  38. package/lib/gen/Dictionary.json +29 -9
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -2
  41. package/lib/gen/languageLexer.js +3 -0
  42. package/lib/gen/languageParser.js +4344 -4530
  43. package/lib/inspect/.eslintrc.json +4 -0
  44. package/lib/inspect/index.js +14 -0
  45. package/lib/inspect/inspectModelStatistics.js +81 -0
  46. package/lib/inspect/inspectPropagation.js +189 -0
  47. package/lib/inspect/inspectUtils.js +44 -0
  48. package/lib/json/from-csn.js +3 -2
  49. package/lib/json/to-csn.js +8 -6
  50. package/lib/language/genericAntlrParser.js +121 -63
  51. package/lib/language/language.g4 +19 -57
  52. package/lib/main.d.ts +1 -0
  53. package/lib/model/api.js +1 -1
  54. package/lib/model/csnRefs.js +55 -29
  55. package/lib/model/csnUtils.js +11 -7
  56. package/lib/model/revealInternalProperties.js +2 -3
  57. package/lib/modelCompare/compare.js +3 -0
  58. package/lib/optionProcessor.js +27 -0
  59. package/lib/render/toCdl.js +57 -32
  60. package/lib/render/toSql.js +24 -8
  61. package/lib/render/utils/common.js +3 -4
  62. package/lib/transform/db/associations.js +43 -35
  63. package/lib/transform/db/cdsPersistence.js +0 -1
  64. package/lib/transform/db/flattening.js +3 -4
  65. package/lib/transform/db/transformExists.js +7 -5
  66. package/lib/transform/draft/db.js +1 -1
  67. package/lib/transform/forHanaNew.js +11 -2
  68. package/lib/transform/forOdataNew.js +1 -1
  69. package/lib/transform/odata/typesExposure.js +14 -5
  70. package/lib/utils/moduleResolve.js +0 -1
  71. package/package.json +2 -2
  72. 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 { forEachDefinition, forEachGeneric, forEachMemberRecursively,
5
- isEdmPropertyRendered, getUtils, cloneCsnNonDict, isBuiltinType } = require('../model/csnUtils');
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, throwWithAnyError } = messageFunctions;
29
+ const { info, warning, error, message } = messageFunctions;
35
30
 
36
- let csnUtils = getUtils(csn);
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 prepareConstraints
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, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
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, initEdmNavPropBindingTargets);
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 [serviceRoots, schemas, reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
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
- // '_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
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, eltName) {
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._containerEntity)
387
- setProp(containee, '_containerEntity', []);
406
+ if (!containee.$containerNames)
407
+ setProp(containee, '$containerNames', []);
388
408
  // add container only once per containee
389
- if (!containee._containerEntity.includes(container.name))
390
- containee._containerEntity.push(container.name);
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 && !(isBuiltinType(elt.type) || elt.elements)) {
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
- [], true, { elementsOnly: true });
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 and rewriteContainmentAnnotations()
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
- // 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
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 = [], construct) => {
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
- (options.odataProxies || options.odataXServiceRefs) &&
945
- // must be a managed association with keys OR an unambiguous backlink
946
- element.keys ||
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
- else if(options.odataProxies) {
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
- // no proxy: no navigation
978
- assignAnnotation(element, '@odata.navigable', false);
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
- assignAnnotation(element, '@odata.navigable', false);
985
- noNavPropMsg(element);
1012
+ muteNavProp(element);
986
1013
  return;
987
1014
  }
988
1015
  }
989
1016
  });
990
1017
  }
991
1018
 
992
- function noNavPropMsg(elt) {
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.$touched) {
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, '$touched', true);
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.$touched;
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.$touched) {
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, '$touched', true);
1712
+ setProp(elt, '$visited', true);
1660
1713
  Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
1661
- delete elt.$touched;
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
- // Checks section starts here
1753
- //
1809
+ // meaningless for non-entities and proxies
1810
+ if(rootContainer.kind !== 'entity' || rootContainer.$proxy)
1811
+ return;
1754
1812
 
1755
- // eslint-disable-next-line no-unused-vars
1756
- function resolveForeignKeyRefs() {
1757
- forEachDefinition(csn, (def, defName) => {
1758
- let currPath = ['definitions', defName ];
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
- function inboundQualificationChecks() {
1771
- forEachDefinition(csn, [ attach$path, checkChainedArray ]);
1772
- checkNestedContextsAndServices();
1773
- throwWithAnyError();
1823
+ if(!isRecursiveContainment && !isRootNode)
1824
+ return;
1774
1825
 
1775
- // attach $path to all
1776
- function attach$path(def, defName) {
1777
- setProp(def, '$path', [ 'definitions', defName ]);
1778
- forEachMemberRecursively(def,
1779
- (member, _memberName, _prop, path) => {
1780
- setProp(member, '$path', path);
1781
- }, [ 'definitions', defName ]);
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
- function checkChainedArray(def, defName) {
1785
- if (!isMyServiceRequested(defName))
1786
- return;
1787
- let currPath = ['definitions', defName];
1788
- checkIfItemsOfItems(def, undefined, undefined, currPath);
1789
- forEachMemberRecursively(def, checkIfItemsOfItems, currPath);
1790
-
1791
- function checkIfItemsOfItems(construct, _constructName, _prop, path) {
1792
- const constructType = csnUtils.effectiveType(construct);
1793
- if (constructType.items) {
1794
- if (constructType.items.items) {
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
- const itemsType = csnUtils.effectiveType(constructType.items);
1800
- if (itemsType.items)
1801
- message('chained-array-of', path);
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 checkNestedContextsAndServices() {
1807
- !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
1808
- const parent = whatsMyServiceRootName(sn, false);
1809
- if(parent && requestedServiceNames.includes(parent) && parent !== sn) {
1810
- message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1811
- 'A service can\'t be nested within a service $(ART)' );
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
- Object.entries(csn.definitions).forEach(([fqName, art]) => {
1816
- if(art.kind === 'context') {
1817
- const parent = whatsMyServiceRootName(fqName);
1818
- if(requestedServiceNames.includes(parent)) {
1819
- message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
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
  }