diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/ClientGenerator.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/ClientGenerator.java deleted file mode 100644 index 647bdfe..0000000 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/ClientGenerator.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.jacobmountain.graphql.client; - -import com.jacobmountain.graphql.client.modules.*; -import com.jacobmountain.graphql.client.query.QueryGenerator; -import com.jacobmountain.graphql.client.utils.AnnotationUtils; -import com.jacobmountain.graphql.client.utils.Schema; -import com.jacobmountain.graphql.client.utils.StringUtils; -import com.jacobmountain.graphql.client.visitor.ClientDetailsVisitor; -import com.jacobmountain.graphql.client.visitor.MethodDetails; -import com.jacobmountain.graphql.client.visitor.MethodDetailsVisitor; -import com.squareup.javapoet.*; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.processing.Filer; -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * ClientGenerator generates the implementation of the interface annotated with @GraphQLClient - */ -@Slf4j -@RequiredArgsConstructor -public class ClientGenerator { - - private final Filer filer; - - private final TypeMapper typeMapper; - - private final String packageName; - - private final Schema schema; - - private final AbstractStage arguments; - - private final AbstractStage query; - - private final AbstractStage returnResults; - - public ClientGenerator(Filer filer, TypeMapper typeMapper, String packageName, String dtoPackageName, Schema schema, boolean reactive) { - this.filer = filer; - this.typeMapper = typeMapper; - this.packageName = packageName; - this.schema = schema; - this.arguments = new ArgumentAssemblyStage(); - QueryGenerator queryGenerator = new QueryGenerator(schema); - if (reactive) { - this.query = new ReactiveQueryStage(queryGenerator, schema, typeMapper, dtoPackageName); - this.returnResults = new ReactiveReturnStage(schema, typeMapper); - } else { - this.query = new BlockingQueryStage(queryGenerator, schema, typeMapper, dtoPackageName); - this.returnResults = new OptionalReturnStage(schema, typeMapper); - } - } - - /** - * Generates the implementation of the @GraphQLClient interface - * - * @param element the Element that has the @GraphQLClient on - * @param suffix the implementations suffix - */ - @SneakyThrows - public void generate(Element element, String suffix) { - if (StringUtils.isEmpty(suffix)) { - throw new IllegalArgumentException("Invalid suffix for implementation of client: " + element.getSimpleName()); - } - ClientDetails details = element.accept(new ClientDetailsVisitor(), null); - // Generate the class - TypeSpec.Builder builder = TypeSpec.classBuilder(element.getSimpleName() + suffix) - .addSuperinterface(ClassName.get((TypeElement) element)) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(AnnotationUtils.generated()); - // Add type argument to the client - Stream.of(arguments, query, returnResults) - .flatMap(it -> it.getTypeArguments().stream()) - .map(TypeVariableName::get) - .forEach(builder::addTypeVariable); - // Add any necessary member variables to the client - List memberVariables = Stream.of(arguments, query, returnResults) - .map(it -> it.getMemberVariables(details)) - .flatMap(Collection::stream) - .peek(memberVariable -> builder.addField(memberVariable.getType(), memberVariable.getName(), Modifier.PRIVATE, Modifier.FINAL)) - .collect(Collectors.toList()); - // generate the constructor - builder.addMethod(generateConstructor(memberVariables)); - - // for each method on the interface, generate its implementation - element.getEnclosedElements() - .forEach(el -> generateImpl(builder, el, details)); - - writeToFile(builder.build()); - } - - /** - * Generates a constructor which takes in any required member variables (usually the fetcher) - * - * @param variables the required member variables - */ - private MethodSpec generateConstructor(List variables) { - MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); - variables.forEach(var -> constructor.addParameter(var.getType(), var.getName()) - .addStatement("this.$L = $L", var.getName(), var.getName())); - return constructor.build(); - } - - /** - * Generates the client implementation of one method of the interface - * - * @param method the method of the @GraphQLClient annotated interface - */ - private void generateImpl(TypeSpec.Builder clazz, Element method, ClientDetails client) { - log.info(""); - MethodDetails details = method.accept(new MethodDetailsVisitor(schema), typeMapper); - log.info("{}", details); - - generateArgumentDTO(details) - .ifPresent(clazz::addType); - - MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getSimpleName().toString()) - .returns(details.getReturnType()) - .addModifiers(Modifier.PUBLIC) - .addParameters(details.getParameterSpec()); - - this.arguments.assemble(client, details).forEach(builder::addStatement); - this.query.assemble(client, details).forEach(builder::addStatement); - this.returnResults.assemble(client, details).forEach(builder::addStatement); - - clazz.addMethod(builder.build()); - } - - public Optional generateArgumentDTO(MethodDetails details) { - return Optional.of(details) - .filter(MethodDetails::hasParameters) - .map(it -> { - String name = details.getArgumentClassname(); - PojoBuilder builder = PojoBuilder.newType(name, packageName).staTic(); - details.getParameters() - .forEach(variable -> { - String field = variable.getName(); - if (variable.getAnnotation() != null) { - field = variable.getAnnotation().value(); - } - builder.withField(variable.getType(), field); - }); - return builder.buildClass(); - }); - } - - private void writeToFile(TypeSpec spec) throws Exception { - JavaFile.builder(packageName, spec) - .indent("\t") - .skipJavaLangImports(true) - .build() - .writeTo(filer); - } - -} - diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java index 3af1a22..21844ad 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java @@ -2,12 +2,8 @@ import com.google.auto.service.AutoService; import com.jacobmountain.graphql.client.annotations.GraphQLClient; -import com.jacobmountain.graphql.client.exceptions.SchemaNotFoundException; -import com.jacobmountain.graphql.client.utils.Schema; -import com.jacobmountain.graphql.client.utils.StringUtils; -import lombok.AllArgsConstructor; +import com.jacobmountain.graphql.client.code.ClientGenerator; import lombok.SneakyThrows; -import lombok.Value; import lombok.extern.slf4j.Slf4j; import javax.annotation.processing.*; @@ -18,10 +14,8 @@ import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; -import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -37,6 +31,8 @@ public class GraphQLClientProcessor extends AbstractProcessor { private Filer filer; + private ClientGenerator clientGenerator; + private Messager messager; private Path root; @@ -46,6 +42,7 @@ public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.filer = processingEnv.getFiler(); this.messager = processingEnv.getMessager(); + this.clientGenerator = new ClientGenerator(this.filer); } @Override @@ -59,7 +56,7 @@ public boolean process(Set annotations, RoundEnvironment } final List interfaces = elements.stream() .map(el -> (TypeElement) el) - .map(Input::new) + .map(type -> new Input(type, getRoot(), processingEnv.getElementUtils().getPackageOf(type).toString())) .collect(toList()); interfaces.stream() .filter(new OncePerSchemaPredicate()) @@ -84,11 +81,10 @@ private void generateJavaDataClasses(Input input) { dtoGenerator.generate(input.getSchema().types().values()); } + @SneakyThrows private void generateClientImplementation(Input client) { - GraphQLClient annotation = client.getAnnotation(); - log.info("Generating java implementation of {}", client.element.getSimpleName()); - new ClientGenerator(this.filer, client.getTypeMapper(), client.getPackage(), client.getDtoPackage(), client.getSchema(), annotation.reactive()) - .generate(client.element, annotation.implSuffix()); + log.info("Generating java implementation of {}", client.getElement().getSimpleName()); + clientGenerator.generate(client); } @SneakyThrows @@ -106,51 +102,4 @@ private Path getRoot() { return root; } - @Value - @AllArgsConstructor - private class Input { - - TypeElement element; - - GraphQLClient getAnnotation() { - return element.getAnnotation(GraphQLClient.class); - } - - TypeMapper getTypeMapper() { - return new TypeMapper(getDtoPackage(), getAnnotation().mapping()); - } - - String getDtoPackage() { - return String.join(".", Arrays.asList( - getPackage(), - getAnnotation().dtoPackage() - )); - } - - String getPackage() { - return processingEnv.getElementUtils().getPackageOf(element).toString(); - } - - Schema getSchema() { - String value = getAnnotation().schema(); - File file = getSchemaFile(); - try { - if (StringUtils.hasLength(value)) { - log.info("Reading schema {}", file); - return new Schema(getSchemaFile()); - } - } catch (Exception e) { - e.printStackTrace(); - } - throw new SchemaNotFoundException(file.getPath()); - } - - File getSchemaFile() { - return getRoot().resolve(getAnnotation().schema()) - .toAbsolutePath() - .toFile(); - } - - } - } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/Input.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/Input.java new file mode 100644 index 0000000..f6b0352 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/Input.java @@ -0,0 +1,77 @@ +package com.jacobmountain.graphql.client; + +import com.jacobmountain.graphql.client.annotations.GraphQLClient; +import com.jacobmountain.graphql.client.exceptions.SchemaNotFoundException; +import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.utils.StringUtils; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +import javax.lang.model.element.TypeElement; +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; + +@Slf4j +@Value +public class Input { + + TypeElement element; + + @Getter(value = AccessLevel.PRIVATE) + String packageStr; + + Path root; + + public Input(TypeElement element, Path root, String packageStr) { + this.element = element; + this.packageStr = packageStr; + this.root = root; + } + + public GraphQLClient getAnnotation() { + return element.getAnnotation(GraphQLClient.class); + } + + public TypeMapper getTypeMapper() { + return new TypeMapper(getDtoPackage(), getAnnotation().mapping()); + } + + public String getDtoPackage() { + return String.join(".", Arrays.asList( + getPackage(), + getAnnotation().dtoPackage() + )); + } + + public String getPackage() { + return packageStr; + } + + public Schema getSchema() { + String value = getAnnotation().schema(); + File file = getSchemaFile(); + try { + if (StringUtils.hasLength(value)) { + log.info("Reading schema {}", file); + return new Schema(getSchemaFile()); + } + } catch (Exception e) { + e.printStackTrace(); + } + throw new SchemaNotFoundException(file.getPath()); + } + + public File getSchemaFile() { + return root.resolve(getAnnotation().schema()) + .toAbsolutePath() + .toFile(); + } + + public boolean isReactive() { + return getAnnotation().reactive(); + } + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractQueryStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractQueryStage.java similarity index 70% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractQueryStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractQueryStage.java index 55fa6e3..c58fc6d 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractQueryStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractQueryStage.java @@ -1,4 +1,4 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code; import com.jacobmountain.graphql.client.TypeMapper; import com.jacobmountain.graphql.client.dto.Response; @@ -10,50 +10,21 @@ import com.squareup.javapoet.*; import graphql.language.ObjectTypeDefinition; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; public abstract class AbstractQueryStage extends AbstractStage { - protected final TypeName query; - - protected final TypeName mutation; - - protected final TypeName subscription; - private final QueryGenerator queryGenerator; - public AbstractQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper, String dtoPackageName) { + public AbstractQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper) { super(schema, typeMapper); - this.query = ClassName.get(dtoPackageName, schema.getQueryTypeName()); - this.mutation = schema.getMutationTypeName().map(it -> ClassName.get(dtoPackageName, it)).orElse(ClassName.get(Void.class)); - this.subscription = schema.getSubscriptionTypeName().map(it -> ClassName.get(dtoPackageName, it)).orElse(ClassName.get(Void.class)); this.queryGenerator = queryGenerator; } - protected TypeName getFetcherTypeName(Class fetcher) { - return ParameterizedTypeName.get( - ClassName.get(fetcher), - query, - mutation, - TypeVariableName.get("Error") - ); - } - - protected TypeName getSubscriberTypeName(Class fetcher) { - return ParameterizedTypeName.get( - ClassName.get(fetcher), - subscription, - TypeVariableName.get("Error") - ); - } - - - @Override - public List getTypeArguments() { - return Collections.singletonList("Error"); - } - protected TypeName getReturnTypeName(MethodDetails details) { ObjectTypeDefinition typeDefinition = getTypeDefinition(details); return ParameterizedTypeName.get( diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractStage.java similarity index 53% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractStage.java index c9ab140..0aa48fa 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractStage.java @@ -1,17 +1,14 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code; import com.jacobmountain.graphql.client.TypeMapper; import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.TypeName; import graphql.language.ObjectTypeDefinition; -import lombok.Builder; import lombok.RequiredArgsConstructor; -import lombok.Value; -import java.util.Collections; -import java.util.List; +import java.util.Optional; @RequiredArgsConstructor public abstract class AbstractStage { @@ -20,17 +17,7 @@ public abstract class AbstractStage { protected final TypeMapper typeMapper; - public List getMemberVariables(ClientDetails details) { - return Collections.emptyList(); - } - - public List getTypeArguments() { - return Collections.emptyList(); - } - - public List assemble(ClientDetails client, MethodDetails method) { - return Collections.emptyList(); - } + public abstract Optional assemble(ClientDetails client, MethodDetails method); protected ObjectTypeDefinition getTypeDefinition(MethodDetails details) { if (details.isQuery()) { @@ -42,14 +29,4 @@ protected ObjectTypeDefinition getTypeDefinition(MethodDetails details) { } } - @Value - @Builder - public static class MemberVariable { - - String name; - - TypeName type; - - } - } \ No newline at end of file diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ArgumentAssemblyStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ArgumentAssemblyStage.java similarity index 80% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ArgumentAssemblyStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ArgumentAssemblyStage.java index 4e2d99c..439bfe3 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ArgumentAssemblyStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ArgumentAssemblyStage.java @@ -1,6 +1,7 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.jacobmountain.graphql.client.visitor.Parameter; import com.squareup.javapoet.ClassName; @@ -8,9 +9,9 @@ import com.squareup.javapoet.TypeName; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; public class ArgumentAssemblyStage extends AbstractStage { @@ -20,10 +21,10 @@ public ArgumentAssemblyStage() { } @Override - public List assemble(ClientDetails client, MethodDetails method) { + public Optional assemble(ClientDetails client, MethodDetails method) { List parameters = method.getParameters(); if (parameters.isEmpty()) { - return Collections.emptyList(); + return Optional.empty(); } List ret = new ArrayList<>(); TypeName type = ClassName.bestGuess(method.getArgumentClassname()); @@ -32,7 +33,9 @@ public List assemble(ClientDetails client, MethodDetails method) { .stream() .map(this::setArgumentField) .forEach(ret::add); - return ret; + return Optional.of( + CodeBlock.join(ret, ";") + ); } private CodeBlock setArgumentField(Parameter param) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/Assembler.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/Assembler.java new file mode 100644 index 0000000..1f2ec28 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/Assembler.java @@ -0,0 +1,90 @@ +package com.jacobmountain.graphql.client.code; + +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.visitor.ClientDetails; +import com.jacobmountain.graphql.client.visitor.MethodDetails; +import com.squareup.javapoet.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public abstract class Assembler { + + private static final TypeVariableName ERROR_TYPE_VARIABLE = TypeVariableName.get("Error"); + + private final TypeName fetcherInterface; + + private final TypeName subscriberInterface; + + protected final AbstractStage arguments; + + protected final AbstractStage query; + + protected final AbstractStage returnResults; + + protected Assembler(AbstractStage query, + AbstractStage returnResults, + Schema schema, + TypeMapper typeMapper, + Class fetcherInterface, + Class subscriberInterface) { + this.arguments = new ArgumentAssemblyStage(); + this.query = query; + this.returnResults = returnResults; + final TypeName queryType = typeMapper.getType(schema.getQueryTypeName()); + final TypeName mutationType = schema.getMutationTypeName() + .map(typeMapper::getType) + .orElse(ClassName.get(Void.class)); + final TypeName subscriptionType = schema.getSubscriptionTypeName() + .map(typeMapper::getType) + .orElse(ClassName.get(Void.class)); + this.fetcherInterface = ParameterizedTypeName.get( + ClassName.get(fetcherInterface), + queryType, + mutationType, + ERROR_TYPE_VARIABLE + ); + this.subscriberInterface = ParameterizedTypeName.get( + ClassName.get(subscriberInterface), + subscriptionType, + ERROR_TYPE_VARIABLE + ); + } + + public Collection getTypeArguments() { + return Collections.singleton(ERROR_TYPE_VARIABLE); + } + + public List getMemberVariables(ClientDetails client) { + ArrayList vars = new ArrayList<>(2); + if (client.requiresFetcher()) { + vars.add( + MemberVariable.builder() + .name("fetcher") + .type(fetcherInterface) + .build() + ); + } + if (client.requiresSubscriber()) { + vars.add( + MemberVariable.builder() + .name("subscriber") + .type(subscriberInterface) + .build() + ); + } + return vars; + } + + public List assemble(ClientDetails client, MethodDetails method) { + List code = new ArrayList<>(3); + this.arguments.assemble(client, method).ifPresent(code::add); + this.query.assemble(client, method).ifPresent(code::add); + this.returnResults.assemble(client, method).ifPresent(code::add); + return code; + } + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java new file mode 100644 index 0000000..464f28f --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java @@ -0,0 +1,161 @@ +package com.jacobmountain.graphql.client.code; + +import com.jacobmountain.graphql.client.Input; +import com.jacobmountain.graphql.client.PojoBuilder; +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.blocking.BlockingAssembler; +import com.jacobmountain.graphql.client.code.reactive.ReactiveAssembler; +import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.utils.AnnotationUtils; +import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; +import com.jacobmountain.graphql.client.visitor.ClientDetailsVisitor; +import com.jacobmountain.graphql.client.visitor.ClientDetailsVisitorArgs; +import com.jacobmountain.graphql.client.visitor.MethodDetails; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.processing.Filer; +import javax.lang.model.element.Modifier; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +/** + * ClientGenerator generates the implementation of the interface annotated with @GraphQLClient + */ +@Slf4j +public class ClientGenerator { + + private final Filer filer; + + public ClientGenerator(Filer filer) { + this.filer = filer; + } + + /** + * Generates the implementation of the @GraphQLClient interface + * + * @param input the Input data required to generate the client implementation + */ + public void generate(Input input) throws IOException { + if (StringUtils.isEmpty(input.getAnnotation().implSuffix())) { + throw new IllegalArgumentException("Invalid suffix for implementation of client: " + input.getElement().getSimpleName()); + } + final Schema schema = input.getSchema(); + final TypeMapper typeMapper = input.getTypeMapper(); + ClientDetails client = input.getElement().accept( + new ClientDetailsVisitor(), + new ClientDetailsVisitorArgs(schema, typeMapper) + ); + + Assembler assembler = initializeAssembler(schema, typeMapper, input.isReactive()); + + // Generate the class + TypeSpec.Builder builder = TypeSpec.classBuilder(client.getName() + input.getAnnotation().implSuffix()) + .addSuperinterface(client.getClientInterface()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(AnnotationUtils.generated()); + + // Add type arguments to the client + for (TypeVariableName typeVariableName : assembler.getTypeArguments()) { + builder.addTypeVariable(typeVariableName); + } + + // Add any necessary member variables to the client + List memberVariables = assembler.getMemberVariables(client); + for (MemberVariable memberVariable : memberVariables) { + builder.addField( + memberVariable.getType(), memberVariable.getName(), Modifier.PRIVATE, Modifier.FINAL + ); + } + + // generate the constructor + builder.addMethod(generateConstructor(memberVariables)); + + // for each method on the interface, generate its implementation + for (MethodDetails method : client.getMethods()) { + generateArgumentDTO(method, input.getDtoPackage()).ifPresent(builder::addType); + builder.addMethod(generateMethodImplementation(assembler, method, client)); + } + + writeToFile(builder.build(), input.getPackage()); + } + + private Assembler initializeAssembler(Schema schema, TypeMapper typeMapper, boolean reactive) { + QueryGenerator queryGenerator = new QueryGenerator(schema); + if (reactive) { + return new ReactiveAssembler(queryGenerator, schema, typeMapper); + } else { + return new BlockingAssembler(queryGenerator, schema, typeMapper); + } + } + + /** + * Generates a constructor which takes in any required member variables (usually the fetcher) + * + * @param variables the required member variables + */ + private MethodSpec generateConstructor(List variables) { + MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); + variables.forEach(var -> constructor.addParameter(var.getType(), var.getName()) + .addStatement("this.$L = $L", var.getName(), var.getName())); + return constructor.build(); + } + + /** + * Generates the client implementation of one method of the interface + * + * @param assembler the assembler that creates the code for the method + * @param method the method details + * @param client the client classes details + * @return the code of the method + */ + private MethodSpec generateMethodImplementation(Assembler assembler, + MethodDetails method, + ClientDetails client) { + log.info(""); + log.info("{}", method); + + MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getMethodName()) + .returns(method.getReturnType()) + .addModifiers(Modifier.PUBLIC) + .addParameters(method.getParameterSpec()); + + assembler.assemble(client, method).forEach(builder::addStatement); + + return builder.build(); + } + + public Optional generateArgumentDTO(MethodDetails details, String packageName) { + return Optional.of(details) + .filter(MethodDetails::hasParameters) + .map(it -> { + String name = details.getArgumentClassname(); + PojoBuilder builder = PojoBuilder.newType(name, packageName).staTic(); + details.getParameters() + .forEach(variable -> { + String field = variable.getName(); + if (variable.getAnnotation() != null) { + field = variable.getAnnotation().value(); + } + builder.withField(variable.getType(), field); + }); + return builder.buildClass(); + }); + } + + private void writeToFile(TypeSpec spec, String packageName) throws IOException { + JavaFile.builder(packageName, spec) + .indent("\t") + .skipJavaLangImports(true) + .build() + .writeTo(filer); + } + +} + diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/MemberVariable.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/MemberVariable.java new file mode 100644 index 0000000..9b4e864 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/MemberVariable.java @@ -0,0 +1,15 @@ +package com.jacobmountain.graphql.client.code; + +import com.squareup.javapoet.TypeName; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class MemberVariable { + + String name; + + TypeName type; + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingAssembler.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingAssembler.java new file mode 100644 index 0000000..550a539 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingAssembler.java @@ -0,0 +1,21 @@ +package com.jacobmountain.graphql.client.code.blocking; + +import com.jacobmountain.graphql.client.Fetcher; +import com.jacobmountain.graphql.client.Subscriber; +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.Assembler; +import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.utils.Schema; + +public class BlockingAssembler extends Assembler { + public BlockingAssembler(QueryGenerator queryGenerator, + Schema schema, + TypeMapper typeMapper) { + super( + new BlockingQueryStage(queryGenerator, schema, typeMapper), + new OptionalReturnStage(schema, typeMapper), + schema, typeMapper, + Fetcher.class, Subscriber.class + ); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/BlockingQueryStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingQueryStage.java similarity index 63% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/BlockingQueryStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingQueryStage.java index bcda7ba..c2f0def 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/BlockingQueryStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingQueryStage.java @@ -1,13 +1,13 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code.blocking; -import com.jacobmountain.graphql.client.Fetcher; -import com.jacobmountain.graphql.client.Subscriber; import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.AbstractQueryStage; import com.jacobmountain.graphql.client.dto.Response; import com.jacobmountain.graphql.client.exceptions.MissingAnnotationException; import com.jacobmountain.graphql.client.query.QueryGenerator; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -15,43 +15,18 @@ import com.squareup.javapoet.TypeVariableName; import graphql.language.ObjectTypeDefinition; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Optional; public class BlockingQueryStage extends AbstractQueryStage { private static final ClassName RESPONSE_CLASS_NAME = ClassName.get(Response.class); - public BlockingQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper, String dtoPackageName) { - super(queryGenerator, schema, typeMapper, dtoPackageName); + public BlockingQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper) { + super(queryGenerator, schema, typeMapper); } @Override - public List getMemberVariables(ClientDetails details) { - ArrayList vars = new ArrayList<>(); - if (details.requiresFetcher()) { - vars.add( - MemberVariable.builder() - .name("fetcher") - .type(getFetcherTypeName(Fetcher.class)) - .build() - ); - } - if (details.requiresSubscriber()) { - vars.add( - MemberVariable.builder() - .name("subscriber") - .type(getSubscriberTypeName(Subscriber.class)) - .build() - ); - } - return vars; - } - - @Override - public List assemble(ClientDetails client, MethodDetails method) { + public Optional assemble(ClientDetails client, MethodDetails method) { String member = method.isSubscription() ? "subscriber" : "fetcher"; ObjectTypeDefinition query = getTypeDefinition(method); final CodeBlock.Builder builder = CodeBlock.builder(); @@ -63,9 +38,8 @@ public List assemble(ClientDetails client, MethodDetails method) { queryCode = generateQueryCode(method.getRequestName(), method); builder.add("$T thing = ", ParameterizedTypeName.get(RESPONSE_CLASS_NAME, typeMapper.getType(query.getName()), TypeVariableName.get("Error"))); } - return Collections.singletonList( - builder.add("$L.$L", member, getMethod(method)).add(queryCode).build() - ); + builder.add("$L.$L", member, getMethod(method)).add(queryCode); + return Optional.of(builder.build()); } private CodeBlock unwrapResponseLambda(ObjectTypeDefinition query, MethodDetails method) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/OptionalReturnStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/OptionalReturnStage.java similarity index 81% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/OptionalReturnStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/OptionalReturnStage.java index 02d48c0..a1a018b 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/OptionalReturnStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/OptionalReturnStage.java @@ -1,9 +1,11 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code.blocking; import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.AbstractStage; import com.jacobmountain.graphql.client.dto.Response; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -11,7 +13,6 @@ import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -25,12 +26,12 @@ public OptionalReturnStage(Schema schema, TypeMapper typeMapper) { } @Override - public List assemble(ClientDetails client, MethodDetails method) { + public Optional assemble(ClientDetails client, MethodDetails method) { if (ClassName.VOID.equals(method.getReturnType())) { if (method.isQuery()) { throw new IllegalArgumentException("void return type on a non mutation method"); } - return Collections.emptyList(); + return Optional.empty(); } ObjectTypeDefinition typeDefinition = getTypeDefinition(method); List ret = new ArrayList<>(); @@ -42,7 +43,7 @@ public List assemble(ClientDetails client, MethodDetails method) { if (!method.returnsClass(Optional.class)) { ret.add(CodeBlock.of("orElse(null)")); } - return Collections.singletonList(CodeBlock.join(ret, "\n\t.")); + return Optional.of(CodeBlock.join(ret, "\n\t.")); } } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveAssembler.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveAssembler.java new file mode 100644 index 0000000..277305d --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveAssembler.java @@ -0,0 +1,21 @@ +package com.jacobmountain.graphql.client.code.reactive; + +import com.jacobmountain.graphql.client.ReactiveFetcher; +import com.jacobmountain.graphql.client.ReactiveSubscriber; +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.Assembler; +import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.utils.Schema; + +public class ReactiveAssembler extends Assembler { + public ReactiveAssembler(QueryGenerator queryGenerator, + Schema schema, + TypeMapper typeMapper) { + super( + new ReactiveQueryStage(queryGenerator, schema, typeMapper), + new ReactiveReturnStage(schema, typeMapper), + schema, typeMapper, + ReactiveFetcher.class, ReactiveSubscriber.class + ); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveQueryStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveQueryStage.java new file mode 100644 index 0000000..ff95658 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveQueryStage.java @@ -0,0 +1,32 @@ +package com.jacobmountain.graphql.client.code.reactive; + +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.AbstractQueryStage; +import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.visitor.ClientDetails; +import com.jacobmountain.graphql.client.visitor.MethodDetails; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.ParameterizedTypeName; +import org.reactivestreams.Publisher; + +import java.util.Optional; + +public class ReactiveQueryStage extends AbstractQueryStage { + + public ReactiveQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper) { + super(queryGenerator, schema, typeMapper); + } + + @Override + public Optional assemble(ClientDetails client, MethodDetails method) { + String member = method.isSubscription() ? "subscriber" : "fetcher"; + return Optional.of( + CodeBlock.builder() + .add("$T thing = ", ParameterizedTypeName.get(ClassName.get(Publisher.class), getReturnTypeName(method))) + .add("$L.$L", member, getMethod(method)).add(generateQueryCode(method.getRequestName(), method)) + .build() + ); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveReturnStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveReturnStage.java similarity index 85% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveReturnStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveReturnStage.java index 40557a5..7df9fa0 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveReturnStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveReturnStage.java @@ -1,9 +1,11 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code.reactive; import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.AbstractStage; import com.jacobmountain.graphql.client.dto.Response; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -12,7 +14,10 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.function.Function; public class ReactiveReturnStage extends AbstractStage { @@ -24,12 +29,12 @@ public ReactiveReturnStage(Schema schema, TypeMapper typeMapper) { } @Override - public List assemble(ClientDetails client, MethodDetails method) { + public Optional assemble(ClientDetails client, MethodDetails method) { if (ClassName.VOID.equals(method.getReturnType())) { if (!method.isMutation()) { throw new IllegalArgumentException("void return type on a non mutation method"); } - return Collections.singletonList( + return Optional.of( CodeBlock.of("$T.from(thing).block()", method.isSubscription() ? Flux.class : Mono.class) ); } @@ -47,7 +52,7 @@ public List assemble(ClientDetails client, MethodDetails method) { unwrapReturnType(method).ifPresent(ret::add); - return Collections.singletonList(CodeBlock.join(ret, "\n\t.")); + return Optional.of(CodeBlock.join(ret, "\n\t.")); } private Optional unwrapReturnType(MethodDetails method) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ClientDetails.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ClientDetails.java deleted file mode 100644 index b66134c..0000000 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ClientDetails.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jacobmountain.graphql.client.modules; - -import lombok.Builder; - -@Builder -public class ClientDetails { - - private final boolean requiresSubscriber; - - private final boolean requiresFetcher; - - public boolean requiresSubscriber() { - return requiresSubscriber; - } - - public boolean requiresFetcher() { - return requiresFetcher; - } - -} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveQueryStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveQueryStage.java deleted file mode 100644 index 2a42d8f..0000000 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveQueryStage.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.jacobmountain.graphql.client.modules; - -import com.jacobmountain.graphql.client.ReactiveFetcher; -import com.jacobmountain.graphql.client.ReactiveSubscriber; -import com.jacobmountain.graphql.client.TypeMapper; -import com.jacobmountain.graphql.client.query.QueryGenerator; -import com.jacobmountain.graphql.client.utils.Schema; -import com.jacobmountain.graphql.client.visitor.MethodDetails; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.ParameterizedTypeName; -import org.reactivestreams.Publisher; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class ReactiveQueryStage extends AbstractQueryStage { - - public ReactiveQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper, String dtoPackageName) { - super(queryGenerator, schema, typeMapper, dtoPackageName); - } - - @Override - public List getMemberVariables(ClientDetails details) { - ArrayList vars = new ArrayList<>(); - if (details.requiresFetcher()) { - vars.add( - MemberVariable.builder() - .name("fetcher") - .type(getFetcherTypeName(ReactiveFetcher.class)) - .build() - ); - } - if (details.requiresSubscriber()) { - vars.add( - MemberVariable.builder() - .name("subscriber") - .type(getSubscriberTypeName(ReactiveSubscriber.class)) - .build() - ); - } - return vars; - } - - @Override - public List assemble(ClientDetails client, MethodDetails method) { - String member = method.isSubscription() ? "subscriber" : "fetcher"; - return Collections.singletonList( - CodeBlock.builder() - .add("$T thing = ", ParameterizedTypeName.get(ClassName.get(Publisher.class), getReturnTypeName(method))) - .add("$L.$L", member, getMethod(method)).add(generateQueryCode(method.getRequestName(), method)) - .build() - ); - } -} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryContext.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryContext.java index 3661ffd..c0244d8 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryContext.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryContext.java @@ -36,4 +36,5 @@ public Type getType() { public void newArg(String arg) { args.add(arg); } + } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryGenerator.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryGenerator.java index 1dbf013..247d8bf 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryGenerator.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryGenerator.java @@ -4,6 +4,7 @@ import com.jacobmountain.graphql.client.query.filters.*; import com.jacobmountain.graphql.client.query.selectors.DefaultFieldSelector; import com.jacobmountain.graphql.client.query.selectors.DelegatingFieldSelector; +import com.jacobmountain.graphql.client.query.selectors.FieldSelector; import com.jacobmountain.graphql.client.query.selectors.InlineFragmentRenderer; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; @@ -19,8 +20,14 @@ public class QueryGenerator { private final Schema schema; + private final FieldSelector fieldSelector; + public QueryGenerator(Schema registry) { this.schema = registry; + this.fieldSelector = new DelegatingFieldSelector( + new DefaultFieldSelector(schema, this), + new InlineFragmentRenderer(schema, this) + ); } public QueryBuilder query() { @@ -64,7 +71,7 @@ public Optional generateFieldSelection(String alias, String type = Schema.unwrap(context.getFieldDefinition().getType()); TypeDefinition typeDefinition = schema.getTypeDefinition(type).orElse(null); - if (!filters.stream().allMatch(fi -> fi.shouldAddField(context))) { + if (!filters.stream().allMatch(filter -> filter.shouldAddField(context))) { return Optional.empty(); } @@ -73,11 +80,7 @@ public Optional generateFieldSelection(String alias, return Optional.of(alias + args); } - return new DelegatingFieldSelector( - new DefaultFieldSelector(schema, this), - new InlineFragmentRenderer(schema, this) - ) - .selectFields(typeDefinition, context, filters) + return fieldSelector.selectFields(typeDefinition, context, filters) .map(children -> alias + args + " " + children) .findFirst(); } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java index 01ec3f8..7b0f1c3 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java @@ -25,8 +25,9 @@ public Stream selectFields(TypeDefinition typeDefinition, .reduce((a, b) -> String.join(" ", a, b)) .map(children -> "{ " + children + - " __typename" + - " }") + " __typename " + + "}" + ) .map(Stream::of) .orElseGet(Stream::empty); } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java new file mode 100644 index 0000000..77a9a9d --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java @@ -0,0 +1,31 @@ +package com.jacobmountain.graphql.client.visitor; + +import com.squareup.javapoet.ClassName; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class ClientDetails { + + private String name; + + private ClassName clientInterface; + + private boolean requiresSubscriber; + + private boolean requiresFetcher; + + private List methods; + + public boolean requiresSubscriber() { + return requiresSubscriber; + } + + public boolean requiresFetcher() { + return requiresFetcher; + } + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java index 8ecc232..26c43ca 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java @@ -3,33 +3,46 @@ import com.jacobmountain.graphql.client.annotations.GraphQLMutation; import com.jacobmountain.graphql.client.annotations.GraphQLQuery; import com.jacobmountain.graphql.client.annotations.GraphQLSubscription; -import com.jacobmountain.graphql.client.modules.ClientDetails; +import com.squareup.javapoet.ClassName; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementKindVisitor8; import java.lang.annotation.Annotation; +import java.util.List; +import java.util.stream.Collectors; -public class ClientDetailsVisitor extends ElementKindVisitor8 { +public class ClientDetailsVisitor extends ElementKindVisitor8 { @Override - public ClientDetails visitType(TypeElement type, Void unused) { + public ClientDetails visitType(TypeElement type, ClientDetailsVisitorArgs args) { return ClientDetails.builder() + .name(type.getSimpleName().toString()) + .clientInterface(ClassName.get(type)) .requiresSubscriber(requiresSubscriber(type)) .requiresFetcher(requiresFetcher(type)) + .methods(visitMethods(args, type)) .build(); } + private List visitMethods(ClientDetailsVisitorArgs args, TypeElement type) { + final MethodDetailsVisitor methodVisitor = new MethodDetailsVisitor(args.getSchema()); + return type.getEnclosedElements() + .stream() + .map(method -> methodVisitor.visit(method, args.getTypeMapper())) + .collect(Collectors.toList()); + } + private boolean requiresSubscriber(TypeElement element) { return element.getEnclosedElements() .stream() - .anyMatch(it -> hasAnnotation(it, GraphQLSubscription.class)); + .anyMatch(method -> hasAnnotation(method, GraphQLSubscription.class)); } private boolean requiresFetcher(TypeElement element) { return element.getEnclosedElements() .stream() - .anyMatch(it -> hasAnnotation(it, GraphQLQuery.class) || hasAnnotation(it, GraphQLMutation.class)); + .anyMatch(method -> hasAnnotation(method, GraphQLQuery.class) || hasAnnotation(method, GraphQLMutation.class)); } private boolean hasAnnotation(Element el, Class annotation) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitorArgs.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitorArgs.java new file mode 100644 index 0000000..535bc55 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitorArgs.java @@ -0,0 +1,16 @@ +package com.jacobmountain.graphql.client.visitor; + +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.utils.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ClientDetailsVisitorArgs { + + private Schema schema; + + private TypeMapper typeMapper; + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetailsVisitor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetailsVisitor.java index b519196..6c3bddb 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetailsVisitor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetailsVisitor.java @@ -22,7 +22,7 @@ import java.util.stream.Collectors; @Slf4j -public class MethodDetailsVisitor extends ElementKindVisitor8 { +class MethodDetailsVisitor extends ElementKindVisitor8 { private final Schema schema; diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ArgumentAssemblyStageSpec.groovy similarity index 83% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ArgumentAssemblyStageSpec.groovy index 2c6a8dd..120d9aa 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ArgumentAssemblyStageSpec.groovy @@ -1,12 +1,12 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.code -import com.jacobmountain.graphql.client.modules.ArgumentAssemblyStage -import com.jacobmountain.graphql.client.modules.ClientDetails +import com.jacobmountain.graphql.client.code.ArgumentAssemblyStage +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import com.jacobmountain.graphql.client.visitor.Parameter import spock.lang.Specification -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class ArgumentAssemblyStageSpec extends Specification { @@ -18,10 +18,9 @@ class ArgumentAssemblyStageSpec extends Specification { .parameters([]) .build() ) - def code = renderBlocks(blocks).split(";") then: - renderBlocks(blocks).split(";") == [] + !blocks.isPresent() } def "When there's a parameter, we should set it"() { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/AssemblerSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/AssemblerSpec.groovy new file mode 100644 index 0000000..65480b2 --- /dev/null +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/AssemblerSpec.groovy @@ -0,0 +1,103 @@ +package com.jacobmountain.client.code + +import com.jacobmountain.graphql.client.* +import com.jacobmountain.graphql.client.code.Assembler +import com.jacobmountain.graphql.client.code.MemberVariable +import com.jacobmountain.graphql.client.code.blocking.BlockingAssembler +import com.jacobmountain.graphql.client.code.reactive.ReactiveAssembler +import com.jacobmountain.graphql.client.query.QueryGenerator +import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails +import spock.lang.Specification + +class AssemblerSpec extends Specification { + + def "The reactive client class is generated with the correct member variables"() { + given: + Assembler assembler = new ReactiveAssembler( + Mock(QueryGenerator), + new Schema(""" + schema { + query: Query + mutation: Mutation + subscription: Subscription + } + type Query { } + type Mutation { } + type Subscription { } + """), + new TypeMapper("com.test") + ) + + when: + def members = assembler.getMemberVariables( + ClientDetails.builder() + .requiresFetcher(requiresFetcher) + .requiresSubscriber(requiresSubscriber) + .build() + ) + + then: + members.size() == (requiresFetcher ? 1 : 0) + (requiresSubscriber ? 1 : 0) + getMemberVariable("fetcher", members) + .map { it -> it.type.toString() == "${ReactiveFetcher.class.getName()}" } + .orElse(true) + getMemberVariable("subscriber", members) + .map { it -> it.type.toString() == "${ReactiveSubscriber.class.getName()}" } + .orElse(true) + + where: + requiresFetcher | requiresSubscriber + true | true + true | false + false | true + false | false + } + + def "The default client class is generated with the correct member variables"() { + given: + Assembler assembler = new BlockingAssembler( + Mock(QueryGenerator), + new Schema(""" + schema { + query: Query + mutation: Mutation + subscription: Subscription + } + type Query { } + type Mutation { } + type Subscription { } + """), + new TypeMapper("com.test") + ) + + when: + def members = assembler.getMemberVariables( + ClientDetails.builder() + .requiresFetcher(requiresFetcher) + .requiresSubscriber(requiresSubscriber) + .build() + ) + + then: + members.size() == (requiresFetcher ? 1 : 0) + (requiresSubscriber ? 1 : 0) + getMemberVariable("fetcher", members) + .map { it -> it.type.toString() == "${Fetcher.class.getName()}" } + .orElse(true) + getMemberVariable("subscriber", members) + .map { it -> it.type.toString() == "${Subscriber.class.getName()}" } + .orElse(true) + + where: + requiresFetcher | requiresSubscriber + true | true + true | false + false | true + false | false + } + + static Optional getMemberVariable(String name, List variables) { + Optional.ofNullable(variables.find { it.name == name }) + } + +} diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ClientGeneratorSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ClientGeneratorSpec.groovy new file mode 100644 index 0000000..e3135e1 --- /dev/null +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ClientGeneratorSpec.groovy @@ -0,0 +1,127 @@ +//package com.jacobmountain.client.code +// +//import com.jacobmountain.graphql.client.TypeMapper +//import com.jacobmountain.graphql.client.code.ClientGenerator +//import com.jacobmountain.graphql.client.utils.Schema +//import spock.lang.Specification +// +//import javax.annotation.processing.Filer +//import javax.lang.model.element.AnnotationMirror +//import javax.lang.model.element.Element +//import javax.lang.model.element.ElementKind +//import javax.lang.model.element.ElementVisitor +//import javax.lang.model.element.Modifier +//import javax.lang.model.element.Name +//import javax.lang.model.element.NestingKind +//import javax.lang.model.element.TypeElement +//import javax.lang.model.element.TypeParameterElement +//import javax.lang.model.type.TypeMirror +//import java.lang.annotation.Annotation +// +//class ClientGeneratorSpec extends Specification { +// +// ClientGenerator generator = new ClientGenerator( +// Mock(Filer), +// new TypeMapper("com.test"), +// "com.test", +// new Schema(""" +// schema { +// query: Query +// } +// type Query {} +// """), +// false +// ) +// +// def "Test"() { +// given: +// Element el = Mock(TypeElement) { +// getSimpleName() >> Mock(Name) { +// toString() >> "MyClient" +// } +// getEnclosingElement() >> new TypeElement() { +// @Override +// List getEnclosedElements() { +// return null +// } +// +// @Override +// NestingKind getNestingKind() { +// return null +// } +// +// @Override +// Name getQualifiedName() { +// return null +// } +// +// @Override +// Name getSimpleName() { +// return null +// } +// +// @Override +// TypeMirror getSuperclass() { +// return null +// } +// +// @Override +// List getInterfaces() { +// return null +// } +// +// @Override +// List getTypeParameters() { +// return null +// } +// +// @Override +// Element getEnclosingElement() { +// return null +// } +// +// @Override +// TypeMirror asType() { +// return null +// } +// +// @Override +// ElementKind getKind() { +// return null +// } +// +// @Override +// Set getModifiers() { +// return null +// } +// +// @Override +// List getAnnotationMirrors() { +// return null +// } +// +// @Override +// def A getAnnotation(Class annotationType) { +// return null +// } +// +// @Override +// def R accept(ElementVisitor v, P p) { +// return null +// } +// +// @Override +// def A[] getAnnotationsByType(Class annotationType) { +// return null +// } +// } +// } +// +// when: +// generator.generate(el, "impl") +// +// then: +// true +// } +// +//} diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/CodeBlockUtils.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/CodeBlockUtils.groovy new file mode 100644 index 0000000..1d4d958 --- /dev/null +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/CodeBlockUtils.groovy @@ -0,0 +1,12 @@ +package com.jacobmountain.client.code + +import com.squareup.javapoet.CodeBlock + +class CodeBlockUtils { + + static def renderBlocks(Optional blocks) { + blocks.get().toString() + .replaceAll("\\t|\\n", "") + ";" + } + +} diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/BlockingQueryStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/BlockingQueryStageSpec.groovy similarity index 60% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/BlockingQueryStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/BlockingQueryStageSpec.groovy index dd8e833..4e12298 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/BlockingQueryStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/BlockingQueryStageSpec.groovy @@ -1,19 +1,16 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.code.blocking + -import com.jacobmountain.graphql.client.Fetcher -import com.jacobmountain.graphql.client.Subscriber import com.jacobmountain.graphql.client.TypeMapper +import com.jacobmountain.graphql.client.code.blocking.BlockingQueryStage import com.jacobmountain.graphql.client.dto.Response -import com.jacobmountain.graphql.client.modules.AbstractStage -import com.jacobmountain.graphql.client.modules.BlockingQueryStage -import com.jacobmountain.graphql.client.modules.ClientDetails import com.jacobmountain.graphql.client.query.QueryGenerator import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import spock.lang.Specification -import spock.lang.Unroll -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class BlockingQueryStageSpec extends Specification { @@ -37,8 +34,7 @@ class BlockingQueryStageSpec extends Specification { type Mutation { } type Subscription { } """), - new TypeMapper("com.test"), - "com.test" + new TypeMapper("com.test") ) def "We can generate the code for a query"() { @@ -80,34 +76,4 @@ class BlockingQueryStageSpec extends Specification { code.endsWith("subscription -> java.util.Optional.ofNullable(subscription).map(com.jacobmountain.graphql.client.dto.Response::getData).map(com.test.Subscription::getField).ifPresent(callback));") } - @Unroll - def "The reactive client class is generated with the correct member variables"() { - when: - def members = stage.getMemberVariables( - ClientDetails.builder() - .requiresFetcher(requiresFetcher) - .requiresSubscriber(requiresSubscriber) - .build() - ) - - then: - members.size() == (requiresFetcher ? 1 : 0) + (requiresSubscriber ? 1 : 0) - getMemberVariable("fetcher", members) - .map { it -> it.type.toString() == "${Fetcher.class.getName()}" } - .orElse(true) - getMemberVariable("subscriber", members) - .map { it -> it.type.toString() == "${Subscriber.class.getName()}" } - .orElse(true) - - where: - requiresFetcher | requiresSubscriber - true | true - false | true - true | false - false | false - } - - static Optional getMemberVariable(String name, List variables) { - Optional.ofNullable(variables.find { it.name == name }) - } } diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/OptionalReturnStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/OptionalReturnStageSpec.groovy similarity index 90% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/OptionalReturnStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/OptionalReturnStageSpec.groovy index 8c13b80..c430714 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/OptionalReturnStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/OptionalReturnStageSpec.groovy @@ -1,16 +1,16 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.code.blocking import com.jacobmountain.graphql.client.TypeMapper +import com.jacobmountain.graphql.client.code.blocking.OptionalReturnStage import com.jacobmountain.graphql.client.dto.Response -import com.jacobmountain.graphql.client.modules.ClientDetails -import com.jacobmountain.graphql.client.modules.OptionalReturnStage import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import com.squareup.javapoet.ClassName import com.squareup.javapoet.ParameterizedTypeName import spock.lang.Specification -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class OptionalReturnStageSpec extends Specification { @@ -39,7 +39,7 @@ class OptionalReturnStageSpec extends Specification { def blocks = stage.assemble(Mock(ClientDetails), methodDetails) then: "there shouldn't be any " - blocks.isEmpty() + !blocks.isPresent() } def "void return types are only supported on mutations"() { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveQueryStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveQueryStageSpec.groovy similarity index 59% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveQueryStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveQueryStageSpec.groovy index 78d501d..ee54224 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveQueryStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveQueryStageSpec.groovy @@ -1,19 +1,17 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.code.reactive + -import com.jacobmountain.graphql.client.ReactiveFetcher -import com.jacobmountain.graphql.client.ReactiveSubscriber import com.jacobmountain.graphql.client.TypeMapper +import com.jacobmountain.graphql.client.code.reactive.ReactiveQueryStage import com.jacobmountain.graphql.client.dto.Response -import com.jacobmountain.graphql.client.modules.AbstractStage -import com.jacobmountain.graphql.client.modules.ClientDetails -import com.jacobmountain.graphql.client.modules.ReactiveQueryStage import com.jacobmountain.graphql.client.query.QueryGenerator import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import org.reactivestreams.Publisher import spock.lang.Specification -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class ReactiveQueryStageSpec extends Specification { @@ -37,8 +35,7 @@ class ReactiveQueryStageSpec extends Specification { type Mutation { } type Subscription { } """), - new TypeMapper("com.test"), - "com.test" + new TypeMapper("com.test") ) def "We can generate the code for a query"() { @@ -76,34 +73,6 @@ class ReactiveQueryStageSpec extends Specification { renderBlocks(blocks) == """${Publisher.class.getName()}<${Response.class.getName()}> thing = subscriber.subscribe("query", null);""" } - def "The reactive client class is generated with the correct member variables"() { - when: - def members = stage.getMemberVariables( - ClientDetails.builder() - .requiresFetcher(requiresFetcher) - .requiresSubscriber(requiresSubscriber) - .build() - ) - - then: - members.size() == (requiresFetcher ? 1 : 0) + (requiresSubscriber ? 1 : 0) - getMemberVariable("fetcher", members) - .map { it -> it.type.toString() == "${ReactiveFetcher.class.getName()}" } - .orElse(true) - getMemberVariable("subscriber", members) - .map { it -> it.type.toString() == "${ReactiveSubscriber.class.getName()}" } - .orElse(true) - - where: - requiresFetcher | requiresSubscriber - true | true - true | false - false | true - false | false - } - static Optional getMemberVariable(String name, List variables) { - Optional.ofNullable(variables.find { it.name == name }) - } } diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveReturnStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveReturnStageSpec.groovy similarity index 94% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveReturnStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveReturnStageSpec.groovy index c81c8b3..91b9ee7 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveReturnStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveReturnStageSpec.groovy @@ -1,10 +1,10 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.code.reactive import com.jacobmountain.graphql.client.TypeMapper +import com.jacobmountain.graphql.client.code.reactive.ReactiveReturnStage import com.jacobmountain.graphql.client.dto.Response -import com.jacobmountain.graphql.client.modules.ClientDetails -import com.jacobmountain.graphql.client.modules.ReactiveReturnStage import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import com.squareup.javapoet.ClassName import com.squareup.javapoet.ParameterizedTypeName @@ -14,7 +14,7 @@ import spock.lang.Specification import java.util.function.Function -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class ReactiveReturnStageSpec extends Specification { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy deleted file mode 100644 index a842b8e..0000000 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy +++ /dev/null @@ -1,14 +0,0 @@ -package com.jacobmountain.client.modules - -import com.squareup.javapoet.CodeBlock - -class CodeBlockUtils { - - static def renderBlocks(List blocks) { - blocks.collect { block -> - block.toString() - .replaceAll("\\t|\\n", "") - }.join(";") + ";" - } - -} diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy deleted file mode 100644 index 65c4c26..0000000 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy +++ /dev/null @@ -1,85 +0,0 @@ -package com.jacobmountain.query - -import com.jacobmountain.graphql.client.query.QueryGenerator -import com.jacobmountain.graphql.client.utils.Schema -import spock.lang.Specification - -class QueryGeneratorSpec extends Specification { - - static QueryGenerator givenQuery(String query, String types) { - new QueryGenerator(new Schema(""" - schema { - query: Query - } - type Query { - $query - } - $types - """)) - } - - def "I can generate a query with a fragment"() { - given: - def generator = givenQuery("hero: Hero",""" - type Hero { - id: String - name: String - } - """) - - when: - def query = generator.query() - .build(null, "hero", [] as Set) - - then: - query == "query Hero { hero { id name __typename } }" - } - - def "I can generate a complex query with a fragment"() { - given: - def generator = givenQuery("hero: Hero",""" - type Hero { - id: String - name: String - ships: [Starship!]! - } - type Starship { - id: String! - name: String! - length(unit: LengthUnit = METER): Float - coordinates: [[Float!]!] - } - enum LengthUnit { - METER - FOOT - } - """) - - when: - def query = generator.query() - .build(null, "hero", [] as Set) - - then: - query == "query Hero { hero { id name ships { id name length coordinates __typename } __typename } }" - } - - def "I can generate a recursive query with a fragment"() { - given: - def generator = givenQuery("hero: Hero",""" - type Hero { - id: String - name: String - friends: [Hero!]! - } - """) - - when: - def query = generator.query() - .maxDepth(3) - .build(null, "hero", [] as Set) - - then: - query == "query Hero { hero { id name friends { id name friends { id name __typename } __typename } __typename } }" - } - -}