Skip to content

Commit 06add25

Browse files
authored
Introspection parser (#463)
This adds the ability to convert an Introspection result (json) to a IDL. This completes the cycle: Schema -> Introspection -> IDL -> RuntimeWiring -> Schema It also fixed some minor Issues with `AstPrinter` regarding comments as descriptions.
1 parent 0faa9f7 commit 06add25

File tree

9 files changed

+3390
-18
lines changed

9 files changed

+3390
-18
lines changed

src/main/java/graphql/Assert.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,24 @@ public static <T> T assertNeverCalled() {
1414
throw new AssertException("Should never been called");
1515
}
1616

17+
public static <T> T assertShouldNeverHappen(String errorMessage) {
18+
throw new AssertException("Internal error: should never happen: " + errorMessage);
19+
}
20+
21+
public static <T> T assertShouldNeverHappen() {
22+
throw new AssertException("Internal error: should never happen");
23+
}
24+
1725
public static <T> Collection<T> assertNotEmpty(Collection<T> c, String errorMessage) {
1826
if (c == null || c.isEmpty()) throw new AssertException(errorMessage);
1927
return c;
2028
}
2129

30+
public static void assertTrue(boolean condition, String errorMessage) {
31+
if (condition) return;
32+
throw new AssertException(errorMessage);
33+
}
34+
2235
private static final String invalidNameErrorMessage = "Name must be non-null, non-empty and match [_A-Za-z][_0-9A-Za-z]*";
2336

2437
/**
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package graphql.introspection;
2+
3+
import graphql.PublicApi;
4+
import graphql.language.Comment;
5+
import graphql.language.Document;
6+
import graphql.language.EnumTypeDefinition;
7+
import graphql.language.EnumValueDefinition;
8+
import graphql.language.FieldDefinition;
9+
import graphql.language.InputObjectTypeDefinition;
10+
import graphql.language.InputValueDefinition;
11+
import graphql.language.InterfaceTypeDefinition;
12+
import graphql.language.ListType;
13+
import graphql.language.NonNullType;
14+
import graphql.language.ObjectTypeDefinition;
15+
import graphql.language.OperationTypeDefinition;
16+
import graphql.language.SchemaDefinition;
17+
import graphql.language.SourceLocation;
18+
import graphql.language.StringValue;
19+
import graphql.language.Type;
20+
import graphql.language.TypeDefinition;
21+
import graphql.language.TypeName;
22+
import graphql.language.UnionTypeDefinition;
23+
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.Collections;
27+
import java.util.List;
28+
import java.util.Map;
29+
30+
import static graphql.Assert.assertNotNull;
31+
import static graphql.Assert.assertShouldNeverHappen;
32+
import static graphql.Assert.assertTrue;
33+
34+
@PublicApi
35+
public class IntrospectionResultToSchema {
36+
37+
38+
@SuppressWarnings("unchecked")
39+
public Document createSchemaDefinition(Map<String, Object> introspectionResult) {
40+
assertTrue(introspectionResult.get("__schema") != null, "__schema expected");
41+
Map<String, Object> schema = (Map<String, Object>) introspectionResult.get("__schema");
42+
43+
SchemaDefinition schemaDefinition = new SchemaDefinition();
44+
45+
Map<String, Object> queryType = (Map<String, Object>) schema.get("queryType");
46+
assertNotNull(queryType, "queryType expected");
47+
TypeName query = new TypeName((String) queryType.get("name"));
48+
schemaDefinition.getOperationTypeDefinitions().add(new OperationTypeDefinition("query", query));
49+
50+
Map<String, Object> mutationType = (Map<String, Object>) schema.get("mutationType");
51+
if (mutationType != null) {
52+
TypeName mutation = new TypeName((String) mutationType.get("name"));
53+
schemaDefinition.getOperationTypeDefinitions().add(new OperationTypeDefinition("mutation", mutation));
54+
}
55+
56+
Map<String, Object> subscriptionType = (Map<String, Object>) schema.get("subscriptionType");
57+
if (subscriptionType != null) {
58+
TypeName subscription = new TypeName((String) subscriptionType.get("name"));
59+
schemaDefinition.getOperationTypeDefinitions().add(new OperationTypeDefinition("subscription", subscription));
60+
}
61+
62+
Document document = new Document();
63+
document.getDefinitions().add(schemaDefinition);
64+
65+
List<Map<String, Object>> types = (List<Map<String, Object>>) schema.get("types");
66+
for (Map<String, Object> type : types) {
67+
TypeDefinition typeDefinition = createTypeDefinition(type);
68+
if (typeDefinition == null) continue;
69+
document.getDefinitions().add(typeDefinition);
70+
}
71+
72+
return document;
73+
}
74+
75+
private TypeDefinition createTypeDefinition(Map<String, Object> type) {
76+
String kind = (String) type.get("kind");
77+
String name = (String) type.get("name");
78+
if (name.startsWith("__")) return null;
79+
switch (kind) {
80+
case "INTERFACE":
81+
return createInterface(type);
82+
case "OBJECT":
83+
return createObject(type);
84+
case "UNION":
85+
return createUnion(type);
86+
case "ENUM":
87+
return createEnum(type);
88+
case "INPUT_OBJECT":
89+
return createInputObject(type);
90+
case "SCALAR":
91+
// todo don't ignore all scalars
92+
return null;
93+
default:
94+
return assertShouldNeverHappen("unexpected kind " + kind);
95+
}
96+
}
97+
98+
99+
@SuppressWarnings("unchecked")
100+
UnionTypeDefinition createUnion(Map<String, Object> input) {
101+
assertTrue(input.get("kind").equals("UNION"), "wrong input");
102+
103+
UnionTypeDefinition unionTypeDefinition = new UnionTypeDefinition((String) input.get("name"));
104+
unionTypeDefinition.setComments(toComment((String) input.get("description")));
105+
106+
List<Map<String, Object>> possibleTypes = (List<Map<String, Object>>) input.get("possibleTypes");
107+
108+
for (Map<String, Object> possibleType : possibleTypes) {
109+
TypeName typeName = new TypeName((String) possibleType.get("name"));
110+
unionTypeDefinition.getMemberTypes().add(typeName);
111+
}
112+
113+
return unionTypeDefinition;
114+
}
115+
116+
@SuppressWarnings("unchecked")
117+
EnumTypeDefinition createEnum(Map<String, Object> input) {
118+
assertTrue(input.get("kind").equals("ENUM"), "wrong input");
119+
120+
EnumTypeDefinition enumTypeDefinition = new EnumTypeDefinition((String) input.get("name"));
121+
enumTypeDefinition.setComments(toComment((String) input.get("description")));
122+
123+
List<Map<String, Object>> enumValues = (List<Map<String, Object>>) input.get("enumValues");
124+
125+
for (Map<String, Object> enumValue : enumValues) {
126+
127+
EnumValueDefinition enumValueDefinition = new EnumValueDefinition((String) enumValue.get("name"));
128+
enumValueDefinition.setComments(toComment((String) enumValue.get("description")));
129+
enumTypeDefinition.getEnumValueDefinitions().add(enumValueDefinition);
130+
}
131+
132+
return enumTypeDefinition;
133+
}
134+
135+
@SuppressWarnings("unchecked")
136+
InterfaceTypeDefinition createInterface(Map<String, Object> input) {
137+
assertTrue(input.get("kind").equals("INTERFACE"), "wrong input");
138+
139+
InterfaceTypeDefinition interfaceTypeDefinition = new InterfaceTypeDefinition((String) input.get("name"));
140+
interfaceTypeDefinition.setComments(toComment((String) input.get("description")));
141+
List<Map<String, Object>> fields = (List<Map<String, Object>>) input.get("fields");
142+
interfaceTypeDefinition.getFieldDefinitions().addAll(createFields(fields));
143+
144+
return interfaceTypeDefinition;
145+
146+
}
147+
148+
@SuppressWarnings("unchecked")
149+
InputObjectTypeDefinition createInputObject(Map<String, Object> input) {
150+
assertTrue(input.get("kind").equals("INPUT_OBJECT"), "wrong input");
151+
152+
InputObjectTypeDefinition inputObjectTypeDefinition = new InputObjectTypeDefinition((String) input.get("name"));
153+
inputObjectTypeDefinition.setComments(toComment((String) input.get("description")));
154+
List<Map<String, Object>> fields = (List<Map<String, Object>>) input.get("inputFields");
155+
List<InputValueDefinition> inputValueDefinitions = createInputValueDefinitions(fields);
156+
inputObjectTypeDefinition.getInputValueDefinitions().addAll(inputValueDefinitions);
157+
158+
return inputObjectTypeDefinition;
159+
}
160+
161+
@SuppressWarnings("unchecked")
162+
ObjectTypeDefinition createObject(Map<String, Object> input) {
163+
assertTrue(input.get("kind").equals("OBJECT"), "wrong input");
164+
165+
ObjectTypeDefinition objectTypeDefinition = new ObjectTypeDefinition((String) input.get("name"));
166+
objectTypeDefinition.setComments(toComment((String) input.get("description")));
167+
List<Map<String, Object>> fields = (List<Map<String, Object>>) input.get("fields");
168+
169+
objectTypeDefinition.getFieldDefinitions().addAll(createFields(fields));
170+
171+
return objectTypeDefinition;
172+
}
173+
174+
private List<FieldDefinition> createFields(List<Map<String, Object>> fields) {
175+
List<FieldDefinition> result = new ArrayList<>();
176+
for (Map<String, Object> field : fields) {
177+
FieldDefinition fieldDefinition = new FieldDefinition((String) field.get("name"));
178+
fieldDefinition.setComments(toComment((String) field.get("description")));
179+
fieldDefinition.setType(createTypeIndirection((Map<String, Object>) field.get("type")));
180+
181+
List<Map<String, Object>> args = (List<Map<String, Object>>) field.get("args");
182+
List<InputValueDefinition> inputValueDefinitions = createInputValueDefinitions(args);
183+
fieldDefinition.getInputValueDefinitions().addAll(inputValueDefinitions);
184+
result.add(fieldDefinition);
185+
}
186+
return result;
187+
}
188+
189+
@SuppressWarnings("unchecked")
190+
private List<InputValueDefinition> createInputValueDefinitions(List<Map<String, Object>> args) {
191+
List<InputValueDefinition> result = new ArrayList<>();
192+
for (Map<String, Object> arg : args) {
193+
Type argType = createTypeIndirection((Map<String, Object>) arg.get("type"));
194+
InputValueDefinition inputValueDefinition = new InputValueDefinition((String) arg.get("name"), argType);
195+
inputValueDefinition.setComments(toComment((String) arg.get("description")));
196+
197+
if (arg.get("defaultValue") != null) {
198+
StringValue defaultValue = new StringValue((String) arg.get("defaultValue"));
199+
inputValueDefinition.setDefaultValue(defaultValue);
200+
}
201+
result.add(inputValueDefinition);
202+
}
203+
return result;
204+
}
205+
206+
@SuppressWarnings("unchecked")
207+
private Type createTypeIndirection(Map<String, Object> type) {
208+
String kind = (String) type.get("kind");
209+
switch (kind) {
210+
case "INTERFACE":
211+
case "OBJECT":
212+
case "UNION":
213+
case "ENUM":
214+
case "INPUT_OBJECT":
215+
case "SCALAR":
216+
return new TypeName((String) type.get("name"));
217+
case "NON_NULL":
218+
return new NonNullType(createTypeIndirection((Map<String, Object>) type.get("ofType")));
219+
case "LIST":
220+
return new ListType(createTypeIndirection((Map<String, Object>) type.get("ofType")));
221+
default:
222+
return assertShouldNeverHappen("Unknown kind " + kind);
223+
}
224+
}
225+
226+
private List<Comment> toComment(String description) {
227+
if (description == null) return Collections.emptyList();
228+
Comment comment = new Comment(description, new SourceLocation(1, 1));
229+
return Arrays.asList(comment);
230+
}
231+
232+
}

src/main/java/graphql/language/AstPrinter.java

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,27 @@ private static NodePrinter<DirectiveLocation> directiveLocation() {
8989
}
9090

9191
private static NodePrinter<EnumTypeDefinition> enumTypeDefinition() {
92-
return (out, node) -> out.printf("%s", spaced(
93-
"enum",
94-
node.getName(),
95-
directives(node.getDirectives()),
96-
block(node.getEnumValueDefinitions())
97-
));
92+
return (out, node) -> {
93+
out.printf("%s", comments(node));
94+
out.printf("%s",
95+
spaced(
96+
"enum",
97+
node.getName(),
98+
directives(node.getDirectives()),
99+
block(node.getEnumValueDefinitions())
100+
));
101+
};
98102
}
99103

100104
private static NodePrinter<EnumValue> enumValue() {
101105
return (out, node) -> out.printf("%s", node.getName());
102106
}
103107

104108
private static NodePrinter<EnumValueDefinition> enumValueDefinition() {
105-
return (out, node) -> out.printf("%s", node.getName());
109+
return (out, node) -> {
110+
out.printf("%s", comments(node));
111+
out.printf("%s", node.getName());
112+
};
106113
}
107114

108115
private static NodePrinter<Field> field() {
@@ -124,16 +131,30 @@ private static NodePrinter<Field> field() {
124131

125132
private static NodePrinter<FieldDefinition> fieldDefinition() {
126133
return (out, node) -> {
127-
String args = join(node.getInputValueDefinitions(), ", ");
128134
out.printf("%s", comments(node));
129-
out.printf("%s", node.getName() +
130-
wrap("(", args, ")") +
131-
": " + type(node.getType()) +
132-
directives(node.getDirectives())
133-
);
135+
String args;
136+
if (hasComments(node.getInputValueDefinitions())) {
137+
args = join(node.getInputValueDefinitions(), "\n");
138+
out.printf("%s", node.getName() +
139+
wrap("(\n", args, "\n)") +
140+
": " + type(node.getType()) +
141+
directives(node.getDirectives())
142+
);
143+
} else {
144+
args = join(node.getInputValueDefinitions(), ", ");
145+
out.printf("%s", node.getName() +
146+
wrap("(", args, ")") +
147+
": " + type(node.getType()) +
148+
directives(node.getDirectives())
149+
);
150+
}
134151
};
135152
}
136153

154+
private static boolean hasComments(List<? extends Node> nodes) {
155+
return nodes.stream().filter(it -> it.getComments().size() > 0).count() > 0;
156+
}
157+
137158
private static NodePrinter<FragmentDefinition> fragmentDefinition() {
138159
return (out, node) -> {
139160
String name = node.getName();
@@ -461,7 +482,6 @@ static String wrap(String start, Node maybeNode, String end) {
461482
* This will pretty print the AST node in graphql language format
462483
*
463484
* @param node the AST node to print
464-
*
465485
* @return the printed node in graphql language format
466486
*/
467487
public static String printAst(Node node) {

src/main/java/graphql/language/InputValueDefinition.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public Value getDefaultValue() {
3939
return defaultValue;
4040
}
4141

42-
public void setValue(Value defaultValue) {
42+
public void setDefaultValue(Value defaultValue) {
4343
this.defaultValue = defaultValue;
4444
}
4545

src/main/java/graphql/parser/GraphqlAntlrToLanguage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ public Void visitInputValueDefinition(GraphqlParser.InputValueDefinitionContext
537537
InputValueDefinition def = new InputValueDefinition(ctx.name().getText());
538538
newNode(def, ctx);
539539
if (ctx.defaultValue() != null) {
540-
def.setValue(getValue(ctx.defaultValue().value()));
540+
def.setDefaultValue(getValue(ctx.defaultValue().value()));
541541
}
542542
for (ContextEntry contextEntry : contextStack) {
543543
if (contextEntry.contextProperty == ContextProperty.FieldDefinition) {

0 commit comments

Comments
 (0)