From 5dc4f88669b7f4f16eac4632a7d2548c8b7b461b Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Fri, 22 Jan 2021 23:15:48 +0000 Subject: [PATCH 01/15] Field Selectors Signed-off-by: Jacob Mountain --- .../com/jacobmountain/StarWarsClient.java | 3 +- .../client/annotations/GraphQLFragment.java | 9 ++++ .../client/query/DefaultFieldSelector.java | 31 +++++++++++ .../graphql/client/query/FieldSelector.java | 16 ++++++ .../client/query/InlineFragmentRenderer.java | 34 ++++++++++++ .../graphql/client/query/QueryContext.java | 12 ++--- .../graphql/client/query/QueryGenerator.java | 54 +++++++++---------- .../query/filters/FieldDuplicationFilter.java | 46 ++++++++++++++++ 8 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java diff --git a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java index a6af762..22c1fcf 100644 --- a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java +++ b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java @@ -22,10 +22,11 @@ public interface StarWarsClient { @GraphQLQuery(value = "hero", name = "HeroSummary") com.jacobmountain.dto.Character getHero(@GraphQLArgument("hero") String id); - @GraphQLQuery(value = "hero", select = { + @GraphQLFragment(type = "Character", select = { @GraphQLField("id"), @GraphQLField("name") }) + @GraphQLQuery(value = "hero") com.jacobmountain.dto.Character getHeroSummary(String id); @GraphQLQuery("hero") diff --git a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java new file mode 100644 index 0000000..be109b4 --- /dev/null +++ b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java @@ -0,0 +1,9 @@ +package com.jacobmountain.graphql.client.annotations; + +public @interface GraphQLFragment { + + String type(); + + GraphQLField[] select() default {}; + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java new file mode 100644 index 0000000..a4eab4c --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java @@ -0,0 +1,31 @@ +package com.jacobmountain.graphql.client.query; + +import com.jacobmountain.graphql.client.utils.Schema; +import graphql.language.TypeDefinition; +import lombok.RequiredArgsConstructor; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public class DefaultFieldSelector implements FieldSelector { + + private final Schema schema; + + private final QueryGenerator queryGenerator; + + @Override + public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { + return schema.getChildren(typeDefinition) + .map(definition -> queryGenerator.generateFieldSelection( + definition.getName(), + context.withType(definition).increment(), + argumentCollector, + filters + )) + .filter(Optional::isPresent) + .map(Optional::get); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java new file mode 100644 index 0000000..6faad28 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java @@ -0,0 +1,16 @@ +package com.jacobmountain.graphql.client.query; + +import graphql.language.TypeDefinition; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +public interface FieldSelector { + + Stream selectFields(TypeDefinition typeDefinition, + QueryContext context, + Set argumentCollector, + List filters); + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java new file mode 100644 index 0000000..7b71d81 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java @@ -0,0 +1,34 @@ +package com.jacobmountain.graphql.client.query; + +import com.jacobmountain.graphql.client.utils.Schema; +import graphql.language.FieldDefinition; +import graphql.language.TypeDefinition; +import graphql.language.TypeName; +import lombok.RequiredArgsConstructor; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public class InlineFragmentRenderer implements FieldSelector { + + private final Schema schema; + + private final QueryGenerator queryGenerator; + + @Override + public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { + return schema.getTypesImplementing(typeDefinition) + .map(interfac -> queryGenerator.generateFieldSelection( + interfac, + context.withType(new FieldDefinition(interfac, new TypeName(interfac))), + argumentCollector, + filters + )) + .filter(Optional::isPresent) + .map(Optional::get) + .map(query -> "... on " + query); + } +} 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 f81bc4a..eb0a280 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 @@ -11,24 +11,20 @@ @AllArgsConstructor public class QueryContext { + QueryContext parent; + int depth; FieldDefinition fieldDefinition; Set params; - Set visited; - QueryContext increment() { - return new QueryContext(depth + 1, fieldDefinition, params, new HashSet<>()); + return new QueryContext(this, depth + 1, fieldDefinition, params); } QueryContext withType(FieldDefinition fieldDefinition) { - return new QueryContext(depth, fieldDefinition, params, visited); - } - - QueryContext withVisited(Set visited) { - return new QueryContext(depth, fieldDefinition, params, visited); + return new QueryContext(parent, depth, fieldDefinition, params); } } 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 fd73449..35e57ec 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 @@ -3,6 +3,7 @@ import com.jacobmountain.graphql.client.annotations.GraphQLField; import com.jacobmountain.graphql.client.exceptions.FieldNotFoundException; import com.jacobmountain.graphql.client.query.filters.AllNonNullArgsFieldFilter; +import com.jacobmountain.graphql.client.query.filters.FieldDuplicationFilter; import com.jacobmountain.graphql.client.query.filters.MaxDepthFieldFilter; import com.jacobmountain.graphql.client.query.filters.SelectionFieldFilter; import com.jacobmountain.graphql.client.utils.Schema; @@ -10,9 +11,11 @@ import graphql.com.google.common.collect.Streams; import graphql.language.*; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.Opt; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; @Slf4j public class QueryGenerator { @@ -40,7 +43,7 @@ private String doGenerateQuery(String request, String field, String type, Set args = new HashSet<>(); - String inner = generateQueryRec(field, new QueryContext(1, definition, params, new HashSet<>()), args, filters).orElseThrow(RuntimeException::new); + String inner = generateFieldSelection(field, new QueryContext(null, 1, definition, params), args, filters).orElseThrow(RuntimeException::new); String collect = String.join(", ", args); @@ -68,10 +71,10 @@ private String generateQueryName(String request, String type, String field) { return type + " " + request; } - private Optional generateQueryRec(String alias, - QueryContext context, - Set argumentCollector, - List filters) { + public Optional generateFieldSelection(String alias, + QueryContext context, + Set argumentCollector, + List filters) { String type = unwrap(context.getFieldDefinition().getType()); TypeDefinition typeDefinition = schema.getTypeDefinition(type).orElse(null); @@ -84,36 +87,26 @@ private Optional generateQueryRec(String alias, return Optional.of(alias + args); } - Set visited = new HashSet<>(); - List children = Streams.concat( - schema.getChildren(typeDefinition) - .peek(it -> visited.add(it.getName())) // add to the list of discovered fields - .filter(it -> context.getVisited().add(it.getName())) // don't add to the list if we've already discovered these fields (used with interfaces) - .map(definition -> generateQueryRec( - definition.getName(), - context.withType(definition).increment(), - argumentCollector, - filters - )) - .filter(Optional::isPresent) - .map(Optional::get), - schema.getTypesImplementing(typeDefinition) - .map(interfac -> generateQueryRec( - interfac, - context.withType(new FieldDefinition(interfac, new TypeName(interfac))).withVisited(visited), - argumentCollector, - filters - )) - .filter(Optional::isPresent) - .map(Optional::get) - .map(query -> "... on " + query) - ).collect(Collectors.toList()); + return selectChildren(typeDefinition, context, argumentCollector, filters) + .map(children -> alias + args + " " + children); + } + private Optional selectChildren(TypeDefinition typeDefinition, + QueryContext context, + Set argumentCollector, + List filters) { + List selectors = Arrays.asList( + new DefaultFieldSelector(schema, this), + new InlineFragmentRenderer(schema, this) + ); + final List children = selectors.stream() + .flatMap(selector -> selector.selectFields(typeDefinition, context, argumentCollector, filters)) + .collect(Collectors.toList()); if (children.isEmpty()) { return Optional.empty(); } return Optional.of( - alias + args + " { " + + "{ " + String.join(" ", children) + " __typename" + " }" @@ -142,6 +135,7 @@ public QueryBuilder select(List selections) { public String build(String request, String field, Set params) { this.filters.add(new AllNonNullArgsFieldFilter()); + this.filters.add(new FieldDuplicationFilter()); return doGenerateQuery(request, field, type, params, filters); } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java new file mode 100644 index 0000000..8831b50 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java @@ -0,0 +1,46 @@ +package com.jacobmountain.graphql.client.query.filters; + +import com.jacobmountain.graphql.client.query.FieldFilter; +import com.jacobmountain.graphql.client.query.QueryContext; +import graphql.language.ListType; +import graphql.language.NonNullType; +import graphql.language.Type; +import lombok.extern.slf4j.Slf4j; + +import java.util.*; + +@Slf4j +public class FieldDuplicationFilter implements FieldFilter { + + private final Map> visited = new HashMap<>(); + + @Override + public boolean shouldAddField(QueryContext context) { + final String path = generatePath(context); + log.info("{} {}", path, context.getDepth()); + final Set strings = visited.computeIfAbsent(path, key -> new HashSet<>()); + return strings.add(context.getFieldDefinition().getName()); + } + + private String generatePath(QueryContext context) { + List path = new ArrayList<>(); + QueryContext parent = context; + while (parent != null) { + path.add(0, parent.getFieldDefinition().getName()); + parent = parent.getParent(); + } + path.set(path.size() - 1, unwrap(context.getFieldDefinition().getType())); + return String.join(".", path); + } + + private String unwrap(Type type) { + if (type instanceof ListType) { + return unwrap(((ListType) type).getType()); + } else if (type instanceof NonNullType) { + return unwrap(((NonNullType) type).getType()); + } else { + return ((graphql.language.TypeName) type).getName(); + } + } + +} From a68de8240c242d6c8377786d895d9f06589b9dc7 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 10:06:05 +0000 Subject: [PATCH 02/15] Add fragments --- .../com/jacobmountain/StarWarsClient.java | 5 +- .../client/annotations/GraphQLFragment.java | 3 + .../client/annotations/GraphQLFragments.java | 7 ++ .../client/modules/AbstractQueryStage.java | 1 + .../client/query/DefaultFieldSelector.java | 6 +- .../graphql/client/query/FieldSelector.java | 1 + .../client/query/FragmentRenderer.java | 73 +++++++++++++++++++ .../client/query/InlineFragmentRenderer.java | 3 +- .../graphql/client/query/QueryGenerator.java | 27 +++++-- .../query/filters/MaxDepthFieldFilter.java | 2 +- .../graphql/client/visitor/MethodDetails.java | 3 + .../client/visitor/MethodDetailsVisitor.java | 24 +++++- .../filters/MaxDepthFieldFilterSpec.groovy | 2 +- 13 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragments.java create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java diff --git a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java index 22c1fcf..b34b05b 100644 --- a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java +++ b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java @@ -26,7 +26,10 @@ public interface StarWarsClient { @GraphQLField("id"), @GraphQLField("name") }) - @GraphQLQuery(value = "hero") + @GraphQLQuery(value = "hero", select = { + @GraphQLField("id"), + @GraphQLField("name") + }) com.jacobmountain.dto.Character getHeroSummary(String id); @GraphQLQuery("hero") diff --git a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java index be109b4..3abacdf 100644 --- a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java +++ b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java @@ -1,5 +1,8 @@ package com.jacobmountain.graphql.client.annotations; +import java.lang.annotation.Repeatable; + +@Repeatable(GraphQLFragments.class) public @interface GraphQLFragment { String type(); diff --git a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragments.java b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragments.java new file mode 100644 index 0000000..e19c8c8 --- /dev/null +++ b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragments.java @@ -0,0 +1,7 @@ +package com.jacobmountain.graphql.client.annotations; + +public @interface GraphQLFragments { + + GraphQLFragment[] value(); + +} 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/modules/AbstractQueryStage.java index a7e308b..b3676b6 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/modules/AbstractQueryStage.java @@ -77,6 +77,7 @@ protected CodeBlock generateQueryCode(String request, MethodDetails details) { .collect(Collectors.toList()) ) .maxDepth(details.getMaxDepth()) + .fragments(details.getFragments()) .build(request, details.getField(), params); return CodeBlock.of( "(\"$L\", $L)", query, details.hasParameters() ? "args" : "null" diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java index a4eab4c..7582fc0 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java @@ -1,6 +1,7 @@ package com.jacobmountain.graphql.client.query; import com.jacobmountain.graphql.client.utils.Schema; +import graphql.language.FieldDefinition; import graphql.language.TypeDefinition; import lombok.RequiredArgsConstructor; @@ -17,15 +18,18 @@ public class DefaultFieldSelector implements FieldSelector { private final QueryGenerator queryGenerator; @Override - public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { + public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, FragmentRenderer fragmentRenderer, Set argumentCollector, List filters) { return schema.getChildren(typeDefinition) .map(definition -> queryGenerator.generateFieldSelection( definition.getName(), context.withType(definition).increment(), + fragmentRenderer, argumentCollector, filters )) .filter(Optional::isPresent) .map(Optional::get); } + + } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java index 6faad28..4ce3b18 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java @@ -10,6 +10,7 @@ public interface FieldSelector { Stream selectFields(TypeDefinition typeDefinition, QueryContext context, + FragmentRenderer fragmentRenderer, Set argumentCollector, List filters); diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java new file mode 100644 index 0000000..d41bdab --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java @@ -0,0 +1,73 @@ +package com.jacobmountain.graphql.client.query; + +import com.jacobmountain.graphql.client.annotations.GraphQLField; +import com.jacobmountain.graphql.client.annotations.GraphQLFragment; +import com.jacobmountain.graphql.client.query.filters.SelectionFieldFilter; +import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.utils.StringUtils; +import graphql.com.google.common.collect.Sets; +import graphql.language.Selection; +import graphql.language.TypeDefinition; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class FragmentRenderer implements FieldSelector { + + private final Schema schema; + + private final QueryGenerator queryGenerator; + + private final Map fragments; + + private final Map generated = new HashMap<>(); + + public FragmentRenderer(Schema schema, QueryGenerator queryGenerator, List fragments) { + this.schema = schema; + this.queryGenerator = queryGenerator; + this.fragments = fragments.stream() + .collect(Collectors.toMap(GraphQLFragment::type, Function.identity())); + } + + @Override + public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, FragmentRenderer fragmentRenderer, Set argumentCollector, List filters) { + final String type = Schema.unwrap(context.getFieldDefinition().getType()); + final GraphQLFragment graphQLFragment = fragments.get(type); + if (graphQLFragment == null || !type.equals(graphQLFragment.type())) { + return Stream.empty(); + } + final Set select = Stream.of(graphQLFragment.select()) + .map(GraphQLField::value) + .collect(Collectors.toSet()); + final List collect = schema.getChildren(typeDefinition) + .filter(field -> select.contains(field.getName())) + .map(definition -> queryGenerator.generateFieldSelection( + definition.getName(), + context.withType(definition).increment(), + this, + argumentCollector, + filters + )) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + final String name = getFragmentName(graphQLFragment); + generated.computeIfAbsent(graphQLFragment.type(), a -> "fragment " + name + " on " + graphQLFragment.type() + " { " + String.join(" ", collect) + " }"); + if (collect.size() > 0) { + return Stream.of("..." + name); + } + return Stream.empty(); + } + + private String getFragmentName(GraphQLFragment fragment) { + return StringUtils.camelCase(fragment.type()); + } + + public String render() { + return String.join(" ", generated.values()); + } + + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java index 7b71d81..bc64a13 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java @@ -19,11 +19,12 @@ public class InlineFragmentRenderer implements FieldSelector { private final QueryGenerator queryGenerator; @Override - public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { + public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, FragmentRenderer fragmentRenderer, Set argumentCollector, List filters) { return schema.getTypesImplementing(typeDefinition) .map(interfac -> queryGenerator.generateFieldSelection( interfac, context.withType(new FieldDefinition(interfac, new TypeName(interfac))), + fragmentRenderer, argumentCollector, filters )) 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 7592a25..17be3fe 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 @@ -1,5 +1,6 @@ package com.jacobmountain.graphql.client.query; +import com.jacobmountain.graphql.client.annotations.GraphQLFragment; import com.jacobmountain.graphql.client.exceptions.FieldNotFoundException; import com.jacobmountain.graphql.client.query.filters.AllNonNullArgsFieldFilter; import com.jacobmountain.graphql.client.query.filters.FieldDuplicationFilter; @@ -38,12 +39,15 @@ public QueryBuilder subscription() { return new QueryBuilder("subscription"); } - private String doGenerateQuery(String request, String field, String type, Set params, List filters) { + private String doGenerateQuery(String request, String field, String type, List fragments, Set params, List filters) { FieldDefinition definition = schema.findField(field).orElseThrow(FieldNotFoundException.create(field)); Set args = new HashSet<>(); - String inner = generateFieldSelection(field, new QueryContext(null, 1, definition, params), args, filters).orElseThrow(RuntimeException::new); + final FragmentRenderer fr = new FragmentRenderer(schema, this, fragments); + final QueryContext root = new QueryContext(null, 0, definition, params); + String inner = generateFieldSelection(field, root, fr, args, filters) + .orElseThrow(RuntimeException::new); String collect = String.join(", ", args); @@ -51,7 +55,7 @@ private String doGenerateQuery(String request, String field, String type, Set generateFieldSelection(String alias, QueryContext context, + FragmentRenderer fr, Set argumentCollector, List filters) { String type = Schema.unwrap(context.getFieldDefinition().getType()); @@ -77,20 +82,22 @@ public Optional generateFieldSelection(String alias, return Optional.of(alias + args); } - return selectChildren(typeDefinition, context, argumentCollector, filters) + return selectChildren(typeDefinition, context, fr, argumentCollector, filters) .map(children -> alias + args + " " + children); } private Optional selectChildren(TypeDefinition typeDefinition, QueryContext context, + FragmentRenderer fr, Set argumentCollector, List filters) { List selectors = Arrays.asList( + fr, new DefaultFieldSelector(schema, this), new InlineFragmentRenderer(schema, this) ); final List children = selectors.stream() - .flatMap(selector -> selector.selectFields(typeDefinition, context, argumentCollector, filters)) + .flatMap(selector -> selector.selectFields(typeDefinition, context, fr, argumentCollector, filters)) .collect(Collectors.toList()); if (children.isEmpty()) { return Optional.empty(); @@ -109,6 +116,8 @@ public class QueryBuilder { private final List filters = new ArrayList<>(); + private List fragments = new ArrayList<>(); + QueryBuilder(String type) { this.type = type; } @@ -123,12 +132,16 @@ public QueryBuilder select(List selections) { return this; } + public QueryBuilder fragments(List fragments) { + this.fragments = fragments; + return this; + } + public String build(String request, String field, Set params) { this.filters.add(new AllNonNullArgsFieldFilter()); this.filters.add(new FieldDuplicationFilter()); - return doGenerateQuery(request, field, type, params, filters); + return doGenerateQuery(request, field, type, fragments, params, filters); } - } private String generateFieldArgs(FieldDefinition field, Set params, Set argsCollector) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java index fea1586..2dd6bed 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java @@ -11,7 +11,7 @@ public class MaxDepthFieldFilter implements FieldFilter { @Override public boolean shouldAddField(QueryContext context) { - return context.getDepth() <= maxDepth; + return context.getDepth() < maxDepth; } } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetails.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetails.java index 1a1c2ff..91c6b4f 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetails.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetails.java @@ -1,6 +1,7 @@ package com.jacobmountain.graphql.client.visitor; import com.jacobmountain.graphql.client.annotations.GraphQLField; +import com.jacobmountain.graphql.client.annotations.GraphQLFragment; import com.jacobmountain.graphql.client.utils.StringUtils; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterSpec; @@ -45,6 +46,8 @@ public class MethodDetails { @Getter private final int maxDepth; + @Getter + private final List fragments; public boolean hasParameters() { return !parameters.isEmpty(); 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 64b284c..ee929b9 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 @@ -1,10 +1,7 @@ package com.jacobmountain.graphql.client.visitor; import com.jacobmountain.graphql.client.TypeMapper; -import com.jacobmountain.graphql.client.annotations.GraphQLArgument; -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.annotations.*; import com.jacobmountain.graphql.client.exceptions.MissingAnnotationException; import com.jacobmountain.graphql.client.utils.OptionalUtils; import com.jacobmountain.graphql.client.utils.Schema; @@ -19,10 +16,13 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.util.ElementKindVisitor8; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import static java.util.Collections.singletonList; + @Slf4j public class MethodDetailsVisitor extends ElementKindVisitor8 { @@ -53,6 +53,7 @@ private Optional getQueryDetails(ExecutableElement e, TypeMapper .maxDepth(annotation.maxDepth()) .parameters(getParameters(e, typeMapper, annotation.value())) .selection(Arrays.asList(annotation.select())) + .fragments(getFragments(e)) .build()); } @@ -68,6 +69,7 @@ private Optional getMutationDetails(ExecutableElement e, TypeMapp .maxDepth(annotation.maxDepth()) .parameters(getParameters(e, typeMapper, annotation.value())) .selection(Arrays.asList(annotation.select())) + .fragments(getFragments(e)) .build()); } @@ -83,10 +85,24 @@ private Optional getSubscriptionDetails(ExecutableElement e, Type .maxDepth(annotation.maxDepth()) .parameters(getParameters(e, typeMapper, annotation.value())) .selection(Arrays.asList(annotation.select())) + .fragments(getFragments(e)) .build() ); } + private List getFragments(ExecutableElement e) { + final GraphQLFragments fragments = e.getAnnotation(GraphQLFragments.class); + if (fragments == null) { + final GraphQLFragment single = e.getAnnotation(GraphQLFragment.class); + if (single == null) { + return Collections.emptyList(); + } + return singletonList(single); + } else { + return Arrays.asList(fragments.value()); + } + } + private List getParameters(ExecutableElement e, TypeMapper typeMapper, String root) { return e.getParameters() .stream() diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy index 39344d5..69c2352 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy @@ -23,7 +23,7 @@ class MaxDepthFieldFilterSpec extends Specification { where: maxDepth | depth 2 | 1 - 2 | 2 + 3 | 2 5 | 4 } From a4d948be456fe6ec563ed184083ae6a89b1fd472 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 10:45:58 +0000 Subject: [PATCH 03/15] Create a root FieldSelector (DelegatingFieldSelector) --- .../client/query/DefaultFieldSelector.java | 3 +- .../client/query/DelegatingFieldSelector.java | 38 ++++++++++++++++++ .../graphql/client/query/FieldSelector.java | 1 - .../client/query/FragmentRenderer.java | 3 +- .../client/query/InlineFragmentRenderer.java | 3 +- .../graphql/client/query/QueryGenerator.java | 39 +++++-------------- 6 files changed, 50 insertions(+), 37 deletions(-) create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java index 7582fc0..de4e83f 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java @@ -18,12 +18,11 @@ public class DefaultFieldSelector implements FieldSelector { private final QueryGenerator queryGenerator; @Override - public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, FragmentRenderer fragmentRenderer, Set argumentCollector, List filters) { + public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { return schema.getChildren(typeDefinition) .map(definition -> queryGenerator.generateFieldSelection( definition.getName(), context.withType(definition).increment(), - fragmentRenderer, argumentCollector, filters )) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java new file mode 100644 index 0000000..8fee48b --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java @@ -0,0 +1,38 @@ +package com.jacobmountain.graphql.client.query; + +import com.jacobmountain.graphql.client.utils.Schema; +import graphql.language.TypeDefinition; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +public class DelegatingFieldSelector implements FieldSelector { + + private final List selectors; + + public DelegatingFieldSelector(FragmentRenderer fr, Schema schema, QueryGenerator queryGenerator) { + this.selectors = Arrays.asList( + fr, + new DefaultFieldSelector(schema, queryGenerator), + new InlineFragmentRenderer(schema, queryGenerator) + ); + } + + @Override + public Stream selectFields(TypeDefinition typeDefinition, + QueryContext context, + Set argumentCollector, + List filters) { + return selectors.stream() + .flatMap(selector -> selector.selectFields(typeDefinition, context, argumentCollector, filters)) + .reduce((a, b) -> a + " " + b) + .map(children -> "{ " + + children + + " __typename" + + " }") + .map(Stream::of) + .orElseGet(Stream::empty); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java index 4ce3b18..6faad28 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java @@ -10,7 +10,6 @@ public interface FieldSelector { Stream selectFields(TypeDefinition typeDefinition, QueryContext context, - FragmentRenderer fragmentRenderer, Set argumentCollector, List filters); diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java index d41bdab..16f4d9c 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java @@ -32,7 +32,7 @@ public FragmentRenderer(Schema schema, QueryGenerator queryGenerator, List selectFields(TypeDefinition typeDefinition, QueryContext context, FragmentRenderer fragmentRenderer, Set argumentCollector, List filters) { + public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { final String type = Schema.unwrap(context.getFieldDefinition().getType()); final GraphQLFragment graphQLFragment = fragments.get(type); if (graphQLFragment == null || !type.equals(graphQLFragment.type())) { @@ -46,7 +46,6 @@ public Stream selectFields(TypeDefinition typeDefinition, QueryContex .map(definition -> queryGenerator.generateFieldSelection( definition.getName(), context.withType(definition).increment(), - this, argumentCollector, filters )) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java index bc64a13..7b71d81 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java @@ -19,12 +19,11 @@ public class InlineFragmentRenderer implements FieldSelector { private final QueryGenerator queryGenerator; @Override - public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, FragmentRenderer fragmentRenderer, Set argumentCollector, List filters) { + public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { return schema.getTypesImplementing(typeDefinition) .map(interfac -> queryGenerator.generateFieldSelection( interfac, context.withType(new FieldDefinition(interfac, new TypeName(interfac))), - fragmentRenderer, argumentCollector, filters )) 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 17be3fe..26a3a08 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 @@ -23,6 +23,8 @@ public class QueryGenerator { private final Schema schema; + private FragmentRenderer fragmentRenderer; + public QueryGenerator(Schema registry) { this.schema = registry; } @@ -44,9 +46,9 @@ private String doGenerateQuery(String request, String field, String type, List args = new HashSet<>(); - final FragmentRenderer fr = new FragmentRenderer(schema, this, fragments); + fragmentRenderer = new FragmentRenderer(schema, this, fragments); final QueryContext root = new QueryContext(null, 0, definition, params); - String inner = generateFieldSelection(field, root, fr, args, filters) + String inner = generateFieldSelection(field, root, args, filters) .orElseThrow(RuntimeException::new); String collect = String.join(", ", args); @@ -55,7 +57,7 @@ private String doGenerateQuery(String request, String field, String type, List generateFieldSelection(String alias, QueryContext context, - FragmentRenderer fr, Set argumentCollector, List filters) { String type = Schema.unwrap(context.getFieldDefinition().getType()); @@ -82,32 +83,10 @@ public Optional generateFieldSelection(String alias, return Optional.of(alias + args); } - return selectChildren(typeDefinition, context, fr, argumentCollector, filters) - .map(children -> alias + args + " " + children); - } - - private Optional selectChildren(TypeDefinition typeDefinition, - QueryContext context, - FragmentRenderer fr, - Set argumentCollector, - List filters) { - List selectors = Arrays.asList( - fr, - new DefaultFieldSelector(schema, this), - new InlineFragmentRenderer(schema, this) - ); - final List children = selectors.stream() - .flatMap(selector -> selector.selectFields(typeDefinition, context, fr, argumentCollector, filters)) - .collect(Collectors.toList()); - if (children.isEmpty()) { - return Optional.empty(); - } - return Optional.of( - "{ " + - String.join(" ", children) + - " __typename" + - " }" - ); + return new DelegatingFieldSelector(fragmentRenderer, schema, this) + .selectFields(typeDefinition, context, argumentCollector, filters) + .map(children -> alias + args + " " + children) + .findFirst(); } public class QueryBuilder { From 8e3e08404abde994dbc3d18e80a17e9e747e5e4b Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 11:55:01 +0000 Subject: [PATCH 04/15] Use another field selector to reduce duplications --- .../client/query/DefaultFieldSelector.java | 4 ++ .../client/query/FragmentRenderer.java | 67 ++++++++++++------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java index de4e83f..6775cc4 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java @@ -20,6 +20,7 @@ public class DefaultFieldSelector implements FieldSelector { @Override public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { return schema.getChildren(typeDefinition) + .filter(this::filter) .map(definition -> queryGenerator.generateFieldSelection( definition.getName(), context.withType(definition).increment(), @@ -30,5 +31,8 @@ public Stream selectFields(TypeDefinition typeDefinition, QueryContex .map(Optional::get); } + protected boolean filter(FieldDefinition fieldDefinition) { + return true; + } } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java index 16f4d9c..ae4da98 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java @@ -5,7 +5,9 @@ import com.jacobmountain.graphql.client.query.filters.SelectionFieldFilter; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.GraphQLFieldSelection; import graphql.com.google.common.collect.Sets; +import graphql.language.FieldDefinition; import graphql.language.Selection; import graphql.language.TypeDefinition; @@ -20,7 +22,22 @@ public class FragmentRenderer implements FieldSelector { private final QueryGenerator queryGenerator; - private final Map fragments; + private final Map fragments; + + static class Fragment { + + String type; + + String name; + + Set selection; + + public Fragment(GraphQLFragment annotation) { + this.type = annotation.type(); + this.name = StringUtils.camelCase(annotation.type()); + this.selection = Stream.of(annotation.select()).map(GraphQLFieldSelection::new).collect(Collectors.toSet()); + } + } private final Map generated = new HashMap<>(); @@ -28,45 +45,43 @@ public FragmentRenderer(Schema schema, QueryGenerator queryGenerator, List selection; + + public DelegateFieldSelector(Schema schema, QueryGenerator queryGenerator, Set selection) { + super(schema, queryGenerator); + this.selection = selection; + } + + @Override + protected boolean filter(FieldDefinition fieldDefinition) { + return selection.contains(new GraphQLFieldSelection(fieldDefinition.getName())); + } } @Override public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { final String type = Schema.unwrap(context.getFieldDefinition().getType()); - final GraphQLFragment graphQLFragment = fragments.get(type); - if (graphQLFragment == null || !type.equals(graphQLFragment.type())) { + final Fragment fragment = fragments.get(type); + if (fragment == null) { return Stream.empty(); } - final Set select = Stream.of(graphQLFragment.select()) - .map(GraphQLField::value) - .collect(Collectors.toSet()); - final List collect = schema.getChildren(typeDefinition) - .filter(field -> select.contains(field.getName())) - .map(definition -> queryGenerator.generateFieldSelection( - definition.getName(), - context.withType(definition).increment(), - argumentCollector, - filters - )) - .filter(Optional::isPresent) - .map(Optional::get) + final List children = new DelegateFieldSelector(schema, queryGenerator, fragment.selection) + .selectFields(typeDefinition, context, argumentCollector, filters) .collect(Collectors.toList()); - final String name = getFragmentName(graphQLFragment); - generated.computeIfAbsent(graphQLFragment.type(), a -> "fragment " + name + " on " + graphQLFragment.type() + " { " + String.join(" ", collect) + " }"); - if (collect.size() > 0) { - return Stream.of("..." + name); + generated.computeIfAbsent(fragment.type, a -> "fragment " + fragment.name + " on " + fragment.type + " { " + String.join(" ", children) + " }"); + if (children.size() > 0) { + return Stream.of("..." + fragment.name); } return Stream.empty(); } - private String getFragmentName(GraphQLFragment fragment) { - return StringUtils.camelCase(fragment.type()); - } - public String render() { return String.join(" ", generated.values()); } - } From 5b89a4fffdbdf2f3e96cb1338e2527f310097e26 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 12:08:13 +0000 Subject: [PATCH 05/15] Add missing annotation retention/targets --- .../graphql/client/annotations/GraphQLField.java | 6 ++++++ .../graphql/client/annotations/GraphQLFragment.java | 4 +++- .../graphql/client/annotations/GraphQLFragments.java | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLField.java b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLField.java index 91be534..eb41a3b 100644 --- a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLField.java +++ b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLField.java @@ -1,5 +1,11 @@ package com.jacobmountain.graphql.client.annotations; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({}) +@Retention(RetentionPolicy.SOURCE) public @interface GraphQLField { String value(); diff --git a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java index 3abacdf..f447d7d 100644 --- a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java +++ b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java @@ -1,7 +1,9 @@ package com.jacobmountain.graphql.client.annotations; -import java.lang.annotation.Repeatable; +import java.lang.annotation.*; +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) @Repeatable(GraphQLFragments.class) public @interface GraphQLFragment { diff --git a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragments.java b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragments.java index e19c8c8..6ba3062 100644 --- a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragments.java +++ b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragments.java @@ -1,5 +1,9 @@ package com.jacobmountain.graphql.client.annotations; +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) public @interface GraphQLFragments { GraphQLFragment[] value(); From decdc749a5eb02490a13e966ad4c3dca3ae46e02 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 12:12:40 +0000 Subject: [PATCH 06/15] Fix change to max depth filter --- .../graphql/client/query/DelegatingFieldSelector.java | 6 +++--- .../graphql/client/query/filters/MaxDepthFieldFilter.java | 2 +- .../graphql/client/query/QueryGeneratorSpec.groovy | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java index 8fee48b..590857d 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java @@ -12,9 +12,9 @@ public class DelegatingFieldSelector implements FieldSelector { private final List selectors; - public DelegatingFieldSelector(FragmentRenderer fr, Schema schema, QueryGenerator queryGenerator) { + public DelegatingFieldSelector(FragmentRenderer fragmentRenderer, Schema schema, QueryGenerator queryGenerator) { this.selectors = Arrays.asList( - fr, + fragmentRenderer, new DefaultFieldSelector(schema, queryGenerator), new InlineFragmentRenderer(schema, queryGenerator) ); @@ -27,7 +27,7 @@ public Stream selectFields(TypeDefinition typeDefinition, List filters) { return selectors.stream() .flatMap(selector -> selector.selectFields(typeDefinition, context, argumentCollector, filters)) - .reduce((a, b) -> a + " " + b) + .reduce((a, b) -> String.join(" ", a, b)) .map(children -> "{ " + children + " __typename" + diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java index 2dd6bed..fea1586 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java @@ -11,7 +11,7 @@ public class MaxDepthFieldFilter implements FieldFilter { @Override public boolean shouldAddField(QueryContext context) { - return context.getDepth() < maxDepth; + return context.getDepth() <= maxDepth; } } diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/graphql/client/query/QueryGeneratorSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/graphql/client/query/QueryGeneratorSpec.groovy index 924eb9b..19fd297 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/graphql/client/query/QueryGeneratorSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/graphql/client/query/QueryGeneratorSpec.groovy @@ -170,6 +170,11 @@ class QueryGeneratorSpec extends Specification { friends { id name + friends { + id + name + __typename + } __typename } __typename From 579169c0a2f1f43bda6a0393e5fdfd8a7d6e5a2e Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 12:12:40 +0000 Subject: [PATCH 07/15] Fix change to max depth filter --- .../graphql/client/query/DelegatingFieldSelector.java | 6 +++--- .../graphql/client/query/filters/MaxDepthFieldFilter.java | 2 +- .../graphql/client/query/QueryGeneratorSpec.groovy | 5 +++++ .../query/filters/MaxDepthFieldFilterSpec.groovy | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java index 8fee48b..590857d 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java @@ -12,9 +12,9 @@ public class DelegatingFieldSelector implements FieldSelector { private final List selectors; - public DelegatingFieldSelector(FragmentRenderer fr, Schema schema, QueryGenerator queryGenerator) { + public DelegatingFieldSelector(FragmentRenderer fragmentRenderer, Schema schema, QueryGenerator queryGenerator) { this.selectors = Arrays.asList( - fr, + fragmentRenderer, new DefaultFieldSelector(schema, queryGenerator), new InlineFragmentRenderer(schema, queryGenerator) ); @@ -27,7 +27,7 @@ public Stream selectFields(TypeDefinition typeDefinition, List filters) { return selectors.stream() .flatMap(selector -> selector.selectFields(typeDefinition, context, argumentCollector, filters)) - .reduce((a, b) -> a + " " + b) + .reduce((a, b) -> String.join(" ", a, b)) .map(children -> "{ " + children + " __typename" + diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java index 2dd6bed..fea1586 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java @@ -11,7 +11,7 @@ public class MaxDepthFieldFilter implements FieldFilter { @Override public boolean shouldAddField(QueryContext context) { - return context.getDepth() < maxDepth; + return context.getDepth() <= maxDepth; } } diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/graphql/client/query/QueryGeneratorSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/graphql/client/query/QueryGeneratorSpec.groovy index 924eb9b..19fd297 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/graphql/client/query/QueryGeneratorSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/graphql/client/query/QueryGeneratorSpec.groovy @@ -170,6 +170,11 @@ class QueryGeneratorSpec extends Specification { friends { id name + friends { + id + name + __typename + } __typename } __typename diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy index 69c2352..39344d5 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy @@ -23,7 +23,7 @@ class MaxDepthFieldFilterSpec extends Specification { where: maxDepth | depth 2 | 1 - 3 | 2 + 2 | 2 5 | 4 } From ac2e488bb939b420f832b13817a6a95bb5b88d26 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 12:20:00 +0000 Subject: [PATCH 08/15] Clean up getFragments(...) --- .../graphql/client/visitor/MethodDetailsVisitor.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) 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 ee929b9..16120fc 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 @@ -91,16 +91,8 @@ private Optional getSubscriptionDetails(ExecutableElement e, Type } private List getFragments(ExecutableElement e) { - final GraphQLFragments fragments = e.getAnnotation(GraphQLFragments.class); - if (fragments == null) { - final GraphQLFragment single = e.getAnnotation(GraphQLFragment.class); - if (single == null) { - return Collections.emptyList(); - } - return singletonList(single); - } else { - return Arrays.asList(fragments.value()); - } + final GraphQLFragment[] fragments = e.getAnnotationsByType(GraphQLFragment.class); + return Arrays.asList(fragments); } private List getParameters(ExecutableElement e, TypeMapper typeMapper, String root) { From 8d93b518ec4a985e6efef7f54b0455792ece14a3 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 12:31:45 +0000 Subject: [PATCH 09/15] Allow custom fragment names --- .../main/java/com/jacobmountain/StarWarsClient.java | 2 +- .../graphql/client/annotations/GraphQLFragment.java | 2 ++ .../graphql/client/query/FragmentRenderer.java | 13 ++++++------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java index b34b05b..ba3aaab 100644 --- a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java +++ b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java @@ -22,7 +22,7 @@ public interface StarWarsClient { @GraphQLQuery(value = "hero", name = "HeroSummary") com.jacobmountain.dto.Character getHero(@GraphQLArgument("hero") String id); - @GraphQLFragment(type = "Character", select = { + @GraphQLFragment(type = "Character", name = "HeroSummary", select = { @GraphQLField("id"), @GraphQLField("name") }) diff --git a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java index f447d7d..35f3cb7 100644 --- a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java +++ b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java @@ -7,6 +7,8 @@ @Repeatable(GraphQLFragments.class) public @interface GraphQLFragment { + String name() default ""; + String type(); GraphQLField[] select() default {}; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java index ae4da98..acf37c8 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java @@ -1,18 +1,13 @@ package com.jacobmountain.graphql.client.query; -import com.jacobmountain.graphql.client.annotations.GraphQLField; import com.jacobmountain.graphql.client.annotations.GraphQLFragment; -import com.jacobmountain.graphql.client.query.filters.SelectionFieldFilter; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; import com.jacobmountain.graphql.client.visitor.GraphQLFieldSelection; -import graphql.com.google.common.collect.Sets; import graphql.language.FieldDefinition; -import graphql.language.Selection; import graphql.language.TypeDefinition; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -34,8 +29,12 @@ static class Fragment { public Fragment(GraphQLFragment annotation) { this.type = annotation.type(); - this.name = StringUtils.camelCase(annotation.type()); - this.selection = Stream.of(annotation.select()).map(GraphQLFieldSelection::new).collect(Collectors.toSet()); + this.name = Optional.of(annotation.name()) + .filter(StringUtils::hasLength) + .orElseGet(() -> StringUtils.camelCase(annotation.type())); + this.selection = Stream.of(annotation.select()) + .map(GraphQLFieldSelection::new) + .collect(Collectors.toSet()); } } From 74dd5177efa4b6b3b684bb7c456727ba2323a14e Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 23 Jan 2021 16:02:11 +0000 Subject: [PATCH 10/15] Move selectors to their own package --- .../graphql/client/query/FieldFilter.java | 8 -------- .../graphql/client/query/QueryContext.java | 4 ++-- .../graphql/client/query/QueryGenerator.java | 10 +++------- .../query/filters/AllNonNullArgsFieldFilter.java | 1 - .../client/query/filters/FieldDuplicationFilter.java | 1 - .../graphql/client/query/filters/FieldFilter.java | 10 ++++++++++ .../client/query/filters/MaxDepthFieldFilter.java | 1 - .../client/query/filters/SelectionFieldFilter.java | 1 - .../query/{ => selectors}/DefaultFieldSelector.java | 5 ++++- .../query/{ => selectors}/DelegatingFieldSelector.java | 5 ++++- .../client/query/{ => selectors}/FieldSelector.java | 4 +++- .../client/query/{ => selectors}/FragmentRenderer.java | 5 ++++- .../query/{ => selectors}/InlineFragmentRenderer.java | 5 ++++- .../query/filters/MaxDepthFieldFilterSpec.groovy | 2 +- .../query/filters/SelectionFieldFilterSpec.groovy | 2 +- 15 files changed, 36 insertions(+), 28 deletions(-) delete mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldFilter.java create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldFilter.java rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/{ => selectors}/DefaultFieldSelector.java (82%) rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/{ => selectors}/DelegatingFieldSelector.java (84%) rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/{ => selectors}/FieldSelector.java (68%) rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/{ => selectors}/FragmentRenderer.java (92%) rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/{ => selectors}/InlineFragmentRenderer.java (82%) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldFilter.java deleted file mode 100644 index bc8310c..0000000 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldFilter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.jacobmountain.graphql.client.query; - -@FunctionalInterface -public interface FieldFilter { - - boolean shouldAddField(QueryContext context); - -} 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 eb0a280..a6fd9a2 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 @@ -19,11 +19,11 @@ public class QueryContext { Set params; - QueryContext increment() { + public QueryContext increment() { return new QueryContext(this, depth + 1, fieldDefinition, params); } - QueryContext withType(FieldDefinition fieldDefinition) { + public QueryContext withType(FieldDefinition fieldDefinition) { return new QueryContext(parent, depth, fieldDefinition, params); } 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 26a3a08..ea76c8f 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 @@ -2,21 +2,17 @@ import com.jacobmountain.graphql.client.annotations.GraphQLFragment; import com.jacobmountain.graphql.client.exceptions.FieldNotFoundException; -import com.jacobmountain.graphql.client.query.filters.AllNonNullArgsFieldFilter; -import com.jacobmountain.graphql.client.query.filters.FieldDuplicationFilter; -import com.jacobmountain.graphql.client.query.filters.MaxDepthFieldFilter; -import com.jacobmountain.graphql.client.query.filters.SelectionFieldFilter; +import com.jacobmountain.graphql.client.query.filters.*; +import com.jacobmountain.graphql.client.query.selectors.DelegatingFieldSelector; +import com.jacobmountain.graphql.client.query.selectors.FragmentRenderer; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; import com.jacobmountain.graphql.client.visitor.GraphQLFieldSelection; -import graphql.com.google.common.collect.Streams; import graphql.language.*; import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.Opt; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; @Slf4j public class QueryGenerator { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/AllNonNullArgsFieldFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/AllNonNullArgsFieldFilter.java index 6d6d8d9..3cbe6db 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/AllNonNullArgsFieldFilter.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/AllNonNullArgsFieldFilter.java @@ -1,6 +1,5 @@ package com.jacobmountain.graphql.client.query.filters; -import com.jacobmountain.graphql.client.query.FieldFilter; import com.jacobmountain.graphql.client.query.QueryContext; import graphql.language.NonNullType; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java index 4f36086..944afca 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java @@ -1,6 +1,5 @@ package com.jacobmountain.graphql.client.query.filters; -import com.jacobmountain.graphql.client.query.FieldFilter; import com.jacobmountain.graphql.client.query.QueryContext; import com.jacobmountain.graphql.client.utils.Schema; import lombok.extern.slf4j.Slf4j; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldFilter.java new file mode 100644 index 0000000..dd79501 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldFilter.java @@ -0,0 +1,10 @@ +package com.jacobmountain.graphql.client.query.filters; + +import com.jacobmountain.graphql.client.query.QueryContext; + +@FunctionalInterface +public interface FieldFilter { + + boolean shouldAddField(QueryContext context); + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java index fea1586..3e73178 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/MaxDepthFieldFilter.java @@ -1,6 +1,5 @@ package com.jacobmountain.graphql.client.query.filters; -import com.jacobmountain.graphql.client.query.FieldFilter; import com.jacobmountain.graphql.client.query.QueryContext; import lombok.RequiredArgsConstructor; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/SelectionFieldFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/SelectionFieldFilter.java index 6fe89fb..0b9d5ae 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/SelectionFieldFilter.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/SelectionFieldFilter.java @@ -1,6 +1,5 @@ package com.jacobmountain.graphql.client.query.filters; -import com.jacobmountain.graphql.client.query.FieldFilter; import com.jacobmountain.graphql.client.query.QueryContext; import com.jacobmountain.graphql.client.visitor.GraphQLFieldSelection; import lombok.RequiredArgsConstructor; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DefaultFieldSelector.java similarity index 82% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DefaultFieldSelector.java index 6775cc4..82d4346 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DefaultFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DefaultFieldSelector.java @@ -1,5 +1,8 @@ -package com.jacobmountain.graphql.client.query; +package com.jacobmountain.graphql.client.query.selectors; +import com.jacobmountain.graphql.client.query.filters.FieldFilter; +import com.jacobmountain.graphql.client.query.QueryContext; +import com.jacobmountain.graphql.client.query.QueryGenerator; import com.jacobmountain.graphql.client.utils.Schema; import graphql.language.FieldDefinition; import graphql.language.TypeDefinition; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java similarity index 84% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java index 590857d..b38a6bf 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/DelegatingFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java @@ -1,5 +1,8 @@ -package com.jacobmountain.graphql.client.query; +package com.jacobmountain.graphql.client.query.selectors; +import com.jacobmountain.graphql.client.query.filters.FieldFilter; +import com.jacobmountain.graphql.client.query.QueryContext; +import com.jacobmountain.graphql.client.query.QueryGenerator; import com.jacobmountain.graphql.client.utils.Schema; import graphql.language.TypeDefinition; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FieldSelector.java similarity index 68% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FieldSelector.java index 6faad28..2206138 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FieldSelector.java @@ -1,5 +1,7 @@ -package com.jacobmountain.graphql.client.query; +package com.jacobmountain.graphql.client.query.selectors; +import com.jacobmountain.graphql.client.query.filters.FieldFilter; +import com.jacobmountain.graphql.client.query.QueryContext; import graphql.language.TypeDefinition; import java.util.List; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java similarity index 92% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java index acf37c8..ef3e155 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/FragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java @@ -1,6 +1,9 @@ -package com.jacobmountain.graphql.client.query; +package com.jacobmountain.graphql.client.query.selectors; import com.jacobmountain.graphql.client.annotations.GraphQLFragment; +import com.jacobmountain.graphql.client.query.filters.FieldFilter; +import com.jacobmountain.graphql.client.query.QueryContext; +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.GraphQLFieldSelection; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/InlineFragmentRenderer.java similarity index 82% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/InlineFragmentRenderer.java index 7b71d81..5440d41 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/InlineFragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/InlineFragmentRenderer.java @@ -1,5 +1,8 @@ -package com.jacobmountain.graphql.client.query; +package com.jacobmountain.graphql.client.query.selectors; +import com.jacobmountain.graphql.client.query.filters.FieldFilter; +import com.jacobmountain.graphql.client.query.QueryContext; +import com.jacobmountain.graphql.client.query.QueryGenerator; import com.jacobmountain.graphql.client.utils.Schema; import graphql.language.FieldDefinition; import graphql.language.TypeDefinition; diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy index 39344d5..9c00097 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/MaxDepthFieldFilterSpec.groovy @@ -1,6 +1,6 @@ package com.jacobmountain.query.filters -import com.jacobmountain.graphql.client.query.FieldFilter +import com.jacobmountain.graphql.client.query.filters.FieldFilter import com.jacobmountain.graphql.client.query.QueryContext import com.jacobmountain.graphql.client.query.filters.MaxDepthFieldFilter import spock.lang.Specification diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/SelectionFieldFilterSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/SelectionFieldFilterSpec.groovy index e0ae282..f9b94e6 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/SelectionFieldFilterSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/filters/SelectionFieldFilterSpec.groovy @@ -1,6 +1,6 @@ package com.jacobmountain.query.filters -import com.jacobmountain.graphql.client.query.FieldFilter +import com.jacobmountain.graphql.client.query.filters.FieldFilter import com.jacobmountain.graphql.client.query.QueryContext import com.jacobmountain.graphql.client.query.filters.SelectionFieldFilter import com.jacobmountain.graphql.client.utils.Schema From a7a8c992286906a6241872ef7bcb46d95d309980 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Mon, 25 Jan 2021 19:25:39 +0000 Subject: [PATCH 11/15] Allow fragments to be defined on the interface --- .../java/com/jacobmountain/StarWarsClient.java | 8 ++++---- .../client/annotations/GraphQLFragment.java | 2 +- .../graphql/client/ClientGenerator.java | 10 +++++----- .../graphql/client/modules/AbstractQueryStage.java | 9 ++++++--- .../graphql/client/modules/AbstractStage.java | 2 +- .../client/modules/ArgumentAssemblyStage.java | 8 ++++---- .../graphql/client/modules/BlockingQueryStage.java | 6 +++--- .../graphql/client/modules/ClientDetails.java | 7 +++++++ .../client/modules/OptionalReturnStage.java | 8 ++++---- .../graphql/client/modules/ReactiveQueryStage.java | 8 ++++---- .../client/modules/ReactiveReturnStage.java | 14 +++++++------- .../query/selectors/DefaultFieldSelector.java | 10 +++++----- .../graphql/client/utils/OptionalUtils.java | 6 ++++++ .../client/visitor/ClientDetailsVisitor.java | 14 ++++++++++++-- 14 files changed, 69 insertions(+), 43 deletions(-) diff --git a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java index ba3aaab..3c6cccf 100644 --- a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java +++ b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java @@ -10,6 +10,10 @@ import java.util.List; import java.util.Optional; +@GraphQLFragment(type = "Character", name = "HeroSummary", select = { + @GraphQLField("id"), + @GraphQLField("name") +}) @GraphQLClient( schema = "Schema.gql", nullChecking = true @@ -22,10 +26,6 @@ public interface StarWarsClient { @GraphQLQuery(value = "hero", name = "HeroSummary") com.jacobmountain.dto.Character getHero(@GraphQLArgument("hero") String id); - @GraphQLFragment(type = "Character", name = "HeroSummary", select = { - @GraphQLField("id"), - @GraphQLField("name") - }) @GraphQLQuery(value = "hero", select = { @GraphQLField("id"), @GraphQLField("name") diff --git a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java index 35f3cb7..301defb 100644 --- a/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java +++ b/graphql-java-client-annotations/src/main/java/com/jacobmountain/graphql/client/annotations/GraphQLFragment.java @@ -2,7 +2,7 @@ import java.lang.annotation.*; -@Target(ElementType.METHOD) +@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.SOURCE) @Repeatable(GraphQLFragments.class) public @interface GraphQLFragment { 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 index 9b32e42..1be9e70 100644 --- 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 @@ -91,7 +91,7 @@ public void generate(Element element, String suffix) { // for each method on the interface, generate its implementation element.getEnclosedElements() .stream() - .map(this::generateImpl) + .map(el -> generateImpl(el, details)) .forEach(builder::addMethod); writeToFile(builder.build()); @@ -115,7 +115,7 @@ private MethodSpec generateConstructor(List variab * @param method the method of the @GraphQLClient annotated interface * @return a method spec to add to the implementation */ - private MethodSpec generateImpl(Element method) { + private MethodSpec generateImpl(Element method, ClientDetails client) { log.info(""); MethodDetails details = method.accept(new MethodDetailsVisitor(schema), typeMapper); log.info("{}", details); @@ -125,9 +125,9 @@ private MethodSpec generateImpl(Element method) { .addModifiers(Modifier.PUBLIC) .addParameters(details.getParameterSpec()); - this.arguments.assemble(details).forEach(builder::addStatement); - this.query.assemble(details).forEach(builder::addStatement); - this.returnResults.assemble(details).forEach(builder::addStatement); + this.arguments.assemble(client, details).forEach(builder::addStatement); + this.query.assemble(client, details).forEach(builder::addStatement); + this.returnResults.assemble(client, details).forEach(builder::addStatement); return builder.build(); } 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/modules/AbstractQueryStage.java index b3676b6..185eb1a 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/modules/AbstractQueryStage.java @@ -1,6 +1,7 @@ package com.jacobmountain.graphql.client.modules; import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.annotations.GraphQLFragment; import com.jacobmountain.graphql.client.dto.Response; import com.jacobmountain.graphql.client.query.QueryGenerator; import com.jacobmountain.graphql.client.utils.Schema; @@ -10,6 +11,7 @@ import com.squareup.javapoet.*; import graphql.language.ObjectTypeDefinition; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; @@ -53,12 +55,11 @@ protected String getMethod(MethodDetails details) { return method; } - protected CodeBlock generateQueryCode(String request, MethodDetails details) { + protected CodeBlock generateQueryCode(String request, ClientDetails client, MethodDetails details) { Set params = details.getParameters() .stream() .map(Parameter::getField) .collect(Collectors.toSet()); - queryGenerator.query(); QueryGenerator.QueryBuilder builder; if (details.isQuery()) { builder = queryGenerator.query(); @@ -69,6 +70,8 @@ protected CodeBlock generateQueryCode(String request, MethodDetails details) { } else { throw new RuntimeException(""); } + List fragments = new ArrayList<>(client.getFragments()); + fragments.addAll(details.getFragments()); String query = builder .select( details.getSelection() @@ -77,7 +80,7 @@ protected CodeBlock generateQueryCode(String request, MethodDetails details) { .collect(Collectors.toList()) ) .maxDepth(details.getMaxDepth()) - .fragments(details.getFragments()) + .fragments(fragments) .build(request, details.getField(), params); return CodeBlock.of( "(\"$L\", $L)", query, details.hasParameters() ? "args" : "null" 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/modules/AbstractStage.java index ce2c607..c9ab140 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/modules/AbstractStage.java @@ -28,7 +28,7 @@ public List getTypeArguments() { return Collections.emptyList(); } - public List assemble(MethodDetails details) { + public List assemble(ClientDetails client, MethodDetails method) { return Collections.emptyList(); } 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/modules/ArgumentAssemblyStage.java index d59c22d..7122ca2 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/modules/ArgumentAssemblyStage.java @@ -23,15 +23,15 @@ public ArgumentAssemblyStage(String dtoPackageName) { } @Override - public List assemble(MethodDetails details) { - List parameters = details.getParameters(); + public List assemble(ClientDetails client, MethodDetails method) { + List parameters = method.getParameters(); if (parameters.isEmpty()) { return Collections.emptyList(); } List ret = new ArrayList<>(); - TypeName type = ClassName.get(dtoPackageName, details.getArgumentClassname()); + TypeName type = ClassName.get(dtoPackageName, method.getArgumentClassname()); ret.add(CodeBlock.of("$T args = new $T()", type, type)); - details.getParameters() + method.getParameters() .stream() .map(this::setArgumentField) .forEach(ret::add); 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/modules/BlockingQueryStage.java index 1532c61..e696991 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/modules/BlockingQueryStage.java @@ -49,12 +49,12 @@ public List getTypeArguments() { } @Override - public List assemble(MethodDetails details) { - ObjectTypeDefinition query = getTypeDefinition(details); + public List assemble(ClientDetails client, MethodDetails method) { + ObjectTypeDefinition query = getTypeDefinition(method); return Collections.singletonList( CodeBlock.builder() .add("$T thing = ", ParameterizedTypeName.get(ClassName.get(Response.class), typeMapper.getType(query.getName()), TypeVariableName.get("Error"))) - .add("fetcher.$L", getMethod(details)).add(generateQueryCode(details.getRequestName(), details)) + .add("fetcher.$L", getMethod(method)).add(generateQueryCode(method.getRequestName(), client, method)) .build() ); } 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 index 6872440..2d76024 100644 --- 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 @@ -1,6 +1,10 @@ package com.jacobmountain.graphql.client.modules; +import com.jacobmountain.graphql.client.annotations.GraphQLFragment; import lombok.Builder; +import lombok.Getter; + +import java.util.List; @Builder public class ClientDetails { @@ -9,6 +13,9 @@ public class ClientDetails { private final boolean requiresFetcher; + @Getter + private final List fragments; + public boolean requiresSubscriber() { return requiresFetcher; } 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/modules/OptionalReturnStage.java index 7be6c9b..469c890 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/modules/OptionalReturnStage.java @@ -19,17 +19,17 @@ public OptionalReturnStage(Schema schema, TypeMapper typeMapper) { } @Override - public List assemble(MethodDetails details) { - ObjectTypeDefinition typeDefinition = getTypeDefinition(details); + public List assemble(ClientDetails client, MethodDetails method) { + ObjectTypeDefinition typeDefinition = getTypeDefinition(method); List ret = new ArrayList<>( Arrays.asList( CodeBlock.of("return $T.ofNullable(thing)", Optional.class), CodeBlock.of("map($T::getData)", ClassName.get(Response.class)), - CodeBlock.of("map($T::$L)", typeMapper.getType(typeDefinition.getName()), StringUtils.camelCase("get", details.getField())) + CodeBlock.of("map($T::$L)", typeMapper.getType(typeDefinition.getName()), StringUtils.camelCase("get", method.getField())) ) ); - if (!returnsOptional(details)) { + if (!returnsOptional(method)) { ret.add(CodeBlock.of("orElse(null)")); } return Collections.singletonList(CodeBlock.join(ret, "\n\t.")); 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 index 6dcbfe8..fdf9a7b 100644 --- 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 @@ -58,12 +58,12 @@ public List getMemberVariables(ClientDetails details) { } @Override - public List assemble(MethodDetails details) { - String member = details.isSubscription() ? "subscriber" : "fetcher"; + 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(details))) - .add("$L.$L", member, getMethod(details)).add(generateQueryCode(details.getRequestName(), details)) + .add("$T thing = ", ParameterizedTypeName.get(ClassName.get(Publisher.class), getReturnTypeName(method))) + .add("$L.$L", member, getMethod(method)).add(generateQueryCode(method.getRequestName(), client, 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/modules/ReactiveReturnStage.java index fb20c65..0050ae6 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/modules/ReactiveReturnStage.java @@ -22,21 +22,21 @@ public ReactiveReturnStage(Schema schema, TypeMapper typeMapper) { } @Override - public List assemble(MethodDetails details) { - ObjectTypeDefinition typeDefinition = getTypeDefinition(details); + public List assemble(ClientDetails client, MethodDetails method) { + ObjectTypeDefinition typeDefinition = getTypeDefinition(method); List ret = new ArrayList<>( Arrays.asList( - CodeBlock.of("return $T.from(thing)", details.isSubscription() ? Flux.class : Mono.class), + CodeBlock.of("return $T.from(thing)", method.isSubscription() ? Flux.class : Mono.class), CodeBlock.of("map($T::getData)", ClassName.get(Response.class)), - CodeBlock.of("map($T::$L)", typeMapper.getType(typeDefinition.getName()), StringUtils.camelCase("get", details.getField())) + CodeBlock.of("map($T::$L)", typeMapper.getType(typeDefinition.getName()), StringUtils.camelCase("get", method.getField())) ) ); - if (!returnsPublisher(details)) { + if (!returnsPublisher(method)) { ret.add(CodeBlock.of("blockOptional()")); - if (!returnsOptional(details)) { + if (!returnsOptional(method)) { ret.add(CodeBlock.of("orElse(null)")); } - } else if (returnsClass(details, Flux.class) && !details.isSubscription()) { + } else if (returnsClass(method, Flux.class) && !method.isSubscription()) { ret.add(CodeBlock.of("flatMapIterable($T.identity())", Function.class)); } return Collections.singletonList(CodeBlock.join(ret, "\n\t.")); diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DefaultFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DefaultFieldSelector.java index 82d4346..18ad5e0 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DefaultFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DefaultFieldSelector.java @@ -3,6 +3,7 @@ import com.jacobmountain.graphql.client.query.filters.FieldFilter; import com.jacobmountain.graphql.client.query.QueryContext; import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.utils.OptionalUtils; import com.jacobmountain.graphql.client.utils.Schema; import graphql.language.FieldDefinition; import graphql.language.TypeDefinition; @@ -24,14 +25,13 @@ public class DefaultFieldSelector implements FieldSelector { public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { return schema.getChildren(typeDefinition) .filter(this::filter) - .map(definition -> queryGenerator.generateFieldSelection( - definition.getName(), - context.withType(definition).increment(), + .map(child -> queryGenerator.generateFieldSelection( + child.getName(), + context.withType(child).increment(), argumentCollector, filters )) - .filter(Optional::isPresent) - .map(Optional::get); + .flatMap(OptionalUtils::toStream); } protected boolean filter(FieldDefinition fieldDefinition) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/utils/OptionalUtils.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/utils/OptionalUtils.java index e39ed5e..ceb14db 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/utils/OptionalUtils.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/utils/OptionalUtils.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Stream; @UtilityClass public class OptionalUtils { @@ -21,4 +22,9 @@ public Optional first(Optional first, Supplier>... later) return first(head, Arrays.copyOfRange(later, 1, later.length)); } + public Stream toStream(Optional optional) { + return optional.map(Stream::of) + .orElseGet(Stream::empty); + } + } 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 d3f6a0a..baf0727 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 @@ -1,11 +1,16 @@ package com.jacobmountain.graphql.client.visitor; +import com.jacobmountain.graphql.client.annotations.GraphQLFragment; +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 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.Arrays; public class ClientDetailsVisitor extends ElementKindVisitor8 { @@ -14,19 +19,24 @@ public ClientDetails visitType(TypeElement type, Void unused) { return ClientDetails.builder() .requiresSubscriber(requiresSubscriber(type)) .requiresFetcher(requiresFetcher(type)) + .fragments(Arrays.asList(type.getAnnotationsByType(GraphQLFragment.class))) .build(); } private boolean requiresSubscriber(TypeElement element) { return element.getEnclosedElements() .stream() - .anyMatch(it -> it.getAnnotation(GraphQLSubscription.class) != null); + .anyMatch(it -> hasAnnotation(it, GraphQLSubscription.class)); } private boolean requiresFetcher(TypeElement element) { return element.getEnclosedElements() .stream() - .anyMatch(it -> it.getAnnotation(GraphQLQuery.class) != null); + .anyMatch(it -> hasAnnotation(it, GraphQLQuery.class) || hasAnnotation(it, GraphQLMutation.class)); + } + + private boolean hasAnnotation(Element el, Class annotation) { + return el.getAnnotation(annotation) != null; } } From ffbe40061e0929f611d253c640058f21b2fba7bb Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Mon, 25 Jan 2021 19:38:39 +0000 Subject: [PATCH 12/15] Refactor to use DelegatingFieldSelector --- .../com/jacobmountain/StarWarsClient.java | 9 ++++---- .../selectors/DelegatingFieldSelector.java | 11 ++++++++- .../query/selectors/FragmentRenderer.java | 23 ++++--------------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java index 3c6cccf..f112dae 100644 --- a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java +++ b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java @@ -10,10 +10,7 @@ import java.util.List; import java.util.Optional; -@GraphQLFragment(type = "Character", name = "HeroSummary", select = { - @GraphQLField("id"), - @GraphQLField("name") -}) + @GraphQLClient( schema = "Schema.gql", nullChecking = true @@ -26,6 +23,10 @@ public interface StarWarsClient { @GraphQLQuery(value = "hero", name = "HeroSummary") com.jacobmountain.dto.Character getHero(@GraphQLArgument("hero") String id); + @GraphQLFragment(type = "Character", name = "HeroSummary", select = { + @GraphQLField("id"), + @GraphQLField("name") + }) @GraphQLQuery(value = "hero", select = { @GraphQLField("id"), @GraphQLField("name") 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 b38a6bf..47cb4db 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 @@ -1,8 +1,8 @@ package com.jacobmountain.graphql.client.query.selectors; -import com.jacobmountain.graphql.client.query.filters.FieldFilter; import com.jacobmountain.graphql.client.query.QueryContext; import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.query.filters.FieldFilter; import com.jacobmountain.graphql.client.utils.Schema; import graphql.language.TypeDefinition; @@ -23,6 +23,15 @@ public DelegatingFieldSelector(FragmentRenderer fragmentRenderer, Schema schema, ); } + public DelegatingFieldSelector(Schema schema, QueryGenerator queryGenerator) { + this.selectors = Arrays.asList( + new DefaultFieldSelector(schema, queryGenerator), + new InlineFragmentRenderer(schema, queryGenerator) + ); + } + + + @Override public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java index ef3e155..b80fdb3 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java @@ -50,21 +50,6 @@ public FragmentRenderer(Schema schema, QueryGenerator queryGenerator, List selection; - - public DelegateFieldSelector(Schema schema, QueryGenerator queryGenerator, Set selection) { - super(schema, queryGenerator); - this.selection = selection; - } - - @Override - protected boolean filter(FieldDefinition fieldDefinition) { - return selection.contains(new GraphQLFieldSelection(fieldDefinition.getName())); - } - } - @Override public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { final String type = Schema.unwrap(context.getFieldDefinition().getType()); @@ -72,11 +57,11 @@ public Stream selectFields(TypeDefinition typeDefinition, QueryContex if (fragment == null) { return Stream.empty(); } - final List children = new DelegateFieldSelector(schema, queryGenerator, fragment.selection) + final Optional children = new DelegatingFieldSelector(schema, queryGenerator) .selectFields(typeDefinition, context, argumentCollector, filters) - .collect(Collectors.toList()); - generated.computeIfAbsent(fragment.type, a -> "fragment " + fragment.name + " on " + fragment.type + " { " + String.join(" ", children) + " }"); - if (children.size() > 0) { + .findFirst(); + generated.computeIfAbsent(fragment.type, a -> "fragment " + fragment.name + " on " + fragment.type + " " + children.orElse("")); + if (children.isPresent()) { return Stream.of("..." + fragment.name); } return Stream.empty(); From 0b29bec1ed9dcbed5168383b6b3d7084b2c9b23d Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Mon, 25 Jan 2021 19:51:12 +0000 Subject: [PATCH 13/15] Don't add to the list of fragments if we don't select any fields --- .../graphql/client/query/selectors/FragmentRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java index b80fdb3..0f76fea 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java @@ -60,8 +60,8 @@ public Stream selectFields(TypeDefinition typeDefinition, QueryContex final Optional children = new DelegatingFieldSelector(schema, queryGenerator) .selectFields(typeDefinition, context, argumentCollector, filters) .findFirst(); - generated.computeIfAbsent(fragment.type, a -> "fragment " + fragment.name + " on " + fragment.type + " " + children.orElse("")); if (children.isPresent()) { + generated.computeIfAbsent(fragment.type, a -> "fragment " + fragment.name + " on " + fragment.type + " " + children.get()); return Stream.of("..." + fragment.name); } return Stream.empty(); From fc2ccf5c1be1da2075c41e110db04bafdda04f12 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Mon, 1 Feb 2021 08:25:40 +0000 Subject: [PATCH 14/15] Don't pass around annotation --- .../com/jacobmountain/StarWarsClient.java | 8 +- .../client/modules/AbstractQueryStage.java | 17 +++- .../graphql/client/query/QueryContext.java | 7 +- .../graphql/client/query/QueryGenerator.java | 10 +-- .../query/filters/FieldDuplicationFilter.java | 2 +- .../client/query/selectors/Fragment.java | 33 +++++++ .../query/selectors/FragmentRenderer.java | 43 ++++----- .../query/QueryGeneratorSpec.groovy | 89 +++++++++++++++++++ 8 files changed, 165 insertions(+), 44 deletions(-) create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/Fragment.java create mode 100644 graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy diff --git a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java index f112dae..85bd259 100644 --- a/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java +++ b/example/example-client/src/main/java/com/jacobmountain/StarWarsClient.java @@ -10,23 +10,19 @@ import java.util.List; import java.util.Optional; - @GraphQLClient( schema = "Schema.gql", nullChecking = true ) public interface StarWarsClient { + @GraphQLFragment(type = "Character", name = "Hero") @GraphQLQuery(value = "hero", name = "HeroByEpisode") com.jacobmountain.dto.Character getHero(Episode episode, int first, String after, LengthUnit unit); - @GraphQLQuery(value = "hero", name = "HeroSummary") + @GraphQLQuery(value = "hero", name = "HeroSummary", maxDepth = 6) com.jacobmountain.dto.Character getHero(@GraphQLArgument("hero") String id); - @GraphQLFragment(type = "Character", name = "HeroSummary", select = { - @GraphQLField("id"), - @GraphQLField("name") - }) @GraphQLQuery(value = "hero", select = { @GraphQLField("id"), @GraphQLField("name") 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/modules/AbstractQueryStage.java index 185eb1a..d8d11b7 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/modules/AbstractQueryStage.java @@ -4,6 +4,7 @@ import com.jacobmountain.graphql.client.annotations.GraphQLFragment; import com.jacobmountain.graphql.client.dto.Response; import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.query.selectors.Fragment; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.visitor.GraphQLFieldSelection; import com.jacobmountain.graphql.client.visitor.MethodDetails; @@ -70,8 +71,7 @@ protected CodeBlock generateQueryCode(String request, ClientDetails client, Meth } else { throw new RuntimeException(""); } - List fragments = new ArrayList<>(client.getFragments()); - fragments.addAll(details.getFragments()); + String query = builder .select( details.getSelection() @@ -80,11 +80,22 @@ protected CodeBlock generateQueryCode(String request, ClientDetails client, Meth .collect(Collectors.toList()) ) .maxDepth(details.getMaxDepth()) - .fragments(fragments) + .fragments(getFragments(client, details)) .build(request, details.getField(), params); return CodeBlock.of( "(\"$L\", $L)", query, details.hasParameters() ? "args" : "null" ); } + private List getFragments(ClientDetails client, MethodDetails details) { + List fragments = new ArrayList<>(); + for (GraphQLFragment f : client.getFragments()) { + fragments.add(new Fragment(f)); + } + for (GraphQLFragment f : details.getFragments()) { + fragments.add(new Fragment(f)); + } + return fragments; + } + } \ No newline at end of file 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 a6fd9a2..e10c698 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 @@ -1,6 +1,7 @@ package com.jacobmountain.graphql.client.query; import graphql.language.FieldDefinition; +import graphql.language.Type; import lombok.AllArgsConstructor; import lombok.Value; @@ -13,6 +14,8 @@ public class QueryContext { QueryContext parent; + Type type; + int depth; FieldDefinition fieldDefinition; @@ -20,11 +23,11 @@ public class QueryContext { Set params; public QueryContext increment() { - return new QueryContext(this, depth + 1, fieldDefinition, params); + return new QueryContext(this, type, depth + 1, fieldDefinition, params); } public QueryContext withType(FieldDefinition fieldDefinition) { - return new QueryContext(parent, depth, fieldDefinition, params); + return new QueryContext(parent, this.fieldDefinition.getType(), depth, fieldDefinition, params); } } 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 ea76c8f..5e84b88 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 @@ -1,9 +1,9 @@ package com.jacobmountain.graphql.client.query; -import com.jacobmountain.graphql.client.annotations.GraphQLFragment; import com.jacobmountain.graphql.client.exceptions.FieldNotFoundException; import com.jacobmountain.graphql.client.query.filters.*; import com.jacobmountain.graphql.client.query.selectors.DelegatingFieldSelector; +import com.jacobmountain.graphql.client.query.selectors.Fragment; import com.jacobmountain.graphql.client.query.selectors.FragmentRenderer; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; @@ -37,13 +37,13 @@ public QueryBuilder subscription() { return new QueryBuilder("subscription"); } - private String doGenerateQuery(String request, String field, String type, List fragments, Set params, List filters) { + private String doGenerateQuery(String request, String field, String type, List fragments, Set params, List filters) { FieldDefinition definition = schema.findField(field).orElseThrow(FieldNotFoundException.create(field)); Set args = new HashSet<>(); fragmentRenderer = new FragmentRenderer(schema, this, fragments); - final QueryContext root = new QueryContext(null, 0, definition, params); + final QueryContext root = new QueryContext(null, definition.getType(), 0, definition, params); String inner = generateFieldSelection(field, root, args, filters) .orElseThrow(RuntimeException::new); @@ -91,7 +91,7 @@ public class QueryBuilder { private final List filters = new ArrayList<>(); - private List fragments = new ArrayList<>(); + private List fragments = new ArrayList<>(); QueryBuilder(String type) { this.type = type; @@ -107,7 +107,7 @@ public QueryBuilder select(List selections) { return this; } - public QueryBuilder fragments(List fragments) { + public QueryBuilder fragments(List fragments) { this.fragments = fragments; return this; } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java index 944afca..21b12eb 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/filters/FieldDuplicationFilter.java @@ -25,7 +25,7 @@ private String generatePath(QueryContext context) { path.add(0, parent.getFieldDefinition().getName()); parent = parent.getParent(); } - path.set(path.size() - 1, Schema.unwrap(context.getFieldDefinition().getType())); + path.set(path.size() - 1, Schema.unwrap(context.getType())); return String.join(".", path); } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/Fragment.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/Fragment.java new file mode 100644 index 0000000..930b1cf --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/Fragment.java @@ -0,0 +1,33 @@ +package com.jacobmountain.graphql.client.query.selectors; + +import com.jacobmountain.graphql.client.annotations.GraphQLFragment; +import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.GraphQLFieldSelection; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Data +@AllArgsConstructor +public class Fragment { + + private String type; + + private String name; + + private Set selection; + + public Fragment(GraphQLFragment annotation) { + this.type = annotation.type(); + this.name = Optional.of(annotation.name()) + .filter(StringUtils::hasLength) + .orElseGet(() -> StringUtils.camelCase(annotation.type())); + this.selection = Stream.of(annotation.select()) + .map(GraphQLFieldSelection::new) + .collect(Collectors.toSet()); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java index 0f76fea..27abdc7 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java @@ -5,15 +5,15 @@ import com.jacobmountain.graphql.client.query.QueryContext; 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.GraphQLFieldSelection; -import graphql.language.FieldDefinition; import graphql.language.TypeDefinition; +import lombok.extern.slf4j.Slf4j; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +@Slf4j public class FragmentRenderer implements FieldSelector { private final Schema schema; @@ -22,47 +22,36 @@ public class FragmentRenderer implements FieldSelector { private final Map fragments; - static class Fragment { - - String type; - - String name; - - Set selection; - - public Fragment(GraphQLFragment annotation) { - this.type = annotation.type(); - this.name = Optional.of(annotation.name()) - .filter(StringUtils::hasLength) - .orElseGet(() -> StringUtils.camelCase(annotation.type())); - this.selection = Stream.of(annotation.select()) - .map(GraphQLFieldSelection::new) - .collect(Collectors.toSet()); - } - } - private final Map generated = new HashMap<>(); - public FragmentRenderer(Schema schema, QueryGenerator queryGenerator, List fragments) { + public FragmentRenderer(Schema schema, QueryGenerator queryGenerator, List fragments) { this.schema = schema; this.queryGenerator = queryGenerator; this.fragments = fragments.stream() - .collect(Collectors.toMap(GraphQLFragment::type, Fragment::new)); + .collect(Collectors.toMap(Fragment::getType, Function.identity(), FragmentRenderer::duplicateFragments)); + } + + private static Fragment duplicateFragments(Fragment fragment, Fragment fragment2) { + if (!fragment.equals(fragment2)) { + log.warn("Duplicate fragments defined {} {}", fragment.getName(), fragment2.getName()); + } + return fragment; } @Override public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, Set argumentCollector, List filters) { + log.info("\nGenerating fragment"); final String type = Schema.unwrap(context.getFieldDefinition().getType()); final Fragment fragment = fragments.get(type); if (fragment == null) { return Stream.empty(); } final Optional children = new DelegatingFieldSelector(schema, queryGenerator) - .selectFields(typeDefinition, context, argumentCollector, filters) + .selectFields(schema.getTypeDefinition(fragment.getType()).orElseThrow(RuntimeException::new), context, argumentCollector, filters) .findFirst(); if (children.isPresent()) { - generated.computeIfAbsent(fragment.type, a -> "fragment " + fragment.name + " on " + fragment.type + " " + children.get()); - return Stream.of("..." + fragment.name); + generated.computeIfAbsent(fragment.getType(), a -> "fragment " + fragment.getName() + " on " + fragment.getName() + " " + children.get()); + return Stream.of("..." + fragment.getName()); } return Stream.empty(); } 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 new file mode 100644 index 0000000..b088d42 --- /dev/null +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy @@ -0,0 +1,89 @@ +package com.jacobmountain.query + +import com.jacobmountain.graphql.client.query.QueryGenerator +import com.jacobmountain.graphql.client.query.selectors.Fragment +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() + .fragments([new Fragment("Hero", "HeroSummary", [] as Set)]) + .build(null, "hero", [] as Set) + + then: + query == "query Hero { hero { ...HeroSummary __typename } } fragment HeroSummary on HeroSummary { 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() + .fragments([new Fragment("Hero", "HeroSummary", [] as Set)]) + .build(null, "hero", [] as Set) + + then: + query == "query Hero { hero { ...HeroSummary __typename } } fragment HeroSummary on HeroSummary { 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() + .fragments([new Fragment("Hero", "HeroSummary", [] as Set)]) + .maxDepth(3) + .build(null, "hero", [] as Set) + + then: + query == "query Hero { hero { ...HeroSummary __typename } } fragment HeroSummary on HeroSummary { id name friends { id name } __typename }" + } + +} From 27111ecb5d9206833bb471a43bc3f4b44f2420b2 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sun, 28 Feb 2021 09:29:26 +0000 Subject: [PATCH 15/15] Remove uses of FragmentRenderer --- .../client/GraphQLClientProcessor.java | 15 ++++++--------- .../graphql/client/query/QueryGenerator.java | 13 +++++++------ .../selectors/DelegatingFieldSelector.java | 19 ++----------------- .../query/selectors/FragmentRenderer.java | 8 +++++--- 4 files changed, 20 insertions(+), 35 deletions(-) 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 7076142..90d8899 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 @@ -23,7 +23,6 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.Set; -import java.util.function.Consumer; @Slf4j @AutoService(Processor.class) @@ -57,18 +56,16 @@ public boolean process(Set annotations, RoundEnvironment return elements.stream() .map(el -> (TypeElement) el) .map(Input::new) - .peek(this.generateJavaDataClasses()) + .peek(this::generateJavaDataClasses) .peek(this::generateClientImplementation) .count() > 0; } - private Consumer generateJavaDataClasses() { - return input -> { - log.info("Generating java classes from GraphQL schema"); - DTOGenerator dtoGenerator = new DTOGenerator(input.getDtoPackage(), new FileWriter(this.filer), input.getTypeMapper()); - dtoGenerator.generate(input.getSchema().types().values()); - dtoGenerator.generateArgumentDTOs(input.element); - }; + private void generateJavaDataClasses(Input input) { + log.info("Generating java classes from GraphQL schema"); + DTOGenerator dtoGenerator = new DTOGenerator(input.getDtoPackage(), new FileWriter(this.filer), input.getTypeMapper()); + dtoGenerator.generate(input.getSchema().types().values()); + dtoGenerator.generateArgumentDTOs(input.element); } private void generateClientImplementation(Input client) { 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 5e84b88..706bb01 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 @@ -2,9 +2,10 @@ import com.jacobmountain.graphql.client.exceptions.FieldNotFoundException; 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.Fragment; -import com.jacobmountain.graphql.client.query.selectors.FragmentRenderer; +import com.jacobmountain.graphql.client.query.selectors.InlineFragmentRenderer; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; import com.jacobmountain.graphql.client.visitor.GraphQLFieldSelection; @@ -19,8 +20,6 @@ public class QueryGenerator { private final Schema schema; - private FragmentRenderer fragmentRenderer; - public QueryGenerator(Schema registry) { this.schema = registry; } @@ -42,7 +41,6 @@ private String doGenerateQuery(String request, String field, String type, List args = new HashSet<>(); - fragmentRenderer = new FragmentRenderer(schema, this, fragments); final QueryContext root = new QueryContext(null, definition.getType(), 0, definition, params); String inner = generateFieldSelection(field, root, args, filters) .orElseThrow(RuntimeException::new); @@ -53,7 +51,7 @@ private String doGenerateQuery(String request, String field, String type, List generateFieldSelection(String alias, return Optional.of(alias + args); } - return new DelegatingFieldSelector(fragmentRenderer, schema, this) + return new DelegatingFieldSelector( + new DefaultFieldSelector(schema, this), + new InlineFragmentRenderer(schema, this) + ) .selectFields(typeDefinition, context, argumentCollector, 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 47cb4db..aea4bc0 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 @@ -1,9 +1,7 @@ package com.jacobmountain.graphql.client.query.selectors; import com.jacobmountain.graphql.client.query.QueryContext; -import com.jacobmountain.graphql.client.query.QueryGenerator; import com.jacobmountain.graphql.client.query.filters.FieldFilter; -import com.jacobmountain.graphql.client.utils.Schema; import graphql.language.TypeDefinition; import java.util.Arrays; @@ -15,23 +13,10 @@ public class DelegatingFieldSelector implements FieldSelector { private final List selectors; - public DelegatingFieldSelector(FragmentRenderer fragmentRenderer, Schema schema, QueryGenerator queryGenerator) { - this.selectors = Arrays.asList( - fragmentRenderer, - new DefaultFieldSelector(schema, queryGenerator), - new InlineFragmentRenderer(schema, queryGenerator) - ); + public DelegatingFieldSelector(FieldSelector... selectors) { + this.selectors = Arrays.asList(selectors); } - public DelegatingFieldSelector(Schema schema, QueryGenerator queryGenerator) { - this.selectors = Arrays.asList( - new DefaultFieldSelector(schema, queryGenerator), - new InlineFragmentRenderer(schema, queryGenerator) - ); - } - - - @Override public Stream selectFields(TypeDefinition typeDefinition, QueryContext context, diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java index 27abdc7..f40a8e8 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/FragmentRenderer.java @@ -1,9 +1,8 @@ package com.jacobmountain.graphql.client.query.selectors; -import com.jacobmountain.graphql.client.annotations.GraphQLFragment; -import com.jacobmountain.graphql.client.query.filters.FieldFilter; import com.jacobmountain.graphql.client.query.QueryContext; import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.query.filters.FieldFilter; import com.jacobmountain.graphql.client.utils.Schema; import graphql.language.TypeDefinition; import lombok.extern.slf4j.Slf4j; @@ -46,7 +45,10 @@ public Stream selectFields(TypeDefinition typeDefinition, QueryContex if (fragment == null) { return Stream.empty(); } - final Optional children = new DelegatingFieldSelector(schema, queryGenerator) + final Optional children = new DelegatingFieldSelector( + new DefaultFieldSelector(schema, queryGenerator), + new InlineFragmentRenderer(schema, queryGenerator) + ) .selectFields(schema.getTypeDefinition(fragment.getType()).orElseThrow(RuntimeException::new), context, argumentCollector, filters) .findFirst(); if (children.isPresent()) {