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
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
</executions>
<configuration>
<includes>
<!-- TODO what to do with this part? -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good question, need to check. I guess we just copied that from somewhere for maven central release.

<file>packages.md</file>
</includes>
</configuration>
Expand Down
18 changes: 18 additions & 0 deletions src/main/kotlin/org/neo4j/graphql/AugmentationHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.neo4j.graphql

import graphql.schema.DataFetcher
import graphql.schema.GraphQLFieldDefinition
import graphql.schema.GraphQLFieldsContainer
import graphql.schema.GraphQLObjectType

abstract class AugmentationHandler(val schemaConfig: SchemaConfig) {
companion object {
const val QUERY = "Query"
const val MUTATION = "Mutation"
}

open fun augmentType(type: GraphQLFieldsContainer, buildingEnv: BuildingEnv) {}

abstract fun createDataFetcher(rootType: GraphQLObjectType, fieldDefinition: GraphQLFieldDefinition): DataFetcher<Cypher>?

}
197 changes: 197 additions & 0 deletions src/main/kotlin/org/neo4j/graphql/BuildingEnv.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package org.neo4j.graphql

import graphql.schema.*

class BuildingEnv(val types: MutableMap<String, GraphQLType>) {

private val typesForRelation = types.values
.filterIsInstance<GraphQLObjectType>()
.filter { it.getDirective(DirectiveConstants.RELATION) != null }
.map { it.getDirectiveArgument<String>(DirectiveConstants.RELATION, DirectiveConstants.RELATION_NAME, null)!! to it.name }
.toMap()

fun buildFieldDefinition(
prefix: String,
resultType: GraphQLOutputType,
scalarFields: List<GraphQLFieldDefinition>,
nullableResult: Boolean,
forceOptionalProvider: (field: GraphQLFieldDefinition) -> Boolean = { false }
): GraphQLFieldDefinition.Builder {
var type: GraphQLOutputType = resultType
if (!nullableResult) {
type = GraphQLNonNull(type)
}
return GraphQLFieldDefinition.newFieldDefinition()
.name("$prefix${resultType.name}")
.arguments(getInputValueDefinitions(scalarFields, forceOptionalProvider))
.type(type.ref() as GraphQLOutputType)
}

fun getInputValueDefinitions(
relevantFields: List<GraphQLFieldDefinition>,
forceOptionalProvider: (field: GraphQLFieldDefinition) -> Boolean): List<GraphQLArgument> {
return relevantFields.map { field ->
var type = field.type as GraphQLType
type = getInputType(type)
type = if (forceOptionalProvider(field)) {
(type as? GraphQLNonNull)?.wrappedType ?: type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't there be an extension function for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since it is operation on a list, I don't see the right cutting point here to extract the function as extension function.

} else {
type
}
input(field.name, type)
}
}

/**
* add the given operation to the corresponding rootType
*/
fun addOperation(rootTypeName: String, fieldDefinition: GraphQLFieldDefinition) {
val rootType = types[rootTypeName]
types[rootTypeName] = if (rootType == null) {
val builder = GraphQLObjectType.newObject()
builder.name(rootTypeName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it also be added to types ?
if not multiple calls to this method would create the root type several times

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the assignment is in line 50

.field(fieldDefinition)
.build()
} else {
val existingRootType = (rootType as? GraphQLObjectType
?: throw IllegalStateException("root type $rootTypeName is not an object type"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should report what it actually is

if (existingRootType.getFieldDefinition(fieldDefinition.name) != null) {
return // definition already exists
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but is it the same?

}
existingRootType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this create a new type or do an in place update?
in the former case we need to replace it in types map

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the assignment is in line 50

.transform { builder -> builder.field(fieldDefinition) }
}
}

fun addFilterType(type: GraphQLFieldsContainer, handled: MutableSet<String> = mutableSetOf()): String {
val filterName = "_${type.name}Filter"
if (handled.contains(filterName)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handled? better name

return filterName
}
val existingFilterType = types[filterName]
if (existingFilterType != null) {
return (existingFilterType as? GraphQLInputType)?.name
?: throw IllegalStateException("Filter type $filterName is already defined but not an input type")
}
handled.add(filterName)
val builder = GraphQLInputObjectType.newInputObject()
.name(filterName)
listOf("AND", "OR", "NOT").forEach {
builder.field(GraphQLInputObjectField.newInputObjectField()
.name(it)
.type(GraphQLList(GraphQLNonNull(GraphQLTypeReference(filterName)))))
}
type.fieldDefinitions
.filter { it.dynamicPrefix() == null } // TODO currently we do not support filtering on dynamic properties
.forEach { field ->
val typeDefinition = field.type.inner()
val filterType = when {
typeDefinition.isNeo4jType() -> getInputType(typeDefinition).name
typeDefinition.isScalar() -> typeDefinition.innerName()
typeDefinition is GraphQLEnumType -> typeDefinition.innerName()
else -> addFilterType(getInnerFieldsContainer(typeDefinition), handled)
}

Operators.forType(types[filterType] ?: typeDefinition).forEach { op ->
val wrappedType: GraphQLInputType = when {
op.list -> GraphQLList(GraphQLTypeReference(filterType))
else -> GraphQLTypeReference(filterType)
}
builder.field(GraphQLInputObjectField.newInputObjectField()
.name(op.fieldName(field.name))
.type(wrappedType))
}
}
types[filterName] = builder.build()
return filterName
}

fun addOrdering(type: GraphQLFieldsContainer): String? {
val orderingName = "_${type.name}Ordering"
var existingOrderingType = types[orderingName]
if (existingOrderingType != null) {
return (existingOrderingType as? GraphQLInputType)?.name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

input type vs. enum?!

Copy link
Collaborator Author

@Andy2003 Andy2003 Oct 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GraphQLEnumType is instance of GraphQLInputType

?: throw IllegalStateException("Ordering type $type.name is already defined but not an input type")
}
val sortingFields = type.fieldDefinitions
.filter { it.type.isScalar() || it.isNeo4jType() }
.sortedByDescending { it.isID() }
if (sortingFields.isEmpty()) {
return null
}
existingOrderingType = GraphQLEnumType.newEnum()
.name(orderingName)
.values(sortingFields.flatMap { fd ->
listOf("_asc", "_desc")
.map {
GraphQLEnumValueDefinition
.newEnumValueDefinition()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add descriptions to these?

.name(fd.name + it)
.value(fd.name + it)
.build()
}
})
.build()
types[orderingName] = existingOrderingType
return orderingName
}

fun addInputType(inputName: String, relevantFields: List<GraphQLFieldDefinition>): GraphQLInputType {
var inputType = types[inputName]
if (inputType != null) {
return inputType as? GraphQLInputType
?: throw IllegalStateException("Filter type $inputName is already defined but not an input type")
}
inputType = getInputType(inputName, relevantFields)
types[inputName] = inputType
return inputType
}

fun getTypeForRelation(nameOfRelation: String): GraphQLObjectType? {
return typesForRelation[nameOfRelation]?.let { types[it] } as? GraphQLObjectType
}

private fun getInputType(inputName: String, relevantFields: List<GraphQLFieldDefinition>): GraphQLInputObjectType {
return GraphQLInputObjectType.newInputObject()
.name(inputName)
.fields(getInputValueDefinitions(relevantFields))
.build()
}

private fun getInputValueDefinitions(relevantFields: List<GraphQLFieldDefinition>): List<GraphQLInputObjectField> {
return relevantFields.map {
val type = (it.type as? GraphQLNonNull)?.wrappedType ?: it.type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about lists?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just make evrything optional

GraphQLInputObjectField
.newInputObjectField()
.name(it.name)
.type(getInputType(type).ref() as GraphQLInputType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need refs for non-object-types?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refs will be only created if required

.build()
}
}

private fun getInnerFieldsContainer(type: GraphQLType): GraphQLFieldsContainer {
var innerType = type.inner()
if (innerType is GraphQLTypeReference) {
innerType = types[innerType.name]
?: throw IllegalArgumentException("${innerType.name} is unknown")
}
return innerType as? GraphQLFieldsContainer
?: throw IllegalArgumentException("${innerType.name} is neither an object nor an interface")
}

private fun getInputType(type: GraphQLType): GraphQLInputType {
val inner = type.inner()
if (inner is GraphQLInputType) {
return type as GraphQLInputType
}
if (inner.isNeo4jType()) {
return neo4jTypeDefinitions
.find { it.typeDefinition == inner.name }
?.let { types[it.inputDefinition] } as? GraphQLInputType
?: throw IllegalArgumentException("Cannot find input type for ${inner.name}")
}
return type as? GraphQLInputType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't have been returned then already in the first if?

?: throw IllegalArgumentException("${type.name} is not allowed for input")
}

}
5 changes: 3 additions & 2 deletions src/main/kotlin/org/neo4j/graphql/Cypher.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package org.neo4j.graphql

import graphql.language.Type
import graphql.schema.GraphQLType

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

companion object {
@JvmStatic
val EMPTY = Cypher("")
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.neo4j.graphql

import graphql.schema.GraphQLArgument
import graphql.schema.GraphQLInputType
import graphql.schema.GraphQLType
import java.io.PrintWriter
import java.io.StringWriter

Expand All @@ -12,3 +15,11 @@ fun Throwable.stackTraceAsString(): String {
fun <T> Iterable<T>.joinNonEmpty(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
return if (iterator().hasNext()) joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() else ""
}

fun input(name: String, type: GraphQLType): GraphQLArgument {
return GraphQLArgument
.newArgument()
.name(name)
.type((type.ref() as? GraphQLInputType)
?: throw IllegalArgumentException("${type.innerName()} is not allowed for input")).build()
}
85 changes: 0 additions & 85 deletions src/main/kotlin/org/neo4j/graphql/Facades.kt

This file was deleted.

Loading