@sap/cds-compiler 2.12.0 → 2.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -290
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -2,7 +2,7 @@
2
2
  /* eslint max-statements-per-line:off */
3
3
  const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
4
4
  const { forEachDefinition, forEachGeneric, forEachMemberRecursively,
5
- isEdmPropertyRendered, getUtils, cloneCsn, isBuiltinType } = require('../model/csnUtils');
5
+ isEdmPropertyRendered, getUtils, cloneCsnNonDict, isBuiltinType } = require('../model/csnUtils');
6
6
  const edmUtils = require('./edmUtils.js');
7
7
  const typesExposure = require('../transform/odata/typesExposure');
8
8
  const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
@@ -18,10 +18,8 @@ const {
18
18
  isParameterizedEntity,
19
19
  resolveOnConditionAndPrepareConstraints,
20
20
  finalizeReferentialConstraints,
21
- isODataSimpleIdentifier,
22
21
  isEntity,
23
22
  getSchemaPrefix,
24
- isActionOrFunction
25
23
  } = require('./edmUtils.js');
26
24
 
27
25
  /**
@@ -34,45 +32,46 @@ const {
34
32
  * @param {CSN.Model} csn
35
33
  * @param {object} _options
36
34
  */
37
- function initializeModel(csn, _options, messageFunctions)
35
+ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=undefined)
38
36
  {
39
- if (!_options)
40
- throw Error('Please debug me: initializeModel must be invoked with options');
37
+ const { info, warning, error, message, throwWithAnyError } = messageFunctions;
41
38
 
42
- const { info, warning, error, message, throwWithError } = messageFunctions;
39
+ let csnUtils = getUtils(csn);
43
40
 
44
- const csnUtils = getUtils(csn);
45
- const {
46
- getCsnDef,
47
- getFinalTypeDef,
48
- isStructured,
49
- isAssocOrComposition,
50
- } = getUtils(csn);
41
+ // proxies are merged into the final model after all proxy elements are collected
42
+ const proxyCache = [];
43
+ // iterarte only over those definitions that need to be preprocessed
44
+ // instead of mangling through the whole model each time
45
+ // preprocess steps removing adding to the model must co-modify this map
46
+ const reqDefs = { definitions: Object.create(null) };
51
47
 
52
48
 
53
49
  // make sure options are complete
54
50
  let options = validateOptions(_options);
55
51
 
56
- // Fetch service definitions
57
- const serviceRoots = Object.keys(csn.definitions || {}).reduce((serviceRoots, artName) => {
58
- const art = csn.definitions[artName];
59
- if(art.kind === 'service') {
60
- serviceRoots[artName] = Object.assign(art, { name: artName });
61
- }
62
- return serviceRoots;
63
- }, Object.create(null) );
52
+ const [ serviceRoots,
53
+ serviceRootNames,
54
+ fallBackSchemaName,
55
+ whatsMyServiceRootName ] = getAnOverviewOnTheServices(csn);
56
+
57
+ if(requestedServiceNames === undefined)
58
+ requestedServiceNames = options.serviceNames;
59
+ if(requestedServiceNames === undefined) {
60
+ requestedServiceNames = serviceRootNames;
61
+ }
64
62
 
65
- // first of all we need to know about all 'real' user defined services
66
- const serviceRootNames = Object.keys(serviceRoots).sort((a,b)=>b.length-a.length);
67
- function whatsMyServiceRootName(n, self=true) {
68
- return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
63
+ function isMyServiceRequested(n) {
64
+ return requestedServiceNames.includes(whatsMyServiceRootName(n));
69
65
  }
66
+
70
67
  if(serviceRootNames.length === 0) {
71
- return [serviceRoots, Object.create(null), whatsMyServiceRootName, options];
68
+ return [serviceRoots, Object.create(null), reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
72
69
  }
73
70
 
74
71
  // Structural CSN inbound QA checks
75
72
  inboundQualificationChecks();
73
+ // not needed at the moment
74
+ // resolveForeignKeyRefs();
76
75
 
77
76
  if(isBetaEnabled(options, undefined)) {
78
77
  splitDottedDefinitionsIntoSeparateServices();
@@ -85,10 +84,13 @@ function initializeModel(csn, _options, messageFunctions)
85
84
  renameDottedDefinitionsInsideServiceOrContext();
86
85
 
87
86
  /*
88
- In order to cover the scenario when the renderer is called with option V2 directly, there is
89
- the need to expand to final base type when the CSN was already transformed, bur for V4.
90
- The logic will run also when the CSN was persisted on a file system and the non-enumerable
91
- properties are lost. Also, will be execute when called with cdsc.
87
+ Final base type expansion is required here when:
88
+ 1) The input CSN was already transformed for V4 but shall be rendered in V2 and the
89
+ edmx generator is called directly (bypassing OData transformation)
90
+ 2) The input CSN was already transformed for V4 and persisted (all non-enumerables are
91
+ stripped of)
92
+ 3) call via cdsc
93
+
92
94
  At the end of the day, this module must be called only here, in the renderer and removed
93
95
  as a step in the OData transformer with the goal to have a protocol agnostic OData CSN.
94
96
  */
@@ -96,20 +98,18 @@ function initializeModel(csn, _options, messageFunctions)
96
98
  const { toFinalBaseType }= require('../transform/transformUtilsNew').getTransformers(csn, options);
97
99
  expandCSNToFinalBaseType(csn, { toFinalBaseType }, csnUtils, serviceRootNames, options);
98
100
  }
101
+
99
102
  /*
100
- Enrich the CSN by de-anonymizing and exposing of required types that are defined outside services.
103
+ Enrich the CSN by de-anonymizing and exposing types that are required to make the service self contained.
101
104
  Type exposure will add additional schema contexts and group the exposed types in these contexts.
102
105
  contexts either represent another service (if the type to be exposed resides in that
103
106
  service), the namespace (including (sub-)contexts) or as last resort (if the type name
104
107
  has no prefix path) a 'root' namespace.
105
108
  */
106
- const schemas = typesExposure(csn, whatsMyServiceRootName, options, csnUtils, { error });
107
-
108
- // First attach names to all definitions (and actions/params) in the model
109
- // elements are done in initializeStruct
110
- forEachDefinition(csn, attachNameProperty);
109
+ const schemas = typesExposure(csn, whatsMyServiceRootName, requestedServiceNames,
110
+ fallBackSchemaName, options, csnUtils, { error });
111
111
 
112
- // next, we must get an overview about all schemas (including the services)
112
+ // Get an overview about all schemas (including the services)
113
113
  const schemaNames = [...serviceRootNames];
114
114
  schemaNames.push(...Object.keys(schemas));
115
115
  // sort schemas in reverse order to allow longest match in whatsMySchemaName function
@@ -119,50 +119,99 @@ function initializeModel(csn, _options, messageFunctions)
119
119
  }
120
120
 
121
121
  if(schemaNames.length) {
122
- Object.values(serviceRoots).forEach(initializeService);
122
+ // First attach names to all definitions (and actions/params) in the model
123
+ // elements are done in initializeStruct
123
124
  // Set myServiceName for later reference and indication of a service member
124
- // First attach names to all definitions in the model
125
+ // First attach names to all definitions in the model and fill reqDefs
125
126
  // Link association targets and spray @odata.contained over untagged compositions
126
- forEachDefinition(csn, [ (def, defName) => {
127
- const mySchemaName = whatsMySchemaName(defName);
128
- mySchemaName && setProp(def, '$mySchemaName', mySchemaName) }, linkAssociationTarget ]);
127
+ forEachDefinition(csn, [
128
+ attachNameProperty,
129
+ (def, defName) => {
130
+ const mySchemaName = whatsMySchemaName(defName);
131
+ mySchemaName && setProp(def, '$mySchemaName', mySchemaName);
132
+ if(isMyServiceRequested(defName))
133
+ reqDefs.definitions[defName] = def;
134
+ },
135
+ linkAssociationTarget ]);
136
+ // initialize requested services
137
+ const skip = { skipArtifact: (_def, defName) => !isMyServiceRequested(defName) };
138
+ forEachDefinition({ definitions: serviceRoots }, initService, skip);
129
139
  // Create data structures for containments
130
- forEachDefinition(csn, initializeContainments);
140
+ forEachDefinition(reqDefs, initContainments);
131
141
  // Initialize entities with parameters (add Parameter entity)
132
- forEachDefinition(csn, initializeParameterizedEntityOrView);
142
+ forEachDefinition(reqDefs, initParameterizedEntityOrView);
133
143
  // Initialize structures
134
- forEachDefinition(csn, initializeStructure);
144
+ forEachDefinition(csn, initStructure);
135
145
  // Initialize associations after _parent linking
136
- forEachDefinition(csn, prepareConstraints);
146
+ forEachDefinition(reqDefs, initConstraints);
137
147
  // Mute V4 elements depending on constraint preparation
138
148
  if(options.isV4())
139
- forEachDefinition(csn, ignoreProperties);
149
+ forEachDefinition(reqDefs, ignoreProperties);
140
150
  // calculate constraints based on ignoreProperties and prepareConstraints
141
- forEachDefinition(csn, finalizeConstraints);
151
+ forEachDefinition(reqDefs, finalizeConstraints);
142
152
  // convert exposed types into cross schema references if required
143
153
  // must be run before proxy exposure to avoid potential reference collisions
144
154
  convertExposedTypesOfOtherServicesIntoCrossReferences();
145
155
  // create association target proxies
146
156
  // Decide if an entity set needs to be constructed or not
147
- forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
157
+ forEachDefinition(reqDefs, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
158
+ // finalize proxy creation
159
+ mergeProxiesIntoModel();
160
+
148
161
  if(options.isV4())
149
- forEachDefinition(csn, initializeEdmNavPropBindingTargets);
162
+ forEachDefinition(reqDefs, initEdmNavPropBindingTargets);
150
163
 
151
164
  // Things that can be done in one pass
152
165
  // Create edmKeyRefPaths
153
166
  // Create NavigationPropertyBindings, requires determineEntitySet
154
167
  // Map /** doc comments */ to @CoreDescription
155
- // Artifact identifier spec compliance check (should be run last)
156
- forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
157
- initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
168
+ forEachDefinition(reqDefs, [
169
+ initEdmKeyRefPaths,
170
+ initEdmNavPropBindingPaths,
171
+ initEdmTypesAndDescription
172
+ ]);
158
173
  }
159
- return [serviceRoots, schemas, whatsMyServiceRootName, options];
174
+ return [serviceRoots, schemas, reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
160
175
 
161
176
  //////////////////////////////////////////////////////////////////////
162
177
  //
163
178
  // Service initialization starts here
164
179
  //
165
180
 
181
+ function getAnOverviewOnTheServices(csn) {
182
+ const defs = csn.definitions || {};
183
+ const serviceRoots = Object.create(null);
184
+ for(const defName in defs) {
185
+ const def = defs[defName];
186
+ if(def && def.kind === 'service')
187
+ serviceRoots[defName] = Object.assign(def, { name: defName });
188
+ }
189
+
190
+ // first of all we need to know about all 'real' user defined services
191
+ const serviceRootNames = Object.keys(serviceRoots).sort((a,b)=>b.length-a.length);
192
+
193
+ function whatsMyServiceRootName(n, self=true) {
194
+ return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
195
+ }
196
+
197
+ // find a globally unambiguous schema name to collect all top level 'root' types
198
+ // TODO: work on service basis (this requires post exposure renaming)
199
+ let fallBackSchemaName = 'root';
200
+ let i = 1;
201
+ while (Object.keys(defs).some(artName => {
202
+ const p = artName.split('.');
203
+ return p.length === 2 && p[0] === fallBackSchemaName;
204
+ })) {
205
+ fallBackSchemaName = 'root' + i++;
206
+ }
207
+
208
+ return [
209
+ serviceRoots,
210
+ serviceRootNames,
211
+ fallBackSchemaName,
212
+ whatsMyServiceRootName ];
213
+ }
214
+
166
215
  /*
167
216
  Replace dots in sub-service and sub-context definitions with underscores to be
168
217
  Odata ID compliant.
@@ -174,9 +223,10 @@ function initializeModel(csn, _options, messageFunctions)
174
223
  // Find the first definition above the current definition or undefined otherwise.
175
224
  // Definition can either be a context or a service
176
225
  function getRootDef(name) {
226
+ const scopeKinds = {'service':1, 'context':1};
177
227
  let pos = name.lastIndexOf('.');
178
228
  name = pos < 0 ? undefined : name.substring(0, pos);
179
- while (name && !['service', 'context'].includes(csn.definitions[name] && csn.definitions[name].kind)) {
229
+ while (name && !((csn.definitions[name] && csn.definitions[name].kind) in scopeKinds)) {
180
230
  pos = name.lastIndexOf('.');
181
231
  name = pos < 0 ? undefined : name.substring(0, pos);
182
232
  }
@@ -185,17 +235,18 @@ function initializeModel(csn, _options, messageFunctions)
185
235
 
186
236
  const dotEntityNameMap = Object.create(null);
187
237
  const dotTypeNameMap = Object.create(null);
238
+ const kinds = {'entity':1, 'type':1, 'action':1, 'function':1};
188
239
  forEachDefinition(csn, (def, defName) => {
189
- if(['entity', 'view', 'type', 'action', 'function'].includes(def.kind)) {
240
+ if(def.kind in kinds) {
190
241
  const rootDef = getRootDef(defName);
191
242
  // if this definition has a root def and the root def is not the service/schema name
192
243
  // => service C { type D.E }, replace the prefix dots with underscores
193
244
  if(rootDef && defName !== rootDef && rootDef !== getSchemaPrefix(defName)) {
194
245
  let newDefName = rootDef + '.' + defName.replace(rootDef + '.', '').replace(/\./g, '_');
195
246
  // store renamed types in correlation maps for later renaming
196
- if(['entity', 'view'].includes(def.kind))
247
+ if(def.kind === 'entity')
197
248
  dotEntityNameMap[defName] = newDefName;
198
- if(['type'].includes(def.kind))
249
+ if(def.kind === 'type')
199
250
  dotTypeNameMap[defName] = newDefName;
200
251
  // rename in csn.definitions
201
252
  const art = csn.definitions[newDefName];
@@ -252,7 +303,7 @@ function initializeModel(csn, _options, messageFunctions)
252
303
  */
253
304
  function splitDottedDefinitionsIntoSeparateServices() {
254
305
  forEachDefinition(csn, (def, defName) => {
255
- if(![ 'service' ].includes(def.kind)) {
306
+ if(def.kind !== 'service') {
256
307
  const myServiceRoot = whatsMyServiceRootName(defName);
257
308
  const mySchemaPrefix = getSchemaPrefix(defName);
258
309
  if(myServiceRoot && options.isV4() &&
@@ -283,18 +334,8 @@ function initializeModel(csn, _options, messageFunctions)
283
334
  }
284
335
 
285
336
  // initialize the service itself
286
- function initializeService(service) {
287
- // check service name
288
- if (service.name.length > 511) {
289
- error(null, ['definitions', service.name], 'OData namespace must not exceed 511 characters' );
290
- }
291
- const simpleIdentifiers = service.name.split('.');
292
- simpleIdentifiers.forEach((identifier) => {
293
- if (!isODataSimpleIdentifier(identifier)) {
294
- signalIllegalIdentifier(identifier, ['definitions', service.name]);
295
- }
296
- });
297
- setSAPSpecificV2AnnotationsToEntityContainer(options, service);
337
+ function initService(serviceRoot) {
338
+ setSAPSpecificV2AnnotationsToEntityContainer(options, serviceRoot);
298
339
  }
299
340
 
300
341
  // link association target to association and add @odata.contained to compositions in V4
@@ -341,8 +382,8 @@ function initializeModel(csn, _options, messageFunctions)
341
382
  // non-containment rendering. If containment rendering is active, the containee has no
342
383
  // entity set. Instead try to rewrite the annotation in such a way that it is effective
343
384
  // on the containment navigation property.
344
- function initializeContainments(container) {
345
- if(['entity', 'view'].includes(container.kind)) {
385
+ function initContainments(container) {
386
+ if(container.kind === 'entity') {
346
387
  forEachMemberRecursively(container, initContainments,
347
388
  [], true, { elementsOnly: true });
348
389
  }
@@ -404,7 +445,7 @@ function initializeModel(csn, _options, messageFunctions)
404
445
  // must be called.
405
446
  // As a param entity is a potential proxy candidate, this split must be performed on
406
447
  // all definitions
407
- function initializeParameterizedEntityOrView(entityCsn, entityName) {
448
+ function initParameterizedEntityOrView(entityCsn, entityName) {
408
449
 
409
450
  if(!isParameterizedEntity(entityCsn))
410
451
  return;
@@ -421,13 +462,71 @@ function initializeModel(csn, _options, messageFunctions)
421
462
  // Backlink Navigation Property "Parameters" to <ViewName>Parameters
422
463
 
423
464
  // this code can be extended for aggregated views
424
- const parameterEntityName = entityName + 'Parameters';
425
- const originalEntityName = entityName + 'Type';
426
- const originalEntitySetName = entityName + 'Set';
427
- const parameterToOriginalAssocName = 'Set';
428
- const backlinkAssocName = 'Parameters';
465
+ const typeEntityName = entityName + 'Type';
466
+ const typeEntitySetName = entityName + 'Set';
467
+ const parameterToTypeAssocName = 'Set';
468
+ const typeToParameterAssocName = 'Parameters';
429
469
  let hasBacklink = true;
430
470
 
471
+
472
+ // create the Parameter Definition
473
+ const parameterCsn = createParameterEntity(entityCsn, entityName, false);
474
+
475
+ // create the Type Definition
476
+ // modify the original parameter entity with backlink and new name
477
+ if(csn.definitions[typeEntityName])
478
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: typeEntityName });
479
+ else {
480
+ csn.definitions[typeEntityName] = entityCsn;
481
+ reqDefs.definitions[typeEntityName] = entityCsn;
482
+ delete csn.definitions[entityCsn.name];
483
+ delete reqDefs.definitions[entityCsn.name];
484
+ entityCsn.name = typeEntityName;
485
+ }
486
+ setProp(entityCsn, '$entitySetName', typeEntitySetName);
487
+ // add backlink association
488
+ if(hasBacklink) {
489
+ entityCsn.elements[typeToParameterAssocName] = {
490
+ name: typeToParameterAssocName,
491
+ target: parameterCsn.name,
492
+ type: 'cds.Association',
493
+ on: [ { ref: [ 'Parameters', 'Set' ] }, '=', { ref: [ '$self' ] } ]
494
+ };
495
+ setProp(entityCsn.elements[typeToParameterAssocName], '_selfReferences', []);
496
+ setProp(entityCsn.elements[typeToParameterAssocName], '_target', parameterCsn);
497
+ setProp(entityCsn.elements[typeToParameterAssocName], '$path',
498
+ [ 'definitions', typeEntityName, 'elements', typeToParameterAssocName ] );
499
+
500
+ // rewrite $path
501
+ if(entityCsn.$path)
502
+ entityCsn.$path[1] = typeEntityName;
503
+ forEachMemberRecursively(entityCsn, (member) => {
504
+ if(member.$path)
505
+ member.$path[1] = typeEntityName;
506
+ });
507
+ }
508
+
509
+ /*
510
+ <EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
511
+ sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
512
+ */
513
+ assignProp(entityCsn, '_SetAttributes',
514
+ {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });
515
+
516
+ // redirect inbound associations/compositions to the parameter entity
517
+ Object.keys(entityCsn.$sources || {}).forEach(n => {
518
+ // preserve the original target for constraint calculation
519
+ setProp(entityCsn.$sources[n], '_originalTarget', entityCsn.$sources[n]._target);
520
+ entityCsn.$sources[n]._target = parameterCsn;
521
+ entityCsn.$sources[n].target = parameterCsn.name;
522
+ });
523
+ rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToTypeAssocName);
524
+ }
525
+
526
+ function createParameterEntity(entityCsn, entityName, isProxy) {
527
+ const parameterEntityName = entityName + 'Parameters';
528
+ const parameterToTypeAssocName = 'Set';
529
+
431
530
  // Construct the parameter entity
432
531
  const parameterCsn = {
433
532
  name: parameterEntityName,
@@ -435,8 +534,8 @@ function initializeModel(csn, _options, messageFunctions)
435
534
  elements: Object.create(null),
436
535
  '@sap.semantics': 'parameters',
437
536
  };
438
- setProp(parameterCsn, '$entitySetName', entityName);
439
-
537
+ if(!isProxy)
538
+ setProp(parameterCsn, '$entitySetName', entityName);
440
539
  if(entityCsn.$location){
441
540
  assignProp(parameterCsn, '$location', entityCsn.$location);
442
541
  }
@@ -462,88 +561,54 @@ function initializeModel(csn, _options, messageFunctions)
462
561
  entityCsn._containerEntity = [ parameterCsn ];
463
562
 
464
563
  forEachGeneric(entityCsn, 'params', (p,n) => {
465
- let elt = cloneCsn(p, options);
564
+ let elt = cloneCsnNonDict(p, options);
466
565
  elt.name = n;
467
566
  delete elt.kind;
567
+ setProp(elt, '$path', [ 'definitions', parameterEntityName, 'elements', n ]);
468
568
  elt.key = true; // params become primary key in parameter entity
569
+ /*
570
+ Spec meeting decision 28.02.22:
571
+ Annotation @sap.parameter allows two values "mandatory"/"optional".
572
+ Question was how to deal with incompatible "optional".
573
+ Only "mandatory" is allowed because in RAP all parameters are NOT NULL
574
+ and so they are in CAP (all view parameters become primary keys which are not null).
575
+ */
576
+ if(options.isV2())
577
+ assignAnnotation(elt, '@sap.parameter', 'mandatory');
578
+ else
579
+ assignAnnotation(elt, '@Common.FieldControl', { '#': 'Mandatory' });
469
580
  parameterCsn.elements[n] = elt;
470
581
  });
471
582
  linkAssociationTarget(parameterCsn);
472
- initializeContainments(parameterCsn);
473
- // add assoc to result set, FIXME: is the cardinality correct?
474
- parameterCsn.elements[parameterToOriginalAssocName] = {
475
- '@odata.contained': true,
476
- name: parameterToOriginalAssocName,
477
- target: entityCsn.name,
478
- type: 'cds.Association',
479
- cardinality: { src: 1, min: 0, max: '*' }
480
- };
481
- setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn);
482
- setProp(parameterCsn.elements[parameterToOriginalAssocName], '$path',
483
- [ 'definitions', parameterEntityName, 'elements', parameterToOriginalAssocName ] );
484
-
485
- // rewrite $path
486
- setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
487
- forEachMemberRecursively(parameterCsn, (member) => {
488
- if(member.$path)
489
- member.$path[1] = parameterEntityName;
490
- });
491
-
492
- if(csn.definitions[parameterCsn.name])
493
- error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
494
- else
495
- csn.definitions[parameterCsn.name] = parameterCsn;
496
- // modify the original parameter entity with backlink and new name
497
- if(csn.definitions[originalEntityName])
498
- error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: originalEntityName });
499
- else {
500
- csn.definitions[originalEntityName] = entityCsn;
501
- delete csn.definitions[entityCsn.name];
502
- entityCsn.name = originalEntityName;
503
- }
504
- setProp(entityCsn, '$entitySetName', originalEntitySetName);
505
- // add backlink association
506
- if(hasBacklink) {
507
- entityCsn.elements[backlinkAssocName] = {
508
- name: backlinkAssocName,
509
- target: parameterCsn.name,
583
+ initContainments(parameterCsn);
584
+ // add assoc to result set, FIXME: is the cardinality correct?
585
+ if(!isProxy) {
586
+ parameterCsn.elements[parameterToTypeAssocName] = {
587
+ '@odata.contained': true,
588
+ name: parameterToTypeAssocName,
589
+ target: entityCsn.name,
510
590
  type: 'cds.Association',
511
- on: [ { ref: [ 'Parameters', 'Set' ] }, '=', { ref: [ '$self' ] } ]
591
+ cardinality: { src: 1, min: 0, max: '*' }
512
592
  };
513
- setProp(entityCsn.elements[backlinkAssocName], '_selfReferences', []);
514
- setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn);
515
- setProp(entityCsn.elements[backlinkAssocName], '$path',
516
- [ 'definitions', originalEntityName, 'elements', backlinkAssocName ] );
517
-
518
- // rewrite $path
519
- if(entityCsn.$path)
520
- entityCsn.$path[1] = originalEntityName;
521
- forEachMemberRecursively(entityCsn, (member) => {
522
- if(member.$path)
523
- member.$path[1] = originalEntityName;
524
- });
593
+ setProp(parameterCsn.elements[parameterToTypeAssocName], '_target', entityCsn);
594
+ setProp(parameterCsn.elements[parameterToTypeAssocName], '$path',
595
+ [ 'definitions', parameterEntityName, 'elements', parameterToTypeAssocName ] );
525
596
  }
597
+ // rewrite $path
598
+ setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
526
599
 
527
- /*
528
- <EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
529
- sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
530
- */
531
- assignProp(entityCsn, '_SetAttributes',
532
- {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });
533
-
534
-
535
-
536
- // redirect inbound associations/compositions to the parameter entity
537
- Object.keys(entityCsn.$sources || {}).forEach(n => {
538
- // preserve the original target for constraint calculation
539
- setProp(entityCsn.$sources[n], '_originalTarget', entityCsn.$sources[n]._target);
540
- entityCsn.$sources[n]._target = parameterCsn;
541
- entityCsn.$sources[n].target = parameterCsn.name;
542
- });
543
- rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToOriginalAssocName);
600
+ // proxies are registered in model separately
601
+ if(!isProxy) {
602
+ if(csn.definitions[parameterCsn.name])
603
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
604
+ else {
605
+ csn.definitions[parameterCsn.name] = parameterCsn;
606
+ reqDefs.definitions[parameterCsn.name] = parameterCsn;
607
+ }
608
+ }
609
+ return parameterCsn;
544
610
  }
545
611
 
546
-
547
612
  function initElement(element, name, struct) {
548
613
  setProp(element, 'name', name)
549
614
  setProp(element, '_parent', struct);
@@ -569,7 +634,7 @@ function initializeModel(csn, _options, messageFunctions)
569
634
  }
570
635
 
571
636
  // Initialize a structured artifact
572
- function initializeStructure(def) {
637
+ function initStructure(def) {
573
638
 
574
639
  // Don't operate on any structured types other than type and entity
575
640
  // such as events and aspects
@@ -580,24 +645,13 @@ function initializeModel(csn, _options, messageFunctions)
580
645
  let validFrom = [], validKey = [];
581
646
 
582
647
  // Iterate all struct elements
583
- forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
584
- if(!['elements'].includes(prop))
648
+ forEachMemberRecursively(def.items || def, (element, elementName, prop, _path = [], construct) => {
649
+ if(prop !== 'elements')
585
650
  return;
586
651
 
587
652
  initElement(element, elementName, construct);
588
653
 
589
- if(!['event', 'aspect'].includes(def.kind)) {
590
- if(element._parent && element._parent.$mySchemaName) {
591
- if(!isODataSimpleIdentifier(elementName)) {
592
- signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
593
- } else if (options.isV2() && /^(_|[0-9])/.test(elementName) && ['view', 'entity'].includes(element._parent.kind)) {
594
- // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
595
- error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
596
- 'Element names must not start with $(PROP) for OData V2');
597
- }
598
- }
599
- }
600
- // collect temporal information
654
+ // collect temporal information
601
655
  if(element['@cds.valid.key']) {
602
656
  validKey.push(element);
603
657
  }
@@ -677,18 +731,19 @@ function initializeModel(csn, _options, messageFunctions)
677
731
  }
678
732
 
679
733
  // Prepare the associations for the subsequent steps
680
- function prepareConstraints(struct) {
681
- if(!isStructuredArtifact(struct))
734
+ function initConstraints(def) {
735
+ if(!isStructuredArtifact(def))
682
736
  return;
683
737
 
684
- forEachMemberRecursively(struct.items || struct, (element) => {
685
- if (isAssociationOrComposition(element) && !element._ignore) {
738
+ forEachMemberRecursively(def.items || def, initConstraintsOnAssoc, [], true, { elementsOnly: true });
739
+ }
740
+ function initConstraintsOnAssoc(element) {
741
+ if (isAssociationOrComposition(element) && !element._ignore && !element._constraints) {
686
742
  // setup the constraints object
687
- setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
743
+ setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
688
744
  // and crack the ON condition
689
- resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
690
- }
691
- }, [], true, { elementsOnly: true });
745
+ resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
746
+ }
692
747
  }
693
748
 
694
749
  /*
@@ -760,38 +815,39 @@ function initializeModel(csn, _options, messageFunctions)
760
815
  It may be that now a number of properties are not rendered and cannot act as constraints (see isConstraintCandidate())
761
816
  in edmUtils
762
817
  */
763
- function finalizeConstraints(struct) {
764
- if(!isStructuredArtifact(struct))
818
+ function finalizeConstraints(def) {
819
+ if(!isStructuredArtifact(def))
765
820
  return;
766
821
 
767
- forEachMemberRecursively(struct.items || struct, (element) => {
768
- if (isAssociationOrComposition(element) && !element._ignore) {
769
- finalizeReferentialConstraints(csn, element, options, info);
822
+ forEachMemberRecursively(def.items || def, finalizeConstraintsOnAssoc, [], true, { elementsOnly: true });
823
+ }
824
+ function finalizeConstraintsOnAssoc(element) {
825
+ if (isAssociationOrComposition(element) && !element._ignore && element._constraints) {
826
+ finalizeReferentialConstraints(csn, element, options, info);
770
827
 
771
- if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
828
+ if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
772
829
  // if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
773
- if(element._constraints._partnerCsn.cardinality) {
830
+ if(element._constraints._partnerCsn.cardinality) {
774
831
  // if the forward association has set a src cardinality and it deviates from the backlink target cardinality raise a warning
775
832
  // in V2 only, in V4 the source cardinality is rendered implicitly at the Type property
776
- if(element._constraints._partnerCsn.cardinality.src) {
777
- let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
778
- let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
779
- if(options.isV2() && srcMult !== newMult) {
833
+ if(element._constraints._partnerCsn.cardinality.src) {
834
+ let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
835
+ let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
836
+ if(options.isV2() && srcMult !== newMult) {
780
837
  // Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
781
- warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
782
- }
783
- }
784
- else {
785
- // .. but only if the original assoc hasn't set src yet
786
- element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
838
+ warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
787
839
  }
788
840
  }
789
841
  else {
790
- element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
842
+ // .. but only if the original assoc hasn't set src yet
843
+ element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
791
844
  }
792
845
  }
846
+ else {
847
+ element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
848
+ }
793
849
  }
794
- }, [], true, { elementsOnly: true });
850
+ }
795
851
  }
796
852
 
797
853
  /*
@@ -807,8 +863,10 @@ function initializeModel(csn, _options, messageFunctions)
807
863
  if(serviceRootNames.includes(targetSchemaName)) {
808
864
  // remove all definitions starting with < fqSchemaName >. and add a schema reference
809
865
  Object.keys(csn.definitions).forEach(dn => {
810
- if(dn.startsWith(fqSchemaName)) // this includes the fqSchemaName context
866
+ if(dn.startsWith(fqSchemaName)) {// this includes the fqSchemaName context
811
867
  delete csn.definitions[dn];
868
+ delete reqDefs.definitions[dn];
869
+ }
812
870
  });
813
871
  if(!schemas[fqSchemaName])
814
872
  schemaNames.push(fqSchemaName);
@@ -839,13 +897,12 @@ function initializeModel(csn, _options, messageFunctions)
839
897
 
840
898
  If option odataExtReferences is used, 'root' proxies are still created.
841
899
 
842
- If an entity type which is a proxy candidate has a managed association as primary key,
843
- all dependent entity types are exposed (or referenced) as well to keep the navigation
844
- graph in tact. This effectively will expose the transitive primary key closure of all
845
- proxies.
900
+ If the association leading to the proxy candidate refers to associations either directly
901
+ or indirectly (via structured elements), these dependent entity types are (recursively) exposed
902
+ (or referenced) as well to keep the navigation graph in tact.
846
903
  */
847
904
  function exposeTargetsAsProxiesOrSchemaRefs(struct) {
848
- if([ 'context', 'service' ].includes(struct.kind) || struct.$proxy)
905
+ if(struct.kind === 'context' || struct.kind === 'service' || struct.$proxy)
849
906
  return;
850
907
 
851
908
  // globalSchemaPrefix is the prefix for all proxy registrations and must not change
@@ -880,6 +937,7 @@ function initializeModel(csn, _options, messageFunctions)
880
937
  // If the target is in another schema, check if both the source and the target share the same service name.
881
938
  // If they share the same service name, then it is just a cross schema navigation within the same EDM, no
882
939
  // proxy required.
940
+ // association must be managed and not unmanaged
883
941
 
884
942
  // odataProxies (P) and odataXServiceRefs (X) are evalutated as follows:
885
943
  // P | X | Action
@@ -890,12 +948,17 @@ function initializeModel(csn, _options, messageFunctions)
890
948
 
891
949
  const targetSchemaName = element._target.$mySchemaName;
892
950
  if(isProxyRequired(element)) {
893
- if(options.isV4() && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs)) {
951
+ if(options.isV4() &&
952
+ (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs) &&
953
+ // must be a managed association with keys OR an unambiguous backlink
954
+ element.keys ||
955
+ (element.on && element._constraints.selfs.length === 1 && element._constraints.termCount === 1)
956
+ ) {
894
957
  // reuse proxy if available
895
958
  let proxy = getProxyForTargetOf(element);
896
959
  if(!proxy) {
897
960
  if(targetSchemaName && options.toOdata.odataXServiceRefs) {
898
- proxy = createSchemaRefFor(element, targetSchemaName);
961
+ proxy = createSchemaRefFor(targetSchemaName);
899
962
  }
900
963
  else if(options.toOdata.odataProxies) {
901
964
  proxy = createProxyFor(element, targetSchemaName);
@@ -906,13 +969,16 @@ function initializeModel(csn, _options, messageFunctions)
906
969
  // if a proxy was either already created or could be created and
907
970
  // if it's a 'real' proxy, link the _target to it and remove constraints
908
971
  // otherwise proxy is a schema reference, then do nothing
972
+ setProp(element, '$noPartner', true);
909
973
  element._constraints.constraints = Object.create(null);
910
974
  if(proxy.kind === 'entity') {
975
+ if(!proxy.$isParamEntity)
976
+ populateProxyElements(element, proxy, getForeignKeyDefinitions(element));
911
977
  element._target = proxy;
912
978
  }
913
979
  else {
914
- // fake the target to be proxy
915
- setProp(element._target, '$externalRef', true);
980
+ // No navigation property bindings on external references
981
+ setProp(element, '$externalRef', true);
916
982
  }
917
983
  }
918
984
  else {
@@ -932,11 +998,11 @@ function initializeModel(csn, _options, messageFunctions)
932
998
  }
933
999
 
934
1000
  function noNavPropMsg(elt) {
935
- warning(null, ['definitions', struct.name, 'elements', elt.name],
936
- { target: elt._target.name }, 'No OData navigation property generated, target $(TARGET) is outside any service');
1001
+ warning('odata-navigation', ['definitions', struct.name, 'elements', elt.name],
1002
+ { target: elt._target.name, service: globalSchemaPrefix });
937
1003
  }
938
1004
 
939
- function createSchemaRefFor(assoc, targetSchemaName) {
1005
+ function createSchemaRefFor(targetSchemaName) {
940
1006
  let ref = csn.definitions[globalSchemaPrefix + '.' + targetSchemaName];
941
1007
  if(!ref) {
942
1008
  ref = createSchemaRef(targetSchemaName);
@@ -951,13 +1017,25 @@ function initializeModel(csn, _options, messageFunctions)
951
1017
  // if it is required in multiple services. The service schema name is prepended upon registration
952
1018
  const proxySchemaName = targetSchemaName || getSchemaPrefix(assoc._target.name);
953
1019
 
954
- // 1) construct the proxy definition
955
- // proxyShortName: strip the serviceName and replace '.' with '_'
956
- const proxyShortName = assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_');
957
- // fullName: Prepend serviceName and if in same service add '_proxy'
958
- const fullName = proxySchemaName + '.' + proxyShortName;
959
- const proxy = { name: fullName, kind: 'entity', $proxy: true, elements: Object.create(null) };
1020
+ // if the target is a parameter entity, it's easy just create the parameter stub
1021
+ const isParamProxy = isParameterizedEntity(assoc._target);
1022
+
1023
+ // 1) construct the proxy definition
1024
+ // proxyDefinitionName: strip the serviceName and replace '.' with '_'
1025
+ let defName =
1026
+ `${assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_')}`;
1027
+
1028
+ // fullName: Prepend serviceName and if in same service add '_proxy'
1029
+ const proxy = isParamProxy
1030
+ ? createParameterEntity(assoc._target, proxySchemaName + '.' + defName, true)
1031
+ : { name: proxySchemaName + '.' + defName, kind: 'entity', elements: Object.create(null) };
1032
+
1033
+ // Final proxyShortName for all further processing
1034
+ const proxyShortName = defName + (isParamProxy ? 'Parameters' : '');
1035
+
1036
+ setProp(proxy, '$proxy', true);
960
1037
  setProp(proxy, '$mySchemaName', proxySchemaName);
1038
+ setProp(proxy, '$proxyShortName', proxyShortName);
961
1039
  setProp(proxy, '$keys', Object.create(null));
962
1040
  setProp(proxy, '$hasEntitySet', false);
963
1041
  setProp(proxy, '$exposedTypes', Object.create(null));
@@ -968,21 +1046,33 @@ function initializeModel(csn, _options, messageFunctions)
968
1046
  });
969
1047
 
970
1048
  // 2) create the elements and $keys
971
- populateProxyElements(proxy, assoc._target.$keys);
972
- // 3) sort the exposed types so that they appear lexicographically ordered in the EDM
973
- proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
974
- dict[tn] = proxy.$exposedTypes[tn];
975
- return dict
976
- }, Object.create(null));
977
-
1049
+ if(isParamProxy) {
1050
+ // Reset param proxy elements to expose element tree
1051
+ const elements = proxy.elements;
1052
+ proxy.elements = Object.create(null);
1053
+ populateProxyElements(assoc, proxy, elements);
1054
+ }
1055
+ else
1056
+ populateProxyElements(assoc, proxy, assoc._target.$keys);
978
1057
  return proxy;
979
1058
 
980
- // copy over the primary keys of the target and trigger the type exposure
981
- function populateProxyElements(proxy, keys) {
982
- forAll(keys, e => {
983
- if (isEdmPropertyRendered(e, options)) {
984
- let newElt = undefined;
985
- if(isAssocOrComposition(e.type)) {
1059
+ }
1060
+
1061
+ // Return top level foreign key element definitions. The full top level
1062
+ // element is exposed instead of merging partial trees into the exposed type
1063
+ // def structure.
1064
+ function getForeignKeyDefinitions(e) {
1065
+ return e.keys ? e.keys.map(fk => e._target.elements[fk.ref[0]]) : [];
1066
+ }
1067
+
1068
+ // copy over the primary keys of the target and trigger the type exposure
1069
+ // if the element already exists we assume it was fully exposed
1070
+ function populateProxyElements(assoc, proxy, elements) {
1071
+ forAll(elements, e => {
1072
+ if (isEdmPropertyRendered(e, options)) {
1073
+ let newElt = proxy.elements[e.name];
1074
+ if(!newElt) {
1075
+ if(csnUtils.isAssocOrComposition(e.type)) {
986
1076
  if(!e.on && e.keys) {
987
1077
  if(options.toOdata.odataNoTransitiveProxies)
988
1078
  newElt = convertManagedAssocIntoStruct(e);
@@ -991,105 +1081,126 @@ function initializeModel(csn, _options, messageFunctions)
991
1081
  }
992
1082
  else {
993
1083
  info(null, ['definitions', struct.name, 'elements', assoc.name],
994
- { name: fullName, target: assoc._target.name },
1084
+ { name: proxy.nname, target: assoc._target.name },
995
1085
  'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
996
1086
  }
997
1087
  }
998
1088
  else {
999
- newElt = cloneCsn(e, options);
1089
+ newElt = Object.create(null);
1090
+ Object.keys(e).forEach(prop => newElt[prop] = e[prop])
1000
1091
  }
1001
1092
  if(newElt) {
1002
1093
  initElement(newElt, e.name, proxy);
1003
- if(isStructured(newElt)) {
1094
+ proxy.elements[newElt.name] = newElt;
1095
+
1096
+ if(csnUtils.isStructured(newElt)) {
1004
1097
  // argument proxySchemaName forces an anonymous type definition for newElt into the
1005
- // proxy schema. If omitted, this exposure defaults to 'root', in case API flavor of the day
1006
- // changes...
1007
- exposeStructTypeForProxyOf(proxy, newElt, proxyShortName + '_' + newElt.name, proxySchemaName);
1008
- // elements of newElt are required for key ref paths
1098
+ // proxy schema. If omitted, this exposure defaults to 'root', in case API flavor
1099
+ // of the day changes...
1100
+ exposeStructTypeForProxyOf(proxy, newElt, proxy.$proxyShortName + '_' + newElt.name,
1101
+ proxy.$mySchemaName, newElt.key, !!(newElt.key && newElt.elements));
1009
1102
  }
1010
- // all elements must become primary key
1011
- proxy.$keys[e.name] = proxy.elements[newElt.name] = newElt;
1103
+ if(newElt.key)
1104
+ proxy.$keys[newElt.name] = newElt;
1012
1105
  }
1013
1106
  }
1014
- });
1015
- }
1107
+ }
1108
+
1109
+ });
1110
+ // 3) sort the exposed types so that they appear lexicographically ordered in the EDM
1111
+ proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
1112
+ dict[tn] = proxy.$exposedTypes[tn];
1113
+ return dict
1114
+ }, Object.create(null));
1016
1115
 
1017
1116
  // If 'node' exists and has a structured type that is not exposed in 'service', (because the type is
1018
1117
  // anonymous or has a definition outside of 'service'), create an equivalent type in 'service', either
1019
1118
  // using the type's name or (if anonymous) 'artificialName', and make 'node' use that type instead.
1020
1119
  // Complain if there is an error.
1021
- function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName='root') {
1022
- const isNotInProtNS = node.type ? !isBuiltinType(node.type) : true;
1023
- // Always expose types referred to by a proxy, never reuse an eventually exisiting type
1120
+ // isKey: Indicates top level element is key or not
1121
+ // forceToNotNull: if top level element is key, recursively set all anonymously exposed elements
1122
+ // to notNull until the first named type is exposed.
1123
+ function exposeStructTypeForProxyOf(proxy, node, artificialName,
1124
+ typeSchemaName=fallBackSchemaName,
1125
+ isKey, forceToNotNull) {
1126
+
1127
+ if(node.type && isBuiltinType(node.type))
1128
+ return;
1129
+
1130
+ // Always expose types referred to by a proxy, never reuse an eventually existing type
1024
1131
  // as the nested elements must all be not nullable
1025
- if (isNotInProtNS) {
1026
- let typeDef = node.type ? csn.definitions[node.type] : /* anonymous type */ node;
1132
+ // elements have precedence over type
1133
+ const typeDef = !node.elements && node.type ? csn.definitions[node.type] : node;
1027
1134
 
1028
- if (typeDef) {
1029
- let typeClone;
1135
+ if (typeDef) {
1136
+ let typeClone;
1030
1137
  // the type clone must be produced for each service as this type may
1031
1138
  // produce references and/or proxies into multiple services
1032
1139
  // (but only once per service, therefore cache it).
1033
- if(typeDef.$proxyTypes && typeDef.$proxyTypes[globalSchemaPrefix]) {
1140
+ if(typeDef.$proxyTypes && typeDef.$proxyTypes[globalSchemaPrefix]) {
1034
1141
  // if type has been exposed in a schema use this type
1035
- typeClone = typeDef.$proxyTypes[globalSchemaPrefix];
1036
- }
1037
- else {
1142
+ typeClone = typeDef.$proxyTypes[globalSchemaPrefix];
1143
+ }
1144
+ else {
1038
1145
  // Set the correct name
1039
- let typeId = artificialName; // the artificialName has no namespace, it's the element
1040
- if(node.type) {
1146
+ let typeId = artificialName; // the artificialName has no namespace, it's the element
1147
+ if(node.type) {
1041
1148
  // same as for proxies, use schema or namespace, 'root' is last resort
1042
- typeSchemaName = typeDef.$mySchemaName || getSchemaPrefix(node.type);
1043
- typeId = node.type.replace(typeSchemaName + '.', '').replace(/\./g, '_');
1149
+ typeSchemaName = typeDef.$mySchemaName || getSchemaPrefix(node.type);
1150
+ typeId = node.type.replace(typeSchemaName + '.', '').replace(/\./g, '_');
1044
1151
  // strip the service root of that type (if any)
1045
- const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
1046
- if(myServiceRootName)
1047
- typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
1048
- }
1152
+ const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
1153
+ if(myServiceRootName)
1154
+ typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
1155
+ }
1049
1156
 
1050
- if(isStructuredArtifact(typeDef)) {
1051
- typeClone = cloneStructTypeForProxy(typeSchemaName, `${typeSchemaName}.${typeId}`, typeDef);
1052
- if(typeClone) {
1157
+ if(isStructuredArtifact(typeDef)) {
1158
+ // pull forceNotNull to false for named types and non-key nodes
1159
+ // only toplevel nodes (elements) can be key
1160
+ forceToNotNull = !!(forceToNotNull && isKey && node.elements && !node.type);
1161
+
1162
+ typeClone = cloneStructTypeForProxy(typeSchemaName, `${typeSchemaName}.${typeId}`, typeDef);
1163
+ if(typeClone) {
1053
1164
  // Recurse into elements of 'type' (if any)
1054
- typeClone.elements && Object.entries(typeClone.elements).forEach( ([elemName, elem]) => {
1055
- // if this is a foreign key elment, we must check wether or not the association
1165
+ typeClone.elements && Object.entries(typeClone.elements).forEach(([elemName, elem]) => {
1166
+ // if this is a foreign key elment, we must check whether or not the association
1056
1167
  // has been exposed as proxy. If it has not been exposed, no further structured
1057
1168
  // types must be exposed as 'Proxy_' types.
1058
1169
 
1059
1170
  // TODO: expose types of assoc.keys and don't rely on exposed foreign keys
1060
- if(!elem['@odata.foreignKey4'] ||
1171
+ if(!elem['@odata.foreignKey4'] ||
1061
1172
  (elem['@odata.foreignKey4'] && !typeClone.elements[elem['@odata.foreignKey4']].$exposed))
1062
- exposeStructTypeForProxyOf(proxy, elem, `${typeId}_${elemName}`, typeSchemaName);
1063
- });
1064
- if(!typeDef.$proxyTypes)
1065
- typeDef.$proxyTypes = Object.create(null);
1066
- typeDef.$proxyTypes[globalSchemaPrefix] = typeClone;
1067
- }
1173
+ exposeStructTypeForProxyOf(proxy, elem, `${typeId}_${elemName}`,
1174
+ typeSchemaName, isKey, forceToNotNull);
1175
+ });
1176
+ if(!typeDef.$proxyTypes)
1177
+ typeDef.$proxyTypes = Object.create(null);
1178
+ typeDef.$proxyTypes[globalSchemaPrefix] = typeClone;
1068
1179
  }
1069
- else {
1180
+ }
1181
+ else {
1070
1182
  // FUTURE: expose scalar type definition as well
1071
- }
1072
1183
  }
1073
- if(typeClone) {
1184
+ }
1185
+ if(typeClone) {
1074
1186
  // register the type clone at the proxy
1075
1187
  // Reminder: Each proxy receives a full set of type clones, even if the types are shared
1076
1188
  // (no scattered type clone caching). registerProxy() checks if a clone needs to be added to
1077
1189
  // csn.definitions.
1078
- proxy.$exposedTypes[typeClone.name] = typeClone;
1190
+ proxy.$exposedTypes[typeClone.name] = typeClone;
1079
1191
 
1080
1192
  // set the node's new type name
1081
- node.type = typeClone.name;
1193
+ node.type = typeClone.name;
1082
1194
  // the key path generator must use the type clone directly, because it can't resolve
1083
1195
  // the type clone in the CSN (its name is the final name and not the definition name).
1084
- setProp(node, '_type', typeClone);
1196
+ setProp(node, '_type', typeClone);
1085
1197
  // Hack alert:
1086
1198
  // beta feature 'subElemRedirections' (now the default in v2) adds elements to the node by
1087
1199
  // default, without we must do it to get the primary key tuple calculation correct.
1088
1200
  // Remember: node.type is the service local type name (not prepended by the service name),
1089
1201
  // so it can't be resolved in definitions later on
1090
- if(typeClone.elements)
1091
- node.elements = typeClone.elements;
1092
- }
1202
+ if(typeClone.elements)
1203
+ node.elements = typeClone.elements;
1093
1204
  }
1094
1205
  }
1095
1206
 
@@ -1107,11 +1218,13 @@ function initializeModel(csn, _options, messageFunctions)
1107
1218
  if(!elem.target) {
1108
1219
  type.elements[elemName] = Object.create(null);
1109
1220
  Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
1110
- type.elements[elemName].notNull = true;
1111
1221
  }
1112
- else {
1222
+ else if(elem.keys && !elem.on) {
1223
+ // a primary key can never be an unmanaged association
1113
1224
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1114
1225
  }
1226
+ if(forceToNotNull)
1227
+ type.elements[elemName].notNull = true;
1115
1228
  setProp(type.elements[elemName], 'name', elem.name);
1116
1229
  });
1117
1230
  return type;
@@ -1121,7 +1234,7 @@ function initializeModel(csn, _options, messageFunctions)
1121
1234
  // Convert a managed association into a structured type and
1122
1235
  // eliminate nested foreign key associations
1123
1236
  function convertManagedAssocIntoStruct(e) {
1124
- let newElt = cloneCsn(e, options);
1237
+ let newElt = cloneCsnNonDict(e, options);
1125
1238
  newElt.elements = Object.create(null);
1126
1239
  // remove all unwanted garbage
1127
1240
  delete newElt.keys;
@@ -1132,14 +1245,15 @@ function initializeModel(csn, _options, messageFunctions)
1132
1245
  let keys = (!e._target.$isParamEntity && e.keys) ||
1133
1246
  Object.keys(e._target.$keys).map(k => { return { ref: [k] } });
1134
1247
  keys.forEach(k => {
1135
- let art = e._target || getCsnDef(e.target);
1248
+ let art = e._target || csnUtils.getCsnDef(e.target);
1136
1249
  for(let ps of k.ref) {
1137
1250
  art = art.elements[ps];
1138
1251
  }
1139
1252
  // art is in the target side, clone it and remove key property
1140
- let cloneArt = cloneCsn(art, options);
1253
+ let cloneArt = cloneCsnNonDict(art, options);
1141
1254
  setProp(cloneArt, 'name', art.name);
1142
- cloneArt.notNull = true;
1255
+ if(e.key)
1256
+ cloneArt.notNull = true;
1143
1257
  delete cloneArt.key;
1144
1258
  newElt.elements[art.name] = cloneArt;
1145
1259
  });
@@ -1155,17 +1269,19 @@ function initializeModel(csn, _options, messageFunctions)
1155
1269
  function createProxyOrSchemaRefForManagedAssoc(e) {
1156
1270
 
1157
1271
  let proxy = e._target;
1158
- let newElt = cloneCsn(e, options);
1272
+ let newElt = cloneCsnNonDict(e, options);
1159
1273
 
1160
1274
  if(isProxyRequired(e)) {
1161
1275
  proxy = getProxyForTargetOf(e);
1162
1276
  if(!proxy) {
1163
1277
  // option odataXServiceRefs has precedence over odataProxies
1164
1278
  if(e._target.$mySchemaName && options.toOdata.odataXServiceRefs) {
1165
- proxy = createSchemaRefFor(e, e._target.$mySchemaName);
1279
+ proxy = createSchemaRefFor(e._target.$mySchemaName);
1166
1280
  }
1167
1281
  else if(options.toOdata.odataProxies) {
1168
1282
  proxy = createProxyFor(e, e._target.$mySchemaName);
1283
+ if(!e._target.$isParamEntity)
1284
+ populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
1169
1285
  }
1170
1286
  proxy = registerProxy(proxy, e);
1171
1287
  }
@@ -1181,7 +1297,10 @@ function initializeModel(csn, _options, messageFunctions)
1181
1297
  setProp(newElt, '$exposed', true);
1182
1298
  // _target must be set with (original) in case
1183
1299
  // a schema ref has been created
1300
+ setProp(newElt, '$noPartner', true);
1184
1301
  setProp(newElt, '_target', e._target);
1302
+ initConstraintsOnAssoc(e);
1303
+ finalizeConstraintsOnAssoc(e);
1185
1304
  setProp(newElt, '_constraints', e._constraints);
1186
1305
  setProp(newElt, '_selfReferences', []);
1187
1306
  if(proxy.kind === 'entity') {
@@ -1229,8 +1348,11 @@ function initializeModel(csn, _options, messageFunctions)
1229
1348
  function registerProxy(proxy, element) {
1230
1349
  if(proxy === undefined)
1231
1350
  return undefined;
1232
- const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
1233
- const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1351
+
1352
+ setProp(proxy, '$globalSchemaPrefix', globalSchemaPrefix);
1353
+ setProp(proxy, '$origin', element);
1354
+
1355
+ const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
1234
1356
 
1235
1357
  if(!element._target.$cachedProxy)
1236
1358
  assignProp(element._target, '$cachedProxy', Object.create(null));
@@ -1238,8 +1360,19 @@ function initializeModel(csn, _options, messageFunctions)
1238
1360
  info(null, ['definitions', struct.name, 'elements', element.name],
1239
1361
  { name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1240
1362
  }
1241
- else
1363
+ else {
1364
+ determineEntitySet(proxy);
1365
+ proxyCache.push(proxy);
1242
1366
  element._target.$cachedProxy[globalSchemaPrefix] = proxy;
1367
+ }
1368
+ return proxy;
1369
+ }
1370
+ }
1371
+
1372
+ function mergeProxiesIntoModel() {
1373
+ proxyCache.forEach(proxy => {
1374
+ const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
1375
+ const fqSchemaName = proxy.$globalSchemaPrefix + '.' + proxy.$mySchemaName;
1243
1376
 
1244
1377
  if(proxy.kind === 'entity') {
1245
1378
  // collect all schemas even for newly exposed types
@@ -1251,7 +1384,7 @@ function initializeModel(csn, _options, messageFunctions)
1251
1384
  // don't forget to prepend the global namespace prefix
1252
1385
  // schemas are ordered in csn2edm.js for each service
1253
1386
  Object.keys(proxy.$exposedTypes).forEach(t =>
1254
- schemaSet.add(globalSchemaPrefix + '.' + getSchemaPrefix(t)));
1387
+ schemaSet.add(proxy.$globalSchemaPrefix + '.' + getSchemaPrefix(t)));
1255
1388
  schemaSet.forEach(schemaName => {
1256
1389
  if(!schemas[schemaName]) {
1257
1390
  schemas[schemaName] = { kind: 'schema', name: schemaName };
@@ -1259,25 +1392,31 @@ function initializeModel(csn, _options, messageFunctions)
1259
1392
  }
1260
1393
  });
1261
1394
  /** @type {object} */
1262
- const alreadyRegistered = csn.definitions[fqProxyName]
1395
+ const alreadyRegistered = csn.definitions[fqProxyName];
1263
1396
  if(!alreadyRegistered) {
1264
1397
  csn.definitions[fqProxyName] = proxy;
1398
+ reqDefs.definitions[fqProxyName] = proxy;
1265
1399
  setProp(proxy, '$path', ['definitions', fqProxyName]);
1266
1400
  Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
1267
- const fqtn = globalSchemaPrefix + '.' + tn;
1401
+ const fqtn = proxy.$globalSchemaPrefix + '.' + tn;
1268
1402
  if(csn.definitions[fqtn] === undefined) {
1269
1403
  csn.definitions[fqtn] = v;
1404
+ reqDefs.definitions[fqtn] = v;
1270
1405
  setProp(v, '$path', ['definitions', fqtn]);
1271
1406
  }
1272
1407
  });
1273
- info(null, ['definitions', element._parent.name, 'elements', element.name],
1408
+ // default location is not always correct in case proxy has been created by a nested assoc
1409
+ // as foreign key targeting another proxy association
1410
+ let loc = ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name];
1411
+ if(proxy.$origin._parent.$path)
1412
+ loc = [...proxy.$origin._parent.$path, 'elements', proxy.$origin.name]
1413
+ info(null, loc,
1274
1414
  { name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
1275
1415
  }
1276
1416
  else if(alreadyRegistered && !alreadyRegistered.$proxy &&
1277
- !['entity', 'view'].includes(alreadyRegistered.kind)) {
1278
- warning('odata-definition-exists', ['definitions', element._parent.name, 'elements', element.name],
1417
+ alreadyRegistered.kind !== 'entity') {
1418
+ warning('odata-definition-exists', ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name],
1279
1419
  { '#': 'proxy', name: fqProxyName, kind: alreadyRegistered.kind });
1280
- return undefined;
1281
1420
  }
1282
1421
  }
1283
1422
  else {
@@ -1285,15 +1424,14 @@ function initializeModel(csn, _options, messageFunctions)
1285
1424
  if(!schemas[fqSchemaName]) {
1286
1425
  schemas[fqSchemaName] = proxy;
1287
1426
  schemaNames.push(fqSchemaName);
1288
- info(null, ['definitions', struct.name, 'elements', element.name],
1427
+ info(null, ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name],
1289
1428
  { name: proxy.name }, 'Created EDM namespace reference $(NAME)');
1290
1429
  }
1291
1430
  // don't error on duplicate schemas, if it's already present then all is good....
1292
1431
  }
1432
+ });
1293
1433
  // sort the global schemaNames array
1294
- schemaNames.sort((a,b) => b.length-a.length);
1295
- return proxy;
1296
- }
1434
+ schemaNames.sort((a,b) => b.length-a.length);
1297
1435
  }
1298
1436
 
1299
1437
  /*
@@ -1309,28 +1447,28 @@ function initializeModel(csn, _options, messageFunctions)
1309
1447
  and do not render associations (this will include the foreign keys of
1310
1448
  the _isToContainer association).
1311
1449
  */
1312
- function initializeEdmKeyRefPaths(struct) {
1313
- if(struct.$mySchemaName && struct.$keys) {
1314
- setProp(struct, '$edmKeyPaths', []);
1450
+ function initEdmKeyRefPaths(def) {
1451
+ if(def.$keys) {
1452
+ setProp(def, '$edmKeyPaths', []);
1315
1453
  // for all key elements that shouldn't be ignored produce the paths
1316
- foreach(struct.$keys, k => !k._ignore && !(k._isToContainer && k._selfReferences.length), (k, kn) => {
1454
+ foreach(def.$keys, k => !k._ignore && !(k._isToContainer && k._selfReferences.length), (k, kn) => {
1317
1455
  if(isEdmPropertyRendered(k, options) &&
1318
1456
  !(options.isV2() && k['@Core.MediaType'])) {
1319
1457
  if(options.isV4() && options.isStructFormat) {
1320
1458
  // This is structured OData ONLY
1321
1459
  // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1322
1460
  if(options.renderForeignKeys && !k.target)
1323
- struct.$edmKeyPaths.push([kn]);
1461
+ def.$edmKeyPaths.push([kn]);
1324
1462
  // else produce paths (isEdmPropertyRendered() has filtered @odata.foreignKey4 already)
1325
1463
  else if(!options.renderForeignKeys)
1326
- struct.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
1464
+ def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
1327
1465
  }
1328
1466
  // In v2/v4 flat, associations are never rendered
1329
1467
  else if(!k.target) {
1330
- struct.$edmKeyPaths.push([kn]);
1468
+ def.$edmKeyPaths.push([kn]);
1331
1469
  }
1332
1470
  // check toplevel key for spec violations
1333
- checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
1471
+ checkKeySpecViolations(k, ['definitions', def.name, 'elements', k.name]);
1334
1472
  }
1335
1473
  });
1336
1474
  }
@@ -1347,6 +1485,8 @@ function initializeModel(csn, _options, messageFunctions)
1347
1485
  */
1348
1486
  function produceKeyRefPaths(eltCsn, prefix) {
1349
1487
  const keyPaths = [];
1488
+ // we want to point to the element in the entity which is the first path step
1489
+ const location = def.$path.concat(['elements']).concat(prefix.split('/')[0]);
1350
1490
  if(!isEdmPropertyRendered(eltCsn, options)) {
1351
1491
  // let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
1352
1492
  // warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
@@ -1355,7 +1495,7 @@ function initializeModel(csn, _options, messageFunctions)
1355
1495
  }
1356
1496
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1357
1497
 
1358
- const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1498
+ const finalType = csnUtils.getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1359
1499
  const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1360
1500
  (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1361
1501
  if(elements) {
@@ -1365,8 +1505,6 @@ function initializeModel(csn, _options, messageFunctions)
1365
1505
  keyPaths.push(...newRefs);
1366
1506
  // check path step key for spec violations
1367
1507
  const pathSegment = `${prefix}/${eltName}`;
1368
- // we want to point to the element in the entity which is the first path step
1369
- const location = struct.$path.concat(['elements']).concat(pathSegment.split('/')[0]);
1370
1508
  checkKeySpecViolations(elt, location, pathSegment);
1371
1509
  }
1372
1510
  });
@@ -1381,12 +1519,15 @@ function initializeModel(csn, _options, messageFunctions)
1381
1519
  // use the primary keys of the target
1382
1520
  let keys = (!eltCsn._target.$isParamEntity && eltCsn.keys) ||
1383
1521
  Object.keys(eltCsn._target.$keys).map(k => { return { ref: [k] } });
1522
+ let pathSegment = prefix
1384
1523
  keys.forEach(k => {
1385
- let art = eltCsn._target || getCsnDef(eltCsn.target);
1524
+ let art = eltCsn._target || csnUtils.getCsnDef(eltCsn.target);
1386
1525
  for(let ps of k.ref) {
1387
1526
  art = art.elements[ps];
1527
+ pathSegment += '/' + art.name
1528
+ checkKeySpecViolations(art, location, pathSegment);
1388
1529
  if(art.type && !isBuiltinType(art.type)) {
1389
- art = art._type || getCsnDef(art.type);
1530
+ art = art._type || csnUtils.getCsnDef(art.type);
1390
1531
  }
1391
1532
  }
1392
1533
  keyPaths.push(...produceKeyRefPaths(art, prefix + options.pathDelimiter + k.ref.join(options.pathDelimiter)));
@@ -1406,7 +1547,7 @@ function initializeModel(csn, _options, messageFunctions)
1406
1547
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1407
1548
  }
1408
1549
  // many
1409
- let type = elt.items || elt.type && !isBuiltinType(elt.type) && getFinalTypeDef(elt.type).items;
1550
+ let type = elt.items || elt.type && !isBuiltinType(elt.type) && csnUtils.getFinalTypeDef(elt.type).items;
1410
1551
  if(type) {
1411
1552
  error('odata-spec-violation-key-array', location,
1412
1553
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
@@ -1420,10 +1561,10 @@ function initializeModel(csn, _options, messageFunctions)
1420
1561
  // V2 allows any Edm.PrimitiveType (even Double and Binary), V4 is more specific:
1421
1562
  if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
1422
1563
  const edmType = edmUtils.mapCdsToEdmType(type);
1423
- const legalEdmTypes = [
1424
- 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
1425
- 'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay' ];
1426
- if(!legalEdmTypes.includes(edmType)) {
1564
+ const legalEdmTypes = {
1565
+ 'Edm.Boolean':1, 'Edm.Byte':1, 'Edm.Date':1, 'Edm.DateTimeOffset':1, 'Edm.Decimal':1, 'Edm.Duration':1,
1566
+ 'Edm.Guid':1, 'Edm.Int16':1, 'Edm.Int32':1, 'Edm.Int64':1, 'Edm.SByte':1, 'Edm.String':1, 'Edm.TimeOfDay':1 };
1567
+ if(!(edmType in legalEdmTypes)) {
1427
1568
  warning('odata-spec-violation-key-type', location,
1428
1569
  {name: pathSegment, type: type.type, id: edmType, '#': pathSegment ? 'std' : 'scalar'});
1429
1570
  }
@@ -1459,10 +1600,10 @@ function initializeModel(csn, _options, messageFunctions)
1459
1600
  Path="items/subitems/subitems/up_" Target="Header/items/subitems"/>
1460
1601
  Path="items/subitems/subitems/toG" Target="G"/>
1461
1602
  */
1462
- function initializeEdmNavPropBindingTargets(struct) {
1463
- if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1464
- forEachGeneric(struct.items || struct, 'elements', (element) => {
1465
- produceTargetPath([edmUtils.getBaseName(struct.name)], element, struct);
1603
+ function initEdmNavPropBindingTargets(def) {
1604
+ if(def.$hasEntitySet) {
1605
+ forEachGeneric(def.items || def, 'elements', (element) => {
1606
+ produceTargetPath([edmUtils.getBaseName(def.name)], element, def);
1466
1607
  });
1467
1608
  }
1468
1609
 
@@ -1493,13 +1634,13 @@ function initializeModel(csn, _options, messageFunctions)
1493
1634
  }
1494
1635
  }
1495
1636
 
1496
- function initializeEdmNavPropBindingPaths(struct) {
1497
- if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1637
+ function initEdmNavPropBindingPaths(def) {
1638
+ if(options.isV4() &&def.$hasEntitySet) {
1498
1639
  let npbs = [];
1499
- forEachGeneric(struct.items || struct, 'elements', (element) => {
1500
- npbs = npbs.concat(produceNavigationPath(element, struct));
1640
+ forEachGeneric(def.items || def, 'elements', (element) => {
1641
+ npbs = npbs.concat(produceNavigationPath(element, def));
1501
1642
  });
1502
- setProp(struct, '$edmNPBs', npbs);
1643
+ setProp(def, '$edmNPBs', npbs);
1503
1644
  }
1504
1645
 
1505
1646
  // collect all paths originating from this element that end up in an entity set
@@ -1513,7 +1654,10 @@ function initializeModel(csn, _options, messageFunctions)
1513
1654
  // drill into target only if
1514
1655
  // 1) target has no entity set and this assoc is not going to the container
1515
1656
  // 2) current definition and target are the same (cycle)
1516
- if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1657
+ if(!elt.$externalRef &&
1658
+ !elt._target.$hasEntitySet &&
1659
+ !elt._isToContainer &&
1660
+ curDef !== elt._target) {
1517
1661
  // follow elements in the target but avoid cycles
1518
1662
  setProp(elt, '$touched', true);
1519
1663
  Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
@@ -1523,16 +1667,18 @@ function initializeModel(csn, _options, messageFunctions)
1523
1667
  // end point reached but must not be an external reference nor a proxy nor a composition itself
1524
1668
  // last assoc step must not be to-n and target a singleton
1525
1669
  let p = undefined;
1526
- if (!elt._target.$externalRef &&
1527
- !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target) && options.isV4())) {
1670
+ if (!elt.$externalRef &&
1671
+ !(edmUtils.isToMany(elt) &&
1672
+ edmUtils.isSingleton(elt._target) &&
1673
+ options.isV4())) {
1528
1674
  if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
1529
- p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(struct.name)) || elt._target.$edmTgtPaths[0];
1675
+ p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(def.name)) || elt._target.$edmTgtPaths[0];
1530
1676
  }
1531
1677
  else if(elt._target.$hasEntitySet) {
1532
1678
  const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
1533
1679
  // if own struct and target have a set they either are in the same $mySchemaName or not
1534
1680
  // if target is in another schema, target the full qualified entity set
1535
- p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1681
+ p = (elt._target.$mySchemaName === def.$mySchemaName) ?
1536
1682
  [ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
1537
1683
  }
1538
1684
  if(p) {
@@ -1562,28 +1708,28 @@ function initializeModel(csn, _options, messageFunctions)
1562
1708
  }
1563
1709
  }
1564
1710
 
1565
- function determineEntitySet(struct) {
1711
+ function determineEntitySet(def) {
1566
1712
  // if this is an entity or a view, determine if an entity set is required or not
1567
1713
  // 1) must not be a proxy and not a containee in V4
1568
1714
  // No annos are rendered for non-existing EntitySet targets.
1569
- if(struct.$mySchemaName && struct.$hasEntitySet === undefined) {
1570
- const hasEntitySet = isEntity(struct) && !(options.isV4() && edmUtils.isContainee(struct)) && !struct.$proxy;
1571
- setProp(struct, '$hasEntitySet', hasEntitySet);
1715
+ if(def.$hasEntitySet === undefined) {
1716
+ const hasEntitySet = isEntity(def) && !(options.isV4() && edmUtils.isContainee(def)) && !def.$proxy;
1717
+ setProp(def, '$hasEntitySet', hasEntitySet);
1572
1718
  }
1573
1719
  }
1574
1720
 
1575
- function initializeEdmTypesAndDescription(artifact) {
1721
+ function initEdmTypesAndDescription(def) {
1576
1722
  // 1. let all doc props become @Core.Descriptions
1577
1723
  // 2. mark a member that will become a collection
1578
1724
  // 3. assign the edm primitive type to elements, to be used in the rendering later
1579
- assignAnnotation(artifact, '@Core.Description', artifact.doc);
1580
- markCollection(artifact);
1581
- mapCdsToEdmProp(artifact);
1582
- if (artifact.returns) {
1583
- markCollection(artifact.returns);
1584
- mapCdsToEdmProp(artifact.returns);
1585
- }
1586
- forEachMemberRecursively(artifact,member => {
1725
+ assignAnnotation(def, '@Core.Description', def.doc);
1726
+ markCollection(def);
1727
+ mapCdsToEdmProp(def);
1728
+ if (def.returns) {
1729
+ markCollection(def.returns);
1730
+ mapCdsToEdmProp(def.returns);
1731
+ }
1732
+ forEachMemberRecursively(def,member => {
1587
1733
  assignAnnotation(member, '@Core.Description', member.doc);
1588
1734
  markCollection(member);
1589
1735
  mapCdsToEdmProp(member);
@@ -1593,7 +1739,6 @@ function initializeModel(csn, _options, messageFunctions)
1593
1739
  mapCdsToEdmProp(member.returns);
1594
1740
  }
1595
1741
  });
1596
-
1597
1742
  // mark members that need to be rendered as collections
1598
1743
  function markCollection(obj) {
1599
1744
  const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
@@ -1610,19 +1755,37 @@ function initializeModel(csn, _options, messageFunctions)
1610
1755
  // Checks section starts here
1611
1756
  //
1612
1757
 
1758
+ // eslint-disable-next-line no-unused-vars
1759
+ function resolveForeignKeyRefs() {
1760
+ forEachDefinition(csn, (def, defName) => {
1761
+ let currPath = ['definitions', defName ];
1762
+ forEachMemberRecursively(def, (construct, _constructName, _prop, path) => {
1763
+ if(construct.target && construct.keys) {
1764
+ construct.keys.forEach((fk, i) => {
1765
+ setProp(fk, '_artifact', csnUtils.inspectRef([...path, 'keys', i]).art);
1766
+ });
1767
+ }
1768
+ }, currPath, true, { elementsOnly: true });
1769
+ });
1770
+ }
1771
+
1772
+
1613
1773
  function inboundQualificationChecks() {
1614
- forEachDefinition(csn, [ checkChainedArray ]);
1774
+ forEachDefinition(csn, [ attach$path, checkChainedArray ]);
1615
1775
  checkNestedContextsAndServices();
1616
- throwWithError();
1617
-
1618
- // NOTE: This is a copy of ./libs/checks/arrayOfs.js//checkChainedArray
1619
- // as long as validators cannot operate on OData processed CSN.
1620
- // TODO: Remove this code.
1621
- // Not possible at the moment, because running this at the beginning of
1622
- // the renderer does not work because the enricher can't handle certain
1623
- // OData specifics.
1776
+ throwWithAnyError();
1777
+
1778
+ // attach $path to all
1779
+ function attach$path(def, defName) {
1780
+ setProp(def, '$path', [ 'definitions', defName ]);
1781
+ forEachMemberRecursively(def,
1782
+ (member, _memberName, _prop, path) => {
1783
+ setProp(member, '$path', path);
1784
+ }, [ 'definitions', defName ]);
1785
+ }
1786
+
1624
1787
  function checkChainedArray(def, defName) {
1625
- if (!whatsMyServiceRootName(defName))
1788
+ if (!isMyServiceRequested(defName))
1626
1789
  return;
1627
1790
  let currPath = ['definitions', defName];
1628
1791
  checkIfItemsOfItems(def, undefined, undefined, currPath);
@@ -1632,13 +1795,13 @@ function initializeModel(csn, _options, messageFunctions)
1632
1795
  const constructType = csnUtils.effectiveType(construct);
1633
1796
  if (constructType.items) {
1634
1797
  if (constructType.items.items) {
1635
- error('chained-array-of', path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
1798
+ message('chained-array-of', path);
1636
1799
  return;
1637
1800
  }
1638
1801
 
1639
1802
  const itemsType = csnUtils.effectiveType(constructType.items);
1640
1803
  if (itemsType.items)
1641
- error('chained-array-of', path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
1804
+ message('chained-array-of', path);
1642
1805
  }
1643
1806
  }
1644
1807
  }
@@ -1646,7 +1809,7 @@ function initializeModel(csn, _options, messageFunctions)
1646
1809
  function checkNestedContextsAndServices() {
1647
1810
  !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
1648
1811
  const parent = whatsMyServiceRootName(sn, false);
1649
- if(parent && parent !== sn) {
1812
+ if(parent && requestedServiceNames.includes(parent) && parent !== sn) {
1650
1813
  message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1651
1814
  'A service can\'t be nested within a service $(ART)' );
1652
1815
  }
@@ -1655,7 +1818,7 @@ function initializeModel(csn, _options, messageFunctions)
1655
1818
  Object.entries(csn.definitions).forEach(([fqName, art]) => {
1656
1819
  if(art.kind === 'context') {
1657
1820
  const parent = whatsMyServiceRootName(fqName);
1658
- if(parent) {
1821
+ if(requestedServiceNames.includes(parent)) {
1659
1822
  message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
1660
1823
  'A context can\'t be nested within a service $(ART)' );
1661
1824
  }
@@ -1664,54 +1827,6 @@ function initializeModel(csn, _options, messageFunctions)
1664
1827
  }
1665
1828
  }
1666
1829
 
1667
- /**
1668
- *
1669
- * @param {String} identifier the illegal identifier
1670
- * @param {CSN.Path} path
1671
- */
1672
- function signalIllegalIdentifier(identifier, path) {
1673
- error(null, path, { id: identifier },
1674
- 'OData identifier $(ID) must start with a letter or underscore, followed by at most 127 letters, underscores or digits'
1675
- );
1676
- }
1677
-
1678
-
1679
- // { '#': this.csnUtils.isComposition(member.type) ? 'cmp' : 'std' },
1680
- // {
1681
- // std: 'An association can\'t have cardinality "to many" without an ON-condition',
1682
- // cmp: 'A composition can\'t have cardinality "to many" without an ON-condition',
1683
- // }
1684
-
1685
- // Check the artifact identifier for compliance with the odata specification
1686
- function checkArtifactIdentifierAndBoundActions(artifact) {
1687
- if(artifact.$mySchemaName) {
1688
- const artifactName = artifact.name.replace(`${artifact.$mySchemaName }.`, '');
1689
- // if the artifact has bound actions, check the action identifiers and their param identifiers to be OData compliant
1690
- if(artifact.actions) {
1691
- Object.keys(artifact.actions).forEach(identifier => checkActionOrFunctionIdentifier(artifact.actions[identifier], identifier))
1692
- }
1693
-
1694
- // if the artifact is an unbound function check it's identifer
1695
- if(isActionOrFunction(artifact)){
1696
- checkActionOrFunctionIdentifier(artifact, artifactName);
1697
- } else if(![ 'service', 'context', 'event', 'aspect' ].includes(artifact.kind) && !isODataSimpleIdentifier(artifactName)) {
1698
- signalIllegalIdentifier(artifactName, ['definitions', artifact.name]);
1699
- }
1700
- }
1701
-
1702
- function checkActionOrFunctionIdentifier(actionOrFunction, actionOrFunctionName) {
1703
- if(!isODataSimpleIdentifier(actionOrFunctionName)){
1704
- signalIllegalIdentifier(actionOrFunctionName, actionOrFunction.$path);
1705
- }
1706
- if(actionOrFunction.params) {
1707
- forEachGeneric(actionOrFunction, 'params', (param) => {
1708
- if(!isODataSimpleIdentifier(param.name)){
1709
- signalIllegalIdentifier(param.name, param.$path);
1710
- }
1711
- });
1712
- }
1713
- }
1714
- }
1715
1830
  //
1716
1831
  // Checks Secition ends here
1717
1832
  //
@@ -1737,12 +1852,13 @@ function initializeModel(csn, _options, messageFunctions)
1737
1852
  // serviceRef.push('');
1738
1853
  if(serviceRef[serviceRef.length-1] !== '$metadata')
1739
1854
  serviceRef.push('$metadata');
1740
- return { kind: 'reference',
1855
+ let sc = { kind: 'reference',
1741
1856
  name: targetSchemaName,
1742
1857
  ref: { Uri: serviceRef.join('/') },
1743
- inc: { Namespace: targetSchemaName },
1744
- $mySchemaName: targetSchemaName,
1858
+ inc: { Namespace: targetSchemaName }
1745
1859
  };
1860
+ setProp(sc, '$mySchemaName', targetSchemaName);
1861
+ return sc;
1746
1862
 
1747
1863
  /**
1748
1864
  * Resolve a service endpoint path to mount it to as follows...
@@ -1799,7 +1915,29 @@ function initializeModel(csn, _options, messageFunctions)
1799
1915
  }, { });
1800
1916
  // if dictionary has entries, add them to navPropEnty
1801
1917
  if(Object.keys(o).length) {
1802
- navPropEntry[prop] = o;
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
+ }
1803
1941
  newEntry = true;
1804
1942
  }
1805
1943
  }
@@ -1823,19 +1961,15 @@ function initializeModel(csn, _options, messageFunctions)
1823
1961
  if (obj.type && isBuiltinType(obj.type) && !isAssociationOrComposition(obj) && !obj.targetAspect) {
1824
1962
  let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1825
1963
  assignProp(obj, '_edmType', edmType);
1826
- } else if (obj._isCollection && (obj.items && isBuiltinType(getFinalTypeDef(obj.items.type)))) {
1827
- let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1964
+ } else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.type)))) {
1965
+ let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType'], obj.$path);
1828
1966
  assignProp(obj, '_edmType', edmType);
1829
1967
  }
1830
1968
  // This is the special case when we have array of array, but will not be supported in the future
1831
- else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(getFinalTypeDef(obj.items.items.type))) {
1969
+ else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.items.type))) {
1832
1970
  let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1833
1971
  assignProp(obj, '_edmType', edmType);
1834
1972
  }
1835
-
1836
- // check against the value of the @odata.Type annotation
1837
- if (obj['@odata.Type'] && !['Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.String'].includes(obj['@odata.Type']))
1838
- info(null, obj.$location, { type: obj['@odata.Type'] }, "@odata.Type: $(TYPE) is ignored, only Edm.String and Edm.Int[16,32,64] are allowed");
1839
1973
  }
1840
1974
 
1841
1975
  function ComputedDefaultValue(member) {
@@ -2158,7 +2292,7 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
2158
2292
  }
2159
2293
 
2160
2294
  function checkSemantics(struct, propName, propValue) {
2161
- if(['timeseries', 'aggregate'].includes(propValue)) {
2295
+ if(propValue === 'timeseries' || propValue === 'aggregate') {
2162
2296
  // aggregate is forwarded to Set and must remain on Type
2163
2297
  addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
2164
2298
  }
@@ -2222,4 +2356,5 @@ function assignProp(obj, prop, value) {
2222
2356
 
2223
2357
  module.exports = {
2224
2358
  initializeModel,
2359
+ assignAnnotation
2225
2360
  }