如何将自定义指令添加到通过单例解析的查询中
How do I add a custom directive to a query resolved through a singleton
我已经设法将自定义指令添加到 GraphQL 架构中,但我正在努力弄清楚如何将自定义指令添加到字段定义中。关于正确实施的任何提示都会非常有帮助。
我正在使用 GraphQL SPQR 0.9.6 生成我的模式
原始答案:(现已过时,请参阅下面的 2 个更新)
目前无法执行此操作。 GraphQL SPQR v0.9.9 将率先支持自定义指令。
不过,在 0.9.8 中,有一种可能的解决方法,具体取决于您要实现的目标。 SPQR 自己的关于字段或类型的元数据保存在自定义指令中。知道了这一点,您就可以掌握 GraphQL 字段定义的基础 Java method/field 。如果你想要的是例如一个基于指令做某事的工具,您可以获得底层元素上的任何注释,拥有 Java 的全部功能供您使用。
获取方法的方式类似于:
Operation operation = Directives.getMappedOperation(env.getField()).get();
Resolver resolver = operation.getApplicableResolver(env.getArguments().keySet());
Member underlyingElement = resolver.getExecutable().getDelegate();
更新:
我在 this GitHub issue 上发布了一个巨大的答案。也贴在这里。
您可以这样注册一个额外的指令:
generator.withSchemaProcessors(
(schemaBuilder, buildContext) -> schemaBuilder.additionalDirective(...));
但是(根据我目前的理解),这仅对查询指令有意义(客户端作为查询的一部分发送的内容,如 @skip
或 @deffered
)。
像 @dateFormat
这样的指令在 SPQR 中根本没有意义:它们可以帮助您解析 SDL 并将其映射到您的代码。在 SPQR 中,没有 SDL,您可以从您的代码开始。
例如。 @dateFormat
用于告诉您在将特定字段映射到 Java 时需要为特定字段提供日期格式。在 SPQR 中,您从 Java 部分开始,而 GraphQL 字段是从 Java 方法生成的,因此该方法必须已经知道它应该 return 的格式。或者它已经有适当的注释。 在 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 安全,请继续正常使用它:
public class BookService {
@PreAuth(...) //standard Spring Security
public Book addBook(Book book) { /*insert a Book into the DB */ }
}
确保你也阅读了 my answer on implementing security in 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 还带有 ResolverInterceptor
s,它可以拦截解析器方法调用并检查 annotations/directives。它们比 Instrumentation
s 使用起来更方便,但并不通用(范围更有限)。有关用法示例,请参阅问题 #180 for details, and the related tests。
例如使用上面的 @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);
我已经设法将自定义指令添加到 GraphQL 架构中,但我正在努力弄清楚如何将自定义指令添加到字段定义中。关于正确实施的任何提示都会非常有帮助。 我正在使用 GraphQL SPQR 0.9.6 生成我的模式
原始答案:(现已过时,请参阅下面的 2 个更新)
目前无法执行此操作。 GraphQL SPQR v0.9.9 将率先支持自定义指令。
不过,在 0.9.8 中,有一种可能的解决方法,具体取决于您要实现的目标。 SPQR 自己的关于字段或类型的元数据保存在自定义指令中。知道了这一点,您就可以掌握 GraphQL 字段定义的基础 Java method/field 。如果你想要的是例如一个基于指令做某事的工具,您可以获得底层元素上的任何注释,拥有 Java 的全部功能供您使用。
获取方法的方式类似于:
Operation operation = Directives.getMappedOperation(env.getField()).get();
Resolver resolver = operation.getApplicableResolver(env.getArguments().keySet());
Member underlyingElement = resolver.getExecutable().getDelegate();
更新: 我在 this GitHub issue 上发布了一个巨大的答案。也贴在这里。
您可以这样注册一个额外的指令:
generator.withSchemaProcessors(
(schemaBuilder, buildContext) -> schemaBuilder.additionalDirective(...));
但是(根据我目前的理解),这仅对查询指令有意义(客户端作为查询的一部分发送的内容,如 @skip
或 @deffered
)。
像 @dateFormat
这样的指令在 SPQR 中根本没有意义:它们可以帮助您解析 SDL 并将其映射到您的代码。在 SPQR 中,没有 SDL,您可以从您的代码开始。
例如。 @dateFormat
用于告诉您在将特定字段映射到 Java 时需要为特定字段提供日期格式。在 SPQR 中,您从 Java 部分开始,而 GraphQL 字段是从 Java 方法生成的,因此该方法必须已经知道它应该 return 的格式。或者它已经有适当的注释。 在 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 安全,请继续正常使用它:
public class BookService {
@PreAuth(...) //standard Spring Security
public Book addBook(Book book) { /*insert a Book into the DB */ }
}
确保你也阅读了 my answer on implementing security in 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 还带有 ResolverInterceptor
s,它可以拦截解析器方法调用并检查 annotations/directives。它们比 Instrumentation
s 使用起来更方便,但并不通用(范围更有限)。有关用法示例,请参阅问题 #180 for details, and the related tests。
例如使用上面的 @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);