From e49f79e7d1cd1b38201eb6efcc923e5d2d70b517 Mon Sep 17 00:00:00 2001 From: Pho Nguyen Date: Sat, 13 Mar 2021 13:04:06 +0700 Subject: [PATCH] Generate name for query input type --- .gitignore | 1 + src/core/query-input-type-builder.test.ts | 89 +++++++++++++++++++++++ src/core/query-input-type-builder.ts | 68 +++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 src/core/query-input-type-builder.test.ts create mode 100644 src/core/query-input-type-builder.ts diff --git a/.gitignore b/.gitignore index 5ceb5c7..99340aa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist coverage .env play +dump.rdb diff --git a/src/core/query-input-type-builder.test.ts b/src/core/query-input-type-builder.test.ts new file mode 100644 index 0000000..669cc74 --- /dev/null +++ b/src/core/query-input-type-builder.test.ts @@ -0,0 +1,89 @@ +import * as td from 'testdouble' +import * as graphql from 'graphql' +import { TestUtils } from '../helpers' +import * as _ from 'lodash' +import { expect } from 'chai' +import { QueryInputTypeBuilder } from './query-input-type-builder' + +describe(TestUtils.getTestTitle(__filename), () => { + it('#build', () => { + const { type } = new QueryInputTypeBuilder() + .build() + + expect(type).instanceOf(graphql.GraphQLInputObjectType) + }) + + it('#addQueryField', () => { + const builder = new QueryInputTypeBuilder() + + builder + .addQueryField({ + field: 'created', + operators: ['gte', 'lte'], + required: true, + type: graphql.GraphQLString, + }) + .addQueryField({ + field: 'status', + operators: ['in'], + required: false, + type: graphql.GraphQLString, + }) + + expect(builder['queryFields'].length).to.equal(2) + }) + + it('#getFields', async () => { + const builder = new QueryInputTypeBuilder() + td.replace(builder, 'queryFields', [ + { + field: 'email', + operators: ['eq', 'in'], + type: graphql.GraphQLString, + }, + { + field: 'email', + operators: ['exists'], + type: graphql.GraphQLString, + }, + { + field: 'role', + operators: ['in'], + require: true, + type: graphql.GraphQLInt, + }, + ]) + + expect(_.keys(builder['getFields']())).to.deep.equal( + ['email', 'role'] + ) + }) + + it('#getInputTypeByFields', () => { + const builder = new QueryInputTypeBuilder() + const inputType = builder['getInputTypeByFields']([ + { + field: 'email', + operators: ['eq', 'in'], + type: graphql.GraphQLString, + }, + { + field: 'email', + operators: ['exists'], + type: graphql.GraphQLString, + required: true, + }, + ]) + + const expected = new graphql.GraphQLInputObjectType({ + name: 'RandomName', + fields: { + eq: { type: graphql.GraphQLString }, + in: { type: graphql.GraphQLList(graphql.GraphQLString) }, + exists: { type: graphql.GraphQLNonNull(graphql.GraphQLBoolean) }, + }, + }) + + expect(inputType.toConfig().fields).to.deep.equal(expected.toConfig().fields) + }) +}) diff --git a/src/core/query-input-type-builder.ts b/src/core/query-input-type-builder.ts new file mode 100644 index 0000000..a10d863 --- /dev/null +++ b/src/core/query-input-type-builder.ts @@ -0,0 +1,68 @@ +import * as graphql from 'graphql' +import * as _ from 'lodash' +import { customAlphabet } from 'nanoid' + +interface IQueryField { + field: string, + type: graphql.GraphQLScalarType + operators: string[] + required?: boolean +} + +const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 8) + +export class QueryInputTypeBuilder { + private queryFields: IQueryField[] = [] + + build() { + const required = _.some(this.queryFields, 'required') + const type = new graphql.GraphQLInputObjectType({ + name: `GeneratedQueryType_${nanoid()}`, + fields: this.getFields(), + }) + if (required) return { type: graphql.GraphQLNonNull(type) } + return { type } + } + + addQueryField(queryField: IQueryField) { + this.queryFields.push(queryField) + return this + } + + private getFields() { + return _.chain(this.queryFields) + .groupBy('field') + .mapValues(fields => { + const type = this.getInputTypeByFields(fields) + if (_.some(fields, 'required')) return ({ type: graphql.GraphQLNonNull(type) }) + return { type } + }) + .value() + } + + private getInputTypeByFields(queryFields: IQueryField[]): graphql.GraphQLInputObjectType { + const { type, field } = _.first(queryFields)! + const operatorConfigs = { + eq: type, + gte: type, + lte: type, + in: graphql.GraphQLList(type), + exists: graphql.GraphQLBoolean, + } + + const fields = _.chain(queryFields) + .map(({ operators, required }) => operators.map(operator => ({ operator, required }))) + .flatMap() + .map(({ operator, required }) => { + const operatorType = operatorConfigs[operator] + return { [operator]: { type: required ? graphql.GraphQLNonNull(operatorType) : operatorType } } + }) + .reduce((a, b) => ({ ...a, ...b })) + .value() + + return new graphql.GraphQLInputObjectType({ + name: `QueryQueryTypeForField${_.capitalize(field)}_${nanoid()}`, + fields, + }) + } +}