@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.
- package/CHANGELOG.md +221 -15
- package/bin/cdsc.js +125 -50
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +47 -84
- package/lib/api/options.js +5 -6
- package/lib/api/validate.js +6 -11
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +114 -18
- package/lib/base/messages.js +101 -90
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +177 -123
- package/lib/checks/annotationsOData.js +12 -33
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +6 -11
- package/lib/compiler/assert-consistency.js +6 -3
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +19 -6
- package/lib/compiler/checks.js +23 -60
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1151 -0
- package/lib/compiler/extend.js +1000 -0
- package/lib/compiler/finalize-parse-cdl.js +237 -0
- package/lib/compiler/index.js +107 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1227 -0
- package/lib/compiler/propagator.js +114 -46
- package/lib/compiler/resolve.js +1521 -0
- package/lib/compiler/shared.js +126 -65
- package/lib/compiler/tweak-assocs.js +535 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -24
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +219 -100
- package/lib/edm/edm.js +302 -230
- package/lib/edm/edmPreprocessor.js +554 -419
- package/lib/edm/edmUtils.js +138 -44
- package/lib/gen/Dictionary.json +100 -19
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5765 -4480
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +15 -3
- package/lib/json/to-csn.js +126 -68
- package/lib/language/docCommentParser.js +4 -4
- package/lib/language/genericAntlrParser.js +123 -5
- package/lib/language/language.g4 +355 -156
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +486 -59
- package/lib/main.js +41 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +252 -156
- package/lib/model/csnUtils.js +384 -297
- package/lib/model/enrichCsn.js +71 -29
- package/lib/model/revealInternalProperties.js +29 -8
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +23 -18
- package/lib/optionProcessor.js +63 -26
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +897 -947
- package/lib/render/toHdbcds.js +205 -257
- package/lib/render/toSql.js +264 -225
- package/lib/render/utils/common.js +136 -25
- package/lib/render/utils/sql.js +4 -3
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/db/.eslintrc.json +3 -1
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +104 -306
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +58 -53
- package/lib/transform/db/expansion.js +60 -33
- package/lib/transform/db/flattening.js +582 -104
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +66 -13
- package/lib/transform/db/views.js +11 -7
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +109 -208
- package/lib/transform/forOdataNew.js +59 -212
- package/lib/transform/localized.js +46 -26
- package/lib/transform/odata/toFinalBaseType.js +85 -11
- package/lib/transform/odata/typesExposure.js +147 -199
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +44 -33
- package/lib/transform/translateAssocsToJoins.js +3 -20
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +172 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -290
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
- 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,
|
|
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
|
-
|
|
40
|
-
throw Error('Please debug me: initializeModel must be invoked with options');
|
|
37
|
+
const { info, warning, error, message, throwWithAnyError } = messageFunctions;
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
let csnUtils = getUtils(csn);
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
|
|
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, [
|
|
127
|
-
|
|
128
|
-
|
|
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(
|
|
140
|
+
forEachDefinition(reqDefs, initContainments);
|
|
131
141
|
// Initialize entities with parameters (add Parameter entity)
|
|
132
|
-
forEachDefinition(
|
|
142
|
+
forEachDefinition(reqDefs, initParameterizedEntityOrView);
|
|
133
143
|
// Initialize structures
|
|
134
|
-
forEachDefinition(csn,
|
|
144
|
+
forEachDefinition(csn, initStructure);
|
|
135
145
|
// Initialize associations after _parent linking
|
|
136
|
-
forEachDefinition(
|
|
146
|
+
forEachDefinition(reqDefs, initConstraints);
|
|
137
147
|
// Mute V4 elements depending on constraint preparation
|
|
138
148
|
if(options.isV4())
|
|
139
|
-
forEachDefinition(
|
|
149
|
+
forEachDefinition(reqDefs, ignoreProperties);
|
|
140
150
|
// calculate constraints based on ignoreProperties and prepareConstraints
|
|
141
|
-
forEachDefinition(
|
|
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(
|
|
157
|
+
forEachDefinition(reqDefs, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
|
|
158
|
+
// finalize proxy creation
|
|
159
|
+
mergeProxiesIntoModel();
|
|
160
|
+
|
|
148
161
|
if(options.isV4())
|
|
149
|
-
forEachDefinition(
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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 && !
|
|
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(
|
|
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(
|
|
247
|
+
if(def.kind === 'entity')
|
|
197
248
|
dotEntityNameMap[defName] = newDefName;
|
|
198
|
-
if(
|
|
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(
|
|
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
|
|
287
|
-
|
|
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
|
|
345
|
-
if(
|
|
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
|
|
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
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
591
|
+
cardinality: { src: 1, min: 0, max: '*' }
|
|
512
592
|
};
|
|
513
|
-
setProp(
|
|
514
|
-
setProp(
|
|
515
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
|
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,
|
|
584
|
-
if(
|
|
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
|
-
|
|
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
|
|
681
|
-
if(!isStructuredArtifact(
|
|
734
|
+
function initConstraints(def) {
|
|
735
|
+
if(!isStructuredArtifact(def))
|
|
682
736
|
return;
|
|
683
737
|
|
|
684
|
-
forEachMemberRecursively(
|
|
685
|
-
|
|
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
|
-
|
|
743
|
+
setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
|
|
688
744
|
// and crack the ON condition
|
|
689
|
-
|
|
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(
|
|
764
|
-
if(!isStructuredArtifact(
|
|
818
|
+
function finalizeConstraints(def) {
|
|
819
|
+
if(!isStructuredArtifact(def))
|
|
765
820
|
return;
|
|
766
821
|
|
|
767
|
-
forEachMemberRecursively(
|
|
768
|
-
|
|
769
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
843
|
-
|
|
844
|
-
|
|
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(
|
|
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() &&
|
|
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(
|
|
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
|
-
//
|
|
915
|
-
setProp(element
|
|
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(
|
|
936
|
-
{ target: elt._target.name
|
|
1001
|
+
warning('odata-navigation', ['definitions', struct.name, 'elements', elt.name],
|
|
1002
|
+
{ target: elt._target.name, service: globalSchemaPrefix });
|
|
937
1003
|
}
|
|
938
1004
|
|
|
939
|
-
function createSchemaRefFor(
|
|
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
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
}
|
|
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
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1006
|
-
// changes...
|
|
1007
|
-
exposeStructTypeForProxyOf(proxy, newElt, proxyShortName + '_' + newElt.name,
|
|
1008
|
-
|
|
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
|
-
|
|
1011
|
-
|
|
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
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
1132
|
+
// elements have precedence over type
|
|
1133
|
+
const typeDef = !node.elements && node.type ? csn.definitions[node.type] : node;
|
|
1027
1134
|
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
1140
|
+
if(typeDef.$proxyTypes && typeDef.$proxyTypes[globalSchemaPrefix]) {
|
|
1034
1141
|
// if type has been exposed in a schema use this type
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1142
|
+
typeClone = typeDef.$proxyTypes[globalSchemaPrefix];
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1038
1145
|
// Set the correct name
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
-
|
|
1043
|
-
|
|
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
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1152
|
+
const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
|
|
1153
|
+
if(myServiceRootName)
|
|
1154
|
+
typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
|
|
1155
|
+
}
|
|
1049
1156
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
-
|
|
1055
|
-
// if this is a foreign key elment, we must check
|
|
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
|
-
|
|
1171
|
+
if(!elem['@odata.foreignKey4'] ||
|
|
1061
1172
|
(elem['@odata.foreignKey4'] && !typeClone.elements[elem['@odata.foreignKey4']].$exposed))
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
typeDef.$proxyTypes
|
|
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
|
-
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1070
1182
|
// FUTURE: expose scalar type definition as well
|
|
1071
|
-
}
|
|
1072
1183
|
}
|
|
1073
|
-
|
|
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
|
-
|
|
1190
|
+
proxy.$exposedTypes[typeClone.name] = typeClone;
|
|
1079
1191
|
|
|
1080
1192
|
// set the node's new type name
|
|
1081
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1091
|
-
|
|
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 =
|
|
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 =
|
|
1253
|
+
let cloneArt = cloneCsnNonDict(art, options);
|
|
1141
1254
|
setProp(cloneArt, 'name', art.name);
|
|
1142
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1278
|
-
warning('odata-definition-exists', ['definitions',
|
|
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',
|
|
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
|
-
|
|
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
|
|
1313
|
-
if(
|
|
1314
|
-
setProp(
|
|
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(
|
|
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
|
-
|
|
1461
|
+
def.$edmKeyPaths.push([kn]);
|
|
1324
1462
|
// else produce paths (isEdmPropertyRendered() has filtered @odata.foreignKey4 already)
|
|
1325
1463
|
else if(!options.renderForeignKeys)
|
|
1326
|
-
|
|
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
|
-
|
|
1468
|
+
def.$edmKeyPaths.push([kn]);
|
|
1331
1469
|
}
|
|
1332
1470
|
// check toplevel key for spec violations
|
|
1333
|
-
checkKeySpecViolations(k, ['definitions',
|
|
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(!
|
|
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
|
|
1463
|
-
if(
|
|
1464
|
-
forEachGeneric(
|
|
1465
|
-
produceTargetPath([edmUtils.getBaseName(
|
|
1603
|
+
function initEdmNavPropBindingTargets(def) {
|
|
1604
|
+
if(def.$hasEntitySet) {
|
|
1605
|
+
forEachGeneric(def.items || def, 'elements', (element) => {
|
|
1606
|
+
produceTargetPath([edmUtils.getBaseName(def.name)], element, def);
|
|
1466
1607
|
});
|
|
1467
1608
|
}
|
|
1468
1609
|
|
|
@@ -1493,13 +1634,13 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1493
1634
|
}
|
|
1494
1635
|
}
|
|
1495
1636
|
|
|
1496
|
-
function
|
|
1497
|
-
if(options.isV4() &&
|
|
1637
|
+
function initEdmNavPropBindingPaths(def) {
|
|
1638
|
+
if(options.isV4() &&def.$hasEntitySet) {
|
|
1498
1639
|
let npbs = [];
|
|
1499
|
-
forEachGeneric(
|
|
1500
|
-
npbs = npbs.concat(produceNavigationPath(element,
|
|
1640
|
+
forEachGeneric(def.items || def, 'elements', (element) => {
|
|
1641
|
+
npbs = npbs.concat(produceNavigationPath(element, def));
|
|
1501
1642
|
});
|
|
1502
|
-
setProp(
|
|
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
|
|
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
|
|
1527
|
-
|
|
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(
|
|
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 ===
|
|
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(
|
|
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(
|
|
1570
|
-
const hasEntitySet = isEntity(
|
|
1571
|
-
setProp(
|
|
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
|
|
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(
|
|
1580
|
-
markCollection(
|
|
1581
|
-
mapCdsToEdmProp(
|
|
1582
|
-
if (
|
|
1583
|
-
markCollection(
|
|
1584
|
-
mapCdsToEdmProp(
|
|
1585
|
-
}
|
|
1586
|
-
forEachMemberRecursively(
|
|
1725
|
+
assignAnnotation(def, '@Core.Description', def.doc);
|
|
1726
|
+
markCollection(def);
|
|
1727
|
+
mapCdsToEdmProp(def);
|
|
1728
|
+
if (def.returns) {
|
|
1729
|
+
markCollection(def.returns);
|
|
1730
|
+
mapCdsToEdmProp(def.returns);
|
|
1731
|
+
}
|
|
1732
|
+
forEachMemberRecursively(def,member => {
|
|
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
|
-
|
|
1617
|
-
|
|
1618
|
-
//
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
}
|