@sap/cds-compiler 5.2.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/bin/cdsc.js +5 -0
- package/bin/cdshi.js +8 -8
- package/doc/CHANGELOG_BETA.md +9 -4
- package/lib/api/validate.js +5 -0
- package/lib/base/message-registry.js +25 -1
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +0 -1
- package/lib/compiler/assert-consistency.js +2 -2
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +25 -6
- package/lib/compiler/define.js +24 -28
- package/lib/compiler/extend.js +11 -13
- package/lib/compiler/generate.js +3 -3
- package/lib/compiler/populate.js +13 -7
- package/lib/compiler/propagator.js +2 -2
- package/lib/compiler/resolve.js +58 -60
- package/lib/compiler/shared.js +5 -5
- package/lib/compiler/tweak-assocs.js +247 -34
- package/lib/compiler/utils.js +40 -32
- package/lib/compiler/xpr-rewrite.js +44 -58
- package/lib/edm/annotations/genericTranslation.js +4 -4
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edm.js +46 -21
- package/lib/edm/edmInboundChecks.js +0 -1
- package/lib/edm/edmPreprocessor.js +40 -27
- package/lib/edm/edmUtils.js +1 -1
- package/lib/gen/BaseParser.js +180 -122
- package/lib/gen/CdlParser.js +2226 -2170
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3820 -3777
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +5 -3
- package/lib/json/to-csn.js +7 -10
- package/lib/language/antlrParser.js +38 -4
- package/lib/language/errorStrategy.js +1 -1
- package/lib/language/genericAntlrParser.js +4 -4
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +23 -0
- package/lib/model/cloneCsn.js +22 -13
- package/lib/optionProcessor.js +7 -7
- package/lib/parsers/AstBuildingParser.js +155 -37
- package/lib/parsers/CdlGrammar.g4 +154 -81
- package/lib/parsers/Lexer.js +20 -10
- package/lib/render/toCdl.js +23 -18
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/forRelationalDB.js +7 -6
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +1 -1
- package/share/messages/redirected-to-complex.md +6 -3
|
@@ -174,7 +174,8 @@ function xprRewriteFns( model ) {
|
|
|
174
174
|
resolvePath,
|
|
175
175
|
navigationEnv,
|
|
176
176
|
resolvePathRoot,
|
|
177
|
-
|
|
177
|
+
cachedRedirectionChain,
|
|
178
|
+
findRewriteTarget,
|
|
178
179
|
} = model.$functions;
|
|
179
180
|
|
|
180
181
|
return {
|
|
@@ -393,21 +394,21 @@ function xprRewriteFns( model ) {
|
|
|
393
394
|
*/
|
|
394
395
|
function rewriteGenericAnnoPath( expr, config, refCtx ) {
|
|
395
396
|
const isAbsolute = isAnnoPathAbsolute( expr );
|
|
396
|
-
const
|
|
397
|
+
const startIndex = isAbsolute ? 1 : 0;
|
|
397
398
|
|
|
398
399
|
// We get the root environment now, even though below we resolve the root item
|
|
399
400
|
// again if it was absolute (e.g. $self). We do so, because for queries, we
|
|
400
401
|
// want to respect the select item's corresponding table alias.
|
|
401
402
|
const rootEnv = getRootEnv( expr, config );
|
|
402
403
|
|
|
403
|
-
// reset artifact link; we'll set it again
|
|
404
|
+
// reset artifact link; we'll set it again if there are no errors
|
|
404
405
|
setArtifactLink( expr, null );
|
|
405
406
|
|
|
406
|
-
// Adapt root path, as it isn't rewritten in rewriteItem
|
|
407
|
-
const rootItem = expr.path[0];
|
|
408
407
|
if (isAbsolute) {
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
// Adapt absolute root path, as it isn't rewritten in rewriteItem
|
|
409
|
+
// The path-prefix was already adapted in rewriteAnnoExpr().
|
|
410
|
+
delete expr.path[0]._artifact;
|
|
411
|
+
delete expr.path[0]._navigation;
|
|
411
412
|
// TODO: What about `up_`? Shouldn't we set `_navigation` as well?
|
|
412
413
|
// TODO: Can we handle `$self` of anonymous-composition-of-aspect?
|
|
413
414
|
const root = resolvePathRoot( expr, refCtx, config.target );
|
|
@@ -415,23 +416,46 @@ function xprRewriteFns( model ) {
|
|
|
415
416
|
return reportAnnoRewriteError( expr, config );
|
|
416
417
|
}
|
|
417
418
|
|
|
419
|
+
// Store the original artifact, so that we can use it to
|
|
420
|
+
// calculate a redirection chain later on.
|
|
421
|
+
expr.path.forEach((item) => {
|
|
422
|
+
if (item._artifact)
|
|
423
|
+
setLink( item, '_originalArtifact', item._artifact );
|
|
424
|
+
});
|
|
425
|
+
|
|
418
426
|
let env = rootEnv;
|
|
419
|
-
let art =
|
|
420
|
-
|
|
427
|
+
let art = expr.path[0]._artifact;
|
|
428
|
+
|
|
429
|
+
for (let i = startIndex; i < expr.path.length; ++i) {
|
|
430
|
+
if (i > startIndex && art.target) {
|
|
431
|
+
// if the current artifact is an association, we need to respect the redirection
|
|
432
|
+
// chain from original target to new one.
|
|
433
|
+
// FIXME: Won't work with associations in projected structures.
|
|
434
|
+
const origTarget = expr.path[i - 1]?._originalArtifact?.target?._artifact;
|
|
435
|
+
const chain = cachedRedirectionChain( art, origTarget );
|
|
436
|
+
if (!chain)
|
|
437
|
+
return reportAnnoRewriteError( expr, config );
|
|
438
|
+
for (const alias of chain) {
|
|
439
|
+
art = rewriteItem( expr, config, alias, i );
|
|
440
|
+
if (!art)
|
|
441
|
+
return reportAnnoRewriteError( expr, config );
|
|
442
|
+
}
|
|
443
|
+
}
|
|
421
444
|
art = rewriteItem( expr, config, env, i );
|
|
422
445
|
if (!art)
|
|
423
446
|
return reportAnnoRewriteError( expr, config );
|
|
447
|
+
// target, items, …
|
|
424
448
|
env = navigationEnv( art, null, null, 'nav' );
|
|
425
449
|
}
|
|
426
450
|
setArtifactLink( expr, art );
|
|
427
451
|
|
|
428
|
-
if (
|
|
452
|
+
if (startIndex === 0 && expr.path[0].id.startsWith('$')) {
|
|
429
453
|
if (config.isInFilter) {
|
|
430
454
|
// In filters, we must not prepend `$self`, as that would change its meaning.
|
|
431
455
|
// We must reject it. See #11775
|
|
432
456
|
return reportAnnoRewriteError( expr, config );
|
|
433
457
|
}
|
|
434
|
-
// After rewriting, an element starts with `$` -> add root prefix
|
|
458
|
+
// After rewriting, if an element starts with `$` -> add root prefix
|
|
435
459
|
prependRootPath( config.origin, config.targetRoot, expr );
|
|
436
460
|
}
|
|
437
461
|
|
|
@@ -640,63 +664,25 @@ function xprRewriteFns( model ) {
|
|
|
640
664
|
* @returns {*|null}
|
|
641
665
|
*/
|
|
642
666
|
function rewriteItem( expr, config, env, index ) {
|
|
643
|
-
const item = expr.path[index];
|
|
644
667
|
const rewriteTarget = findRewriteTarget( expr, index, env, config.target );
|
|
645
|
-
const found = setArtifactLink(
|
|
668
|
+
const found = setArtifactLink( expr.path[index], rewriteTarget[0] );
|
|
646
669
|
if (!found)
|
|
647
670
|
return null;
|
|
648
671
|
|
|
649
|
-
if (
|
|
650
|
-
//
|
|
651
|
-
|
|
652
|
-
item.id = found.name.id;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (rewriteTarget[1] > index)
|
|
672
|
+
if (rewriteTarget[1] > index) {
|
|
673
|
+
// we keep the last segment, in case it has non-enumerable properties
|
|
674
|
+
expr.path[index] = expr.path[rewriteTarget[1]];
|
|
656
675
|
expr.path.splice(index + 1, rewriteTarget[1] - index);
|
|
657
|
-
|
|
658
|
-
return rewriteTarget[0];
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function findRewriteTarget( expr, index, env, target ) {
|
|
662
|
-
if (env.kind === '$navElement' || env.kind === '$tableAlias') {
|
|
663
|
-
const r = firstProjectionForPath( expr.path, index, env, target );
|
|
664
|
-
return [ r.elem, r.index ];
|
|
665
676
|
}
|
|
666
677
|
|
|
667
678
|
const item = expr.path[index];
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return [ env.elements[item.id], index ];
|
|
673
|
-
return [ null, expr.path.length ];
|
|
674
|
-
}
|
|
675
|
-
const items = (env._leadingQuery || env)._combined?.[item.id];
|
|
676
|
-
const allNavs = !items || Array.isArray(items) ? items : [ items ];
|
|
677
|
-
|
|
678
|
-
// If the annotation target itself has a table alias, require projections of that
|
|
679
|
-
// table alias. Of course, that only works if we're talking about the same query.
|
|
680
|
-
const tableAlias = (target._main?._origin === item._artifact._main &&
|
|
681
|
-
target.value?.path[0]?._navigation?.kind === '$tableAlias')
|
|
682
|
-
? target.value.path[0]._navigation : null;
|
|
683
|
-
|
|
684
|
-
// Look at all table aliase that could project `item` and only select
|
|
685
|
-
// those that have actual projections.
|
|
686
|
-
const navs = allNavs?.filter(p => p._origin === item._artifact &&
|
|
687
|
-
(!tableAlias || tableAlias === p._parent));
|
|
688
|
-
if (!navs || navs.length === 0)
|
|
689
|
-
return [ null, expr.path.length ];
|
|
690
|
-
|
|
691
|
-
// If there are multiple navigations for the element, just use the first that matches.
|
|
692
|
-
// In case of table aliases, it's just one.
|
|
693
|
-
for (const nav of navs) {
|
|
694
|
-
const r = firstProjectionForPath( expr.path, index, nav._parent, target );
|
|
695
|
-
if (r.elem)
|
|
696
|
-
return [ r.elem, r.index ];
|
|
679
|
+
if (item.id !== found.name.id || (rewriteTarget[1] - index) !== 0) {
|
|
680
|
+
// Path was rewritten; original token text string is no longer accurate
|
|
681
|
+
config.tokenExpr.$tokenTexts = true;
|
|
682
|
+
item.id = found.name.id;
|
|
697
683
|
}
|
|
698
684
|
|
|
699
|
-
return
|
|
685
|
+
return setArtifactLink( expr.path[index], found );
|
|
700
686
|
}
|
|
701
687
|
}
|
|
702
688
|
|
|
@@ -932,7 +932,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
932
932
|
anno: msg.anno(),
|
|
933
933
|
type: dTypeName,
|
|
934
934
|
value: `"#${enumSymbol}"`,
|
|
935
|
-
rawvalues: Object.keys(typeDef.$Allowed.Symbols).map(m =>
|
|
935
|
+
rawvalues: Object.keys(typeDef.$Allowed.Symbols).map(m => `#${m}`),
|
|
936
936
|
'#': 'enum',
|
|
937
937
|
});
|
|
938
938
|
}
|
|
@@ -1024,7 +1024,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
1024
1024
|
anno: msg.anno(),
|
|
1025
1025
|
type: dTypeName,
|
|
1026
1026
|
value: `"#${value}"`,
|
|
1027
|
-
rawvalues: expectedType.Members.map(m =>
|
|
1027
|
+
rawvalues: expectedType.Members.map(m => `#${m}`),
|
|
1028
1028
|
'#': 'enum',
|
|
1029
1029
|
});
|
|
1030
1030
|
}
|
|
@@ -1060,7 +1060,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
1060
1060
|
anno: msg.anno(),
|
|
1061
1061
|
type: dTypeName,
|
|
1062
1062
|
value: value['='] || value,
|
|
1063
|
-
rawvalues: type.Members.map(m =>
|
|
1063
|
+
rawvalues: type.Members.map(m => `#${m}`),
|
|
1064
1064
|
'#': 'enum',
|
|
1065
1065
|
});
|
|
1066
1066
|
}
|
|
@@ -1120,7 +1120,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
1120
1120
|
|
|
1121
1121
|
if (isEnumType(resolvedType)) {
|
|
1122
1122
|
const type = getDictType(resolvedType);
|
|
1123
|
-
const expected = type.Members.map(m =>
|
|
1123
|
+
const expected = type.Members.map(m => `#${m}`);
|
|
1124
1124
|
message('odata-anno-value', msg.location,
|
|
1125
1125
|
{
|
|
1126
1126
|
anno: msg.anno(),
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -736,8 +736,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
736
736
|
attributes.IsComposable = false;
|
|
737
737
|
|
|
738
738
|
/** @type {object} */
|
|
739
|
-
const actionNode = (iAmAnAction) ? new Edm.Action(v, attributes)
|
|
740
|
-
: new Edm.FunctionDefinition(v, attributes);
|
|
739
|
+
const actionNode = (iAmAnAction) ? new Edm.Action(v, attributes, actionCsn)
|
|
740
|
+
: new Edm.FunctionDefinition(v, attributes, actionCsn);
|
|
741
741
|
|
|
742
742
|
const bpType = entityCsn ? fullQualified(entityCsn.name) : undefined;
|
|
743
743
|
/*
|
package/lib/edm/edm.js
CHANGED
|
@@ -31,6 +31,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
31
31
|
this._edmAttributes = Object.assign(Object.create(null), attributes);
|
|
32
32
|
this._xmlOnlyAttributes = Object.create(null);
|
|
33
33
|
this._jsonOnlyAttributes = Object.create(null);
|
|
34
|
+
this._openApiHints = Object.create(null);
|
|
34
35
|
|
|
35
36
|
this._children = [];
|
|
36
37
|
this._ignoreChildren = false;
|
|
@@ -38,6 +39,8 @@ function getEdm( options, messageFunctions ) {
|
|
|
38
39
|
|
|
39
40
|
if (this.v2)
|
|
40
41
|
this.setSapVocabularyAsAttributes(csn);
|
|
42
|
+
|
|
43
|
+
this.setOpenApiHints(csn);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
get v2() {
|
|
@@ -103,6 +106,17 @@ function getEdm( options, messageFunctions ) {
|
|
|
103
106
|
return this;
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
setOpenApiHints(csn) {
|
|
110
|
+
if (csn && options.odataOpenapiHints) {
|
|
111
|
+
const jsonAttr = Object.create(null);
|
|
112
|
+
Object.entries(csn).filter(([ k, _v ] ) => k.startsWith('@OpenAPI.')).forEach(([ k, v ]) => {
|
|
113
|
+
jsonAttr[k] = v;
|
|
114
|
+
});
|
|
115
|
+
Object.assign(this._openApiHints, jsonAttr);
|
|
116
|
+
}
|
|
117
|
+
return this._openApiHints;
|
|
118
|
+
}
|
|
119
|
+
|
|
106
120
|
// virtual
|
|
107
121
|
toJSON() {
|
|
108
122
|
const json = Object.create(null);
|
|
@@ -110,16 +124,24 @@ function getEdm( options, messageFunctions ) {
|
|
|
110
124
|
if (!(this.kind in Node.noJsonKinds))
|
|
111
125
|
json.$Kind = this.kind;
|
|
112
126
|
|
|
113
|
-
this.toJSONattributes(json);
|
|
114
|
-
return this.toJSONchildren(json);
|
|
127
|
+
return this.toJSONchildren(this.toJSONattributes(json));
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
// virtual
|
|
118
|
-
toJSONattributes(json) {
|
|
131
|
+
toJSONattributes(json, withHints = true) {
|
|
119
132
|
forEach(this._edmAttributes, (p, v) => {
|
|
120
133
|
if (p !== 'Name')
|
|
121
134
|
json[p[0] === '@' ? p : `$${p}`] = v;
|
|
122
135
|
});
|
|
136
|
+
return (withHints ? this.toOpenApiHints(json) : json);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
toOpenApiHints(json) {
|
|
140
|
+
if (options.odataOpenapiHints && this._openApiHints) {
|
|
141
|
+
Object.entries(this._openApiHints).forEach(([ p, v ]) => {
|
|
142
|
+
json[p[0] === '@' ? p : `$${p}`] = v;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
123
145
|
return json;
|
|
124
146
|
}
|
|
125
147
|
|
|
@@ -243,6 +265,10 @@ function getEdm( options, messageFunctions ) {
|
|
|
243
265
|
super.setSapVocabularyAsAttributes(csn, true);
|
|
244
266
|
}
|
|
245
267
|
|
|
268
|
+
toJSONattributes(json) {
|
|
269
|
+
return super.toJSONattributes(json, false);
|
|
270
|
+
}
|
|
271
|
+
|
|
246
272
|
register(entry) {
|
|
247
273
|
if (!this._registry[entry._edmAttributes.Name])
|
|
248
274
|
this._registry[entry._edmAttributes.Name] = [ entry ];
|
|
@@ -258,6 +284,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
258
284
|
if (alias !== undefined)
|
|
259
285
|
props.Alias = alias;
|
|
260
286
|
super(version, props);
|
|
287
|
+
this.setOpenApiHints(serviceCsn);
|
|
261
288
|
this._annotations = annotations;
|
|
262
289
|
this._actions = Object.create(null);
|
|
263
290
|
this.setXml( { xmlns: (this.v2) ? 'http://schemas.microsoft.com/ado/2008/09/edm' : 'http://docs.oasis-open.org/odata/ns/edm' } );
|
|
@@ -321,6 +348,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
321
348
|
json[p[0] === '@' ? p : `$${p}`] = v;
|
|
322
349
|
});
|
|
323
350
|
}
|
|
351
|
+
return this.toOpenApiHints(json);
|
|
324
352
|
}
|
|
325
353
|
|
|
326
354
|
toJSONchildren(json) {
|
|
@@ -550,8 +578,8 @@ function getEdm( options, messageFunctions ) {
|
|
|
550
578
|
*/
|
|
551
579
|
|
|
552
580
|
class ActionFunctionBase extends Node {
|
|
553
|
-
constructor(version, details) {
|
|
554
|
-
super(version, details);
|
|
581
|
+
constructor(version, details, csn) {
|
|
582
|
+
super(version, details, csn);
|
|
555
583
|
this._returnType = undefined;
|
|
556
584
|
}
|
|
557
585
|
|
|
@@ -702,7 +730,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
702
730
|
if (this.$isCollection)
|
|
703
731
|
json.$Collection = this.$isCollection;
|
|
704
732
|
|
|
705
|
-
return json;
|
|
733
|
+
return this.toOpenApiHints(json);
|
|
706
734
|
}
|
|
707
735
|
}
|
|
708
736
|
|
|
@@ -753,11 +781,11 @@ function getEdm( options, messageFunctions ) {
|
|
|
753
781
|
else
|
|
754
782
|
this._keys = undefined;
|
|
755
783
|
|
|
756
|
-
if (
|
|
784
|
+
if (this._openApiHints) {
|
|
757
785
|
if (csn['@cds.autoexpose'])
|
|
758
|
-
this.
|
|
786
|
+
this._openApiHints['@cds.autoexpose'] = true;
|
|
759
787
|
if (csn['@cds.autoexposed'])
|
|
760
|
-
this.
|
|
788
|
+
this._openApiHints['@cds.autoexposed'] = true;
|
|
761
789
|
}
|
|
762
790
|
}
|
|
763
791
|
|
|
@@ -806,7 +834,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
806
834
|
class Member extends Node {
|
|
807
835
|
toJSONattributes(json) {
|
|
808
836
|
json[this._edmAttributes.Name] = this._edmAttributes.Value;
|
|
809
|
-
return json;
|
|
837
|
+
return super.toOpenApiHints(json);
|
|
810
838
|
}
|
|
811
839
|
}
|
|
812
840
|
|
|
@@ -823,11 +851,6 @@ function getEdm( options, messageFunctions ) {
|
|
|
823
851
|
}
|
|
824
852
|
}
|
|
825
853
|
|
|
826
|
-
toJSONattributes(json) {
|
|
827
|
-
super.toJSONattributes(json);
|
|
828
|
-
return json;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
854
|
toJSONchildren(json) {
|
|
832
855
|
this._children.forEach(c => c.toJSONattributes(json));
|
|
833
856
|
return json;
|
|
@@ -1161,6 +1184,10 @@ function getEdm( options, messageFunctions ) {
|
|
|
1161
1184
|
return this.toJSONchildren(json);
|
|
1162
1185
|
}
|
|
1163
1186
|
|
|
1187
|
+
toJSONattributes(json) {
|
|
1188
|
+
return super.toJSONattributes(json, false);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1164
1191
|
getConstantExpressionValue() {
|
|
1165
1192
|
// short form: key: value
|
|
1166
1193
|
const inlineConstExpr
|
|
@@ -1290,6 +1317,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
1290
1317
|
const keys = Object.keys(this._edmAttributes).filter(k => k !== 'Type');
|
|
1291
1318
|
for (const key of keys)
|
|
1292
1319
|
json[`$${key}`] = this._edmAttributes[key];
|
|
1320
|
+
return json;
|
|
1293
1321
|
}
|
|
1294
1322
|
|
|
1295
1323
|
toJSONchildren(json) {
|
|
@@ -1397,8 +1425,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
1397
1425
|
toJSON() {
|
|
1398
1426
|
const json = this.mergeJSONAnnotations();
|
|
1399
1427
|
json[`$${this.kind}`] = this._children.filter(c => c.kind !== 'Annotation').map(c => c.toJSON());
|
|
1400
|
-
this.toJSONattributes(json);
|
|
1401
|
-
return json;
|
|
1428
|
+
return this.toJSONattributes(json);
|
|
1402
1429
|
}
|
|
1403
1430
|
}
|
|
1404
1431
|
class Cast extends AnnotationBase {
|
|
@@ -1417,8 +1444,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
1417
1444
|
// first expression only, if any
|
|
1418
1445
|
const children = this._children.filter(child => child.kind !== 'Annotation');
|
|
1419
1446
|
json[`$${this.kind}`] = children.length ? children[0].toJSON() : {};
|
|
1420
|
-
this.toJSONattributes(json);
|
|
1421
|
-
return json;
|
|
1447
|
+
return this.toJSONattributes(json);
|
|
1422
1448
|
}
|
|
1423
1449
|
toJSONattributes(json) {
|
|
1424
1450
|
super.toJSONattributes(json);
|
|
@@ -1445,8 +1471,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
1445
1471
|
// first expression only, if any
|
|
1446
1472
|
const children = this._children.filter(child => child.kind !== 'Annotation');
|
|
1447
1473
|
json[`$${this.kind}`] = children.length ? children[0].toJSON() : '';
|
|
1448
|
-
this.toJSONattributes(json);
|
|
1449
|
-
return json;
|
|
1474
|
+
return this.toJSONattributes(json);
|
|
1450
1475
|
}
|
|
1451
1476
|
|
|
1452
1477
|
toJSONattributes(json) { // including Name
|
|
@@ -59,7 +59,6 @@ function inboundQualificationChecks( csn, options, messageFunctions,
|
|
|
59
59
|
markBindingParamPaths(action, aLoc);
|
|
60
60
|
forEachMemberRecursively(action, checkIfItemsOfItems, aLoc);
|
|
61
61
|
checkIfItemsOfItems(action.returns, undefined, undefined, aLoc.concat('returns'));
|
|
62
|
-
markBindingParamPaths(action, aLoc);
|
|
63
62
|
});
|
|
64
63
|
}
|
|
65
64
|
|
|
@@ -160,6 +160,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
160
160
|
forEachDefinition(reqDefs, [
|
|
161
161
|
exposeTargetsAsProxiesOrSchemaRefs,
|
|
162
162
|
determineEntitySet,
|
|
163
|
+
annotateOptionalActFuncParams,
|
|
163
164
|
]);
|
|
164
165
|
// finalize proxy creation
|
|
165
166
|
mergeProxiesIntoModel();
|
|
@@ -170,7 +171,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
170
171
|
forEachDefinition(reqDefs, [
|
|
171
172
|
initEdmNavPropBindingTargets,
|
|
172
173
|
pullupCapabilitiesAnnotations,
|
|
173
|
-
annotateOptionalActFuncParams,
|
|
174
174
|
]);
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -685,8 +685,8 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
685
685
|
return;
|
|
686
686
|
|
|
687
687
|
let keys = Object.create(null);
|
|
688
|
-
const validFrom = [];
|
|
689
|
-
|
|
688
|
+
const validFrom = [];
|
|
689
|
+
const validKey = [];
|
|
690
690
|
|
|
691
691
|
// Iterate all struct elements
|
|
692
692
|
forEachMemberRecursively(def.items || def, (element, elementName, prop, _path, construct) => {
|
|
@@ -796,7 +796,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
796
796
|
Do not render (ignore) elements as properties
|
|
797
797
|
In V4:
|
|
798
798
|
1) If this is a foreign key of an association to a container which *is* used
|
|
799
|
-
to establish the
|
|
799
|
+
to establish the relation via composition and $self comparison.
|
|
800
800
|
The $self comparison can only be evaluated after the ON conditions have been
|
|
801
801
|
parsed in prepareConstraints().
|
|
802
802
|
2) For all other foreign keys let isEdmPropertyRendered() decide.
|
|
@@ -2095,45 +2095,58 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
2095
2095
|
|
|
2096
2096
|
function iterateParams( action, location ) {
|
|
2097
2097
|
let optPns = [];
|
|
2098
|
+
const isBP = p => (p.items?.type || p.type) === special$self;
|
|
2099
|
+
|
|
2098
2100
|
if (action.params) {
|
|
2099
2101
|
Object.entries(action.params).forEach(([ pn, p ]) => {
|
|
2100
|
-
|
|
2102
|
+
// user assigned annotation, don't touch it
|
|
2101
2103
|
const defT = reqDefs.definitions[p.items?.type || p.type];
|
|
2102
2104
|
const isStructType = !!(defT?.items?.elements || defT?.elements);
|
|
2103
2105
|
const isItems = !!(p.items || defT?.items);
|
|
2104
2106
|
|
|
2105
|
-
if (Object.keys(p).some(a => a.startsWith('@Core.OptionalParameter') && p[a]
|
|
2106
|
-
|
|
2107
|
+
if (Object.keys(p).some(a => a.startsWith('@Core.OptionalParameter') && p[a] != null)) {
|
|
2108
|
+
// expand short cut annotation for unspecified default value
|
|
2109
|
+
if (typeof p['@Core.OptionalParameter'] === 'boolean') {
|
|
2110
|
+
if (p['@Core.OptionalParameter'] && !isBP(p) && !options.isV2()) {
|
|
2111
|
+
if (p.default?.val !== undefined) {
|
|
2112
|
+
if (p.default.val !== null && (isStructType || isItems))
|
|
2113
|
+
warning('odata-ignoring-param-default', location.concat(pn), { '#': 'colitem' });
|
|
2114
|
+
else
|
|
2115
|
+
p['@Core.OptionalParameter'] = { DefaultValue: p.default.val };
|
|
2116
|
+
}
|
|
2117
|
+
else {
|
|
2118
|
+
p['@Core.OptionalParameter'] = { $Type: '' };
|
|
2119
|
+
}
|
|
2120
|
+
optPns.push(p);
|
|
2121
|
+
}
|
|
2122
|
+
else { // reset falsy annotation, param is NOT optional
|
|
2123
|
+
p['@Core.OptionalParameter'] = null;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
else {
|
|
2127
|
+
optPns.push(p);
|
|
2128
|
+
}
|
|
2107
2129
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2130
|
+
else if (!isBP(p) && !options.isV2()) {
|
|
2131
|
+
// default value automatically makes param optional
|
|
2110
2132
|
if (p.default?.val !== undefined) {
|
|
2111
|
-
if (p.default.val !== null && (isStructType || isItems))
|
|
2133
|
+
if (p.default.val !== null && (isStructType || isItems))
|
|
2112
2134
|
warning('odata-ignoring-param-default', location.concat(pn), { '#': 'colitem' });
|
|
2113
|
-
|
|
2114
|
-
else {
|
|
2135
|
+
else
|
|
2115
2136
|
edmUtils.assignAnnotation(p, '@Core.OptionalParameter.DefaultValue', p.default.val);
|
|
2116
|
-
|
|
2117
|
-
}
|
|
2137
|
+
optPns.push(p);
|
|
2118
2138
|
}
|
|
2119
|
-
//
|
|
2120
|
-
else if (!p.notNull) {
|
|
2121
|
-
edmUtils.assignAnnotation(p, '@Core.OptionalParameter.$Type', '');
|
|
2139
|
+
// nullable action params are optional (implicit default null)
|
|
2140
|
+
else if (!p.notNull && action.kind === 'action') {
|
|
2122
2141
|
optPns.push(p);
|
|
2123
2142
|
}
|
|
2124
|
-
else {
|
|
2125
|
-
|
|
2126
|
-
optPns.
|
|
2127
|
-
|
|
2128
|
-
if (type !== special$self)
|
|
2129
|
-
error('odata-parameter-order', location.concat(op.name));
|
|
2130
|
-
});
|
|
2143
|
+
else if (action.kind === 'function') {
|
|
2144
|
+
// this is a mandatory parameter, warn about all previously collected optional parameters
|
|
2145
|
+
if (optPns.filter(op => (op.items?.type || op.type) !== special$self).length)
|
|
2146
|
+
error('odata-parameter-order', location.concat(pn));
|
|
2131
2147
|
optPns = [];
|
|
2132
2148
|
}
|
|
2133
2149
|
}
|
|
2134
|
-
else if (p.default) {
|
|
2135
|
-
warning('odata-ignoring-param-default', location.concat(pn));
|
|
2136
|
-
}
|
|
2137
2150
|
});
|
|
2138
2151
|
}
|
|
2139
2152
|
}
|
package/lib/edm/edmUtils.js
CHANGED
|
@@ -386,7 +386,7 @@ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
|
|
|
386
386
|
// If this association points to a redirected Parameter EntityType, do not calculate any constraints,
|
|
387
387
|
// continue with multiplicity
|
|
388
388
|
if (assocCsn._target.$isParamEntity)
|
|
389
|
-
assocCsn._constraints.constraints =
|
|
389
|
+
assocCsn._constraints.constraints = {};
|
|
390
390
|
|
|
391
391
|
return assocCsn._constraints;
|
|
392
392
|
|