Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,19 @@ Options:
* `useDefaultGenericWrappers`: Defaults to `true`. Tells the parser whether or not to add it's own list of well-known generic wrappers, such as `Future` and `CompletableFuture`.
* `allowUnimplementedResolvers`: Defaults to `false`. Allows a schema to be created even if not all GraphQL fields have resolvers. Intended only for development, it will log a warning to remind you to turn it off for production. Any unimplemented resolvers will throw errors when queried.
* `objectMapperConfigurer`: Exposes the Jackson `ObjectMapper` that handles marshalling arguments in method resolvers. Every method resolver gets its own mapper, and the configurer can configure it differently based on the GraphQL field definition.
* `preferGraphQLResolver`: In cases where you have a Resolver class and legacy class that conflict on type arguements, use the Resolver class instead of throwing an error.

Specificly this situation can occur when you have a graphql schema type `Foo` with a `bars` property and classes:
```java
// legacy class you can't change
class Foo {
Set<Bar> getBars() {...returns set of bars...}
}

// nice resolver that does what you want
class FooResolver implements GraphQLResolver<Foo> {
Set<BarDTO> getBars() {...converts Bar objects to BarDTO objects and retunrs set...}
}
```
You will now have the code find two different return types for getBars() and application will not start with the error ```Caused by: com.coxautodev.graphql.tools.SchemaClassScannerError: Two different classes used for type```
If this property is true it will ignore the legacy version.
17 changes: 15 additions & 2 deletions src/main/kotlin/com/coxautodev/graphql/tools/SchemaClassScanner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,11 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
typeWasSet = realEntry.setTypeIfMissing(clazz)

if (realEntry.typeClass != clazz) {
throw SchemaClassScannerError("Two different classes used for type ${type.name}:\n${realEntry.joinReferences()}\n\n- $clazz:\n| ${reference.getDescription()}")
if (options.preferGraphQLResolver && realEntry.hasResolverRef()) {
log.warn("The real entry ${realEntry.joinReferences()} is a GraphQLResolver so ignoring this one $clazz $reference")
} else {
throw SchemaClassScannerError("Two different classes used for type ${type.name}:\n${realEntry.joinReferences()}\n\n- $clazz:\n| ${reference.getDescription()}")
}
}
}

Expand Down Expand Up @@ -346,8 +350,16 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
fun addReference(reference: Reference) {
references.add(reference)
}

fun joinReferences() = "- $typeClass:\n| " + references.joinToString("\n| ") { it.getDescription() }

fun hasResolverRef():Boolean {
references.filterIsInstance<ReturnValueReference>().forEach{ reference ->
if (GraphQLResolver::class.java.isAssignableFrom(reference.getMethod().declaringClass)) {
return true
}
}
return false
}
}

abstract class Reference {
Expand Down Expand Up @@ -378,6 +390,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
}

class ReturnValueReference(private val method: Method): Reference() {
fun getMethod () = method;
override fun getDescription() = "return type of method $method"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ class SchemaParserDictionary {
}
}

data class SchemaParserOptions internal constructor(val contextClass: Class<*>?, val genericWrappers: List<GenericWrapper>, val allowUnimplementedResolvers: Boolean, val objectMapperProvider: PerFieldObjectMapperProvider, val proxyHandlers: List<ProxyHandler>) {
data class SchemaParserOptions internal constructor(val contextClass: Class<*>?, val genericWrappers: List<GenericWrapper>, val allowUnimplementedResolvers: Boolean, val objectMapperProvider: PerFieldObjectMapperProvider, val proxyHandlers: List<ProxyHandler>, val preferGraphQLResolver: Boolean) {
companion object {
@JvmStatic fun newOptions() = Builder()
@JvmStatic fun defaultOptions() = Builder().build()
Expand All @@ -238,6 +238,7 @@ data class SchemaParserOptions internal constructor(val contextClass: Class<*>?,
private var allowUnimplementedResolvers = false
private var objectMapperProvider: PerFieldObjectMapperProvider = PerFieldConfiguringObjectMapperProvider()
private val proxyHandlers: MutableList<ProxyHandler> = mutableListOf(Spring4AopProxyHandler(), GuiceAopProxyHandler(), JavassistProxyHandler())
private var preferGraphQLResolver = false

fun contextClass(contextClass: Class<*>) = this.apply {
this.contextClass = contextClass
Expand All @@ -263,6 +264,10 @@ data class SchemaParserOptions internal constructor(val contextClass: Class<*>?,
this.allowUnimplementedResolvers = allowUnimplementedResolvers
}

fun preferGraphQLResolver(preferGraphQLResolver: Boolean) = this.apply {
this.preferGraphQLResolver = preferGraphQLResolver
}

fun objectMapperConfigurer(objectMapperConfigurer: ObjectMapperConfigurer) = this.apply {
this.objectMapperProvider = PerFieldConfiguringObjectMapperProvider(objectMapperConfigurer)
}
Expand Down Expand Up @@ -290,7 +295,7 @@ data class SchemaParserOptions internal constructor(val contextClass: Class<*>?,
genericWrappers
}

return SchemaParserOptions(contextClass, wrappers, allowUnimplementedResolvers, objectMapperProvider, proxyHandlers)
return SchemaParserOptions(contextClass, wrappers, allowUnimplementedResolvers, objectMapperProvider, proxyHandlers, preferGraphQLResolver)
}
}

Expand Down