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+ }
0 commit comments