Skip to content

Commit 9235282

Browse files
committed
Add an option to fallback to comments if a description is not provided
The default behavior, to match existing behavior, is to fallback to comments. Setting `commentsAsFallbackDescription` to false in the `SchemaParserOptions` will result in comments not being used when a description is not provided.
1 parent c694d1a commit 9235282

File tree

5 files changed

+96
-17
lines changed

5 files changed

+96
-17
lines changed

src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ internal class SchemaClassScanner(
168168
?: throw SchemaClassScannerError("Expected a user-defined GraphQL scalar type with name '${definition.name}' but found none!")
169169
GraphQLScalarType.newScalar()
170170
.name(provided.name)
171-
.description(definition.description?.content ?: getDocumentation(definition) ?: provided.description)
171+
.description(getDocumentation(definition, options) ?: provided.description)
172172
.coercing(provided.coercing)
173173
.definition(definition)
174174
.build()

src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class SchemaParser internal constructor(
122122
val builder = GraphQLObjectType.newObject()
123123
.name(name)
124124
.definition(objectDefinition)
125-
.description(if (objectDefinition.description != null) objectDefinition.description.content else getDocumentation(objectDefinition))
125+
.description(getDocumentation(objectDefinition, options))
126126

127127
builder.withDirectives(*buildDirectives(objectDefinition.directives, Introspection.DirectiveLocation.OBJECT))
128128

@@ -133,7 +133,6 @@ class SchemaParser internal constructor(
133133
}
134134

135135
objectDefinition.getExtendedFieldDefinitions(extensionDefinitions).forEach { fieldDefinition ->
136-
fieldDefinition.description
137136
builder.field { field ->
138137
createField(field, fieldDefinition, inputObjects)
139138
codeRegistryBuilder.dataFetcher(
@@ -162,7 +161,7 @@ class SchemaParser internal constructor(
162161
.name(definition.name)
163162
.definition(definition)
164163
.extensionDefinitions(extensionDefinitions)
165-
.description(if (definition.description != null) definition.description.content else getDocumentation(definition))
164+
.description(getDocumentation(definition, options))
166165

167166
builder.withDirectives(*buildDirectives(definition.directives, Introspection.DirectiveLocation.INPUT_OBJECT))
168167

@@ -171,7 +170,7 @@ class SchemaParser internal constructor(
171170
val fieldBuilder = GraphQLInputObjectField.newInputObjectField()
172171
.name(inputDefinition.name)
173172
.definition(inputDefinition)
174-
.description(if (inputDefinition.description != null) inputDefinition.description.content else getDocumentation(inputDefinition))
173+
.description(getDocumentation(inputDefinition, options))
175174
.defaultValue(buildDefaultValue(inputDefinition.defaultValue))
176175
.type(determineInputType(inputDefinition.type, inputObjects))
177176
.withDirectives(*buildDirectives(inputDefinition.directives, Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION))
@@ -191,7 +190,7 @@ class SchemaParser internal constructor(
191190
val builder = GraphQLEnumType.newEnum()
192191
.name(name)
193192
.definition(definition)
194-
.description(if (definition.description != null) definition.description.content else getDocumentation(definition))
193+
.description(getDocumentation(definition, options))
195194

196195
builder.withDirectives(*buildDirectives(definition.directives, Introspection.DirectiveLocation.ENUM))
197196

@@ -204,7 +203,7 @@ class SchemaParser internal constructor(
204203
getDeprecated(enumDefinition.directives).let {
205204
val enumValueDefinition = GraphQLEnumValueDefinition.newEnumValueDefinition()
206205
.name(enumName)
207-
.description(if (enumDefinition.description != null) enumDefinition.description.content else getDocumentation(enumDefinition))
206+
.description(getDocumentation(enumDefinition, options))
208207
.value(enumValue)
209208
.deprecationReason(it)
210209
.withDirectives(*enumValueDirectives)
@@ -223,7 +222,7 @@ class SchemaParser internal constructor(
223222
val builder = GraphQLInterfaceType.newInterface()
224223
.name(name)
225224
.definition(interfaceDefinition)
226-
.description(if (interfaceDefinition.description != null) interfaceDefinition.description.content else getDocumentation(interfaceDefinition))
225+
.description(getDocumentation(interfaceDefinition, options))
227226

228227
builder.withDirectives(*buildDirectives(interfaceDefinition.directives, Introspection.DirectiveLocation.INTERFACE))
229228

@@ -239,7 +238,7 @@ class SchemaParser internal constructor(
239238
val builder = GraphQLUnionType.newUnionType()
240239
.name(name)
241240
.definition(definition)
242-
.description(if (definition.description != null) definition.description.content else getDocumentation(definition))
241+
.description(getDocumentation(definition, options))
243242

244243
builder.withDirectives(*buildDirectives(definition.directives, Introspection.DirectiveLocation.UNION))
245244

@@ -270,7 +269,7 @@ class SchemaParser internal constructor(
270269
private fun createField(field: GraphQLFieldDefinition.Builder, fieldDefinition: FieldDefinition, inputObjects: List<GraphQLInputObjectType>): GraphQLFieldDefinition.Builder {
271270
field
272271
.name(fieldDefinition.name)
273-
.description(fieldDefinition.description?.content ?: getDocumentation(fieldDefinition))
272+
.description(getDocumentation(fieldDefinition, options))
274273
.definition(fieldDefinition)
275274
.apply { getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
276275
.type(determineOutputType(fieldDefinition.type, inputObjects))
@@ -279,7 +278,7 @@ class SchemaParser internal constructor(
279278
val argumentBuilder = GraphQLArgument.newArgument()
280279
.name(argumentDefinition.name)
281280
.definition(argumentDefinition)
282-
.description(if (argumentDefinition.description != null) argumentDefinition.description.content else getDocumentation(argumentDefinition))
281+
.description(getDocumentation(argumentDefinition, options))
283282
.type(determineInputType(argumentDefinition.type, inputObjects))
284283
.apply { buildDefaultValue(argumentDefinition.defaultValue)?.let { defaultValue(it) } }
285284
.withDirectives(*buildDirectives(argumentDefinition.directives, Introspection.DirectiveLocation.ARGUMENT_DEFINITION))

src/main/kotlin/graphql/kickstart/tools/SchemaParserOptions.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ data class SchemaParserOptions internal constructor(
3030
val coroutineContextProvider: CoroutineContextProvider,
3131
val typeDefinitionFactories: List<TypeDefinitionFactory>,
3232
val fieldVisibility: GraphqlFieldVisibility?,
33-
val includeUnusedTypes: Boolean
33+
val includeUnusedTypes: Boolean,
34+
val commentsAsFallbackDescription: Boolean
3435
) {
3536
companion object {
3637
@JvmStatic
@@ -58,6 +59,7 @@ data class SchemaParserOptions internal constructor(
5859
private var typeDefinitionFactories: MutableList<TypeDefinitionFactory> = mutableListOf(RelayConnectionFactory())
5960
private var fieldVisibility: GraphqlFieldVisibility? = null
6061
private var includeUnusedTypes = false
62+
private var commentsAsFallbackDescription = true
6163

6264
fun contextClass(contextClass: Class<*>) = this.apply {
6365
this.contextClass = contextClass
@@ -131,6 +133,10 @@ data class SchemaParserOptions internal constructor(
131133
this.includeUnusedTypes = includeUnusedTypes
132134
}
133135

136+
fun commentsAsFallbackDescription(commentsAsFallbackDescription: Boolean) = this.apply {
137+
this.commentsAsFallbackDescription = commentsAsFallbackDescription
138+
}
139+
134140
@ExperimentalCoroutinesApi
135141
fun build(): SchemaParserOptions {
136142
val coroutineContextProvider = coroutineContextProvider
@@ -169,7 +175,8 @@ data class SchemaParserOptions internal constructor(
169175
coroutineContextProvider,
170176
typeDefinitionFactories,
171177
fieldVisibility,
172-
includeUnusedTypes
178+
includeUnusedTypes,
179+
commentsAsFallbackDescription
173180
)
174181
}
175182
}

src/main/kotlin/graphql/kickstart/tools/util/Utils.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.kickstart.tools.util
22

33
import graphql.kickstart.tools.GraphQLResolver
4+
import graphql.kickstart.tools.SchemaParserOptions
45
import graphql.language.*
56
import graphql.schema.DataFetchingEnvironment
67
import kotlinx.coroutines.CoroutineScope
@@ -49,10 +50,17 @@ internal val Class<*>.declaredNonProxyMethods: List<JavaMethod>
4950
}
5051
}
5152

52-
internal fun getDocumentation(node: AbstractNode<*>): String? = node.comments?.asSequence()
53-
?.filter { !it.content.startsWith("#") }
54-
?.joinToString("\n") { it.content.trimEnd() }
55-
?.trimIndent()
53+
internal fun getDocumentation(node: AbstractDescribedNode<*>, options: SchemaParserOptions): String? =
54+
when {
55+
node.description != null -> node.description.content
56+
!options.commentsAsFallbackDescription -> null
57+
node.comments == null -> null
58+
node.comments.isEmpty() -> null
59+
else -> node.comments.asSequence()
60+
.filter { !it.content.startsWith("#") }
61+
.joinToString("\n") { it.content.trimEnd() }
62+
.trimIndent()
63+
}
5664

5765
/**
5866
* Simple heuristic to check is a method is a trivial data fetcher.

src/test/groovy/graphql/kickstart/tools/SchemaParserSpec.groovy

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,71 @@ class SchemaParserSpec extends Specification {
368368
noExceptionThrown()
369369
}
370370

371+
def "parser should use comments for descriptions"() {
372+
when:
373+
def schema = SchemaParser.newParser().schemaString('''\
374+
type Query {
375+
"description"
376+
description: String
377+
#comment
378+
comment: String
379+
omitted: String
380+
"description"
381+
#comment
382+
both: String
383+
""
384+
empty: String
385+
}
386+
'''.stripIndent())
387+
.options(SchemaParserOptions.newOptions()
388+
.allowUnimplementedResolvers(true)
389+
.build())
390+
.resolvers(new GraphQLQueryResolver() {})
391+
.build()
392+
.makeExecutableSchema()
393+
394+
then:
395+
def children = schema.getQueryType().getChildren()
396+
children.find { it.name == "description" }.description == "description"
397+
children.find { it.name == "comment" }.description == "comment"
398+
children.find { it.name == "omitted" }.description == null
399+
children.find { it.name == "both" }.description == "description"
400+
children.find { it.name == "empty" }.description == ""
401+
}
402+
403+
def "parser should not use comments for descriptions"() {
404+
when:
405+
def schema = SchemaParser.newParser().schemaString('''\
406+
type Query {
407+
"description"
408+
description: String
409+
#comment
410+
comment: String
411+
omitted: String
412+
"description"
413+
#comment
414+
both: String
415+
""
416+
empty: String
417+
}
418+
'''.stripIndent())
419+
.options(SchemaParserOptions.newOptions()
420+
.commentsAsFallbackDescription(false)
421+
.allowUnimplementedResolvers(true)
422+
.build())
423+
.resolvers(new GraphQLQueryResolver() {})
424+
.build()
425+
.makeExecutableSchema()
426+
427+
then:
428+
def children = schema.getQueryType().getChildren()
429+
children.find { it.name == "description" }.description == "description"
430+
children.find { it.name == "comment" }.description == null
431+
children.find { it.name == "omitted" }.description == null
432+
children.find { it.name == "both" }.description == "description"
433+
children.find { it.name == "empty" }.description == ""
434+
}
435+
371436
enum EnumType {
372437
TEST
373438
}

0 commit comments

Comments
 (0)