无需在模式中显式声明的 GraphQL 自定义指令

GraphQL custom directive without declaring explicitly in schema

我正在尝试实现自定义 GraphQL 指令。我的理解是,如果我的 SchemaDirectiveVisitor 子类实现了 static getDirectiveDeclaration(directiveName, schema),那么我 不必 必须在我的 SDL(模式定义语言)中手动声明该指令。

Because AuthDirective implements getDirectiveDeclaration, it’s no longer necessary for the schema author to include the directive @auth ... declaration explicitly in the schema. The returned GraphQLDirective object will be used to enforce the argument types and default values, as well as enabling tools like GraphiQL to discover the directive using schema introspection. Additionally, if the AuthDirective class fails to implement visitObject or visitFieldDefinition, a helpful error will be thrown.

Source: https://blog.apollographql.com/reusable-graphql-schema-directives-131fb3a177d1

However, if you’re implementing a reusable SchemaDirectiveVisitor for public consumption, you will probably not be the person writing the SDL syntax, so you may not have control over which directives the schema author decides to declare, and how. That’s why a well-implemented, reusable SchemaDirectiveVisitor should consider overriding the getDirectiveDeclaration method

Source: https://www.apollographql.com/docs/apollo-server/features/creating-directives.html

在我的代码中,尽管已经实现 static getDirectiveDeclaration(directiveName, schema) 我仍然必须在 SDL 中声明该指令。

不在SDL中手动声明不就可以了吗?

完整示例代码:

const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { DirectiveLocation, GraphQLDirective, defaultFieldResolver } = require("graphql");

class UpperCaseDirective extends SchemaDirectiveVisitor {
  static getDirectiveDeclaration(directiveName, schema) {
    console.log("inside getDirectiveDeclaration", directiveName)
    return new GraphQLDirective({
      name: directiveName,
      locations: [
        DirectiveLocation.FIELD_DEFINITION,
      ],
      args: {}
    });
  }

  visitFieldDefinition(field) {
    console.log("inside visitFieldDefinition")
    const { resolve = defaultFieldResolver } = field;
    field.resolve = async function (...args) {
      const result = await resolve.apply(this, args);
      if (typeof result === 'string') {
        return result.toUpperCase();
      }
      return result;
    };
  }
}

const books = [
  {
    title: 'Harry Potter and the Chamber of Secrets',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
];

const typeDefs = gql`

  #########################################
  # ONLY WORKS WITH THIS LINE UNCOMMENTED #
  #########################################
  directive @upper on FIELD_DEFINITION

  type Book {
    title: String
    author: String @upper
  }

  type Query {
    books: [Book]
  }
`;

const resolvers = {
  Query: {
    books: () => books,
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  schemaDirectives: {
    upper: UpperCaseDirective
  }
});

server.listen().then(({ url }) => {
  console.log(`  Server ready at ${url}`);
});

我遇到了同样的问题,并且能够从 graphql-tools issue #957 找到这条评论。

From the changelog:

NOTE: graphql 14 includes breaking changes. We're bumping the major version of graphql-tools to accommodate those breaking changes. If you're planning on using graphql 14 with graphql-tools 4.0.0, please make sure you've reviewed the graphql breaking changes list.

这可能是由于 graphql-js 现在要求您在尝试使用指令之前先在架构中定义它们。例如:

directive @upper on FIELD_DEFINITION

type TestObject {
  hello: String @upper
}

您可能可以通过在架构中预定义指令来解决此问题,但我想确认这一点。如果可行,我们需要更新文档。