|
16 | 16 |
|
17 | 17 | package com.expediagroup.graphql.federation
|
18 | 18 |
|
19 |
| -import com.expediagroup.graphql.federation.directives.FieldSet |
20 | 19 | import com.expediagroup.graphql.federation.exception.InvalidFederatedSchema
|
| 20 | +import com.expediagroup.graphql.federation.extensions.isFederatedType |
| 21 | +import com.expediagroup.graphql.federation.validation.validateDirective |
| 22 | +import com.expediagroup.graphql.federation.validation.validateProvidesDirective |
| 23 | +import com.expediagroup.graphql.federation.validation.validateRequiresDirective |
21 | 24 | import graphql.schema.GraphQLDirective
|
22 |
| -import graphql.schema.GraphQLDirectiveContainer |
23 | 25 | import graphql.schema.GraphQLFieldDefinition
|
24 | 26 | import graphql.schema.GraphQLInterfaceType
|
25 |
| -import graphql.schema.GraphQLList |
26 | 27 | import graphql.schema.GraphQLObjectType
|
27 | 28 | import graphql.schema.GraphQLType
|
28 | 29 | import graphql.schema.GraphQLTypeUtil
|
29 |
| -import graphql.schema.GraphQLUnionType |
30 | 30 |
|
31 | 31 | /**
|
32 | 32 | * Validates generated federated objects.
|
@@ -87,122 +87,4 @@ class FederatedSchemaValidator {
|
87 | 87 | throw InvalidFederatedSchema(errors)
|
88 | 88 | }
|
89 | 89 | }
|
90 |
| - |
91 |
| - // [OK] @requires references valid fields marked @external |
92 |
| - // [ERROR] @requires specified on base type |
93 |
| - // [ERROR] @requires specifies non-existent fields |
94 |
| - private fun validateRequiresDirective(validatedType: String, validatedField: GraphQLFieldDefinition, fieldMap: Map<String, GraphQLFieldDefinition>, extendedType: Boolean): List<String> { |
95 |
| - val errors = mutableListOf<String>() |
96 |
| - if (extendedType) { |
97 |
| - errors.addAll(validateDirective("$validatedType.${validatedField.name}", "requires", validatedField.directivesByName, fieldMap, extendedType)) |
98 |
| - } else { |
99 |
| - errors.add("base $validatedType type has fields marked with @requires directive, validatedField=${validatedField.name}") |
100 |
| - } |
101 |
| - return errors |
102 |
| - } |
103 |
| - |
104 |
| - // [OK] @provides on base type references valid @external fields on @extend object |
105 |
| - // [ERROR] @provides on base type references local object fields |
106 |
| - // [ERROR] @provides on base type references local fields on @extends object |
107 |
| - // [ERROR] @provides references interface type |
108 |
| - // [OK] @provides references list of valid @extend objects |
109 |
| - // [ERROR] @provides references @external list field |
110 |
| - // [ERROR] @provides references @external interface field |
111 |
| - private fun validateProvidesDirective(federatedType: String, field: GraphQLFieldDefinition): List<String> { |
112 |
| - val errors = mutableListOf<String>() |
113 |
| - val returnType = GraphQLTypeUtil.unwrapAll(field.type) |
114 |
| - if (returnType is GraphQLObjectType) { |
115 |
| - if (!returnType.isExtendedType()) { |
116 |
| - errors.add("@provides directive is specified on a $federatedType.${field.name} field references local object") |
117 |
| - } else { |
118 |
| - val returnTypeFields = returnType.fieldDefinitions.associateBy { it.name } |
119 |
| - // @provides is applicable on both base and federated types and always references @external fields |
120 |
| - errors.addAll( |
121 |
| - validateDirective( |
122 |
| - "$federatedType.${field.name}", |
123 |
| - "provides", |
124 |
| - field.directivesByName, |
125 |
| - returnTypeFields, |
126 |
| - true)) |
127 |
| - } |
128 |
| - } else { |
129 |
| - errors.add("@provides directive is specified on a $federatedType.${field.name} field but it does not return an object type") |
130 |
| - } |
131 |
| - return errors |
132 |
| - } |
133 |
| - |
134 |
| - private fun validateDirective( |
135 |
| - validatedType: String, |
136 |
| - targetDirective: String, |
137 |
| - directives: Map<String, GraphQLDirective>, |
138 |
| - fieldMap: Map<String, GraphQLFieldDefinition>, |
139 |
| - extendedType: Boolean |
140 |
| - ): List<String> { |
141 |
| - val validationErrors = mutableListOf<String>() |
142 |
| - val directive = directives[targetDirective] |
143 |
| - |
144 |
| - if (directive == null) { |
145 |
| - validationErrors.add("@$targetDirective directive is missing on federated $validatedType type") |
146 |
| - } else { |
147 |
| - val fieldSetValue = (directive.getArgument("fields")?.value as? FieldSet)?.value |
148 |
| - val fieldSet = fieldSetValue?.split(" ")?.filter { it.isNotEmpty() }.orEmpty() |
149 |
| - if (fieldSet.isEmpty()) { |
150 |
| - validationErrors.add("@$targetDirective directive on $validatedType is missing field information") |
151 |
| - } else { |
152 |
| - // validate key field set |
153 |
| - val validatedDirectiveInfo = "@$targetDirective(fields = $fieldSetValue) directive on $validatedType" |
154 |
| - validateFieldSelection(validatedDirectiveInfo, fieldSet.iterator(), fieldMap, extendedType, validationErrors) |
155 |
| - } |
156 |
| - } |
157 |
| - return validationErrors |
158 |
| - } |
159 |
| - |
160 |
| - private fun validateFieldSelection( |
161 |
| - validatedDirective: String, |
162 |
| - iterator: Iterator<String>, |
163 |
| - fields: Map<String, GraphQLFieldDefinition>, |
164 |
| - extendedType: Boolean, |
165 |
| - errors: MutableList<String> |
166 |
| - ) { |
167 |
| - var previousField: String? = null |
168 |
| - while (iterator.hasNext()) { |
169 |
| - val currentField = iterator.next() |
170 |
| - when (currentField) { |
171 |
| - "{" -> { |
172 |
| - val targetField = fields[previousField]?.type |
173 |
| - when (val unwrappedType = GraphQLTypeUtil.unwrapAll(targetField)) { |
174 |
| - is GraphQLInterfaceType -> validateFieldSelection(validatedDirective, iterator, unwrappedType.fieldDefinitions.associateBy { it.name }, extendedType, errors) |
175 |
| - is GraphQLObjectType -> validateFieldSelection(validatedDirective, iterator, unwrappedType.fieldDefinitions.associateBy { it.name }, extendedType, errors) |
176 |
| - else -> errors.add("$validatedDirective specifies invalid field set - field set defines nested selection set on unsupported type") |
177 |
| - } |
178 |
| - } |
179 |
| - "}" -> return |
180 |
| - else -> validateKeySetField(fields[currentField], extendedType, errors, validatedDirective) |
181 |
| - } |
182 |
| - previousField = currentField |
183 |
| - } |
184 |
| - } |
185 |
| - |
186 |
| - private fun validateKeySetField(targetField: GraphQLFieldDefinition?, extendedType: Boolean, errors: MutableList<String>, validatedDirective: String) { |
187 |
| - if (null != targetField) { |
188 |
| - val externalField = targetField.getDirective("external") != null |
189 |
| - if (extendedType && !externalField) { |
190 |
| - errors.add("$validatedDirective specifies invalid field set - extended type incorrectly references local field=${targetField.name}") |
191 |
| - } else if (!extendedType && externalField) { |
192 |
| - errors.add("$validatedDirective specifies invalid field set - type incorrectly references external field=${targetField.name}") |
193 |
| - } |
194 |
| - |
195 |
| - when (GraphQLTypeUtil.unwrapNonNull(targetField.type)) { |
196 |
| - is GraphQLList -> errors.add("$validatedDirective specifies invalid field set - field set references GraphQLList, field=${targetField.name}") |
197 |
| - is GraphQLInterfaceType -> errors.add("$validatedDirective specifies invalid field set - field set references GraphQLInterfaceType, field=${targetField.name}") |
198 |
| - is GraphQLUnionType -> errors.add("$validatedDirective specifies invalid field set - field set references GraphQLUnionType, field=${targetField.name}") |
199 |
| - } |
200 |
| - } else { |
201 |
| - errors.add("$validatedDirective specifies invalid field set - field set specifies fields that do not exist") |
202 |
| - } |
203 |
| - } |
204 |
| - |
205 |
| - private fun GraphQLDirectiveContainer.isFederatedType() = this.getDirective("key") != null || isExtendedType() |
206 |
| - |
207 |
| - private fun GraphQLDirectiveContainer.isExtendedType() = this.getDirective("extends") != null |
208 | 90 | }
|
0 commit comments