Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use serviceinfo to determine services to compile #178

Merged
merged 9 commits into from
Jul 29, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Bump required `@sap/cds` version to `>=7.8`
- To improve performance, binary payloads are no longer validated to check if they are properly base64 or base64url encoded
- Bump required `node` version to `^16` due to usage of `Buffer.toString('base64url')`
- Use `cds.compile.to.serviceinfo` to determine if a service should be compiled to GraphQL schema

### Fixed

Expand Down
27 changes: 7 additions & 20 deletions lib/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,16 @@ 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 model = cds.linked(csn)
const serviceinfo = cds.compile.to.serviceinfo(csn, options)
const services = Object.fromEntries(
m.services
.map(s => [s.name, new cds.ApplicationService(s.name, m)])
model.services
.map(s => [s.name, new cds.ApplicationService(s.name, model)])
// Only compile services with GraphQL endpoints
.filter(([_, service]) => _isServiceAnnotatedWithGraphQL(service))
.filter(([_, service]) =>
serviceinfo.find(s => s.name === service.name)?.endpoints.some(e => e.kind === 'graphql')
)
)

let schema = generateSchema4(services)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"graphql-http": "^1.18.0"
},
"peerDependencies": {
"@sap/cds": ">=7.3"
"@sap/cds": ">=7.8"
},
"devDependencies": {
"@cap-js/graphql": "file:.",
Expand Down
40 changes: 21 additions & 19 deletions test/resources/annotations/srv/protocols.cds
Original file line number Diff line number Diff line change
@@ -1,47 +1,49 @@
service NotAnnotated {
context protocols {
entity A {
key id : UUID;
}
}

service NotAnnotated {
entity A as projection on protocols.A;
}

@protocol: 'none'
service AnnotatedWithAtProtocolNone {
entity A {
key id : UUID;
}
entity A as projection on protocols.A;
}

@protocol: 'odata'
service AnnotatedWithNonGraphQL {
entity A {
key id : UUID;
}
entity A as projection on protocols.A;
}

@graphql
service AnnotatedWithAtGraphQL {
entity A {
key id : UUID;
}
entity A as projection on protocols.A;
}

@protocol: 'graphql'
service AnnotatedWithAtProtocolString {
entity A {
key id : UUID;
}
entity A as projection on protocols.A;
}

@protocol: ['graphql']
service AnnotatedWithAtProtocolStringList {
entity A {
key id : UUID;
}
entity A as projection on protocols.A;
}

@protocol: [{kind: 'graphql'}]
service AnnotatedWithAtProtocolObjectList {
entity A {
key id : UUID;
}
entity A as projection on protocols.A;
}

@protocol: { graphql }
service AnnotatedWithAtProtocolObjectWithKey {
entity A as projection on protocols.A;
}

@protocol: { graphql: 'dummy' }
service AnnotatedWithAtProtocolObjectWithKeyAndValue {
entity A as projection on protocols.A;
}
72 changes: 72 additions & 0 deletions test/schemas/annotations.gql
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,74 @@ type AnnotatedWithAtProtocolObjectList_input {
A: AnnotatedWithAtProtocolObjectList_A_input
}

type AnnotatedWithAtProtocolObjectWithKey {
A(filter: [AnnotatedWithAtProtocolObjectWithKey_A_filter], orderBy: [AnnotatedWithAtProtocolObjectWithKey_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolObjectWithKey_A_connection
}

type AnnotatedWithAtProtocolObjectWithKeyAndValue {
A(filter: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A_filter], orderBy: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolObjectWithKeyAndValue_A_connection
}

type AnnotatedWithAtProtocolObjectWithKeyAndValue_A {
id: ID
}

input AnnotatedWithAtProtocolObjectWithKeyAndValue_A_C {
id: ID
}

type AnnotatedWithAtProtocolObjectWithKeyAndValue_A_connection {
nodes: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A]
totalCount: Int
}

input AnnotatedWithAtProtocolObjectWithKeyAndValue_A_filter {
id: [ID_filter]
}

type AnnotatedWithAtProtocolObjectWithKeyAndValue_A_input {
create(input: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A_C]!): [AnnotatedWithAtProtocolObjectWithKeyAndValue_A]
delete(filter: [AnnotatedWithAtProtocolObjectWithKeyAndValue_A_filter]!): Int
}

input AnnotatedWithAtProtocolObjectWithKeyAndValue_A_orderBy {
id: SortDirection
}

type AnnotatedWithAtProtocolObjectWithKeyAndValue_input {
A: AnnotatedWithAtProtocolObjectWithKeyAndValue_A_input
}

type AnnotatedWithAtProtocolObjectWithKey_A {
id: ID
}

input AnnotatedWithAtProtocolObjectWithKey_A_C {
id: ID
}

type AnnotatedWithAtProtocolObjectWithKey_A_connection {
nodes: [AnnotatedWithAtProtocolObjectWithKey_A]
totalCount: Int
}

input AnnotatedWithAtProtocolObjectWithKey_A_filter {
id: [ID_filter]
}

type AnnotatedWithAtProtocolObjectWithKey_A_input {
create(input: [AnnotatedWithAtProtocolObjectWithKey_A_C]!): [AnnotatedWithAtProtocolObjectWithKey_A]
delete(filter: [AnnotatedWithAtProtocolObjectWithKey_A_filter]!): Int
}

input AnnotatedWithAtProtocolObjectWithKey_A_orderBy {
id: SortDirection
}

type AnnotatedWithAtProtocolObjectWithKey_input {
A: AnnotatedWithAtProtocolObjectWithKey_A_input
}

type AnnotatedWithAtProtocolString {
A(filter: [AnnotatedWithAtProtocolString_A_filter], orderBy: [AnnotatedWithAtProtocolString_A_orderBy], skip: Int, top: Int): AnnotatedWithAtProtocolString_A_connection
}
Expand Down Expand Up @@ -147,13 +215,17 @@ input ID_filter {
type Mutation {
AnnotatedWithAtGraphQL: AnnotatedWithAtGraphQL_input
AnnotatedWithAtProtocolObjectList: AnnotatedWithAtProtocolObjectList_input
AnnotatedWithAtProtocolObjectWithKey: AnnotatedWithAtProtocolObjectWithKey_input
AnnotatedWithAtProtocolObjectWithKeyAndValue: AnnotatedWithAtProtocolObjectWithKeyAndValue_input
AnnotatedWithAtProtocolString: AnnotatedWithAtProtocolString_input
AnnotatedWithAtProtocolStringList: AnnotatedWithAtProtocolStringList_input
}

type Query {
AnnotatedWithAtGraphQL: AnnotatedWithAtGraphQL
AnnotatedWithAtProtocolObjectList: AnnotatedWithAtProtocolObjectList
AnnotatedWithAtProtocolObjectWithKey: AnnotatedWithAtProtocolObjectWithKey
AnnotatedWithAtProtocolObjectWithKeyAndValue: AnnotatedWithAtProtocolObjectWithKeyAndValue
AnnotatedWithAtProtocolString: AnnotatedWithAtProtocolString
AnnotatedWithAtProtocolStringList: AnnotatedWithAtProtocolStringList
}
Expand Down
32 changes: 32 additions & 0 deletions test/tests/annotations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,37 @@ describe('graphql - annotations', () => {
const response = await POST(path, { query })
expect(response.data).not.toHaveProperty('errors')
})

test('service annotated with "@protocol: { graphql }" is served at configured path', async () => {
const query = gql`
{
AnnotatedWithAtProtocolObjectWithKey {
A {
nodes {
id
}
}
}
}
`
const response = await POST(path, { query })
expect(response.data).not.toHaveProperty('errors')
})

test('service annotated with "@protocol: { graphql: \'dummy\' }" is served at configured path', async () => {
const query = gql`
{
AnnotatedWithAtProtocolObjectWithKeyAndValue {
A {
nodes {
id
}
}
}
}
`
const response = await POST(path, { query })
expect(response.data).not.toHaveProperty('errors')
})
})
})
Loading