Skip to content

4. Add support for interfaces #51

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 2 commits into from
Aug 23, 2019
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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<artifactId>neo4j-graphql-java</artifactId>
<name>Neo4j GraphQL Java</name>
<description>GraphQL to Cypher Mapping</description>
<version>1.0.0-M03-SNAPSHOT</version>
<version>1.0.0-M04-SNAPSHOT</version>
<url>http://github.com/neo4j-graphql/neo4j-graphql-java</url>

<licenses>
Expand All @@ -22,7 +22,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<kotlin.version>1.3.21</kotlin.version>
<kotlin.version>1.3.41</kotlin.version>
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
<neo4j.version>3.5.6</neo4j.version>
<driver.version>1.7.2</driver.version>
Expand Down
4 changes: 2 additions & 2 deletions readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ val schema =
# Optional if you use generated queries
type Query {
person : [Person]
personByName(name:String) : Person
personByName(name:ID) : Person
}"""

val query = """ { p:personByName(name:"Joe") { age } } """
Expand Down Expand Up @@ -229,12 +229,12 @@ This example doesn't handle introspection queries but the one in the test direct
* auto-generate mutation fields for all objects to create, update, delete
* @cypher directive for top level queries and mutations, supports arguments
* date(time)
* interfaces

=== Next

* skip limit params
* sorting (nested)
* interfaces
* input types
* unions
* scalars
Expand Down
5 changes: 4 additions & 1 deletion src/main/kotlin/org/neo4j/graphql/QueryContext.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.neo4j.graphql

data class QueryContext @JvmOverloads constructor(
val topLevelWhere: Boolean = true
/**
* if true the <code>__typename</code> will be always returned for interfaces, no matter if it was queried or not
*/
var queryTypeOfInterfaces: Boolean = false
)
12 changes: 12 additions & 0 deletions src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ object SchemaBuilder {

AugmentationProcessor(typeDefinitionRegistry, config, builder).augmentSchema()

typeDefinitionRegistry
.getTypes(InterfaceTypeDefinition::class.java)
.forEach { typeDefinition ->
builder.type(typeDefinition.name) {
it.typeResolver { env ->
(env.getObject() as? Map<String, Any>)
?.let { data -> data[ProjectionBase.TYPE_NAME] as? String }
?.let { typeName -> env.schema.getObjectType(typeName) }
}
}
}

val runtimeWiring = builder.build()

// todo add new queries, filters, enums etc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ open class ProjectionBase(val metaProvider: MetaProvider) {
const val FIRST = "first"
const val OFFSET = "offset"
const val FILTER = "filter"

const val TYPE_NAME = "__typename"
}

fun orderBy(variable: String, args: MutableList<Argument>): String {
Expand Down Expand Up @@ -135,17 +137,39 @@ open class ProjectionBase(val metaProvider: MetaProvider) {
// a{.name},
// CASE WHEN a:Location THEN a { .foo } ELSE {} END
// ])
return selectionSet.selections.flatMapTo(mutableListOf<Cypher>()) {
var hasTypeName = false
val projections = selectionSet.selections.flatMapTo(mutableListOf<Cypher>()) {
when (it) {
is Field -> listOf(projectField(variable, it, nodeType, env, variableSuffix))
is Field -> {
hasTypeName = hasTypeName || (it.name == TYPE_NAME)
listOf(projectField(variable, it, nodeType, env, variableSuffix))
}
is InlineFragment -> projectInlineFragment(variable, it, env, variableSuffix)
is FragmentSpread -> projectNamedFragments(variable, it, env, variableSuffix)
else -> emptyList()
}
}
if (nodeType is InterfaceDefinitionNodeFacade
&& !hasTypeName
&& (env.getLocalContext() as? QueryContext)?.queryTypeOfInterfaces == true
) {
// for interfaces the typename is required to determine the correct implementation
projections.add(projectField(variable, Field(TYPE_NAME), nodeType, env, variableSuffix))
}
return projections
}

private fun projectField(variable: String, field: Field, type: NodeFacade, env: DataFetchingEnvironment, variableSuffix: String?): Cypher {
if (field.name == TYPE_NAME) {
return if (type.isRelationType()) {
Cypher("${field.aliasOrName()}: '${type.name()}'")
} else {
val paramName = paramName(variable, "validTypes", null)
val validTypeLabels = metaProvider.getValidTypeLabels(type)
Cypher("${field.aliasOrName()}: head( [ label IN labels($variable) WHERE label IN $$paramName ] )",
mapOf(paramName to validTypeLabels))
}
}
val fieldDefinition = type.getFieldDefinition(field.name)
?: throw IllegalStateException("No field ${field.name} in ${type.name()}")
val cypherDirective = fieldDefinition.cypherDirective()
Expand Down Expand Up @@ -324,4 +348,4 @@ open class ProjectionBase(val metaProvider: MetaProvider) {

private fun numericArgument(arguments: List<Argument>, name: String, defaultValue: Number = 0) =
(arguments.find { it.name.toLowerCase() == name }?.value?.toJavaValue() as Number?) ?: defaultValue
}
}
Loading