Skip to content

Commit 4a80519

Browse files
committed
fix for graphql-java#1434 directive definition printing
1 parent d008832 commit 4a80519

2 files changed

Lines changed: 129 additions & 11 deletions

File tree

src/main/java/graphql/schema/idl/SchemaPrinter.java

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
import java.util.LinkedHashMap;
3434
import java.util.List;
3535
import java.util.Map;
36+
import java.util.function.Predicate;
3637
import java.util.function.Supplier;
38+
import java.util.stream.Collectors;
3739
import java.util.stream.Stream;
3840

3941
import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY;
@@ -64,11 +66,11 @@ public static class Options {
6466
private final SchemaPrinterComparatorRegistry comparatorRegistry;
6567

6668
private Options(boolean includeIntrospectionTypes,
67-
boolean includeScalars,
68-
boolean includeExtendedScalars,
69-
boolean includeSchemaDefinition,
70-
boolean includeDirectives,
71-
SchemaPrinterComparatorRegistry comparatorRegistry) {
69+
boolean includeScalars,
70+
boolean includeExtendedScalars,
71+
boolean includeSchemaDefinition,
72+
boolean includeDirectives,
73+
SchemaPrinterComparatorRegistry comparatorRegistry) {
7274
this.includeIntrospectionTypes = includeIntrospectionTypes;
7375
this.includeScalars = includeScalars;
7476
this.includeExtendedScalars = includeExtendedScalars;
@@ -106,6 +108,7 @@ public static Options defaultOptions() {
106108
* This will allow you to include introspection types that are contained in a schema
107109
*
108110
* @param flag whether to include them
111+
*
109112
* @return options
110113
*/
111114
public Options includeIntrospectionTypes(boolean flag) {
@@ -116,6 +119,7 @@ public Options includeIntrospectionTypes(boolean flag) {
116119
* This will allow you to include scalar types that are contained in a schema
117120
*
118121
* @param flag whether to include them
122+
*
119123
* @return options
120124
*/
121125
public Options includeScalarTypes(boolean flag) {
@@ -127,6 +131,7 @@ public Options includeScalarTypes(boolean flag) {
127131
* GraphQLBigDecimal or GraphQLBigInteger
128132
*
129133
* @param flag whether to include them
134+
*
130135
* @return options
131136
*/
132137
public Options includeExtendedScalarTypes(boolean flag) {
@@ -140,6 +145,7 @@ public Options includeExtendedScalarTypes(boolean flag) {
140145
* types do not use the default names.
141146
*
142147
* @param flag whether to force include the schema definition
148+
*
143149
* @return options
144150
*/
145151
public Options includeSchemaDefintion(boolean flag) {
@@ -151,6 +157,7 @@ public Options includeSchemaDefintion(boolean flag) {
151157
* make the printout noisy and having this flag would allow cleaner printout. On by default.
152158
*
153159
* @param flag whether to print directives
160+
*
154161
* @return new instance of options
155162
*/
156163
public Options includeDirectives(boolean flag) {
@@ -161,6 +168,7 @@ public Options includeDirectives(boolean flag) {
161168
* The comparator registry controls the printing order for registered {@code GraphQLType}s.
162169
*
163170
* @param comparatorRegistry The registry containing the {@code Comparator} and environment scoping rules.
171+
*
164172
* @return options
165173
*/
166174
public Options setComparators(SchemaPrinterComparatorRegistry comparatorRegistry) {
@@ -199,6 +207,7 @@ public SchemaPrinter(Options options) {
199207
* first to get the {@link graphql.language.Document} and then print that.
200208
*
201209
* @param schemaIDL the parsed schema IDL
210+
*
202211
* @return the logical schema definition
203212
*/
204213
public String print(Document schemaIDL) {
@@ -210,6 +219,7 @@ public String print(Document schemaIDL) {
210219
* This can print an in memory GraphQL schema back to a logical schema definition
211220
*
212221
* @param schema the schema in play
222+
*
213223
* @return the logical schema definition
214224
*/
215225
public String print(GraphQLSchema schema) {
@@ -436,10 +446,10 @@ private static String printAst(Object value, GraphQLInputType type) {
436446
}
437447

438448
private TypePrinter<GraphQLSchema> schemaPrinter() {
439-
return (out, type, visibility) -> {
440-
GraphQLObjectType queryType = type.getQueryType();
441-
GraphQLObjectType mutationType = type.getMutationType();
442-
GraphQLObjectType subscriptionType = type.getSubscriptionType();
449+
return (out, schema, visibility) -> {
450+
GraphQLObjectType queryType = schema.getQueryType();
451+
GraphQLObjectType mutationType = schema.getMutationType();
452+
GraphQLObjectType subscriptionType = schema.getSubscriptionType();
443453

444454
// when serializing a GraphQL schema using the type system language, a
445455
// schema definition should be omitted if only uses the default root type names.
@@ -470,9 +480,26 @@ private TypePrinter<GraphQLSchema> schemaPrinter() {
470480
}
471481
out.format("}\n\n");
472482
}
483+
484+
if (options.includeDirectives) {
485+
out.format("%s", directiveDefinitions(getDirectives(schema)));
486+
}
473487
};
474488
}
475489

490+
private List<GraphQLDirective> getDirectives(GraphQLSchema schema) {
491+
492+
// we don't print the standard directives that always ship with graphql-java
493+
List<String> standardDirectives = Arrays.asList(
494+
"skip",
495+
"include",
496+
"defer",
497+
"deprecated");
498+
499+
Predicate<GraphQLDirective> standard = directive -> standardDirectives.contains(directive.getName());
500+
return schema.getDirectives().stream().filter(standard.negate()).collect(Collectors.toList());
501+
}
502+
476503
String typeString(GraphQLType rawType) {
477504
return GraphQLTypeUtil.simplePrint(rawType);
478505
}
@@ -612,6 +639,50 @@ private String directiveString(GraphQLDirective directive) {
612639
return sb.toString();
613640
}
614641

642+
private String directiveDefinitions(List<GraphQLDirective> directives) {
643+
StringBuilder sb = new StringBuilder();
644+
for (GraphQLDirective directive : directives) {
645+
sb.append(directiveDefinition(directive));
646+
sb.append("\n\n");
647+
}
648+
return sb.toString();
649+
}
650+
651+
private String directiveDefinition(GraphQLDirective directive) {
652+
StringBuilder sb = new StringBuilder();
653+
654+
StringWriter sw = new StringWriter();
655+
PrintWriter pw = new PrintWriter(sw);
656+
657+
printComments(pw, directive, "");
658+
659+
sb.append(sw.toString());
660+
661+
sb.append("directive @").append(directive.getName());
662+
663+
SchemaPrinterComparatorEnvironment environment = SchemaPrinterComparatorEnvironment.newEnvironment()
664+
.parentType(GraphQLDirective.class)
665+
.elementType(GraphQLArgument.class)
666+
.build();
667+
Comparator<? super GraphQLType> comparator = options.comparatorRegistry.getComparator(environment);
668+
669+
List<GraphQLArgument> args = directive.getArguments();
670+
args = args
671+
.stream()
672+
.sorted(comparator)
673+
.collect(toList());
674+
675+
sb.append(argsString(GraphQLDirective.class, args));
676+
677+
sb.append(" on ");
678+
679+
String locations = directive.validLocations().stream().map(Enum::name).collect(Collectors.joining(" | "));
680+
sb.append(locations);
681+
682+
return sb.toString();
683+
}
684+
685+
615686
@SuppressWarnings("unchecked")
616687
private <T> TypePrinter<T> printer(Class<?> clazz) {
617688
TypePrinter typePrinter = printers.computeIfAbsent(clazz, k -> {
@@ -732,13 +803,16 @@ private AstDescriptionAndComments getDescriptionAndComments(Object descriptionHo
732803
} else if (descriptionHolder instanceof GraphQLArgument) {
733804
GraphQLArgument type = (GraphQLArgument) descriptionHolder;
734805
return descriptionAndComments(type::getDescription, type::getDefinition, () -> type.getDefinition().getDescription());
806+
} else if (descriptionHolder instanceof GraphQLDirective) {
807+
GraphQLDirective type = (GraphQLDirective) descriptionHolder;
808+
return descriptionAndComments(type::getDescription, () -> null, () -> null);
735809
} else {
736810
return Assert.assertShouldNeverHappen();
737811
}
738812
}
739813

740-
AstDescriptionAndComments descriptionAndComments(Supplier<String> stringSupplier, Supplier<Node> nodeSupplier, Supplier<Description> descriptionSupplier) {
741-
String runtimeDesc = stringSupplier.get();
814+
AstDescriptionAndComments descriptionAndComments(Supplier<String> runtimeDescriptionSupplier, Supplier<Node> nodeSupplier, Supplier<Description> descriptionSupplier) {
815+
String runtimeDesc = runtimeDescriptionSupplier.get();
742816
Node node = nodeSupplier.get();
743817
Description description = null;
744818
List<Comment> comments = null;

src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,4 +922,48 @@ enum Enum {
922922
'''
923923
}
924924

925+
926+
def "directives are printed as top level types when the includeDirectives flag is set"() {
927+
def simpleIdlWithDirective = '''
928+
directive @example on FIELD_DEFINITION
929+
930+
directive @moreComplex(arg1 : String = "default", arg2 : Int)
931+
on FIELD_DEFINITION |
932+
INPUT_FIELD_DEFINITION
933+
934+
type Query {
935+
fieldA : String @example @moreComplex(arg2 : 666)
936+
}
937+
'''
938+
given:
939+
def registry = new SchemaParser().parse(simpleIdlWithDirective)
940+
def runtimeWiring = newRuntimeWiring().build()
941+
def options = SchemaGenerator.Options.defaultOptions().enforceSchemaDirectives(true)
942+
def schema = new SchemaGenerator().makeExecutableSchema(options, registry, runtimeWiring)
943+
944+
when:
945+
def resultWithNoDirectives = new SchemaPrinter(defaultOptions().includeDirectives(false)).print(schema)
946+
947+
then:
948+
resultWithNoDirectives == '''\
949+
type Query {
950+
fieldA: String
951+
}
952+
'''
953+
954+
when:
955+
def resultWithDirectives = new SchemaPrinter(defaultOptions().includeDirectives(true)).print(schema)
956+
957+
then:
958+
resultWithDirectives == '''\
959+
directive @example on FIELD_DEFINITION
960+
961+
directive @moreComplex(arg1: String = "default", arg2: Int) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
962+
963+
type Query {
964+
fieldA: String @example @moreComplex(arg1 : "default", arg2 : 666)
965+
}
966+
'''
967+
}
968+
925969
}

0 commit comments

Comments
 (0)