From 3e6a130baaf4b538fb0e188830d2a4aadb625616 Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 00:21:44 -0700 Subject: [PATCH 01/10] Bump graphql-java to v17.3 in pom.xml Co-authored-by: Martin Bonnin --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a7e87aef..23b1e9e6 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ --> false 2.7 - 16.1 + 17.3 11.0.0 1.8 17.0.0 From d4fefe859da6ababfee9b8b987b30db74eb32503 Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 00:24:21 -0700 Subject: [PATCH 02/10] Remove old FederationSdlPrinter Co-authored-by: Martin Bonnin --- .../graphqljava/FederationSdlPrinter.java | 1303 ----------------- 1 file changed, 1303 deletions(-) delete mode 100644 graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java diff --git a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java deleted file mode 100644 index 739ffe50..00000000 --- a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java +++ /dev/null @@ -1,1303 +0,0 @@ -package com.apollographql.federation.graphqljava; - -import static graphql.Directives.DeprecatedDirective; -import static graphql.introspection.Introspection.DirectiveLocation.ENUM_VALUE; -import static graphql.introspection.Introspection.DirectiveLocation.FIELD_DEFINITION; -import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY; -import static graphql.util.EscapeUtil.escapeJsonString; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; - -import graphql.Assert; -import graphql.PublicApi; -import graphql.language.AstPrinter; -import graphql.language.AstValueHelper; -import graphql.language.Description; -import graphql.language.Document; -import graphql.language.EnumTypeDefinition; -import graphql.language.EnumValueDefinition; -import graphql.language.FieldDefinition; -import graphql.language.InputObjectTypeDefinition; -import graphql.language.InputValueDefinition; -import graphql.language.InterfaceTypeDefinition; -import graphql.language.ObjectTypeDefinition; -import graphql.language.ScalarTypeDefinition; -import graphql.language.TypeDefinition; -import graphql.language.UnionTypeDefinition; -import graphql.schema.DefaultGraphqlTypeComparatorRegistry; -import graphql.schema.GraphQLArgument; -import graphql.schema.GraphQLDirective; -import graphql.schema.GraphQLEnumType; -import graphql.schema.GraphQLEnumValueDefinition; -import graphql.schema.GraphQLFieldDefinition; -import graphql.schema.GraphQLInputObjectField; -import graphql.schema.GraphQLInputObjectType; -import graphql.schema.GraphQLInputType; -import graphql.schema.GraphQLInterfaceType; -import graphql.schema.GraphQLNamedOutputType; -import graphql.schema.GraphQLNamedType; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLOutputType; -import graphql.schema.GraphQLScalarType; -import graphql.schema.GraphQLSchema; -import graphql.schema.GraphQLSchemaElement; -import graphql.schema.GraphQLType; -import graphql.schema.GraphQLTypeUtil; -import graphql.schema.GraphQLUnionType; -import graphql.schema.GraphqlTypeComparatorEnvironment; -import graphql.schema.GraphqlTypeComparatorRegistry; -import graphql.schema.idl.ScalarInfo; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; -import graphql.schema.idl.UnExecutableSchemaGenerator; -import graphql.schema.visibility.GraphqlFieldVisibility; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** This can print an in memory GraphQL schema back to a logical schema definition */ -@PublicApi -public class FederationSdlPrinter { - // - // we use this so that we get the simple "@deprecated" as text and not a full exploded - // text with arguments (but only when we auto add this) - // - private static final GraphQLDirective DeprecatedDirective4Printing = - GraphQLDirective.newDirective() - .name("deprecated") - .validLocations(FIELD_DEFINITION, ENUM_VALUE) - .build(); - - /** Options to use when printing a schema */ - public static class Options { - - private final boolean includeIntrospectionTypes; - - private final boolean includeScalars; - - private final boolean useAstDefinitions; - - private final boolean includeSchemaDefinition; - - private final boolean includeDirectiveDefinitions; - - private final boolean descriptionsAsHashComments; - - private final Predicate includeDirective; - - private final Predicate includeDirectiveDefinition; - - private final Predicate includeTypeDefinition; - - private final Predicate includeSchemaElement; - - private final GraphqlTypeComparatorRegistry comparatorRegistry; - - private Options( - boolean includeIntrospectionTypes, - boolean includeScalars, - boolean includeSchemaDefinition, - boolean includeDirectiveDefinitions, - boolean useAstDefinitions, - boolean descriptionsAsHashComments, - Predicate includeDirective, - Predicate includeDirectiveDefinition, - Predicate includeTypeDefinition, - Predicate includeSchemaElement, - GraphqlTypeComparatorRegistry comparatorRegistry) { - this.includeIntrospectionTypes = includeIntrospectionTypes; - this.includeScalars = includeScalars; - this.includeSchemaDefinition = includeSchemaDefinition; - this.includeDirectiveDefinitions = includeDirectiveDefinitions; - this.includeDirective = includeDirective; - this.includeDirectiveDefinition = includeDirectiveDefinition; - this.includeTypeDefinition = includeTypeDefinition; - this.useAstDefinitions = useAstDefinitions; - this.descriptionsAsHashComments = descriptionsAsHashComments; - this.comparatorRegistry = comparatorRegistry; - this.includeSchemaElement = includeSchemaElement; - } - - public boolean isIncludeIntrospectionTypes() { - return includeIntrospectionTypes; - } - - public boolean isIncludeScalars() { - return includeScalars; - } - - public boolean isIncludeSchemaDefinition() { - return includeSchemaDefinition; - } - - public boolean isIncludeDirectiveDefinitions() { - return includeDirectiveDefinitions; - } - - public Predicate getIncludeDirective() { - return includeDirective; - } - - public Predicate getIncludeDirectiveDefinition() { - return includeDirectiveDefinition; - } - - public Predicate getIncludeTypeDefinition() { - return includeTypeDefinition; - } - - public Predicate getIncludeSchemaElement() { - return includeSchemaElement; - } - - public boolean isDescriptionsAsHashComments() { - return descriptionsAsHashComments; - } - - public GraphqlTypeComparatorRegistry getComparatorRegistry() { - return comparatorRegistry; - } - - public boolean isUseAstDefinitions() { - return useAstDefinitions; - } - - public static Options defaultOptions() { - return new Options( - false, - true, - false, - true, - false, - false, - directive -> true, - directiveDefinition -> true, - typeDefinition -> true, - element -> true, - DefaultGraphqlTypeComparatorRegistry.defaultComparators()); - } - - /** - * This will allow you to include introspection types that are contained in a schema - * - * @param flag whether to include them - * @return options - */ - public Options includeIntrospectionTypes(boolean flag) { - return new Options( - flag, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - this.includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * This will allow you to include scalar types that are contained in a schema - * - * @param flag whether to include them - * @return options - */ - public Options includeScalarTypes(boolean flag) { - return new Options( - this.includeIntrospectionTypes, - flag, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - this.includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * This will force the printing of the graphql schema definition even if the query, mutation, - * and/or subscription types use the default names. Some graphql parsers require this - * information even if the schema uses the default type names. The schema definition will always - * be printed if any of the query, mutation, or subscription types do not use the default names. - * - * @param flag whether to force include the schema definition - * @return options - */ - public Options includeSchemaDefinition(boolean flag) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - flag, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - this.includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * This flag controls whether schema printer will include directive definitions at the top of - * the schema, but does not remove them from the field or type usage. - * - *

In some schema defintions, like Apollo Federation, the schema should be printed without - * the directive definitions. This simplified schema is returned by a GraphQL query to other - * services, in a format that is different that the introspection query. - * - *

On by default. - * - * @param flag whether to print directive definitions - * @return new instance of options - */ - public Options includeDirectiveDefinitions(boolean flag) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - flag, - this.useAstDefinitions, - this.descriptionsAsHashComments, - this.includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * Allow to print directives. In some situations, auto-generated schemas contain a lot of - * directives that make the printout noisy and having this flag would allow cleaner printout. On - * by default. - * - * @param flag whether to print directives - * @return new instance of options - */ - public Options includeDirectives(boolean flag) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - directive -> flag, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * This is a Predicate that decides whether a directive element is printed. - * - * @param includeDirective the predicate to decide of a directive is printed - * @return new instance of options - */ - public Options includeDirectives(Predicate includeDirective) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * Filter printing of directive definitions. In Apollo Federation, some directive definitions - * need to be hidden, and this predicate allows filtering out such definitions. Prints all - * definitions by default. Note that this predicate, the predicate in {@link - * #includeDirectives(Predicate)}, the predicate in {@link #includeSchemaElement(Predicate)}, - * and the boolean in {@link #includeDirectiveDefinitions(boolean)} must be true for a - * definition to be printed. - * - * @param includeDirectiveDefinition returns true if the definition should be printed - * @return new instance of options - */ - public Options includeDirectiveDefinitions( - Predicate includeDirectiveDefinition) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - this.includeDirective, - includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * Filter printing of type definitions. In Apollo Federation, some type definitions need to be - * hidden, and this predicate allows filtering out such definitions. Prints all definitions by - * default. Note that this predicate and the predicate in {@link - * #includeSchemaElement(Predicate)} must be true for a definition to be printed. - * - * @param includeTypeDefinition returns true if the definition should be printed - * @return new instance of options - */ - public Options includeTypeDefinitions(Predicate includeTypeDefinition) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - this.includeDirective, - this.includeDirectiveDefinition, - includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * This is a general purpose Predicate that decides whether a schema element is printed ever. - * - * @param includeSchemaElement the predicate to decide of a schema is printed - * @return new instance of options - */ - public Options includeSchemaElement(Predicate includeSchemaElement) { - Assert.assertNotNull(includeSchemaElement); - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - this.includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - includeSchemaElement, - this.comparatorRegistry); - } - - /** - * This flag controls whether schema printer will use the {@link graphql.schema.GraphQLType}'s - * original Ast {@link graphql.language.TypeDefinition}s when printing the type. This allows - * access to any `extend type` declarations that might have been originally made. - * - * @param flag whether to print via AST type definitions - * @return new instance of options - */ - public Options useAstDefinitions(boolean flag) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - flag, - this.descriptionsAsHashComments, - this.includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * Descriptions are defined as preceding string literals, however an older legacy versions of - * SDL supported preceding '#' comments as descriptions. Set this to true to enable this - * deprecated behavior. This option is provided to ease adoption and may be removed in future - * versions. - * - * @param flag whether to print description as # comments - * @return new instance of options - */ - public Options descriptionsAsHashComments(boolean flag) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - flag, - this.includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - this.comparatorRegistry); - } - - /** - * The comparator registry controls the printing order for registered {@code GraphQLType}s. - * - *

The default is to sort elements by name but you can put in your own code to decide on the - * field order - * - * @param comparatorRegistry The registry containing the {@code Comparator} and environment - * scoping rules. - * @return options - */ - public Options setComparators(GraphqlTypeComparatorRegistry comparatorRegistry) { - return new Options( - this.includeIntrospectionTypes, - this.includeScalars, - this.includeSchemaDefinition, - this.includeDirectiveDefinitions, - this.useAstDefinitions, - this.descriptionsAsHashComments, - this.includeDirective, - this.includeDirectiveDefinition, - this.includeTypeDefinition, - this.includeSchemaElement, - comparatorRegistry); - } - } - - private final Map, TypePrinter> printers = new LinkedHashMap<>(); - - private final Options options; - - public FederationSdlPrinter() { - this(Options.defaultOptions()); - } - - public FederationSdlPrinter(Options options) { - this.options = options; - printers.put(GraphQLSchema.class, schemaPrinter()); - printers.put(GraphQLObjectType.class, objectPrinter()); - printers.put(GraphQLEnumType.class, enumPrinter()); - printers.put(GraphQLScalarType.class, scalarPrinter()); - printers.put(GraphQLInterfaceType.class, interfacePrinter()); - printers.put(GraphQLUnionType.class, unionPrinter()); - printers.put(GraphQLInputObjectType.class, inputObjectPrinter()); - } - - /** - * 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 schema) then - * use {@link - * graphql.introspection.IntrospectionResultToSchema#createSchemaDefinition(java.util.Map)} first - * to get the {@link graphql.language.Document} and then print that. - * - * @param schemaIDL the parsed schema IDL - * @return the logical schema definition - */ - public String print(Document schemaIDL) { - TypeDefinitionRegistry registry = new SchemaParser().buildRegistry(schemaIDL); - return print(UnExecutableSchemaGenerator.makeUnExecutableSchema(registry)); - } - - /** - * This can print an in memory GraphQL schema back to a logical schema definition - * - * @param schema the schema in play - * @return the logical schema definition - */ - public String print(GraphQLSchema schema) { - StringWriter sw = new StringWriter(); - PrintWriter out = new PrintWriter(sw); - - GraphqlFieldVisibility visibility = schema.getCodeRegistry().getFieldVisibility(); - - printer(schema.getClass()).print(out, schema, visibility); - - List typesAsList = - schema.getAllTypesAsList().stream() - .sorted(Comparator.comparing(GraphQLNamedType::getName)) - .filter(options.getIncludeTypeDefinition()) - .collect(toList()); - - printType(out, typesAsList, GraphQLInterfaceType.class, visibility); - printType(out, typesAsList, GraphQLUnionType.class, visibility); - printType(out, typesAsList, GraphQLObjectType.class, visibility); - printType(out, typesAsList, GraphQLEnumType.class, visibility); - printType(out, typesAsList, GraphQLScalarType.class, visibility); - printType(out, typesAsList, GraphQLInputObjectType.class, visibility); - - String result = sw.toString(); - if (result.endsWith("\n\n")) { - result = result.substring(0, result.length() - 1); - } - return result; - } - - private interface TypePrinter { - - void print(PrintWriter out, T type, GraphqlFieldVisibility visibility); - } - - private boolean isIntrospectionType(GraphQLNamedType type) { - return !options.isIncludeIntrospectionTypes() && type.getName().startsWith("__"); - } - - private TypePrinter scalarPrinter() { - return (out, type, visibility) -> { - if (!options.isIncludeScalars()) { - return; - } - boolean printScalar; - if (ScalarInfo.isGraphqlSpecifiedScalar(type)) { - printScalar = false; - //noinspection RedundantIfStatement - if (!ScalarInfo.isGraphqlSpecifiedScalar(type)) { - printScalar = true; - } - } else { - printScalar = true; - } - if (printScalar) { - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - out.format( - "scalar %s%s\n\n", - type.getName(), directivesString(GraphQLScalarType.class, type.getDirectives())); - } - } - }; - } - - private TypePrinter enumPrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLEnumType.class) - .elementType(GraphQLEnumValueDefinition.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - out.format( - "enum %s%s", - type.getName(), directivesString(GraphQLEnumType.class, type.getDirectives())); - List values = - type.getValues().stream().sorted(comparator).collect(toList()); - if (values.size() > 0) { - out.format(" {\n"); - for (GraphQLEnumValueDefinition enumValueDefinition : values) { - printComments(out, enumValueDefinition, " "); - List enumValueDirectives = enumValueDefinition.getDirectives(); - if (enumValueDefinition.isDeprecated()) { - enumValueDirectives = addDeprecatedDirectiveIfNeeded(enumValueDirectives); - } - out.format( - " %s%s\n", - enumValueDefinition.getName(), - directivesString(GraphQLEnumValueDefinition.class, enumValueDirectives)); - } - out.format("}"); - } - out.format("\n\n"); - } - }; - } - - private void printFieldDefinitions( - PrintWriter out, - Comparator comparator, - List fieldDefinitions) { - if (fieldDefinitions.size() == 0) { - return; - } - - out.format(" {\n"); - fieldDefinitions.stream() - .filter(options.getIncludeSchemaElement()) - .sorted(comparator) - .forEach( - fd -> { - printComments(out, fd, " "); - List fieldDirectives = fd.getDirectives(); - if (fd.isDeprecated()) { - fieldDirectives = addDeprecatedDirectiveIfNeeded(fieldDirectives); - } - - out.format( - " %s%s: %s%s\n", - fd.getName(), - argsString(GraphQLFieldDefinition.class, fd.getArguments()), - typeString(fd.getType()), - directivesString(GraphQLFieldDefinition.class, fieldDirectives)); - }); - out.format("}"); - } - - private TypePrinter interfacePrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } - - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - if (type.getInterfaces().isEmpty()) { - out.format( - "interface %s%s", - type.getName(), directivesString(GraphQLInterfaceType.class, type.getDirectives())); - } else { - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLInterfaceType.class) - .elementType(GraphQLOutputType.class) - .build(); - Comparator implementsComparator = - options.comparatorRegistry.getComparator(environment); - - Stream interfaceNames = - type.getInterfaces().stream() - .sorted(implementsComparator) - .map(GraphQLNamedType::getName); - out.format( - "interface %s implements %s%s", - type.getName(), - interfaceNames.collect(joining(" & ")), - directivesString(GraphQLInterfaceType.class, type.getDirectives())); - } - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLInterfaceType.class) - .elementType(GraphQLFieldDefinition.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type)); - out.format("\n\n"); - } - }; - } - - private TypePrinter unionPrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLUnionType.class) - .elementType(GraphQLOutputType.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - out.format( - "union %s%s = ", - type.getName(), directivesString(GraphQLUnionType.class, type.getDirectives())); - List types = - type.getTypes().stream().sorted(comparator).collect(toList()); - for (int i = 0; i < types.size(); i++) { - GraphQLNamedOutputType objectType = types.get(i); - if (i > 0) { - out.format(" | "); - } - out.format("%s", objectType.getName()); - } - out.format("\n\n"); - } - }; - } - - private TypePrinter objectPrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - if (type.getInterfaces().isEmpty()) { - out.format( - "type %s%s", - type.getName(), directivesString(GraphQLObjectType.class, type.getDirectives())); - } else { - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLObjectType.class) - .elementType(GraphQLOutputType.class) - .build(); - Comparator implementsComparator = - options.comparatorRegistry.getComparator(environment); - - Stream interfaceNames = - type.getInterfaces().stream() - .sorted(implementsComparator) - .map(GraphQLNamedType::getName); - out.format( - "type %s implements %s%s", - type.getName(), - interfaceNames.collect(joining(" & ")), - directivesString(GraphQLObjectType.class, type.getDirectives())); - } - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLObjectType.class) - .elementType(GraphQLFieldDefinition.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type)); - out.format("\n\n"); - } - }; - } - - private TypePrinter inputObjectPrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLInputObjectType.class) - .elementType(GraphQLInputObjectField.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - out.format( - "input %s%s", - type.getName(), directivesString(GraphQLInputObjectType.class, type.getDirectives())); - List inputObjectFields = visibility.getFieldDefinitions(type); - if (inputObjectFields.size() > 0) { - out.format(" {\n"); - inputObjectFields.stream() - .filter(options.getIncludeSchemaElement()) - .sorted(comparator) - .forEach( - fd -> { - printComments(out, fd, " "); - out.format(" %s: %s", fd.getName(), typeString(fd.getType())); - Object defaultValue = fd.getDefaultValue(); - if (defaultValue != null) { - String astValue = printAst(defaultValue, fd.getType()); - out.format(" = %s", astValue); - } - out.format(directivesString(GraphQLInputObjectField.class, fd.getDirectives())); - out.format("\n"); - }); - out.format("}"); - } - out.format("\n\n"); - } - }; - } - - /** - * This will return true if the options say to use the AST and we have an AST element - * - * @param definition the AST type definition - * @return true if we should print using AST nodes - */ - private boolean shouldPrintAsAst(TypeDefinition definition) { - return options.isUseAstDefinitions() && definition != null; - } - - /** - * 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)} - * - * @param out the output writer - * @param definition the AST type definition - * @param extensions a list of type definition extensions - */ - private void printAsAst( - PrintWriter out, TypeDefinition definition, List> extensions) { - out.printf("%s\n", AstPrinter.printAst(definition)); - if (extensions != null) { - for (TypeDefinition extension : extensions) { - out.printf("\n%s\n", AstPrinter.printAst(extension)); - } - } - out.println(); - } - - private static String printAst(Object value, GraphQLInputType type) { - return AstPrinter.printAst(AstValueHelper.astFromValue(value, type)); - } - - private TypePrinter schemaPrinter() { - return (out, schema, visibility) -> { - GraphQLObjectType queryType = schema.getQueryType(); - GraphQLObjectType mutationType = schema.getMutationType(); - 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. - boolean needsSchemaPrinted = options.isIncludeSchemaDefinition(); - - if (!needsSchemaPrinted) { - if (queryType != null && !queryType.getName().equals("Query")) { - needsSchemaPrinted = true; - } - if (mutationType != null && !mutationType.getName().equals("Mutation")) { - needsSchemaPrinted = true; - } - if (subscriptionType != null && !subscriptionType.getName().equals("Subscription")) { - needsSchemaPrinted = true; - } - } - - if (needsSchemaPrinted) { - out.format("schema {\n"); - 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"); - } - - if (options.isIncludeDirectiveDefinitions()) { - List directives = getSchemaDirectives(schema); - if (!directives.isEmpty()) { - out.format("%s", directiveDefinitions(directives)); - } - } - }; - } - - private List getSchemaDirectives(GraphQLSchema schema) { - return schema.getDirectives().stream() - .filter(options.getIncludeDirective()) - .filter(options.getIncludeSchemaElement()) - .filter(options.getIncludeDirectiveDefinition()) - .collect(toList()); - } - - String typeString(GraphQLType rawType) { - return GraphQLTypeUtil.simplePrint(rawType); - } - - String argsString(List arguments) { - return argsString(null, arguments); - } - - String argsString(Class parent, List arguments) { - boolean hasDescriptions = arguments.stream().anyMatch(this::hasDescription); - String halfPrefix = hasDescriptions ? " " : ""; - String prefix = hasDescriptions ? " " : ""; - int count = 0; - StringBuilder sb = new StringBuilder(); - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(parent) - .elementType(GraphQLArgument.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - arguments = - arguments.stream() - .sorted(comparator) - .filter(options.getIncludeSchemaElement()) - .collect(toList()); - for (GraphQLArgument argument : arguments) { - if (count == 0) { - sb.append("("); - } else { - sb.append(", "); - } - if (hasDescriptions) { - sb.append("\n"); - } - sb.append(printComments(argument, prefix)); - - sb.append(prefix) - .append(argument.getName()) - .append(": ") - .append(typeString(argument.getType())); - Object defaultValue = argument.getDefaultValue(); - if (defaultValue != null) { - sb.append(" = "); - sb.append(printAst(defaultValue, argument.getType())); - } - - argument.getDirectives().stream() - .filter(options.getIncludeSchemaElement()) - .map(this::directiveString) - .filter(it -> !it.isEmpty()) - .forEach(directiveString -> sb.append(" ").append(directiveString)); - - count++; - } - if (count > 0) { - if (hasDescriptions) { - sb.append("\n"); - } - sb.append(halfPrefix).append(")"); - } - return sb.toString(); - } - - String directivesString( - Class parent, List directives) { - directives = - directives.stream() - // @deprecated is special - we always print it if something is deprecated - .filter( - directive -> - options.getIncludeDirective().test(directive) - || isDeprecatedDirective(directive)) - .filter(options.getIncludeSchemaElement()) - .collect(toList()); - - if (directives.isEmpty()) { - return ""; - } - StringBuilder sb = new StringBuilder(); - sb.append(" "); - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(parent) - .elementType(GraphQLDirective.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - directives = directives.stream().sorted(comparator).collect(toList()); - for (int i = 0; i < directives.size(); i++) { - GraphQLDirective directive = directives.get(i); - sb.append(directiveString(directive)); - if (i < directives.size() - 1) { - sb.append(" "); - } - } - return sb.toString(); - } - - private String directiveString(GraphQLDirective directive) { - if (!options.getIncludeSchemaElement().test(directive)) { - return ""; - } - if (!options.getIncludeDirective().test(directive)) { - // @deprecated is special - we always print it if something is deprecated - if (!isDeprecatedDirective(directive)) { - return ""; - } - } - - StringBuilder sb = new StringBuilder(); - sb.append("@").append(directive.getName()); - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLDirective.class) - .elementType(GraphQLArgument.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - List args = directive.getArguments(); - args = args.stream().sorted(comparator).collect(toList()); - if (!args.isEmpty()) { - sb.append("("); - for (int i = 0; i < args.size(); i++) { - GraphQLArgument arg = args.get(i); - String argValue = null; - if (arg.getValue() != null) { - argValue = printAst(arg.getValue(), arg.getType()); - } else if (arg.getDefaultValue() != null) { - argValue = printAst(arg.getDefaultValue(), arg.getType()); - } - if (!isNullOrEmpty(argValue)) { - sb.append(arg.getName()); - sb.append(" : "); - sb.append(argValue); - if (i < args.size() - 1) { - sb.append(", "); - } - } - } - sb.append(")"); - } - return sb.toString(); - } - - private boolean isDeprecatedDirective(GraphQLDirective directive) { - return directive.getName().equals(DeprecatedDirective.getName()); - } - - private boolean hasDeprecatedDirective(List directives) { - return directives.stream().filter(this::isDeprecatedDirective).count() == 1; - } - - private List addDeprecatedDirectiveIfNeeded(List directives) { - if (!hasDeprecatedDirective(directives)) { - directives = new ArrayList<>(directives); - directives.add(DeprecatedDirective4Printing); - } - return directives; - } - - private String directiveDefinitions(List directives) { - StringBuilder sb = new StringBuilder(); - directives.stream() - .filter(options.getIncludeSchemaElement()) - .forEach( - directive -> { - sb.append(directiveDefinition(directive)); - sb.append("\n\n"); - }); - return sb.toString(); - } - - private String directiveDefinition(GraphQLDirective directive) { - StringBuilder sb = new StringBuilder(); - - StringWriter sw = new StringWriter(); - printComments(new PrintWriter(sw), directive, ""); - - sb.append(sw.toString()); - - sb.append("directive @").append(directive.getName()); - - GraphqlTypeComparatorEnvironment environment = - GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLDirective.class) - .elementType(GraphQLArgument.class) - .build(); - Comparator comparator = - options.comparatorRegistry.getComparator(environment); - - List args = directive.getArguments(); - args = - args.stream() - .filter(options.getIncludeSchemaElement()) - .sorted(comparator) - .collect(toList()); - - sb.append(argsString(GraphQLDirective.class, args)); - - if (directive.isRepeatable()) { - sb.append(" repeatable"); - } - - sb.append(" on "); - - String locations = - directive.validLocations().stream().map(Enum::name).collect(Collectors.joining(" | ")); - sb.append(locations); - - return sb.toString(); - } - - @SuppressWarnings("unchecked") - private TypePrinter printer(Class clazz) { - TypePrinter typePrinter = printers.get(clazz); - if (typePrinter == null) { - Class superClazz = clazz.getSuperclass(); - if (superClazz != Object.class) { - typePrinter = printer(superClazz); - } else { - typePrinter = (out, type, visibility) -> out.println("Type not implemented : " + type); - } - printers.put(clazz, typePrinter); - } - return (TypePrinter) typePrinter; - } - - public String print(GraphQLType type) { - StringWriter sw = new StringWriter(); - PrintWriter out = new PrintWriter(sw); - - printType(out, type, DEFAULT_FIELD_VISIBILITY); - - return sw.toString(); - } - - private void printType( - PrintWriter out, - List typesAsList, - Class typeClazz, - GraphqlFieldVisibility visibility) { - typesAsList.stream() - .filter(type -> typeClazz.isAssignableFrom(type.getClass())) - .filter(type -> options.getIncludeSchemaElement().test(type)) - .forEach(type -> printType(out, type, visibility)); - } - - private void printType(PrintWriter out, GraphQLType type, GraphqlFieldVisibility visibility) { - TypePrinter printer = printer(type.getClass()); - printer.print(out, type, visibility); - } - - private String printComments(Object graphQLType, String prefix) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - printComments(pw, graphQLType, prefix); - return sw.toString(); - } - - private void printComments(PrintWriter out, Object graphQLType, String prefix) { - - String descriptionText = getDescription(graphQLType); - if (isNullOrEmpty(descriptionText)) { - return; - } - - if (!isNullOrEmpty(descriptionText)) { - List lines = Arrays.asList(descriptionText.split("\n")); - if (options.isDescriptionsAsHashComments()) { - printMultiLineHashDescription(out, prefix, lines); - } else if (!lines.isEmpty()) { - if (lines.size() > 1) { - printMultiLineDescription(out, prefix, lines); - } else { - printSingleLineDescription(out, prefix, lines.get(0)); - } - } - } - } - - private void printMultiLineHashDescription(PrintWriter out, String prefix, List lines) { - lines.forEach(l -> out.printf("%s#%s\n", prefix, l)); - } - - private void printMultiLineDescription(PrintWriter out, String prefix, List lines) { - out.printf("%s\"\"\"\n", prefix); - lines.forEach(l -> out.printf("%s%s\n", prefix, l)); - out.printf("%s\"\"\"\n", prefix); - } - - private void printSingleLineDescription(PrintWriter out, String prefix, String s) { - // See: https://github.com/graphql/graphql-spec/issues/148 - String desc = escapeJsonString(s); - out.printf("%s\"%s\"\n", prefix, desc); - } - - private boolean hasDescription(Object descriptionHolder) { - String description = getDescription(descriptionHolder); - return !isNullOrEmpty(description); - } - - private String getDescription(Object descriptionHolder) { - if (descriptionHolder instanceof GraphQLObjectType) { - GraphQLObjectType type = (GraphQLObjectType) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()).map(ObjectTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLEnumType) { - GraphQLEnumType type = (GraphQLEnumType) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()).map(EnumTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLFieldDefinition) { - GraphQLFieldDefinition type = (GraphQLFieldDefinition) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()).map(FieldDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLEnumValueDefinition) { - GraphQLEnumValueDefinition type = (GraphQLEnumValueDefinition) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()).map(EnumValueDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLUnionType) { - GraphQLUnionType type = (GraphQLUnionType) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()).map(UnionTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLInputObjectType) { - GraphQLInputObjectType type = (GraphQLInputObjectType) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()) - .map(InputObjectTypeDefinition::getDescription) - .orElse(null)); - } else if (descriptionHolder instanceof GraphQLInputObjectField) { - GraphQLInputObjectField type = (GraphQLInputObjectField) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()).map(InputValueDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLInterfaceType) { - GraphQLInterfaceType type = (GraphQLInterfaceType) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()) - .map(InterfaceTypeDefinition::getDescription) - .orElse(null)); - } else if (descriptionHolder instanceof GraphQLScalarType) { - GraphQLScalarType type = (GraphQLScalarType) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()).map(ScalarTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLArgument) { - GraphQLArgument type = (GraphQLArgument) descriptionHolder; - return description( - type.getDescription(), - ofNullable(type.getDefinition()).map(InputValueDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLDirective) { - GraphQLDirective type = (GraphQLDirective) descriptionHolder; - return description(type.getDescription(), null); - } else { - return Assert.assertShouldNeverHappen(); - } - } - - String description(String runtimeDescription, Description descriptionAst) { - // - // 95% of the time if the schema was built from SchemaGenerator then the runtime description is - // the only description - // So the other code here is a really defensive way to get the description - // - String descriptionText = runtimeDescription; - if (isNullOrEmpty(descriptionText)) { - if (descriptionAst != null) { - descriptionText = descriptionAst.getContent(); - } - } - return descriptionText; - } - - private static boolean isNullOrEmpty(String s) { - return s == null || s.isEmpty(); - } -} From 969ecb4b4d6ca2e0f0083a072bd74d3d125c2902 Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 00:35:16 -0700 Subject: [PATCH 03/10] Copy SchemaPrinter v17.3 and change name to FederationSdlPrinter Co-authored-by: Martin Bonnin --- .../graphqljava/FederationSdlPrinter.java | 1078 +++++++++++++++++ 1 file changed, 1078 insertions(+) create mode 100644 graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java diff --git a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java new file mode 100644 index 00000000..217190bd --- /dev/null +++ b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java @@ -0,0 +1,1078 @@ +package com.apollographql.federation.graphqljava; + +import static com.google.common.base.Predicates.not; +import static graphql.Directives.DeprecatedDirective; +import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION; +import static graphql.introspection.Introspection.DirectiveLocation.ENUM_VALUE; +import static graphql.introspection.Introspection.DirectiveLocation.FIELD_DEFINITION; +import static graphql.introspection.Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION; +import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY; +import static graphql.util.EscapeUtil.escapeJsonString; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import graphql.Assert; +import graphql.PublicApi; +import graphql.execution.ValuesResolver; +import graphql.language.AstPrinter; +import graphql.language.Description; +import graphql.language.Document; +import graphql.language.EnumTypeDefinition; +import graphql.language.EnumValueDefinition; +import graphql.language.FieldDefinition; +import graphql.language.InputObjectTypeDefinition; +import graphql.language.InputValueDefinition; +import graphql.language.InterfaceTypeDefinition; +import graphql.language.ObjectTypeDefinition; +import graphql.language.ScalarTypeDefinition; +import graphql.language.TypeDefinition; +import graphql.language.UnionTypeDefinition; +import graphql.schema.DefaultGraphqlTypeComparatorRegistry; +import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLEnumValueDefinition; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLNamedOutputType; +import graphql.schema.GraphQLNamedType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLOutputType; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLType; +import graphql.schema.GraphQLTypeUtil; +import graphql.schema.GraphQLUnionType; +import graphql.schema.GraphqlTypeComparatorEnvironment; +import graphql.schema.GraphqlTypeComparatorRegistry; +import graphql.schema.InputValueWithState; +import graphql.schema.idl.DirectiveInfo; +import graphql.schema.idl.ScalarInfo; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import graphql.schema.idl.UnExecutableSchemaGenerator; +import graphql.schema.visibility.GraphqlFieldVisibility; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * This can print an in memory GraphQL schema back to a logical schema definition + */ +@PublicApi +public class FederationSdlPrinter { + // + // we use this so that we get the simple "@deprecated" as text and not a full exploded + // text with arguments (but only when we auto add this) + // + private static final GraphQLDirective DeprecatedDirective4Printing = GraphQLDirective.newDirective() + .name("deprecated") + .validLocations(FIELD_DEFINITION, ENUM_VALUE, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITION) + .build(); + + /** + * This predicate excludes all directives which are specified bt the GraphQL Specification. + * Printing these directives is optional. + */ + public static final Predicate ExcludeGraphQLSpecifiedDirectivesPredicate = not( + DirectiveInfo::isGraphqlSpecifiedDirective); + + /** + * Options to use when printing a schema + */ + public static class Options { + + private final boolean includeIntrospectionTypes; + + private final boolean includeScalars; + + private final boolean useAstDefinitions; + + private final boolean includeSchemaDefinition; + + private final boolean includeDirectiveDefinitions; + + private final boolean descriptionsAsHashComments; + + private final Predicate includeDirective; + + private final Predicate includeSchemaElement; + + private final GraphqlTypeComparatorRegistry comparatorRegistry; + + private Options(boolean includeIntrospectionTypes, + boolean includeScalars, + boolean includeSchemaDefinition, + boolean includeDirectiveDefinitions, + boolean useAstDefinitions, + boolean descriptionsAsHashComments, + Predicate includeDirective, + Predicate includeSchemaElement, + GraphqlTypeComparatorRegistry comparatorRegistry) { + this.includeIntrospectionTypes = includeIntrospectionTypes; + this.includeScalars = includeScalars; + this.includeSchemaDefinition = includeSchemaDefinition; + this.includeDirectiveDefinitions = includeDirectiveDefinitions; + this.includeDirective = includeDirective; + this.useAstDefinitions = useAstDefinitions; + this.descriptionsAsHashComments = descriptionsAsHashComments; + this.comparatorRegistry = comparatorRegistry; + this.includeSchemaElement = includeSchemaElement; + } + + public boolean isIncludeIntrospectionTypes() { + return includeIntrospectionTypes; + } + + public boolean isIncludeScalars() { + return includeScalars; + } + + public boolean isIncludeSchemaDefinition() { + return includeSchemaDefinition; + } + + public boolean isIncludeDirectiveDefinitions() { + return includeDirectiveDefinitions; + } + + public Predicate getIncludeDirective() { + return includeDirective; + } + + public Predicate getIncludeSchemaElement() { + return includeSchemaElement; + } + + public boolean isDescriptionsAsHashComments() { + return descriptionsAsHashComments; + } + + public GraphqlTypeComparatorRegistry getComparatorRegistry() { + return comparatorRegistry; + } + + public boolean isUseAstDefinitions() { + return useAstDefinitions; + } + + public static Options defaultOptions() { + return new Options(false, true, + false, true, false, false, + directive -> true, element -> true, DefaultGraphqlTypeComparatorRegistry.defaultComparators()); + } + + /** + * This will allow you to include introspection types that are contained in a schema + * + * @param flag whether to include them + * + * @return options + */ + public Options includeIntrospectionTypes(boolean flag) { + return new Options(flag, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * This will allow you to include scalar types that are contained in a schema + * + * @param flag whether to include them + * + * @return options + */ + public Options includeScalarTypes(boolean flag) { + return new Options(this.includeIntrospectionTypes, flag, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * This will force the printing of the graphql schema definition even if the query, mutation, and/or subscription + * types use the default names. Some graphql parsers require this information even if the schema uses the + * default type names. The schema definition will always be printed if any of the query, mutation, or subscription + * types do not use the default names. + * + * @param flag whether to force include the schema definition + * + * @return options + */ + public Options includeSchemaDefinition(boolean flag) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, flag, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * This flag controls whether schema printer will include directive definitions at the top of the schema, but does not remove them from the field or type usage. + *

+ * In some schema defintions, like Apollo Federation, the schema should be printed without the directive definitions. + * This simplified schema is returned by a GraphQL query to other services, in a format that is different that the introspection query. + *

+ * On by default. + * + * @param flag whether to print directive definitions + * + * @return new instance of options + */ + public Options includeDirectiveDefinitions(boolean flag) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, flag, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * Allow to print directives. In some situations, auto-generated schemas contain a lot of directives that + * make the printout noisy and having this flag would allow cleaner printout. On by default. + * + * @param flag whether to print directives + * + * @return new instance of options + */ + public Options includeDirectives(boolean flag) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, directive -> flag, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * This is a Predicate that decides whether a directive element is printed. + * + * @param includeDirective the predicate to decide of a directive is printed + * + * @return new instance of options + */ + public Options includeDirectives(Predicate includeDirective) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, includeDirective, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * This is a general purpose Predicate that decides whether a schema element is printed ever. + * + * @param includeSchemaElement the predicate to decide of a schema is printed + * + * @return new instance of options + */ + public Options includeSchemaElement(Predicate includeSchemaElement) { + Assert.assertNotNull(includeSchemaElement); + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, includeDirective, includeSchemaElement, this.comparatorRegistry); + } + + /** + * This flag controls whether schema printer will use the {@link GraphQLType}'s original Ast {@link TypeDefinition}s when printing the type. This + * allows access to any `extend type` declarations that might have been originally made. + * + * @param flag whether to print via AST type definitions + * + * @return new instance of options + */ + public Options useAstDefinitions(boolean flag) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, flag, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * Descriptions are defined as preceding string literals, however an older legacy + * versions of SDL supported preceding '#' comments as + * descriptions. Set this to true to enable this deprecated behavior. + * This option is provided to ease adoption and may be removed in future versions. + * + * @param flag whether to print description as # comments + * + * @return new instance of options + */ + public Options descriptionsAsHashComments(boolean flag) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, flag, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * The comparator registry controls the printing order for registered {@code GraphQLType}s. + *

+ * The default is to sort elements by name but you can put in your own code to decide on the field order + * + * @param comparatorRegistry The registry containing the {@code Comparator} and environment scoping rules. + * + * @return options + */ + public Options setComparators(GraphqlTypeComparatorRegistry comparatorRegistry) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, comparatorRegistry); + } + } + + private final Map, TypePrinter> printers = new LinkedHashMap<>(); + + private final Options options; + + public FederationSdlPrinter() { + this(Options.defaultOptions()); + } + + public FederationSdlPrinter(Options options) { + this.options = options; + printers.put(GraphQLSchema.class, schemaPrinter()); + printers.put(GraphQLObjectType.class, objectPrinter()); + printers.put(GraphQLEnumType.class, enumPrinter()); + printers.put(GraphQLScalarType.class, scalarPrinter()); + printers.put(GraphQLInterfaceType.class, interfacePrinter()); + printers.put(GraphQLUnionType.class, unionPrinter()); + printers.put(GraphQLInputObjectType.class, inputObjectPrinter()); + } + + /** + * 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 + * schema) then use {@link graphql.introspection.IntrospectionResultToSchema#createSchemaDefinition(Map)} + * first to get the {@link Document} and then print that. + * + * @param schemaIDL the parsed schema IDL + * + * @return the logical schema definition + */ + public String print(Document schemaIDL) { + TypeDefinitionRegistry registry = new SchemaParser().buildRegistry(schemaIDL); + return print(UnExecutableSchemaGenerator.makeUnExecutableSchema(registry)); + } + + /** + * This can print an in memory GraphQL schema back to a logical schema definition + * + * @param schema the schema in play + * + * @return the logical schema definition + */ + public String print(GraphQLSchema schema) { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + + GraphqlFieldVisibility visibility = schema.getCodeRegistry().getFieldVisibility(); + + printer(schema.getClass()).print(out, schema, visibility); + + List typesAsList = schema.getAllTypesAsList() + .stream() + .sorted(Comparator.comparing(GraphQLNamedType::getName)) + .collect(toList()); + + printType(out, typesAsList, GraphQLInterfaceType.class, visibility); + printType(out, typesAsList, GraphQLUnionType.class, visibility); + printType(out, typesAsList, GraphQLObjectType.class, visibility); + printType(out, typesAsList, GraphQLEnumType.class, visibility); + printType(out, typesAsList, GraphQLScalarType.class, visibility); + printType(out, typesAsList, GraphQLInputObjectType.class, visibility); + + String result = sw.toString(); + if (result.endsWith("\n\n")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + private interface TypePrinter { + + void print(PrintWriter out, T type, GraphqlFieldVisibility visibility); + + } + + private boolean isIntrospectionType(GraphQLNamedType type) { + return !options.isIncludeIntrospectionTypes() && type.getName().startsWith("__"); + } + + private TypePrinter scalarPrinter() { + return (out, type, visibility) -> { + if (!options.isIncludeScalars()) { + return; + } + boolean printScalar; + if (ScalarInfo.isGraphqlSpecifiedScalar(type)) { + printScalar = false; + //noinspection RedundantIfStatement + if (!ScalarInfo.isGraphqlSpecifiedScalar(type)) { + printScalar = true; + } + } else { + printScalar = true; + } + if (printScalar) { + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + out.format("scalar %s%s\n\n", type.getName(), directivesString(GraphQLScalarType.class, type.getDirectives())); + } + } + }; + } + + + private TypePrinter enumPrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLEnumType.class) + .elementType(GraphQLEnumValueDefinition.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + out.format("enum %s%s", type.getName(), directivesString(GraphQLEnumType.class, type.getDirectives())); + List values = type.getValues() + .stream() + .sorted(comparator) + .collect(toList()); + if (values.size() > 0) { + out.format(" {\n"); + for (GraphQLEnumValueDefinition enumValueDefinition : values) { + printComments(out, enumValueDefinition, " "); + List enumValueDirectives = enumValueDefinition.getDirectives(); + if (enumValueDefinition.isDeprecated()) { + enumValueDirectives = addDeprecatedDirectiveIfNeeded(enumValueDirectives); + } + out.format(" %s%s\n", enumValueDefinition.getName(), directivesString(GraphQLEnumValueDefinition.class, enumValueDirectives)); + } + out.format("}"); + } + out.format("\n\n"); + } + }; + } + + private void printFieldDefinitions(PrintWriter out, Comparator comparator, List fieldDefinitions) { + if (fieldDefinitions.size() == 0) { + return; + } + + out.format(" {\n"); + fieldDefinitions + .stream() + .filter(options.getIncludeSchemaElement()) + .sorted(comparator) + .forEach(fd -> { + printComments(out, fd, " "); + List fieldDirectives = fd.getDirectives(); + if (fd.isDeprecated()) { + fieldDirectives = addDeprecatedDirectiveIfNeeded(fieldDirectives); + } + + out.format(" %s%s: %s%s\n", + fd.getName(), argsString(GraphQLFieldDefinition.class, fd.getArguments()), typeString(fd.getType()), + directivesString(GraphQLFieldDefinition.class, fieldDirectives)); + }); + out.format("}"); + } + + private TypePrinter interfacePrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + if (type.getInterfaces().isEmpty()) { + out.format("interface %s%s", type.getName(), directivesString(GraphQLInterfaceType.class, type.getDirectives())); + } else { + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLInterfaceType.class) + .elementType(GraphQLOutputType.class) + .build(); + Comparator implementsComparator = options.comparatorRegistry.getComparator(environment); + + Stream interfaceNames = type.getInterfaces() + .stream() + .sorted(implementsComparator) + .map(GraphQLNamedType::getName); + out.format("interface %s implements %s%s", + type.getName(), + interfaceNames.collect(joining(" & ")), + directivesString(GraphQLInterfaceType.class, type.getDirectives())); + } + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLInterfaceType.class) + .elementType(GraphQLFieldDefinition.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type)); + out.format("\n\n"); + } + }; + } + + private TypePrinter unionPrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLUnionType.class) + .elementType(GraphQLOutputType.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + out.format("union %s%s = ", type.getName(), directivesString(GraphQLUnionType.class, type.getDirectives())); + List types = type.getTypes() + .stream() + .sorted(comparator) + .collect(toList()); + for (int i = 0; i < types.size(); i++) { + GraphQLNamedOutputType objectType = types.get(i); + if (i > 0) { + out.format(" | "); + } + out.format("%s", objectType.getName()); + } + out.format("\n\n"); + } + }; + } + + private TypePrinter objectPrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + if (type.getInterfaces().isEmpty()) { + out.format("type %s%s", type.getName(), directivesString(GraphQLObjectType.class, type.getDirectives())); + } else { + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLObjectType.class) + .elementType(GraphQLOutputType.class) + .build(); + Comparator implementsComparator = options.comparatorRegistry.getComparator(environment); + + Stream interfaceNames = type.getInterfaces() + .stream() + .sorted(implementsComparator) + .map(GraphQLNamedType::getName); + out.format("type %s implements %s%s", + type.getName(), + interfaceNames.collect(joining(" & ")), + directivesString(GraphQLObjectType.class, type.getDirectives())); + } + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLObjectType.class) + .elementType(GraphQLFieldDefinition.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type)); + out.format("\n\n"); + } + }; + } + + private TypePrinter inputObjectPrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLInputObjectType.class) + .elementType(GraphQLInputObjectField.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + out.format("input %s%s", type.getName(), directivesString(GraphQLInputObjectType.class, type.getDirectives())); + List inputObjectFields = visibility.getFieldDefinitions(type); + if (inputObjectFields.size() > 0) { + out.format(" {\n"); + inputObjectFields + .stream() + .filter(options.getIncludeSchemaElement()) + .sorted(comparator) + .forEach(fd -> { + printComments(out, fd, " "); + out.format(" %s: %s", + fd.getName(), typeString(fd.getType())); + if (fd.hasSetDefaultValue()) { + InputValueWithState defaultValue = fd.getInputFieldDefaultValue(); + String astValue = printAst(defaultValue, fd.getType()); + out.format(" = %s", astValue); + } + out.format(directivesString(GraphQLInputObjectField.class, fd.getDirectives())); + out.format("\n"); + }); + out.format("}"); + } + out.format("\n\n"); + } + }; + } + + /** + * This will return true if the options say to use the AST and we have an AST element + * + * @param definition the AST type definition + * + * @return true if we should print using AST nodes + */ + private boolean shouldPrintAsAst(TypeDefinition definition) { + return options.isUseAstDefinitions() && definition != null; + } + + /** + * 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)} + * + * @param out the output writer + * @param definition the AST type definition + * @param extensions a list of type definition extensions + */ + private void printAsAst(PrintWriter out, TypeDefinition definition, List> extensions) { + out.printf("%s\n", AstPrinter.printAst(definition)); + if (extensions != null) { + for (TypeDefinition extension : extensions) { + out.printf("\n%s\n", AstPrinter.printAst(extension)); + } + } + out.println(); + } + + private static String printAst(InputValueWithState value, GraphQLInputType type) { + return AstPrinter.printAst(ValuesResolver.valueToLiteral(value, type)); + } + + private TypePrinter schemaPrinter() { + return (out, schema, visibility) -> { + List schemaDirectives = schema.getSchemaDirectives(); + GraphQLObjectType queryType = schema.getQueryType(); + GraphQLObjectType mutationType = schema.getMutationType(); + 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. + boolean needsSchemaPrinted = options.isIncludeSchemaDefinition(); + + if (!needsSchemaPrinted) { + if (queryType != null && !queryType.getName().equals("Query")) { + needsSchemaPrinted = true; + } + if (mutationType != null && !mutationType.getName().equals("Mutation")) { + needsSchemaPrinted = true; + } + if (subscriptionType != null && !subscriptionType.getName().equals("Subscription")) { + needsSchemaPrinted = true; + } + } + + if (needsSchemaPrinted) { + out.format("schema %s{\n", directivesString(GraphQLSchemaElement.class, schemaDirectives)); + 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"); + } + + if (options.isIncludeDirectiveDefinitions()) { + List directives = getSchemaDirectives(schema); + if (!directives.isEmpty()) { + out.format("%s", directiveDefinitions(directives)); + } + } + }; + } + + private List getSchemaDirectives(GraphQLSchema schema) { + return schema.getDirectives().stream() + .filter(options.getIncludeDirective()) + .filter(options.getIncludeSchemaElement()) + .collect(toList()); + } + + String typeString(GraphQLType rawType) { + return GraphQLTypeUtil.simplePrint(rawType); + } + + String argsString(List arguments) { + return argsString(null, arguments); + } + + String argsString(Class parent, List arguments) { + boolean hasDescriptions = arguments.stream().anyMatch(this::hasDescription); + String halfPrefix = hasDescriptions ? " " : ""; + String prefix = hasDescriptions ? " " : ""; + int count = 0; + StringBuilder sb = new StringBuilder(); + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(parent) + .elementType(GraphQLArgument.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + arguments = arguments + .stream() + .sorted(comparator) + .filter(options.getIncludeSchemaElement()) + .collect(toList()); + for (GraphQLArgument argument : arguments) { + if (count == 0) { + sb.append("("); + } else { + sb.append(", "); + } + if (hasDescriptions) { + sb.append("\n"); + } + sb.append(printComments(argument, prefix)); + + sb.append(prefix).append(argument.getName()).append(": ").append(typeString(argument.getType())); + if (argument.hasSetDefaultValue()) { + InputValueWithState defaultValue = argument.getArgumentDefaultValue(); + sb.append(" = "); + sb.append(printAst(defaultValue, argument.getType())); + } + + argument.getDirectives().stream() + .filter(options.getIncludeSchemaElement()) + .map(this::directiveString) + .filter(it -> !it.isEmpty()) + .forEach(directiveString -> sb.append(" ").append(directiveString)); + + count++; + } + if (count > 0) { + if (hasDescriptions) { + sb.append("\n"); + } + sb.append(halfPrefix).append(")"); + } + return sb.toString(); + } + + String directivesString(Class parent, List directives) { + directives = directives.stream() + // @deprecated is special - we always print it if something is deprecated + .filter(directive -> options.getIncludeDirective().test(directive) || isDeprecatedDirective(directive)) + .filter(options.getIncludeSchemaElement()) + .collect(toList()); + + if (directives.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + if (parent != GraphQLSchemaElement.class) { + sb.append(" "); + } + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(parent) + .elementType(GraphQLDirective.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + directives = directives + .stream() + .sorted(comparator) + .collect(toList()); + for (int i = 0; i < directives.size(); i++) { + GraphQLDirective directive = directives.get(i); + sb.append(directiveString(directive)); + if (i < directives.size() - 1) { + sb.append(" "); + } + } + return sb.toString(); + } + + private String directiveString(GraphQLDirective directive) { + if (!options.getIncludeSchemaElement().test(directive)) { + return ""; + } + if (!options.getIncludeDirective().test(directive)) { + // @deprecated is special - we always print it if something is deprecated + if (!isDeprecatedDirective(directive)) { + return ""; + } + } + + StringBuilder sb = new StringBuilder(); + sb.append("@").append(directive.getName()); + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLDirective.class) + .elementType(GraphQLArgument.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + List args = directive.getArguments(); + args = args + .stream() + .filter(arg -> arg.getArgumentValue().isSet() || arg.getArgumentDefaultValue().isSet()) + .sorted(comparator) + .collect(toList()); + if (!args.isEmpty()) { + sb.append("("); + for (int i = 0; i < args.size(); i++) { + GraphQLArgument arg = args.get(i); + String argValue = null; + if (arg.hasSetValue()) { + argValue = printAst(arg.getArgumentValue(), arg.getType()); + } else if (arg.hasSetDefaultValue()) { + argValue = printAst(arg.getArgumentDefaultValue(), arg.getType()); + } + if (!isNullOrEmpty(argValue)) { + sb.append(arg.getName()); + sb.append(" : "); + sb.append(argValue); + if (i < args.size() - 1) { + sb.append(", "); + } + } + } + sb.append(")"); + } + return sb.toString(); + } + + private boolean isDeprecatedDirective(GraphQLDirective directive) { + return directive.getName().equals(DeprecatedDirective.getName()); + } + + private boolean hasDeprecatedDirective(List directives) { + return directives.stream() + .filter(this::isDeprecatedDirective) + .count() == 1; + } + + private List addDeprecatedDirectiveIfNeeded(List directives) { + if (!hasDeprecatedDirective(directives)) { + directives = new ArrayList<>(directives); + directives.add(DeprecatedDirective4Printing); + } + return directives; + } + + private String directiveDefinitions(List directives) { + StringBuilder sb = new StringBuilder(); + directives.stream().filter(options.getIncludeSchemaElement()).forEach(directive -> { + sb.append(directiveDefinition(directive)); + sb.append("\n\n"); + }); + return sb.toString(); + } + + private String directiveDefinition(GraphQLDirective directive) { + StringBuilder sb = new StringBuilder(); + + StringWriter sw = new StringWriter(); + printComments(new PrintWriter(sw), directive, ""); + + sb.append(sw.toString()); + + sb.append("directive @").append(directive.getName()); + + GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLDirective.class) + .elementType(GraphQLArgument.class) + .build(); + Comparator comparator = options.comparatorRegistry.getComparator(environment); + + List args = directive.getArguments(); + args = args + .stream() + .filter(options.getIncludeSchemaElement()) + .sorted(comparator) + .collect(toList()); + + sb.append(argsString(GraphQLDirective.class, args)); + + if (directive.isRepeatable()) { + sb.append(" repeatable"); + } + + sb.append(" on "); + + String locations = directive.validLocations().stream().map(Enum::name).collect(Collectors.joining(" | ")); + sb.append(locations); + + return sb.toString(); + } + + + @SuppressWarnings("unchecked") + private TypePrinter printer(Class clazz) { + TypePrinter typePrinter = printers.get(clazz); + if (typePrinter == null) { + Class superClazz = clazz.getSuperclass(); + if (superClazz != Object.class) { + typePrinter = printer(superClazz); + } else { + typePrinter = (out, type, visibility) -> out.println("Type not implemented : " + type); + } + printers.put(clazz, typePrinter); + } + return (TypePrinter) typePrinter; + } + + + public String print(GraphQLType type) { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + + printType(out, type, DEFAULT_FIELD_VISIBILITY); + + return sw.toString(); + } + + public String print(GraphQLDirective graphQLDirective) { + return directiveDefinition(graphQLDirective); + } + + private void printType(PrintWriter out, List typesAsList, Class + typeClazz, GraphqlFieldVisibility visibility) { + typesAsList.stream() + .filter(type -> typeClazz.isAssignableFrom(type.getClass())) + .filter(type -> options.getIncludeSchemaElement().test(type)) + .forEach(type -> printType(out, type, visibility)); + } + + private void printType(PrintWriter out, GraphQLType type, GraphqlFieldVisibility visibility) { + TypePrinter printer = printer(type.getClass()); + printer.print(out, type, visibility); + } + + private String printComments(Object graphQLType, String prefix) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + printComments(pw, graphQLType, prefix); + return sw.toString(); + } + + private void printComments(PrintWriter out, Object graphQLType, String prefix) { + + String descriptionText = getDescription(graphQLType); + if (isNullOrEmpty(descriptionText)) { + return; + } + + if (!isNullOrEmpty(descriptionText)) { + List lines = Arrays.asList(descriptionText.split("\n")); + if (options.isDescriptionsAsHashComments()) { + printMultiLineHashDescription(out, prefix, lines); + } else if (!lines.isEmpty()) { + if (lines.size() > 1) { + printMultiLineDescription(out, prefix, lines); + } else { + printSingleLineDescription(out, prefix, lines.get(0)); + } + } + } + } + + private void printMultiLineHashDescription(PrintWriter out, String prefix, List lines) { + lines.forEach(l -> out.printf("%s#%s\n", prefix, l)); + } + + private void printMultiLineDescription(PrintWriter out, String prefix, List lines) { + out.printf("%s\"\"\"\n", prefix); + lines.forEach(l -> { + String escapedTripleQuotes = l.replaceAll("\"\"\"", "\\\\\"\"\""); + out.printf("%s%s\n", prefix, escapedTripleQuotes); + }); + out.printf("%s\"\"\"\n", prefix); + } + + private void printSingleLineDescription(PrintWriter out, String prefix, String s) { + // See: https://github.com/graphql/graphql-spec/issues/148 + String desc = escapeJsonString(s); + out.printf("%s\"%s\"\n", prefix, desc); + } + + private boolean hasDescription(Object descriptionHolder) { + String description = getDescription(descriptionHolder); + return !isNullOrEmpty(description); + } + + private String getDescription(Object descriptionHolder) { + if (descriptionHolder instanceof GraphQLObjectType) { + GraphQLObjectType type = (GraphQLObjectType) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(ObjectTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLEnumType) { + GraphQLEnumType type = (GraphQLEnumType) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(EnumTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLFieldDefinition) { + GraphQLFieldDefinition type = (GraphQLFieldDefinition) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(FieldDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLEnumValueDefinition) { + GraphQLEnumValueDefinition type = (GraphQLEnumValueDefinition) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(EnumValueDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLUnionType) { + GraphQLUnionType type = (GraphQLUnionType) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(UnionTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLInputObjectType) { + GraphQLInputObjectType type = (GraphQLInputObjectType) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(InputObjectTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLInputObjectField) { + GraphQLInputObjectField type = (GraphQLInputObjectField) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(InputValueDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLInterfaceType) { + GraphQLInterfaceType type = (GraphQLInterfaceType) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(InterfaceTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLScalarType) { + GraphQLScalarType type = (GraphQLScalarType) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(ScalarTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLArgument) { + GraphQLArgument type = (GraphQLArgument) descriptionHolder; + return description(type.getDescription(), ofNullable(type.getDefinition()).map(InputValueDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLDirective) { + GraphQLDirective type = (GraphQLDirective) descriptionHolder; + return description(type.getDescription(), null); + } else { + return Assert.assertShouldNeverHappen(); + } + } + + String description(String runtimeDescription, Description descriptionAst) { + // + // 95% of the time if the schema was built from SchemaGenerator then the runtime description is the only description + // So the other code here is a really defensive way to get the description + // + String descriptionText = runtimeDescription; + if (isNullOrEmpty(descriptionText)) { + if (descriptionAst != null) { + descriptionText = descriptionAst.getContent(); + } + } + return descriptionText; + } + + private static boolean isNullOrEmpty(String s) { + return s == null || s.isEmpty(); + } +} From 372bd64a24975ddf010ca7bbe4cfd7dc835d06e9 Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 00:57:20 -0700 Subject: [PATCH 04/10] Fix static import of shadowed package in FederationSdlPrinter Co-authored-by: Martin Bonnin --- .../federation/graphqljava/FederationSdlPrinter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java index 217190bd..d521da83 100644 --- a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java +++ b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java @@ -1,6 +1,6 @@ package com.apollographql.federation.graphqljava; -import static com.google.common.base.Predicates.not; +import static graphql.com.google.common.base.Predicates.not; import static graphql.Directives.DeprecatedDirective; import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION; import static graphql.introspection.Introspection.DirectiveLocation.ENUM_VALUE; From 64351c09cfa04170b495f368914693804f462476 Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 01:20:14 -0700 Subject: [PATCH 05/10] Add directive definition and type definition filtering to FederationSdlPrinter Co-authored-by: Martin Bonnin --- .../graphqljava/FederationSdlPrinter.java | 69 ++++++++++++++++--- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java index d521da83..8302f921 100644 --- a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java +++ b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java @@ -109,6 +109,10 @@ public static class Options { private final Predicate includeDirective; + private final Predicate includeDirectiveDefinition; + + private final Predicate includeTypeDefinition; + private final Predicate includeSchemaElement; private final GraphqlTypeComparatorRegistry comparatorRegistry; @@ -120,6 +124,8 @@ private Options(boolean includeIntrospectionTypes, boolean useAstDefinitions, boolean descriptionsAsHashComments, Predicate includeDirective, + Predicate includeDirectiveDefinition, + Predicate includeTypeDefinition, Predicate includeSchemaElement, GraphqlTypeComparatorRegistry comparatorRegistry) { this.includeIntrospectionTypes = includeIntrospectionTypes; @@ -127,6 +133,8 @@ private Options(boolean includeIntrospectionTypes, this.includeSchemaDefinition = includeSchemaDefinition; this.includeDirectiveDefinitions = includeDirectiveDefinitions; this.includeDirective = includeDirective; + this.includeDirectiveDefinition = includeDirectiveDefinition; + this.includeTypeDefinition = includeTypeDefinition; this.useAstDefinitions = useAstDefinitions; this.descriptionsAsHashComments = descriptionsAsHashComments; this.comparatorRegistry = comparatorRegistry; @@ -153,6 +161,14 @@ public Predicate getIncludeDirective() { return includeDirective; } + public Predicate getIncludeDirectiveDefinition() { + return includeDirectiveDefinition; + } + + public Predicate getIncludeTypeDefinition() { + return includeTypeDefinition; + } + public Predicate getIncludeSchemaElement() { return includeSchemaElement; } @@ -172,7 +188,8 @@ public boolean isUseAstDefinitions() { public static Options defaultOptions() { return new Options(false, true, false, true, false, false, - directive -> true, element -> true, DefaultGraphqlTypeComparatorRegistry.defaultComparators()); + directive -> true, directiveDefinition -> true, typeDefinition -> true, + element -> true, DefaultGraphqlTypeComparatorRegistry.defaultComparators()); } /** @@ -183,7 +200,7 @@ public static Options defaultOptions() { * @return options */ public Options includeIntrospectionTypes(boolean flag) { - return new Options(flag, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + return new Options(flag, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); } /** @@ -194,7 +211,7 @@ public Options includeIntrospectionTypes(boolean flag) { * @return options */ public Options includeScalarTypes(boolean flag) { - return new Options(this.includeIntrospectionTypes, flag, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + return new Options(this.includeIntrospectionTypes, flag, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); } /** @@ -208,7 +225,7 @@ public Options includeScalarTypes(boolean flag) { * @return options */ public Options includeSchemaDefinition(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, flag, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + return new Options(this.includeIntrospectionTypes, this.includeScalars, flag, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); } /** @@ -224,7 +241,7 @@ public Options includeSchemaDefinition(boolean flag) { * @return new instance of options */ public Options includeDirectiveDefinitions(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, flag, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, flag, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); } /** @@ -236,7 +253,7 @@ public Options includeDirectiveDefinitions(boolean flag) { * @return new instance of options */ public Options includeDirectives(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, directive -> flag, this.includeSchemaElement, this.comparatorRegistry); + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, directive -> flag, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); } /** @@ -247,7 +264,35 @@ public Options includeDirectives(boolean flag) { * @return new instance of options */ public Options includeDirectives(Predicate includeDirective) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, includeDirective, this.includeSchemaElement, this.comparatorRegistry); + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * Filter printing of directive definitions. In Apollo Federation, some directive + * definitions need to be hidden, and this predicate allows filtering out such definitions. + * Prints all definitions by default. Note that this predicate, the predicate in {@link + * #includeDirectives(Predicate)}, the predicate in {@link + * #includeSchemaElement(Predicate)}, and the boolean in {@link + * #includeDirectiveDefinitions(boolean)} must be true for a definition to be printed. + * + * @param includeDirectiveDefinition returns true if the definition should be printed + * @return new instance of options + */ + public Options includeDirectiveDefinitions(Predicate includeDirectiveDefinition) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); + } + + /** + * Filter printing of type definitions. In Apollo Federation, some type definitions need to + * be hidden, and this predicate allows filtering out such definitions. Prints all + * definitions by default. Note that this predicate and the predicate in {@link + * #includeSchemaElement(Predicate)} must be true for a definition to be printed. + * + * @param includeTypeDefinition returns true if the definition should be printed + * @return new instance of options + */ + public Options includeTypeDefinitions(Predicate includeTypeDefinition) { + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); } /** @@ -259,7 +304,7 @@ public Options includeDirectives(Predicate includeDirective) { */ public Options includeSchemaElement(Predicate includeSchemaElement) { Assert.assertNotNull(includeSchemaElement); - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, includeDirective, includeSchemaElement, this.comparatorRegistry); + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, includeSchemaElement, this.comparatorRegistry); } /** @@ -271,7 +316,7 @@ public Options includeSchemaElement(Predicate includeSchem * @return new instance of options */ public Options useAstDefinitions(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, flag, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, flag, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); } /** @@ -285,7 +330,7 @@ public Options useAstDefinitions(boolean flag) { * @return new instance of options */ public Options descriptionsAsHashComments(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, flag, this.includeDirective, this.includeSchemaElement, this.comparatorRegistry); + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, flag, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); } /** @@ -298,7 +343,7 @@ public Options descriptionsAsHashComments(boolean flag) { * @return options */ public Options setComparators(GraphqlTypeComparatorRegistry comparatorRegistry) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeSchemaElement, comparatorRegistry); + return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, comparatorRegistry); } } @@ -354,6 +399,7 @@ public String print(GraphQLSchema schema) { List typesAsList = schema.getAllTypesAsList() .stream() .sorted(Comparator.comparing(GraphQLNamedType::getName)) + .filter(options.getIncludeTypeDefinition()) .collect(toList()); printType(out, typesAsList, GraphQLInterfaceType.class, visibility); @@ -711,6 +757,7 @@ private List getSchemaDirectives(GraphQLSchema schema) { return schema.getDirectives().stream() .filter(options.getIncludeDirective()) .filter(options.getIncludeSchemaElement()) + .filter(options.getIncludeDirectiveDefinition()) .collect(toList()); } From 5932388699f1f7a6ed7198b4bf35561657c3bcbc Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 01:30:07 -0700 Subject: [PATCH 06/10] Run "mvn spotless:apply" to fix formatting in FederationSdlPrinter Co-authored-by: Martin Bonnin --- .../graphqljava/FederationSdlPrinter.java | 2104 +++++++++-------- 1 file changed, 1154 insertions(+), 950 deletions(-) diff --git a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java index 8302f921..1491a93b 100644 --- a/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java +++ b/graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/FederationSdlPrinter.java @@ -1,7 +1,7 @@ package com.apollographql.federation.graphqljava; -import static graphql.com.google.common.base.Predicates.not; import static graphql.Directives.DeprecatedDirective; +import static graphql.com.google.common.base.Predicates.not; import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION; import static graphql.introspection.Introspection.DirectiveLocation.ENUM_VALUE; import static graphql.introspection.Introspection.DirectiveLocation.FIELD_DEFINITION; @@ -69,1057 +69,1261 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -/** - * This can print an in memory GraphQL schema back to a logical schema definition - */ +/** This can print an in memory GraphQL schema back to a logical schema definition */ @PublicApi public class FederationSdlPrinter { - // - // we use this so that we get the simple "@deprecated" as text and not a full exploded - // text with arguments (but only when we auto add this) - // - private static final GraphQLDirective DeprecatedDirective4Printing = GraphQLDirective.newDirective() - .name("deprecated") - .validLocations(FIELD_DEFINITION, ENUM_VALUE, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITION) - .build(); - - /** - * This predicate excludes all directives which are specified bt the GraphQL Specification. - * Printing these directives is optional. - */ - public static final Predicate ExcludeGraphQLSpecifiedDirectivesPredicate = not( - DirectiveInfo::isGraphqlSpecifiedDirective); - - /** - * Options to use when printing a schema - */ - public static class Options { - - private final boolean includeIntrospectionTypes; - - private final boolean includeScalars; - - private final boolean useAstDefinitions; - - private final boolean includeSchemaDefinition; - - private final boolean includeDirectiveDefinitions; - - private final boolean descriptionsAsHashComments; - - private final Predicate includeDirective; - - private final Predicate includeDirectiveDefinition; - - private final Predicate includeTypeDefinition; - - private final Predicate includeSchemaElement; - - private final GraphqlTypeComparatorRegistry comparatorRegistry; - - private Options(boolean includeIntrospectionTypes, - boolean includeScalars, - boolean includeSchemaDefinition, - boolean includeDirectiveDefinitions, - boolean useAstDefinitions, - boolean descriptionsAsHashComments, - Predicate includeDirective, - Predicate includeDirectiveDefinition, - Predicate includeTypeDefinition, - Predicate includeSchemaElement, - GraphqlTypeComparatorRegistry comparatorRegistry) { - this.includeIntrospectionTypes = includeIntrospectionTypes; - this.includeScalars = includeScalars; - this.includeSchemaDefinition = includeSchemaDefinition; - this.includeDirectiveDefinitions = includeDirectiveDefinitions; - this.includeDirective = includeDirective; - this.includeDirectiveDefinition = includeDirectiveDefinition; - this.includeTypeDefinition = includeTypeDefinition; - this.useAstDefinitions = useAstDefinitions; - this.descriptionsAsHashComments = descriptionsAsHashComments; - this.comparatorRegistry = comparatorRegistry; - this.includeSchemaElement = includeSchemaElement; - } - - public boolean isIncludeIntrospectionTypes() { - return includeIntrospectionTypes; - } - - public boolean isIncludeScalars() { - return includeScalars; - } - - public boolean isIncludeSchemaDefinition() { - return includeSchemaDefinition; - } - - public boolean isIncludeDirectiveDefinitions() { - return includeDirectiveDefinitions; - } - - public Predicate getIncludeDirective() { - return includeDirective; - } - - public Predicate getIncludeDirectiveDefinition() { - return includeDirectiveDefinition; - } - - public Predicate getIncludeTypeDefinition() { - return includeTypeDefinition; - } - - public Predicate getIncludeSchemaElement() { - return includeSchemaElement; - } - - public boolean isDescriptionsAsHashComments() { - return descriptionsAsHashComments; - } - - public GraphqlTypeComparatorRegistry getComparatorRegistry() { - return comparatorRegistry; - } - - public boolean isUseAstDefinitions() { - return useAstDefinitions; - } - - public static Options defaultOptions() { - return new Options(false, true, - false, true, false, false, - directive -> true, directiveDefinition -> true, typeDefinition -> true, - element -> true, DefaultGraphqlTypeComparatorRegistry.defaultComparators()); - } - - /** - * This will allow you to include introspection types that are contained in a schema - * - * @param flag whether to include them - * - * @return options - */ - public Options includeIntrospectionTypes(boolean flag) { - return new Options(flag, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * This will allow you to include scalar types that are contained in a schema - * - * @param flag whether to include them - * - * @return options - */ - public Options includeScalarTypes(boolean flag) { - return new Options(this.includeIntrospectionTypes, flag, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * This will force the printing of the graphql schema definition even if the query, mutation, and/or subscription - * types use the default names. Some graphql parsers require this information even if the schema uses the - * default type names. The schema definition will always be printed if any of the query, mutation, or subscription - * types do not use the default names. - * - * @param flag whether to force include the schema definition - * - * @return options - */ - public Options includeSchemaDefinition(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, flag, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * This flag controls whether schema printer will include directive definitions at the top of the schema, but does not remove them from the field or type usage. - *

- * In some schema defintions, like Apollo Federation, the schema should be printed without the directive definitions. - * This simplified schema is returned by a GraphQL query to other services, in a format that is different that the introspection query. - *

- * On by default. - * - * @param flag whether to print directive definitions - * - * @return new instance of options - */ - public Options includeDirectiveDefinitions(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, flag, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * Allow to print directives. In some situations, auto-generated schemas contain a lot of directives that - * make the printout noisy and having this flag would allow cleaner printout. On by default. - * - * @param flag whether to print directives - * - * @return new instance of options - */ - public Options includeDirectives(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, directive -> flag, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * This is a Predicate that decides whether a directive element is printed. - * - * @param includeDirective the predicate to decide of a directive is printed - * - * @return new instance of options - */ - public Options includeDirectives(Predicate includeDirective) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * Filter printing of directive definitions. In Apollo Federation, some directive - * definitions need to be hidden, and this predicate allows filtering out such definitions. - * Prints all definitions by default. Note that this predicate, the predicate in {@link - * #includeDirectives(Predicate)}, the predicate in {@link - * #includeSchemaElement(Predicate)}, and the boolean in {@link - * #includeDirectiveDefinitions(boolean)} must be true for a definition to be printed. - * - * @param includeDirectiveDefinition returns true if the definition should be printed - * @return new instance of options - */ - public Options includeDirectiveDefinitions(Predicate includeDirectiveDefinition) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * Filter printing of type definitions. In Apollo Federation, some type definitions need to - * be hidden, and this predicate allows filtering out such definitions. Prints all - * definitions by default. Note that this predicate and the predicate in {@link - * #includeSchemaElement(Predicate)} must be true for a definition to be printed. - * - * @param includeTypeDefinition returns true if the definition should be printed - * @return new instance of options - */ - public Options includeTypeDefinitions(Predicate includeTypeDefinition) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * This is a general purpose Predicate that decides whether a schema element is printed ever. - * - * @param includeSchemaElement the predicate to decide of a schema is printed - * - * @return new instance of options - */ - public Options includeSchemaElement(Predicate includeSchemaElement) { - Assert.assertNotNull(includeSchemaElement); - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, includeSchemaElement, this.comparatorRegistry); - } - - /** - * This flag controls whether schema printer will use the {@link GraphQLType}'s original Ast {@link TypeDefinition}s when printing the type. This - * allows access to any `extend type` declarations that might have been originally made. - * - * @param flag whether to print via AST type definitions - * - * @return new instance of options - */ - public Options useAstDefinitions(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, flag, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * Descriptions are defined as preceding string literals, however an older legacy - * versions of SDL supported preceding '#' comments as - * descriptions. Set this to true to enable this deprecated behavior. - * This option is provided to ease adoption and may be removed in future versions. - * - * @param flag whether to print description as # comments - * - * @return new instance of options - */ - public Options descriptionsAsHashComments(boolean flag) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, flag, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, this.comparatorRegistry); - } - - /** - * The comparator registry controls the printing order for registered {@code GraphQLType}s. - *

- * The default is to sort elements by name but you can put in your own code to decide on the field order - * - * @param comparatorRegistry The registry containing the {@code Comparator} and environment scoping rules. - * - * @return options - */ - public Options setComparators(GraphqlTypeComparatorRegistry comparatorRegistry) { - return new Options(this.includeIntrospectionTypes, this.includeScalars, this.includeSchemaDefinition, this.includeDirectiveDefinitions, this.useAstDefinitions, this.descriptionsAsHashComments, this.includeDirective, this.includeDirectiveDefinition, this.includeTypeDefinition, this.includeSchemaElement, comparatorRegistry); - } + // + // we use this so that we get the simple "@deprecated" as text and not a full exploded + // text with arguments (but only when we auto add this) + // + private static final GraphQLDirective DeprecatedDirective4Printing = + GraphQLDirective.newDirective() + .name("deprecated") + .validLocations(FIELD_DEFINITION, ENUM_VALUE, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITION) + .build(); + + /** + * This predicate excludes all directives which are specified bt the GraphQL Specification. + * Printing these directives is optional. + */ + public static final Predicate ExcludeGraphQLSpecifiedDirectivesPredicate = + not(DirectiveInfo::isGraphqlSpecifiedDirective); + + /** Options to use when printing a schema */ + public static class Options { + + private final boolean includeIntrospectionTypes; + + private final boolean includeScalars; + + private final boolean useAstDefinitions; + + private final boolean includeSchemaDefinition; + + private final boolean includeDirectiveDefinitions; + + private final boolean descriptionsAsHashComments; + + private final Predicate includeDirective; + + private final Predicate includeDirectiveDefinition; + + private final Predicate includeTypeDefinition; + + private final Predicate includeSchemaElement; + + private final GraphqlTypeComparatorRegistry comparatorRegistry; + + private Options( + boolean includeIntrospectionTypes, + boolean includeScalars, + boolean includeSchemaDefinition, + boolean includeDirectiveDefinitions, + boolean useAstDefinitions, + boolean descriptionsAsHashComments, + Predicate includeDirective, + Predicate includeDirectiveDefinition, + Predicate includeTypeDefinition, + Predicate includeSchemaElement, + GraphqlTypeComparatorRegistry comparatorRegistry) { + this.includeIntrospectionTypes = includeIntrospectionTypes; + this.includeScalars = includeScalars; + this.includeSchemaDefinition = includeSchemaDefinition; + this.includeDirectiveDefinitions = includeDirectiveDefinitions; + this.includeDirective = includeDirective; + this.includeDirectiveDefinition = includeDirectiveDefinition; + this.includeTypeDefinition = includeTypeDefinition; + this.useAstDefinitions = useAstDefinitions; + this.descriptionsAsHashComments = descriptionsAsHashComments; + this.comparatorRegistry = comparatorRegistry; + this.includeSchemaElement = includeSchemaElement; } - private final Map, TypePrinter> printers = new LinkedHashMap<>(); - - private final Options options; - - public FederationSdlPrinter() { - this(Options.defaultOptions()); + public boolean isIncludeIntrospectionTypes() { + return includeIntrospectionTypes; } - public FederationSdlPrinter(Options options) { - this.options = options; - printers.put(GraphQLSchema.class, schemaPrinter()); - printers.put(GraphQLObjectType.class, objectPrinter()); - printers.put(GraphQLEnumType.class, enumPrinter()); - printers.put(GraphQLScalarType.class, scalarPrinter()); - printers.put(GraphQLInterfaceType.class, interfacePrinter()); - printers.put(GraphQLUnionType.class, unionPrinter()); - printers.put(GraphQLInputObjectType.class, inputObjectPrinter()); + public boolean isIncludeScalars() { + return includeScalars; } - /** - * 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 - * schema) then use {@link graphql.introspection.IntrospectionResultToSchema#createSchemaDefinition(Map)} - * first to get the {@link Document} and then print that. - * - * @param schemaIDL the parsed schema IDL - * - * @return the logical schema definition - */ - public String print(Document schemaIDL) { - TypeDefinitionRegistry registry = new SchemaParser().buildRegistry(schemaIDL); - return print(UnExecutableSchemaGenerator.makeUnExecutableSchema(registry)); + public boolean isIncludeSchemaDefinition() { + return includeSchemaDefinition; } - /** - * This can print an in memory GraphQL schema back to a logical schema definition - * - * @param schema the schema in play - * - * @return the logical schema definition - */ - public String print(GraphQLSchema schema) { - StringWriter sw = new StringWriter(); - PrintWriter out = new PrintWriter(sw); - - GraphqlFieldVisibility visibility = schema.getCodeRegistry().getFieldVisibility(); - - printer(schema.getClass()).print(out, schema, visibility); - - List typesAsList = schema.getAllTypesAsList() - .stream() - .sorted(Comparator.comparing(GraphQLNamedType::getName)) - .filter(options.getIncludeTypeDefinition()) - .collect(toList()); - - printType(out, typesAsList, GraphQLInterfaceType.class, visibility); - printType(out, typesAsList, GraphQLUnionType.class, visibility); - printType(out, typesAsList, GraphQLObjectType.class, visibility); - printType(out, typesAsList, GraphQLEnumType.class, visibility); - printType(out, typesAsList, GraphQLScalarType.class, visibility); - printType(out, typesAsList, GraphQLInputObjectType.class, visibility); - - String result = sw.toString(); - if (result.endsWith("\n\n")) { - result = result.substring(0, result.length() - 1); - } - return result; + public boolean isIncludeDirectiveDefinitions() { + return includeDirectiveDefinitions; } - private interface TypePrinter { - - void print(PrintWriter out, T type, GraphqlFieldVisibility visibility); - + public Predicate getIncludeDirective() { + return includeDirective; } - private boolean isIntrospectionType(GraphQLNamedType type) { - return !options.isIncludeIntrospectionTypes() && type.getName().startsWith("__"); + public Predicate getIncludeDirectiveDefinition() { + return includeDirectiveDefinition; } - private TypePrinter scalarPrinter() { - return (out, type, visibility) -> { - if (!options.isIncludeScalars()) { - return; - } - boolean printScalar; - if (ScalarInfo.isGraphqlSpecifiedScalar(type)) { - printScalar = false; - //noinspection RedundantIfStatement - if (!ScalarInfo.isGraphqlSpecifiedScalar(type)) { - printScalar = true; - } - } else { - printScalar = true; - } - if (printScalar) { - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - out.format("scalar %s%s\n\n", type.getName(), directivesString(GraphQLScalarType.class, type.getDirectives())); - } - } - }; + public Predicate getIncludeTypeDefinition() { + return includeTypeDefinition; } - - private TypePrinter enumPrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } - - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLEnumType.class) - .elementType(GraphQLEnumValueDefinition.class) - .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - out.format("enum %s%s", type.getName(), directivesString(GraphQLEnumType.class, type.getDirectives())); - List values = type.getValues() - .stream() - .sorted(comparator) - .collect(toList()); - if (values.size() > 0) { - out.format(" {\n"); - for (GraphQLEnumValueDefinition enumValueDefinition : values) { - printComments(out, enumValueDefinition, " "); - List enumValueDirectives = enumValueDefinition.getDirectives(); - if (enumValueDefinition.isDeprecated()) { - enumValueDirectives = addDeprecatedDirectiveIfNeeded(enumValueDirectives); - } - out.format(" %s%s\n", enumValueDefinition.getName(), directivesString(GraphQLEnumValueDefinition.class, enumValueDirectives)); - } - out.format("}"); - } - out.format("\n\n"); - } - }; + public Predicate getIncludeSchemaElement() { + return includeSchemaElement; } - private void printFieldDefinitions(PrintWriter out, Comparator comparator, List fieldDefinitions) { - if (fieldDefinitions.size() == 0) { - return; - } - - out.format(" {\n"); - fieldDefinitions - .stream() - .filter(options.getIncludeSchemaElement()) - .sorted(comparator) - .forEach(fd -> { - printComments(out, fd, " "); - List fieldDirectives = fd.getDirectives(); - if (fd.isDeprecated()) { - fieldDirectives = addDeprecatedDirectiveIfNeeded(fieldDirectives); - } - - out.format(" %s%s: %s%s\n", - fd.getName(), argsString(GraphQLFieldDefinition.class, fd.getArguments()), typeString(fd.getType()), - directivesString(GraphQLFieldDefinition.class, fieldDirectives)); - }); - out.format("}"); + public boolean isDescriptionsAsHashComments() { + return descriptionsAsHashComments; } - private TypePrinter interfacePrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } + public GraphqlTypeComparatorRegistry getComparatorRegistry() { + return comparatorRegistry; + } - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - if (type.getInterfaces().isEmpty()) { - out.format("interface %s%s", type.getName(), directivesString(GraphQLInterfaceType.class, type.getDirectives())); - } else { - - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLInterfaceType.class) - .elementType(GraphQLOutputType.class) - .build(); - Comparator implementsComparator = options.comparatorRegistry.getComparator(environment); - - Stream interfaceNames = type.getInterfaces() - .stream() - .sorted(implementsComparator) - .map(GraphQLNamedType::getName); - out.format("interface %s implements %s%s", - type.getName(), - interfaceNames.collect(joining(" & ")), - directivesString(GraphQLInterfaceType.class, type.getDirectives())); - } - - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLInterfaceType.class) - .elementType(GraphQLFieldDefinition.class) - .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type)); - out.format("\n\n"); - } - }; + public boolean isUseAstDefinitions() { + return useAstDefinitions; } - private TypePrinter unionPrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } + public static Options defaultOptions() { + return new Options( + false, + true, + false, + true, + false, + false, + directive -> true, + directiveDefinition -> true, + typeDefinition -> true, + element -> true, + DefaultGraphqlTypeComparatorRegistry.defaultComparators()); + } - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLUnionType.class) - .elementType(GraphQLOutputType.class) - .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - out.format("union %s%s = ", type.getName(), directivesString(GraphQLUnionType.class, type.getDirectives())); - List types = type.getTypes() - .stream() - .sorted(comparator) - .collect(toList()); - for (int i = 0; i < types.size(); i++) { - GraphQLNamedOutputType objectType = types.get(i); - if (i > 0) { - out.format(" | "); - } - out.format("%s", objectType.getName()); - } - out.format("\n\n"); - } - }; + /** + * This will allow you to include introspection types that are contained in a schema + * + * @param flag whether to include them + * @return options + */ + public Options includeIntrospectionTypes(boolean flag) { + return new Options( + flag, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + this.includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } - private TypePrinter objectPrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - if (type.getInterfaces().isEmpty()) { - out.format("type %s%s", type.getName(), directivesString(GraphQLObjectType.class, type.getDirectives())); - } else { - - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLObjectType.class) - .elementType(GraphQLOutputType.class) - .build(); - Comparator implementsComparator = options.comparatorRegistry.getComparator(environment); - - Stream interfaceNames = type.getInterfaces() - .stream() - .sorted(implementsComparator) - .map(GraphQLNamedType::getName); - out.format("type %s implements %s%s", - type.getName(), - interfaceNames.collect(joining(" & ")), - directivesString(GraphQLObjectType.class, type.getDirectives())); - } - - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLObjectType.class) - .elementType(GraphQLFieldDefinition.class) - .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type)); - out.format("\n\n"); - } - }; + /** + * This will allow you to include scalar types that are contained in a schema + * + * @param flag whether to include them + * @return options + */ + public Options includeScalarTypes(boolean flag) { + return new Options( + this.includeIntrospectionTypes, + flag, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + this.includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } - private TypePrinter inputObjectPrinter() { - return (out, type, visibility) -> { - if (isIntrospectionType(type)) { - return; - } - if (shouldPrintAsAst(type.getDefinition())) { - printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); - } else { - printComments(out, type, ""); - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLInputObjectType.class) - .elementType(GraphQLInputObjectField.class) - .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - out.format("input %s%s", type.getName(), directivesString(GraphQLInputObjectType.class, type.getDirectives())); - List inputObjectFields = visibility.getFieldDefinitions(type); - if (inputObjectFields.size() > 0) { - out.format(" {\n"); - inputObjectFields - .stream() - .filter(options.getIncludeSchemaElement()) - .sorted(comparator) - .forEach(fd -> { - printComments(out, fd, " "); - out.format(" %s: %s", - fd.getName(), typeString(fd.getType())); - if (fd.hasSetDefaultValue()) { - InputValueWithState defaultValue = fd.getInputFieldDefaultValue(); - String astValue = printAst(defaultValue, fd.getType()); - out.format(" = %s", astValue); - } - out.format(directivesString(GraphQLInputObjectField.class, fd.getDirectives())); - out.format("\n"); - }); - out.format("}"); - } - out.format("\n\n"); - } - }; + /** + * This will force the printing of the graphql schema definition even if the query, mutation, + * and/or subscription types use the default names. Some graphql parsers require this + * information even if the schema uses the default type names. The schema definition will always + * be printed if any of the query, mutation, or subscription types do not use the default names. + * + * @param flag whether to force include the schema definition + * @return options + */ + public Options includeSchemaDefinition(boolean flag) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + flag, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + this.includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } /** - * This will return true if the options say to use the AST and we have an AST element + * This flag controls whether schema printer will include directive definitions at the top of + * the schema, but does not remove them from the field or type usage. + * + *

In some schema defintions, like Apollo Federation, the schema should be printed without + * the directive definitions. This simplified schema is returned by a GraphQL query to other + * services, in a format that is different that the introspection query. * - * @param definition the AST type definition + *

On by default. * - * @return true if we should print using AST nodes + * @param flag whether to print directive definitions + * @return new instance of options */ - private boolean shouldPrintAsAst(TypeDefinition definition) { - return options.isUseAstDefinitions() && definition != null; + public Options includeDirectiveDefinitions(boolean flag) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + flag, + this.useAstDefinitions, + this.descriptionsAsHashComments, + this.includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } /** - * 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)} + * Allow to print directives. In some situations, auto-generated schemas contain a lot of + * directives that make the printout noisy and having this flag would allow cleaner printout. On + * by default. * - * @param out the output writer - * @param definition the AST type definition - * @param extensions a list of type definition extensions + * @param flag whether to print directives + * @return new instance of options */ - private void printAsAst(PrintWriter out, TypeDefinition definition, List> extensions) { - out.printf("%s\n", AstPrinter.printAst(definition)); - if (extensions != null) { - for (TypeDefinition extension : extensions) { - out.printf("\n%s\n", AstPrinter.printAst(extension)); - } - } - out.println(); + public Options includeDirectives(boolean flag) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + directive -> flag, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } - private static String printAst(InputValueWithState value, GraphQLInputType type) { - return AstPrinter.printAst(ValuesResolver.valueToLiteral(value, type)); + /** + * This is a Predicate that decides whether a directive element is printed. + * + * @param includeDirective the predicate to decide of a directive is printed + * @return new instance of options + */ + public Options includeDirectives(Predicate includeDirective) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } - private TypePrinter schemaPrinter() { - return (out, schema, visibility) -> { - List schemaDirectives = schema.getSchemaDirectives(); - GraphQLObjectType queryType = schema.getQueryType(); - GraphQLObjectType mutationType = schema.getMutationType(); - 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. - boolean needsSchemaPrinted = options.isIncludeSchemaDefinition(); - - if (!needsSchemaPrinted) { - if (queryType != null && !queryType.getName().equals("Query")) { - needsSchemaPrinted = true; - } - if (mutationType != null && !mutationType.getName().equals("Mutation")) { - needsSchemaPrinted = true; - } - if (subscriptionType != null && !subscriptionType.getName().equals("Subscription")) { - needsSchemaPrinted = true; - } - } - - if (needsSchemaPrinted) { - out.format("schema %s{\n", directivesString(GraphQLSchemaElement.class, schemaDirectives)); - 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"); - } - - if (options.isIncludeDirectiveDefinitions()) { - List directives = getSchemaDirectives(schema); - if (!directives.isEmpty()) { - out.format("%s", directiveDefinitions(directives)); - } - } - }; + /** + * Filter printing of directive definitions. In Apollo Federation, some directive definitions + * need to be hidden, and this predicate allows filtering out such definitions. Prints all + * definitions by default. Note that this predicate, the predicate in {@link + * #includeDirectives(Predicate)}, the predicate in {@link #includeSchemaElement(Predicate)}, + * and the boolean in {@link #includeDirectiveDefinitions(boolean)} must be true for a + * definition to be printed. + * + * @param includeDirectiveDefinition returns true if the definition should be printed + * @return new instance of options + */ + public Options includeDirectiveDefinitions( + Predicate includeDirectiveDefinition) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + this.includeDirective, + includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } - private List getSchemaDirectives(GraphQLSchema schema) { - return schema.getDirectives().stream() - .filter(options.getIncludeDirective()) - .filter(options.getIncludeSchemaElement()) - .filter(options.getIncludeDirectiveDefinition()) - .collect(toList()); + /** + * Filter printing of type definitions. In Apollo Federation, some type definitions need to be + * hidden, and this predicate allows filtering out such definitions. Prints all definitions by + * default. Note that this predicate and the predicate in {@link + * #includeSchemaElement(Predicate)} must be true for a definition to be printed. + * + * @param includeTypeDefinition returns true if the definition should be printed + * @return new instance of options + */ + public Options includeTypeDefinitions(Predicate includeTypeDefinition) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + this.includeDirective, + this.includeDirectiveDefinition, + includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } - String typeString(GraphQLType rawType) { - return GraphQLTypeUtil.simplePrint(rawType); + /** + * This is a general purpose Predicate that decides whether a schema element is printed ever. + * + * @param includeSchemaElement the predicate to decide of a schema is printed + * @return new instance of options + */ + public Options includeSchemaElement(Predicate includeSchemaElement) { + Assert.assertNotNull(includeSchemaElement); + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + includeSchemaElement, + this.comparatorRegistry); } - String argsString(List arguments) { - return argsString(null, arguments); + /** + * This flag controls whether schema printer will use the {@link GraphQLType}'s original Ast + * {@link TypeDefinition}s when printing the type. This allows access to any `extend type` + * declarations that might have been originally made. + * + * @param flag whether to print via AST type definitions + * @return new instance of options + */ + public Options useAstDefinitions(boolean flag) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + flag, + this.descriptionsAsHashComments, + this.includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); } - String argsString(Class parent, List arguments) { - boolean hasDescriptions = arguments.stream().anyMatch(this::hasDescription); - String halfPrefix = hasDescriptions ? " " : ""; - String prefix = hasDescriptions ? " " : ""; - int count = 0; - StringBuilder sb = new StringBuilder(); - - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(parent) - .elementType(GraphQLArgument.class) - .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - arguments = arguments - .stream() - .sorted(comparator) - .filter(options.getIncludeSchemaElement()) - .collect(toList()); - for (GraphQLArgument argument : arguments) { - if (count == 0) { - sb.append("("); - } else { - sb.append(", "); - } - if (hasDescriptions) { - sb.append("\n"); - } - sb.append(printComments(argument, prefix)); - - sb.append(prefix).append(argument.getName()).append(": ").append(typeString(argument.getType())); - if (argument.hasSetDefaultValue()) { - InputValueWithState defaultValue = argument.getArgumentDefaultValue(); - sb.append(" = "); - sb.append(printAst(defaultValue, argument.getType())); - } - - argument.getDirectives().stream() - .filter(options.getIncludeSchemaElement()) - .map(this::directiveString) - .filter(it -> !it.isEmpty()) - .forEach(directiveString -> sb.append(" ").append(directiveString)); + /** + * Descriptions are defined as preceding string literals, however an older legacy versions of + * SDL supported preceding '#' comments as descriptions. Set this to true to enable this + * deprecated behavior. This option is provided to ease adoption and may be removed in future + * versions. + * + * @param flag whether to print description as # comments + * @return new instance of options + */ + public Options descriptionsAsHashComments(boolean flag) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + flag, + this.includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + this.comparatorRegistry); + } - count++; + /** + * The comparator registry controls the printing order for registered {@code GraphQLType}s. + * + *

The default is to sort elements by name but you can put in your own code to decide on the + * field order + * + * @param comparatorRegistry The registry containing the {@code Comparator} and environment + * scoping rules. + * @return options + */ + public Options setComparators(GraphqlTypeComparatorRegistry comparatorRegistry) { + return new Options( + this.includeIntrospectionTypes, + this.includeScalars, + this.includeSchemaDefinition, + this.includeDirectiveDefinitions, + this.useAstDefinitions, + this.descriptionsAsHashComments, + this.includeDirective, + this.includeDirectiveDefinition, + this.includeTypeDefinition, + this.includeSchemaElement, + comparatorRegistry); + } + } + + private final Map, TypePrinter> printers = new LinkedHashMap<>(); + + private final Options options; + + public FederationSdlPrinter() { + this(Options.defaultOptions()); + } + + public FederationSdlPrinter(Options options) { + this.options = options; + printers.put(GraphQLSchema.class, schemaPrinter()); + printers.put(GraphQLObjectType.class, objectPrinter()); + printers.put(GraphQLEnumType.class, enumPrinter()); + printers.put(GraphQLScalarType.class, scalarPrinter()); + printers.put(GraphQLInterfaceType.class, interfacePrinter()); + printers.put(GraphQLUnionType.class, unionPrinter()); + printers.put(GraphQLInputObjectType.class, inputObjectPrinter()); + } + + /** + * 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 schema) then + * use {@link graphql.introspection.IntrospectionResultToSchema#createSchemaDefinition(Map)} first + * to get the {@link Document} and then print that. + * + * @param schemaIDL the parsed schema IDL + * @return the logical schema definition + */ + public String print(Document schemaIDL) { + TypeDefinitionRegistry registry = new SchemaParser().buildRegistry(schemaIDL); + return print(UnExecutableSchemaGenerator.makeUnExecutableSchema(registry)); + } + + /** + * This can print an in memory GraphQL schema back to a logical schema definition + * + * @param schema the schema in play + * @return the logical schema definition + */ + public String print(GraphQLSchema schema) { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + + GraphqlFieldVisibility visibility = schema.getCodeRegistry().getFieldVisibility(); + + printer(schema.getClass()).print(out, schema, visibility); + + List typesAsList = + schema.getAllTypesAsList().stream() + .sorted(Comparator.comparing(GraphQLNamedType::getName)) + .filter(options.getIncludeTypeDefinition()) + .collect(toList()); + + printType(out, typesAsList, GraphQLInterfaceType.class, visibility); + printType(out, typesAsList, GraphQLUnionType.class, visibility); + printType(out, typesAsList, GraphQLObjectType.class, visibility); + printType(out, typesAsList, GraphQLEnumType.class, visibility); + printType(out, typesAsList, GraphQLScalarType.class, visibility); + printType(out, typesAsList, GraphQLInputObjectType.class, visibility); + + String result = sw.toString(); + if (result.endsWith("\n\n")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + private interface TypePrinter { + + void print(PrintWriter out, T type, GraphqlFieldVisibility visibility); + } + + private boolean isIntrospectionType(GraphQLNamedType type) { + return !options.isIncludeIntrospectionTypes() && type.getName().startsWith("__"); + } + + private TypePrinter scalarPrinter() { + return (out, type, visibility) -> { + if (!options.isIncludeScalars()) { + return; + } + boolean printScalar; + if (ScalarInfo.isGraphqlSpecifiedScalar(type)) { + printScalar = false; + //noinspection RedundantIfStatement + if (!ScalarInfo.isGraphqlSpecifiedScalar(type)) { + printScalar = true; } - if (count > 0) { - if (hasDescriptions) { - sb.append("\n"); + } else { + printScalar = true; + } + if (printScalar) { + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + out.format( + "scalar %s%s\n\n", + type.getName(), directivesString(GraphQLScalarType.class, type.getDirectives())); + } + } + }; + } + + private TypePrinter enumPrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLEnumType.class) + .elementType(GraphQLEnumValueDefinition.class) + .build(); + Comparator comparator = + options.comparatorRegistry.getComparator(environment); + + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + out.format( + "enum %s%s", + type.getName(), directivesString(GraphQLEnumType.class, type.getDirectives())); + List values = + type.getValues().stream().sorted(comparator).collect(toList()); + if (values.size() > 0) { + out.format(" {\n"); + for (GraphQLEnumValueDefinition enumValueDefinition : values) { + printComments(out, enumValueDefinition, " "); + List enumValueDirectives = enumValueDefinition.getDirectives(); + if (enumValueDefinition.isDeprecated()) { + enumValueDirectives = addDeprecatedDirectiveIfNeeded(enumValueDirectives); } - sb.append(halfPrefix).append(")"); + out.format( + " %s%s\n", + enumValueDefinition.getName(), + directivesString(GraphQLEnumValueDefinition.class, enumValueDirectives)); + } + out.format("}"); } - return sb.toString(); + out.format("\n\n"); + } + }; + } + + private void printFieldDefinitions( + PrintWriter out, + Comparator comparator, + List fieldDefinitions) { + if (fieldDefinitions.size() == 0) { + return; } - String directivesString(Class parent, List directives) { - directives = directives.stream() - // @deprecated is special - we always print it if something is deprecated - .filter(directive -> options.getIncludeDirective().test(directive) || isDeprecatedDirective(directive)) - .filter(options.getIncludeSchemaElement()) - .collect(toList()); + out.format(" {\n"); + fieldDefinitions.stream() + .filter(options.getIncludeSchemaElement()) + .sorted(comparator) + .forEach( + fd -> { + printComments(out, fd, " "); + List fieldDirectives = fd.getDirectives(); + if (fd.isDeprecated()) { + fieldDirectives = addDeprecatedDirectiveIfNeeded(fieldDirectives); + } + + out.format( + " %s%s: %s%s\n", + fd.getName(), + argsString(GraphQLFieldDefinition.class, fd.getArguments()), + typeString(fd.getType()), + directivesString(GraphQLFieldDefinition.class, fieldDirectives)); + }); + out.format("}"); + } + + private TypePrinter interfacePrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + if (type.getInterfaces().isEmpty()) { + out.format( + "interface %s%s", + type.getName(), directivesString(GraphQLInterfaceType.class, type.getDirectives())); + } else { - if (directives.isEmpty()) { - return ""; - } - StringBuilder sb = new StringBuilder(); - if (parent != GraphQLSchemaElement.class) { - sb.append(" "); + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLInterfaceType.class) + .elementType(GraphQLOutputType.class) + .build(); + Comparator implementsComparator = + options.comparatorRegistry.getComparator(environment); + + Stream interfaceNames = + type.getInterfaces().stream() + .sorted(implementsComparator) + .map(GraphQLNamedType::getName); + out.format( + "interface %s implements %s%s", + type.getName(), + interfaceNames.collect(joining(" & ")), + directivesString(GraphQLInterfaceType.class, type.getDirectives())); } - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(parent) - .elementType(GraphQLDirective.class) + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLInterfaceType.class) + .elementType(GraphQLFieldDefinition.class) .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - directives = directives - .stream() - .sorted(comparator) - .collect(toList()); - for (int i = 0; i < directives.size(); i++) { - GraphQLDirective directive = directives.get(i); - sb.append(directiveString(directive)); - if (i < directives.size() - 1) { - sb.append(" "); - } + Comparator comparator = + options.comparatorRegistry.getComparator(environment); + + printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type)); + out.format("\n\n"); + } + }; + } + + private TypePrinter unionPrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLUnionType.class) + .elementType(GraphQLOutputType.class) + .build(); + Comparator comparator = + options.comparatorRegistry.getComparator(environment); + + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + out.format( + "union %s%s = ", + type.getName(), directivesString(GraphQLUnionType.class, type.getDirectives())); + List types = + type.getTypes().stream().sorted(comparator).collect(toList()); + for (int i = 0; i < types.size(); i++) { + GraphQLNamedOutputType objectType = types.get(i); + if (i > 0) { + out.format(" | "); + } + out.format("%s", objectType.getName()); } - return sb.toString(); - } + out.format("\n\n"); + } + }; + } + + private TypePrinter objectPrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + if (type.getInterfaces().isEmpty()) { + out.format( + "type %s%s", + type.getName(), directivesString(GraphQLObjectType.class, type.getDirectives())); + } else { - private String directiveString(GraphQLDirective directive) { - if (!options.getIncludeSchemaElement().test(directive)) { - return ""; + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLObjectType.class) + .elementType(GraphQLOutputType.class) + .build(); + Comparator implementsComparator = + options.comparatorRegistry.getComparator(environment); + + Stream interfaceNames = + type.getInterfaces().stream() + .sorted(implementsComparator) + .map(GraphQLNamedType::getName); + out.format( + "type %s implements %s%s", + type.getName(), + interfaceNames.collect(joining(" & ")), + directivesString(GraphQLObjectType.class, type.getDirectives())); } - if (!options.getIncludeDirective().test(directive)) { - // @deprecated is special - we always print it if something is deprecated - if (!isDeprecatedDirective(directive)) { - return ""; - } - } - - StringBuilder sb = new StringBuilder(); - sb.append("@").append(directive.getName()); - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLDirective.class) - .elementType(GraphQLArgument.class) + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLObjectType.class) + .elementType(GraphQLFieldDefinition.class) .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - List args = directive.getArguments(); - args = args - .stream() - .filter(arg -> arg.getArgumentValue().isSet() || arg.getArgumentDefaultValue().isSet()) - .sorted(comparator) - .collect(toList()); - if (!args.isEmpty()) { - sb.append("("); - for (int i = 0; i < args.size(); i++) { - GraphQLArgument arg = args.get(i); - String argValue = null; - if (arg.hasSetValue()) { - argValue = printAst(arg.getArgumentValue(), arg.getType()); - } else if (arg.hasSetDefaultValue()) { - argValue = printAst(arg.getArgumentDefaultValue(), arg.getType()); - } - if (!isNullOrEmpty(argValue)) { - sb.append(arg.getName()); - sb.append(" : "); - sb.append(argValue); - if (i < args.size() - 1) { - sb.append(", "); + Comparator comparator = + options.comparatorRegistry.getComparator(environment); + + printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type)); + out.format("\n\n"); + } + }; + } + + private TypePrinter inputObjectPrinter() { + return (out, type, visibility) -> { + if (isIntrospectionType(type)) { + return; + } + if (shouldPrintAsAst(type.getDefinition())) { + printAsAst(out, type.getDefinition(), type.getExtensionDefinitions()); + } else { + printComments(out, type, ""); + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLInputObjectType.class) + .elementType(GraphQLInputObjectField.class) + .build(); + Comparator comparator = + options.comparatorRegistry.getComparator(environment); + + out.format( + "input %s%s", + type.getName(), directivesString(GraphQLInputObjectType.class, type.getDirectives())); + List inputObjectFields = visibility.getFieldDefinitions(type); + if (inputObjectFields.size() > 0) { + out.format(" {\n"); + inputObjectFields.stream() + .filter(options.getIncludeSchemaElement()) + .sorted(comparator) + .forEach( + fd -> { + printComments(out, fd, " "); + out.format(" %s: %s", fd.getName(), typeString(fd.getType())); + if (fd.hasSetDefaultValue()) { + InputValueWithState defaultValue = fd.getInputFieldDefaultValue(); + String astValue = printAst(defaultValue, fd.getType()); + out.format(" = %s", astValue); } - } - } - sb.append(")"); + out.format(directivesString(GraphQLInputObjectField.class, fd.getDirectives())); + out.format("\n"); + }); + out.format("}"); } - return sb.toString(); - } - - private boolean isDeprecatedDirective(GraphQLDirective directive) { - return directive.getName().equals(DeprecatedDirective.getName()); - } - - private boolean hasDeprecatedDirective(List directives) { - return directives.stream() - .filter(this::isDeprecatedDirective) - .count() == 1; + out.format("\n\n"); + } + }; + } + + /** + * This will return true if the options say to use the AST and we have an AST element + * + * @param definition the AST type definition + * @return true if we should print using AST nodes + */ + private boolean shouldPrintAsAst(TypeDefinition definition) { + return options.isUseAstDefinitions() && definition != null; + } + + /** + * 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)} + * + * @param out the output writer + * @param definition the AST type definition + * @param extensions a list of type definition extensions + */ + private void printAsAst( + PrintWriter out, TypeDefinition definition, List> extensions) { + out.printf("%s\n", AstPrinter.printAst(definition)); + if (extensions != null) { + for (TypeDefinition extension : extensions) { + out.printf("\n%s\n", AstPrinter.printAst(extension)); + } } - - private List addDeprecatedDirectiveIfNeeded(List directives) { - if (!hasDeprecatedDirective(directives)) { - directives = new ArrayList<>(directives); - directives.add(DeprecatedDirective4Printing); + out.println(); + } + + private static String printAst(InputValueWithState value, GraphQLInputType type) { + return AstPrinter.printAst(ValuesResolver.valueToLiteral(value, type)); + } + + private TypePrinter schemaPrinter() { + return (out, schema, visibility) -> { + List schemaDirectives = schema.getSchemaDirectives(); + GraphQLObjectType queryType = schema.getQueryType(); + GraphQLObjectType mutationType = schema.getMutationType(); + 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. + boolean needsSchemaPrinted = options.isIncludeSchemaDefinition(); + + if (!needsSchemaPrinted) { + if (queryType != null && !queryType.getName().equals("Query")) { + needsSchemaPrinted = true; } - return directives; - } - - private String directiveDefinitions(List directives) { - StringBuilder sb = new StringBuilder(); - directives.stream().filter(options.getIncludeSchemaElement()).forEach(directive -> { - sb.append(directiveDefinition(directive)); - sb.append("\n\n"); - }); - return sb.toString(); - } - - private String directiveDefinition(GraphQLDirective directive) { - StringBuilder sb = new StringBuilder(); - - StringWriter sw = new StringWriter(); - printComments(new PrintWriter(sw), directive, ""); - - sb.append(sw.toString()); - - sb.append("directive @").append(directive.getName()); - - GraphqlTypeComparatorEnvironment environment = GraphqlTypeComparatorEnvironment.newEnvironment() - .parentType(GraphQLDirective.class) - .elementType(GraphQLArgument.class) - .build(); - Comparator comparator = options.comparatorRegistry.getComparator(environment); - - List args = directive.getArguments(); - args = args - .stream() - .filter(options.getIncludeSchemaElement()) - .sorted(comparator) - .collect(toList()); - - sb.append(argsString(GraphQLDirective.class, args)); - - if (directive.isRepeatable()) { - sb.append(" repeatable"); + if (mutationType != null && !mutationType.getName().equals("Mutation")) { + needsSchemaPrinted = true; } + if (subscriptionType != null && !subscriptionType.getName().equals("Subscription")) { + needsSchemaPrinted = true; + } + } - sb.append(" on "); - - String locations = directive.validLocations().stream().map(Enum::name).collect(Collectors.joining(" | ")); - sb.append(locations); - - return sb.toString(); - } - + if (needsSchemaPrinted) { + out.format("schema %s{\n", directivesString(GraphQLSchemaElement.class, schemaDirectives)); + 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"); + } - @SuppressWarnings("unchecked") - private TypePrinter printer(Class clazz) { - TypePrinter typePrinter = printers.get(clazz); - if (typePrinter == null) { - Class superClazz = clazz.getSuperclass(); - if (superClazz != Object.class) { - typePrinter = printer(superClazz); - } else { - typePrinter = (out, type, visibility) -> out.println("Type not implemented : " + type); - } - printers.put(clazz, typePrinter); + if (options.isIncludeDirectiveDefinitions()) { + List directives = getSchemaDirectives(schema); + if (!directives.isEmpty()) { + out.format("%s", directiveDefinitions(directives)); } - return (TypePrinter) typePrinter; + } + }; + } + + private List getSchemaDirectives(GraphQLSchema schema) { + return schema.getDirectives().stream() + .filter(options.getIncludeDirective()) + .filter(options.getIncludeSchemaElement()) + .filter(options.getIncludeDirectiveDefinition()) + .collect(toList()); + } + + String typeString(GraphQLType rawType) { + return GraphQLTypeUtil.simplePrint(rawType); + } + + String argsString(List arguments) { + return argsString(null, arguments); + } + + String argsString(Class parent, List arguments) { + boolean hasDescriptions = arguments.stream().anyMatch(this::hasDescription); + String halfPrefix = hasDescriptions ? " " : ""; + String prefix = hasDescriptions ? " " : ""; + int count = 0; + StringBuilder sb = new StringBuilder(); + + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(parent) + .elementType(GraphQLArgument.class) + .build(); + Comparator comparator = + options.comparatorRegistry.getComparator(environment); + + arguments = + arguments.stream() + .sorted(comparator) + .filter(options.getIncludeSchemaElement()) + .collect(toList()); + for (GraphQLArgument argument : arguments) { + if (count == 0) { + sb.append("("); + } else { + sb.append(", "); + } + if (hasDescriptions) { + sb.append("\n"); + } + sb.append(printComments(argument, prefix)); + + sb.append(prefix) + .append(argument.getName()) + .append(": ") + .append(typeString(argument.getType())); + if (argument.hasSetDefaultValue()) { + InputValueWithState defaultValue = argument.getArgumentDefaultValue(); + sb.append(" = "); + sb.append(printAst(defaultValue, argument.getType())); + } + + argument.getDirectives().stream() + .filter(options.getIncludeSchemaElement()) + .map(this::directiveString) + .filter(it -> !it.isEmpty()) + .forEach(directiveString -> sb.append(" ").append(directiveString)); + + count++; } - - - public String print(GraphQLType type) { - StringWriter sw = new StringWriter(); - PrintWriter out = new PrintWriter(sw); - - printType(out, type, DEFAULT_FIELD_VISIBILITY); - - return sw.toString(); + if (count > 0) { + if (hasDescriptions) { + sb.append("\n"); + } + sb.append(halfPrefix).append(")"); } + return sb.toString(); + } - public String print(GraphQLDirective graphQLDirective) { - return directiveDefinition(graphQLDirective); + String directivesString( + Class parent, List directives) { + directives = + directives.stream() + // @deprecated is special - we always print it if something is deprecated + .filter( + directive -> + options.getIncludeDirective().test(directive) + || isDeprecatedDirective(directive)) + .filter(options.getIncludeSchemaElement()) + .collect(toList()); + + if (directives.isEmpty()) { + return ""; } - - private void printType(PrintWriter out, List typesAsList, Class - typeClazz, GraphqlFieldVisibility visibility) { - typesAsList.stream() - .filter(type -> typeClazz.isAssignableFrom(type.getClass())) - .filter(type -> options.getIncludeSchemaElement().test(type)) - .forEach(type -> printType(out, type, visibility)); + StringBuilder sb = new StringBuilder(); + if (parent != GraphQLSchemaElement.class) { + sb.append(" "); } - private void printType(PrintWriter out, GraphQLType type, GraphqlFieldVisibility visibility) { - TypePrinter printer = printer(type.getClass()); - printer.print(out, type, visibility); + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(parent) + .elementType(GraphQLDirective.class) + .build(); + Comparator comparator = + options.comparatorRegistry.getComparator(environment); + + directives = directives.stream().sorted(comparator).collect(toList()); + for (int i = 0; i < directives.size(); i++) { + GraphQLDirective directive = directives.get(i); + sb.append(directiveString(directive)); + if (i < directives.size() - 1) { + sb.append(" "); + } } + return sb.toString(); + } - private String printComments(Object graphQLType, String prefix) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - printComments(pw, graphQLType, prefix); - return sw.toString(); + private String directiveString(GraphQLDirective directive) { + if (!options.getIncludeSchemaElement().test(directive)) { + return ""; + } + if (!options.getIncludeDirective().test(directive)) { + // @deprecated is special - we always print it if something is deprecated + if (!isDeprecatedDirective(directive)) { + return ""; + } } - private void printComments(PrintWriter out, Object graphQLType, String prefix) { + StringBuilder sb = new StringBuilder(); + sb.append("@").append(directive.getName()); - String descriptionText = getDescription(graphQLType); - if (isNullOrEmpty(descriptionText)) { - return; + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLDirective.class) + .elementType(GraphQLArgument.class) + .build(); + Comparator comparator = + options.comparatorRegistry.getComparator(environment); + + List args = directive.getArguments(); + args = + args.stream() + .filter(arg -> arg.getArgumentValue().isSet() || arg.getArgumentDefaultValue().isSet()) + .sorted(comparator) + .collect(toList()); + if (!args.isEmpty()) { + sb.append("("); + for (int i = 0; i < args.size(); i++) { + GraphQLArgument arg = args.get(i); + String argValue = null; + if (arg.hasSetValue()) { + argValue = printAst(arg.getArgumentValue(), arg.getType()); + } else if (arg.hasSetDefaultValue()) { + argValue = printAst(arg.getArgumentDefaultValue(), arg.getType()); } - - if (!isNullOrEmpty(descriptionText)) { - List lines = Arrays.asList(descriptionText.split("\n")); - if (options.isDescriptionsAsHashComments()) { - printMultiLineHashDescription(out, prefix, lines); - } else if (!lines.isEmpty()) { - if (lines.size() > 1) { - printMultiLineDescription(out, prefix, lines); - } else { - printSingleLineDescription(out, prefix, lines.get(0)); - } - } + if (!isNullOrEmpty(argValue)) { + sb.append(arg.getName()); + sb.append(" : "); + sb.append(argValue); + if (i < args.size() - 1) { + sb.append(", "); + } } + } + sb.append(")"); } + return sb.toString(); + } - private void printMultiLineHashDescription(PrintWriter out, String prefix, List lines) { - lines.forEach(l -> out.printf("%s#%s\n", prefix, l)); - } + private boolean isDeprecatedDirective(GraphQLDirective directive) { + return directive.getName().equals(DeprecatedDirective.getName()); + } - private void printMultiLineDescription(PrintWriter out, String prefix, List lines) { - out.printf("%s\"\"\"\n", prefix); - lines.forEach(l -> { - String escapedTripleQuotes = l.replaceAll("\"\"\"", "\\\\\"\"\""); - out.printf("%s%s\n", prefix, escapedTripleQuotes); - }); - out.printf("%s\"\"\"\n", prefix); + private boolean hasDeprecatedDirective(List directives) { + return directives.stream().filter(this::isDeprecatedDirective).count() == 1; + } + + private List addDeprecatedDirectiveIfNeeded(List directives) { + if (!hasDeprecatedDirective(directives)) { + directives = new ArrayList<>(directives); + directives.add(DeprecatedDirective4Printing); } + return directives; + } + + private String directiveDefinitions(List directives) { + StringBuilder sb = new StringBuilder(); + directives.stream() + .filter(options.getIncludeSchemaElement()) + .forEach( + directive -> { + sb.append(directiveDefinition(directive)); + sb.append("\n\n"); + }); + return sb.toString(); + } + + private String directiveDefinition(GraphQLDirective directive) { + StringBuilder sb = new StringBuilder(); + + StringWriter sw = new StringWriter(); + printComments(new PrintWriter(sw), directive, ""); + + sb.append(sw.toString()); + + sb.append("directive @").append(directive.getName()); + + GraphqlTypeComparatorEnvironment environment = + GraphqlTypeComparatorEnvironment.newEnvironment() + .parentType(GraphQLDirective.class) + .elementType(GraphQLArgument.class) + .build(); + Comparator comparator = + options.comparatorRegistry.getComparator(environment); - private void printSingleLineDescription(PrintWriter out, String prefix, String s) { - // See: https://github.com/graphql/graphql-spec/issues/148 - String desc = escapeJsonString(s); - out.printf("%s\"%s\"\n", prefix, desc); + List args = directive.getArguments(); + args = + args.stream() + .filter(options.getIncludeSchemaElement()) + .sorted(comparator) + .collect(toList()); + + sb.append(argsString(GraphQLDirective.class, args)); + + if (directive.isRepeatable()) { + sb.append(" repeatable"); } - private boolean hasDescription(Object descriptionHolder) { - String description = getDescription(descriptionHolder); - return !isNullOrEmpty(description); + sb.append(" on "); + + String locations = + directive.validLocations().stream().map(Enum::name).collect(Collectors.joining(" | ")); + sb.append(locations); + + return sb.toString(); + } + + @SuppressWarnings("unchecked") + private TypePrinter printer(Class clazz) { + TypePrinter typePrinter = printers.get(clazz); + if (typePrinter == null) { + Class superClazz = clazz.getSuperclass(); + if (superClazz != Object.class) { + typePrinter = printer(superClazz); + } else { + typePrinter = (out, type, visibility) -> out.println("Type not implemented : " + type); + } + printers.put(clazz, typePrinter); + } + return (TypePrinter) typePrinter; + } + + public String print(GraphQLType type) { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + + printType(out, type, DEFAULT_FIELD_VISIBILITY); + + return sw.toString(); + } + + public String print(GraphQLDirective graphQLDirective) { + return directiveDefinition(graphQLDirective); + } + + private void printType( + PrintWriter out, + List typesAsList, + Class typeClazz, + GraphqlFieldVisibility visibility) { + typesAsList.stream() + .filter(type -> typeClazz.isAssignableFrom(type.getClass())) + .filter(type -> options.getIncludeSchemaElement().test(type)) + .forEach(type -> printType(out, type, visibility)); + } + + private void printType(PrintWriter out, GraphQLType type, GraphqlFieldVisibility visibility) { + TypePrinter printer = printer(type.getClass()); + printer.print(out, type, visibility); + } + + private String printComments(Object graphQLType, String prefix) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + printComments(pw, graphQLType, prefix); + return sw.toString(); + } + + private void printComments(PrintWriter out, Object graphQLType, String prefix) { + + String descriptionText = getDescription(graphQLType); + if (isNullOrEmpty(descriptionText)) { + return; } - private String getDescription(Object descriptionHolder) { - if (descriptionHolder instanceof GraphQLObjectType) { - GraphQLObjectType type = (GraphQLObjectType) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(ObjectTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLEnumType) { - GraphQLEnumType type = (GraphQLEnumType) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(EnumTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLFieldDefinition) { - GraphQLFieldDefinition type = (GraphQLFieldDefinition) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(FieldDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLEnumValueDefinition) { - GraphQLEnumValueDefinition type = (GraphQLEnumValueDefinition) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(EnumValueDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLUnionType) { - GraphQLUnionType type = (GraphQLUnionType) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(UnionTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLInputObjectType) { - GraphQLInputObjectType type = (GraphQLInputObjectType) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(InputObjectTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLInputObjectField) { - GraphQLInputObjectField type = (GraphQLInputObjectField) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(InputValueDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLInterfaceType) { - GraphQLInterfaceType type = (GraphQLInterfaceType) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(InterfaceTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLScalarType) { - GraphQLScalarType type = (GraphQLScalarType) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(ScalarTypeDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLArgument) { - GraphQLArgument type = (GraphQLArgument) descriptionHolder; - return description(type.getDescription(), ofNullable(type.getDefinition()).map(InputValueDefinition::getDescription).orElse(null)); - } else if (descriptionHolder instanceof GraphQLDirective) { - GraphQLDirective type = (GraphQLDirective) descriptionHolder; - return description(type.getDescription(), null); + if (!isNullOrEmpty(descriptionText)) { + List lines = Arrays.asList(descriptionText.split("\n")); + if (options.isDescriptionsAsHashComments()) { + printMultiLineHashDescription(out, prefix, lines); + } else if (!lines.isEmpty()) { + if (lines.size() > 1) { + printMultiLineDescription(out, prefix, lines); } else { - return Assert.assertShouldNeverHappen(); + printSingleLineDescription(out, prefix, lines.get(0)); } + } } - - String description(String runtimeDescription, Description descriptionAst) { - // - // 95% of the time if the schema was built from SchemaGenerator then the runtime description is the only description - // So the other code here is a really defensive way to get the description - // - String descriptionText = runtimeDescription; - if (isNullOrEmpty(descriptionText)) { - if (descriptionAst != null) { - descriptionText = descriptionAst.getContent(); - } - } - return descriptionText; + } + + private void printMultiLineHashDescription(PrintWriter out, String prefix, List lines) { + lines.forEach(l -> out.printf("%s#%s\n", prefix, l)); + } + + private void printMultiLineDescription(PrintWriter out, String prefix, List lines) { + out.printf("%s\"\"\"\n", prefix); + lines.forEach( + l -> { + String escapedTripleQuotes = l.replaceAll("\"\"\"", "\\\\\"\"\""); + out.printf("%s%s\n", prefix, escapedTripleQuotes); + }); + out.printf("%s\"\"\"\n", prefix); + } + + private void printSingleLineDescription(PrintWriter out, String prefix, String s) { + // See: https://github.com/graphql/graphql-spec/issues/148 + String desc = escapeJsonString(s); + out.printf("%s\"%s\"\n", prefix, desc); + } + + private boolean hasDescription(Object descriptionHolder) { + String description = getDescription(descriptionHolder); + return !isNullOrEmpty(description); + } + + private String getDescription(Object descriptionHolder) { + if (descriptionHolder instanceof GraphQLObjectType) { + GraphQLObjectType type = (GraphQLObjectType) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()).map(ObjectTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLEnumType) { + GraphQLEnumType type = (GraphQLEnumType) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()).map(EnumTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLFieldDefinition) { + GraphQLFieldDefinition type = (GraphQLFieldDefinition) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()).map(FieldDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLEnumValueDefinition) { + GraphQLEnumValueDefinition type = (GraphQLEnumValueDefinition) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()).map(EnumValueDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLUnionType) { + GraphQLUnionType type = (GraphQLUnionType) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()).map(UnionTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLInputObjectType) { + GraphQLInputObjectType type = (GraphQLInputObjectType) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()) + .map(InputObjectTypeDefinition::getDescription) + .orElse(null)); + } else if (descriptionHolder instanceof GraphQLInputObjectField) { + GraphQLInputObjectField type = (GraphQLInputObjectField) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()).map(InputValueDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLInterfaceType) { + GraphQLInterfaceType type = (GraphQLInterfaceType) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()) + .map(InterfaceTypeDefinition::getDescription) + .orElse(null)); + } else if (descriptionHolder instanceof GraphQLScalarType) { + GraphQLScalarType type = (GraphQLScalarType) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()).map(ScalarTypeDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLArgument) { + GraphQLArgument type = (GraphQLArgument) descriptionHolder; + return description( + type.getDescription(), + ofNullable(type.getDefinition()).map(InputValueDefinition::getDescription).orElse(null)); + } else if (descriptionHolder instanceof GraphQLDirective) { + GraphQLDirective type = (GraphQLDirective) descriptionHolder; + return description(type.getDescription(), null); + } else { + return Assert.assertShouldNeverHappen(); } + } - private static boolean isNullOrEmpty(String s) { - return s == null || s.isEmpty(); + String description(String runtimeDescription, Description descriptionAst) { + // + // 95% of the time if the schema was built from SchemaGenerator then the runtime description is + // the only description + // So the other code here is a really defensive way to get the description + // + String descriptionText = runtimeDescription; + if (isNullOrEmpty(descriptionText)) { + if (descriptionAst != null) { + descriptionText = descriptionAst.getContent(); + } } + return descriptionText; + } + + private static boolean isNullOrEmpty(String s) { + return s == null || s.isEmpty(); + } } From 956a23c68d322fb6490ca1f8bbbba518eac3d21d Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 01:48:08 -0700 Subject: [PATCH 07/10] Bump graphql-java-kickstart artifacts to 11.1.0 and graphql-java-tools to 11.1.2 Co-authored-by: Martin Bonnin Co-authored-by: Bernardo Gomez Palacio --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 23b1e9e6..93edff48 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,8 @@ false 2.7 17.3 - 11.0.0 + 11.1.0 + 11.1.2 1.8 17.0.0 5.7.1 @@ -120,7 +121,7 @@ com.graphql-java-kickstart graphql-java-tools - ${graphql-java-kickstart.version} + ${graphql-java-tools.version} com.graphql-java From 8ce17ee380536b86f4b8af7b8ff2b971be33609d Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 02:17:21 -0700 Subject: [PATCH 08/10] Bumped Spring Boot artifacts to 2.5.4, slf4j to 1.7.32, junit to 5.7.2, and Jackson to 2.12.5, to satisfy Maven enforcer upper bounds in spring-example. We also remove an unneeded dependencyManagement section for spring-aop Co-authored-by: Martin Bonnin Co-authored-by: Bernardo Gomez Palacio --- pom.xml | 6 +++--- spring-example/pom.xml | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 93edff48..bf64ebfb 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ 11.1.2 1.8 17.0.0 - 5.7.1 + 5.7.2 3.1.2 true - 2.12.0 + 2.12.5 @@ -49,11 +49,6 @@ jackson-datatype-jdk8 ${fasterxml.jackson.version} - - org.springframework - spring-aop - 5.2.11.RELEASE - From e8ad8b385117891d787961421fa6d4ec46bce031 Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 02:35:04 -0700 Subject: [PATCH 09/10] Force com.graphql-java:graphql-java-extended-scalars to 17.0 in spring-example to fix bug in graphql-spring-boot-starter Co-authored-by: Martin Bonnin Co-authored-by: Bernardo Gomez Palacio --- spring-example/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spring-example/pom.xml b/spring-example/pom.xml index 3810c618..a108da44 100644 --- a/spring-example/pom.xml +++ b/spring-example/pom.xml @@ -49,6 +49,16 @@ jackson-datatype-jdk8 ${fasterxml.jackson.version} + + + com.graphql-java + graphql-java-extended-scalars + 17.0 + From 7b60c82c9c949f56974839aeee094bb3863fffa4 Mon Sep 17 00:00:00 2001 From: "Sachin D. Shinde" Date: Wed, 22 Sep 2021 09:47:00 -0700 Subject: [PATCH 10/10] Move spring-example dependencyManagement (and relevant properties) to the child POM, as they're not shared Co-authored-by: Martin Bonnin --- pom.xml | 41 ----------------------------------------- spring-example/pom.xml | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/pom.xml b/pom.xml index bf64ebfb..dbaab4df 100644 --- a/pom.xml +++ b/pom.xml @@ -77,8 +77,6 @@ false 2.7 17.3 - 11.1.0 - 11.1.2 1.8 17.0.0 5.7.2 @@ -96,33 +94,10 @@ 1.6.8 1.7.32 2.12.2 - 2.5.4 - - com.graphql-java-kickstart - graphql-spring-boot-starter - ${graphql-java-kickstart.version} - - - com.graphql-java-kickstart - graphiql-spring-boot-starter - ${graphql-java-kickstart.version} - runtime - - - com.graphql-java-kickstart - graphql-spring-boot-starter-test - ${graphql-java-kickstart.version} - test - - - com.graphql-java-kickstart - graphql-java-tools - ${graphql-java-tools.version} - com.graphql-java graphql-java @@ -145,22 +120,6 @@ slf4j-api ${slf4j.version} - - org.springframework.boot - spring-boot-starter-actuator - ${spring-boot.version} - - - org.springframework.boot - spring-boot-starter-web - ${spring-boot.version} - - - org.springframework.boot - spring-boot-starter-test - ${spring-boot.version} - test - diff --git a/spring-example/pom.xml b/spring-example/pom.xml index a108da44..76615cf1 100644 --- a/spring-example/pom.xml +++ b/spring-example/pom.xml @@ -16,13 +16,54 @@ Spring Boot example of federation-graphql-java-support usage + 11.1.0 + 11.1.2 true 2.12.5 + 2.5.4 + + com.graphql-java-kickstart + graphql-spring-boot-starter + ${graphql-java-kickstart.version} + + + com.graphql-java-kickstart + graphiql-spring-boot-starter + ${graphql-java-kickstart.version} + runtime + + + com.graphql-java-kickstart + graphql-spring-boot-starter-test + ${graphql-java-kickstart.version} + test + + + com.graphql-java-kickstart + graphql-java-tools + ${graphql-java-tools.version} + + + org.springframework.boot + spring-boot-starter-actuator + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot.version} + test +