@sap/cds-compiler 2.15.2 → 2.15.4

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 CHANGED
@@ -7,6 +7,19 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 2.15.4 - 2022-06-09
11
+
12
+ ### Fixed
13
+
14
+ - for.odata:
15
+ + Fix derived type to scalar type resolution with intermediate `many`.
16
+ - to.edm(x):
17
+ + (V4 structured) Fix key paths in combination with `--odata-foreign-keys`.
18
+ + Add `Edm.PrimitiveType` to `@odata.Type`.
19
+ + (V4 JSON) Render constant expressions for `Edm.Stream` and `Edm.Untyped`.
20
+ + Fix a bug in target path calculation for `NavigationPropertyBinding`s to external references.
21
+ + Render inner annotations even if `$value` is missing.
22
+ - Update OData vocabularies 'Common', 'UI'.
10
23
 
11
24
  ## Version 2.15.2 - 2022-05-12
12
25
 
@@ -158,12 +158,12 @@ const centralMessages = {
158
158
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
159
159
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
160
160
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
161
- 'odata-spec-violation-id': { severity: 'Error' },
162
- 'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
161
+ 'odata-spec-violation-id': { severity: 'Error', configurableFor: true },
162
+ 'odata-spec-violation-type': { severity: 'Error', configurableFor: true },
163
163
  'odata-spec-violation-type-unknown': { severity: 'Warning' },
164
164
  'odata-spec-violation-no-key': { severity: 'Warning' },
165
- 'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
166
- 'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
165
+ 'odata-spec-violation-key-array': { severity: 'Error', configurableFor: true }, // more than 30 chars
166
+ 'odata-spec-violation-key-null': { severity: 'Error', configurableFor: true }, // more than 30 chars
167
167
  'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
168
168
  'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
169
169
  };
@@ -333,7 +333,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
333
333
  function handleNestedElements(objname, baseElemName, elementsObj) {
334
334
  if(!elementsObj) return;
335
335
  Object.entries(elementsObj).forEach(([elemName, element]) => {
336
- if (Object.keys(element).filter( x => x.substr(0,1) === '@' ).filter(filterKnownVocabularies).length > 0) {
336
+ if (Object.keys(element).filter( x => x[0] === '@' ).filter(filterKnownVocabularies).length > 0) {
337
337
  message(warning, null, `annotations at nested elements are not yet supported, object ${objname}, element ${baseElemName}.${elemName}`);
338
338
  }
339
339
 
@@ -437,11 +437,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
437
437
 
438
438
  // Filter unknown toplevel annotations
439
439
  // Final filtering of all annotations is done in handleTerm
440
- const annoNames = Object.keys(carrier).filter( x => x.substr(0,1) === '@' );
440
+
441
+ let annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
441
442
  const nullWhitelist = [ '@Core.OperationAvailable' ];
442
- const knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
443
+ let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
443
444
  if (knownAnnos.length === 0) return;
444
445
 
446
+ if(rewriteInnerAnnotations()) {
447
+ annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
448
+ knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
449
+ if (knownAnnos.length === 0) return;
450
+ }
445
451
  const prefixTree = createPrefixTree();
446
452
 
447
453
  // usually, for a given carrier there is one target
@@ -610,6 +616,51 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
610
616
  */
611
617
  }
612
618
 
619
+ function rewriteInnerAnnotations() {
620
+ let rc = false;
621
+ for (let a of knownAnnos) {
622
+ const [ prefix, innerAnnotation ] = a.split('.@');
623
+ /*
624
+ New inner annotation (de-)structuring of the core compiler to make
625
+ $value arrays extendable via ellipsis
626
+ @anno: { $value: [ ... ], @innerAnno: ... } is now cracked up by
627
+ the core compiler into:
628
+ @anno: [ ...]
629
+ @anno.@innerAnno: ...
630
+
631
+ Conflict handling if $value is present:
632
+ @anno
633
+ @anno.$value
634
+ @anno.@innerAnno
635
+
636
+ @anno has precedence (as it was before this change) but now
637
+ @anno.$value is overwritten with @anno and the inner annotations
638
+ are applied.
639
+
640
+ Trigger is always the inner annotation, if no inner annotation
641
+ is available, @anno has precedence.
642
+
643
+ Insert $value into $edmJson with inner annotation as well.
644
+ */
645
+ if(innerAnnotation) {
646
+ if(carrier[prefix]) {
647
+ const valPrefix = prefix + '.$value';
648
+ carrier[valPrefix] = carrier[prefix];
649
+ delete carrier[prefix];
650
+ rc = true;
651
+ }
652
+ const edmJsonPrefix = prefix + '.$edmJson';
653
+ if(carrier[edmJsonPrefix]) {
654
+ const valPrefix = prefix + '.$value.$edmJson';
655
+ carrier[valPrefix] = carrier[edmJsonPrefix];
656
+ delete carrier[edmJsonPrefix];
657
+ rc = true;
658
+ }
659
+ }
660
+ }
661
+ return rc;
662
+ }
663
+
613
664
  function createPrefixTree() {
614
665
  // in csn, all annotations are flattened
615
666
  // => values can be - primitive values (string, number)
@@ -619,6 +670,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
619
670
  // by building a "prefix tree" for the annotations attached to the carrier
620
671
  // see example at definition of function mergePathStepsIntoPrefixTree
621
672
  const prefixTree = {};
673
+
622
674
  for (let a of knownAnnos) {
623
675
  // remove leading @ and split at "."
624
676
  // stop splitting at ".@" (used for nested annotations)
@@ -845,7 +897,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
845
897
  // "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
846
898
  oTarget.append(handleEdmJson(cAnnoValue['$edmJson'], context));
847
899
  }
848
- else if ( Object.keys(cAnnoValue).filter( x => x.substr(0,1) !== '@' ).length === 0) {
900
+ else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
849
901
  // object consists only of properties starting with "@"
850
902
  message(warning, context, 'nested annotations without corresponding base annotation');
851
903
  }
@@ -952,6 +1004,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
952
1004
  // Edm.Decimal -> Decimal
953
1005
  // integer tpye -> Int
954
1006
  function handleSimpleValue(val, dTypeName, context) {
1007
+ // these types must be represented as "String" values in XML:
1008
+ const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
955
1009
  // caller already made sure that val is neither object nor array
956
1010
  dTypeName = resolveType(dTypeName);
957
1011
 
@@ -989,12 +1043,12 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
989
1043
  message(warning, context, `found String, but expected enum type ${ dTypeName }`);
990
1044
  typeName = 'EnumMember';
991
1045
  }
992
- else if (dTypeName && dTypeName.startsWith('Edm.') && dTypeName !== 'Edm.PrimitiveType') {
1046
+ else if (dTypeName && dTypeName.startsWith('Edm.') && !castToXmlString.includes(dTypeName)) {
993
1047
  // this covers also all paths
994
1048
  typeName = dTypeName.substring(4);
995
1049
  }
996
1050
  else {
997
- if(dTypeName == undefined || dTypeName === 'Edm.PrimitiveType')
1051
+ if(dTypeName == undefined || castToXmlString.some(t => t === dTypeName))
998
1052
  dTypeName = 'Edm.String';
999
1053
  // TODO
1000
1054
  //message(warning, context, "type is not yet handled: found String, expected type: " + dTypeName);
@@ -14,7 +14,7 @@ const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../mo
14
14
  const { checkCSNVersion } = require('../json/csnVersion');
15
15
  const { makeMessageFunction } = require('../base/messages');
16
16
  const { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm } = require('./edm.js');
17
-
17
+
18
18
  /*
19
19
  OData V2 spec 06/01/2017 PDF version is available from here:
20
20
  https://msdn.microsoft.com/en-us/library/dd541474.aspx
@@ -421,7 +421,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
421
421
  message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
422
422
  else if (options.isV2() && /^(_|[0-9])/.test(p._edmAttributes.Name)) {
423
423
  // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
424
- error('odata-spec-violation-id', pLoc,
424
+ message('odata-spec-violation-id', pLoc,
425
425
  { prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
426
426
  }
427
427
  });
@@ -621,9 +621,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
621
621
  message('odata-spec-violation-id', pLoc, { id: parameterName });
622
622
 
623
623
  // only scalar or structured type in V2 (not entity)
624
- if(param._type &&
625
- !param._type.startsWith('Edm.') &&
626
- csn.definitions[param._type] &&
624
+ if(param._type &&
625
+ !param._type.startsWith('Edm.') &&
626
+ csn.definitions[param._type] &&
627
627
  !edmUtils.isStructuredType(csn.definitions[param._type]))
628
628
  warning('odata-spec-violation-param', pLoc, { version: '2.0' });
629
629
 
@@ -933,7 +933,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
933
933
 
934
934
  // generate the Edm.Annotations tree and append it to the corresponding schema
935
935
  function addAnnotations() {
936
- let { annos, usedVocabularies } = translate.csn2annotationEdm(csn, serviceCsn.name, Edm, options, messageFunctions);
936
+ let { annos, usedVocabularies } = translate.csn2annotationEdm(reqDefs, serviceCsn.name, Edm, options, messageFunctions);
937
937
  // distribute edm:Annotations into the schemas
938
938
  // Distribute each anno into Schema
939
939
  annos.forEach(anno => {
@@ -990,7 +990,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
990
990
  });
991
991
  }
992
992
  else {
993
- message('odata-spec-violation-type-unknown', pLoc,
993
+ message('odata-spec-violation-type-unknown', pLoc,
994
994
  { type:edmType });
995
995
  }
996
996
  }
package/lib/edm/edm.js CHANGED
@@ -54,7 +54,7 @@ const EdmPrimitiveTypeMap = {
54
54
  'Edm.GeometryMultiLineString': { v4: true, SRID: true, desc: 'Collection of line strings in a flat-earth coordinate system' },
55
55
  'Edm.GeometryMultiPolygon': { v4: true, SRID: true, desc: 'Collection of polygons in a flat-earth coordinate system' },
56
56
  'Edm.GeometryCollection': { v4: true, SRID: true, desc: 'Collection of arbitrary Geometry values' },
57
- //'Edm.PrimitiveType': { v4: true, desc: 'Abstract meta type' },
57
+ 'Edm.PrimitiveType': { v4: true, desc: 'Abstract meta type' },
58
58
  //'Edm.Untyped': { v4: true, desc: 'Abstract void type' },
59
59
  };
60
60
 
@@ -1223,9 +1223,15 @@ function getEdm(options, messageFunctions) {
1223
1223
  // short form: key: value
1224
1224
  const inlineConstExpr =
1225
1225
  [ 'Edm.Binary', 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Double', 'Edm.Duration', 'Edm.Guid',
1226
- 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte','Edm.Single', /*'Edm.Stream',*/ 'Edm.String', 'Edm.TimeOfDay',
1226
+ 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte','Edm.Single', 'Edm.Stream', 'Edm.String', 'Edm.TimeOfDay',
1227
+ // Edm.Geo* according to https://issues.oasis-open.org/browse/ODATA-1323
1228
+ /* 'Edm.Geography', 'Edm.GeographyPoint', 'Edm.GeographyLineString', 'Edm.GeographyPolygon', 'Edm.GeographyMultiPoint',
1229
+ 'Edm.GeographyMultiLineString', 'Edm.GeographyMultiPolygon', 'Edm.GeographyCollection', 'Edm.Geometry', 'Edm.GeometryPoint',
1230
+ 'Edm.GeometryLineString', 'Edm.GeometryPolygon', 'Edm.GeometryMultiPoint', 'Edm.GeometryMultiLineString', 'Edm.GeometryMultiPolygon',
1231
+ 'Edm.GeometryCollection',
1232
+ */
1227
1233
  /* UI.xml: defines Annotations with generic type 'Edm.PrimitiveType' */
1228
- 'Edm.PrimitiveType', 'Bool',
1234
+ 'Edm.PrimitiveType', 'Edm.Untyped', 'Bool',
1229
1235
  // Official JSON V4.01 Spec defines these paths as constant inline expression:
1230
1236
  'AnnotationPath', 'ModelElementPath', 'NavigationPropertyPath', 'PropertyPath',
1231
1237
  ];
@@ -1455,12 +1455,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1455
1455
  if(isEdmPropertyRendered(k, options) &&
1456
1456
  !(options.isV2() && k['@Core.MediaType'])) {
1457
1457
  if(options.isV4() && options.isStructFormat) {
1458
- // This is structured OData ONLY
1459
- // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1460
- if(options.renderForeignKeys && !k.target)
1461
- def.$edmKeyPaths.push([kn]);
1462
- // else produce paths (isEdmPropertyRendered() has filtered @odata.foreignKey4 already)
1463
- else if(!options.renderForeignKeys)
1458
+ // This is structured OData ONLY
1459
+ // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1460
+ if(!options.renderForeignKeys || (options.renderForeignKeys && !k.target))
1464
1461
  def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
1465
1462
  }
1466
1463
  // In v2/v4 flat, associations are never rendered
@@ -1543,13 +1540,13 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1543
1540
  // Nullability
1544
1541
  if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
1545
1542
  elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
1546
- error('odata-spec-violation-key-null', location,
1543
+ message('odata-spec-violation-key-null', location,
1547
1544
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1548
1545
  }
1549
1546
  // many
1550
1547
  let type = elt.items || elt.type && !isBuiltinType(elt.type) && csnUtils.getFinalTypeDef(elt.type).items;
1551
1548
  if(type) {
1552
- error('odata-spec-violation-key-array', location,
1549
+ message('odata-spec-violation-key-array', location,
1553
1550
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1554
1551
  }
1555
1552
  // type
@@ -1615,7 +1612,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1615
1612
  if(isAssociationOrComposition(elt) && !elt.$touched) {
1616
1613
  if(!elt._target.$edmTgtPaths)
1617
1614
  setProp(elt._target, '$edmTgtPaths', []);
1618
- if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1615
+ // drill into target only if
1616
+ // 1) target has no entity set and this assoc is not going to the container
1617
+ // 2) current definition and target are the same (cycle)
1618
+ // 3) it's no external reference
1619
+ if(!elt.$externalRef &&
1620
+ !elt._target.$hasEntitySet &&
1621
+ !elt._isToContainer &&
1622
+ curDef !== elt._target) {
1619
1623
  // follow elements in the target but avoid cycles
1620
1624
  setProp(elt, '$touched', true);
1621
1625
  elt._target.$edmTgtPaths.push(newPrefix);
@@ -1654,6 +1658,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1654
1658
  // drill into target only if
1655
1659
  // 1) target has no entity set and this assoc is not going to the container
1656
1660
  // 2) current definition and target are the same (cycle)
1661
+ // 3) it's no external reference
1657
1662
  if(!elt.$externalRef &&
1658
1663
  !elt._target.$hasEntitySet &&
1659
1664
  !elt._isToContainer &&
@@ -658,6 +658,7 @@
658
658
  "Type": "Common.FieldControlType",
659
659
  "AppliesTo": [
660
660
  "Property",
661
+ "Parameter",
661
662
  "Record",
662
663
  "EntityType"
663
664
  ]
@@ -3246,6 +3247,7 @@
3246
3247
  "TypeNamePlural": "Edm.String",
3247
3248
  "Title": "UI.DataFieldAbstract",
3248
3249
  "Description": "UI.DataFieldAbstract",
3250
+ "Image": "Edm.Stream",
3249
3251
  "ImageUrl": "Edm.String",
3250
3252
  "TypeImageUrl": "Edm.String",
3251
3253
  "Initials": "Edm.String"
@@ -3693,7 +3695,7 @@
3693
3695
  "$kind": "ComplexType",
3694
3696
  "BaseType": "UI.DataFieldAbstract",
3695
3697
  "Properties": {
3696
- "Value": "Edm.PrimitiveType",
3698
+ "Value": "Edm.Untyped",
3697
3699
  "Label": "Edm.String",
3698
3700
  "Criticality": "UI.CriticalityType",
3699
3701
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3704,8 +3706,8 @@
3704
3706
  "$kind": "ComplexType",
3705
3707
  "BaseType": "UI.DataField",
3706
3708
  "Properties": {
3707
- "Action": "Common.QualifiedName",
3708
3709
  "Value": "Edm.PrimitiveType",
3710
+ "Action": "Common.QualifiedName",
3709
3711
  "Label": "Edm.String",
3710
3712
  "Criticality": "UI.CriticalityType",
3711
3713
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3716,10 +3718,10 @@
3716
3718
  "$kind": "ComplexType",
3717
3719
  "BaseType": "UI.DataField",
3718
3720
  "Properties": {
3721
+ "Value": "Edm.PrimitiveType",
3719
3722
  "SemanticObject": "Edm.String",
3720
3723
  "Action": "Edm.String",
3721
3724
  "Mapping": "Collection(Common.SemanticObjectMappingType)",
3722
- "Value": "Edm.PrimitiveType",
3723
3725
  "Label": "Edm.String",
3724
3726
  "Criticality": "UI.CriticalityType",
3725
3727
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3730,8 +3732,8 @@
3730
3732
  "$kind": "ComplexType",
3731
3733
  "BaseType": "UI.DataField",
3732
3734
  "Properties": {
3733
- "Target": "Edm.NavigationPropertyPath",
3734
3735
  "Value": "Edm.PrimitiveType",
3736
+ "Target": "Edm.NavigationPropertyPath",
3735
3737
  "Label": "Edm.String",
3736
3738
  "Criticality": "UI.CriticalityType",
3737
3739
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3742,9 +3744,9 @@
3742
3744
  "$kind": "ComplexType",
3743
3745
  "BaseType": "UI.DataField",
3744
3746
  "Properties": {
3747
+ "Value": "Edm.PrimitiveType",
3745
3748
  "Url": "Edm.String",
3746
3749
  "UrlContentType": "Edm.String",
3747
- "Value": "Edm.PrimitiveType",
3748
3750
  "Label": "Edm.String",
3749
3751
  "Criticality": "UI.CriticalityType",
3750
3752
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3755,8 +3757,8 @@
3755
3757
  "$kind": "ComplexType",
3756
3758
  "BaseType": "UI.DataField",
3757
3759
  "Properties": {
3758
- "Actions": "Collection(UI.DataField)",
3759
3760
  "Value": "Edm.PrimitiveType",
3761
+ "Actions": "Collection(UI.DataField)",
3760
3762
  "Label": "Edm.String",
3761
3763
  "Criticality": "UI.CriticalityType",
3762
3764
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -148,7 +148,9 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
148
148
  // example in actions: 'action act() return Primitive; type Primitive: array of String;'
149
149
  const currService = csnUtils.getServiceName(defName);
150
150
  const finalType = csnUtils.getFinalTypeDef(node.type);
151
- if (finalType.items && isBuiltinType(finalType.items.type)) {
151
+ if (finalType.items &&
152
+ (isBuiltinType(finalType.items.type) || isBuiltinType(csnUtils.getFinalBaseType(finalType.items.type))))
153
+ {
152
154
  if (!isArtifactInService(node.type, currService) || !isV4) {
153
155
  node.items = finalType.items;
154
156
  delete node.type;
@@ -1818,6 +1818,10 @@ function walkPath(node, env)
1818
1818
  // or that are parameters ($parameters or escaped paths (':')
1819
1819
  //path.length && path[ path.length-1 ]._artifact
1820
1820
  const art = path && path.length && path[path.length-1]._artifact;
1821
+
1822
+ // regardless of the position in the query, ignore paths that have virtual path steps
1823
+ if(art && path.some(ps => ps._artifact && ps._artifact.virtual && ps._artifact.virtual.val))
1824
+ return path;
1821
1825
  if(art && !internalArtifactKinds.includes(art.kind))
1822
1826
  {
1823
1827
  if(env.callback)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "2.15.2",
3
+ "version": "2.15.4",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",