From 6f1e85e319537ca5eb742f19921e8aa5afedb329 Mon Sep 17 00:00:00 2001 From: nodkz Date: Thu, 24 Jun 2021 02:19:06 +0600 Subject: [PATCH] fix(filterOperators): field operators in `filter` arg now do not create nested types without fields --- .../helpers/__tests__/filterOperators-test.ts | 67 ++++++++++++++++++- src/resolvers/helpers/filterOperators.ts | 36 +++++----- 2 files changed, 81 insertions(+), 22 deletions(-) diff --git a/src/resolvers/helpers/__tests__/filterOperators-test.ts b/src/resolvers/helpers/__tests__/filterOperators-test.ts index ccb5f641..fd0405ed 100644 --- a/src/resolvers/helpers/__tests__/filterOperators-test.ts +++ b/src/resolvers/helpers/__tests__/filterOperators-test.ts @@ -1,6 +1,7 @@ import mongoose from 'mongoose'; -import { schemaComposer, InputTypeComposer } from 'graphql-compose'; +import { schemaComposer, InputTypeComposer, SchemaComposer, graphql } from 'graphql-compose'; import { composeWithMongoose } from '../../../composeWithMongoose'; +import { composeMongoose } from '../../../composeMongoose'; import { _createOperatorsField, @@ -271,4 +272,68 @@ describe('Resolver helper `filter` ->', () => { }); }); }); + + describe('integration tests', () => { + it('should not throw error: must define one or more fields', async () => { + const OrderDetailsSchema = new mongoose.Schema( + { + productID: Number, + unitPrice: Number, + quantity: Number, + discount: Number, + }, + { + _id: false, + } + ); + + const OrderSchema = new mongoose.Schema( + { + orderID: { + type: Number, + description: 'Order unique ID', + unique: true, + }, + customerID: String, + employeeID: Number, + orderDate: Date, + requiredDate: Date, + shippedDate: Date, + shipVia: Number, + freight: Number, + shipName: String, + details: { + type: [OrderDetailsSchema], + index: true, + description: 'List of ordered products', + }, + }, + { + collection: 'northwind_orders', + } + ); + + const OrderModel = mongoose.model('Order', OrderSchema); + + const OrderTC = composeMongoose(OrderModel); + + const orderFindOneResolver = OrderTC.mongooseResolvers.findOne(); + + const sc = new SchemaComposer(); + sc.Query.addFields({ + order: orderFindOneResolver, + }); + + const schema = sc.buildSchema(); + + const res = await graphql.graphql({ + schema, + source: `{ __typename }`, + }); + + expect(res?.errors?.[0]?.message).not.toBe( + 'Input Object type FilterFindOneOrderDetailsOperatorsInput must define one or more fields.' + ); + }); + }); }); diff --git a/src/resolvers/helpers/filterOperators.ts b/src/resolvers/helpers/filterOperators.ts index c2756675..35505122 100644 --- a/src/resolvers/helpers/filterOperators.ts +++ b/src/resolvers/helpers/filterOperators.ts @@ -1,12 +1,5 @@ -import { - getNamedType, - GraphQLInputObjectType, - GraphQLScalarType, - GraphQLEnumType, - GraphQLString, -} from 'graphql-compose/lib/graphql'; import type { Model } from 'mongoose'; -import { InputTypeComposer, inspect } from 'graphql-compose'; +import { EnumTypeComposer, InputTypeComposer, inspect, ScalarTypeComposer } from 'graphql-compose'; import type { InputTypeComposerFieldConfigAsObjectDefinition } from 'graphql-compose'; import { upperFirst, getIndexesFromModel } from '../../utils'; @@ -89,24 +82,23 @@ export function _availableOperatorsFields( : availableOperators; operators.forEach((operatorName: string) => { - // unwrap from GraphQLNonNull and GraphQLList, if present - const fieldType = getNamedType(itc.getFieldType(fieldName)); + const fieldTC = itc.getFieldTC(fieldName); - if (fieldType) { + if (fieldTC) { if (['in', 'nin', 'in[]', 'nin[]'].includes(operatorName)) { // wrap with GraphQLList, if operator required this with `[]` const newName = operatorName.slice(-2) === '[]' ? operatorName.slice(0, -2) : operatorName; - fields[newName] = { type: [fieldType] as any }; + fields[newName] = { type: [fieldTC] }; } else { if (operatorName === 'exists') { fields[operatorName] = { type: 'Boolean' }; } else if (operatorName === 'regex') { // Only for fields with type String allow regex operator - if (fieldType === GraphQLString) { + if (fieldTC.getTypeName() === 'String') { fields[operatorName] = { type: GraphQLRegExpAsString }; } } else { - fields[operatorName] = { type: fieldType as any }; + fields[operatorName] = { type: fieldTC }; } } } @@ -148,15 +140,14 @@ export function _recurseSchema( } const fieldTC = sourceITC.getFieldTC(fieldName); - const fieldType = fieldTC.getType(); // prevent infinite recursion - if (sourceITC.getType() === fieldType) return; + if (sourceITC === fieldTC) return; const baseTypeName = `${opts.baseTypeName}${upperFirst(fieldName)}`; const inputFieldTypeName = `${opts.prefix || ''}${baseTypeName}${opts.suffix || ''}`; - if (fieldType instanceof GraphQLScalarType || fieldType instanceof GraphQLEnumType) { + if (fieldTC instanceof ScalarTypeComposer || fieldTC instanceof EnumTypeComposer) { if ( fieldOperatorsConfig && !Array.isArray(fieldOperatorsConfig) && @@ -181,7 +172,7 @@ export function _recurseSchema( [fieldName]: fieldOperatorsITC, }); } - } else if (fieldType instanceof GraphQLInputObjectType) { + } else if (fieldTC instanceof InputTypeComposer) { const fieldOperatorsITC = schemaComposer.createInputTC(inputFieldTypeName); _recurseSchema( fieldOperatorsITC, @@ -194,9 +185,12 @@ export function _recurseSchema( indexedFields, fieldPath ); - inputITC.addFields({ - [fieldName]: fieldOperatorsITC, - }); + + if (fieldOperatorsITC.getFieldNames().length > 0) { + inputITC.addFields({ + [fieldName]: fieldOperatorsITC, + }); + } } }); }