Skip to content

Commit

Permalink
fix(filterOperators): field operators in filter arg now do not crea…
Browse files Browse the repository at this point in the history
…te nested types without fields
  • Loading branch information
nodkz committed Jun 23, 2021
1 parent 4c7de01 commit 6f1e85e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 22 deletions.
67 changes: 66 additions & 1 deletion src/resolvers/helpers/__tests__/filterOperators-test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<any>('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.'
);
});
});
});
36 changes: 15 additions & 21 deletions src/resolvers/helpers/filterOperators.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 };
}
}
}
Expand Down Expand Up @@ -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) &&
Expand All @@ -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,
Expand All @@ -194,9 +185,12 @@ export function _recurseSchema(
indexedFields,
fieldPath
);
inputITC.addFields({
[fieldName]: fieldOperatorsITC,
});

if (fieldOperatorsITC.getFieldNames().length > 0) {
inputITC.addFields({
[fieldName]: fieldOperatorsITC,
});
}
}
});
}
Expand Down

0 comments on commit 6f1e85e

Please sign in to comment.