Skip to content

Commit 0ab80bb

Browse files
Andy2003jexp
authored andcommitted
Rely on GraphQL-Schema instead of Schema-Definitions (#62)
1 parent 03530b1 commit 0ab80bb

27 files changed

+1116
-838
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@
195195
</executions>
196196
<configuration>
197197
<includes>
198+
<!-- TODO what to do with this part? -->
198199
<file>packages.md</file>
199200
</includes>
200201
</configuration>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.neo4j.graphql
2+
3+
import graphql.schema.DataFetcher
4+
import graphql.schema.GraphQLFieldDefinition
5+
import graphql.schema.GraphQLFieldsContainer
6+
import graphql.schema.GraphQLObjectType
7+
8+
abstract class AugmentationHandler(val schemaConfig: SchemaConfig) {
9+
companion object {
10+
const val QUERY = "Query"
11+
const val MUTATION = "Mutation"
12+
}
13+
14+
open fun augmentType(type: GraphQLFieldsContainer, buildingEnv: BuildingEnv) {}
15+
16+
abstract fun createDataFetcher(rootType: GraphQLObjectType, fieldDefinition: GraphQLFieldDefinition): DataFetcher<Cypher>?
17+
18+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package org.neo4j.graphql
2+
3+
import graphql.schema.*
4+
5+
class BuildingEnv(val types: MutableMap<String, GraphQLType>) {
6+
7+
private val typesForRelation = types.values
8+
.filterIsInstance<GraphQLObjectType>()
9+
.filter { it.getDirective(DirectiveConstants.RELATION) != null }
10+
.map { it.getDirectiveArgument<String>(DirectiveConstants.RELATION, DirectiveConstants.RELATION_NAME, null)!! to it.name }
11+
.toMap()
12+
13+
fun buildFieldDefinition(
14+
prefix: String,
15+
resultType: GraphQLOutputType,
16+
scalarFields: List<GraphQLFieldDefinition>,
17+
nullableResult: Boolean,
18+
forceOptionalProvider: (field: GraphQLFieldDefinition) -> Boolean = { false }
19+
): GraphQLFieldDefinition.Builder {
20+
var type: GraphQLOutputType = resultType
21+
if (!nullableResult) {
22+
type = GraphQLNonNull(type)
23+
}
24+
return GraphQLFieldDefinition.newFieldDefinition()
25+
.name("$prefix${resultType.name}")
26+
.arguments(getInputValueDefinitions(scalarFields, forceOptionalProvider))
27+
.type(type.ref() as GraphQLOutputType)
28+
}
29+
30+
fun getInputValueDefinitions(
31+
relevantFields: List<GraphQLFieldDefinition>,
32+
forceOptionalProvider: (field: GraphQLFieldDefinition) -> Boolean): List<GraphQLArgument> {
33+
return relevantFields.map { field ->
34+
var type = field.type as GraphQLType
35+
type = getInputType(type)
36+
type = if (forceOptionalProvider(field)) {
37+
(type as? GraphQLNonNull)?.wrappedType ?: type
38+
} else {
39+
type
40+
}
41+
input(field.name, type)
42+
}
43+
}
44+
45+
/**
46+
* add the given operation to the corresponding rootType
47+
*/
48+
fun addOperation(rootTypeName: String, fieldDefinition: GraphQLFieldDefinition) {
49+
val rootType = types[rootTypeName]
50+
types[rootTypeName] = if (rootType == null) {
51+
val builder = GraphQLObjectType.newObject()
52+
builder.name(rootTypeName)
53+
.field(fieldDefinition)
54+
.build()
55+
} else {
56+
val existingRootType = (rootType as? GraphQLObjectType
57+
?: throw IllegalStateException("root type $rootTypeName is not an object type"))
58+
if (existingRootType.getFieldDefinition(fieldDefinition.name) != null) {
59+
return // definition already exists
60+
}
61+
existingRootType
62+
.transform { builder -> builder.field(fieldDefinition) }
63+
}
64+
}
65+
66+
fun addFilterType(type: GraphQLFieldsContainer, handled: MutableSet<String> = mutableSetOf()): String {
67+
val filterName = "_${type.name}Filter"
68+
if (handled.contains(filterName)) {
69+
return filterName
70+
}
71+
val existingFilterType = types[filterName]
72+
if (existingFilterType != null) {
73+
return (existingFilterType as? GraphQLInputType)?.name
74+
?: throw IllegalStateException("Filter type $filterName is already defined but not an input type")
75+
}
76+
handled.add(filterName)
77+
val builder = GraphQLInputObjectType.newInputObject()
78+
.name(filterName)
79+
listOf("AND", "OR", "NOT").forEach {
80+
builder.field(GraphQLInputObjectField.newInputObjectField()
81+
.name(it)
82+
.type(GraphQLList(GraphQLNonNull(GraphQLTypeReference(filterName)))))
83+
}
84+
type.fieldDefinitions
85+
.filter { it.dynamicPrefix() == null } // TODO currently we do not support filtering on dynamic properties
86+
.forEach { field ->
87+
val typeDefinition = field.type.inner()
88+
val filterType = when {
89+
typeDefinition.isNeo4jType() -> getInputType(typeDefinition).name
90+
typeDefinition.isScalar() -> typeDefinition.innerName()
91+
typeDefinition is GraphQLEnumType -> typeDefinition.innerName()
92+
else -> addFilterType(getInnerFieldsContainer(typeDefinition), handled)
93+
}
94+
95+
Operators.forType(types[filterType] ?: typeDefinition).forEach { op ->
96+
val wrappedType: GraphQLInputType = when {
97+
op.list -> GraphQLList(GraphQLTypeReference(filterType))
98+
else -> GraphQLTypeReference(filterType)
99+
}
100+
builder.field(GraphQLInputObjectField.newInputObjectField()
101+
.name(op.fieldName(field.name))
102+
.type(wrappedType))
103+
}
104+
}
105+
types[filterName] = builder.build()
106+
return filterName
107+
}
108+
109+
fun addOrdering(type: GraphQLFieldsContainer): String? {
110+
val orderingName = "_${type.name}Ordering"
111+
var existingOrderingType = types[orderingName]
112+
if (existingOrderingType != null) {
113+
return (existingOrderingType as? GraphQLInputType)?.name
114+
?: throw IllegalStateException("Ordering type $type.name is already defined but not an input type")
115+
}
116+
val sortingFields = type.fieldDefinitions
117+
.filter { it.type.isScalar() || it.isNeo4jType() }
118+
.sortedByDescending { it.isID() }
119+
if (sortingFields.isEmpty()) {
120+
return null
121+
}
122+
existingOrderingType = GraphQLEnumType.newEnum()
123+
.name(orderingName)
124+
.values(sortingFields.flatMap { fd ->
125+
listOf("_asc", "_desc")
126+
.map {
127+
GraphQLEnumValueDefinition
128+
.newEnumValueDefinition()
129+
.name(fd.name + it)
130+
.value(fd.name + it)
131+
.build()
132+
}
133+
})
134+
.build()
135+
types[orderingName] = existingOrderingType
136+
return orderingName
137+
}
138+
139+
fun addInputType(inputName: String, relevantFields: List<GraphQLFieldDefinition>): GraphQLInputType {
140+
var inputType = types[inputName]
141+
if (inputType != null) {
142+
return inputType as? GraphQLInputType
143+
?: throw IllegalStateException("Filter type $inputName is already defined but not an input type")
144+
}
145+
inputType = getInputType(inputName, relevantFields)
146+
types[inputName] = inputType
147+
return inputType
148+
}
149+
150+
fun getTypeForRelation(nameOfRelation: String): GraphQLObjectType? {
151+
return typesForRelation[nameOfRelation]?.let { types[it] } as? GraphQLObjectType
152+
}
153+
154+
private fun getInputType(inputName: String, relevantFields: List<GraphQLFieldDefinition>): GraphQLInputObjectType {
155+
return GraphQLInputObjectType.newInputObject()
156+
.name(inputName)
157+
.fields(getInputValueDefinitions(relevantFields))
158+
.build()
159+
}
160+
161+
private fun getInputValueDefinitions(relevantFields: List<GraphQLFieldDefinition>): List<GraphQLInputObjectField> {
162+
return relevantFields.map {
163+
val type = (it.type as? GraphQLNonNull)?.wrappedType ?: it.type
164+
GraphQLInputObjectField
165+
.newInputObjectField()
166+
.name(it.name)
167+
.type(getInputType(type).ref() as GraphQLInputType)
168+
.build()
169+
}
170+
}
171+
172+
private fun getInnerFieldsContainer(type: GraphQLType): GraphQLFieldsContainer {
173+
var innerType = type.inner()
174+
if (innerType is GraphQLTypeReference) {
175+
innerType = types[innerType.name]
176+
?: throw IllegalArgumentException("${innerType.name} is unknown")
177+
}
178+
return innerType as? GraphQLFieldsContainer
179+
?: throw IllegalArgumentException("${innerType.name} is neither an object nor an interface")
180+
}
181+
182+
private fun getInputType(type: GraphQLType): GraphQLInputType {
183+
val inner = type.inner()
184+
if (inner is GraphQLInputType) {
185+
return type as GraphQLInputType
186+
}
187+
if (inner.isNeo4jType()) {
188+
return neo4jTypeDefinitions
189+
.find { it.typeDefinition == inner.name }
190+
?.let { types[it.inputDefinition] } as? GraphQLInputType
191+
?: throw IllegalArgumentException("Cannot find input type for ${inner.name}")
192+
}
193+
return type as? GraphQLInputType
194+
?: throw IllegalArgumentException("${type.name} is not allowed for input")
195+
}
196+
197+
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package org.neo4j.graphql
22

3-
import graphql.language.Type
3+
import graphql.schema.GraphQLType
44

5-
data class Cypher @JvmOverloads constructor(val query: String, val params: Map<String, Any?> = emptyMap(), var type: Type<*>? = null) {
5+
data class Cypher @JvmOverloads constructor(val query: String, val params: Map<String, Any?> = emptyMap(), var type: GraphQLType? = null) {
66
fun with(p: Map<String, Any?>) = this.copy(params = this.params + p)
77
fun escapedQuery() = query.replace("\"", "\\\"").replace("'", "\\'")
88

99
companion object {
10+
@JvmStatic
1011
val EMPTY = Cypher("")
1112
}
1213
}

src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.neo4j.graphql
22

3+
import graphql.schema.GraphQLArgument
4+
import graphql.schema.GraphQLInputType
5+
import graphql.schema.GraphQLType
36
import java.io.PrintWriter
47
import java.io.StringWriter
58

@@ -12,3 +15,11 @@ fun Throwable.stackTraceAsString(): String {
1215
fun <T> Iterable<T>.joinNonEmpty(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
1316
return if (iterator().hasNext()) joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() else ""
1417
}
18+
19+
fun input(name: String, type: GraphQLType): GraphQLArgument {
20+
return GraphQLArgument
21+
.newArgument()
22+
.name(name)
23+
.type((type.ref() as? GraphQLInputType)
24+
?: throw IllegalArgumentException("${type.innerName()} is not allowed for input")).build()
25+
}

src/main/kotlin/org/neo4j/graphql/Facades.kt

Lines changed: 0 additions & 85 deletions
This file was deleted.

0 commit comments

Comments
 (0)