Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 52 additions & 17 deletions src/main/java/graphql/schema/idl/SchemaPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import graphql.language.ObjectTypeDefinition;
import graphql.language.ScalarTypeDefinition;
import graphql.language.SchemaDefinition;
import graphql.language.SchemaExtensionDefinition;
import graphql.language.TypeDefinition;
import graphql.language.UnionTypeDefinition;
import graphql.schema.DefaultGraphqlTypeComparatorRegistry;
Expand Down Expand Up @@ -283,7 +284,7 @@ public Options includeDirectiveDefinitions(boolean flag) {
/**
* This is a Predicate that decides whether a directive definition is printed.
*
* @param includeDirectiveDefinition the predicate to decide of a directive defintion is printed
* @param includeDirectiveDefinition the predicate to decide of a directive definition is printed
*
* @return new instance of options
*/
Expand Down Expand Up @@ -482,7 +483,7 @@ public SchemaPrinter(Options options) {

/**
* This can print an in memory GraphQL IDL document back to a logical schema definition.
* If you want to turn a Introspection query result into a Document (and then into a printed
* If you want to turn an Introspection query result into a Document (and then into a printed
* schema) then use {@link graphql.introspection.IntrospectionResultToSchema#createSchemaDefinition(java.util.Map)}
* first to get the {@link graphql.language.Document} and then print that.
*
Expand Down Expand Up @@ -773,6 +774,17 @@ private boolean shouldPrintAsAst(TypeDefinition<?> definition) {
return options.isUseAstDefinitions() && definition != null;
}

/**
* This will return true if the options say to use the AST and we have an AST element
*
* @param definition the AST schema definition
*
* @return true if we should print using AST nodes
*/
private boolean shouldPrintAsAst(SchemaDefinition definition) {
return options.isUseAstDefinitions() && definition != null;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to type definition version above

/**
* This will print out a runtime graphql schema element using its contained AST type definition. This
* must be guarded by a called to {@link #shouldPrintAsAst(TypeDefinition)}
Expand All @@ -792,6 +804,25 @@ private void printAsAst(PrintWriter out, TypeDefinition<?> definition, List<? ex
out.print('\n');
}

/**
* This will print out a runtime graphql schema block using its AST definition. This
* must be guarded by a called to {@link #shouldPrintAsAst(SchemaDefinition)}
*
* @param out the output writer
* @param definition the AST schema definition
* @param extensions a list of schema definition extensions
*/
private void printAsAst(PrintWriter out, SchemaDefinition definition, List<SchemaExtensionDefinition> extensions) {
out.printf("%s\n", AstPrinter.printAst(definition));
if (extensions != null) {
for (SchemaExtensionDefinition extension : extensions) {
out.printf("\n%s\n", AstPrinter.printAst(extension));
}
}
out.print('\n');
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to type definition version above. This prints everything including directives and description.


private static String printAst(InputValueWithState value, GraphQLInputType type) {
return AstPrinter.printAst(ValuesResolver.valueToLiteral(value, type, GraphQLContext.getDefault(), Locale.getDefault()));
}
Expand All @@ -803,7 +834,7 @@ private SchemaElementPrinter<GraphQLSchema> schemaPrinter() {
GraphQLObjectType subscriptionType = schema.getSubscriptionType();

// when serializing a GraphQL schema using the type system language, a
// schema definition should be omitted if only uses the default root type names.
// schema definition should be omitted only if it uses the default root type names.
boolean needsSchemaPrinted = options.isIncludeSchemaDefinition();

if (!needsSchemaPrinted) {
Expand All @@ -819,21 +850,25 @@ private SchemaElementPrinter<GraphQLSchema> schemaPrinter() {
}

if (needsSchemaPrinted) {
if (hasAstDefinitionComments(schema) || hasDescription(schema)) {
out.print(printComments(schema, ""));
}
List<GraphQLAppliedDirective> directives = DirectivesUtil.toAppliedDirectives(schema.getSchemaAppliedDirectives(), schema.getSchemaDirectives());
out.format("schema %s{\n", directivesString(GraphQLSchemaElement.class, directives));
if (queryType != null) {
out.format(" query: %s\n", queryType.getName());
}
if (mutationType != null) {
out.format(" mutation: %s\n", mutationType.getName());
}
if (subscriptionType != null) {
out.format(" subscription: %s\n", subscriptionType.getName());
if (shouldPrintAsAst(schema.getDefinition())) {
printAsAst(out, schema.getDefinition(), schema.getExtensionDefinitions());
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main change on this line: if AST flag is on, use the new AST print method for SchemaDefinition

} else {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What follows is the original code, this will run if AST switch is off

if (hasAstDefinitionComments(schema) || hasDescription(schema)) {
out.print(printComments(schema, ""));
}
List<GraphQLAppliedDirective> directives = DirectivesUtil.toAppliedDirectives(schema.getSchemaAppliedDirectives(), schema.getSchemaDirectives());
out.format("schema %s{\n", directivesString(GraphQLSchemaElement.class, directives));
if (queryType != null) {
out.format(" query: %s\n", queryType.getName());
}
if (mutationType != null) {
out.format(" mutation: %s\n", mutationType.getName());
}
if (subscriptionType != null) {
out.format(" subscription: %s\n", subscriptionType.getName());
}
out.format("}\n\n");
}
out.format("}\n\n");
}
};
}
Expand Down
186 changes: 186 additions & 0 deletions src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -1342,9 +1342,195 @@ type Query {
'''
}

def "can print extend schema block when AST printing enabled"() {
def sdl = '''
directive @schemaDirective on SCHEMA

"""
My schema block description
"""
schema {
mutation: MyMutation
}

extend schema @schemaDirective {
query: MyQuery
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: although there is an implicit schema block if it is not specified, if you want to extend schema you MUST also have an explicit schema block defined in the schema.


extend schema {
subscription: MySubscription
}

type MyQuery {
foo: String
}

type MyMutation {
pizza: String
}

type MySubscription {
chippies: String
}
'''

when:
def runtimeWiring = newRuntimeWiring().build()

def options = SchemaGenerator.Options.defaultOptions()
def types = new SchemaParser().parse(sdl)
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(options, types, runtimeWiring)

def printOptions = defaultOptions()
.useAstDefinitions(true)
.includeSchemaDefinition(true)
def result = new SchemaPrinter(printOptions).print(schema)

then:
result == '''"""
My schema block description
"""
schema {
mutation: MyMutation
}

extend schema @schemaDirective {
query: MyQuery
}

extend schema {
subscription: MySubscription
}

"Marks the field, argument, input field or enum value as deprecated"
directive @deprecated(
"The reason for the deprecation"
reason: String = "No longer supported"
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION

"Directs the executor to include this field or fragment only when the `if` argument is true"
directive @include(
"Included when true."
if: Boolean!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Indicates an Input Object is a OneOf Input Object."
directive @oneOf on INPUT_OBJECT

directive @schemaDirective on SCHEMA

"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(
"Skipped when true."
if: Boolean!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Exposes a URL that specifies the behaviour of this scalar."
directive @specifiedBy(
"The URL that specifies the behaviour of this scalar."
url: String!
) on SCALAR

type MyMutation {
pizza: String
}

type MyQuery {
foo: String
}

type MySubscription {
chippies: String
}
'''
}

def "will not print extend schema block when AST printing not enabled"() {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If AST switch is off, all extend definitions are combined.

def sdl = '''
directive @schemaDirective on SCHEMA

"""
My schema block description
"""
schema {
mutation: MyMutation
}

extend schema @schemaDirective {
query: MyQuery
}

type MyQuery {
foo: String
}

type MyMutation {
pizza: String
}
'''

when:
def runtimeWiring = newRuntimeWiring().build()

def options = SchemaGenerator.Options.defaultOptions()
def types = new SchemaParser().parse(sdl)
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(options, types, runtimeWiring)

def printOptions = defaultOptions()
.useAstDefinitions(false)
.includeSchemaDefinition(true)
def result = new SchemaPrinter(printOptions).print(schema)

then:
result == '''"My schema block description"
schema @schemaDirective{
query: MyQuery
mutation: MyMutation
}

"Marks the field, argument, input field or enum value as deprecated"
directive @deprecated(
"The reason for the deprecation"
reason: String = "No longer supported"
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION

"Directs the executor to include this field or fragment only when the `if` argument is true"
directive @include(
"Included when true."
if: Boolean!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Indicates an Input Object is a OneOf Input Object."
directive @oneOf on INPUT_OBJECT

directive @schemaDirective on SCHEMA

"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(
"Skipped when true."
if: Boolean!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Exposes a URL that specifies the behaviour of this scalar."
directive @specifiedBy(
"The URL that specifies the behaviour of this scalar."
url: String!
) on SCALAR

type MyMutation {
pizza: String
}

type MyQuery {
foo: String
}
'''
}

def "can print a schema as AST elements"() {
def sdl = '''
directive @directive1 on SCALAR

type Query {
foo : String
}
Expand Down