Skip to content

Commit

Permalink
update apollo server and graphql (#226)
Browse files Browse the repository at this point in the history
* update apollo server and graphql
  • Loading branch information
ocshawn authored Aug 8, 2023
1 parent 5dd094e commit 7ee2eaa
Show file tree
Hide file tree
Showing 11 changed files with 1,444 additions and 1,789 deletions.
3,007 changes: 1,290 additions & 1,717 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@
},
"homepage": "https://github.com/uc-cdis/guppy#readme",
"dependencies": {
"@apollo/server": "^4.7.5",
"@elastic/elasticsearch": "~7.13.0",
"@gen3/ui-component": "^0.11.4",
"apollo-server": "^2.4.8",
"apollo-server-express": "^2.4.8",
"array.prototype.flat": "^1.2.2",
"array.prototype.flatmap": "^1.3.1",
"body-parser": "^1.20.2",
Expand All @@ -40,11 +39,11 @@
"express": "^4.18.2",
"file-saver": "^2.0.5",
"flat": "^5.0.2",
"graphql": "^14.1.1",
"graphql": "^16.7.1",
"graphql-depth-limit": "^1.1.0",
"graphql-middleware": "^3.0.2",
"graphql-parse-resolve-info": "^4.1.0",
"graphql-tools": "^4.0.4",
"graphql-middleware": "^6.1.34",
"graphql-parse-resolve-info": "^4.13.0",
"graphql-tools": "^9.0.0",
"graphql-type-json": "^0.3.2",
"helmet": "^7.0.0",
"isomorphic-fetch": "^3.0.0",
Expand Down
8 changes: 4 additions & 4 deletions src/server/es/__tests__/filter.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// eslint-disable-next-line
import nock from 'nock'; // must import this to enable mock data by nock
import { UserInputError } from 'apollo-server';
import { GraphQLError } from 'graphql';
import getFilterObj from '../filter';
import esInstance from '../index';
import setupMockDataEndpoint from '../../__mocks__/mockDataFromES';
Expand Down Expand Up @@ -216,12 +216,12 @@ describe('Transfer GraphQL filter to ES filter, filter unit', () => {
expect(() => { // for string field
const gqlFilter = { '+': { gender: 'female' } };
getFilterObj(esInstance, esIndex, esType, gqlFilter);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);

expect(() => { // for int field
const gqlFilter = { '+': { file_count: 10 } };
getFilterObj(esInstance, esIndex, esType, gqlFilter);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);
});

test('could throw err for nonexisting field', async () => {
Expand All @@ -230,7 +230,7 @@ describe('Transfer GraphQL filter to ES filter, filter unit', () => {
expect(() => { // for string field
const gqlFilter = { '=': { strange_field: 'value' } };
getFilterObj(esInstance, esIndex, esType, gqlFilter);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);
});
});

Expand Down
18 changes: 9 additions & 9 deletions src/server/es/__tests__/sort.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// eslint-disable-next-line
import nock from 'nock'; // must import this to enable mock data by nock
import { UserInputError } from 'apollo-server';
import { GraphQLError } from 'graphql';
import getESSortBody from '../sort';
import esInstance from '../index';
import setupMockDataEndpoint from '../../__mocks__/mockDataFromES';
Expand Down Expand Up @@ -125,44 +125,44 @@ describe('Transfer GraphQL sort argument to ES sort argument', () => {
expect(() => {
const graphQLSort = { invalid_field: 'asc' };
getESSortBody(graphQLSort, esInstance, esIndex);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);

expect(() => {
const graphQLSort = [{ invalid_field: 'asc' }];
getESSortBody(graphQLSort, esInstance, esIndex);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);

expect(() => {
const graphQLSort = { gender: 'female', invalid_field: 'asc' };
getESSortBody(graphQLSort, esInstance, esIndex);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);

expect(() => {
const graphQLSort = [{ gender: 'female', 'visits.invalid_field': 'asc' }];
getESSortBody(graphQLSort, esInstance, esIndex);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);
});

test('array format sort arg with invalid method', async () => {
await esInstance.initialize();
expect(() => {
const graphQLSort = { gender: 'invalid_method' };
getESSortBody(graphQLSort, esInstance, esIndex);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);

expect(() => {
const graphQLSort = [{ gender: 'invalid_method' }];
getESSortBody(graphQLSort, esInstance, esIndex);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);

expect(() => {
const graphQLSort = { gender: 'asc', file_count: 'invalid_method' };
getESSortBody(graphQLSort, esInstance, esIndex);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);

expect(() => {
const graphQLSort = { gender: 'asc', 'visits.visit_label': 'invalid_method' };
getESSortBody(graphQLSort, esInstance, esIndex);
}).toThrow(UserInputError);
}).toThrow(GraphQLError);
});
});
26 changes: 21 additions & 5 deletions src/server/es/aggs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UserInputError } from 'apollo-server';
import { GraphQLError } from 'graphql';
import getFilterObj from './filter';
import {
AGGS_GLOBAL_STATS_NAME,
Expand Down Expand Up @@ -447,16 +447,32 @@ export const numericAggregation = async (
},
) => {
if (rangeStep <= 0) {
throw new UserInputError(`Invalid rangeStep ${rangeStep}`);
throw new GraphQLError(`Invalid rangeStep ${rangeStep}`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
if (rangeStart > rangeEnd) {
throw new UserInputError(`Invalid rangeStart (${rangeStep}) > rangeEnd (${rangeEnd})`);
throw new GraphQLError(`Invalid rangeStart (${rangeStep}) > rangeEnd (${rangeEnd})`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
if (binCount <= 0) {
throw new UserInputError(`Invalid binCount ${binCount}`);
throw new GraphQLError(`Invalid binCount ${binCount}`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
if (typeof rangeStep !== 'undefined' && typeof binCount !== 'undefined') {
throw new UserInputError('Invalid to set "rangeStep" and "binCount" at same time');
throw new GraphQLError('Invalid to set "rangeStep" and "binCount" at same time', {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
if (typeof rangeStep !== 'undefined') {
return numericHistogramWithFixedRangeStep(
Expand Down
68 changes: 56 additions & 12 deletions src/server/es/filter.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { ApolloError, UserInputError } from 'apollo-server';
import { GraphQLError } from 'graphql';
import { esFieldNumericTextTypeMapping, NumericTextTypeTypeEnum } from './const';
import config from '../config';

Expand All @@ -11,7 +11,11 @@ const fromPathToNode = (esInstance, esIndex, path) => {
if (n in node) {
node = node[n].properties;
} else {
throw new UserInputError(`Field ${n} does not exist in ES index`);
throw new GraphQLError(`Field ${n} does not exist in ES index`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
});
}
Expand All @@ -38,11 +42,19 @@ const getNumericTextType = (
) => {
const node = fromPathToNode(esInstance, esIndex, path);
if (!esInstance.fieldTypes[esIndex] || !node[field]) {
throw new UserInputError('Please check your syntax for input "filter" argument');
throw new GraphQLError('Please check your syntax for input "filter" argument', {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
const numericTextType = esFieldNumericTextTypeMapping[node[field].type];
if (typeof numericTextType === 'undefined') {
throw new ApolloError(`ES type ${node[field].type} not supported.`, 500);
throw new GraphQLError(`ES type ${node[field].type} not supported.`, {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
},
});
}
return numericTextType;
};
Expand Down Expand Up @@ -122,7 +134,11 @@ const getFilterItemForString = (op, pField, value, path) => {
},
};
default:
throw new UserInputError(`Invalid operation "${op}" in filter argument.`);
throw new GraphQLError(`Invalid operation "${op}" in filter argument.`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
};

Expand Down Expand Up @@ -181,7 +197,11 @@ const getFilterItemForNumbers = (op, pField, value, path) => {
},
};
}
throw new UserInputError(`Invalid numeric operation "${op}" for field "${field}" in filter argument`);
throw new GraphQLError(`Invalid numeric operation "${op}" for field "${field}" in filter argument`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
};

const getESSearchFilterFragment = (esInstance, esIndex, fields, keyword) => {
Expand All @@ -193,9 +213,17 @@ const getESSearchFilterFragment = (esInstance, esIndex, fields, keyword) => {
// Check fields are valid
fields.forEach((f) => {
if (!esInstance.fieldTypes[esIndex]) {
throw new UserInputError(`es index ${esIndex} doesn't exist`);
throw new GraphQLError(`es index ${esIndex} doesn't exist`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
} else if (!esInstance.fieldTypes[esIndex][f]) {
throw new UserInputError(`invalid field ${f} in "filter" variable`);
throw new GraphQLError(`invalid field ${f} in "filter" variable`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
});
analyzedFields = fields.map((f) => `${f}${config.analyzedTextFieldSuffix}`);
Expand Down Expand Up @@ -285,16 +313,28 @@ const getFilterObj = (
}
} else if (topLevelOpLowerCase === 'search') {
if (!('keyword' in graphqlFilterObj[topLevelOp])) { // "keyword" required
throw new UserInputError('Invalid search filter syntax: missing \'keyword\' field');
throw new GraphQLError('Invalid search filter syntax: missing \'keyword\' field', {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
Object.keys(graphqlFilterObj[topLevelOp]).forEach((o) => { // check filter syntax
if (o !== 'keyword' && o !== 'fields') {
throw new UserInputError(`Invalid search filter syntax: unrecognized field '${o}'`);
throw new GraphQLError(`Invalid search filter syntax: unrecognized field '${o}'`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
});
const targetSearchKeyword = graphqlFilterObj[topLevelOp].keyword;
if (targetSearchKeyword.length < config.allowedMinimumSearchLen) {
throw new UserInputError(`Keyword too short (length < ${config.allowedMinimumSearchLen}`);
throw new GraphQLError(`Keyword too short (length < ${config.allowedMinimumSearchLen}`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
const targetSearchFields = graphqlFilterObj[topLevelOp].fields;
resultFilterObj = getESSearchFilterFragment(
Expand Down Expand Up @@ -348,7 +388,11 @@ const getFilterObj = (
} else if (numericOrTextType === NumericTextTypeTypeEnum.ES_NUMERIC_TYPE) {
resultFilterObj = getFilterItemForNumbers(topLevelOp, field, value, objPath);
} else {
throw new ApolloError(`Invalid es field type ${numericOrTextType}`, 500);
throw new GraphQLError(`Invalid es field type ${numericOrTextType}`, {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
},
});
}
}
return resultFilterObj;
Expand Down
10 changes: 7 additions & 3 deletions src/server/es/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Client } from '@elastic/elasticsearch';
import _ from 'lodash';
import { UserInputError } from 'apollo-server';
import { GraphQLError } from 'graphql';
import config from '../config';
import getFilterObj from './filter';
import getESSortBody from './sort';
Expand Down Expand Up @@ -475,9 +475,13 @@ class ES {
esIndex, esType, fields, filter, sort, offset, size,
}) {
if (typeof size !== 'undefined' && offset + size > SCROLL_PAGE_SIZE) {
throw new UserInputError(`Large graphql query forbidden for offset + size > ${SCROLL_PAGE_SIZE},
throw new GraphQLError(`Large graphql query forbidden for offset + size > ${SCROLL_PAGE_SIZE},
offset = ${offset} and size = ${size},
please use download endpoint for large data queries instead.`);
please use download endpoint for large data queries instead.`, {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
const result = await this.filterData(
{ esInstance: this, esIndex, esType },
Expand Down
26 changes: 21 additions & 5 deletions src/server/es/sort.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UserInputError } from 'apollo-server';
import { GraphQLError } from 'graphql';

/**
* Transfer graphql sort arg to ES sort object
Expand Down Expand Up @@ -33,17 +33,29 @@ const getESSortBody = (graphqlSort, esInstance, esIndex) => {
// check fields and sort methods are valid
for (let i = 0; i < graphqlSortObj.length; i += 1) {
if (!graphqlSortObj[i] || Object.keys(graphqlSortObj[i]).length !== 1) {
throw new UserInputError('Invalid sort argument');
throw new GraphQLError('Invalid sort argument', {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
const field = Object.keys(graphqlSortObj[i])[0];
const method = graphqlSortObj[i][field];
if (method !== 'asc' && method !== 'desc') {
throw new UserInputError('Invalid sort argument');
throw new GraphQLError('Invalid sort argument', {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
if (!field.includes('.')) {
// non-nested field name, normal check logic
if (typeof esInstance.fieldTypes[esIndex][field] === 'undefined') {
throw new UserInputError('Invalid sort argument');
throw new GraphQLError('Invalid sort argument', {
extensions: {
code: 'BAD_USER_INPUT',
},
});
} else {
sortBody.push({
[field]: {
Expand All @@ -60,7 +72,11 @@ const getESSortBody = (graphqlSort, esInstance, esIndex) => {
if (fieldTypesToCheck && fieldTypesToCheck[FieldNameToCheck]) {
fieldTypesToCheck = fieldTypesToCheck[FieldNameToCheck].properties;
} else {
throw new UserInputError('Invalid sort argument');
throw new GraphQLError('Invalid sort argument', {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
}
// if we got here, everything looks good
Expand Down
Loading

0 comments on commit 7ee2eaa

Please sign in to comment.