graphql-spqr
graphql-spqr copied to clipboard
Generate FieldValidationInstrumentation
graphql-java has recently implemented field level validation that is tested after the query is parsed, but before it is executed. https://github.com/graphql-java/graphql-java/issues/162
It would be really handy if this could be automatically generated using javax.validation annotations!
I'm only just starting my own implementation so I haven't thought it through fully, but I can see it working as follows:
- In the method that generates a
GraphQLSchema, also generate aFieldValidationInstrumentation - This generate would, for each GraphQL query or mutation, check the arguments
- For each argument and fields of each argument get any
javax.validationannotations - Create a rule for each annotation. Add each rule to a
SimpleFieldValidation - The programmer could then add the
FieldValidationInstrumentationto the GraphQL builderGraphQL.newGraphQL(...).instrumentation(instrumentation).build()
It would also be nice to have a custom org.springframework.validation.Validator bean that could check business logic (e.g. the email already exists in the database) but I haven't thought about how that could be implemented yet.
There's an example of the graphql-java validation in the method fieldValidation(), Line 161, shows an example.
https://github.com/graphql-java/graphql-java/blob/2f8f3de1d722ec0128b960c5e565405057fa76ff/src/test/groovy/readme/InstrumentationExamples.java#L161
This is an interesting proposition, and a framework generating a schema is indeed in a perfect position to attach such a validation, but I have to think about it more thoroughly. It entails dragging in a dependency to a bean validation implementation, and extending the generator to generate more than the schema...
As for a Spring Validator, won't it already work just fine if the beans you expose with SPQR are managed beans?
Oh, and since you mentioned you're starting your own implementation, if it ends up being something you can contribute - please do ^_^
Thanks for the consideration!
javax.validation is included in the Spring Starter, so I assumed that would be fair game to include, though of course unnecessary dependencies are bad.
I'm looking into this again today, maybe you can help. So as I understand, basically, SPQR fetches all Java methods, method arguments, and class fields that are annotated. I think you could just loop over every thing, and for each validation annotation create a new rule.
I can't see how to get all the mutations, queries, arguments though. How can I do this?
for (Method mutation : @GraphQLMutations) {
for (Argument argument : @GraphQLArguments) {
// somehow fetch validation annotations like @NotNull?
for (Annotation a : validationAnnotations) {
rules.add(
new BiFunction<...>(){
@Override
public Optional<GraphQLError> apply(FieldAndArguments fieldAndArguments, FieldValidationEnvironment env) {
Object o = fieldAndArguments.getArgumentValue(mutation.getName()).get(argument.getName());
if (a.isValid(o))
return Optional.empty();
else
return Optional.of(environment.mkError("error on " + argument.getName(), fieldAndArguments));
}
}
)
}
}
}
javax.validation is already an optional dependency (you can use it's @Nonnull to produce a GraphQLNonnullType), so that one is ok. But I wouldn't want to actually implement the validation logic mandated by JSR 380 myself. I'd instead delegate it to an existing implementation (Hibernate Validator, in all likelihood). I'd like to put this into a separate module that would be auto-detected, similar to how I'm planning to make library specific mappers (e.g. Joda Time mappers, Spring Flux mappers etc) work.
But, it may ultimately makes more sense to add this is a module to the SPQR Spring Starter (being developed as we speak ;) ) instead of SPQR itself... As you can see, I have to give this some serious thought first. But it will surely materialize in some form 😄
As for the code above, right now there's no convenient way to get a hold of the raw java.reflect objects from the outside... I'd have to somehow expose them to the client code code via new methods/hooks on the generator.
Okay, cool. Sounds like you've got a better idea of how to implement it than me ;)
I've put something together manually for now, without using annotations.
+1 for this enhancement :)
+1 for this enhancement :)
@EdgarArguelles While this is something I'm still planning to look into, I think it's much less needed now than at the time this issue was opened. The reason is that these days it should be rather easy to use ResolverInterceptor to achieve the same result (check the tests for inspiration). Of course, it has always been possible to utilize the capabilities of the underlying framework (e.g. Spring, CDI, Guice etc) as most of them provide Bean Validation integration.
Hey there! I am using spqr in a spring-boot environment. For authentication I need to read a JWT token from every incoming request. I found out how to do for a single method it with the @GraphQLRootContext annotation, but I want to do it on a global level. To my understanding this can be done with a ResolverInterceptor, but I don't know how to register it with spring. Can anyone help me?
@Matthias-Walter-Innio I'd whole-heartedly suggest you use existing Spring features for handling security, as it already has everything you need to process JWT. Since SPQR will invoke Spring-managed beans, all Spring features will work normally.
If that's for some reason not an option, yes, ResolverInterceptor is the way to go, as it can intercept any resolver invocation and inspect the root context.
To register any extension in Spring, simply register a bean such as:
//E is any extension type, such as TypeMapper ResolverInterceptorFactory, or anything else
@Bean
public ExtensionProvider<GeneratorConfiguration, E> customExtensions() {
return (config, extensions) -> extensions.append/drop/insert(...); //modify the current list as you please
}
For your case, if you want a ResolverInterceptor that is applicable globally (to all resolvers), you can use GlobalResolverInterceptorFactory:
@Bean
public ExtensionProvider<GeneratorConfiguration, ResolverInterceptorFactory> customInterceptors() {
return (config, interceptors) -> interceptors.append(new GlobalResolverInterceptorFactory(customGlobalInterceptors);
}
If you want to react to e.g. an annotation (so that only annotated methods are intercepted), you can provide a custom ResolverInterceptorFactory. that way you e.g. only intercept top-level methods.
Inside of your ResolverInterceptor if the token in missing or invalid, you want to throw an AbortExecutionException to prevent further execution.
Thanks a lot, I will try out!
@Matthias-Walter-Innio Answered your SO question as well.
Sorry if this is an old thread...I'm using JavaX validation constraints in my Spring app, how should I get SPQR to pick these up? Currently I have the below Object and am trying to validate this in my Graph query:
public class Example {
@NotBlank
@Pattern(regexp = ".{3,}")
private String query;
}
@GraphQLQuery(name = "exampleSearch")
public List<String> searchList(@GraphQLArgument(name = "query") @RequestBody @Valid Example query) {
return searchService.exampleSearch(query);
}