Graphql-tools: Multiple detectives don't execute LTR

Created on 12 Feb 2018  路  8Comments  路  Source: ardatan/graphql-tools

Multiple detectives don't execute LTR

The problem

When using multiple directives on the same field, only the last directive is actually executed.

Example code

import * as bodyParser from 'body-parser';
import * as express from 'express';
import { graphqlExpress } from 'apollo-server-express';
import { makeExecutableSchema } from 'graphql-tools';

const typeDefs = [
  `
  directive @directiveOne on FIELD_DEFINITION 
  directive @directiveTwo on FIELD_DEFINITION

  type Query {
    queryOne: String @directiveOne
    queryTwo: String @directiveTwo
    queryThree: String @directiveOne @directiveTwo
  }

  schema {
    query: Query
  }
`
];

const resolvers = {
  Query: {
    queryOne(obj, args, context, info): Promise<String> {
      return Promise.resolve('Query result one');
    },
    queryTwo(obj, args, context, info): Promise<String> {
      return Promise.resolve('Query result two');
    },
    queryThree(obj, args, context, info): Promise<String> {
      return Promise.resolve('Query result three');
    }
  }
};

const directiveResolvers = {
  directiveOne(fieldResolver, root, args, context): Promise<any> {
    console.log('directive one starts to execute');

    if (context.something) {
      return fieldResolver();
    }

    return Promise.reject(
      'Sorry, somthing is not true while directive one executes'
    );
  },
  directiveTwo(fieldResolver, root, args, context): Promise<any> {
    console.log('directive two starts to execute');

    if (context.something) {
      return fieldResolver();
    }

    return Promise.reject(
      'Sorry, somthing is not true while directive two executes'
    );
  }
};

const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
  directiveResolvers
});

const app = express();

app.use(
  '/graphql',
  bodyParser.json(),
  graphqlExpress((req, res) => {
    return {
      schema,
      context: {
        something: false
      }
    };
  })
);

app.listen(8003);

Expected behaviour

The following query should fail on directiveOne before execution directiveTwo.

{
  queryThree
}

Actual behaviour

The query above never calls directiveOne but only calls directiveTwo and fails with the the message Error: Sorry, somthing is not true while directive two executes.

Reason

The current tests with the directives @upper and @lower don't actually test this particular scenario. The way the tests are currently setup the test would still pass if only the last directive is called, see testSchemaGenrator.ts.

Also the are currently no tests for directives that prevents the execution of the field resolver. This would be really useful when implementing authorisation with custom directives.

Most helpful comment

Hi, going to close this issue. We've also updated the way schema directives work to make more things possible.

Better documentation would still be appreciated!

All 8 comments

@giautm Just tried with trowing errors rather then returning a rejected promise.

const directiveResolvers = {
  async directiveOne(fieldResolver, root, args, context): Promise<any> {
    console.log('directive one starts to execute');

    if (context.something) {
      return fieldResolver();
    }

    throw new Error(
      'Sorry, something is not true while directive one executes'
    );
  },
  async directiveTwo(fieldResolver, root, args, context): Promise<any> {
    console.log('directive two starts to execute');

    if (context.something) {
      return fieldResolver();
    }

    throw new Error(
      'Sorry, something is not true while directive two executes'
    );
  }
};

Still only logs the messages directive two starts to execute and Error: Sorry, somthing is not true while directive two executes.

It should at least also log directive one starts to execute.

I think that the issue is that in the test cases and example you work with the resolved value of next()(the field resolver). I this case and if you want to use it for authentication or authorization, you might not want to execute the field resolver at all. (e.x. on a createUser mutation that with a @isAuthorized directive.)

When I do resolve the fields like in the example it still does not behave as expected.

const directiveResolvers = {
  async directiveOne(fieldResolver, root, args, context): Promise<any> {
    console.log('directive one starts to execute');

    const resolvedField = await fieldResolver();

    if (context.something) {
      return resolvedField;
    }

    throw new Error(
      'Sorry, something is not true while directive one executes'
    );
  },
  async directiveTwo(fieldResolver, root, args, context): Promise<any> {
    console.log('directive two starts to execute');

    const resolvedField = await fieldResolver();

    if (context.something) {
      return resolvedField;
    }

    throw new Error(
      'Sorry, something is not true while directive two executes'
    );
  }
};

This logs:

directive two starts to execute
directive one starts to execute
Error: Sorry, something is not true while directive one executes

Which made me think I might understood it all wrong, and that it still blocked the actual execution of the field resolver.

So a added some logs in the field resolvers resolver to find out

const resolvers = {
  Query: {
    queryOne(obj, args, context, info): Promise<String> {
      console.log('resolver one starts to execute');
      return Promise.resolve('Query result one');
    },
    queryTwo(obj, args, context, info): Promise<String> {
      console.log('resolver two starts to execute');
      return Promise.resolve('Query result two');
    },
    queryThree(obj, args, context, info): Promise<String> {
      console.log('resolver three starts to execute');
      return Promise.resolve('Query result three');
    }
  }
};

It does actually still fire the field resolver, but does not return it's result to the client

directive two starts to execute
directive one starts to execute
resolver three starts to execute
Error: Sorry, something is not true while directive one executes

I think I have a confusion here.
The results are returned in the left-to-right direction. But the execution happens in the opposite direction, right-to-left.

Because the outermost directive will be on the top of the directive list.

In my example, I called the next resolver before returning the result.

In your case, changing the order of the directives will resolve the issue.

queryThree: String @directiveTwo @directiveOne

Sorry about this issue, I will update test-case and document.

@giautm That actually makes a load of sense.

I might be useful to add such an example to the docs, since I'm quite sure I won't be the only one that will try to use it this way. (Will avoid a load of open issues like this.)

When you open the PR for the extra tests, I can help write those docs if you like.

Cheers!

Hi, going to close this issue. We've also updated the way schema directives work to make more things possible.

Better documentation would still be appreciated!

Just wondering: what is the meaning of "LTR" ?

@Renaud009 It means: Left To Right. Hope that helps. 馃憤馃徏

Well at the moment my multi-directives are clearly executed Right-to-Left juste like @giautm answer https://github.com/apollographql/graphql-tools/issues/630#issuecomment-365158687 !!

@Renaud009, Of course, it works now, they fixed the issue. Would be weird if it did not 馃.

Hi, going to close this issue. We've also updated the way schema directives work to make more things possible.

Better documentation would still be appreciated!

Was this page helpful?
0 / 5 - 0 ratings