Skip to content

Commit 8d7acd7

Browse files
authored
Merge pull request #204 from graphql-java-kickstart/feature/directives
Add support for directives
2 parents 2c96c35 + 041c36f commit 8d7acd7

File tree

15 files changed

+466
-52
lines changed

15 files changed

+466
-52
lines changed

example/pom.xml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
<parent>
1515
<groupId>org.springframework.boot</groupId>
1616
<artifactId>spring-boot-starter-parent</artifactId>
17-
<version>2.0.4.RELEASE</version>
17+
<version>2.0.5.RELEASE</version>
1818
<relativePath/> <!-- lookup parent from repository -->
1919
</parent>
2020

2121
<properties>
2222
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2323
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
2424
<java.version>1.8</java.version>
25-
<graphql-spring-boot-starter.version>5.0.2</graphql-spring-boot-starter.version>
25+
<graphql-spring-boot-starter.version>5.0.6</graphql-spring-boot-starter.version>
2626
</properties>
2727

2828
<dependencies>
@@ -46,24 +46,24 @@
4646
</dependency>
4747

4848
<dependency>
49-
<groupId>com.graphql-java</groupId>
49+
<groupId>com.graphql-java-kickstart</groupId>
5050
<artifactId>graphql-spring-boot-starter</artifactId>
5151
<version>${graphql-spring-boot-starter.version}</version>
5252
</dependency>
5353
<dependency>
54-
<groupId>com.graphql-java</groupId>
54+
<groupId>com.graphql-java-kickstart</groupId>
5555
<artifactId>graphiql-spring-boot-starter</artifactId>
5656
<version>${graphql-spring-boot-starter.version}</version>
5757
</dependency>
5858
<dependency>
59-
<groupId>com.graphql-java</groupId>
59+
<groupId>com.graphql-java-kickstart</groupId>
6060
<artifactId>voyager-spring-boot-starter</artifactId>
6161
<version>${graphql-spring-boot-starter.version}</version>
6262
</dependency>
6363
<dependency>
64-
<groupId>com.graphql-java</groupId>
64+
<groupId>com.graphql-java-kickstart</groupId>
6565
<artifactId>graphql-java-tools</artifactId>
66-
<version>5.2.3</version>
66+
<version>5.3.5</version>
6767
</dependency>
6868
</dependencies>
6969

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.coxautodev.graphql.tools.example.resolvers;
2+
3+
import com.coxautodev.graphql.tools.GraphQLMutationResolver;
4+
import com.coxautodev.graphql.tools.example.types.Human;
5+
import org.springframework.stereotype.Component;
6+
7+
import java.util.Map;
8+
import java.util.UUID;
9+
10+
@Component
11+
public class Mutation implements GraphQLMutationResolver {
12+
13+
public Human createHuman(Map<String,String> createHumanInput) {
14+
String name = null;
15+
if (createHumanInput.containsKey("name")) {
16+
name = createHumanInput.get("name");
17+
}
18+
String homePlanet = "Jakku";
19+
if (createHumanInput.containsKey("homePlanet")) {
20+
homePlanet = createHumanInput.get("homePlanet");
21+
}
22+
return new Human(UUID.randomUUID().toString(), name, null, homePlanet);
23+
}
24+
25+
}

example/src/main/resources/swapi.graphqls

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,16 @@ type Droid implements Character {
5757
appearsIn: [Episode]
5858
# The primary function of the droid
5959
primaryFunction: String
60-
}
60+
}
61+
62+
type Mutation {
63+
# Creates a new human character
64+
createHuman(input: CreateHumanInput!): Human
65+
}
66+
67+
input CreateHumanInput {
68+
# The name of the human
69+
name: String
70+
# The home planet of the human, or null if unknown
71+
homePlanet: String
72+
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>com.graphql-java-kickstart</groupId>
66
<artifactId>graphql-java-tools</artifactId>
7-
<version>5.3.6-SNAPSHOT</version>
7+
<version>5.4.0-SNAPSHOT</version>
88
<packaging>jar</packaging>
99

1010
<name>GraphQL Java Tools</name>

src/main/kotlin/com/coxautodev/graphql/tools/SchemaClassScanner.kt

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import graphql.language.FieldDefinition
88
import graphql.language.InputObjectTypeDefinition
99
import graphql.language.InputValueDefinition
1010
import graphql.language.InterfaceTypeDefinition
11+
import graphql.language.ListType
12+
import graphql.language.NonNullType
1113
import graphql.language.ObjectTypeDefinition
1214
import graphql.language.ObjectTypeExtensionDefinition
1315
import graphql.language.ScalarTypeDefinition
@@ -18,7 +20,6 @@ import graphql.language.UnionTypeDefinition
1820
import graphql.schema.GraphQLScalarType
1921
import graphql.schema.idl.ScalarInfo
2022
import org.slf4j.LoggerFactory
21-
import java.lang.reflect.Field
2223
import java.lang.reflect.Method
2324

2425
/**
@@ -134,7 +135,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
134135
val dictionary = try {
135136
Maps.unmodifiableBiMap(HashBiMap.create<TypeDefinition<*>, JavaType>().also {
136137
dictionary.filter {
137-
it.value.javaType != null
138+
it.value.javaType != null
138139
&& it.value.typeClass() != java.lang.Object::class.java
139140
&& !java.util.Map::class.java.isAssignableFrom(it.value.typeClass())
140141
&& it.key !is InputObjectTypeDefinition
@@ -216,9 +217,9 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
216217
}
217218
}
218219

219-
private fun getResolverInfoFromTypeDictionary(typeName: String) : ResolverInfo? {
220+
private fun getResolverInfoFromTypeDictionary(typeName: String): ResolverInfo? {
220221
val dictionaryType = initialDictionary[typeName]?.get()
221-
return if(dictionaryType != null) {
222+
return if (dictionaryType != null) {
222223
resolverInfosByDataClass[dictionaryType] ?: DataClassResolverInfo(dictionaryType);
223224
} else {
224225
null
@@ -230,33 +231,51 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
230231
*/
231232
private fun scanQueueItemForPotentialMatches(item: QueueItem) {
232233
val resolverInfoList = this.resolverInfos.filter { it.dataClassType == item.clazz }
233-
val resolverInfo: ResolverInfo? = if (resolverInfoList.size > 1) {
234+
val resolverInfo: ResolverInfo = (if (resolverInfoList.size > 1) {
234235
MultiResolverInfo(resolverInfoList)
235236
} else {
236-
if(item.clazz.equals(Object::class.java)) {
237+
if (item.clazz.equals(Object::class.java)) {
237238
getResolverInfoFromTypeDictionary(item.type.name)
238239
} else {
239240
resolverInfosByDataClass[item.clazz] ?: DataClassResolverInfo(item.clazz)
240241
}
241-
}
242-
if(resolverInfo == null) {
243-
throw throw SchemaClassScannerError("The GraphQL schema type '${item.type.name}' maps to a field of type java.lang.Object however there is no matching entry for this type in the type dictionary. You may need to add this type to the dictionary before building the schema.")
244-
}
242+
}) ?: throw throw SchemaClassScannerError("The GraphQL schema type '${item.type.name}' maps to a field of type java.lang.Object however there is no matching entry for this type in the type dictionary. You may need to add this type to the dictionary before building the schema.")
245243

246244
scanResolverInfoForPotentialMatches(item.type, resolverInfo)
247245
}
248246

249247
private fun scanResolverInfoForPotentialMatches(type: ObjectTypeDefinition, resolverInfo: ResolverInfo) {
250248
type.getExtendedFieldDefinitions(extensionDefinitions).forEach { field ->
249+
// val searchField = applyDirective(field)
251250
val fieldResolver = fieldResolverScanner.findFieldResolver(field, resolverInfo)
252251

253252
fieldResolversByType.getOrPut(type) { mutableMapOf() }[fieldResolver.field] = fieldResolver
253+
254254
fieldResolver.scanForMatches().forEach { potentialMatch ->
255-
handleFoundType(typeClassMatcher.match(potentialMatch))
255+
// if (potentialMatch.graphQLType is TypeName && !definitionsByName.containsKey((potentialMatch.graphQLType.name))) {
256+
// val typeDefinition = ObjectTypeDefinition.newObjectTypeDefinition()
257+
// .name(potentialMatch.graphQLType.name)
258+
// .build()
259+
// handleFoundType(TypeClassMatcher.ValidMatch(typeDefinition, typeClassMatcher.toRealType(potentialMatch), potentialMatch.reference))
260+
// } else {
261+
handleFoundType(typeClassMatcher.match(potentialMatch))
262+
// }
256263
}
257264
}
258265
}
259266

267+
// private fun applyDirective(field: FieldDefinition): FieldDefinition {
268+
// val connectionDirectives = field.directives.filter { it.name == "connection" }
269+
// if (connectionDirectives.isNotEmpty()) {
270+
// val directive = connectionDirectives.first()
271+
// val originalType:TypeName = field.type as TypeName
272+
// val wrappedField = field.deepCopy()
273+
// wrappedField.type = TypeName(originalType.name + "Connection")
274+
// return wrappedField
275+
// }
276+
// return field
277+
// }
278+
260279
private fun handleFoundType(match: TypeClassMatcher.Match) {
261280
when (match) {
262281
is TypeClassMatcher.ScalarMatch -> {
@@ -287,7 +306,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
287306
if (options.preferGraphQLResolver && realEntry.hasResolverRef()) {
288307
log.warn("The real entry ${realEntry.joinReferences()} is a GraphQLResolver so ignoring this one ${javaType.unwrap()} $reference")
289308
} else {
290-
if(java.util.Map::class.java.isAssignableFrom(javaType.unwrap())) {
309+
if (java.util.Map::class.java.isAssignableFrom(javaType.unwrap())) {
291310
throw SchemaClassScannerError("Two different property map classes used for type ${type.name}:\n${realEntry.joinReferences()}\n\n- ${javaType}:\n| ${reference.getDescription()}")
292311
}
293312
throw SchemaClassScannerError("Two different classes used for type ${type.name}:\n${realEntry.joinReferences()}\n\n- ${javaType.unwrap()}:\n| ${reference.getDescription()}")

src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.coxautodev.graphql.tools
22

3+
import graphql.introspection.Introspection
34
import graphql.language.AbstractNode
45
import graphql.language.ArrayValue
56
import graphql.language.BooleanValue
@@ -22,6 +23,7 @@ import graphql.language.TypeDefinition
2223
import graphql.language.TypeName
2324
import graphql.language.UnionTypeDefinition
2425
import graphql.language.Value
26+
import graphql.schema.GraphQLDirective
2527
import graphql.schema.GraphQLEnumType
2628
import graphql.schema.GraphQLFieldDefinition
2729
import graphql.schema.GraphQLInputObjectType
@@ -36,15 +38,20 @@ import graphql.schema.GraphQLType
3638
import graphql.schema.GraphQLTypeReference
3739
import graphql.schema.GraphQLUnionType
3840
import graphql.schema.TypeResolverProxy
41+
import graphql.schema.idl.DirectiveBehavior
42+
import graphql.schema.idl.RuntimeWiring
3943
import graphql.schema.idl.ScalarInfo
44+
import graphql.schema.idl.SchemaGeneratorHelper
45+
import java.util.ArrayList
46+
import java.util.HashSet
4047
import kotlin.reflect.KClass
4148

4249
/**
4350
* Parses a GraphQL Schema and maps object fields to provided class methods.
4451
*
4552
* @author Andrew Potter
4653
*/
47-
class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, private val options: SchemaParserOptions) {
54+
class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, private val options: SchemaParserOptions, private val runtimeWiring: RuntimeWiring) {
4855

4956
companion object {
5057
const val DEFAULT_DEPRECATION_MESSAGE = "No longer supported"
@@ -79,6 +86,9 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat
7986
private val permittedTypesForInputObject: Set<String> =
8087
(inputObjectDefinitions.map { it.name } + enumDefinitions.map { it.name }).toSet()
8188

89+
private val schemaGeneratorHelper = SchemaGeneratorHelper()
90+
private val directiveGenerator = DirectiveBehavior()
91+
8292
/**
8393
* Parses the given schema with respect to the given dictionary and returns GraphQL objects.
8494
*/
@@ -124,19 +134,40 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat
124134
.definition(definition)
125135
.description(getDocumentation(definition))
126136

137+
builder.withDirectives(*buildDirectives(definition.directives, setOf(), Introspection.DirectiveLocation.OBJECT))
138+
127139
definition.implements.forEach { implementsDefinition ->
128140
val interfaceName = (implementsDefinition as TypeName).name
129141
builder.withInterface(interfaces.find { it.name == interfaceName } ?: throw SchemaError("Expected interface type with name '$interfaceName' but found none!"))
130142
}
131143

132144
definition.getExtendedFieldDefinitions(extensionDefinitions).forEach { fieldDefinition ->
145+
fieldDefinition.description
133146
builder.field { field ->
134147
createField(field, fieldDefinition)
135148
field.dataFetcher(fieldResolversByType[definition]?.get(fieldDefinition)?.createDataFetcher() ?: throw SchemaError("No resolver method found for object type '${definition.name}' and field '${fieldDefinition.name}', this is most likely a bug with graphql-java-tools"))
149+
150+
val wiredField = directiveGenerator.onField(field.build(), DirectiveBehavior.Params(runtimeWiring))
151+
GraphQLFieldDefinition.Builder(wiredField)
136152
}
137153
}
138154

139-
return builder.build()
155+
val objectType = builder.build()
156+
157+
return directiveGenerator.onObject(objectType, DirectiveBehavior.Params(runtimeWiring))
158+
}
159+
160+
private fun buildDirectives(directives: List<Directive>, directiveDefinitions: Set<GraphQLDirective>, directiveLocation: Introspection.DirectiveLocation): Array<GraphQLDirective> {
161+
val names = HashSet<String>()
162+
163+
val output = ArrayList<GraphQLDirective>()
164+
for (directive in directives) {
165+
if (!names.contains(directive.name)) {
166+
names.add(directive.name)
167+
output.add(schemaGeneratorHelper.buildDirective(directive, directiveDefinitions, directiveLocation))
168+
}
169+
}
170+
return output.toTypedArray()
140171
}
141172

142173
private fun createInputObject(definition: InputObjectTypeDefinition): GraphQLInputObjectType {
@@ -145,17 +176,20 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat
145176
.definition(definition)
146177
.description(getDocumentation(definition))
147178

179+
builder.withDirectives(*buildDirectives(definition.directives, setOf(), Introspection.DirectiveLocation.INPUT_OBJECT))
180+
148181
definition.inputValueDefinitions.forEach { inputDefinition ->
149182
builder.field { field ->
150183
field.name(inputDefinition.name)
151184
field.definition(inputDefinition)
152185
field.description(getDocumentation(inputDefinition))
153186
field.defaultValue(inputDefinition.defaultValue)
154187
field.type(determineInputType(inputDefinition.type))
188+
field.withDirectives(*buildDirectives(definition.directives, setOf(), Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION))
155189
}
156190
}
157191

158-
return builder.build()
192+
return directiveGenerator.onInputObject(builder.build(), DirectiveBehavior.Params(runtimeWiring))
159193
}
160194

161195
private fun createEnumObject(definition: EnumTypeDefinition): GraphQLEnumType {
@@ -168,6 +202,8 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat
168202
.definition(definition)
169203
.description(getDocumentation(definition))
170204

205+
builder.withDirectives(*buildDirectives(definition.directives, setOf(), Introspection.DirectiveLocation.ENUM))
206+
171207
definition.enumValueDefinitions.forEach { enumDefinition ->
172208
val enumName = enumDefinition.name
173209
val enumValue = type.unwrap().enumConstants.find { (it as Enum<*>).name == enumName } ?: throw SchemaError("Expected value for name '$enumName' in enum '${type.unwrap().simpleName}' but found none!")
@@ -179,7 +215,7 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat
179215
}
180216
}
181217

182-
return builder.build()
218+
return directiveGenerator.onEnum(builder.build(), DirectiveBehavior.Params(runtimeWiring))
183219
}
184220

185221
private fun createInterfaceObject(definition: InterfaceTypeDefinition): GraphQLInterfaceType {
@@ -190,11 +226,13 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat
190226
.description(getDocumentation(definition))
191227
.typeResolver(TypeResolverProxy())
192228

229+
builder.withDirectives(*buildDirectives(definition.directives, setOf(), Introspection.DirectiveLocation.INTERFACE))
230+
193231
definition.fieldDefinitions.forEach { fieldDefinition ->
194232
builder.field { field -> createField(field, fieldDefinition) }
195233
}
196234

197-
return builder.build()
235+
return directiveGenerator.onInterface(builder.build(), DirectiveBehavior.Params(runtimeWiring))
198236
}
199237

200238
private fun createUnionObject(definition: UnionTypeDefinition, types: List<GraphQLObjectType>): GraphQLUnionType {
@@ -205,8 +243,10 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat
205243
.description(getDocumentation(definition))
206244
.typeResolver(TypeResolverProxy())
207245

246+
builder.withDirectives(*buildDirectives(definition.directives, setOf(), Introspection.DirectiveLocation.UNION))
247+
208248
getLeafUnionObjects(definition, types).forEach { builder.possibleType(it) }
209-
return builder.build()
249+
return directiveGenerator.onUnion(builder.build(), DirectiveBehavior.Params(runtimeWiring))
210250
}
211251

212252
private fun getLeafUnionObjects(definition: UnionTypeDefinition, types: List<GraphQLObjectType>): List<GraphQLObjectType> {
@@ -241,8 +281,11 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat
241281
argument.description(getDocumentation(argumentDefinition))
242282
argument.defaultValue(buildDefaultValue(argumentDefinition.defaultValue))
243283
argument.type(determineInputType(argumentDefinition.type))
284+
argument.withDirectives(*buildDirectives(argumentDefinition.directives, setOf(), Introspection.DirectiveLocation.ARGUMENT_DEFINITION))
285+
244286
}
245287
}
288+
field.withDirectives(*buildDirectives(fieldDefinition.directives, setOf(), Introspection.DirectiveLocation.FIELD_DEFINITION))
246289
return field
247290
}
248291

0 commit comments

Comments
 (0)