graphql-compose / graphql-compose-mongoose

Mongoose model converter to GraphQL types with resolvers for graphql-compose https://github.com/nodkz/graphql-compose
MIT License
708 stars 94 forks source link

Fields on _operators are lost on child resolvers when using discriminators #390

Open tatejones opened 2 years ago

tatejones commented 2 years ago

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.