Skip to content

Commit

Permalink
Adding GrpcSchemaModule that generates Queries and Mutations from gRP…
Browse files Browse the repository at this point in the history
…C service stubs
  • Loading branch information
siderakis committed Mar 28, 2018
1 parent 46438b9 commit 747d5ef
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 15 deletions.
2 changes: 1 addition & 1 deletion examples/build.gradle
Expand Up @@ -33,7 +33,7 @@ dependencies {
compile "io.grpc:grpc-netty:${grpcVersion}"
compile "io.grpc:grpc-protobuf:${grpcVersion}"
compile "io.grpc:grpc-stub:${grpcVersion}"
compile "com.google.api.graphql:rejoiner:0.0.1-SNAPSHOT"
compile "com.google.api.graphql:rejoiner:0.0.2-SNAPSHOT"
compile "com.google.api.graphql:execution:0.0.1-SNAPSHOT"
compile group: 'org.eclipse.jetty', name: 'jetty-server', version: '9.3.8.v20160314'
compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.6.2'
Expand Down
Expand Up @@ -14,20 +14,26 @@

package com.google.api.graphql.examples.library.graphqlserver.schema;

import com.google.api.graphql.rejoiner.Mutation;
import com.google.api.graphql.rejoiner.GrpcSchemaModule;
import com.google.api.graphql.rejoiner.Query;
import com.google.api.graphql.rejoiner.RelayNode;
import com.google.api.graphql.rejoiner.SchemaModule;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.example.library.shelf.v1.*;
import com.google.example.library.shelf.v1.GetShelfRequest;
import com.google.example.library.shelf.v1.ListShelvesRequest;
import com.google.example.library.shelf.v1.ListShelvesResponse;
import com.google.example.library.shelf.v1.Shelf;
import com.google.example.library.shelf.v1.ShelfServiceGrpc;

/** A GraphQL {@link SchemaModule} backed by a gRPC service. */
final class ShelfSchemaModule extends SchemaModule {
final class ShelfSchemaModule extends GrpcSchemaModule {

@Mutation("createShelf")
ListenableFuture<Shelf> createShelf(
CreateShelfRequest request, ShelfServiceGrpc.ShelfServiceFutureStub client) {
return client.createShelf(request);
@Override
protected void configureSchema() {
addMutationList(
serviceToFields(ShelfServiceGrpc.ShelfServiceFutureStub.class, ImmutableList.of("createShelf", "mergeShelves"))
);
}

@Query("getShelf")
Expand All @@ -43,9 +49,4 @@ ListenableFuture<ListShelvesResponse> listShelves(
return client.listShelves(request);
}

@Mutation("mergeShelves")
ListenableFuture<Shelf> mergeShelves(
MergeShelvesRequest request, ShelfServiceGrpc.ShelfServiceFutureStub client) {
return client.mergeShelves(request);
}
}
@@ -0,0 +1,90 @@
package com.google.api.graphql.rejoiner;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provider;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLOutputType;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.stream.Stream;

/** SchemaModule that generates queries and mutations for gRPC clients. */
public abstract class GrpcSchemaModule extends SchemaModule {

protected ImmutableList<GraphQLFieldDefinition> serviceToFields(
Class<?> client, ImmutableList<String> methodWhitelist) {

return getMethods(client, methodWhitelist)
.map(
method -> {
try {
method.setAccessible(true);
ParameterizedType returnType = (ParameterizedType) method.getGenericReturnType();
GraphQLOutputType responseType = getReturnType(returnType);
Class<? extends Message> requestMessageClass =
(Class<? extends Message>) method.getParameterTypes()[0];
Descriptors.Descriptor requestDescriptor =
(Descriptors.Descriptor)
requestMessageClass.getMethod("getDescriptor").invoke(null);
Message requestMessage =
((Message.Builder) requestMessageClass.getMethod("newBuilder").invoke(null))
.buildPartial();
Provider<?> service = getProvider(client);

GqlInputConverter inputConverter =
GqlInputConverter.newBuilder().add(requestDescriptor.getFile()).build();

DataFetcher dataFetcher =
(DataFetchingEnvironment env) -> {
Message input =
inputConverter.createProtoBuf(
requestDescriptor,
requestMessage.toBuilder(),
env.getArgument("input"));
try {
Object[] methodParameterValues = new Object[] {input};
return method.invoke(service.get(), methodParameterValues);
} catch (Exception e) {
throw new RuntimeException(e);
}
};

return GraphQLFieldDefinition.newFieldDefinition()
.name(method.getName())
.argument(GqlInputConverter.createArgument(requestDescriptor, "input"))
.type(responseType)
.dataFetcher(dataFetcher)
.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.collect(ImmutableList.toImmutableList());
}

private Stream<Method> getMethods(Class<?> clientClass, ImmutableList<String> methodWhitelist) {
ImmutableSet<String> asyncNameWhitelist =
methodWhitelist.stream().collect(ImmutableSet.toImmutableSet());
return ImmutableList.copyOf(clientClass.getMethods())
.stream()
.filter(method -> asyncNameWhitelist.contains(method.getName()));
}

/** Returns a GraphQLOutputType for type T for an input of ListenableFuture<T>. */
private GraphQLOutputType getReturnType(ParameterizedType parameterizedType)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<? extends Message> responseClass =
(Class<? extends Message>) parameterizedType.getActualTypeArguments()[0];
Descriptors.Descriptor responseDescriptor =
(Descriptors.Descriptor) responseClass.getMethod("getDescriptor").invoke(null);
addExtraType(responseDescriptor);
return ProtoToGql.getReference(responseDescriptor);
}
}
Expand Up @@ -432,8 +432,8 @@ private GraphQLOutputType getReturnType(Method method)

// Assume Message or Scalar
if (!(method.getGenericReturnType() instanceof ParameterizedType)) {
java.lang.reflect.Type returnType = method.getReturnType();
if (returnType instanceof Message) {
Class<?> returnType = method.getReturnType();
if (Message.class.isAssignableFrom(returnType)) {
@SuppressWarnings("unchecked")
Class<? extends Message> responseClass = (Class<? extends Message>) method.getReturnType();
Descriptor responseDescriptor =
Expand Down

0 comments on commit 747d5ef

Please sign in to comment.