@sap/cds 5.5.5 → 5.6.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 +107 -1
- package/apis/services.d.ts +27 -1
- package/app/index.js +22 -11
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +1 -1
- package/bin/build/provider/fiori/index.js +1 -1
- package/bin/build/provider/hana/2migration.js +8 -7
- package/bin/build/provider/java-cf/index.js +1 -1
- package/bin/deploy/to-hana/hana.js +1 -17
- package/common.cds +8 -0
- package/lib/compile/to/sql.js +22 -2
- package/lib/connect/bindings.js +2 -1
- package/lib/core/reflect.js +3 -1
- package/lib/env/index.js +175 -41
- package/lib/env/requires.js +16 -1
- package/lib/i18n/localize.js +31 -4
- package/lib/index.js +3 -3
- package/lib/log/format/kibana.js +6 -2
- package/lib/ql/Query.js +1 -0
- package/lib/ql/SELECT.js +15 -8
- package/lib/ql/Whereable.js +5 -0
- package/lib/req/context.js +13 -5
- package/lib/serve/Service-dispatch.js +8 -1
- package/lib/utils/axios.js +7 -0
- package/lib/utils/data.js +1 -1
- package/lib/utils/tests.js +1 -1
- package/libx/_runtime/audit/Service.js +18 -18
- package/libx/_runtime/audit/generic/personal/access.js +1 -1
- package/libx/_runtime/audit/generic/personal/modification.js +3 -2
- package/libx/_runtime/audit/generic/personal/utils.js +23 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
- package/libx/_runtime/cds-services/util/assert.js +29 -13
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/Association.js +72 -0
- package/libx/_runtime/common/aspects/any.js +8 -45
- package/libx/_runtime/common/aspects/entity.js +0 -1
- package/libx/_runtime/common/aspects/relation.js +40 -0
- package/libx/_runtime/common/aspects/utils.js +73 -1
- package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
- package/libx/_runtime/common/composition/data.js +3 -2
- package/libx/_runtime/common/composition/delete.js +3 -1
- package/libx/_runtime/common/composition/tree.js +23 -18
- package/libx/_runtime/common/composition/utils.js +34 -8
- package/libx/_runtime/common/error/frontend.js +6 -1
- package/libx/_runtime/common/generic/auth.js +5 -9
- package/libx/_runtime/common/generic/crud.js +2 -2
- package/libx/_runtime/common/generic/etag.js +11 -8
- package/libx/_runtime/common/generic/input.js +3 -3
- package/libx/_runtime/common/generic/paging.js +9 -5
- package/libx/_runtime/common/generic/put.js +3 -2
- package/libx/_runtime/common/generic/sorting.js +3 -3
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/utils/cqn.js +20 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
- package/libx/_runtime/common/utils/csn.js +50 -52
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
- package/libx/_runtime/common/utils/generateOnCond.js +40 -70
- package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
- package/libx/_runtime/common/utils/postProcessing.js +3 -0
- package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
- package/libx/_runtime/common/utils/resolveStructured.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
- package/libx/_runtime/common/utils/template.js +54 -46
- package/libx/_runtime/db/Service.js +9 -2
- package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
- package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
- package/libx/_runtime/db/generic/create.js +1 -0
- package/libx/_runtime/db/generic/input.js +7 -11
- package/libx/_runtime/db/generic/integrity.js +2 -2
- package/libx/_runtime/db/generic/rewrite.js +2 -5
- package/libx/_runtime/db/generic/update.js +1 -0
- package/libx/_runtime/db/query/read.js +9 -4
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
- package/libx/_runtime/db/sql-builder/annotations.js +1 -0
- package/libx/_runtime/db/utils/columns.js +14 -43
- package/libx/_runtime/fiori/generic/activate.js +3 -2
- package/libx/_runtime/fiori/generic/before.js +2 -2
- package/libx/_runtime/fiori/generic/cancel.js +3 -2
- package/libx/_runtime/fiori/generic/delete.js +3 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +2 -2
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +17 -63
- package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
- package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
- package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
- package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
- package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
- package/libx/_runtime/fiori/uiflex/index.js +35 -0
- package/libx/_runtime/fiori/uiflex/utils.js +78 -0
- package/libx/_runtime/fiori/utils/handler.js +3 -13
- package/libx/_runtime/fiori/utils/where.js +6 -1
- package/libx/_runtime/hana/pool.js +12 -11
- package/libx/_runtime/hana/search2cqn4sql.js +34 -43
- package/libx/_runtime/hana/searchToContains.js +3 -3
- package/libx/_runtime/index.js +5 -2
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
- package/libx/_runtime/messaging/common-utils/connections.js +11 -14
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
- package/libx/_runtime/messaging/message-queuing.js +18 -0
- package/libx/_runtime/remote/Service.js +14 -2
- package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
- package/libx/_runtime/remote/utils/client.js +117 -23
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
- package/libx/gql/GraphQLAdapter.js +33 -0
- package/libx/gql/constants/adapter.js +69 -0
- package/libx/gql/constants/cds.js +18 -0
- package/libx/gql/constants/graphql.js +33 -0
- package/libx/gql/resolvers/crud/create.js +15 -0
- package/libx/gql/resolvers/crud/delete.js +24 -0
- package/libx/gql/resolvers/crud/index.js +6 -0
- package/libx/gql/resolvers/crud/read.js +25 -0
- package/libx/gql/resolvers/crud/update.js +31 -0
- package/libx/gql/resolvers/crud/utils/index.js +36 -0
- package/libx/gql/resolvers/field.js +5 -0
- package/libx/gql/resolvers/index.js +7 -0
- package/libx/gql/resolvers/mutation.js +23 -0
- package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
- package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
- package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
- package/libx/gql/resolvers/parse/ast/index.js +3 -0
- package/libx/gql/resolvers/parse/ast/meta.js +4 -0
- package/libx/gql/resolvers/parse/ast/variable.js +7 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
- package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
- package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
- package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
- package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
- package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
- package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
- package/libx/gql/resolvers/parse/utils/index.js +8 -0
- package/libx/gql/resolvers/query.js +13 -0
- package/libx/gql/resolvers/root.js +34 -0
- package/libx/gql/schema/generate.js +18 -0
- package/libx/gql/schema/index.js +5 -0
- package/libx/gql/schema/mutation.js +76 -0
- package/libx/gql/schema/query.js +108 -0
- package/libx/gql/schema/typeDefMap.js +45 -0
- package/libx/gql/schema/utils/index.js +54 -0
- package/libx/gql/utils/index.js +12 -0
- package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
- package/libx/odata/index.js +80 -0
- package/libx/odata/odata2cqn/afterburner.js +170 -0
- package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
- package/libx/odata/odata2cqn/index.js +3 -0
- package/libx/odata/odata2cqn/parser.js +1 -0
- package/libx/odata/utils/index.js +64 -0
- package/libx/rest/RestAdapter.js +101 -0
- package/libx/rest/RestRequest.js +30 -0
- package/libx/rest/index.js +3 -0
- package/libx/rest/middleware/auth.js +22 -0
- package/libx/rest/middleware/content.js +15 -0
- package/libx/rest/middleware/create.js +40 -0
- package/libx/rest/middleware/delete.js +20 -0
- package/libx/rest/middleware/error.js +56 -0
- package/libx/rest/middleware/operation.js +39 -0
- package/libx/rest/middleware/parse.js +90 -0
- package/libx/rest/middleware/read.js +29 -0
- package/libx/rest/middleware/update.js +42 -0
- package/libx/rest/utils/data.js +65 -0
- package/package.json +4 -1
- package/server.js +20 -2
- package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
- package/libx/_runtime/cds-services/util/auditlog.js +0 -247
- package/libx/_runtime/cds-services/util/xsenv.js +0 -51
- package/libx/_runtime/common/utils/backlinks.js +0 -83
- package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
- package/libx/_runtime/odata/index.js +0 -55
- package/libx/_runtime/odata/odata2cqn.js +0 -1
- package/libx/_runtime/odata/readToCqn.js +0 -129
- package/libx/_runtime/remote/cqn2odata/index.js +0 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { gqlName } = require('../utils')
|
|
2
|
+
const resolveQuery = require('./query')
|
|
3
|
+
const resolveMutation = require('./mutation')
|
|
4
|
+
const { enrichAST } = require('./parse/ast')
|
|
5
|
+
|
|
6
|
+
const wrapResolver = (service, resolver) => (root, args, context, info) => {
|
|
7
|
+
const response = {}
|
|
8
|
+
|
|
9
|
+
const enrichedFieldNodes = enrichAST(info)
|
|
10
|
+
|
|
11
|
+
for (const fieldNode of enrichedFieldNodes) {
|
|
12
|
+
for (const field of fieldNode.selectionSet.selections) {
|
|
13
|
+
const gqlName = field.name.value
|
|
14
|
+
const responseKey = field.alias ? field.alias.value : gqlName
|
|
15
|
+
|
|
16
|
+
response[responseKey] = resolver(service, gqlName, field)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return response
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = services => {
|
|
24
|
+
const Query = {}
|
|
25
|
+
const Mutation = {}
|
|
26
|
+
|
|
27
|
+
for (const service of services) {
|
|
28
|
+
const gqlServiceName = gqlName(service.name)
|
|
29
|
+
Query[gqlServiceName] = wrapResolver(service, resolveQuery)
|
|
30
|
+
Mutation[gqlServiceName] = wrapResolver(service, resolveMutation)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { Query, Mutation }
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { typeDefMapToMutationStringArray } = require('./mutation')
|
|
2
|
+
const { typeDefMapToQueryStringArray } = require('./query')
|
|
3
|
+
const { servicesToTypeDefMap } = require('./typeDefMap')
|
|
4
|
+
|
|
5
|
+
const typeDefMapToSchemaString = typeDefs => {
|
|
6
|
+
let schema = []
|
|
7
|
+
|
|
8
|
+
schema.push(...typeDefMapToQueryStringArray(typeDefs))
|
|
9
|
+
|
|
10
|
+
schema.push(...typeDefMapToMutationStringArray(typeDefs))
|
|
11
|
+
|
|
12
|
+
return schema.join('\n')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = services => {
|
|
16
|
+
const typeDefMap = servicesToTypeDefMap(services)
|
|
17
|
+
return typeDefMapToSchemaString(typeDefMap)
|
|
18
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const { ARGUMENT, MUTATION_PREFIX, INPUT_OBJECT_SUFFIX } = require('../constants/adapter')
|
|
2
|
+
const { GQL_ROOT, GQL_KEYWORDS, SCALAR_TYPES } = require('../constants/graphql')
|
|
3
|
+
const { generateSchemaObject, typeToArgumentType, isTypeScalar, appendSuffixToType } = require('./utils')
|
|
4
|
+
|
|
5
|
+
const typeDefMapToMutationStringArray = typeDefs => {
|
|
6
|
+
let schema = []
|
|
7
|
+
|
|
8
|
+
// Create root mutation (containing services)
|
|
9
|
+
schema.push(
|
|
10
|
+
...generateSchemaObject(
|
|
11
|
+
GQL_KEYWORDS.TYPE,
|
|
12
|
+
GQL_ROOT.MUTATION,
|
|
13
|
+
Object.keys(typeDefs).reduce((fields, serviceName) => {
|
|
14
|
+
fields[serviceName] = `${serviceName}_${INPUT_OBJECT_SUFFIX.INPUT}`
|
|
15
|
+
return fields
|
|
16
|
+
}, {})
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// Create types for mutations from services (each containing entities)
|
|
21
|
+
for (const [serviceName, entities] of Object.entries(typeDefs)) {
|
|
22
|
+
const fields = {}
|
|
23
|
+
for (const entityName of Object.keys(entities)) {
|
|
24
|
+
const entityNameWithoutServicePrefix = entityName.replace(`${serviceName}_`, '')
|
|
25
|
+
|
|
26
|
+
const createMutationName = `${MUTATION_PREFIX.CREATE}_${entityNameWithoutServicePrefix}`
|
|
27
|
+
const createMutationArgs = `(${ARGUMENT.INPUT}: [${entityName}_${INPUT_OBJECT_SUFFIX.CREATE}]!)`
|
|
28
|
+
fields[`${createMutationName}${createMutationArgs}`] = `[${entityName}]`
|
|
29
|
+
|
|
30
|
+
const updateMutationName = `${MUTATION_PREFIX.UPDATE}_${entityNameWithoutServicePrefix}`
|
|
31
|
+
const updateMutationArgs = `(${ARGUMENT.FILTER}: ${typeToArgumentType(entityName, ARGUMENT.FILTER)}, ${
|
|
32
|
+
ARGUMENT.INPUT
|
|
33
|
+
}: ${entityName}_${INPUT_OBJECT_SUFFIX.UPDATE}!)`
|
|
34
|
+
fields[`${updateMutationName}${updateMutationArgs}`] = `[${entityName}]`
|
|
35
|
+
|
|
36
|
+
const deleteMutationName = `${MUTATION_PREFIX.DELETE}_${entityNameWithoutServicePrefix}`
|
|
37
|
+
const deleteMutationArgs = `(${ARGUMENT.FILTER}: ${typeToArgumentType(entityName, ARGUMENT.FILTER)})`
|
|
38
|
+
fields[`${deleteMutationName}${deleteMutationArgs}`] = SCALAR_TYPES.INT
|
|
39
|
+
}
|
|
40
|
+
schema.push(...generateSchemaObject(GQL_KEYWORDS.TYPE, `${serviceName}_${INPUT_OBJECT_SUFFIX.INPUT}`, fields))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create input types for create mutations from entities (each containing elements)
|
|
44
|
+
for (const entities of Object.values(typeDefs)) {
|
|
45
|
+
for (const [entityName, elements] of Object.entries(entities)) {
|
|
46
|
+
const fields = {}
|
|
47
|
+
for (const [elementName, elementType] of Object.entries(elements)) {
|
|
48
|
+
if (isTypeScalar(elementType)) {
|
|
49
|
+
fields[elementName] = elementType
|
|
50
|
+
} else {
|
|
51
|
+
fields[elementName] = appendSuffixToType(elementType, INPUT_OBJECT_SUFFIX.CREATE)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
schema.push(...generateSchemaObject(GQL_KEYWORDS.INPUT, `${entityName}_${INPUT_OBJECT_SUFFIX.CREATE}`, fields))
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Create input types for update mutations from entities (each containing elements)
|
|
59
|
+
for (const entities of Object.values(typeDefs)) {
|
|
60
|
+
for (const [entityName, elements] of Object.entries(entities)) {
|
|
61
|
+
const fields = {}
|
|
62
|
+
for (const [elementName, elementType] of Object.entries(elements)) {
|
|
63
|
+
if (isTypeScalar(elementType)) {
|
|
64
|
+
fields[elementName] = elementType
|
|
65
|
+
} else {
|
|
66
|
+
fields[elementName] = appendSuffixToType(elementType, INPUT_OBJECT_SUFFIX.UPDATE)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
schema.push(...generateSchemaObject(GQL_KEYWORDS.INPUT, `${entityName}_${INPUT_OBJECT_SUFFIX.UPDATE}`, fields))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return schema
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { typeDefMapToMutationStringArray }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { ARGUMENT, HELPER_TYPES, EQUALITY_OPERATOR, STRING_MATCH_OPERATOR } = require('../constants/adapter')
|
|
2
|
+
const { GQL_ROOT, GQL_KEYWORDS, SCALAR_TYPES } = require('../constants/graphql')
|
|
3
|
+
const {
|
|
4
|
+
generateSchemaObject,
|
|
5
|
+
typeToArgumentType,
|
|
6
|
+
generateArgumentsForType,
|
|
7
|
+
isTypeScalar,
|
|
8
|
+
typeWithoutListBrackets
|
|
9
|
+
} = require('./utils')
|
|
10
|
+
|
|
11
|
+
const generateScalarFilterTypes = () => Object.values(SCALAR_TYPES).flatMap(scalarType => fullFilter(scalarType))
|
|
12
|
+
|
|
13
|
+
const fullFilter = type => {
|
|
14
|
+
const operations = Object.values(EQUALITY_OPERATOR)
|
|
15
|
+
if (type === SCALAR_TYPES.STRING) {
|
|
16
|
+
operations.push(...Object.values(STRING_MATCH_OPERATOR))
|
|
17
|
+
}
|
|
18
|
+
const entries = operations.reduce((acc, operation) => {
|
|
19
|
+
acc[operation] = type
|
|
20
|
+
return acc
|
|
21
|
+
}, {})
|
|
22
|
+
return generateSchemaObject(GQL_KEYWORDS.INPUT, `${type}_${ARGUMENT.FILTER}`, entries)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const typeDefMapToQueryStringArray = typeDefs => {
|
|
26
|
+
let schema = []
|
|
27
|
+
|
|
28
|
+
// Create root query (containing services)
|
|
29
|
+
schema.push(
|
|
30
|
+
...generateSchemaObject(
|
|
31
|
+
GQL_KEYWORDS.TYPE,
|
|
32
|
+
GQL_ROOT.QUERY,
|
|
33
|
+
Object.keys(typeDefs).reduce((fields, serviceName) => {
|
|
34
|
+
fields[serviceName] = serviceName
|
|
35
|
+
return fields
|
|
36
|
+
}, {})
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// Create types from services (each containing entities)
|
|
41
|
+
for (const [serviceName, entities] of Object.entries(typeDefs)) {
|
|
42
|
+
const fields = {}
|
|
43
|
+
for (const entityName of Object.keys(entities)) {
|
|
44
|
+
const entityNameWithoutServicePrefix = entityName.replace(`${serviceName}_`, '')
|
|
45
|
+
const argsString = generateArgumentsForType(entityName, [
|
|
46
|
+
ARGUMENT.FILTER,
|
|
47
|
+
ARGUMENT.ORDER_BY,
|
|
48
|
+
ARGUMENT.TOP,
|
|
49
|
+
ARGUMENT.SKIP
|
|
50
|
+
])
|
|
51
|
+
fields[`${entityNameWithoutServicePrefix}${argsString}`] = `[${entityName}]`
|
|
52
|
+
}
|
|
53
|
+
schema.push(...generateSchemaObject(GQL_KEYWORDS.TYPE, serviceName, fields))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Create types from entities (each containing elements)
|
|
57
|
+
for (const entities of Object.values(typeDefs)) {
|
|
58
|
+
for (const [entityName, elements] of Object.entries(entities)) {
|
|
59
|
+
const fields = {}
|
|
60
|
+
for (const [elementName, elementType] of Object.entries(elements)) {
|
|
61
|
+
const argsString = generateArgumentsForType(elementType, [
|
|
62
|
+
ARGUMENT.FILTER,
|
|
63
|
+
ARGUMENT.ORDER_BY,
|
|
64
|
+
ARGUMENT.TOP,
|
|
65
|
+
ARGUMENT.SKIP
|
|
66
|
+
])
|
|
67
|
+
fields[`${elementName}${argsString}`] = elementType
|
|
68
|
+
}
|
|
69
|
+
schema.push(...generateSchemaObject(GQL_KEYWORDS.TYPE, entityName, fields))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Create filter input types for scalars
|
|
74
|
+
schema.push(...generateScalarFilterTypes())
|
|
75
|
+
|
|
76
|
+
// Create filter input types for entity types
|
|
77
|
+
for (const entities of Object.values(typeDefs)) {
|
|
78
|
+
for (const [entityName, elements] of Object.entries(entities)) {
|
|
79
|
+
const fields = {}
|
|
80
|
+
for (const [elementName, elementType] of Object.entries(elements)) {
|
|
81
|
+
if (isTypeScalar(typeWithoutListBrackets(elementType))) {
|
|
82
|
+
fields[elementName] = typeToArgumentType(elementType, ARGUMENT.FILTER)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (Object.keys(fields).length > 0) {
|
|
86
|
+
schema.push(...generateSchemaObject(GQL_KEYWORDS.INPUT, `${entityName}_${ARGUMENT.FILTER}`, fields))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Create sort direction enum types for order by
|
|
92
|
+
schema.push(...generateSchemaObject(GQL_KEYWORDS.ENUM, HELPER_TYPES.SORT_DIRECTION, ['asc', 'desc']))
|
|
93
|
+
|
|
94
|
+
// Create orderBy input types for entity types
|
|
95
|
+
for (const entities of Object.values(typeDefs)) {
|
|
96
|
+
for (const [entityName, elements] of Object.entries(entities)) {
|
|
97
|
+
const fields = {}
|
|
98
|
+
for (const [elementName, elementType] of Object.entries(elements)) {
|
|
99
|
+
fields[elementName] = typeToArgumentType(elementType, ARGUMENT.ORDER_BY)
|
|
100
|
+
}
|
|
101
|
+
schema.push(...generateSchemaObject(GQL_KEYWORDS.INPUT, `${entityName}_${ARGUMENT.ORDER_BY}`, fields))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return schema
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { typeDefMapToQueryStringArray }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { CDS_TO_GRAPHQL_TYPES } = require('../constants/adapter')
|
|
2
|
+
const { gqlName } = require('../utils')
|
|
3
|
+
|
|
4
|
+
const servicesToTypeDefMap = services => {
|
|
5
|
+
const typeDefs = {}
|
|
6
|
+
|
|
7
|
+
// Create nested map of services, their entities, and their respective elements
|
|
8
|
+
for (const service of services) {
|
|
9
|
+
const serviceDefs = (typeDefs[gqlName(service.name)] = {})
|
|
10
|
+
|
|
11
|
+
const serviceNamePrefix = `${service.name}.`
|
|
12
|
+
const entitiesKV = Object.entries(service.model.definitions).filter(
|
|
13
|
+
// eslint-disable-next-line no-unused-vars
|
|
14
|
+
([k, _]) => k.startsWith(serviceNamePrefix) && service.model.definitions[k].kind === 'entity'
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line no-unused-vars
|
|
18
|
+
for (const [_, entity] of entitiesKV) {
|
|
19
|
+
const def = (serviceDefs[gqlName(entity.name)] = {})
|
|
20
|
+
for (const ele of Object.values(entity.elements)) {
|
|
21
|
+
if (ele.name.startsWith('up_') || ele.name === 'localized' || ele.name === 'texts') {
|
|
22
|
+
continue
|
|
23
|
+
} else if (ele.isAssociation || ele.isComposition) {
|
|
24
|
+
if (!ele.target.startsWith(serviceNamePrefix)) {
|
|
25
|
+
// TODO entities in other namespaces
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
def[ele.name] = ele.is2one ? gqlName(ele.target) : `[${gqlName(ele.target)}]`
|
|
29
|
+
} else if (ele.elements) {
|
|
30
|
+
// TODO structured types
|
|
31
|
+
continue
|
|
32
|
+
} else {
|
|
33
|
+
if (CDS_TO_GRAPHQL_TYPES[ele.type]) {
|
|
34
|
+
def[ele.name] = CDS_TO_GRAPHQL_TYPES[ele.type]
|
|
35
|
+
}
|
|
36
|
+
// TODO aspects
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return typeDefs
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { servicesToTypeDefMap }
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { ARGUMENT, HELPER_TYPES } = require('../../constants/adapter')
|
|
2
|
+
const { GQL_LIST_REGEX, SCALAR_TYPES } = require('../../constants/graphql')
|
|
3
|
+
|
|
4
|
+
const generateSchemaObject = (type, name, fields) => {
|
|
5
|
+
const schema = [`${type} ${name} {`]
|
|
6
|
+
if (Array.isArray(fields)) {
|
|
7
|
+
schema.push(...fields.map(e => ` ${e}`))
|
|
8
|
+
} else {
|
|
9
|
+
schema.push(...Object.entries(fields).map(([k, v]) => ` ${k}: ${v}`))
|
|
10
|
+
}
|
|
11
|
+
schema.push('}', '')
|
|
12
|
+
return schema
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const generateArgumentsForType = (type, args) =>
|
|
16
|
+
`(${args.map(action => `${action}: ${typeToArgumentType(type, action)}`).join(', ')})`
|
|
17
|
+
|
|
18
|
+
const typeToArgumentType = (type, action) => {
|
|
19
|
+
switch (action) {
|
|
20
|
+
case ARGUMENT.FILTER:
|
|
21
|
+
return appendSuffixToType(type, ARGUMENT.FILTER, true)
|
|
22
|
+
case ARGUMENT.ORDER_BY:
|
|
23
|
+
return isTypeScalar(type) ? HELPER_TYPES.SORT_DIRECTION : appendSuffixToType(type, ARGUMENT.ORDER_BY, true)
|
|
24
|
+
case ARGUMENT.TOP:
|
|
25
|
+
return SCALAR_TYPES.INT
|
|
26
|
+
case ARGUMENT.SKIP:
|
|
27
|
+
return SCALAR_TYPES.INT
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const isTypeScalar = type => Object.values(SCALAR_TYPES).includes(type)
|
|
32
|
+
|
|
33
|
+
const isTypeList = type => type.match(GQL_LIST_REGEX)
|
|
34
|
+
|
|
35
|
+
// Type without list brackets even if type isn't a list
|
|
36
|
+
const typeWithoutListBrackets = type => type.replace(GQL_LIST_REGEX, '$1')
|
|
37
|
+
|
|
38
|
+
const appendSuffixToType = (type, suffix, forceToList) => {
|
|
39
|
+
if (isTypeList(type) || forceToList) {
|
|
40
|
+
return `[${typeWithoutListBrackets(type)}_${suffix}]`
|
|
41
|
+
} else {
|
|
42
|
+
return `${type}_${suffix}`
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
generateSchemaObject,
|
|
48
|
+
generateArgumentsForType,
|
|
49
|
+
typeToArgumentType,
|
|
50
|
+
isTypeScalar,
|
|
51
|
+
isTypeList,
|
|
52
|
+
typeWithoutListBrackets,
|
|
53
|
+
appendSuffixToType
|
|
54
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { formatVal } = require('../utils')
|
|
2
|
+
|
|
1
3
|
const OPERATORS = {
|
|
2
4
|
'=': 'eq',
|
|
3
5
|
'!=': 'ne',
|
|
@@ -10,23 +12,13 @@ const OPERATORS = {
|
|
|
10
12
|
|
|
11
13
|
const LAMBDA_VARIABLE = 'd'
|
|
12
14
|
|
|
13
|
-
const getSafeNumber = str => {
|
|
14
|
-
const n = Number(str)
|
|
15
|
-
return Number.isSafeInteger(n) || String(n) === str ? n : str
|
|
16
|
-
}
|
|
17
|
-
|
|
18
15
|
const needArrayProps = Object.fromEntries(
|
|
19
|
-
['where', 'search', 'xpr', 'columns', '
|
|
16
|
+
['where', 'search', 'xpr', 'columns', 'orderBy', 'ref', 'args'].map(propName => [
|
|
20
17
|
propName,
|
|
21
|
-
cur =>
|
|
22
|
-
(Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')) ||
|
|
23
|
-
(propName === 'expand' && cur === '*')
|
|
18
|
+
cur => Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')
|
|
24
19
|
])
|
|
25
20
|
)
|
|
26
21
|
|
|
27
|
-
const V4UUIDREGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
28
|
-
const isV4UUID = val => V4UUIDREGEXP.test(val)
|
|
29
|
-
|
|
30
22
|
const validators = {
|
|
31
23
|
SELECT: SELECT => SELECT && SELECT.from,
|
|
32
24
|
INSERT: INSERT => {
|
|
@@ -50,63 +42,13 @@ const validators = {
|
|
|
50
42
|
func: func => typeof func === 'string',
|
|
51
43
|
one: count => typeof count === 'boolean',
|
|
52
44
|
as: any => typeof any === 'string',
|
|
45
|
+
expand: any => any === '*' || Array.isArray(any),
|
|
53
46
|
...needArrayProps
|
|
54
47
|
}
|
|
55
48
|
|
|
56
49
|
// strip service & namespace prefixes
|
|
57
50
|
const _entityUrl = path => path.match(/^(\w*\.)*(.*)$/)[2]
|
|
58
51
|
|
|
59
|
-
const formatVal = (val, element, csnTarget, kind) => {
|
|
60
|
-
if (val === null || val === 'null') return 'null'
|
|
61
|
-
if (typeof val === 'boolean') return val
|
|
62
|
-
if (typeof val === 'number') return getSafeNumber(val)
|
|
63
|
-
if (!csnTarget && typeof val === 'string' && isV4UUID(val)) return kind === 'odata-v2' ? `guid'${val}'` : val
|
|
64
|
-
|
|
65
|
-
const csnElement = (csnTarget && csnTarget.elements && csnTarget.elements[element]) || { type: undefined }
|
|
66
|
-
return kind === 'odata-v2' ? _odataV2Val(val, csnElement.type) : _val(val, csnElement.type)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const _isTimestamp = val =>
|
|
70
|
-
/^\d+-\d\d-\d\d(T\d\d:\d\d(:\d\d(\.\d+)?)?(Z|([+-]{1}\d\d:\d\d))?)?$/.test(val) && !isNaN(Date.parse(val))
|
|
71
|
-
|
|
72
|
-
const _odataV2Val = (val, type) => {
|
|
73
|
-
switch (type) {
|
|
74
|
-
case 'cds.Binary':
|
|
75
|
-
case 'cds.LargeBinary':
|
|
76
|
-
return `binary'${val}'`
|
|
77
|
-
case 'cds.Date':
|
|
78
|
-
case 'cds.DateTime':
|
|
79
|
-
return `datetime'${val}'`
|
|
80
|
-
case 'cds.Time':
|
|
81
|
-
// eslint-disable-next-line no-case-declarations
|
|
82
|
-
const [hh, mm, ss] = val.split(':')
|
|
83
|
-
return `time'PT${hh}H${mm}M${ss}S'`
|
|
84
|
-
case 'cds.Timestamp':
|
|
85
|
-
return `datetimeoffset'${val}'`
|
|
86
|
-
case 'cds.UUID':
|
|
87
|
-
return `guid'${val}'`
|
|
88
|
-
default:
|
|
89
|
-
return `'${val}'`
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const _val = (val, type) => {
|
|
94
|
-
switch (type) {
|
|
95
|
-
case 'cds.Decimal':
|
|
96
|
-
case 'cds.Integer64':
|
|
97
|
-
return getSafeNumber(val)
|
|
98
|
-
case 'cds.Boolean':
|
|
99
|
-
case 'cds.DateTime':
|
|
100
|
-
case 'cds.Date':
|
|
101
|
-
case 'cds.Timestamp':
|
|
102
|
-
case 'cds.Time':
|
|
103
|
-
case 'cds.UUID':
|
|
104
|
-
return val
|
|
105
|
-
default:
|
|
106
|
-
return _isTimestamp(val) ? val : `'${val}'`
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
52
|
function getProp(obj, propName) {
|
|
111
53
|
const validate = validators[propName]
|
|
112
54
|
const isValid = validate && validate(obj[propName])
|
|
@@ -166,13 +108,25 @@ const _in = (column, /* in */ collection, target, kind, isLambda) => {
|
|
|
166
108
|
}
|
|
167
109
|
}
|
|
168
110
|
|
|
111
|
+
const _odataV2Func = (func, args) => {
|
|
112
|
+
switch (func) {
|
|
113
|
+
case 'contains':
|
|
114
|
+
// this doesn't support the contains signature with two collections as args, introduced in odata v4.01
|
|
115
|
+
return `substringof(${_args([args[1], args[0]])})`
|
|
116
|
+
default:
|
|
117
|
+
return `${func}(${_args(args)})`
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
169
121
|
const _format = (cur, element, target, kind, isLambda) => {
|
|
170
122
|
if (typeof cur !== 'object') return formatVal(cur, element, target, kind)
|
|
171
123
|
if (hasValidProps(cur, 'ref')) return isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref.join('/')
|
|
172
124
|
if (hasValidProps(cur, 'val')) return formatVal(cur.val, element, target, kind)
|
|
173
125
|
if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
|
|
174
126
|
// REVISIT: How to detect the types for all functions?
|
|
175
|
-
if (hasValidProps(cur, 'func', 'args'))
|
|
127
|
+
if (hasValidProps(cur, 'func', 'args')) {
|
|
128
|
+
return kind === 'odata-v2' ? _odataV2Func(cur.func, cur.args) : `${cur.func}(${_args(cur.args)})`
|
|
129
|
+
}
|
|
176
130
|
}
|
|
177
131
|
|
|
178
132
|
const _isLambda = (cur, next) => {
|
|
@@ -328,62 +282,48 @@ const _parseColumnsV2 = (columns, prefix = []) => {
|
|
|
328
282
|
return { select, expand }
|
|
329
283
|
}
|
|
330
284
|
|
|
331
|
-
const _parseColumns =
|
|
332
|
-
const isExpand = options.expand
|
|
285
|
+
const _parseColumns = columns => {
|
|
333
286
|
const select = []
|
|
334
287
|
const expand = []
|
|
335
288
|
|
|
336
|
-
if (columns === '*') {
|
|
337
|
-
return { select, expand }
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const isSelectAll = select =>
|
|
341
|
-
select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))
|
|
342
|
-
|
|
343
289
|
for (const column of columns) {
|
|
344
290
|
if (hasValidProps(column, 'ref')) {
|
|
345
|
-
|
|
346
|
-
let refNameWithOptions = refName
|
|
347
|
-
|
|
291
|
+
let refName = column.ref.join('/')
|
|
348
292
|
if (hasValidProps(column, 'expand')) {
|
|
293
|
+
// REVISIT: incomplete, see test Foo?$expand=invoices($count=true;$expand=item($search="some"))
|
|
294
|
+
if (!columns.some(c => !c.expand)) select.push(refName)
|
|
349
295
|
const curOptions = getOptions(column).join(';')
|
|
350
|
-
|
|
351
|
-
expand.push(
|
|
352
|
-
if (!isSelectAll(select) && !isExpand) select.push(refName)
|
|
296
|
+
refName += curOptions ? `(${curOptions})` : ''
|
|
297
|
+
expand.push(refName)
|
|
353
298
|
// REVISIT: expand to one & limit in options
|
|
354
|
-
// > const expanded = $expand(
|
|
355
|
-
// > expand.push(expanded ? `${
|
|
299
|
+
// > const expanded = $expand(col.expand)
|
|
300
|
+
// > expand.push(expanded ? `${ref}(${expanded})` : ref)
|
|
356
301
|
// see xtest('READ with expand'... in custom handler test
|
|
357
302
|
} else {
|
|
358
|
-
select.push(
|
|
303
|
+
select.push(refName)
|
|
359
304
|
}
|
|
305
|
+
} else if (hasValidProps(column, 'expand') && column.expand === '*') {
|
|
306
|
+
expand.push('*')
|
|
360
307
|
}
|
|
361
|
-
|
|
362
308
|
if (column === '*') {
|
|
363
309
|
select.push(column)
|
|
364
310
|
}
|
|
365
311
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (isSelectAll(select)) {
|
|
312
|
+
// omit '$select' option if contains only '*'
|
|
313
|
+
if (select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))) {
|
|
369
314
|
select.pop()
|
|
370
315
|
}
|
|
371
|
-
|
|
372
|
-
const uniqueSelect = [...new Set(select)]
|
|
373
|
-
return { select: uniqueSelect, expand }
|
|
316
|
+
return { select, expand }
|
|
374
317
|
}
|
|
375
318
|
|
|
376
|
-
function $select(columns, kind, separator = '&'
|
|
377
|
-
const { select, expand } =
|
|
378
|
-
kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns, { expand: isExpand })
|
|
379
|
-
|
|
319
|
+
function $select(columns, kind, separator = '&') {
|
|
320
|
+
const { select, expand } = kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns)
|
|
380
321
|
const res = []
|
|
381
322
|
if (expand.length) res.unshift('$expand=' + expand.join(','))
|
|
382
323
|
if (select.length) res.unshift('$select=' + select.join(','))
|
|
383
324
|
return res.join(separator)
|
|
384
325
|
}
|
|
385
|
-
|
|
386
|
-
const $expand = columns => $select(columns, 'odata', ';', true)
|
|
326
|
+
const $expand = columns => $select(columns, 'odata', ';')
|
|
387
327
|
|
|
388
328
|
function $count(count, kind) {
|
|
389
329
|
if (count !== true) return ''
|
|
@@ -476,7 +416,9 @@ const _isOdataUrlWithKeys = (url, kind) => kind !== 'rest' && /^[\w\.]+\(.*\)/.t
|
|
|
476
416
|
const parsers = {
|
|
477
417
|
columns: (cqnPart, url, kind, target, isCount) => !isCount && $select(cqnPart, kind),
|
|
478
418
|
expand: (cqnPart, url, kind, target, isCount) => !isCount && $expand(cqnPart),
|
|
419
|
+
// eslint-disable-next-line no-unused-vars
|
|
479
420
|
where: (cqnPart, url, kind, target, isCount) => $where(cqnPart, target, kind),
|
|
421
|
+
// eslint-disable-next-line no-unused-vars
|
|
480
422
|
search: (cqnPart, url, kind, target, isCount) => $search(cqnPart, kind),
|
|
481
423
|
orderBy: (cqnPart, url, kind, target, isCount) => !isCount && $orderBy(cqnPart),
|
|
482
424
|
count: (cqnPart, url, kind, target, isCount) => !isCount && $count(cqnPart, kind),
|
|
@@ -503,9 +445,7 @@ function getOptions(cqnPart, url, kind, target, isCount) {
|
|
|
503
445
|
const _isCount = SELECT => {
|
|
504
446
|
if (SELECT.columns) {
|
|
505
447
|
const columns = getProp(SELECT, 'columns')
|
|
506
|
-
return columns.some(
|
|
507
|
-
c => c.func === 'count' && c.as === '$count' && c.args && c.args.length === 1 && c.args[0] === '*'
|
|
508
|
-
)
|
|
448
|
+
return columns.some(c => c.func === 'count' && c.as === '$count')
|
|
509
449
|
}
|
|
510
450
|
return false
|
|
511
451
|
}
|
|
@@ -535,7 +475,6 @@ const _copyData = data => {
|
|
|
535
475
|
? data[property].val
|
|
536
476
|
: data[property]
|
|
537
477
|
}
|
|
538
|
-
|
|
539
478
|
return copied
|
|
540
479
|
}
|
|
541
480
|
|
|
@@ -581,4 +520,4 @@ function cqn2odata(cqn, kind, model) {
|
|
|
581
520
|
throw new Error('Unknown CQN object cannot be translated to URL: ' + JSON.stringify(cqn))
|
|
582
521
|
}
|
|
583
522
|
|
|
584
|
-
module.exports =
|
|
523
|
+
module.exports = cqn2odata
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const cds = require('../_runtime/cds')
|
|
2
|
+
const { SELECT } = cds.ql
|
|
3
|
+
|
|
4
|
+
const odata2cqn = require('./odata2cqn')
|
|
5
|
+
const cqn2odata = require('./cqn2odata')
|
|
6
|
+
|
|
7
|
+
const afterburner = require('./odata2cqn/afterburner')
|
|
8
|
+
const { getSafeNumber: safeNumber } = require('./utils')
|
|
9
|
+
|
|
10
|
+
const strict = {
|
|
11
|
+
functions: {
|
|
12
|
+
contains: 1,
|
|
13
|
+
startswith: 1,
|
|
14
|
+
endswith: 1,
|
|
15
|
+
tolower: 1,
|
|
16
|
+
toupper: 1,
|
|
17
|
+
length: 1,
|
|
18
|
+
indexof: 1,
|
|
19
|
+
substring: 1,
|
|
20
|
+
trim: 1,
|
|
21
|
+
concat: 1,
|
|
22
|
+
year: 1,
|
|
23
|
+
month: 1,
|
|
24
|
+
day: 1,
|
|
25
|
+
hour: 1,
|
|
26
|
+
minute: 1,
|
|
27
|
+
second: 1,
|
|
28
|
+
time: 1,
|
|
29
|
+
now: 1
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* cds.odata API
|
|
35
|
+
*/
|
|
36
|
+
module.exports = {
|
|
37
|
+
parse: (url, options = {}) => {
|
|
38
|
+
// first arg may also be req
|
|
39
|
+
if (url.url) url = url.url
|
|
40
|
+
// REVISIT: for okra, remove when no longer needed
|
|
41
|
+
else if (url.getIncomingRequest) url = url.getIncomingRequest().url
|
|
42
|
+
url = decodeURIComponent(url)
|
|
43
|
+
|
|
44
|
+
options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
|
|
45
|
+
if (options.service) Object.assign(options, { minimal: true, afterburner: afterburner.for(options.service) })
|
|
46
|
+
options.safeNumber = safeNumber
|
|
47
|
+
|
|
48
|
+
let cqn
|
|
49
|
+
try {
|
|
50
|
+
cqn = odata2cqn(url, options)
|
|
51
|
+
} catch (e) {
|
|
52
|
+
// REVISIT: additional try in catch isn't nice -> find better way
|
|
53
|
+
// known gaps -> e.message is a stringified error -> use that
|
|
54
|
+
// unknown errors -> e is the error to keep
|
|
55
|
+
let err = e
|
|
56
|
+
try {
|
|
57
|
+
err = JSON.parse(e.message)
|
|
58
|
+
} catch {
|
|
59
|
+
/* nothing to do */
|
|
60
|
+
}
|
|
61
|
+
err.message = 'Parsing URL failed with error: ' + err.message
|
|
62
|
+
err.statusCode = err.statusCode || 400
|
|
63
|
+
throw err
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof options.afterburner === 'function') cqn = options.afterburner(cqn)
|
|
67
|
+
|
|
68
|
+
const query = cqn.SELECT.one ? SELECT.one(cqn.SELECT.from) : SELECT.from(cqn.SELECT.from)
|
|
69
|
+
Object.assign(query.SELECT, cqn.SELECT)
|
|
70
|
+
|
|
71
|
+
// REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
|
|
72
|
+
// DO NOT USE __target outside of libx/rest!!!
|
|
73
|
+
query.__target = cqn.__target
|
|
74
|
+
|
|
75
|
+
return query
|
|
76
|
+
},
|
|
77
|
+
urlify: (cqn, options = {}) => {
|
|
78
|
+
return cqn2odata(cqn, options.kind, options.model)
|
|
79
|
+
}
|
|
80
|
+
}
|