spring-graphql
spring-graphql copied to clipboard
Support custom argument resolvers for @SchemaMapping methods
As discussed in https://github.com/spring-projects/spring-graphql/issues/250 this is feature request for support of custom argument resolvers in GraphQL controllers.
E.g. to support
@QueryMapping
public Page<Season> seasons(Pageable pageable) {
int first = pageable.getFirst();
int offset = pageable.getOffset();
..
}
The plain example as above could already be implemented using a @ProjectedPayload
@ProjectedPayload
public interface Pageable {
int getFirst();
int getOffset();
}
(Note: using the @Argument
annotation for above case does not work me, see https://github.com/spring-projects/spring-graphql/issues/258)
But supporting custom argument resolvers could offer more flexibility I was looking for like the ability to use a custom @interface
to provide defaults , e.g.
@QueryMapping
public Page<Season> seasons(@PageableDefault(first=10, offset=0) Pageable pageable) {
..
}
@marceloverdijk I'm wondering if the changes for #258 addressed the case here with Pageable
or do you have another use case in mind?
Generally, both @SchemaMapping
and @BatchMapping
methods could have custom resolvers but they resolve from different contexts, DataFetchingEnvironment
vs BatchLoaderEnvironment
, and can't share the same contract. I'm also not certain what this would be used for, considering that we expose more or less everything there is.
Hi @rstoyanchev #258 already helps a lot to be honest.
Having support for custom argument resolvers - like in e.g. MVC - I could do even more.
Besides binding the arguments to an object I could also apply some login when binding, like defining default values as can be seen in the example below. The idea below is build upon Spring Data's PageableHandlerMethodArgumentResolver
.
@QueryMapping
public Page<Season> seasons(@PageableDefault(first=10, offset=0) Pageable pageable) {
..
}
Thanks for clarifying. For the Pageable
example, isn't the logic within it and hence orthogonal to the instantiation or is there something specific about the instantiation?
Of course it makes perfect sense to have such resolvers but for now it doesn't seem to be holding anything up, and with the two different types of handler methods and resolver contracts, I'd rather give this more time, before we find the best way to proceed.
I understand.
For me it's about having more control to which default value to set in the argument resolver; it's coming from https://github.com/spring-projects/spring-graphql/issues/250
I'm having a similar issue, but I'm not sure if a custom argument resolver can help:
Schema:
scalar ConnectionCursor
type Query {
books(after: ConnectionCursor): [Book]
authors(after: ConnectionCursor): [Author]
}
Controller:
@Controller
public class MyController{
@QueryMapping
Flux<Book> books(@Argument BookConnectionCursor after) {
return Flux.empty();
}
@QueryMapping
Flux<Author> authors(@Argument AuthorConnectionCursor after) {
return Flux.empty();
}
}
As you can see, in the schema there's only a generic ConnectionCursor
scalar, but inside the controller code each @Argument
should be able to use its ows specialized version.
When creating custom ConnectionCursor
scalar with RuntimeWiringConfigurer
it is easy to serialize
, because I have type information. But with parseValue
and parseLiteral
I don't have any type information and no access to controller method and parameter, though I can't figure out if I should create a BookConnectionCursor
or a AuthorConnectionCursor
object.
Can a custom argument resolver solve this issue? (I guess there I should have the class type of the controller method argument)
EDIT: I just figured out that I can register org.springframework.core.convert.converter.Converter<String, BookConnectionCursor>
and Converter<String, AuthorConnectionCursor>
@Bean
s and then register the scalar as String Scalar:
@Bean
RuntimeWiringConfigurer configurer() {
return builder -> builder
.scalar(GraphQLScalarType.newScalar(Scalars.GraphQLString).name("ConnectionCursor").build());
}
Works as intended 😸 First the registered scalar creates a String
from the input value, and then the GraphQlArgumentBinder
looks for the matching Converter
. Nice!
I'm closing this as superseded by #325.