Как добавить пользовательскую директиву к запросу, разрешенному с помощью синглтона?
Мне удалось добавить пользовательские директивы в схему 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 будет иметь полную поддержку пользовательских директив для любого элемента, если они вам все еще нужны.