-
Notifications
You must be signed in to change notification settings - Fork 49
Rely on GraphQL-Schema instead of Schema-Definitions #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dda2c81
9d3cb74
41e5b27
cbb3f93
c4427b0
a7d4574
4ebc7ab
5c17939
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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>? | ||
|
|
||
| } |
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't there be an extension function for this?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should it also be added to types ?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but is it the same? |
||
| } | ||
| existingRootType | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will this create a new type or do an in place update?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. input type vs. enum?!
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about lists?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need refs for non-object-types?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
| } | ||
|
|
||
| } | ||
| 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("") | ||
| } | ||
| } |
This file was deleted.
There was a problem hiding this comment.
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.