Skip to content

Commit

Permalink
release 0.11.0 (#182)
Browse files Browse the repository at this point in the history
Co-authored-by: Johannes Vogel <[email protected]>
Co-authored-by: Daniel Hutzel <[email protected]>
Co-authored-by: d049904 <[email protected]>
Co-authored-by: Heiko Witteborg <[email protected]>
Co-authored-by: Mahati Shankar <[email protected]>
Co-authored-by: Samuel Brucksch <[email protected]>
Co-authored-by: Samuel Brucksch <[email protected]>
Co-authored-by: D050513 <[email protected]>
Co-authored-by: sjvans <[email protected]>
Co-authored-by: Mariya Yordanova <[email protected]>
  • Loading branch information
11 people authored Jul 30, 2024
1 parent 132afcd commit 72f0246
Show file tree
Hide file tree
Showing 47 changed files with 717 additions and 679 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/label-issues.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Label issues
on:
issues:
types:
- reopened
- opened
jobs:
label_issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- run: gh issue edit "$NUMBER" --add-label "$LABELS"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
NUMBER: ${{ github.event.issue.number }}
LABELS: new
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Version 0.11.0 - 2024-07-30

### Added

- Support for configuring request payload limit using global flag `cds.server.body_parser.limit`

### 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

## Version 0.10.1 - 2024-03-07

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion lib/GraphQLAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function GraphQLAdapter(options) {
: defaultErrorFormatter

router
.use(express.json()) //> required by logger below
.use(express.json({ ...cds.env.server.body_parser })) //> required by logger below
.use(queryLogger)

if (options.graphiql) router.use(graphiql)
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 lib/resolvers/GraphQLRequest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')

class GraphQLRequest extends cds.Request {
constructor(args) {
Expand Down
2 changes: 1 addition & 1 deletion lib/resolvers/crud/create.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')
const { INSERT } = cds.ql
const { ARGS } = require('../../constants')
const { getArgumentByName, astToEntries } = require('../parse/ast2cqn')
Expand Down
2 changes: 1 addition & 1 deletion lib/resolvers/crud/delete.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')
const { DELETE } = cds.ql
const { ARGS } = require('../../constants')
const { getArgumentByName, astToWhere } = require('../parse/ast2cqn')
Expand Down
2 changes: 1 addition & 1 deletion lib/resolvers/crud/read.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')
const { SELECT } = cds.ql
const { ARGS, CONNECTION_FIELDS } = require('../../constants')
const { getArgumentByName, astToColumns, astToWhere, astToOrderBy, astToLimit } = require('../parse/ast2cqn')
Expand Down
2 changes: 1 addition & 1 deletion lib/resolvers/crud/update.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')
const { SELECT, UPDATE } = cds.ql
const { ARGS } = require('../../constants')
const { getArgumentByName, astToColumns, astToWhere, astToEntries } = require('../parse/ast2cqn')
Expand Down
4 changes: 2 additions & 2 deletions lib/resolvers/parse/ast2cqn/where.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const _arrayInsertBetweenFlat = (array, element) =>

const _joinedXprFrom_xprs = (_xprs, operator) => ({ xpr: _arrayInsertBetweenFlat(_xprs, operator) })

const _true_xpr = [{ val: 1 }, '=', { val: 1 }]
const _true_xpr = [{ val: '1' }, '=', { val: '1' }]

const _parseObjectValue = (objectValue, columnName) => {
const _xprs = objectValue.fields
Expand All @@ -76,7 +76,7 @@ const _parseObjectValue = (objectValue, columnName) => {
return _joinedXprFrom_xprs(_xprs, 'and')
}

const _false_xpr = [{ val: 0 }, '=', { val: 1 }]
const _false_xpr = [{ val: '0' }, '=', { val: '1' }]

const _parseListValue = (listValue, columnName) => {
const _xprs = listValue.values.map(value => _parseObjectValue(value, columnName)).filter(value => value !== undefined)
Expand Down
19 changes: 1 addition & 18 deletions lib/schema/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,6 @@ const queryGenerator = require('./query')
const mutationGenerator = require('./mutation')
const { GraphQLSchema } = require('graphql')
const { createRootResolvers, registerAliasFieldResolvers } = require('../resolvers')
const { printSchema } = require('graphql')

// REVISIT: remove class with cds^8
class SchemaGenerator {
generate(services) {
this._schema = generateSchema4(services)
return this
}

getSchema() {
return this._schema
}

printSchema() {
return printSchema(this._schema)
}
}

function generateSchema4(services) {
const resolvers = createRootResolvers(services)
Expand All @@ -30,4 +13,4 @@ function generateSchema4(services) {
return schema
}

module.exports = { SchemaGenerator, generateSchema4 }
module.exports = { generateSchema4 }
32 changes: 9 additions & 23 deletions lib/schema/types/custom/GraphQLBinary.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
const { GraphQLScalarType, Kind, GraphQLError } = require('graphql')
const { GraphQLScalarType, Kind } = require('graphql')
const { getGraphQLValueError } = require('./util')

const ERROR_NON_STRING_VALUE = 'Binary cannot represent non string value'
const ERROR_NON_BASE64_OR_BASE64URL = 'Binary values must be base64 or base64url encoded and normalized strings'

const _normalizeBase64 = value => (Buffer.isBuffer(value) ? value : Buffer.from(value, 'base64')).toString('base64')

const _validateBase64String = (value, buffer, valueNode) => {
const base64Value = _toBase64(value)
const normalized = _normalizeBase64(buffer)
if (_stripPadding(base64Value) !== _stripPadding(normalized) || base64Value.length > normalized.length)
throw new GraphQLError(ERROR_NON_BASE64_OR_BASE64URL, valueNode)
const serialize = value => {
// Normalize to base64url string
const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value, 'base64')
const base64url = buffer.toString('base64url')
// Buffer base64url encoding does not have padding by default -> add it
return base64url.padEnd(Math.ceil(base64url.length / 4) * 4, '=')
}

const _toBase64 = value => value.replace(/_/g, '/').replace(/-/g, '+')
const _toBase64Url = value => value.replace(/\//g, '_').replace(/\+/g, '-')
const _stripPadding = value => value.replace(/=/g, '')

const serialize = outputValue => _toBase64Url(_normalizeBase64(outputValue))

const parseValue = inputValue => {
if (typeof inputValue !== 'string') throw getGraphQLValueError(ERROR_NON_STRING_VALUE, inputValue)

const buffer = Buffer.from(inputValue, 'base64')
_validateBase64String(inputValue, buffer)

return buffer
return Buffer.from(inputValue, 'base64')
}

const parseLiteral = valueNode => {
Expand All @@ -36,10 +25,7 @@ const parseLiteral = valueNode => {
// WORKAROUND: value could have already been parsed to a Buffer, necessary because of manual parsing in enrich AST
if (Buffer.isBuffer(value)) return value

const buffer = Buffer.from(value, 'base64')
_validateBase64String(value, buffer, valueNode)

return buffer
return Buffer.from(value, 'base64')
}

module.exports = new GraphQLScalarType({
Expand Down
2 changes: 1 addition & 1 deletion lib/schema/types/custom/GraphQLDate.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const ERROR_NON_DATE_VALUE = 'Date values must be strings in the ISO 8601 format
const _parseDate = inputValueOrValueNode => {
const date = parseDate(inputValueOrValueNode, ERROR_NON_DATE_VALUE)
// Only return YYYY-MM-DD
return date.substring(0, date.indexOf('T'))
return date.slice(0, 10)
}

const parseValue = inputValue => {
Expand Down
17 changes: 8 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cap-js/graphql",
"version": "0.10.1",
"version": "0.11.0",
"description": "CDS protocol adapter for GraphQL",
"keywords": [
"CAP",
Expand All @@ -22,12 +22,12 @@
"LICENSE"
],
"engines": {
"node": ">=14"
"node": ">=16"
},
"scripts": {
"prettier": "npx prettier --write app lib test",
"prettier:check": "npx prettier --check app lib test",
"lint": "npx eslint .",
"prettier": "npm_config_yes=true npx prettier@latest --write app lib test",
"prettier:check": "npm_config_yes=true npx prettier@latest --check app lib test",
"lint": "npm_config_yes=true npx eslint@latest .",
"test": "jest --silent",
"test:generate-schemas": "node ./test/scripts/generate-schemas.js"
},
Expand All @@ -36,15 +36,14 @@
"graphql-http": "^1.18.0"
},
"peerDependencies": {
"@sap/cds": ">=7.3"
"@sap/cds": ">=7.8"
},
"devDependencies": {
"@cap-js/graphql": "file:.",
"@cap-js/sqlite": "^1",
"axios": "^1",
"eslint": "^8",
"express": "^4.17.1",
"jest": "^29.3.1",
"semver": "^7.4.0",
"@cap-js/sqlite": "^1"
"semver": "^7.4.0"
}
}
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;
}
2 changes: 1 addition & 1 deletion test/resources/bookshop-graphql/srv/test-service.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')

module.exports = cds.service.impl(srv => {
const { Foo } = srv.entities
Expand Down
1 change: 1 addition & 0 deletions test/resources/bookshop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"index.js"
],
"dependencies": {
"@cap-js/graphql": "*",
"@sap/cds": ">=5.9",
"express": "^4.17.1"
},
Expand Down
4 changes: 4 additions & 0 deletions test/resources/custom-handlers/package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"Note: Programmatic configuration of GraphQL protocol adapter in server.js": "",
"__dependencies": {
"@cap-js/graphql": "*"
},
"devDependencies": {
"@cap-js/sqlite": "*"
}
Expand Down
13 changes: 13 additions & 0 deletions test/resources/types/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
{
"dependencies": {
"@cap-js/graphql": "*"
},
"devDependencies": {
"@cap-js/sqlite": "*"
},
"cds": {
"server": {
"body_parser": {
"limit": "110KB"
}
},
"features": {
"ieee754compatible": true
}
}
}
6 changes: 0 additions & 6 deletions test/resources/types/server.js

This file was deleted.

Loading

0 comments on commit 72f0246

Please sign in to comment.