Skip to content

Commit c35b84d

Browse files
authored
Merge branch 'master' into #434_GraphQlInputObjectType_at_runtime_wiring_on_nonnull_input
2 parents cb9bf84 + 3abb3ed commit c35b84d

File tree

15 files changed

+505
-171
lines changed

15 files changed

+505
-171
lines changed

.github/ISSUE_TEMPLATE/bug.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
name: Bug
3+
about: Have you found and confirmed a bug? Report it here.
4+
title: ''
5+
labels: bug
6+
assignees: ''
7+
---
8+
9+
<!-- ⚠️ Please ensure you are reporting the bug in the correct repository! -->
10+
11+
## Description
12+
13+
<!-- Please provide a brief description of the bug -->
14+
15+
### Expected behavior
16+
17+
<!-- Please describe what you expect to happen -->
18+
19+
### Actual behavior
20+
21+
<!-- Please describe what is actually happening -->
22+
23+
### Steps to reproduce the bug
24+
25+
<!-- Please provide the steps to reproduce the issue -->
26+
27+
1. ...
28+
2. ...
29+
3. ...
30+

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Question
4+
url: https://spectrum.chat/graphql-java-kick
5+
about: Anything you are not sure about? Ask the community on Spectrum!

.github/ISSUE_TEMPLATE/feature.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
name: Feature
3+
about: Suggest an idea for a new feature.
4+
title: ''
5+
labels: enhancement
6+
assignees: ''
7+
---
8+
9+
<!-- ⚠️ Please ensure you are requesting the feature in the correct repository! -->
10+
11+
__I want to suggest an idea and checked that ...__
12+
13+
- [ ] ... to my best knowledge, my idea wouldn't break something for other users
14+
- [ ] ... the documentation does not mention anything about my idea
15+
- [ ] ... there are no open or closed issues that are related to my idea
16+
17+
## Description
18+
19+
<!-- Please provide a brief description of the feature -->
20+
21+
### Use Cases
22+
23+
<!-- Please describe how your suggestion would benefit you and other users -->

README.md

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,52 @@
22

33
[![TravisCI Build](https://travis-ci.org/graphql-java-kickstart/graphql-java-tools.svg?branch=master)](https://travis-ci.org/graphql-java-kickstart/graphql-java-tools)
44
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.graphql-java-kickstart/graphql-java-tools/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java-kickstart/graphql-java-tools)
5-
[![Chat on Slack](https://img.shields.io/badge/slack-join%20chat-informational)](https://graphqljavakickstart.slack.com)
5+
[![Chat on Spectrum](https://img.shields.io/badge/spectrum-join%20the%20community-%23800080)](https://spectrum.chat/graphql-java-kick)
66

77
This library allows you to use the GraphQL schema language to build your [graphql-java](https://github.com/graphql-java/graphql-java) schema.
88
Inspired by [graphql-tools](https://github.com/apollographql/graphql-tools), it parses the given GraphQL schema and allows you to BYOO (bring your own object) to fill in the implementations.
9-
GraphQL Java Tools works extremely well if you already have domain POJOs that hold your data (e.g. for RPC, ORM, REST, etc) by allowing you to map these magically to GraphQL objects.
9+
GraphQL Java Tools works well if you already have domain POJOs that hold your data (e.g. for RPC, ORM, REST, etc) by allowing you to map these "magically" to GraphQL objects.
1010

1111
GraphQL Java Tools aims for seamless integration with Java, but works for any JVM language. Try it with Kotlin!
1212

13-
## WARNING: NoClassDefFoundError when using Spring Boot
13+
## We are looking for contributors!
14+
Are you interested in improving our documentation, working on the codebase, reviewing PRs?
1415

15-
If you're using `graphl-java-tools` with Spring Boot version lower than 2.2 you need to set the `kotlin.version` in
16-
your Spring Boot project explicitly to version 1.3.70, because Spring Boot Starter parent currently overrides it with
17-
a 1.2.* version of Kotlin.
18-
`graphql-java-tools` requires 1.3.* however because of its coroutine support. If you don't override this version
19-
you will run into a `NoClassDefFoundError`.
16+
[Reach out to us on Spectrum](https://spectrum.chat/graphql-java-kick) and join the team!
2017

21-
Spring Boot team has indicated the Kotlin version will be upgraded to 1.3 in Spring Boot 2.2.
18+
## Quick start
2219

2320
### Using Gradle
24-
Set the Kotlin version in your `gradle.properties`
21+
Set the Kotlin version in your `gradle.properties`:
2522
```
2623
kotlin.version=1.3.70
2724
```
2825

26+
Add the dependency:
27+
```groovy
28+
compile 'com.graphql-java-kickstart:graphql-java-tools:6.2.0'
29+
```
30+
2931
### Using Maven
30-
Set the Kotlin version in your `<properties>` section
32+
Set the Kotlin version in your `<properties>` section:
3133
```xml
3234
<properties>
3335
<kotlin.version>1.3.70</kotlin.version>
3436
</properties>
3537
```
3638

39+
Add the dependency:
40+
```xml
41+
<dependency>
42+
<groupId>com.graphql-java-kickstart</groupId>
43+
<artifactId>graphql-java-tools</artifactId>
44+
<version>6.2.0</version>
45+
</dependency>
46+
```
47+
3748
## Documentation
3849

39-
Take a look at our new [documentation](https://www.graphql-java-kickstart.com/tools/) for more details.
50+
Take a look at our [documentation](https://www.graphql-java-kickstart.com/tools/) for more details.
4051

4152
## Why GraphQL Java Tools?
4253

@@ -49,31 +60,12 @@ A few libraries exist to ease the boilerplate pain, including [GraphQL-Java's bu
4960
* **Class Validation**: Since there aren't any compile-time checks of the type->class relationship, GraphQL Java Tools will warn you if you provide classes/types that you don't need to, as well as erroring if you use the wrong Java class for a certain GraphQL type when it builds the schema.
5061
* **Unit Testing**: Since your GraphQL schema is independent of your data model, this makes your classes simple and extremely testable.
5162

52-
## Build with Maven or Gradle
63+
## WARNING: NoClassDefFoundError when using Spring Boot
5364

54-
```xml
55-
<dependency>
56-
<groupId>com.graphql-java-kickstart</groupId>
57-
<artifactId>graphql-java-tools</artifactId>
58-
<version>6.2.0</version>
59-
</dependency>
60-
```
61-
```groovy
62-
compile 'com.graphql-java-kickstart:graphql-java-tools:6.0.2'
63-
```
65+
If you're using `graphl-java-tools` with Spring Boot version lower than 2.2 you need to set the `kotlin.version` in
66+
your Spring Boot project explicitly to version 1.3.70, because Spring Boot Starter parent currently overrides it with
67+
a 1.2.* version of Kotlin.
68+
`graphql-java-tools` requires 1.3.* however because of its coroutine support. If you don't override this version
69+
you will run into a `NoClassDefFoundError`.
6470

65-
New releases will be available faster in the JCenter repository than in Maven Central. Add the following to use for Maven
66-
```xml
67-
<repositories>
68-
<repository>
69-
<id>jcenter</id>
70-
<url>https://jcenter.bintray.com/</url>
71-
</repository>
72-
</repositories>
73-
```
74-
For Gradle:
75-
```groovy
76-
repositories {
77-
jcenter()
78-
}
79-
```
71+
Spring Boot team has indicated the Kotlin version will be upgraded to 1.3 in Spring Boot 2.2.

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
<properties>
1515
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1616
<java.version>1.8</java.version>
17-
<kotlin.version>1.3.70</kotlin.version>
18-
<kotlin-coroutines.version>1.2.1</kotlin-coroutines.version>
17+
<kotlin.version>1.3.72</kotlin.version>
18+
<kotlin-coroutines.version>1.3.9</kotlin-coroutines.version>
1919
<jackson.version>2.10.3</jackson.version>
2020
<graphql-java.version>15.0</graphql-java.version>
2121

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,30 @@ internal class SchemaClassScanner(
7777
do {
7878
do {
7979
// Require all implementors of discovered interfaces to be discovered or provided.
80-
handleInterfaceOrUnionSubTypes(getAllObjectTypesImplementingDiscoveredInterfaces()) { "Object type '${it.name}' implements a known interface, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
80+
handleDictionaryTypes(getAllObjectTypesImplementingDiscoveredInterfaces()) { "Object type '${it.name}' implements a known interface, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
8181
} while (scanQueue())
8282

8383
// Require all members of discovered unions to be discovered.
84-
handleInterfaceOrUnionSubTypes(getAllObjectTypeMembersOfDiscoveredUnions()) { "Object type '${it.name}' is a member of a known union, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
84+
handleDictionaryTypes(getAllObjectTypeMembersOfDiscoveredUnions()) { "Object type '${it.name}' is a member of a known union, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
8585
} while (scanQueue())
8686

87+
// Find unused types and include them if required
88+
if (options.includeUnusedTypes) {
89+
do {
90+
val unusedDefinitions = (definitionsByName.values - (dictionary.keys.toSet() + unvalidatedTypes))
91+
.filter { definition -> definition.name != "PageInfo" }
92+
.filterIsInstance<ObjectTypeDefinition>().distinct()
93+
94+
if (unusedDefinitions.isEmpty()) {
95+
break
96+
}
97+
98+
val unusedDefinition = unusedDefinitions.first()
99+
100+
handleDictionaryTypes(listOf(unusedDefinition)) { "Object type '${it.name}' is unused and includeUnusedTypes is true. Please pass a class for type '${it.name}' in the parser's dictionary." }
101+
} while (scanQueue())
102+
}
103+
87104
return validateAndCreateResult(rootTypeHolder)
88105
}
89106

@@ -208,7 +225,7 @@ internal class SchemaClassScanner(
208225
}.flatten().distinct()
209226
}
210227

211-
private fun handleInterfaceOrUnionSubTypes(types: List<ObjectTypeDefinition>, failureMessage: (ObjectTypeDefinition) -> String) {
228+
private fun handleDictionaryTypes(types: List<ObjectTypeDefinition>, failureMessage: (ObjectTypeDefinition) -> String) {
212229
types.forEach { type ->
213230
val dictionaryContainsType = dictionary.filter { it.key.name == type.name }.isNotEmpty()
214231
if (!unvalidatedTypes.contains(type) && !dictionaryContainsType) {

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class SchemaParser internal constructor(
7676
val inputObjects: MutableList<GraphQLInputObjectType> = mutableListOf()
7777
inputObjectDefinitions.forEach {
7878
if (inputObjects.none { io -> io.name == it.name }) {
79-
inputObjects.add(createInputObject(it, inputObjects))
79+
inputObjects.add(createInputObject(it, inputObjects, mutableSetOf()))
8080
}
8181
}
8282
val interfaces = interfaceDefinitions.map { createInterfaceObject(it, inputObjects) }
@@ -155,7 +155,8 @@ class SchemaParser internal constructor(
155155
return schemaGeneratorDirectiveHelper.onObject(objectType, directiveHelperParameters)
156156
}
157157

158-
private fun createInputObject(definition: InputObjectTypeDefinition, inputObjects: List<GraphQLInputObjectType>): GraphQLInputObjectType {
158+
private fun createInputObject(definition: InputObjectTypeDefinition, inputObjects: List<GraphQLInputObjectType>,
159+
referencingInputObjects: MutableSet<String>): GraphQLInputObjectType {
159160
val extensionDefinitions = inputExtensionDefinitions.filter { it.name == definition.name }
160161

161162
val builder = GraphQLInputObjectType.newInputObject()
@@ -166,14 +167,16 @@ class SchemaParser internal constructor(
166167

167168
builder.withDirectives(*buildDirectives(definition.directives, Introspection.DirectiveLocation.INPUT_OBJECT))
168169

170+
referencingInputObjects.add(definition.name)
171+
169172
(extensionDefinitions + definition).forEach {
170173
it.inputValueDefinitions.forEach { inputDefinition ->
171174
val fieldBuilder = GraphQLInputObjectField.newInputObjectField()
172175
.name(inputDefinition.name)
173176
.definition(inputDefinition)
174177
.description(if (inputDefinition.description != null) inputDefinition.description.content else getDocumentation(inputDefinition))
175178
.defaultValue(buildDefaultValue(inputDefinition.defaultValue))
176-
.type(determineInputType(inputDefinition.type, inputObjects))
179+
.type(determineInputType(inputDefinition.type, inputObjects, referencingInputObjects))
177180
.withDirectives(*buildDirectives(inputDefinition.directives, Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION))
178181
builder.field(fieldBuilder.build())
179182
}
@@ -280,7 +283,7 @@ class SchemaParser internal constructor(
280283
.name(argumentDefinition.name)
281284
.definition(argumentDefinition)
282285
.description(if (argumentDefinition.description != null) argumentDefinition.description.content else getDocumentation(argumentDefinition))
283-
.type(determineInputType(argumentDefinition.type, inputObjects))
286+
.type(determineInputType(argumentDefinition.type, inputObjects, setOf()))
284287
.apply { buildDefaultValue(argumentDefinition.defaultValue)?.let { defaultValue(it) } }
285288
.withDirectives(*buildDirectives(argumentDefinition.directives, Introspection.DirectiveLocation.ARGUMENT_DEFINITION))
286289

@@ -380,7 +383,7 @@ class SchemaParser internal constructor(
380383
is NonNullType -> GraphQLNonNull(determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
381384
is InputObjectTypeDefinition -> {
382385
log.info("Create input object")
383-
createInputObject(typeDefinition, inputObjects)
386+
createInputObject(typeDefinition, inputObjects, mutableSetOf())
384387
}
385388
is TypeName -> {
386389
val scalarType = customScalars[typeDefinition.name]
@@ -398,16 +401,19 @@ class SchemaParser internal constructor(
398401
else -> throw SchemaError("Unknown type: $typeDefinition")
399402
}
400403

401-
private fun determineInputType(typeDefinition: Type<*>, inputObjects: List<GraphQLInputObjectType>) =
402-
determineInputType(GraphQLInputType::class, typeDefinition, permittedTypesForInputObject, inputObjects) as GraphQLInputType
404+
private fun determineInputType(typeDefinition: Type<*>, inputObjects: List<GraphQLInputObjectType>, referencingInputObjects: Set<String>) =
405+
determineInputType(GraphQLInputType::class, typeDefinition, permittedTypesForInputObject, inputObjects, referencingInputObjects) as GraphQLInputType
403406

404-
private fun <T : Any> determineInputType(expectedType: KClass<T>, typeDefinition: Type<*>, allowedTypeReferences: Set<String>, inputObjects: List<GraphQLInputObjectType>): GraphQLType =
407+
private fun <T : Any> determineInputType(expectedType: KClass<T>,
408+
typeDefinition: Type<*>, allowedTypeReferences: Set<String>,
409+
inputObjects: List<GraphQLInputObjectType>,
410+
referencingInputObjects: Set<String>): GraphQLType =
405411
when (typeDefinition) {
406412
is ListType -> GraphQLList(determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
407413
is NonNullType -> GraphQLNonNull(determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
408414
is InputObjectTypeDefinition -> {
409415
log.info("Create input object")
410-
createInputObject(typeDefinition, inputObjects)
416+
createInputObject(typeDefinition, inputObjects, referencingInputObjects as MutableSet<String>)
411417
}
412418
is TypeName -> {
413419
val scalarType = customScalars[typeDefinition.name]
@@ -425,9 +431,14 @@ class SchemaParser internal constructor(
425431
} else {
426432
val filteredDefinitions = inputObjectDefinitions.filter { it.name == typeDefinition.name }
427433
if (filteredDefinitions.isNotEmpty()) {
428-
val inputObject = createInputObject(filteredDefinitions[0], inputObjects)
429-
(inputObjects as MutableList).add(inputObject)
430-
inputObject
434+
val referencingInputObject = referencingInputObjects.find { it == typeDefinition.name }
435+
if (referencingInputObject != null) {
436+
GraphQLTypeReference(referencingInputObject)
437+
} else {
438+
val inputObject = createInputObject(filteredDefinitions[0], inputObjects, referencingInputObjects as MutableSet<String>)
439+
(inputObjects as MutableList).add(inputObject)
440+
inputObject
441+
}
431442
} else {
432443
// todo: handle enum type
433444
GraphQLTypeReference(typeDefinition.name)

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import graphql.kickstart.tools.proxy.*
55
import graphql.kickstart.tools.relay.RelayConnectionFactory
66
import graphql.kickstart.tools.util.JavaType
77
import graphql.kickstart.tools.util.ParameterizedTypeImpl
8-
import graphql.kickstart.tools.util.coroutineScope
98
import graphql.schema.DataFetchingEnvironment
109
import graphql.schema.visibility.GraphqlFieldVisibility
1110
import kotlinx.coroutines.Dispatchers
@@ -30,7 +29,8 @@ data class SchemaParserOptions internal constructor(
3029
val introspectionEnabled: Boolean,
3130
val coroutineContextProvider: CoroutineContextProvider,
3231
val typeDefinitionFactories: List<TypeDefinitionFactory>,
33-
val fieldVisibility: GraphqlFieldVisibility?
32+
val fieldVisibility: GraphqlFieldVisibility?,
33+
val includeUnusedTypes: Boolean
3434
) {
3535
companion object {
3636
@JvmStatic
@@ -57,6 +57,7 @@ data class SchemaParserOptions internal constructor(
5757
private var coroutineContextProvider: CoroutineContextProvider? = null
5858
private var typeDefinitionFactories: MutableList<TypeDefinitionFactory> = mutableListOf(RelayConnectionFactory())
5959
private var fieldVisibility: GraphqlFieldVisibility? = null
60+
private var includeUnusedTypes = false
6061

6162
fun contextClass(contextClass: Class<*>) = this.apply {
6263
this.contextClass = contextClass
@@ -126,6 +127,10 @@ data class SchemaParserOptions internal constructor(
126127
this.fieldVisibility = fieldVisibility
127128
}
128129

130+
fun includeUnusedTypes(includeUnusedTypes: Boolean) = this.apply {
131+
this.includeUnusedTypes = includeUnusedTypes
132+
}
133+
129134
@ExperimentalCoroutinesApi
130135
fun build(): SchemaParserOptions {
131136
val coroutineContextProvider = coroutineContextProvider
@@ -136,8 +141,8 @@ data class SchemaParserOptions internal constructor(
136141
GenericWrapper(CompletableFuture::class, 0),
137142
GenericWrapper(CompletionStage::class, 0),
138143
GenericWrapper(Publisher::class, 0),
139-
GenericWrapper.withTransformer(ReceiveChannel::class, 0, { receiveChannel, environment ->
140-
environment.coroutineScope().publish(coroutineContextProvider.provide()) {
144+
GenericWrapper.withTransformer(ReceiveChannel::class, 0, { receiveChannel, _ ->
145+
publish(coroutineContextProvider.provide()) {
141146
try {
142147
for (item in receiveChannel) {
143148
send(item)
@@ -163,7 +168,8 @@ data class SchemaParserOptions internal constructor(
163168
introspectionEnabled,
164169
coroutineContextProvider,
165170
typeDefinitionFactories,
166-
fieldVisibility
171+
fieldVisibility,
172+
includeUnusedTypes
167173
)
168174
}
169175
}

0 commit comments

Comments
 (0)