Skip to content

Commit 11052a0

Browse files
committed
WIP: Field selection generator
1 parent e52a508 commit 11052a0

4 files changed

Lines changed: 308 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package graphql.util.querygenerator;
2+
3+
import graphql.schema.*;
4+
5+
import javax.annotation.Nullable;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.stream.Collectors;
9+
import java.util.stream.Stream;
10+
11+
import static java.util.stream.Collectors.toList;
12+
13+
public class QueryGenerator {
14+
private final QueryGeneratorOptions options;
15+
private final GraphQLSchema schema;
16+
17+
public QueryGenerator(QueryGeneratorOptions options) {
18+
this.options = options;
19+
this.schema = options.getSchema();
20+
}
21+
22+
public List<FieldData> generateQuery(String typeName) {
23+
GraphQLType type = this.schema.getType(typeName);
24+
25+
if (type == null) {
26+
throw new IllegalArgumentException("Type " + typeName + " not found in schema");
27+
}
28+
29+
if(!(type instanceof GraphQLOutputType)) {
30+
throw new IllegalArgumentException("Type " + typeName + " is not an output type");
31+
}
32+
33+
return buildFields((GraphQLOutputType) type);
34+
}
35+
36+
private List<FieldData> buildFields(
37+
GraphQLOutputType type
38+
) {
39+
GraphQLOutputType unwrappedType = GraphQLTypeUtil.unwrapAllAs(type);
40+
41+
if (unwrappedType instanceof GraphQLScalarType
42+
|| unwrappedType instanceof GraphQLEnumType) {
43+
return null;
44+
}
45+
46+
if (unwrappedType instanceof GraphQLObjectType) {
47+
List<GraphQLFieldDefinition> fields = ((GraphQLObjectType) unwrappedType).getFieldDefinitions();
48+
49+
return fields.stream()
50+
.map(fieldDef ->
51+
new FieldData(fieldDef.getName(), buildFields(fieldDef.getType())))
52+
.collect(toList());
53+
}
54+
55+
throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName());
56+
}
57+
58+
public static class FieldData {
59+
public final String name;
60+
public final List<FieldData> fields;
61+
62+
public FieldData(String name, List<FieldData> fields) {
63+
this.name = name;
64+
this.fields = fields;
65+
}
66+
67+
}
68+
69+
public static class QueryGeneratorResult {
70+
71+
}
72+
73+
public static QueryGeneratorOptions.QueryGeneratorOptionsBuilder builder() {
74+
return new QueryGeneratorOptions.QueryGeneratorOptionsBuilder();
75+
}
76+
77+
public static QueryGeneratorOptions.QueryGeneratorOptionsBuilder defaultOptions() {
78+
return new QueryGeneratorOptions.QueryGeneratorOptionsBuilder()
79+
.maxDepth(5);
80+
}
81+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package graphql.util.querygenerator;
2+
3+
import graphql.schema.GraphQLSchema;
4+
5+
public class QueryGeneratorOptions {
6+
private final GraphQLSchema schema;
7+
private final int maxDepth;
8+
9+
public QueryGeneratorOptions(GraphQLSchema schema, int maxDepth) {
10+
this.schema = schema;
11+
this.maxDepth = maxDepth;
12+
}
13+
14+
public GraphQLSchema getSchema() {
15+
return schema;
16+
}
17+
18+
public int getMaxDepth() {
19+
return maxDepth;
20+
}
21+
22+
23+
public static class QueryGeneratorOptionsBuilder {
24+
private int maxDepth;
25+
private GraphQLSchema schema;
26+
27+
QueryGeneratorOptionsBuilder maxDepth(int maxDepth) {
28+
this.maxDepth = maxDepth;
29+
return this;
30+
}
31+
32+
QueryGeneratorOptionsBuilder schema(GraphQLSchema schema) {
33+
this.schema = schema;
34+
return this;
35+
}
36+
37+
public QueryGeneratorOptions build() {
38+
if (schema == null) {
39+
throw new IllegalArgumentException("Schema cannot be null");
40+
}
41+
42+
return new QueryGeneratorOptions(
43+
schema,
44+
maxDepth
45+
);
46+
}
47+
}
48+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package graphql.util.querygenerator;
2+
3+
import java.util.List;
4+
import java.util.stream.Collectors;
5+
6+
public class QueryGeneratorPrinter {
7+
private final String indentationString;
8+
private final int indentationSpaces;
9+
private final int startingIndentationLevel;
10+
11+
public QueryGeneratorPrinter(String indentationString, int indentationSpaces, int startingIndentationLevel) {
12+
this.indentationString = indentationString;
13+
this.indentationSpaces = indentationSpaces;
14+
this.startingIndentationLevel = startingIndentationLevel;
15+
}
16+
17+
public String print(List<QueryGenerator.FieldData> fields) {
18+
String initialIndentation = startingIndentationLevel == 0 ? ""
19+
: indentationString.repeat(this.startingIndentationLevel * this.indentationSpaces);
20+
21+
return fields.stream()
22+
.map(field -> printField(field, startingIndentationLevel + 1))
23+
.collect(Collectors.joining("", initialIndentation + "{\n", initialIndentation + "}\n"));
24+
}
25+
26+
private String printField(QueryGenerator.FieldData fieldData, int level) {
27+
String indentation = indentationString.repeat(level * this.indentationSpaces);
28+
StringBuilder sb = new StringBuilder();
29+
sb.append(indentation).append(fieldData.name);
30+
if (fieldData.fields != null && !fieldData.fields.isEmpty()) {
31+
sb.append(" {\n");
32+
for (QueryGenerator.FieldData subField : fieldData.fields) {
33+
sb.append(printField(subField, level + 1));
34+
}
35+
sb.append(indentation).append("}\n");
36+
} else {
37+
sb.append("\n");
38+
}
39+
return sb.toString();
40+
}
41+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package graphql.util.querygenerator
2+
3+
4+
import graphql.TestUtil
5+
import org.junit.Assert
6+
import spock.lang.Specification
7+
8+
class QueryGeneratorTest extends Specification {
9+
def schema = TestUtil.schema("""
10+
type Query {
11+
foo: Foo
12+
}
13+
14+
type Foo {
15+
id: ID!
16+
bar: Bar
17+
bars: [Bar]
18+
}
19+
20+
type FooFoo {
21+
id: ID!
22+
name: String
23+
fooFoo: FooFoo
24+
}
25+
26+
type FooBarFoo {
27+
id: ID!
28+
name: String
29+
barFoo: BarFoo
30+
}
31+
32+
type BarFoo {
33+
id: ID!
34+
name: String
35+
fooBarFoo: FooBarFoo
36+
}
37+
38+
type Bar {
39+
id: ID!
40+
name: String
41+
type: TypeEnum
42+
}
43+
44+
enum TypeEnum {
45+
FOO
46+
BAR
47+
}
48+
49+
""")
50+
51+
def queryGenerator = new QueryGenerator(
52+
QueryGenerator.defaultOptions()
53+
.schema(schema)
54+
.build()
55+
)
56+
57+
def printer = new QueryGeneratorPrinter(" ", 2, 0)
58+
59+
def "generate fields for simple type"() {
60+
given:
61+
62+
def typeName = "Bar"
63+
64+
when:
65+
def result = queryGenerator.generateQuery(typeName)
66+
67+
then:
68+
String printed = printer.print(result)
69+
70+
Assert.assertEquals(printed.trim(), """
71+
{
72+
id
73+
name
74+
type
75+
}
76+
""".trim())
77+
}
78+
79+
def "generate fields for type with nested type"() {
80+
given:
81+
82+
def typeName = "Foo"
83+
84+
when:
85+
def result = queryGenerator.generateQuery(typeName)
86+
87+
then:
88+
String printed = printer.print(result)
89+
90+
Assert.assertEquals(printed.trim(), """
91+
{
92+
id
93+
bar {
94+
id
95+
name
96+
type
97+
}
98+
bars {
99+
id
100+
name
101+
type
102+
}
103+
}
104+
""".trim())
105+
}
106+
107+
def "straight forward cyclic dependency"() {
108+
given:
109+
110+
def typeName = "FooFoo"
111+
112+
when:
113+
def result = queryGenerator.generateQuery(typeName)
114+
115+
then:
116+
String printed = printer.print(result)
117+
118+
Assert.assertEquals(printed.trim(), """
119+
120+
""".trim())
121+
}
122+
123+
def "transitive cyclic dependency"() {
124+
given:
125+
126+
def typeName = "FooBarFoo"
127+
128+
when:
129+
def result = queryGenerator.generateQuery(typeName)
130+
131+
then:
132+
String printed = printer.print(result)
133+
134+
Assert.assertEquals(printed.trim(), """
135+
136+
""".trim())
137+
}
138+
}

0 commit comments

Comments
 (0)