From 5ee79994adb5ad5fe9926ec870ad0337e9ef5de4 Mon Sep 17 00:00:00 2001 From: Tom Groves Date: Thu, 28 Oct 2021 10:04:40 +0100 Subject: [PATCH 1/3] Surface schema description when instantiating GraphQLSchema. --- .../graphql/kickstart/tools/RootTypeInfo.kt | 9 +++++-- .../kickstart/tools/SchemaClassScanner.kt | 1 + .../graphql/kickstart/tools/SchemaObjects.kt | 4 ++- .../graphql/kickstart/tools/SchemaParser.kt | 2 +- .../kickstart/tools/SchemaParserTest.kt | 26 +++++++++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/graphql/kickstart/tools/RootTypeInfo.kt b/src/main/kotlin/graphql/kickstart/tools/RootTypeInfo.kt index e2044dc4..1d9183d7 100644 --- a/src/main/kotlin/graphql/kickstart/tools/RootTypeInfo.kt +++ b/src/main/kotlin/graphql/kickstart/tools/RootTypeInfo.kt @@ -1,5 +1,6 @@ package graphql.kickstart.tools +import graphql.language.Description import graphql.language.SchemaDefinition import graphql.language.TypeName @@ -9,25 +10,29 @@ import graphql.language.TypeName internal class RootTypeInfo private constructor( private val queryType: TypeName?, private val mutationType: TypeName?, - private val subscriptionType: TypeName? + private val subscriptionType: TypeName?, + private val description: Description? ) { companion object { const val DEFAULT_QUERY_NAME = "Query" const val DEFAULT_MUTATION_NAME = "Mutation" const val DEFAULT_SUBSCRIPTION_NAME = "Subscription" + const val DEFAULT_DESCRIPTION = "A GraphQL schema provides a root type for each kind of operation." fun fromSchemaDefinitions(definitions: List): RootTypeInfo { val queryType = definitions.lastOrNull()?.operationTypeDefinitions?.find { it.name == "query" }?.typeName val mutationType = definitions.lastOrNull()?.operationTypeDefinitions?.find { it.name == "mutation" }?.typeName val subscriptionType = definitions.lastOrNull()?.operationTypeDefinitions?.find { it.name == "subscription" }?.typeName + val description = definitions.lastOrNull()?.description - return RootTypeInfo(queryType, mutationType, subscriptionType) + return RootTypeInfo(queryType, mutationType, subscriptionType, description) } } fun getQueryName() = queryType?.name ?: DEFAULT_QUERY_NAME fun getMutationName() = mutationType?.name ?: DEFAULT_MUTATION_NAME fun getSubscriptionName() = subscriptionType?.name ?: DEFAULT_SUBSCRIPTION_NAME + fun getDescription() = description?.content ?: DEFAULT_DESCRIPTION fun isMutationRequired() = mutationType != null fun isSubscriptionRequired() = subscriptionType != null diff --git a/src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt b/src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt index 2472bef8..d90441f9 100644 --- a/src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt +++ b/src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt @@ -495,6 +495,7 @@ internal class SchemaClassScanner( val query = createRootType("query", queryDefinition, queryName, true, queryResolvers, GraphQLQueryResolver::class.java, queryResolverInfo) val mutation = createRootType("mutation", mutationDefinition, mutationName, rootInfo.isMutationRequired(), mutationResolvers, GraphQLMutationResolver::class.java, mutationResolverInfo) val subscription = createRootType("subscription", subscriptionDefinition, subscriptionName, rootInfo.isSubscriptionRequired(), subscriptionResolvers, GraphQLSubscriptionResolver::class.java, subscriptionResolverInfo) + val schemaDescription = rootInfo.getDescription() private fun createRootType(name: String, type: TypeDefinition<*>?, typeName: String, required: Boolean, resolvers: List, resolverInterface: Class<*>, resolverInfo: RootResolverInfo): RootType? { if (type == null) { diff --git a/src/main/kotlin/graphql/kickstart/tools/SchemaObjects.kt b/src/main/kotlin/graphql/kickstart/tools/SchemaObjects.kt index eebdc488..206ae721 100644 --- a/src/main/kotlin/graphql/kickstart/tools/SchemaObjects.kt +++ b/src/main/kotlin/graphql/kickstart/tools/SchemaObjects.kt @@ -13,13 +13,15 @@ data class SchemaObjects( val mutation: GraphQLObjectType?, val subscription: GraphQLObjectType?, val dictionary: Set, - val codeRegistryBuilder: GraphQLCodeRegistry.Builder + val codeRegistryBuilder: GraphQLCodeRegistry.Builder, + val description: String? ) { /** * Makes a GraphQLSchema with query, mutation and subscription. */ fun toSchema(): GraphQLSchema { return GraphQLSchema.newSchema() + .description(description) .query(query) .mutation(mutation) .subscription(subscription) diff --git a/src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt b/src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt index 673d7010..bf0a3e8d 100644 --- a/src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt +++ b/src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt @@ -103,7 +103,7 @@ class SchemaParser internal constructor( val additionalObjects = objects.filter { o -> o != query && o != subscription && o != mutation } val types = (additionalObjects.toSet() as Set) + inputObjects + enums + interfaces + unions - return SchemaObjects(query, mutation, subscription, types, codeRegistryBuilder) + return SchemaObjects(query, mutation, subscription, types, codeRegistryBuilder, rootInfo.getDescription()) } /** diff --git a/src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt b/src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt index ece86bc4..1d9204c8 100644 --- a/src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt +++ b/src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt @@ -574,6 +574,32 @@ class SchemaParserTest { assertEquals(schema.queryType.getFieldDefinition("empty").description, "") } + @Test + fun `parser should include schema descriptions`() { + val schema = SchemaParser.newParser() + .schemaString( + """ + "This is a schema level description" + schema { + query: SubstituteQuery + } + + type SubstituteQuery { + description: String + comment: String + omitted: String + both: String + empty: String + } + """) + .resolvers(object : GraphQLQueryResolver {}) + .options(SchemaParserOptions.newOptions().allowUnimplementedResolvers(true).build()) + .build() + .makeExecutableSchema() + + assertEquals(schema.description, "This is a schema level description") + } + enum class EnumType { TEST } From 43fb3857199473a14628a9c46d04e7771931c269 Mon Sep 17 00:00:00 2001 From: Tom Groves Date: Thu, 28 Oct 2021 10:22:29 +0100 Subject: [PATCH 2/3] Default schema description to null when not provided --- .../graphql/kickstart/tools/RootTypeInfo.kt | 2 +- .../kickstart/tools/SchemaParserTest.kt | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/graphql/kickstart/tools/RootTypeInfo.kt b/src/main/kotlin/graphql/kickstart/tools/RootTypeInfo.kt index 1d9183d7..71ae0f9f 100644 --- a/src/main/kotlin/graphql/kickstart/tools/RootTypeInfo.kt +++ b/src/main/kotlin/graphql/kickstart/tools/RootTypeInfo.kt @@ -17,7 +17,7 @@ internal class RootTypeInfo private constructor( const val DEFAULT_QUERY_NAME = "Query" const val DEFAULT_MUTATION_NAME = "Mutation" const val DEFAULT_SUBSCRIPTION_NAME = "Subscription" - const val DEFAULT_DESCRIPTION = "A GraphQL schema provides a root type for each kind of operation." + val DEFAULT_DESCRIPTION: String? = null // According to the GraphQL Specification description should be a string or `null` fun fromSchemaDefinitions(definitions: List): RootTypeInfo { val queryType = definitions.lastOrNull()?.operationTypeDefinitions?.find { it.name == "query" }?.typeName diff --git a/src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt b/src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt index 1d9204c8..d382a3cb 100644 --- a/src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt +++ b/src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt @@ -575,7 +575,7 @@ class SchemaParserTest { } @Test - fun `parser should include schema descriptions`() { + fun `parser should include schema descriptions when declared`() { val schema = SchemaParser.newParser() .schemaString( """ @@ -600,6 +600,31 @@ class SchemaParserTest { assertEquals(schema.description, "This is a schema level description") } + @Test + fun `parser should return null schema description when not declared`() { + val schema = SchemaParser.newParser() + .schemaString( + """ + schema { + query: SubstituteQuery + } + + type SubstituteQuery { + description: String + comment: String + omitted: String + both: String + empty: String + } + """) + .resolvers(object : GraphQLQueryResolver {}) + .options(SchemaParserOptions.newOptions().allowUnimplementedResolvers(true).build()) + .build() + .makeExecutableSchema() + + assertNull(schema.description) + } + enum class EnumType { TEST } From b3fcd8543cc420e1f27db528abf88ca023a0c538 Mon Sep 17 00:00:00 2001 From: Tom Groves Date: Thu, 28 Oct 2021 17:01:34 +0100 Subject: [PATCH 3/3] Remove unnecessary description val in SchemaClassScanner --- src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt b/src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt index d90441f9..2472bef8 100644 --- a/src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt +++ b/src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt @@ -495,7 +495,6 @@ internal class SchemaClassScanner( val query = createRootType("query", queryDefinition, queryName, true, queryResolvers, GraphQLQueryResolver::class.java, queryResolverInfo) val mutation = createRootType("mutation", mutationDefinition, mutationName, rootInfo.isMutationRequired(), mutationResolvers, GraphQLMutationResolver::class.java, mutationResolverInfo) val subscription = createRootType("subscription", subscriptionDefinition, subscriptionName, rootInfo.isSubscriptionRequired(), subscriptionResolvers, GraphQLSubscriptionResolver::class.java, subscriptionResolverInfo) - val schemaDescription = rootInfo.getDescription() private fun createRootType(name: String, type: TypeDefinition<*>?, typeName: String, required: Boolean, resolvers: List, resolverInterface: Class<*>, resolverInfo: RootResolverInfo): RootType? { if (type == null) {