@sap/cds 9.2.1 → 9.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +77 -1
  2. package/_i18n/i18n_es.properties +3 -3
  3. package/_i18n/i18n_es_MX.properties +3 -3
  4. package/_i18n/i18n_fr.properties +2 -2
  5. package/_i18n/messages.properties +6 -0
  6. package/app/index.js +0 -1
  7. package/bin/deploy.js +1 -1
  8. package/bin/serve.js +7 -20
  9. package/lib/compile/cdsc.js +3 -0
  10. package/lib/compile/for/flows.js +102 -0
  11. package/lib/compile/for/nodejs.js +28 -0
  12. package/lib/compile/to/edm.js +11 -4
  13. package/lib/core/classes.js +1 -1
  14. package/lib/core/linked-csn.js +8 -0
  15. package/lib/dbs/cds-deploy.js +12 -12
  16. package/lib/env/cds-env.js +1 -1
  17. package/lib/env/cds-requires.js +21 -20
  18. package/lib/env/defaults.js +2 -1
  19. package/lib/index.js +5 -6
  20. package/lib/log/cds-log.js +6 -5
  21. package/lib/log/format/aspects/cf.js +2 -2
  22. package/lib/plugins.js +1 -1
  23. package/lib/ql/cds-ql.js +0 -3
  24. package/lib/req/request.js +3 -3
  25. package/lib/req/response.js +12 -7
  26. package/lib/srv/bindings.js +17 -17
  27. package/lib/srv/cds-connect.js +6 -9
  28. package/lib/srv/cds-serve.js +74 -137
  29. package/lib/srv/cds.Service.js +49 -0
  30. package/lib/srv/factory.js +4 -4
  31. package/lib/srv/middlewares/auth/ias-auth.js +29 -9
  32. package/lib/srv/middlewares/auth/index.js +3 -2
  33. package/lib/srv/middlewares/auth/jwt-auth.js +19 -6
  34. package/lib/srv/protocols/hcql.js +16 -1
  35. package/lib/srv/srv-dispatch.js +1 -1
  36. package/lib/utils/cds-utils.js +4 -8
  37. package/lib/utils/csv-reader.js +27 -7
  38. package/libx/_runtime/cds.js +0 -6
  39. package/libx/_runtime/common/Service.js +5 -0
  40. package/libx/_runtime/common/generic/crud.js +1 -1
  41. package/libx/_runtime/common/generic/flows.js +106 -0
  42. package/libx/_runtime/common/generic/paging.js +3 -3
  43. package/libx/_runtime/common/utils/differ.js +5 -15
  44. package/libx/_runtime/common/utils/resolveView.js +2 -2
  45. package/libx/_runtime/fiori/lean-draft.js +76 -40
  46. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  47. package/libx/_runtime/remote/Service.js +68 -62
  48. package/libx/_runtime/remote/utils/client.js +29 -216
  49. package/libx/_runtime/remote/utils/query.js +197 -0
  50. package/libx/_runtime/ucl/Service.js +180 -112
  51. package/libx/_runtime/ucl/queries.js +61 -0
  52. package/libx/odata/ODataAdapter.js +1 -4
  53. package/libx/odata/index.js +2 -10
  54. package/libx/odata/middleware/error.js +8 -1
  55. package/libx/odata/middleware/stream.js +1 -1
  56. package/libx/odata/middleware/update.js +12 -2
  57. package/libx/odata/parse/afterburner.js +113 -20
  58. package/libx/odata/parse/cqn2odata.js +1 -3
  59. package/libx/rest/middleware/parse.js +9 -2
  60. package/package.json +2 -2
  61. package/server.js +2 -0
  62. package/srv/app-service.js +1 -0
  63. package/srv/db-service.js +1 -0
  64. package/srv/msg-service.js +1 -0
  65. package/srv/remote-service.js +1 -0
  66. package/srv/ucl-service.cds +32 -0
  67. package/srv/ucl-service.js +1 -0
  68. package/lib/ql/resolve.js +0 -45
  69. package/libx/common/assert/type-strict.js +0 -109
  70. package/libx/common/assert/utils.js +0 -60
@@ -8,6 +8,9 @@ const normalizeTimestamp = require('../../_runtime/common/utils/normalizeTimesta
8
8
  const { rewriteExpandAsterisk } = require('../../_runtime/common/utils/rewriteAsterisks')
9
9
  const resolveStructured = require('../../_runtime/common/utils/resolveStructured')
10
10
 
11
+ // Same regex as peggy parser
12
+ const RELAXED_UUID_REGEX = /^[0-9a-z]{8}-?[0-9a-z]{4}-?[0-9a-z]{4}-?[0-9a-z]{4}-?[0-9a-z]{12}$/i
13
+
11
14
  function _getDefinition(definition, name, namespace) {
12
15
  return (
13
16
  definition?.definitions?.[name] ||
@@ -185,6 +188,13 @@ function _convertVal(value, element) {
185
188
  case 'cds.Timestamp':
186
189
  return normalizeTimestamp(value)
187
190
 
191
+ case 'cds.UUID':
192
+ if (!RELAXED_UUID_REGEX.test(value)) {
193
+ const msg = `Element "${element.name}" does not contain a valid UUID`
194
+ throw Object.assign(new Error(msg), { statusCode: 400 })
195
+ }
196
+ return value
197
+
188
198
  default:
189
199
  return value
190
200
  }
@@ -528,6 +538,10 @@ function _processSegments(from, model, namespace, cqn, protocol) {
528
538
 
529
539
  const AGGR_DFLT = '@Aggregation.default'
530
540
  const CSTM_AGGR = '@Aggregation.CustomAggregate'
541
+ const SMTCS_CC = '@Semantics.currencyCode'
542
+ const SMTCS_UOM = '@Semantics.unitOfMeasure'
543
+ const SMTCS_AMT_CC = '@Semantics.amount.currencyCode'
544
+ const SMTCS_AMT_UOM = '@Semantics.amount.unitOfMeasure'
531
545
 
532
546
  function _addKeys(columns, target) {
533
547
  let hasAggregatedColumn = false,
@@ -586,17 +600,20 @@ const _structProperty = (ref, target) => {
586
600
  }
587
601
 
588
602
  function _processColumns(cqn, target, protocol) {
603
+ // Recursively process columns for nested SELECTs
589
604
  if (cqn.SELECT.from.SELECT) _processColumns(cqn.SELECT.from, target)
590
605
 
591
606
  let columns = cqn.SELECT.columns
592
607
 
608
+ // Error if groupBy is present but no columns are selected
593
609
  if (columns && !columns.length && cqn.SELECT.groupBy) {
594
- cds.error('Explicit select must include at least one column available in the result set of groupby!', {
610
+ throw cds.error('Explicit select must include at least one column available in the result set of groupby', {
595
611
  code: '400',
596
612
  statusCode: 400
597
613
  })
598
614
  }
599
615
 
616
+ // If columns exist and no groupBy, handle asterisk and expand logic
600
617
  if (columns && !cqn.SELECT.groupBy) {
601
618
  let entity
602
619
  if (target.kind === 'entity') entity = target
@@ -606,32 +623,108 @@ function _processColumns(cqn, target, protocol) {
606
623
  _removeUnneededColumnsIfHasAsterisk(columns)
607
624
  rewriteExpandAsterisk(columns, entity)
608
625
 
609
- // in case of odata, add all missing key fields (i.e., not in $select)
626
+ // For OData, add missing key fields to columns
610
627
  if (protocol?.match(/odata/i)) _addKeys(columns, entity)
611
628
  }
612
629
 
613
630
  if (!Array.isArray(columns)) return
614
631
 
615
- let aggrProp, aggrElem, defaultAggregation
632
+ // Iterate columns to set default aggregation function if needed
616
633
  for (let i = 0; i < columns.length; i++) {
617
- if (
618
- columns[i].func === null &&
619
- columns[i].args &&
620
- columns[i].args.length &&
621
- columns[i].args[0].ref &&
622
- columns[i].args[0].ref.length
623
- ) {
624
- // REVISIT: also support aggregate(Sales/Amount)?
625
- aggrProp = columns[i].args[0].ref[0]
626
- aggrElem = target.elements[aggrProp]
627
- if (aggrElem && target[`${CSTM_AGGR}#${aggrProp}`] && aggrElem[AGGR_DFLT] && aggrElem[AGGR_DFLT]['#']) {
628
- defaultAggregation = aggrElem[AGGR_DFLT]['#'].toLowerCase()
629
- if (defaultAggregation === 'count_distinct') defaultAggregation = 'countdistinct'
630
- columns[i].func = defaultAggregation
631
- columns[i].as = columns[i].as || aggrProp
632
- } else {
633
- throw new Error(`Default aggregation for property "${aggrProp}" not found`)
634
+ const processedColumn = columns[i]
635
+
636
+ // Skip if column is not an object or has a ref (not an aggregation)
637
+ if (typeof processedColumn !== 'object' || processedColumn.ref) continue
638
+
639
+ if (!processedColumn.args?.length) continue
640
+ if (!processedColumn.args[0].ref?.length) continue
641
+ const processedColumnRef = processedColumn.args[0].ref
642
+
643
+ // Extract relevant element characteristics
644
+ let aggregatedPropertyName, aggregatedElement
645
+ for (let refIdx = 0; refIdx < processedColumnRef.length; refIdx++) {
646
+ aggregatedPropertyName = processedColumnRef[refIdx]
647
+ if (aggregatedPropertyName.id) aggregatedPropertyName = aggregatedPropertyName.id
648
+ if (aggregatedElement && aggregatedElement.isAssociation) target = aggregatedElement._target
649
+ aggregatedElement = target.elements[aggregatedPropertyName]
650
+ }
651
+ const prefixRef = processedColumnRef.slice(0, processedColumnRef.length - 1)
652
+ const aggregatedElementRef = [...prefixRef, aggregatedPropertyName]
653
+ const isCurrencyCodeOrUnitOfMeasure = !!(aggregatedElement[SMTCS_CC] || aggregatedElement[SMTCS_UOM])
654
+ processedColumn.as = processedColumn.as || aggregatedPropertyName
655
+
656
+ // Specifically handle aggregating semantic amounts
657
+ if (isCurrencyCodeOrUnitOfMeasure) {
658
+ columns[i] = {
659
+ xpr: [
660
+ 'case',
661
+ 'when',
662
+ { xpr: [{ func: 'max', args: [{ ref: aggregatedElementRef }] }, '=', { val: null }] },
663
+ 'then',
664
+ { val: '' },
665
+ 'else',
666
+ {
667
+ xpr: [
668
+ 'case',
669
+ 'when',
670
+ {
671
+ xpr: [
672
+ { func: 'min', args: [{ ref: aggregatedElementRef }] },
673
+ '=',
674
+ { func: 'max', args: [{ ref: aggregatedElementRef }] }
675
+ ]
676
+ },
677
+ 'then',
678
+ { func: 'min', args: [{ ref: aggregatedElementRef }] },
679
+ 'else',
680
+ { val: null },
681
+ 'end'
682
+ ]
683
+ },
684
+ 'end'
685
+ ],
686
+ as: processedColumn.as
634
687
  }
688
+
689
+ continue
690
+ }
691
+
692
+ // Determine default aggregation function if necessary
693
+ const customAggregate = target[`${CSTM_AGGR}#${aggregatedPropertyName}`]
694
+ if (processedColumn.func === null) {
695
+ processedColumn.func = aggregatedElement?.[AGGR_DFLT]?.['#']?.toLowerCase()
696
+ if (!customAggregate)
697
+ throw cds.error(`Result type for custom aggregation of property "${aggregatedPropertyName}" not found`)
698
+ if (!processedColumn.func)
699
+ throw cds.error(`Default aggregation for property "${aggregatedPropertyName}" not found`)
700
+ if (processedColumn.func === 'count_distinct') processedColumn.func = 'countdistinct'
701
+ }
702
+
703
+ // Process Semantics Amount - Currency Code / Unit of Measure - if present
704
+ const semanticsAmountElementName = aggregatedElement?.[SMTCS_AMT_CC] ?? aggregatedElement?.[SMTCS_AMT_UOM]
705
+ if (!semanticsAmountElementName) continue
706
+ if (!target.elements[semanticsAmountElementName])
707
+ throw cds.error(`Referenced semantics amount element not found: ${semanticsAmountElementName}`)
708
+ const semanticsAmountElementRef = [...prefixRef, semanticsAmountElementName]
709
+
710
+ columns[i] = {
711
+ xpr: [
712
+ 'case',
713
+ 'when',
714
+ {
715
+ xpr: [
716
+ { func: 'min', args: [{ ref: semanticsAmountElementRef }] },
717
+ '=',
718
+ { func: 'max', args: [{ ref: semanticsAmountElementRef }] }
719
+ ]
720
+ },
721
+ 'then',
722
+ { func: processedColumn.func, args: [{ ref: aggregatedElementRef }] },
723
+ 'else',
724
+ { val: null },
725
+ 'end'
726
+ ],
727
+ as: processedColumn.as
635
728
  }
636
729
  }
637
730
  }
@@ -598,7 +598,7 @@ const _delete = (cqn, kind, model) => {
598
598
  return { method: 'DELETE', path: `${url}${keys}` }
599
599
  }
600
600
 
601
- function cqn2odata(cqn, { kind, model, method }) {
601
+ module.exports.cqn2odata = (cqn, { kind, model, method }) => {
602
602
  if (cqn.SELECT) return _select(cqn, kind, model)
603
603
  if (cqn.INSERT) return _insert(cqn, kind, model)
604
604
  if (cqn.UPDATE) return _update(cqn, kind, model, method)
@@ -606,5 +606,3 @@ function cqn2odata(cqn, { kind, model, method }) {
606
606
 
607
607
  throw new Error('Unknown CQN object cannot be translated to URL: ' + JSON.stringify(cqn))
608
608
  }
609
-
610
- module.exports = cqn2odata
@@ -23,7 +23,7 @@ exports.parse = function (req, res, next) {
23
23
  if (typeof definition === 'string') {
24
24
  definition =
25
25
  service.model.definitions[definition] ||
26
- service.model.definitions[definition.split(':$:')[0]].actions[definition.split(':$:')[1]]
26
+ service.model.definitions[definition.split(':$:')[0]].actions?.[definition.split(':$:')[1]]
27
27
  }
28
28
  delete query.__target
29
29
 
@@ -79,7 +79,14 @@ exports.parse = function (req, res, next) {
79
79
  }
80
80
  cds.error(errorMsg, { code: 400 })
81
81
  }
82
- if (!one) throw { statusCode: 400, code: '400', message: `INVALID_${req.method}` }
82
+ if (!one) {
83
+ if (req.method === 'PATCH') throw cds.error ({ status: 405, code: `INVALID_${req.method}` })
84
+ const entity = definition, keys = {}, data = req.body || {}
85
+ for (let k in entity.keys) keys[k] = data[k]
86
+ || entity.keys[k]['@cds.on.insert']?.['='] === '$user' && cds.context?.user?.id
87
+ || cds.error ({ status: 405, code: `INVALID_${req.method}` })
88
+ from.ref[0] = { id: from.ref[0], where: cds.ql.predicate (keys) }
89
+ }
83
90
  query = UPDATE(_target)
84
91
  break
85
92
  case 'DELETE':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "9.2.1",
3
+ "version": "9.3.0",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -32,7 +32,7 @@
32
32
  "node": ">=20"
33
33
  },
34
34
  "dependencies": {
35
- "@sap/cds-compiler": "^6",
35
+ "@sap/cds-compiler": "^6.1",
36
36
  "@sap/cds-fiori": "^2",
37
37
  "js-yaml": "^4.1.0"
38
38
  },
package/server.js CHANGED
@@ -36,6 +36,8 @@ module.exports = async function cds_server (options) {
36
36
  }
37
37
 
38
38
  const app = cds.app = o.app || express()
39
+ // disable x-powered-by on express instance created by us
40
+ if (process.env.NODE_ENV === 'production' && !o.app) app.disable('x-powered-by')
39
41
  cds.emit ('bootstrap', app)
40
42
 
41
43
  // mount static resources and cors middleware
@@ -0,0 +1 @@
1
+ module.exports = require('../libx/_runtime/common/Service')
@@ -0,0 +1 @@
1
+ module.exports = require('@cap-js/db-service').DatabaseService
@@ -0,0 +1 @@
1
+ module.exports = require('../libx/_runtime/messaging/service')
@@ -0,0 +1 @@
1
+ module.exports = require('../libx/_runtime/remote/Service')
@@ -0,0 +1,32 @@
1
+ @rest
2
+ @path : '/ucl/spii/v1'
3
+ @requires: 'any'
4
+ @impl : '@sap/cds/srv/ucl-service.js'
5
+ service UCLService {
6
+ @open
7
+ @cds.persistence.skip
8
+ entity tenantMappings {
9
+ key tenantId : String;
10
+ context : Map;
11
+ receiverTenant : Map;
12
+ assignedTenant : Map;
13
+ }
14
+
15
+ /*
16
+ action assign(tenantId: String, context: Map, receiverTenant: Map, assignedTenant: Map) returns {
17
+ state : String enum {
18
+ CONFIG_PENDING = 'CONFIG_PENDING';
19
+ READY = 'READY';
20
+ };
21
+ configuration : Map;
22
+ };
23
+ */
24
+
25
+ /*
26
+ action unassign(tenantId: String, context: Map, receiverTenant: Map, assignedTenant: Map) returns {
27
+ state : String enum {
28
+ READY = 'READY';
29
+ }
30
+ };
31
+ */
32
+ }
@@ -0,0 +1 @@
1
+ module.exports = require('../libx/_runtime/ucl/Service')
package/lib/ql/resolve.js DELETED
@@ -1,45 +0,0 @@
1
- const { resolveView, getTransition } = require('../../libx/_runtime/common/utils/resolveView')
2
- const cds = require('..')
3
-
4
- const PERSISTENCE_TABLE = '@cds.persistence.table'
5
- const _isPersistenceTable = target =>
6
- Object.prototype.hasOwnProperty.call(target, PERSISTENCE_TABLE) && target[PERSISTENCE_TABLE]
7
-
8
- // REVISIT revert after cds-dbs pr
9
- // REVISIT: Remove once we get rid of old db
10
- const _abortDB = resolve.abortDB = target => !!(_isPersistenceTable(target)|| !target.query?._target)
11
- // _service seems to be inherited in projections, so do not consider prototype chain
12
- const _defaultAbort = tx => e => e._service?.name === tx.definition?.name
13
-
14
- function resolve(query, tx, abortCondition) {
15
- const ctx = cds.context
16
- const abort = abortCondition ?? (typeof tx === 'function' ? tx : undefined)
17
- const _tx = typeof tx === 'function' ? ctx?.tx : tx
18
- const model = ctx?.model ?? _tx.model
19
-
20
- return resolveView(query, model, _tx, abort || _defaultAbort(_tx))
21
- }
22
-
23
- resolve.resolve4db = (query, tx) => resolve(query, tx, _abortDB)
24
-
25
- // REVISIT: Remove once we get rid of composition tree
26
- resolve.table = target => {
27
- if (target.query?._target && !_isPersistenceTable(target)) {
28
- return resolve.table(target.query._target)
29
- }
30
- return target
31
- }
32
-
33
- // REVISIT: Remove argument `skipForbiddenViewCheck` once we get rid of composition tree
34
- resolve.transitions = (query, tx, abortCondition, skipForbiddenViewCheck) => {
35
- const target = query && typeof query === 'object' ? cds.infer.target(query) || query?._target : undefined
36
- const abort = abortCondition ?? (typeof tx === 'function' ? tx : undefined)
37
- const _tx = typeof tx === 'function' ? cds.context?.tx : tx
38
- return getTransition(target, _tx, skipForbiddenViewCheck, undefined, {
39
- abort: abort ?? (tx.isDatabaseService ? _abortDB : _defaultAbort(tx))
40
- })
41
- }
42
-
43
- resolve.transitions4db = (query, tx, skipForbiddenViewCheck) => resolve.transitions(query, tx, _abortDB, skipForbiddenViewCheck)
44
-
45
- module.exports = resolve
@@ -1,109 +0,0 @@
1
- const { cds } = global
2
-
3
- const { Readable } = require('stream')
4
-
5
- const { getNormalizedDecimal, getTarget, isBase64String } = require('./utils')
6
-
7
- const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i //> "i" is acutally not OK, but we'll leave as is for now to avoid breaking changes
8
- const RELAXED_UUID_REGEX = /^[0-9a-z]{8}-?[0-9a-z]{4}-?[0-9a-z]{4}-?[0-9a-z]{4}-?[0-9a-z]{12}$/i
9
-
10
- const ISO_DATE_PART1 =
11
- '[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)'
12
- const ISO_DATE_PART2 = '(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29'
13
- const ISO_DATE = `(?:${ISO_DATE_PART1}|${ISO_DATE_PART2})`
14
- const ISO_TIME_NO_MILLIS = '(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d'
15
- const ISO_TIME = `${ISO_TIME_NO_MILLIS}(?:\\.\\d{1,9})?`
16
- const ISO_DATE_TIME = `${ISO_DATE}T${ISO_TIME_NO_MILLIS}(?:Z|[+-][01]\\d:?[0-5]\\d)`
17
- const ISO_TIMESTAMP = `${ISO_DATE}T${ISO_TIME}(?:Z|[+-][01]\\d:?[0-5]\\d)`
18
- const ISO_DATE_REGEX = new RegExp(`^${ISO_DATE}$`, 'i')
19
- const ISO_TIME_NO_MILLIS_REGEX = new RegExp(`^${ISO_TIME_NO_MILLIS}$`, 'i')
20
- const ISO_DATE_TIME_REGEX = new RegExp(`^${ISO_DATE_TIME}$`, 'i')
21
- const ISO_TIMESTAMP_REGEX = new RegExp(`^${ISO_TIMESTAMP}$`, 'i')
22
-
23
- const _checkString = value => typeof value === 'string'
24
-
25
- const _checkNumber = value => typeof value === 'number' && !Number.isNaN(value)
26
-
27
- const _oldCheckDecimal = (value, element) => {
28
- const [left, right] = String(value).split('.')
29
- return (
30
- _checkNumber(value) &&
31
- (!element.precision || left.length <= element.precision - (element.scale || 0)) &&
32
- (!element.scale || ((right || '').length <= element.scale && parseFloat(right) !== 0))
33
- )
34
- }
35
-
36
- // REVISIT: only use a cheaper check if not in strictDecimal mode?
37
- const _checkDecimal = (v, ele, errs, path, k) => {
38
- if (!errs) return _oldCheckDecimal(v, ele)
39
-
40
- const { precision, scale } = ele
41
- let val = getNormalizedDecimal(v)
42
- if (precision != null && scale != null) {
43
- let isValid = true
44
- if (!val.match(/\./)) val += '.0'
45
- if (precision === scale) {
46
- if (!val.match(new RegExp(`^-?0\\.\\d{0,${scale}}$`, 'g'))) isValid = false
47
- } else if (scale === 0) {
48
- if (!val.match(new RegExp(`^-?\\d{1,${precision - scale}}\\.0{0,1}$`, 'g'))) isValid = false
49
- } else if (!val.match(new RegExp(`^-?\\d{1,${precision - scale}}\\.\\d{0,${scale}}$`, 'g'))) {
50
- isValid = false
51
- }
52
- if (!isValid) {
53
- const args = [v, `Decimal(${precision},${scale})`]
54
- const target = getTarget(path, k)
55
- errs.push(new cds.error('ASSERT_DATA_TYPE', { args, target, statusCode: 400, code: '400' }))
56
- }
57
- } else if (precision != null) {
58
- if (!val.match(new RegExp(`^-?\\d{1,${precision}}$`, 'g'))) {
59
- const args = [v, `Decimal(${precision})`]
60
- const target = getTarget(path, k)
61
- errs.push(new cds.error('ASSERT_DATA_TYPE', { args, target, statusCode: 400, code: '400' }))
62
- }
63
- }
64
- }
65
-
66
- const _checkInt = value => _checkNumber(value) && parseInt(value, 10) === value
67
-
68
- const _checkInt64 = value => typeof value === 'string' ? value.match(/^\d+$/) : _checkInt(value)
69
-
70
- const _checkBoolean = value => typeof value === 'boolean'
71
-
72
- const _checkBuffer = value => Buffer.isBuffer(value) || value.type === 'Buffer' || isBase64String(value)
73
-
74
- const _checkStreamOrBuffer = value => value instanceof Readable || _checkBuffer(value)
75
-
76
- const _checkUUID = value => _checkString(value) && UUID_REGEX.test(value)
77
-
78
- const _checkRelaxedUUID = value => _checkString(value) && RELAXED_UUID_REGEX.test(value)
79
-
80
- const _checkISODate = value => (_checkString(value) && ISO_DATE_REGEX.test(value)) || value instanceof Date
81
-
82
- const _checkISOTime = value => _checkString(value) && ISO_TIME_NO_MILLIS_REGEX.test(value)
83
-
84
- const _checkISODateTime = value => (_checkString(value) && ISO_DATE_TIME_REGEX.test(value)) || value instanceof Date
85
-
86
- const _checkISOTimestamp = value => (_checkString(value) && ISO_TIMESTAMP_REGEX.test(value)) || value instanceof Date
87
-
88
- module.exports = {
89
- 'cds.UUID': _checkUUID,
90
- 'relaxed.UUID': _checkRelaxedUUID,
91
- 'cds.Boolean': _checkBoolean,
92
- 'cds.Integer': _checkInt,
93
- 'cds.UInt8': _checkInt,
94
- 'cds.Int16': _checkInt,
95
- 'cds.Int32': _checkInt,
96
- 'cds.Integer64': _checkInt64,
97
- 'cds.Int64': _checkInt64,
98
- 'cds.Decimal': _checkDecimal,
99
- 'cds.DecimalFloat': _checkNumber,
100
- 'cds.Double': _checkNumber,
101
- 'cds.Date': _checkISODate,
102
- 'cds.Time': _checkISOTime,
103
- 'cds.DateTime': _checkISODateTime,
104
- 'cds.Timestamp': _checkISOTimestamp,
105
- 'cds.String': _checkString,
106
- 'cds.Binary': _checkBuffer,
107
- 'cds.LargeString': _checkString,
108
- 'cds.LargeBinary': _checkStreamOrBuffer
109
- }
@@ -1,60 +0,0 @@
1
- const getNormalizedDecimal = val => {
2
- let v = `${val}`
3
- const cgs = v.match(/^(\d*\.*\d*)e([+|-]*)(\d*)$/)
4
- if (cgs) {
5
- let [l, r = ''] = cgs[1].split('.')
6
- const dir = cgs[2] || '+'
7
- const exp = Number(cgs[3])
8
- if (dir === '+') {
9
- // move decimal point to the right
10
- r = r.padEnd(exp, '0')
11
- l += r.substring(0, exp)
12
- r = r.slice(exp)
13
- v = `${l}${r ? '.' + r : ''}`
14
- } else {
15
- // move decimal point to the left
16
- l = l.padStart(exp, '0')
17
- r = l.substring(0, exp) + r
18
- l = l.slice(exp)
19
- v = `${l ? l : '0'}.${r}`
20
- }
21
- }
22
- return v
23
- }
24
-
25
- function getTarget(path, k) {
26
- return path.length && path[path.length - 1].match(/\[\d+\]$/) ? path.join('/') : path.concat(k).join('/')
27
- }
28
-
29
- // non-strict mode also allows url-safe base64 strings
30
- function isBase64String(string, strict = false) {
31
- if (typeof string !== 'string') return false
32
-
33
- if (strict && string.length % 4 !== 0) return false
34
-
35
- let length = string.length
36
- if (string.endsWith('==')) length -= 2
37
- else if (string.endsWith('=')) length -= 1
38
-
39
- let char
40
- for (let i = 0; i < length; i++) {
41
- char = string[i]
42
- if (char >= 'A' && char <= 'Z') continue
43
- else if (char >= 'a' && char <= 'z') continue
44
- else if (char >= '0' && char <= '9') continue
45
- else if (char === '+' || char === '/') continue
46
- else if (!strict && (char === '-' || char === '_')) continue
47
- return false
48
- }
49
-
50
- return true
51
- }
52
-
53
-
54
-
55
-
56
- module.exports = {
57
- getNormalizedDecimal,
58
- getTarget,
59
- isBase64String
60
- }