Skip to content

Commit 5c4a376

Browse files
committed
Process repeated directives
1 parent 9dfa462 commit 5c4a376

File tree

3 files changed

+129
-32
lines changed

3 files changed

+129
-32
lines changed

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

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class SchemaParser internal constructor(
5656
(inputObjectDefinitions.map { it.name } + enumDefinitions.map { it.name }).toSet()
5757

5858
private val codeRegistryBuilder = GraphQLCodeRegistry.newCodeRegistry()
59-
private val directiveWiringHelper = DirectiveWiringHelper(options, runtimeWiring, codeRegistryBuilder)
59+
private val directiveWiringHelper = DirectiveWiringHelper(options, runtimeWiring, codeRegistryBuilder, directiveDefinitions)
6060

6161
/**
6262
* Parses the given schema with respect to the given dictionary and returns GraphQL objects.
@@ -316,32 +316,22 @@ class SchemaParser internal constructor(
316316
}
317317

318318
private fun buildAppliedDirectives(directives: List<Directive>): Array<GraphQLAppliedDirective> {
319-
val names = mutableSetOf<String>()
320-
321-
val output = mutableListOf<GraphQLAppliedDirective>()
322-
for (directive in directives) {
323-
if (!names.contains(directive.name)) {
324-
names.add(directive.name)
325-
val graphQLDirective = GraphQLAppliedDirective.newDirective()
326-
.name(directive.name)
327-
.description(getDocumentation(directive, options))
328-
.comparatorRegistry(runtimeWiring.comparatorRegistry)
329-
.apply {
330-
directive.arguments.forEach { arg ->
331-
argument(GraphQLAppliedDirectiveArgument.newArgument()
332-
.name(arg.name)
333-
.type(directiveWiringHelper.buildDirectiveInputType(arg.value))
334-
.valueLiteral(arg.value)
335-
.build())
336-
}
319+
return directives.map {
320+
GraphQLAppliedDirective.newDirective()
321+
.name(it.name)
322+
.description(getDocumentation(it, options))
323+
.comparatorRegistry(runtimeWiring.comparatorRegistry)
324+
.apply {
325+
it.arguments.forEach { arg ->
326+
argument(GraphQLAppliedDirectiveArgument.newArgument()
327+
.name(arg.name)
328+
.type(directiveWiringHelper.buildDirectiveInputType(arg.value))
329+
.valueLiteral(arg.value)
330+
.build())
337331
}
338-
.build()
339-
340-
output.add(graphQLDirective)
341-
}
342-
}
343-
344-
return output.toTypedArray()
332+
}
333+
.build()
334+
}.toTypedArray()
345335
}
346336

347337
private fun determineOutputType(typeDefinition: Type<*>, inputObjects: List<GraphQLInputObjectType>) =

src/main/kotlin/graphql/kickstart/tools/directive/DirectiveWiringHelper.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import java.util.*
1616
class DirectiveWiringHelper(
1717
private val options: SchemaParserOptions,
1818
private val runtimeWiring: RuntimeWiring,
19-
codeRegistryBuilder: GraphQLCodeRegistry.Builder
19+
codeRegistryBuilder: GraphQLCodeRegistry.Builder,
20+
private val directiveDefinitions: List<DirectiveDefinition>
2021
) {
2122
private val schemaDirectiveParameters = Parameters(runtimeWiring, codeRegistryBuilder)
2223

@@ -76,39 +77,42 @@ class DirectiveWiringHelper(
7677
private fun <T : GraphQLDirectiveContainer> wireDirectives(wrapper: WiringWrapper<T>): T {
7778
val directivesContainer = wrapper.graphQlType.definition as DirectivesContainer<*>
7879
val directives = buildDirectives(directivesContainer.directives, wrapper.directiveLocation)
80+
var output = wrapper.graphQlType
7981
// first the specific named directives
8082
directives.forEach { directive ->
8183
val env = buildEnvironment(wrapper, directives, directive)
8284
val wiring = runtimeWiring.registeredDirectiveWiring[directive.name]
83-
wiring?.let { return wrapper.invoker(it, env) }
85+
wiring?.let { output = wrapper.invoker(it, env) }
8486
}
8587
// now call any statically added to the runtime
8688
runtimeWiring.directiveWiring.forEach { staticWiring ->
8789
val env = buildEnvironment(wrapper, directives, null)
88-
return wrapper.invoker(staticWiring, env)
90+
output = wrapper.invoker(staticWiring, env)
8991
}
9092
// wiring factory is last (if present)
9193
val env = buildEnvironment(wrapper, directives, null)
9294
if (runtimeWiring.wiringFactory.providesSchemaDirectiveWiring(env)) {
9395
val factoryWiring = runtimeWiring.wiringFactory.getSchemaDirectiveWiring(env)
94-
return wrapper.invoker(factoryWiring, env)
96+
output = wrapper.invoker(factoryWiring, env)
9597
}
9698

97-
return wrapper.graphQlType
99+
return output
98100
}
99101

100102
private fun buildDirectives(directives: List<Directive>, directiveLocation: Introspection.DirectiveLocation): List<GraphQLDirective> {
101103
val names = mutableSetOf<String>()
102104
val output = mutableListOf<GraphQLDirective>()
103105

104106
for (directive in directives) {
105-
if (!names.contains(directive.name)) {
107+
val repeatable = directiveDefinitions.find { it.name.equals(directive.name) }?.isRepeatable ?: false
108+
if (repeatable || !names.contains(directive.name)) {
106109
names.add(directive.name)
107110
output.add(GraphQLDirective.newDirective()
108111
.name(directive.name)
109112
.description(getDocumentation(directive, options))
110113
.comparatorRegistry(runtimeWiring.comparatorRegistry)
111114
.validLocation(directiveLocation)
115+
.repeatable(repeatable)
112116
.apply {
113117
directive.arguments.forEach { arg ->
114118
argument(GraphQLArgument.newArgument()

src/test/kotlin/graphql/kickstart/tools/DirectiveTest.kt

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,91 @@ class DirectiveTest {
116116
assertEquals(result.getData(), expected)
117117
}
118118

119+
@Test
120+
fun `should apply multiple directives`() {
121+
val schema = SchemaParser.newParser()
122+
.schemaString(
123+
"""
124+
directive @double repeatable on FIELD_DEFINITION
125+
126+
type Query {
127+
user: User
128+
}
129+
130+
type User {
131+
id: ID!
132+
name: String @uppercase @double
133+
}
134+
""")
135+
.resolvers(UsersQueryResolver())
136+
.directive("double", DoubleDirective())
137+
.directive("uppercase", UppercaseDirective())
138+
.build()
139+
.makeExecutableSchema()
140+
141+
val gql = GraphQL.newGraphQL(schema)
142+
.queryExecutionStrategy(AsyncExecutionStrategy())
143+
.build()
144+
145+
val result = gql.execute(
146+
"""
147+
query {
148+
user {
149+
id
150+
name
151+
}
152+
}
153+
""")
154+
155+
val expected = mapOf(
156+
"user" to mapOf("id" to "1", "name" to "LUKELUKE")
157+
)
158+
159+
assertEquals(result.getData(), expected)
160+
}
161+
162+
@Test
163+
fun `should apply repeated directive`() {
164+
val schema = SchemaParser.newParser()
165+
.schemaString(
166+
"""
167+
directive @double repeatable on FIELD_DEFINITION
168+
169+
type Query {
170+
user: User
171+
}
172+
173+
type User {
174+
id: ID!
175+
name: String @double @double
176+
}
177+
""")
178+
.resolvers(UsersQueryResolver())
179+
.directive("double", DoubleDirective())
180+
.build()
181+
.makeExecutableSchema()
182+
183+
val gql = GraphQL.newGraphQL(schema)
184+
.queryExecutionStrategy(AsyncExecutionStrategy())
185+
.build()
186+
187+
val result = gql.execute(
188+
"""
189+
query {
190+
user {
191+
id
192+
name
193+
}
194+
}
195+
""")
196+
197+
val expected = mapOf(
198+
"user" to mapOf("id" to "1", "name" to "LukeLukeLukeLuke")
199+
)
200+
201+
assertEquals(result.getData(), expected)
202+
}
203+
119204
@Test
120205
@Ignore("Ignore until enums work in directives")
121206
fun `should compile schema with directive that has enum parameter`() {
@@ -208,6 +293,24 @@ class DirectiveTest {
208293
}
209294
}
210295

296+
private class DoubleDirective : SchemaDirectiveWiring {
297+
298+
override fun onField(environment: SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition>): GraphQLFieldDefinition {
299+
val field = environment.element
300+
val parentType = environment.fieldsContainer
301+
302+
val originalDataFetcher = environment.codeRegistry.getDataFetcher(parentType, field)
303+
val wrappedDataFetcher = DataFetcherFactories.wrapDataFetcher(originalDataFetcher) { _, value ->
304+
val string = value as? String
305+
string + string
306+
}
307+
308+
environment.codeRegistry.dataFetcher(parentType, field, wrappedDataFetcher)
309+
310+
return field
311+
}
312+
}
313+
211314
private class UsersQueryResolver : GraphQLQueryResolver {
212315
fun users(env: DataFetchingEnvironment): Connection<User> {
213316
return SimpleListConnection(listOf(User(1L, "Luke"))).get(env)

0 commit comments

Comments
 (0)