Мне удалось добавить пользовательские директивы в схему GraphQL, но я изо всех сил пытаюсь понять, как добавить пользовательскую директиву в определение поля. Любые подсказки по правильной реализации были бы очень полезны. Я использую GraphQL SPQR 0.9.6 для создания своей схемы
Как добавить пользовательскую директиву в запрос, разрешенный через синглтон
Ответы (1)
В настоящее время это невозможно сделать. GraphQL SPQR v0.9.9 будет первым, поддерживающим пользовательские директивы.
Тем не менее, в 0.9.8 есть возможный обходной путь, в зависимости от того, чего вы пытаетесь достичь. Собственные метаданные SPQR о поле или типе хранятся внутри пользовательских директив. Зная это, вы можете получить доступ к методу/полю Java, лежащему в основе определения поля GraphQL. Если вы хотите, например. инструментарий, который делает что-то на основе директивы, вместо этого вы можете получить любые аннотации к базовому элементу, имея в своем распоряжении всю мощь 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 (возможно, даже в Джерси есть необходимые функции безопасности), в котором уже есть способ перехвата всех методов и реализации безопасности. Так что вы можете просто использовать это вместо этого. Гораздо проще и безопаснее. Например. для 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")
}
То есть аннотация @Auth
была сопоставлена с директивой из-за наличия мета-аннотации @GraphQLDirective
.
Клиентские директивы можно добавить через: GraphQLSchemaGenerator#withAdditionalDirectives(java.lang.reflect.Type...)
.
SPQR 0.9.9 также поставляется с ResolverInterceptor
s, которые могут перехватывать вызов метода распознавателя и проверять аннотации/директивы. Их гораздо удобнее использовать, чем Instrumentation
s, но они не такие общие (имеют гораздо более ограниченную область применения). Подробнее см. в выпуске #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);