Java 如何向通过单例解析的查询添加自定义指令

Java 如何向通过单例解析的查询添加自定义指令,java,graphql-java,graphql-spqr,Java,Graphql Java,Graphql Spqr,我已经设法将自定义指令添加到GraphQL模式中,但我正在努力解决如何将自定义指令添加到字段定义中的问题。关于正确实现的任何提示都将非常有用。 我正在使用GraphQL SPQR 0.9.6生成我的模式目前不可能这样做。GraphQL SPQR v0.9.9将是第一个支持自定义指令的版本 尽管如此,在0.9.8中仍然有一个可能的解决方案,这取决于您试图实现的目标。SPQR自己关于字段或类型的元数据保存在自定义指令中。知道了这一点,您就可以掌握GraphQL字段定义背后的Java方法/字段。如果您

我已经设法将自定义指令添加到GraphQL模式中,但我正在努力解决如何将自定义指令添加到字段定义中的问题。关于正确实现的任何提示都将非常有用。
我正在使用GraphQL SPQR 0.9.6生成我的模式

目前不可能这样做。GraphQL SPQR v0.9.9将是第一个支持自定义指令的版本

尽管如此,在0.9.8中仍然有一个可能的解决方案,这取决于您试图实现的目标。SPQR自己关于字段或类型的元数据保存在自定义指令中。知道了这一点,您就可以掌握GraphQL字段定义背后的Java方法/字段。如果您想要的是一个基于指令执行某些操作的工具,那么您可以取而代之的是获取底层元素上的任何注释,拥有Java的全部功能

获取该方法的方法如下所示:

Operation operation = Directives.getMappedOperation(env.getField()).get();
Resolver resolver = operation.getApplicableResolver(env.getArguments().keySet());
Member underlyingElement = resolver.getExecutable().getDelegate();
更新: 我在网上贴了一个巨大的答案。把它也贴在这里

您可以注册其他指令,例如:

generator.withSchemaProcessors(
    (schemaBuilder, buildContext) -> schemaBuilder.additionalDirective(...));
但是(根据我目前的理解),这只适用于查询指令(客户端作为查询的一部分发送的内容,如
@skip
@defered

@dateFormat
这样的指令在SPQR中根本没有意义:它们在解析SDL并将其映射到代码时帮助您。在SPQR中,没有SDL,您从代码开始。 例如,
@dateFormat
用于告诉您在将特定字段映射到Java时需要为其提供日期格式。在SPQR中,您从Java部分开始,GraphQL字段是从Java方法生成的,因此该方法必须已经知道它应该返回什么格式。或者它已经有了适当的注释在SPQR中,Java是真理的源泉。您可以使用注释来提供额外的映射信息。指令基本上是SDL中的注释

尽管如此,字段或类型级指令(或注释)在检测中还是非常有用的。例如,如果要截取字段解析并检查验证指令。 在这种情况下,我建议您只需出于同样的目的使用注释

public class BookService {

      @Auth(roles= {"Admin"}) //example custom annotation
      public Book addBook(Book book) { /*insert a Book into the DB */ }
}
由于每个GraphQLFieldDefinition都有一个Java方法(或字段)支持,因此您可以在拦截器中或任何位置获取底层对象:

GraphQLFieldDefinition field = ...;
Operation operation = Directives.getMappedOperation(field).get();

//Multiple methods can be hooked up to a single GraphQL operation. This gets the @Auth annotations from all of them
Set<Auth> allAuthAnnotations = operation.getResolvers().stream()
                .map(res -> res.getExecutable().getDelegate()) //get the underlying method
                .filter(method -> method.isAnnotationPresent(Auth.class))
                .map(method -> method.getAnnotation(Auth.class))
                .collect(Collectors.toSet());
然后,您可以根据需要检查注释,例如:

Set<String> allNeededRoles = allAuthAnnotations.stream()
                                             .flatMap(auth -> Arrays.stream(auth.roles))
                                             .collect(Collectors.toSet());

if (!currentUser.getRoles().containsAll(allNeededRoles)) {
    throw new AccessDeniedException(); //or whatever is appropriate
}
确保你也阅读了你想要的东西

您可以使用检测以相同的方式动态筛选结果:在方法上添加注释,从检测访问注释,并动态处理结果:

public class BookService {

      @Filter("title ~ 'Monkey'") //example custom annotation
      public List<Book> findBooks(...) { /*get books from the DB */ }
}

new SimpleInstrumentation() {

    // You can also use beginFieldFetch and then onCompleted instead of instrumentDataFetcher
    @Override
    public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters) {
        GraphQLFieldDefinition field = parameters.getEnvironment().getFieldDefinition();
        Optional<String> filterExpression = Directives.getMappedOperation(field)
                .map(operation ->
                        operation.getApplicableResolver(parameters.getEnvironment().getArguments().keySet())
                                .getExecutable().getDelegate()
                                .getAnnotation(Filter.class).value()); //get the filtering expression from the annotation
        return filterExpression.isPresent() ? env -> filterResultBasedOn Expression(dataFetcher.get(parameters.getEnvironment()), filterExpression) : dataFetcher;
    }
}
同样,这可能只有在仪器中才有意义。因为通常指令会提供额外的信息来将SDL映射到GraphQL类型。在SPQR中,您将Java类型映射到GraphQL类型,因此在大多数情况下,指令在该上下文中没有意义

当然,如果您仍然需要对类型执行实际的GraphQL指令,则始终可以提供一个自定义的
TypeMapper
,将它们放在那里

对于字段上的指令,目前在0.9.8中是不可能的

0.9.9将在任何元素上提供完整的自定义指令支持,以防您仍然需要它们

更新2:已退出

现在支持自定义指令。有关详细信息,请参阅问题

使用
@graphqldirection
注释的任何自定义注释元都将映射为注释元素上的指令

例如,想象一个自定义注释
@Auth(requiredRole=“Admin”)
用于表示访问限制:

@GraphQLDirective //Should be mapped as a GraphQLDirective
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) //Applicable to methods
public @interface Auth {
        String requiredRole();
}
如果随后使用
@Auth
对解析器方法进行注释:

@GraphQLMutation
@Auth(requiredRole = {"Admin"})
public Book addBook(Book newBook) { ... }
生成的GraphQL字段填充如下所示:

type Mutation {
  addBook(newBook: BookInput): Book @auth(requiredRole : "Admin")
}
也就是说,由于存在
@graphqldirection
元注释,因此
@Auth
注释被映射到一个指令

可以通过以下方式添加客户机指令:
GraphQLSchemaGenerator#以及附加指令(java.lang.reflect.Type…

SPQR 0.9.9还附带了
ResolverInterceptor
s,它可以拦截解析器方法调用并检查注释/指令。它们的使用比
插装
s方便得多,但不那么通用(范围更为有限)。有关详细信息,请参阅问题,有关用法示例,请参阅

例如,要使用上面的
@Auth
注释(不是说
@Auth
不需要是一个指令就可以工作):

如果
@Auth
是一个指令,您也可以通过常规API获得它,例如

List<GraphQLDirective> directives = dataFetchingEnvironment.getFieldDefinition().get.getDirectives();
DirectivesUtil.directivesByName(directives);
List directives=dataFetchingEnvironment.getFieldDefinition().get.getDirectives();
DirectivesUtil.directivesByName(指令);

我的回答提到了当前状态和一些可能的解决方法,但如果我更准确地了解您的用例,我可能会想出更多的解决方法。在开发SPQR中对自定义指令的支持时,了解自定义指令是如何使用的也是很有用的。
@GraphQLMutation
@Auth(requiredRole = {"Admin"})
public Book addBook(Book newBook) { ... }
type Mutation {
  addBook(newBook: BookInput): Book @auth(requiredRole : "Admin")
}
public class AuthInterceptor implements ResolverInterceptor {

    @Override
    public Object aroundInvoke(InvocationContext context, Continuation continuation) throws Exception {
        Auth auth = context.getResolver().getExecutable().getDelegate().getAnnotation(Auth.class);
        User currentUser = context.getResolutionEnvironment().dataFetchingEnvironment.getContext();
        if (auth != null && !currentUser.getRoles().containsAll(Arrays.asList(auth.rolesRequired()))) {
            throw new IllegalAccessException("Access denied"); // or return null
            }
        return continuation.proceed(context);
    }
}
List<GraphQLDirective> directives = dataFetchingEnvironment.getFieldDefinition().get.getDirectives();
DirectivesUtil.directivesByName(directives);