如何将自定义指令添加到通过单例解析的查询中



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

原始答案:(现已过时,请参阅下面的2个更新)

目前不可能做到这一点。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();

更新:我发布了一个关于这个GitHub问题的巨大答案。也粘贴在这里。

您可以注册一个附加指令:

generator.withSchemaProcessors(
(schemaBuilder, buildContext) -> schemaBuilder.additionalDirective(...));

但是(根据我目前的理解),这只对查询指令(客户端作为查询的一部分发送的东西,如@skip@deffered)有意义。

@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());

或者,只检查可以处理当前请求的方法:

DataFetchingEnvironment env = ...; //get it from the instrumentation params      
Auth auth = operation.getApplicableResolver(env.getArguments().keySet()).getExecutable().getDelegate().getAnnotation(Auth.class);

然后,您可以根据自己的意愿检查注释,例如

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
}

当然,实际上没有必要以这种方式实现身份验证,因为您可能正在使用Spring或Guice这样的框架(甚至Jersey也可能具有所需的安全功能),它已经有了拦截所有方法并实现安全性的方法。所以你可以用它来代替。更简单、更安全。例如,对于Spring Security,只需继续正常使用即可:

public class BookService {

@PreAuth(...) //standard Spring Security
public Book addBook(Book book) { /*insert a Book into the DB */ }
}

如果你想要的是在GraphQL中实现安全性,请确保你也阅读了我的答案。

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

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;
}
}

同样,对于类型的指令,只需使用Java注释。您可以通过以下途径访问底层类型:

Directives.getMappedType(graphQLType).getAnnotation(...);

同样,这可能只有在乐器中才有意义。这样说是因为通常指令会提供额外的信息来将SDL映射到GraphQL类型。在SPQR中,您可以将Java类型映射到GraphQL类型,因此在大多数情况下,指令在该上下文中没有意义。

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

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

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

更新2:GraphQL SPQR 0.9.9已过期。

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

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

例如,想象一个用于表示访问限制的自定义注释@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")
}

也就是说,由于@GraphQLDirective元注释的存在,@Auth注释被映射到指令。

可以通过以下方式添加客户端指令:GraphQLSchemaGenerator#withAdditionalDirectives(java.lang.reflect.Type...)

SPQR 0.9.9还附带了ResolverInterceptors,它可以拦截解析器方法调用并检查注释/指令。它们比Instrumentation更便于使用,但不那么通用(范围要有限得多)。有关详细信息,请参阅第180期,有关使用示例,请参阅相关测试。

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

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);
}
}

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

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

相关内容

  • 没有找到相关文章

最新更新