@sap/cds-compiler 2.13.6 → 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.
Files changed (78) hide show
  1. package/CHANGELOG.md +128 -4
  2. package/bin/cdsc.js +112 -37
  3. package/lib/api/main.js +20 -22
  4. package/lib/api/options.js +2 -3
  5. package/lib/api/validate.js +6 -6
  6. package/lib/base/message-registry.js +92 -17
  7. package/lib/base/messages.js +85 -64
  8. package/lib/base/optionProcessorHelper.js +19 -0
  9. package/lib/checks/annotationsOData.js +11 -32
  10. package/lib/checks/arrayOfs.js +1 -34
  11. package/lib/checks/validator.js +2 -4
  12. package/lib/compiler/assert-consistency.js +1 -0
  13. package/lib/compiler/base.js +1 -0
  14. package/lib/compiler/builtins.js +11 -0
  15. package/lib/compiler/checks.js +22 -70
  16. package/lib/compiler/define.js +59 -11
  17. package/lib/compiler/extend.js +20 -3
  18. package/lib/compiler/finalize-parse-cdl.js +26 -20
  19. package/lib/compiler/index.js +75 -26
  20. package/lib/compiler/populate.js +6 -5
  21. package/lib/compiler/propagator.js +4 -1
  22. package/lib/compiler/resolve.js +104 -16
  23. package/lib/compiler/shared.js +61 -27
  24. package/lib/compiler/tweak-assocs.js +7 -1
  25. package/lib/edm/annotations/genericTranslation.js +93 -21
  26. package/lib/edm/csn2edm.js +216 -98
  27. package/lib/edm/edm.js +305 -226
  28. package/lib/edm/edmPreprocessor.js +499 -423
  29. package/lib/edm/edmUtils.js +22 -22
  30. package/lib/gen/Dictionary.json +98 -22
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +3 -1
  33. package/lib/gen/languageParser.js +4636 -4368
  34. package/lib/json/csnVersion.js +10 -11
  35. package/lib/json/from-csn.js +3 -2
  36. package/lib/json/to-csn.js +0 -2
  37. package/lib/language/docCommentParser.js +2 -2
  38. package/lib/language/genericAntlrParser.js +47 -2
  39. package/lib/language/language.g4 +59 -27
  40. package/lib/main.d.ts +19 -1
  41. package/lib/main.js +6 -0
  42. package/lib/model/csnRefs.js +33 -6
  43. package/lib/model/csnUtils.js +193 -75
  44. package/lib/model/enrichCsn.js +1 -0
  45. package/lib/model/revealInternalProperties.js +2 -2
  46. package/lib/modelCompare/compare.js +6 -6
  47. package/lib/optionProcessor.js +62 -26
  48. package/lib/render/toCdl.js +844 -679
  49. package/lib/render/toHdbcds.js +189 -243
  50. package/lib/render/toSql.js +180 -198
  51. package/lib/render/utils/common.js +131 -15
  52. package/lib/transform/db/.eslintrc.json +1 -1
  53. package/lib/transform/db/associations.js +2 -2
  54. package/lib/transform/db/constraints.js +3 -1
  55. package/lib/transform/db/expansion.js +15 -10
  56. package/lib/transform/db/flattening.js +95 -68
  57. package/lib/transform/db/transformExists.js +7 -7
  58. package/lib/transform/db/views.js +6 -3
  59. package/lib/transform/forHanaNew.js +43 -26
  60. package/lib/transform/forOdataNew.js +43 -42
  61. package/lib/transform/localized.js +12 -7
  62. package/lib/transform/odata/toFinalBaseType.js +8 -6
  63. package/lib/transform/odata/typesExposure.js +145 -197
  64. package/lib/transform/transformUtilsNew.js +9 -12
  65. package/lib/transform/translateAssocsToJoins.js +5 -1
  66. package/lib/transform/universalCsn/coreComputed.js +5 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
  68. package/lib/utils/moduleResolve.js +13 -6
  69. package/package.json +1 -1
  70. package/share/messages/message-explanations.json +2 -1
  71. package/share/messages/syntax-expected-integer.md +37 -0
  72. package/lib/transform/odata/attachPath.js +0 -96
  73. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  74. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  75. package/lib/transform/odata/referenceFlattener.js +0 -296
  76. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  77. package/lib/transform/odata/structuralPath.js +0 -72
  78. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -7,12 +7,13 @@ const NAVPROP_TRENNER = '_';
7
7
  const VALUELIST_NAVPROP_PREFIX = '';
8
8
 
9
9
  const edmUtils = require('./edmUtils.js')
10
- const { initializeModel } = require('./edmPreprocessor.js');
10
+ const { initializeModel, assignAnnotation } = require('./edmPreprocessor.js');
11
11
  const translate = require('./annotations/genericTranslation.js');
12
12
  const { setProp } = require('../base/model');
13
- const { cloneCsn, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
13
+ const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
14
14
  const { checkCSNVersion } = require('../json/csnVersion');
15
15
  const { makeMessageFunction } = require('../base/messages');
16
+ const { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm } = require('./edm.js');
16
17
 
17
18
  /*
18
19
  OData V2 spec 06/01/2017 PDF version is available from here:
@@ -25,11 +26,11 @@ function csn2edm(_csn, serviceName, _options) {
25
26
 
26
27
  function csn2edmAll(_csn, _options, serviceNames=undefined) {
27
28
  // get us a fresh model copy that we can work with
28
- const csn = cloneCsn(_csn, _options);
29
+ const csn = cloneCsnNonDict(_csn, _options);
29
30
 
30
31
  // use original options for messages; cloned CSN for semantic location
31
32
  const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
32
- const { info, warning, error, message, throwWithError } = messageFunctions;
33
+ const { info, warning, error, message, throwWithAnyError } = messageFunctions;
33
34
  checkCSNVersion(csn, _options);
34
35
 
35
36
  let rc = Object.create(null);
@@ -42,13 +43,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
42
43
  setProp(csn.meta, 'options', _csn.meta.options);
43
44
  }
44
45
 
45
- let [ allServices,
46
+ const [ allServices,
46
47
  allSchemas,
48
+ reqDefs,
47
49
  whatsMyServiceRootName,
48
- autoexposeSchemaName,
49
- options ] = initializeModel(csn, _options, messageFunctions);
50
+ fallBackSchemaName,
51
+ options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
50
52
 
51
- const Edm = require('./edm.js')(options, error);
53
+ const Edm = getEdm(options, messageFunctions);
52
54
 
53
55
  const v = options.v;
54
56
  if(Object.keys(allServices).length === 0) {
@@ -74,7 +76,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
74
76
  services[serviceCsn.name] = createEdm(serviceCsn);
75
77
  return services; }, rc);
76
78
  }
77
- throwWithError();
79
+ throwWithAnyError();
78
80
  return rc;
79
81
 
80
82
  //--------------------------------------------------------------------------------
@@ -189,9 +191,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
189
191
  fqSchemaXRef.sort((a,b) => b.length-a.length);
190
192
 
191
193
  // Bring the schemas in alphabetical order, service first, root last
192
- const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== autoexposeSchemaName && n !== serviceCsn.name).sort();
193
- if(subSchemaDictionary[autoexposeSchemaName])
194
- sortedSchemaNames.push(autoexposeSchemaName);
194
+ const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== fallBackSchemaName && n !== serviceCsn.name).sort();
195
+ if(subSchemaDictionary[fallBackSchemaName])
196
+ sortedSchemaNames.push(fallBackSchemaName);
195
197
 
196
198
  // Finally create the schemas and register them in the service.
197
199
  LeadSchema = createSchema(subSchemaDictionary[serviceCsn.name]);
@@ -221,7 +223,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
221
223
  service._children.forEach(c => {
222
224
  c._ec && Object.entries(c._ec._registry).forEach((([setName, arr]) => {
223
225
  if(arr.length > 1) {
224
- error(null, null, { name: c.Namespace, id: setName },
226
+ error(null, null, { name: c._edmAttributes.Namespace, id: setName },
225
227
  `Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for ${arr.map(a =>a.getDuplicateMessage()).join(', ')} `);
226
228
  }
227
229
  }));
@@ -232,7 +234,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
232
234
 
233
235
  // Sort definitions into their schema container
234
236
  function populateSchemas(schemas) {
235
- Object.entries(csn.definitions).forEach(([fqName, art]) => {
237
+ Object.entries(reqDefs.definitions).forEach(([fqName, art]) => {
236
238
  // Identify service members by their definition name only, this allows
237
239
  // to let the internal object.name have the sub-schema name.
238
240
  // With nested services we must do a longest path match and check whether
@@ -301,10 +303,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
301
303
 
302
304
  // Same check for alias (if supported by us)
303
305
  const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
304
- if(reservedNames.includes(schema.name)) {
305
- warning('odata-spec-violation-namespace',
306
- [ 'definitions', schema.name ], { names: reservedNames });
306
+ const loc = ['definitions', schema.name];
307
+ if(reservedNames.includes(schema.name))
308
+ warning('odata-spec-violation-namespace', loc, { names: reservedNames });
309
+ if (schema.name.length > 511)
310
+ warning('odata-spec-violation-namespace', loc, { '#': 'length' });
311
+ else {
312
+ schema.name.split('.').forEach(id => {
313
+ if (!edmUtils.isODataSimpleIdentifier(id))
314
+ message('odata-spec-violation-id', loc, { id });
315
+ });
307
316
  }
317
+
308
318
  /** @type {any} */
309
319
  const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
310
320
  const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
@@ -343,10 +353,11 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
343
353
 
344
354
  // fetch all exising children names in a map
345
355
  const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
346
- if(acc[cur.Name] === undefined) {
347
- acc[cur.Name] = [ cur ];
356
+ const name = cur._edmAttributes.Name;
357
+ if(acc[name] === undefined) {
358
+ acc[name] = [ cur ];
348
359
  } else {
349
- acc[cur.Name].push(cur);
360
+ acc[name].push(cur);
350
361
  }
351
362
  return acc;
352
363
  }, Object.create(null) );
@@ -368,12 +379,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
368
379
  }
369
380
  if(Schema._children.length === 0) {
370
381
  // FIXME: Location for sub schemas?
371
- warning(null, ['definitions', Schema.Namespace], { name: Schema.Namespace }, 'Schema $(NAME) is empty');
382
+ warning(null, ['definitions', Schema._edmAttributes.Namespace], { name: Schema._edmAttributes.Namespace }, 'Schema $(NAME) is empty');
372
383
  }
373
384
 
374
385
  Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {
375
386
  if(refs.length > 1) {
376
- error(null, ['definitions', `${Schema.Namespace}.${name}`], { name: Schema.Namespace },
387
+ error(null, ['definitions', `${Schema._edmAttributes.Namespace}.${name}`], { name: Schema._edmAttributes.Namespace },
377
388
  'Duplicate name in Schema $(NAME)');
378
389
  }
379
390
  });
@@ -389,21 +400,29 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
389
400
 
390
401
  const loc = ['definitions', entityCsn.name];
391
402
  const type = `${schema.name}.${EntityTypeName}`;
392
- if(properties.length === 0) {
403
+ if(properties.length === 0)
393
404
  warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
394
- } else if(entityCsn.$edmKeyPaths.length === 0) {
405
+ else if(entityCsn.$edmKeyPaths.length === 0)
395
406
  message('odata-spec-violation-no-key', loc);
396
- }
407
+
408
+ if(!edmUtils.isODataSimpleIdentifier(EntityTypeName))
409
+ message('odata-spec-violation-id', loc, { id: EntityTypeName });
410
+
397
411
  properties.forEach(p => {
398
- const pLoc = [...loc, 'elements', p.Name];
399
- if(!p[p._typeName]) {
400
- message('odata-spec-violation-type', pLoc);
401
- }
402
- if(p.Name === EntityTypeName) {
412
+ const pLoc = [...loc, 'elements', p._edmAttributes.Name];
413
+ edmTypeCompatibilityCheck(p, pLoc);
414
+ if(p._edmAttributes.Name === EntityTypeName)
403
415
  warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
404
- }
405
- if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
406
- warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
416
+
417
+ if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
418
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
419
+
420
+ if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
421
+ message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
422
+ else if (options.isV2() && /^(_|[0-9])/.test(p._edmAttributes.Name)) {
423
+ // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
424
+ message('odata-spec-violation-id', pLoc,
425
+ { prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
407
426
  }
408
427
  });
409
428
 
@@ -411,8 +430,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
411
430
  const attributes = { Name : EntityTypeName };
412
431
 
413
432
  // CDXCORE-CDXCORE-173
414
- if(options.isV2() && hasStream)
415
- attributes['m:HasStream'] = hasStream;
433
+ if(options.isV2() && hasStream) {
434
+ attributes['m:HasStream'] = true;
435
+ assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
436
+ }
416
437
 
417
438
  Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));
418
439
 
@@ -424,7 +445,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
424
445
  if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
425
446
  containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
426
447
  if(entityCsn['@odata.singleton.nullable'])
427
- containerEntry.Nullable= true;
448
+ containerEntry._edmAttributes.Nullable= true;
428
449
  }
429
450
  else {
430
451
  containerEntry = new Edm.EntitySet(v, { Name: EntitySetName, EntityType: fullQualified(EntityTypeName) }, entityCsn);
@@ -447,14 +468,19 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
447
468
  }
448
469
 
449
470
  // add bound/unbound actions/functions for V4
450
- function createActionV4(actionCsn, name, entityCsn=undefined)
471
+ function createActionV4(actionCsn, _name, entityCsn=undefined)
451
472
  {
452
473
  const iAmAnAction = actionCsn.kind === 'action';
453
-
454
474
  const actionName = edmUtils.getBaseName(actionCsn.name);
455
-
456
475
  const attributes = { Name: actionName, IsBound : false };
457
476
 
477
+ const loc = entityCsn
478
+ ? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
479
+ : [ 'definitions', actionCsn.name ];
480
+
481
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
482
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
483
+
458
484
  if(!iAmAnAction)
459
485
  attributes.IsComposable = false;
460
486
 
@@ -468,7 +494,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
468
494
 
469
495
  if(entityCsn != undefined)
470
496
  {
471
- actionNode.IsBound = true;
497
+ actionNode.setEdmAttribute('IsBound', true);
472
498
  const bpType = fullQualified(entityCsn.name);
473
499
  // Binding Parameter: 'in' at first position in sequence, this is decisive!
474
500
  if(actionCsn['@cds.odata.bindingparameter.collection'])
@@ -489,7 +515,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
489
515
  const definition = schemaCsn.definitions[rt];
490
516
  if(definition && definition.kind === 'entity' && !definition.abstract)
491
517
  {
492
- actionImport.EntitySet = edmUtils.getBaseName(rt);
518
+ actionImport.setEdmAttribute('EntitySet', edmUtils.getBaseName(rt));
493
519
  }
494
520
  }
495
521
  EntityContainer.register(actionImport);
@@ -497,15 +523,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
497
523
 
498
524
  // Parameter Nodes
499
525
  edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
500
- actionNode.append(new Edm.Parameter(v, { Name: parameterName }, parameterCsn ));
526
+ const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
527
+ const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
528
+ if(!edmUtils.isODataSimpleIdentifier(parameterName))
529
+ message('odata-spec-violation-id', pLoc, { id: parameterName });
530
+
531
+ edmTypeCompatibilityCheck(p, pLoc);
532
+ actionNode.append(p);
501
533
  });
502
534
 
503
535
  // return type if any
504
536
  if(actionCsn.returns) {
505
537
  actionNode._returnType = new Edm.ReturnType(v, actionCsn.returns);
538
+ edmTypeCompatibilityCheck(actionNode._returnType, [ ...loc, 'returns' ]);
506
539
  // if binding type matches return type add attribute EntitySetPath
507
540
  if(entityCsn != undefined && fullQualified(entityCsn.name) === actionNode._returnType._type) {
508
- actionNode.EntitySetPath = bpName;
541
+ actionNode.setEdmAttribute('EntitySetPath', bpName);
509
542
  }
510
543
  }
511
544
  Schema.addAction(actionNode);
@@ -515,7 +548,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
515
548
  function createActionV2(actionCsn, name, entityCsn=undefined)
516
549
  {
517
550
  /** @type {object} */
518
- const functionImport = new Edm.FunctionImport(v, { Name: name.replace(schemaNamePrefix, '') } );
551
+ const attributes = { Name: name.replace(schemaNamePrefix, '') };
552
+ const functionImport = new Edm.FunctionImport(v, attributes );
519
553
 
520
554
  // inserted now to maintain attribute order with old odata generator...
521
555
  /*
@@ -528,19 +562,25 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
528
562
  in the spec and advised mention it as in V4
529
563
  */
530
564
 
531
- const actLoc = ['definitions', ...(entityCsn ? [entityCsn.name, 'actions', actionCsn.name] : [actionCsn.name])];
565
+ const loc = entityCsn
566
+ ? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
567
+ : [ 'definitions', actionCsn.name ];
568
+
569
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
570
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
571
+
532
572
  const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
533
573
  if(rt) // add EntitySet attribute only if return type is an entity
534
574
  {
535
575
  const defintion = schemaCsn.definitions[rt];
536
576
  if(defintion && edmUtils.isEntity(defintion))
537
577
  {
538
- functionImport.EntitySet = rt.replace(schemaNamePrefix, '');
578
+ functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
539
579
  }
540
580
  }
541
581
 
542
582
  if(actionCsn.returns)
543
- functionImport.ReturnType = getReturnType(actionCsn);
583
+ functionImport.setEdmAttribute('ReturnType', getReturnType(actionCsn));
544
584
 
545
585
  if(actionCsn.kind === 'function')
546
586
  functionImport.setXml( {'m:HttpMethod': 'GET' });
@@ -551,7 +591,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
551
591
  {
552
592
  // Make bound function names always unique as per Ralf's recommendation
553
593
  functionImport.setXml( {'sap:action-for': fullQualified(entityCsn.name) } );
554
- functionImport.Name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport.Name;
594
+ const name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport._edmAttributes.Name;
595
+ functionImport.setEdmAttribute('Name', name);
555
596
 
556
597
  // Binding Parameter: Primary Keys at first position in sequence, this is decisive!
557
598
  // V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
@@ -573,14 +614,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
573
614
  // V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
574
615
  // the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
575
616
  edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
576
- const paramLoc = [...actLoc, 'params', parameterName];
617
+ const pLoc = [...loc, 'params', parameterName];
577
618
  const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
578
- if(param._type && !param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
579
- warning('odata-spec-violation-param', paramLoc, { api: 'OData V2' });
580
- }
581
- if(param._isCollection) {
582
- warning('odata-spec-violation-array', paramLoc, { api: 'OData V2' });
583
- }
619
+ edmTypeCompatibilityCheck(param, pLoc);
620
+ if(!edmUtils.isODataSimpleIdentifier(parameterName))
621
+ message('odata-spec-violation-id', pLoc, { id: parameterName });
622
+
623
+ // only scalar or structured type in V2 (not entity)
624
+ if(param._type &&
625
+ !param._type.startsWith('Edm.') &&
626
+ csn.definitions[param._type] &&
627
+ !edmUtils.isStructuredType(csn.definitions[param._type]))
628
+ warning('odata-spec-violation-param', pLoc, { version: '2.0' });
629
+
630
+ if(param._isCollection)
631
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
632
+
584
633
  functionImport.append(param);
585
634
  });
586
635
 
@@ -590,19 +639,33 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
590
639
  function getReturnType(action)
591
640
  {
592
641
  // it is safe to assume that either type or items.type are set
642
+ const returnsLoc = [ ...loc, 'returns'];
593
643
  const returns = action.returns.items || action.returns;
594
644
  let type = returns.type;
595
- if (type){
645
+ if (type) {
596
646
  if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
597
- const returnsLoc = [ ...actLoc, 'returns'];
598
- warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });
647
+ warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
648
+ }
649
+ else if(isBuiltinType(type)) {
650
+ type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
651
+ if(type) {
652
+ const td = EdmPrimitiveTypeMap[type];
653
+ if(td && !td.v2) {
654
+ message('odata-spec-violation-type', returnsLoc,
655
+ { type, version: '2.0', '#': 'incompatible' });
656
+ }
657
+ }
658
+ else {
659
+ message('odata-spec-violation-type-unknown', returnsLoc, { type });
660
+ }
599
661
  }
600
- type = edmUtils.mapCdsToEdmType(returns, messageFunctions, options.isV2());
662
+ if(action.returns._isCollection)
663
+ type = `Collection(${type})`
664
+ }
665
+ else {
666
+ // type is missing
667
+ message('odata-spec-violation-type', returnsLoc);
601
668
  }
602
-
603
- if(action.returns._isCollection)
604
- type = `Collection(${type})`
605
-
606
669
  return type;
607
670
  }
608
671
  }
@@ -610,14 +673,16 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
610
673
  /**
611
674
  * @param {object} elementsCsn
612
675
  * @param {object} edmParentCsn
613
- * @returns {[object[], boolean]} Returns a [ [ Edm Properties ], boolean hasStream ]:
676
+ * @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]:
614
677
  * array of Edm Properties
615
- * boolean hasStream : true if at least one element has @Core.MediaType assignment
678
+ * hasStream : value of @Core.MediaType assignment
616
679
  */
617
680
  function createProperties(elementsCsn, edmParentCsn=elementsCsn)
618
681
  {
619
682
  const props = [];
620
683
  let hasStream = false;
684
+ const streamProps = [];
685
+
621
686
  edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>
622
687
  {
623
688
  if(elementCsn._edmParentCsn == undefined)
@@ -653,12 +718,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
653
718
  // CDXCORE-CDXCORE-173
654
719
  // V2: filter @Core.MediaType
655
720
  if ( options.isV2() && elementCsn['@Core.MediaType']) {
721
+ hasStream = elementCsn['@Core.MediaType'];
722
+ delete elementCsn['@Core.MediaType'];
656
723
  // CDXCORE-CDXCORE-177:
657
724
  // V2: don't render element but add attribute 'm:HasStream="true' to EntityType
658
725
  // V4: render property type 'Edm.Stream'
659
- hasStream = true;
660
- info(null, ['definitions', elementsCsn.name], { name: elementsCsn.name, id: elementName, anno: '@Core.MediaType' },
661
- '$(NAME): Property $(ID) annotated with $(ANNO) is removed from EDM in OData V2');
726
+ streamProps.push(elementName);
662
727
 
663
728
  } else {
664
729
  props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
@@ -667,6 +732,16 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
667
732
  }
668
733
 
669
734
  });
735
+ if(options.isV2()) {
736
+ if(streamProps.length > 1) {
737
+ error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' },
738
+ `Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)`);
739
+ }
740
+ else if(streamProps.length === 1) {
741
+ info(null, ['definitions', elementsCsn.name], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' },
742
+ 'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)');
743
+ }
744
+ }
670
745
  return [ props, hasStream ];
671
746
  }
672
747
 
@@ -681,24 +756,27 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
681
756
  const loc = ['definitions', structuredTypeCsn.name];
682
757
 
683
758
  if(properties.length === 0) {
684
- warning(null, ['definitions', structuredTypeCsn.name], { name: structuredTypeCsn.name },
759
+ warning(null, loc, { name: structuredTypeCsn.name },
685
760
  'EDM ComplexType $(NAME) has no properties');
686
761
  }
762
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
763
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
764
+
687
765
  properties.forEach(p => {
688
- const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p.Name ];
689
- if(!p[p._typeName]) {
690
- message('odata-spec-violation-type', pLoc);
691
- }
692
- if(p.Name === complexType.Name) {
766
+ const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ];
767
+ edmTypeCompatibilityCheck(p, pLoc);
768
+ if(p._edmAttributes.Name === complexType._edmAttributes.Name)
693
769
  warning('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
694
- }
770
+
771
+ if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
772
+ message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
773
+
695
774
  if(options.isV2()) {
696
- if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
697
- warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
698
- }
699
- if(edmUtils.isAssociationOrComposition(p._csn)) {
700
- warning('odata-spec-violation-assoc', pLoc, { api: 'OData V2' });
701
- }
775
+ if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
776
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
777
+
778
+ if(edmUtils.isAssociationOrComposition(p._csn))
779
+ warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
702
780
  }
703
781
  });
704
782
 
@@ -712,8 +790,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
712
790
  function createTypeDefinition(typeCsn)
713
791
  {
714
792
  // derived types are already resolved to base types
715
- const props = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
716
- const typeDef = new Edm.TypeDefinition(v, props, typeCsn );
793
+ const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
794
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
795
+ message('odata-spec-violation-id', ['definitions', typeCsn.name], { id: attributes.Name });
796
+
797
+ const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn );
798
+ edmTypeCompatibilityCheck(typeDef, [ 'definitions', typeCsn.name ]);
717
799
  Schema.append(typeDef);
718
800
  }
719
801
 
@@ -735,7 +817,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
735
817
  {
736
818
  let constraints = navigationProperty._csn._constraints;
737
819
  let parentName = navigationProperty._csn._edmParentCsn.name.replace(schemaNamePrefix, '');
738
- let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
820
+ let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty._edmAttributes.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
739
821
  let assocName = plainAssocName;
740
822
  let i = 1;
741
823
  while(NamesInSchemaXRef[assocName] !== undefined) {
@@ -743,7 +825,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
743
825
  }
744
826
 
745
827
  let fromRole = parentName;
746
- let toRole = navigationProperty.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
828
+ let toRole = navigationProperty._edmAttributes.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
747
829
 
748
830
  let fromEntityType = fromRole;
749
831
  let toEntityType = toRole;
@@ -763,17 +845,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
763
845
  }
764
846
 
765
847
  // add V2 attributes to navigationProperty
766
- navigationProperty.Relationship = fullQualified(assocName);
767
- navigationProperty.FromRole = fromRole;
768
- navigationProperty.ToRole = toRole;
848
+ navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
849
+ navigationProperty.setEdmAttribute('FromRole', fromRole);
850
+ navigationProperty.setEdmAttribute('ToRole', toRole);
769
851
 
770
852
  // remove V4 attributes
771
- if(navigationProperty.Type != undefined)
772
- delete navigationProperty.Type;
773
- if(navigationProperty.Partner != undefined)
774
- delete navigationProperty.Partner;
775
- if(navigationProperty.ContainsTarget != undefined)
776
- delete navigationProperty.ContainsTarget;
853
+ navigationProperty.removeEdmAttribute('Type');
854
+ navigationProperty.removeEdmAttribute('Partner');
855
+ navigationProperty.removeEdmAttribute('ContainsTarget');
777
856
 
778
857
  /*
779
858
  If NavigationProperty is a backlink association (constraints._originAssocCsn is set), then there are two options:
@@ -799,7 +878,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
799
878
  assocName = plainAssocName + '_' + i++;
800
879
  }
801
880
 
802
- navigationProperty.Relationship = fullQualified(assocName)
881
+ navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
803
882
 
804
883
  reuseAssoc = !!forwardAssocCsn._NavigationProperty;
805
884
  constraints = forwardAssocCsn._constraints;
@@ -854,17 +933,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
854
933
 
855
934
  // generate the Edm.Annotations tree and append it to the corresponding schema
856
935
  function addAnnotations() {
857
- let { annos, usedVocabularies } = translate.csn2annotationEdm(csn, serviceCsn.name, Edm, options, messageFunctions);
936
+ let { annos, usedVocabularies } = translate.csn2annotationEdm(reqDefs, serviceCsn.name, Edm, options, messageFunctions);
858
937
  // distribute edm:Annotations into the schemas
859
938
  // Distribute each anno into Schema
860
939
  annos.forEach(anno => {
861
- let targetSchema = whatsMySchemaName(anno.Target);
940
+ let targetSchema = whatsMySchemaName(anno._edmAttributes.Target);
862
941
  // if no target schema has been found, it's a service annotation that applies to the service schema
863
942
  if(targetSchema === undefined)
864
943
  targetSchema = serviceCsn.name;
865
944
  if(targetSchema) {
866
945
  if(targetSchema !== serviceCsn.name) {
867
- anno.Target = anno.Target.replace(serviceCsn.name + '.', '');
946
+ const newTarget = anno._edmAttributes.Target.replace(serviceCsn.name + '.', '');
947
+ anno.setEdmAttribute('Target', newTarget);
868
948
  }
869
949
  edm._service._schemas[targetSchema]._annotations.push(anno);
870
950
  }
@@ -877,6 +957,44 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
877
957
  edm._defaultRefs.push(r);
878
958
  })
879
959
  }
960
+
961
+ function edmTypeCompatibilityCheck(p, pLoc) {
962
+ const edmType = p._type;
963
+ if(!edmType) {
964
+ message('odata-spec-violation-type', pLoc);
965
+ }
966
+ else if(p._scalarType) {
967
+ const td = EdmPrimitiveTypeMap[edmType];
968
+ if(td) {
969
+ // The renderer/type mapper doesn't/shouldn't produce incompatible types and facets.
970
+ // Only the unknown type warning may be triggered by an unknown @odata.Type override.
971
+ if(td.v2 !== p.v2 && td.v4 !== p.v4)
972
+ message('odata-spec-violation-type', pLoc,
973
+ { type:edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
974
+ EdmTypeFacetNames.forEach(name => {
975
+ const facet = EdmTypeFacetMap[name];
976
+ const optional =
977
+ (facet.optional !== undefined)
978
+ ? (Array.isArray(facet.optional)
979
+ ? facet.optional.includes(edmType)
980
+ : facet.optional)
981
+ : false;
982
+
983
+ // facet is not in attributes
984
+ // facet is member of type definition and mandatory
985
+ // node and facet version match
986
+ if(!p._edmAttributes[name] && td[name] && !optional && (p.v2 === facet.v2 || p.v4 === facet.v4)) {
987
+ message('odata-spec-violation-type', pLoc,
988
+ { type:edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
989
+ }
990
+ });
991
+ }
992
+ else {
993
+ message('odata-spec-violation-type-unknown', pLoc,
994
+ { type:edmType });
995
+ }
996
+ }
997
+ }
880
998
  }
881
999
  }
882
1000
  module.exports = { csn2edm, csn2edmAll };