From 1b0d1647ce89a89961f1b24fce2097443867428b Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Fri, 21 Oct 2022 17:37:12 -0600 Subject: [PATCH 01/10] use ast builders and stub test --- build.gradle | 10 +- .../introspection/IntrospectionQuery.java | 302 ++++++++++++------ .../introspection/IntrospectionTest.groovy | 29 ++ 3 files changed, 234 insertions(+), 107 deletions(-) diff --git a/build.gradle b/build.gradle index 37dcab18a..e493e69e5 100644 --- a/build.gradle +++ b/build.gradle @@ -39,11 +39,11 @@ def getDevelopmentVersion() { version } -if (JavaVersion.current() != JavaVersion.VERSION_1_8) { - def msg = String.format("This build must be run with java 1.8 - you are running %s - gradle finds the JDK via JAVA_HOME=%s", - JavaVersion.current(), System.getenv("JAVA_HOME")) - throw new GradleException(msg) -} +//if (JavaVersion.current() != JavaVersion.VERSION_1_8) { +// def msg = String.format("This build must be run with java 1.8 - you are running %s - gradle finds the JDK via JAVA_HOME=%s", +// JavaVersion.current(), System.getenv("JAVA_HOME")) +// throw new GradleException(msg) +//} sourceCompatibility = 1.8 diff --git a/src/main/java/graphql/introspection/IntrospectionQuery.java b/src/main/java/graphql/introspection/IntrospectionQuery.java index f93617951..9ed1e61e0 100644 --- a/src/main/java/graphql/introspection/IntrospectionQuery.java +++ b/src/main/java/graphql/introspection/IntrospectionQuery.java @@ -1,109 +1,207 @@ package graphql.introspection; +import com.google.common.collect.ImmutableList; import graphql.PublicApi; +import graphql.language.Argument; +import graphql.language.AstPrinter; +import graphql.language.BooleanValue; +import graphql.language.Document; +import graphql.language.Field; +import graphql.language.FragmentDefinition; +import graphql.language.FragmentSpread; +import graphql.language.OperationDefinition; +import graphql.language.SelectionSet; +import graphql.language.TypeName; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; @PublicApi -public interface IntrospectionQuery { +public interface IntrospectionQuery { // todo iwds support + static void main(String... args) { + System.out.println(INTROSPECTION_QUERY); + } + + static List filter(T... args) { + return Arrays.stream(args).filter(Objects::nonNull).collect(Collectors.toList()); + } + + static String getIntrospectionQuery( + boolean descriptions, + boolean specifiedByUrl, + boolean directiveIsRepeatable, + boolean schemaDescription, + boolean inputValueDeprecation, + int typeRefFragmentDepth + ) { + SelectionSet schemaSelectionSet = SelectionSet.newSelectionSet().selections( filter( + schemaDescription ? Field.newField("description").build() : null, + Field.newField("queryType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("mutationType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("subscriptionType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("types", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("FullType").build() ) + .build() + ) + .build(), + Field.newField("directives", SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + descriptions ? Field.newField("description").build() : null, + Field.newField("locations").build(), + Field.newField("args") + .arguments( filter( + inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + directiveIsRepeatable ? Field.newField("isRepeatable").build() : null + ) ) + .build() + ) + .build() + ) + ).build(); + + SelectionSet fullTypeSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build(), + descriptions ? Field.newField("description").build() : null, + specifiedByUrl ? Field.newField("specifiedByUrl").build() : null, + Field.newField("fields") + .arguments( ImmutableList.of( + Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() + ) ) + .selectionSet( SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + descriptions ? Field.newField("description").build() : null, + Field.newField("args") + .arguments( filter( + inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + Field.newField("type", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("isDeprecated").build(), + Field.newField("deprecationReason").build() + ) ).build() + ) + .build(), + Field.newField("inputFields") + .arguments( filter( + inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + Field.newField("interfaces", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("enumValues") + .arguments( ImmutableList.of( + Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() + ) ) + .selectionSet( SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + descriptions ? Field.newField("description").build() : null, + Field.newField("isDeprecated").build(), + Field.newField("deprecationReason").build() + ) ) + .build() + ) + .build(), + Field.newField("possibleTypes", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build() + ) ).build(); + + SelectionSet inputValueSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + descriptions ? Field.newField("description").build() : null, + Field.newField("type", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("defaultValue").build(), + inputValueDeprecation ? Field.newField("isDeprecated").build() : null, + inputValueDeprecation ? Field.newField("deprecationReason").build() : null + ) ).build(); + + SelectionSet typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build() + ) ).build(); + + for(int i=typeRefFragmentDepth; i>0; i-=1) { + typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build(), + Field.newField("ofType", typeRefSelectionSet).build() + ) ).build(); + } + + Document query = Document.newDocument() + .definition( OperationDefinition.newOperationDefinition() + .operation(OperationDefinition.Operation.QUERY) + .name("IntrospectionQuery") + .selectionSet( SelectionSet.newSelectionSet() + .selection( Field.newField("__schema", schemaSelectionSet).build() ) + .build() + ) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("FullType") + .typeCondition( TypeName.newTypeName().name("__Type").build() ) + .selectionSet(fullTypeSelectionSet) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("InputValue") + .typeCondition( TypeName.newTypeName().name("__InputValue").build() ) + .selectionSet(inputValueSelectionSet) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("TypeRef") + .typeCondition( TypeName.newTypeName().name("__Type").build() ) + .selectionSet(typeRefSelectionSet) + .build() + ) + .build(); + + return AstPrinter.printAst(query); + } - String INTROSPECTION_QUERY = "\n" + - " query IntrospectionQuery {\n" + - " __schema {\n" + - " queryType { name }\n" + - " mutationType { name }\n" + - " subscriptionType { name }\n" + - " types {\n" + - " ...FullType\n" + - " }\n" + - " directives {\n" + - " name\n" + - " description\n" + - " locations\n" + - " args(includeDeprecated: true) {\n" + - " ...InputValue\n" + - " }\n" + - " isRepeatable\n" + - " }\n" + - " }\n" + - " }\n" + - "\n" + - " fragment FullType on __Type {\n" + - " kind\n" + - " name\n" + - " description\n" + - " fields(includeDeprecated: true) {\n" + - " name\n" + - " description\n" + - " args(includeDeprecated: true) {\n" + - " ...InputValue\n" + - " }\n" + - " type {\n" + - " ...TypeRef\n" + - " }\n" + - " isDeprecated\n" + - " deprecationReason\n" + - " }\n" + - " inputFields(includeDeprecated: true) {\n" + - " ...InputValue\n" + - " }\n" + - " interfaces {\n" + - " ...TypeRef\n" + - " }\n" + - " enumValues(includeDeprecated: true) {\n" + - " name\n" + - " description\n" + - " isDeprecated\n" + - " deprecationReason\n" + - " }\n" + - " possibleTypes {\n" + - " ...TypeRef\n" + - " }\n" + - " }\n" + - "\n" + - " fragment InputValue on __InputValue {\n" + - " name\n" + - " description\n" + - " type { ...TypeRef }\n" + - " defaultValue\n" + - " isDeprecated\n" + - " deprecationReason\n" + - " }\n" + - "\n" + - // - // The depth of the types is actually an arbitrary decision. It could be any depth in fact. This depth - // was taken from GraphIQL https://github.com/graphql/graphiql/blob/master/src/utility/introspectionQueries.js - // which uses 7 levels and hence could represent a type like say [[[[[Float!]]]]] - // - "fragment TypeRef on __Type {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - "\n"; + String INTROSPECTION_QUERY = getIntrospectionQuery(true, false, true, false, true, 7); } diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 18f259cbe..03d0d6925 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -435,4 +435,33 @@ class IntrospectionTest extends Specification { graphql.execute(query).data == [__type: [fields: [[args: [[defaultValue: '{inputField : "foo"}']]]]]] } + + def "test parameterized introspection queries"() { + def spec = ''' + + directive @someDirective( + deprecatedArg : String @deprecated + notDeprecatedArg : String + ) on FIELD + + type Query { + namedField(arg : InputType @deprecated, notDeprecatedArg : InputType ) : Enum @deprecated + notDeprecated(arg : InputType @deprecated, notDeprecatedArg : InputType) : Enum + } + enum Enum { + RED @deprecated + BLUE + } + input InputType { + inputField : String @deprecated + notDeprecatedInputField : String + } + ''' + + def graphQL = TestUtil.graphQL(spec).build() + + def allFalseExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(false, false, false, false, false, 7)) + + def allTrueExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(true, true, true, true, true, 7)) + } } From 966309e0ca25d8823cd10c5a63b42d878b0e05c6 Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Mon, 24 Oct 2022 13:41:14 -0600 Subject: [PATCH 02/10] remove main and add test --- .../introspection/IntrospectionQuery.java | 4 -- .../introspection/IntrospectionTest.groovy | 39 ++++++++++++++++--- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/main/java/graphql/introspection/IntrospectionQuery.java b/src/main/java/graphql/introspection/IntrospectionQuery.java index 9ed1e61e0..38dad824e 100644 --- a/src/main/java/graphql/introspection/IntrospectionQuery.java +++ b/src/main/java/graphql/introspection/IntrospectionQuery.java @@ -20,10 +20,6 @@ @PublicApi public interface IntrospectionQuery { // todo iwds support - static void main(String... args) { - System.out.println(INTROSPECTION_QUERY); - } - static List filter(T... args) { return Arrays.stream(args).filter(Objects::nonNull).collect(Collectors.toList()); } diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 03d0d6925..2079944cc 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -226,6 +226,7 @@ class IntrospectionTest extends Specification { def "can filter out deprecated things in introspection"() { def spec = ''' + directive @someDirective( deprecatedArg : String @deprecated @@ -438,15 +439,24 @@ class IntrospectionTest extends Specification { def "test parameterized introspection queries"() { def spec = ''' + scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") + + directive @repeatableDirective(arg: String) repeatable on FIELD + + """schema description""" + schema { + query: Query + } directive @someDirective( deprecatedArg : String @deprecated notDeprecatedArg : String - ) on FIELD + ) repeatable on FIELD type Query { - namedField(arg : InputType @deprecated, notDeprecatedArg : InputType ) : Enum @deprecated + """notDeprecated root field description""" notDeprecated(arg : InputType @deprecated, notDeprecatedArg : InputType) : Enum + tenDimensionalList : [[[[[[[[[[String]]]]]]]]]] } enum Enum { RED @deprecated @@ -454,14 +464,33 @@ class IntrospectionTest extends Specification { } input InputType { inputField : String @deprecated - notDeprecatedInputField : String } ''' def graphQL = TestUtil.graphQL(spec).build() - def allFalseExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(false, false, false, false, false, 7)) + def parseExecutionResult = { + [ + it.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "notDeprecated"}["description"] == null, // descriptions + it.data["__schema"]["types"].find{it["name"] == "UUID"}["specifiedByUrl"] == null, // specifiedByUrl + it.data["__schema"]["directives"].find{it["name"] == "repeatableDirective"}["isRepeatable"] == null, // directiveIsRepeatable + it.data["__schema"]["description"] == null, // schemaDescription + it.data["__schema"]["types"].find { it['name'] == 'InputType' }["inputFields"].find({ it["name"] == "inputField" }) == null // inputValueDeprecation + ] + } + + when: + def allFalseExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(false, false, false, false, false, 5)) + then: + parseExecutionResult(allFalseExecutionResult).every() + allFalseExecutionResult.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "tenDimensionalList"}["type"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"] == null // typeRefFragmentDepth is 5 + true - def allTrueExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(true, true, true, true, true, 7)) + when: + def allTrueExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(true, true, true, true, true, 7)) + then: + !parseExecutionResult(allTrueExecutionResult).any() + allTrueExecutionResult.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "tenDimensionalList"}["type"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"] == null // typeRefFragmentDepth is 7 + true } } From f64f1e62ca2e5c0f4bd340ea2b5e37d4327fef7e Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Mon, 24 Oct 2022 13:47:14 -0600 Subject: [PATCH 03/10] restore jvm check --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index e493e69e5..37dcab18a 100644 --- a/build.gradle +++ b/build.gradle @@ -39,11 +39,11 @@ def getDevelopmentVersion() { version } -//if (JavaVersion.current() != JavaVersion.VERSION_1_8) { -// def msg = String.format("This build must be run with java 1.8 - you are running %s - gradle finds the JDK via JAVA_HOME=%s", -// JavaVersion.current(), System.getenv("JAVA_HOME")) -// throw new GradleException(msg) -//} +if (JavaVersion.current() != JavaVersion.VERSION_1_8) { + def msg = String.format("This build must be run with java 1.8 - you are running %s - gradle finds the JDK via JAVA_HOME=%s", + JavaVersion.current(), System.getenv("JAVA_HOME")) + throw new GradleException(msg) +} sourceCompatibility = 1.8 From 438ce16f38045222272d54138da87fa05026645a Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Mon, 24 Oct 2022 13:50:46 -0600 Subject: [PATCH 04/10] revert test spacing --- src/test/groovy/graphql/introspection/IntrospectionTest.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 2079944cc..8bbacb673 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -226,7 +226,6 @@ class IntrospectionTest extends Specification { def "can filter out deprecated things in introspection"() { def spec = ''' - directive @someDirective( deprecatedArg : String @deprecated From f0ee6741cade0ecaf6f346de1fea3d28eb0cd218 Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Mon, 24 Oct 2022 14:21:14 -0600 Subject: [PATCH 05/10] fix test --- build.gradle | 10 +++++----- .../introspection/IntrospectionTest.groovy | 16 +++++++--------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 37dcab18a..e493e69e5 100644 --- a/build.gradle +++ b/build.gradle @@ -39,11 +39,11 @@ def getDevelopmentVersion() { version } -if (JavaVersion.current() != JavaVersion.VERSION_1_8) { - def msg = String.format("This build must be run with java 1.8 - you are running %s - gradle finds the JDK via JAVA_HOME=%s", - JavaVersion.current(), System.getenv("JAVA_HOME")) - throw new GradleException(msg) -} +//if (JavaVersion.current() != JavaVersion.VERSION_1_8) { +// def msg = String.format("This build must be run with java 1.8 - you are running %s - gradle finds the JDK via JAVA_HOME=%s", +// JavaVersion.current(), System.getenv("JAVA_HOME")) +// throw new GradleException(msg) +//} sourceCompatibility = 1.8 diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 8bbacb673..5fafb73de 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -470,26 +470,24 @@ class IntrospectionTest extends Specification { def parseExecutionResult = { [ - it.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "notDeprecated"}["description"] == null, // descriptions - it.data["__schema"]["types"].find{it["name"] == "UUID"}["specifiedByUrl"] == null, // specifiedByUrl - it.data["__schema"]["directives"].find{it["name"] == "repeatableDirective"}["isRepeatable"] == null, // directiveIsRepeatable - it.data["__schema"]["description"] == null, // schemaDescription - it.data["__schema"]["types"].find { it['name'] == 'InputType' }["inputFields"].find({ it["name"] == "inputField" }) == null // inputValueDeprecation + it.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "notDeprecated"}["description"] != null, // descriptions is true + it.data["__schema"]["types"].find{it["name"] == "UUID"}["specifiedByUrl"] != null, // specifiedByUrl is true + it.data["__schema"]["directives"].find{it["name"] == "repeatableDirective"}["isRepeatable"] != null, // directiveIsRepeatable is true + it.data["__schema"]["description"] != null, // schemaDescription is true + it.data["__schema"]["types"].find { it['name'] == 'InputType' }["inputFields"].find({ it["name"] == "inputField" }) != null // inputValueDeprecation is true ] } when: def allFalseExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(false, false, false, false, false, 5)) then: - parseExecutionResult(allFalseExecutionResult).every() + !parseExecutionResult(allFalseExecutionResult).any() allFalseExecutionResult.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "tenDimensionalList"}["type"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"] == null // typeRefFragmentDepth is 5 - true when: def allTrueExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(true, true, true, true, true, 7)) then: - !parseExecutionResult(allTrueExecutionResult).any() + parseExecutionResult(allTrueExecutionResult).every() allTrueExecutionResult.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "tenDimensionalList"}["type"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"] == null // typeRefFragmentDepth is 7 - true } } From 887a9c798a054f9b3349d173f3e58512c5f738fc Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Mon, 24 Oct 2022 14:21:35 -0600 Subject: [PATCH 06/10] revers build.gradle --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index e493e69e5..37dcab18a 100644 --- a/build.gradle +++ b/build.gradle @@ -39,11 +39,11 @@ def getDevelopmentVersion() { version } -//if (JavaVersion.current() != JavaVersion.VERSION_1_8) { -// def msg = String.format("This build must be run with java 1.8 - you are running %s - gradle finds the JDK via JAVA_HOME=%s", -// JavaVersion.current(), System.getenv("JAVA_HOME")) -// throw new GradleException(msg) -//} +if (JavaVersion.current() != JavaVersion.VERSION_1_8) { + def msg = String.format("This build must be run with java 1.8 - you are running %s - gradle finds the JDK via JAVA_HOME=%s", + JavaVersion.current(), System.getenv("JAVA_HOME")) + throw new GradleException(msg) +} sourceCompatibility = 1.8 From 2b1b786011121df9f4977c003301b72f17fdf69e Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Mon, 24 Oct 2022 14:33:01 -0600 Subject: [PATCH 07/10] use spec specifiedByURL capitalization --- src/main/java/graphql/introspection/Introspection.java | 6 +++--- src/main/java/graphql/introspection/IntrospectionQuery.java | 2 +- src/test/groovy/graphql/GraphQLTest.groovy | 2 +- .../groovy/graphql/introspection/IntrospectionTest.groovy | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/introspection/Introspection.java b/src/main/java/graphql/introspection/Introspection.java index 9db9414bc..555005923 100644 --- a/src/main/java/graphql/introspection/Introspection.java +++ b/src/main/java/graphql/introspection/Introspection.java @@ -90,7 +90,7 @@ public enum TypeKind { public static final GraphQLEnumType __TypeKind = GraphQLEnumType.newEnum() .name("__TypeKind") .description("An enum describing what kind of type a given __Type is") - .value("SCALAR", TypeKind.SCALAR, "Indicates this type is a scalar. 'specifiedByUrl' is a valid field") + .value("SCALAR", TypeKind.SCALAR, "Indicates this type is a scalar. 'specifiedByURL' is a valid field") .value("OBJECT", TypeKind.OBJECT, "Indicates this type is an object. `fields` and `interfaces` are valid fields.") .value("INTERFACE", TypeKind.INTERFACE, "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.") .value("UNION", TypeKind.UNION, "Indicates this type is a union. `possibleTypes` is a valid field.") @@ -398,7 +398,7 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, .name("ofType") .type(typeRef("__Type"))) .field(newFieldDefinition() - .name("specifiedByUrl") + .name("specifiedByURL") .type(GraphQLString)) .build(); @@ -412,7 +412,7 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, register(__Type, "ofType", OfTypeFetcher); register(__Type, "name", nameDataFetcher); register(__Type, "description", descriptionDataFetcher); - register(__Type, "specifiedByUrl", specifiedByUrlDataFetcher); + register(__Type, "specifiedByURL", specifiedByUrlDataFetcher); } diff --git a/src/main/java/graphql/introspection/IntrospectionQuery.java b/src/main/java/graphql/introspection/IntrospectionQuery.java index 38dad824e..d98463d4a 100644 --- a/src/main/java/graphql/introspection/IntrospectionQuery.java +++ b/src/main/java/graphql/introspection/IntrospectionQuery.java @@ -79,7 +79,7 @@ static String getIntrospectionQuery( Field.newField("kind").build(), Field.newField("name").build(), descriptions ? Field.newField("description").build() : null, - specifiedByUrl ? Field.newField("specifiedByUrl").build() : null, + specifiedByUrl ? Field.newField("specifiedByURL").build() : null, Field.newField("fields") .arguments( ImmutableList.of( Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 505350e74..754f6c90c 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -1251,7 +1251,7 @@ many lines'''] GraphQLSchema schema = TestUtil.schema('type Query {foo: MyScalar} scalar MyScalar @specifiedBy(url:"myUrl")') when: - def result = GraphQL.newGraphQL(schema).build().execute('{__type(name: "MyScalar") {name specifiedByUrl}}').getData() + def result = GraphQL.newGraphQL(schema).build().execute('{__type(name: "MyScalar") {name specifiedByURL}}').getData() then: result == [__type: [name: "MyScalar", specifiedByUrl: "myUrl"]] diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 5fafb73de..925ec1ca0 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -471,7 +471,7 @@ class IntrospectionTest extends Specification { def parseExecutionResult = { [ it.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "notDeprecated"}["description"] != null, // descriptions is true - it.data["__schema"]["types"].find{it["name"] == "UUID"}["specifiedByUrl"] != null, // specifiedByUrl is true + it.data["__schema"]["types"].find{it["name"] == "UUID"}["specifiedByURL"] != null, // specifiedByUrl is true it.data["__schema"]["directives"].find{it["name"] == "repeatableDirective"}["isRepeatable"] != null, // directiveIsRepeatable is true it.data["__schema"]["description"] != null, // schemaDescription is true it.data["__schema"]["types"].find { it['name'] == 'InputType' }["inputFields"].find({ it["name"] == "inputField" }) != null // inputValueDeprecation is true From e0adefe411c9f3e102467acae033605050be05cc Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Tue, 25 Oct 2022 11:51:02 -0600 Subject: [PATCH 08/10] update another specifiedByURL test --- src/test/groovy/graphql/GraphQLTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 754f6c90c..ef2d4e19f 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -1254,7 +1254,7 @@ many lines'''] def result = GraphQL.newGraphQL(schema).build().execute('{__type(name: "MyScalar") {name specifiedByURL}}').getData() then: - result == [__type: [name: "MyScalar", specifiedByUrl: "myUrl"]] + result == [__type: [name: "MyScalar", specifiedByURL: "myUrl"]] } def "test DFR and CF"() { From adafa6a3d73228a92619ef9861b0942d0f2d57dd Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Wed, 26 Oct 2022 23:32:31 -0600 Subject: [PATCH 09/10] refactor introspection query builder, restore and deprecate __Type.specifiedByUrl --- .../graphql/introspection/Introspection.java | 6 + .../introspection/IntrospectionQuery.java | 199 +--------- .../IntrospectionQueryBuilder.java | 356 ++++++++++++++++++ .../introspection/IntrospectionTest.groovy | 24 +- 4 files changed, 386 insertions(+), 199 deletions(-) create mode 100644 src/main/java/graphql/introspection/IntrospectionQueryBuilder.java diff --git a/src/main/java/graphql/introspection/Introspection.java b/src/main/java/graphql/introspection/Introspection.java index 555005923..15c41f3a7 100644 --- a/src/main/java/graphql/introspection/Introspection.java +++ b/src/main/java/graphql/introspection/Introspection.java @@ -400,6 +400,11 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, .field(newFieldDefinition() .name("specifiedByURL") .type(GraphQLString)) + .field(newFieldDefinition() + .name("specifiedByUrl") + .type(GraphQLString) + .deprecate("see `specifiedByURL`") + ) .build(); static { @@ -413,6 +418,7 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, register(__Type, "name", nameDataFetcher); register(__Type, "description", descriptionDataFetcher); register(__Type, "specifiedByURL", specifiedByUrlDataFetcher); + register(__Type, "specifiedByUrl", specifiedByUrlDataFetcher); // note that this field is deprecated } diff --git a/src/main/java/graphql/introspection/IntrospectionQuery.java b/src/main/java/graphql/introspection/IntrospectionQuery.java index d98463d4a..4d372fe99 100644 --- a/src/main/java/graphql/introspection/IntrospectionQuery.java +++ b/src/main/java/graphql/introspection/IntrospectionQuery.java @@ -1,203 +1,8 @@ package graphql.introspection; -import com.google.common.collect.ImmutableList; import graphql.PublicApi; -import graphql.language.Argument; -import graphql.language.AstPrinter; -import graphql.language.BooleanValue; -import graphql.language.Document; -import graphql.language.Field; -import graphql.language.FragmentDefinition; -import graphql.language.FragmentSpread; -import graphql.language.OperationDefinition; -import graphql.language.SelectionSet; -import graphql.language.TypeName; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; @PublicApi -public interface IntrospectionQuery { // todo iwds support - static List filter(T... args) { - return Arrays.stream(args).filter(Objects::nonNull).collect(Collectors.toList()); - } - - static String getIntrospectionQuery( - boolean descriptions, - boolean specifiedByUrl, - boolean directiveIsRepeatable, - boolean schemaDescription, - boolean inputValueDeprecation, - int typeRefFragmentDepth - ) { - SelectionSet schemaSelectionSet = SelectionSet.newSelectionSet().selections( filter( - schemaDescription ? Field.newField("description").build() : null, - Field.newField("queryType", SelectionSet.newSelectionSet() - .selection( Field.newField("name").build() ) - .build() - ) - .build(), - Field.newField("mutationType", SelectionSet.newSelectionSet() - .selection( Field.newField("name").build() ) - .build() - ) - .build(), - Field.newField("subscriptionType", SelectionSet.newSelectionSet() - .selection( Field.newField("name").build() ) - .build() - ) - .build(), - Field.newField("types", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("FullType").build() ) - .build() - ) - .build(), - Field.newField("directives", SelectionSet.newSelectionSet().selections( filter( - Field.newField("name").build(), - descriptions ? Field.newField("description").build() : null, - Field.newField("locations").build(), - Field.newField("args") - .arguments( filter( - inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null - ) ) - .selectionSet( SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) - .build() - ) - .build(), - directiveIsRepeatable ? Field.newField("isRepeatable").build() : null - ) ) - .build() - ) - .build() - ) - ).build(); - - SelectionSet fullTypeSelectionSet = SelectionSet.newSelectionSet().selections( filter( - Field.newField("kind").build(), - Field.newField("name").build(), - descriptions ? Field.newField("description").build() : null, - specifiedByUrl ? Field.newField("specifiedByURL").build() : null, - Field.newField("fields") - .arguments( ImmutableList.of( - Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() - ) ) - .selectionSet( SelectionSet.newSelectionSet().selections( filter( - Field.newField("name").build(), - descriptions ? Field.newField("description").build() : null, - Field.newField("args") - .arguments( filter( - inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null - ) ) - .selectionSet( SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) - .build() - ) - .build(), - Field.newField("type", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) - .build() - ) - .build(), - Field.newField("isDeprecated").build(), - Field.newField("deprecationReason").build() - ) ).build() - ) - .build(), - Field.newField("inputFields") - .arguments( filter( - inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null - ) ) - .selectionSet( SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) - .build() - ) - .build(), - Field.newField("interfaces", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) - .build() - ) - .build(), - Field.newField("enumValues") - .arguments( ImmutableList.of( - Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() - ) ) - .selectionSet( SelectionSet.newSelectionSet().selections( filter( - Field.newField("name").build(), - descriptions ? Field.newField("description").build() : null, - Field.newField("isDeprecated").build(), - Field.newField("deprecationReason").build() - ) ) - .build() - ) - .build(), - Field.newField("possibleTypes", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) - .build() - ) - .build() - ) ).build(); - - SelectionSet inputValueSelectionSet = SelectionSet.newSelectionSet().selections( filter( - Field.newField("name").build(), - descriptions ? Field.newField("description").build() : null, - Field.newField("type", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) - .build() - ) - .build(), - Field.newField("defaultValue").build(), - inputValueDeprecation ? Field.newField("isDeprecated").build() : null, - inputValueDeprecation ? Field.newField("deprecationReason").build() : null - ) ).build(); - - SelectionSet typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( - Field.newField("kind").build(), - Field.newField("name").build() - ) ).build(); - - for(int i=typeRefFragmentDepth; i>0; i-=1) { - typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( - Field.newField("kind").build(), - Field.newField("name").build(), - Field.newField("ofType", typeRefSelectionSet).build() - ) ).build(); - } - - Document query = Document.newDocument() - .definition( OperationDefinition.newOperationDefinition() - .operation(OperationDefinition.Operation.QUERY) - .name("IntrospectionQuery") - .selectionSet( SelectionSet.newSelectionSet() - .selection( Field.newField("__schema", schemaSelectionSet).build() ) - .build() - ) - .build() - ) - .definition( FragmentDefinition.newFragmentDefinition() - .name("FullType") - .typeCondition( TypeName.newTypeName().name("__Type").build() ) - .selectionSet(fullTypeSelectionSet) - .build() - ) - .definition( FragmentDefinition.newFragmentDefinition() - .name("InputValue") - .typeCondition( TypeName.newTypeName().name("__InputValue").build() ) - .selectionSet(inputValueSelectionSet) - .build() - ) - .definition( FragmentDefinition.newFragmentDefinition() - .name("TypeRef") - .typeCondition( TypeName.newTypeName().name("__Type").build() ) - .selectionSet(typeRefSelectionSet) - .build() - ) - .build(); - - return AstPrinter.printAst(query); - } - - String INTROSPECTION_QUERY = getIntrospectionQuery(true, false, true, false, true, 7); +public interface IntrospectionQuery { + String INTROSPECTION_QUERY = IntrospectionQueryBuilder.build(); } diff --git a/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java b/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java new file mode 100644 index 000000000..cece9aeec --- /dev/null +++ b/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java @@ -0,0 +1,356 @@ +package graphql.introspection; + +import com.google.common.collect.ImmutableList; +import graphql.language.Argument; +import graphql.language.AstPrinter; +import graphql.language.BooleanValue; +import graphql.language.Document; +import graphql.language.Field; +import graphql.language.FragmentDefinition; +import graphql.language.FragmentSpread; +import graphql.language.OperationDefinition; +import graphql.language.SelectionSet; +import graphql.language.TypeName; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class IntrospectionQueryBuilder { + public static class Options { + + private final boolean descriptions; + + private final boolean specifiedByUrl; + + private final boolean directiveIsRepeatable; + + private final boolean schemaDescription; + + private final boolean inputValueDeprecation; + + private final int typeRefFragmentDepth; + + private Options(boolean descriptions, + boolean specifiedByUrl, + boolean directiveIsRepeatable, + boolean schemaDescription, + boolean inputValueDeprecation, + int typeRefFragmentDepth) { + this.descriptions = descriptions; + this.specifiedByUrl = specifiedByUrl; + this.directiveIsRepeatable = directiveIsRepeatable; + this.schemaDescription = schemaDescription; + this.inputValueDeprecation = inputValueDeprecation; + this.typeRefFragmentDepth = typeRefFragmentDepth; + } + + public boolean isDescriptions() { + return descriptions; + } + + public boolean isSpecifiedByUrl() { + return specifiedByUrl; + } + + public boolean isDirectiveIsRepeatable() { + return directiveIsRepeatable; + } + + public boolean isSchemaDescription() { + return schemaDescription; + } + + public boolean isInputValueDeprecation() { + return inputValueDeprecation; + } + + public int getTypeRefFragmentDepth() { + return typeRefFragmentDepth; + } + + public static Options defaultOptions() { + return new Options( + true, + false, + true, + false, + true, + 7 + ); + } + + /** + * This will allow you to include description fields in the introspection query + * + * @param flag whether to include them + * + * @return options + */ + public Options descriptions(boolean flag) { + return new Options(flag, + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to include the `specifiedByURL` field for scalar types in the introspection query. + * + * @param flag whether to include them + * + * @return options + */ + public Options specifiedByUrl(boolean flag) { + return new Options(this.descriptions, + flag, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to include the `isRepeatable` field for directives in the introspection query. + * + * @param flag whether to include them + * + * @return options + */ + public Options directiveIsRepeatable(boolean flag) { + return new Options(this.descriptions, + this.specifiedByUrl, + flag, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to include the `description` field for the schema type in the introspection query. + * + * @param flag whether to include them + * + * @return options + */ + public Options schemaDescription(boolean flag) { + return new Options(this.descriptions, + this.specifiedByUrl, + this.directiveIsRepeatable, + flag, + this.inputValueDeprecation, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to include deprecated input fields in the introspection query. + * + * @param flag whether to include them + * + * @return options + */ + public Options inputValueDeprecation(boolean flag) { + return new Options(this.descriptions, + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + flag, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to control the depth of the `TypeRef` fragment in the introspection query. + * + * @param typeRefFragmentDepth the depth of the `TypeRef` fragment. + * + * @return options + */ + public Options typeRefFragmentDepth(int typeRefFragmentDepth) { + return new Options(this.descriptions, + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + typeRefFragmentDepth); + } + } + + private static List filter(T... args) { + return Arrays.stream(args).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static String build() { + return build(Options.defaultOptions()); + } + + public static String build(Options options) { + SelectionSet schemaSelectionSet = SelectionSet.newSelectionSet().selections( filter( + options.schemaDescription ? Field.newField("description").build() : null, + Field.newField("queryType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("mutationType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("subscriptionType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("types", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("FullType").build() ) + .build() + ) + .build(), + Field.newField("directives", SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + Field.newField("locations").build(), + Field.newField("args") + .arguments( filter( + options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + options.directiveIsRepeatable ? Field.newField("isRepeatable").build() : null + ) ) + .build() + ) + .build() + ) + ).build(); + + SelectionSet fullTypeSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + options.specifiedByUrl ? Field.newField("specifiedByURL").build() : null, + Field.newField("fields") + .arguments( ImmutableList.of( + Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() + ) ) + .selectionSet( SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + Field.newField("args") + .arguments( filter( + options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + Field.newField("type", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("isDeprecated").build(), + Field.newField("deprecationReason").build() + ) ).build() + ) + .build(), + Field.newField("inputFields") + .arguments( filter( + options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + Field.newField("interfaces", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("enumValues") + .arguments( ImmutableList.of( + Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() + ) ) + .selectionSet( SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + Field.newField("isDeprecated").build(), + Field.newField("deprecationReason").build() + ) ) + .build() + ) + .build(), + Field.newField("possibleTypes", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build() + ) ).build(); + + SelectionSet inputValueSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + Field.newField("type", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("defaultValue").build(), + options.inputValueDeprecation ? Field.newField("isDeprecated").build() : null, + options.inputValueDeprecation ? Field.newField("deprecationReason").build() : null + ) ).build(); + + SelectionSet typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build() + ) ).build(); + + for(int i=options.typeRefFragmentDepth; i>0; i-=1) { + typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build(), + Field.newField("ofType", typeRefSelectionSet).build() + ) ).build(); + } + + Document query = Document.newDocument() + .definition( OperationDefinition.newOperationDefinition() + .operation(OperationDefinition.Operation.QUERY) + .name("IntrospectionQuery") + .selectionSet( SelectionSet.newSelectionSet() + .selection( Field.newField("__schema", schemaSelectionSet).build() ) + .build() + ) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("FullType") + .typeCondition( TypeName.newTypeName().name("__Type").build() ) + .selectionSet(fullTypeSelectionSet) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("InputValue") + .typeCondition( TypeName.newTypeName().name("__InputValue").build() ) + .selectionSet(inputValueSelectionSet) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("TypeRef") + .typeCondition( TypeName.newTypeName().name("__Type").build() ) + .selectionSet(typeRefSelectionSet) + .build() + ) + .build(); + + return AstPrinter.printAst(query); + } +} diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 925ec1ca0..bfd43479f 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -479,13 +479,33 @@ class IntrospectionTest extends Specification { } when: - def allFalseExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(false, false, false, false, false, 5)) + def allFalseExecutionResult = graphQL.execute( + IntrospectionQueryBuilder.build( + IntrospectionQueryBuilder.Options.defaultOptions() + .descriptions(false) + .specifiedByUrl(false) + .directiveIsRepeatable(false) + .schemaDescription(false) + .inputValueDeprecation(false) + .typeRefFragmentDepth(5) + ) + ) then: !parseExecutionResult(allFalseExecutionResult).any() allFalseExecutionResult.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "tenDimensionalList"}["type"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"] == null // typeRefFragmentDepth is 5 when: - def allTrueExecutionResult = graphQL.execute(IntrospectionQuery.getIntrospectionQuery(true, true, true, true, true, 7)) + def allTrueExecutionResult = graphQL.execute( + IntrospectionQueryBuilder.build( + IntrospectionQueryBuilder.Options.defaultOptions() + .descriptions(true) + .specifiedByUrl(true) + .directiveIsRepeatable(true) + .schemaDescription(true) + .inputValueDeprecation(true) + .typeRefFragmentDepth(7) + ) + ) then: parseExecutionResult(allTrueExecutionResult).every() allTrueExecutionResult.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "tenDimensionalList"}["type"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"] == null // typeRefFragmentDepth is 7 From 3cef2a54cc3a6866c33124cfb9a68a9327d934c2 Mon Sep 17 00:00:00 2001 From: aaron_paterson Date: Thu, 27 Oct 2022 00:32:17 -0600 Subject: [PATCH 10/10] test that AST printed introspection query is equivalent to original string --- .../introspection/IntrospectionTest.groovy | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index bfd43479f..712ac8048 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -436,6 +436,118 @@ class IntrospectionTest extends Specification { [__type: [fields: [[args: [[defaultValue: '{inputField : "foo"}']]]]]] } + def "test AST printed introspection query is equivalent to original string"() { + when: + def oldIntrospectionQuery = "\n" + + " query IntrospectionQuery {\n" + + " __schema {\n" + + " queryType { name }\n" + + " mutationType { name }\n" + + " subscriptionType { name }\n" + + " types {\n" + + " ...FullType\n" + + " }\n" + + " directives {\n" + + " name\n" + + " description\n" + + " locations\n" + + " args(includeDeprecated: true) {\n" + + " ...InputValue\n" + + " }\n" + + " isRepeatable\n" + + " }\n" + + " }\n" + + " }\n" + + "\n" + + " fragment FullType on __Type {\n" + + " kind\n" + + " name\n" + + " description\n" + + " fields(includeDeprecated: true) {\n" + + " name\n" + + " description\n" + + " args(includeDeprecated: true) {\n" + + " ...InputValue\n" + + " }\n" + + " type {\n" + + " ...TypeRef\n" + + " }\n" + + " isDeprecated\n" + + " deprecationReason\n" + + " }\n" + + " inputFields(includeDeprecated: true) {\n" + + " ...InputValue\n" + + " }\n" + + " interfaces {\n" + + " ...TypeRef\n" + + " }\n" + + " enumValues(includeDeprecated: true) {\n" + + " name\n" + + " description\n" + + " isDeprecated\n" + + " deprecationReason\n" + + " }\n" + + " possibleTypes {\n" + + " ...TypeRef\n" + + " }\n" + + " }\n" + + "\n" + + " fragment InputValue on __InputValue {\n" + + " name\n" + + " description\n" + + " type { ...TypeRef }\n" + + " defaultValue\n" + + " isDeprecated\n" + + " deprecationReason\n" + + " }\n" + + "\n" + + // + // The depth of the types is actually an arbitrary decision. It could be any depth in fact. This depth + // was taken from GraphIQL https://github.com/graphql/graphiql/blob/master/src/utility/introspectionQueries.js + // which uses 7 levels and hence could represent a type like say [[[[[Float!]]]]] + // + "fragment TypeRef on __Type {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "\n" + + def newIntrospectionQuery = IntrospectionQuery.INTROSPECTION_QUERY; + + then: + oldIntrospectionQuery.replaceAll("\\s+","").equals( + newIntrospectionQuery.replaceAll("\\s+","") + ) + } + def "test parameterized introspection queries"() { def spec = ''' scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")