Как добавить пользовательскую директиву в запрос, разрешенный через синглтон

Мне удалось добавить пользовательские директивы в схему GraphQL, но я изо всех сил пытаюсь понять, как добавить пользовательскую директиву в определение поля. Любые подсказки по правильной реализации были бы очень полезны. Я использую GraphQL SPQR 0.9.6 для создания своей схемы


person Jacob Mountain    schedule 23.07.2018    source источник
comment
В моем ответе упоминается текущее состояние и некоторые возможные обходные пути, но если бы я знал ваш вариант использования более точно, я мог бы придумать больше. Также было бы полезно узнать, как используются пользовательские директивы при разработке их поддержки в SPQR.   -  person kaqqao    schedule 30.07.2018


Ответы (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 также поставляется с ResolverInterceptors, которые могут перехватывать вызов метода распознавателя и проверять аннотации/директивы. Их гораздо удобнее использовать, чем Instrumentations, но они не такие общие (имеют гораздо более ограниченную область применения). Подробнее см. в выпуске #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);
person kaqqao    schedule 30.07.2018