graphql-compose-mongoose icon indicating copy to clipboard operation
graphql-compose-mongoose copied to clipboard

Fields on _operators are lost on child resolvers when using discriminators

Open tatejones opened this issue 3 years ago • 0 comments

When using discriminators the prepareChildResolver process (copyResolverArgTypes) will treat _operators, AND and OR as fields and call extendField from the baseResolver args to the childResolver args. This will overwrite the contents of these fields removing any specialisation found in the child resolver.

A child based query will result in a GraphQLError

        clickedLinkEventFindMany( filter: { AND: [ { _operators: { url: { in: [ "url1", "url2" ] } } } ] }) {
          __typename
          refId
          url
        }
"errors": Array [
     [GraphQLError: Field "url" is not defined by type "FilterFindManyEventOperatorsInput".],
 ],

Note: it has no knowledge of the "FilterFindManyClickedLinkEventOperatorsInput" as it has been overwritten by "FilterFindManyEventOperatorsInput"

The issue is with copyResolverArgTypes on ./src/discriminators/prepareChildResolvers.ts

 for (const baseArgField of baseResolverArgTCFields) {
          if (childResolverArgTC.hasField(baseArgField) && baseArgField !== '_id') {
            childResolverArgTC.extendField(baseArgField, {
              type: baseResolverArgTC.getField(baseArgField).type,
            });
          }
        }

It checks for '_id', but '_operators', 'AND' and 'OR' are extended overwriting the childResolver details.

I believe the fix is

@@ -85,7 +86,10 @@ function copyResolverArgTypes(
         const baseResolverArgTCFields = baseResolverArgTC.getFieldNames();
 
         for (const baseArgField of baseResolverArgTCFields) {
-          if (childResolverArgTC.hasField(baseArgField) && baseArgField !== '_id') {
+          if (
+            childResolverArgTC.hasField(baseArgField) &&
+            ['_id', OPERATORS_FIELDNAME, 'OR', 'AND'].indexOf(baseArgField) === -1
+          ) {
             childResolverArgTC.extendField(baseArgField, {
               type: baseResolverArgTC.getField(baseArgField).type,
             });

Integration test that produces the problem

Must have an index field to be included in _operators

 const eventSchema = new mongoose.Schema(
    { refId: String, name: { type: String, index: true } },
    options
  );
  it('perform filter operation on a child model', async () => {
    // let's check graphql response
    await Event.deleteMany({});
    await Event.create({ refId: 'aaa', name: 'aName' });
    await Event.create({ refId: 'bbb', name: 'bName' });
    await ClickedLinkEvent.create({ refId: 'ccc', name: 'cName', url: 'url1' });
    await ClickedLinkEvent.create({ refId: 'ddd', name: 'dName', url: 'url2' });

    schemaComposer.Query.addFields({
      clickedLinkEventFindMany: ClickedLinkEventTC.getResolver('findMany'),
    });

    const schema = schemaComposer.buildSchema();

    const res = await graphql.graphql(
      schema,
      `{
        clickedLinkEventFindMany( filter: { AND: [ { _operators: { url: { in: [ "url1", "url2" ] } } }, { name: "dName" } ] }) {
          __typename
          refId
          name
          url
        }
      }`
    );

    expect(res).toEqual({
      data: {
        clickedLinkEventFindMany: [
          { __typename: 'ClickedLinkEvent', refId: 'ddd', name: 'dName', url: 'url2' },
        ],
      },
    });
  });

A pull request will be made and the appropriate fix plus tests will be submitted for review.

tatejones avatar Nov 03 '21 22:11 tatejones