From 153c3767aa3dea6caa73033d5e63c3851cf78353 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Fri, 27 Oct 2023 09:31:44 +0200 Subject: [PATCH 01/11] Only compile services annotated with GraphQL protocol --- lib/compile.js | 7 ++++++- test/resources/edge-cases/srv/field-named-localized.cds | 1 + .../model-structure/srv/composition-of-aspect.cds | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/compile.js b/lib/compile.js index 2e34e54b..9582ac77 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -5,8 +5,13 @@ const { lexicographicSortSchema, printSchema } = require('graphql') function cds_compile_to_gql(csn, options = {}) { const m = cds.linked(csn) const services = Object.fromEntries(m.services.map(s => [s.name, new cds.ApplicationService(s.name, m)])) + const gqlServices = Object.fromEntries( + Object.entries(services).filter(([_, service]) => + service.definition.endpoints.some(e => e.kind.match(/graphql/)) + ) + ) - let schema = generateSchema4(services) + let schema = generateSchema4(gqlServices) if (options.sort) schema = lexicographicSortSchema(schema) if (/^obj|object$/i.test(options.as)) return schema diff --git a/test/resources/edge-cases/srv/field-named-localized.cds b/test/resources/edge-cases/srv/field-named-localized.cds index 2cb150ad..acc96591 100644 --- a/test/resources/edge-cases/srv/field-named-localized.cds +++ b/test/resources/edge-cases/srv/field-named-localized.cds @@ -1,5 +1,6 @@ using {managed} from '@sap/cds/common'; +@graphql service FieldNamedLocalizedService { entity localized { key ID : Integer; diff --git a/test/resources/model-structure/srv/composition-of-aspect.cds b/test/resources/model-structure/srv/composition-of-aspect.cds index bdd37470..1fd8be65 100644 --- a/test/resources/model-structure/srv/composition-of-aspect.cds +++ b/test/resources/model-structure/srv/composition-of-aspect.cds @@ -1,3 +1,4 @@ +@protocol: ['graphql'] service CompositionOfAspectService { entity Books { key id : UUID; From 10c7388ac47bf818f7215118ce2f4edf5253ecfa Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Fri, 27 Oct 2023 09:36:40 +0200 Subject: [PATCH 02/11] Shorter services definition --- lib/compile.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index 9582ac77..5ce1a6fa 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -4,14 +4,14 @@ const { lexicographicSortSchema, printSchema } = require('graphql') function cds_compile_to_gql(csn, options = {}) { const m = cds.linked(csn) - const services = Object.fromEntries(m.services.map(s => [s.name, new cds.ApplicationService(s.name, m)])) - const gqlServices = Object.fromEntries( - Object.entries(services).filter(([_, service]) => - service.definition.endpoints.some(e => e.kind.match(/graphql/)) - ) + const services = Object.fromEntries( + m.services + .map(s => [s.name, new cds.ApplicationService(s.name, m)]) + // Only compile services with graphql endpoints + .filter(([_, service]) => service.definition.endpoints.some(e => e.kind.match(/graphql/))) ) - let schema = generateSchema4(gqlServices) + let schema = generateSchema4(services) if (options.sort) schema = lexicographicSortSchema(schema) if (/^obj|object$/i.test(options.as)) return schema From c1c99eb7c172fa26e85b4d4a6dca205b8604e989 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Mon, 13 Nov 2023 10:39:14 +0100 Subject: [PATCH 03/11] Add schema test for compiling with protocol annotations --- test/resources/models.json | 4 + test/schemas/annotations.gql | 163 +++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 test/schemas/annotations.gql diff --git a/test/resources/models.json b/test/resources/models.json index 36a0900a..644c5947 100644 --- a/test/resources/models.json +++ b/test/resources/models.json @@ -7,6 +7,10 @@ ], "requires_cds": ">=6.8.0" }, + { + "name": "annotations", + "files": ["./annotations/srv/protocols.cds"] + }, { "name": "types", "files": ["./types/srv/types.cds"] diff --git a/test/schemas/annotations.gql b/test/schemas/annotations.gql new file mode 100644 index 00000000..2e878a90 --- /dev/null +++ b/test/schemas/annotations.gql @@ -0,0 +1,163 @@ +type AnnotatedWithAtGraphQL { + A(filter: [AnnotatedWithAtGraphQL_A_filter], orderBy: [AnnotatedWithAtGraphQL_A_orderBy], skip: Int, top: Int): AnnotatedWithAtGraphQL_A_connection +} + +type AnnotatedWithAtGraphQL_A { + id: ID +} + +input AnnotatedWithAtGraphQL_A_C { + id: ID +} + +type AnnotatedWithAtGraphQL_A_connection { + nodes: [AnnotatedWithAtGraphQL_A] + totalCount: Int +} + +input AnnotatedWithAtGraphQL_A_filter { + id: [ID_filter] +} + +type AnnotatedWithAtGraphQL_A_input { + create(input: [AnnotatedWithAtGraphQL_A_C]!): [AnnotatedWithAtGraphQL_A] + delete(filter: [AnnotatedWithAtGraphQL_A_filter]!): Int +} + +input AnnotatedWithAtGraphQL_A_orderBy { + id: SortDirection +} + +type AnnotatedWithAtGraphQL_input { + A: AnnotatedWithAtGraphQL_A_input +} + +type AnnotatedWithAtProtocolObjectList { + A(filter: [AnnotatedWithAtProtocolObjectList_A_filter], orderBy: [AnnotatedWithAtProtocolObjectList_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolObjectList_A_connection +} + +type AnnotatedWithAtProtocolObjectList_A { + id: ID +} + +input AnnotatedWithAtProtocolObjectList_A_C { + id: ID +} + +type AnnotatedWithAtProtocolObjectList_A_connection { + nodes: [AnnotatedWithAtProtocolObjectList_A] + totalCount: Int +} + +input AnnotatedWithAtProtocolObjectList_A_filter { + id: [ID_filter] +} + +type AnnotatedWithAtProtocolObjectList_A_input { + create(input: [AnnotatedWithAtProtocolObjectList_A_C]!): [AnnotatedWithAtProtocolObjectList_A] + delete(filter: [AnnotatedWithAtProtocolObjectList_A_filter]!): Int +} + +input AnnotatedWithAtProtocolObjectList_A_orderBy { + id: SortDirection +} + +type AnnotatedWithAtProtocolObjectList_input { + A: AnnotatedWithAtProtocolObjectList_A_input +} + +type AnnotatedWithAtProtocolString { + A(filter: [AnnotatedWithAtProtocolString_A_filter], orderBy: [AnnotatedWithAtProtocolString_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolString_A_connection +} + +type AnnotatedWithAtProtocolStringList { + A(filter: [AnnotatedWithAtProtocolStringList_A_filter], orderBy: [AnnotatedWithAtProtocolStringList_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolStringList_A_connection +} + +type AnnotatedWithAtProtocolStringList_A { + id: ID +} + +input AnnotatedWithAtProtocolStringList_A_C { + id: ID +} + +type AnnotatedWithAtProtocolStringList_A_connection { + nodes: [AnnotatedWithAtProtocolStringList_A] + totalCount: Int +} + +input AnnotatedWithAtProtocolStringList_A_filter { + id: [ID_filter] +} + +type AnnotatedWithAtProtocolStringList_A_input { + create(input: [AnnotatedWithAtProtocolStringList_A_C]!): [AnnotatedWithAtProtocolStringList_A] + delete(filter: [AnnotatedWithAtProtocolStringList_A_filter]!): Int +} + +input AnnotatedWithAtProtocolStringList_A_orderBy { + id: SortDirection +} + +type AnnotatedWithAtProtocolStringList_input { + A: AnnotatedWithAtProtocolStringList_A_input +} + +type AnnotatedWithAtProtocolString_A { + id: ID +} + +input AnnotatedWithAtProtocolString_A_C { + id: ID +} + +type AnnotatedWithAtProtocolString_A_connection { + nodes: [AnnotatedWithAtProtocolString_A] + totalCount: Int +} + +input AnnotatedWithAtProtocolString_A_filter { + id: [ID_filter] +} + +type AnnotatedWithAtProtocolString_A_input { + create(input: [AnnotatedWithAtProtocolString_A_C]!): [AnnotatedWithAtProtocolString_A] + delete(filter: [AnnotatedWithAtProtocolString_A_filter]!): Int +} + +input AnnotatedWithAtProtocolString_A_orderBy { + id: SortDirection +} + +type AnnotatedWithAtProtocolString_input { + A: AnnotatedWithAtProtocolString_A_input +} + +input ID_filter { + eq: ID + ge: ID + gt: ID + le: ID + lt: ID + ne: [ID] +} + +type Mutation { + AnnotatedWithAtGraphQL: AnnotatedWithAtGraphQL_input + AnnotatedWithAtProtocolObjectList: AnnotatedWithAtProtocolObjectList_input + AnnotatedWithAtProtocolString: AnnotatedWithAtProtocolString_input + AnnotatedWithAtProtocolStringList: AnnotatedWithAtProtocolStringList_input +} + +type Query { + AnnotatedWithAtGraphQL: AnnotatedWithAtGraphQL + AnnotatedWithAtProtocolObjectList: AnnotatedWithAtProtocolObjectList + AnnotatedWithAtProtocolString: AnnotatedWithAtProtocolString + AnnotatedWithAtProtocolStringList: AnnotatedWithAtProtocolStringList +} + +enum SortDirection { + asc + desc +} \ No newline at end of file From ae80d022dc06677f7e77acca3513dad616740887 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Mon, 13 Nov 2023 10:45:27 +0100 Subject: [PATCH 04/11] Add test for non-GraphQL protocol annotation --- test/resources/annotations/srv/protocols.cds | 7 +++++++ test/tests/annotations.test.js | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/test/resources/annotations/srv/protocols.cds b/test/resources/annotations/srv/protocols.cds index d1b5bcab..0c9a6521 100644 --- a/test/resources/annotations/srv/protocols.cds +++ b/test/resources/annotations/srv/protocols.cds @@ -4,6 +4,13 @@ service NotAnnotated { } } +@protocol: 'odata-v4' +service AnnotatedWithNonGraphQL { + entity A { + key id : UUID; + } +} + @graphql service AnnotatedWithAtGraphQL { entity A { diff --git a/test/tests/annotations.test.js b/test/tests/annotations.test.js index e4cdce73..35e57354 100644 --- a/test/tests/annotations.test.js +++ b/test/tests/annotations.test.js @@ -26,6 +26,22 @@ describe('graphql - annotations', () => { expect(response.data.errors[0].message).toMatch(/^Cannot query field "NotAnnotated" on type "Query"\./) }) + test('service annotated with non-GraphQL protocol is not served', async () => { + const query = gql` + { + AnnotatedWithNonGraphQL { + A { + nodes { + id + } + } + } + } + ` + const response = await POST(path, { query }) + expect(response.data.errors[0].message).toMatch(/^Cannot query field "AnnotatedWithNonGraphQL" on type "Query"\./) + }) + test('service annotated with @graphql is served at configured path', async () => { const query = gql` { From ec2f4c2f860f3a50b9b6f0baebeb552eec170c33 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Mon, 13 Nov 2023 15:28:07 +0100 Subject: [PATCH 05/11] Make protocol check case insensitive --- lib/compile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compile.js b/lib/compile.js index 5ce1a6fa..48f9d1b1 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -8,7 +8,7 @@ function cds_compile_to_gql(csn, options = {}) { m.services .map(s => [s.name, new cds.ApplicationService(s.name, m)]) // Only compile services with graphql endpoints - .filter(([_, service]) => service.definition.endpoints.some(e => e.kind.match(/graphql/))) + .filter(([_, service]) => service.definition.endpoints.some(e => e.kind.match(/graphql/i))) ) let schema = generateSchema4(services) From eddf76b292d649a77e7c0211484132703a1b8b3f Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Mon, 8 Jan 2024 17:22:28 +0100 Subject: [PATCH 06/11] Regenerate test schemas --- test/schemas/annotations.gql | 1 + 1 file changed, 1 insertion(+) diff --git a/test/schemas/annotations.gql b/test/schemas/annotations.gql index 2e878a90..9918197c 100644 --- a/test/schemas/annotations.gql +++ b/test/schemas/annotations.gql @@ -138,6 +138,7 @@ input ID_filter { eq: ID ge: ID gt: ID + in: [ID] le: ID lt: ID ne: [ID] From 61209ec90ba9a87d03d359cbda410620db94d1b4 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Mon, 8 Jan 2024 17:35:51 +0100 Subject: [PATCH 07/11] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9644da52..f9bf64a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@graphiql/plugin-explorer` version to `^1` +- When compiling to GraphQL using the CDS API or CLI, only generate GraphQL schemas for services that are annotated with GraphQL protocol annotations ### Fixed From b2a9346d6823c5fd678ad21bceaae3e9ff3cd4ea Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 10 Jan 2024 15:21:07 +0100 Subject: [PATCH 08/11] Directly check annotations instead of `srv.endpoints` --- lib/compile.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index 48f9d1b1..54d0e502 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -2,13 +2,29 @@ const cds = require('@sap/cds') const { generateSchema4 } = require('./schema') const { lexicographicSortSchema, printSchema } = require('graphql') +const _isServiceAnnotatedWithGraphQL = service => { + const { definition } = service + + if (definition['@graphql']) return true + + const protocol = definition['@protocol'] + if (protocol) { + // @protocol: 'graphql' or @protocol: ['graphql', 'odata'] + const protocols = Array.isArray(protocol) ? protocol : [protocol] + // Normalize objects such as { kind: 'graphql' } to strings + return protocols.map(p => (typeof p === 'object' ? p.kind : p)).some(p => p.match(/graphql/i)) + } + + return false +} + function cds_compile_to_gql(csn, options = {}) { const m = cds.linked(csn) const services = Object.fromEntries( m.services .map(s => [s.name, new cds.ApplicationService(s.name, m)]) - // Only compile services with graphql endpoints - .filter(([_, service]) => service.definition.endpoints.some(e => e.kind.match(/graphql/i))) + // Only compile services with GraphQL endpoints + .filter(([_, service]) => _isServiceAnnotatedWithGraphQL(service)) ) let schema = generateSchema4(services) From 4652ba893bd2feeda661940ed7edd1c262d62c09 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 10 Jan 2024 15:22:06 +0100 Subject: [PATCH 09/11] Add test for `@protocol: 'none'` --- test/resources/annotations/srv/protocols.cds | 7 +++++++ test/tests/annotations.test.js | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/test/resources/annotations/srv/protocols.cds b/test/resources/annotations/srv/protocols.cds index 0c9a6521..c7af7c9c 100644 --- a/test/resources/annotations/srv/protocols.cds +++ b/test/resources/annotations/srv/protocols.cds @@ -4,6 +4,13 @@ service NotAnnotated { } } +@protocol: 'none' +service AnnotatedWithAtProtocolNone { + entity A { + key id : UUID; + } +} + @protocol: 'odata-v4' service AnnotatedWithNonGraphQL { entity A { diff --git a/test/tests/annotations.test.js b/test/tests/annotations.test.js index 35e57354..60288c36 100644 --- a/test/tests/annotations.test.js +++ b/test/tests/annotations.test.js @@ -26,6 +26,22 @@ describe('graphql - annotations', () => { expect(response.data.errors[0].message).toMatch(/^Cannot query field "NotAnnotated" on type "Query"\./) }) + test('service annotated with "@protocol: \'none\'" is not served', async () => { + const query = gql` + { + AnnotatedWithAtProtocolNone { + A { + nodes { + id + } + } + } + } + ` + const response = await POST(path, { query }) + expect(response.data.errors[0].message).toMatch(/^Cannot query field "AnnotatedWithAtProtocolNone " on type "Query"\./) + }) + test('service annotated with non-GraphQL protocol is not served', async () => { const query = gql` { From ecbff70c9724d1282e8537fc1e27e46067844e97 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 10 Jan 2024 15:25:34 +0100 Subject: [PATCH 10/11] Fix expect regex --- test/tests/annotations.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/annotations.test.js b/test/tests/annotations.test.js index 60288c36..df1d4806 100644 --- a/test/tests/annotations.test.js +++ b/test/tests/annotations.test.js @@ -39,7 +39,7 @@ describe('graphql - annotations', () => { } ` const response = await POST(path, { query }) - expect(response.data.errors[0].message).toMatch(/^Cannot query field "AnnotatedWithAtProtocolNone " on type "Query"\./) + expect(response.data.errors[0].message).toMatch(/^Cannot query field "AnnotatedWithAtProtocolNone" on type "Query"\./) }) test('service annotated with non-GraphQL protocol is not served', async () => { From 036f45f76771943dce5b9efa2076845d343570fe Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Fri, 19 Jan 2024 14:53:16 +0100 Subject: [PATCH 11/11] Annotate non GraphQL service with `odata` instead of `odata-v4` --- test/resources/annotations/srv/protocols.cds | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resources/annotations/srv/protocols.cds b/test/resources/annotations/srv/protocols.cds index c7af7c9c..c4d0947c 100644 --- a/test/resources/annotations/srv/protocols.cds +++ b/test/resources/annotations/srv/protocols.cds @@ -11,7 +11,7 @@ service AnnotatedWithAtProtocolNone { } } -@protocol: 'odata-v4' +@protocol: 'odata' service AnnotatedWithNonGraphQL { entity A { key id : UUID;