|
| 1 | +package graphql.normalized.nf; |
| 2 | + |
| 3 | +import com.google.common.collect.ImmutableList; |
| 4 | +import graphql.Assert; |
| 5 | +import graphql.PublicApi; |
| 6 | +import graphql.introspection.Introspection; |
| 7 | +import graphql.language.Argument; |
| 8 | +import graphql.language.Document; |
| 9 | +import graphql.language.Field; |
| 10 | +import graphql.language.InlineFragment; |
| 11 | +import graphql.language.OperationDefinition; |
| 12 | +import graphql.language.Selection; |
| 13 | +import graphql.language.SelectionSet; |
| 14 | +import graphql.language.TypeName; |
| 15 | +import graphql.schema.GraphQLCompositeType; |
| 16 | +import graphql.schema.GraphQLFieldDefinition; |
| 17 | +import graphql.schema.GraphQLObjectType; |
| 18 | +import graphql.schema.GraphQLSchema; |
| 19 | +import graphql.schema.GraphQLUnmodifiedType; |
| 20 | +import org.jetbrains.annotations.NotNull; |
| 21 | +import org.jetbrains.annotations.Nullable; |
| 22 | + |
| 23 | +import java.util.ArrayList; |
| 24 | +import java.util.LinkedHashMap; |
| 25 | +import java.util.List; |
| 26 | +import java.util.Map; |
| 27 | + |
| 28 | +import static graphql.collect.ImmutableKit.emptyList; |
| 29 | +import static graphql.language.Field.newField; |
| 30 | +import static graphql.language.InlineFragment.newInlineFragment; |
| 31 | +import static graphql.language.SelectionSet.newSelectionSet; |
| 32 | +import static graphql.language.TypeName.newTypeName; |
| 33 | +import static graphql.schema.GraphQLTypeUtil.unwrapAll; |
| 34 | + |
| 35 | +/** |
| 36 | + * This class can take a list of {@link NormalizedField}s and compiling out a |
| 37 | + * normalised operation {@link Document} that would represent how those fields |
| 38 | + * may be executed. |
| 39 | + * <p> |
| 40 | + * This is essentially the reverse of {@link NormalizedDocumentFactory} which takes |
| 41 | + * operation text and makes {@link NormalizedField}s from it, this takes {@link NormalizedField}s |
| 42 | + * and makes operation text from it. |
| 43 | + * <p> |
| 44 | + * You could for example send that operation text onto to some other graphql server if it |
| 45 | + * has the same schema as the one provided. |
| 46 | + */ |
| 47 | +@PublicApi |
| 48 | +public class NormalizedOperationToAstCompiler { |
| 49 | + |
| 50 | + /** |
| 51 | + * The result is a {@link Document} and a map of variables |
| 52 | + * that would go with that document. |
| 53 | + */ |
| 54 | + public static class CompilerResult { |
| 55 | + private final Document document; |
| 56 | + private final Map<String, Object> variables; |
| 57 | + |
| 58 | + public CompilerResult(Document document, Map<String, Object> variables) { |
| 59 | + this.document = document; |
| 60 | + this.variables = variables; |
| 61 | + } |
| 62 | + |
| 63 | + public Document getDocument() { |
| 64 | + return document; |
| 65 | + } |
| 66 | + |
| 67 | + public Map<String, Object> getVariables() { |
| 68 | + return variables; |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + public static CompilerResult compileToDocument(@NotNull GraphQLSchema schema, |
| 73 | + NormalizedOperation normalizedOperation) { |
| 74 | + GraphQLObjectType operationType = getOperationType(schema, normalizedOperation.getOperation()); |
| 75 | + |
| 76 | + List<Selection<?>> selections = subSelectionsForNormalizedField(schema, operationType.getName(), normalizedOperation.getTopLevelFields()); |
| 77 | + SelectionSet selectionSet = new SelectionSet(selections); |
| 78 | + |
| 79 | + OperationDefinition.Builder definitionBuilder = OperationDefinition.newOperationDefinition() |
| 80 | + .name(normalizedOperation.getOperationName()) |
| 81 | + .operation(normalizedOperation.getOperation()) |
| 82 | + .selectionSet(selectionSet); |
| 83 | + |
| 84 | +// definitionBuilder.variableDefinitions(variableAccumulator.getVariableDefinitions()); |
| 85 | + |
| 86 | + return new CompilerResult( |
| 87 | + Document.newDocument() |
| 88 | + .definition(definitionBuilder.build()) |
| 89 | + .build(), |
| 90 | + null |
| 91 | + ); |
| 92 | + } |
| 93 | + |
| 94 | + private static List<Selection<?>> subSelectionsForNormalizedField(GraphQLSchema schema, |
| 95 | + @NotNull String parentOutputType, |
| 96 | + List<NormalizedField> normalizedFields |
| 97 | + ) { |
| 98 | + ImmutableList.Builder<Selection<?>> selections = ImmutableList.builder(); |
| 99 | + |
| 100 | + // All conditional fields go here instead of directly to selections, so they can be grouped together |
| 101 | + // in the same inline fragment in the output |
| 102 | + Map<String, List<Field>> fieldsByTypeCondition = new LinkedHashMap<>(); |
| 103 | + |
| 104 | + for (NormalizedField nf : normalizedFields) { |
| 105 | + if (nf.isConditional(schema)) { |
| 106 | + selectionForNormalizedField(schema, nf) |
| 107 | + .forEach((objectTypeName, field) -> |
| 108 | + fieldsByTypeCondition |
| 109 | + .computeIfAbsent(objectTypeName, ignored -> new ArrayList<>()) |
| 110 | + .add(field)); |
| 111 | + } else { |
| 112 | + selections.add(selectionForNormalizedField(schema, parentOutputType, nf)); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + fieldsByTypeCondition.forEach((objectTypeName, fields) -> { |
| 117 | + TypeName typeName = newTypeName(objectTypeName).build(); |
| 118 | + InlineFragment inlineFragment = newInlineFragment() |
| 119 | + .typeCondition(typeName) |
| 120 | + .selectionSet(selectionSet(fields)) |
| 121 | + .build(); |
| 122 | + selections.add(inlineFragment); |
| 123 | + }); |
| 124 | + |
| 125 | + return selections.build(); |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * @return Map of object type names to list of fields |
| 130 | + */ |
| 131 | + private static Map<String, Field> selectionForNormalizedField(GraphQLSchema schema, |
| 132 | + NormalizedField normalizedField |
| 133 | + ) { |
| 134 | + Map<String, Field> groupedFields = new LinkedHashMap<>(); |
| 135 | + |
| 136 | + for (String objectTypeName : normalizedField.getObjectTypeNames()) { |
| 137 | + groupedFields.put(objectTypeName, selectionForNormalizedField(schema, objectTypeName, normalizedField)); |
| 138 | + } |
| 139 | + |
| 140 | + return groupedFields; |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * @return Map of object type names to list of fields |
| 145 | + */ |
| 146 | + private static Field selectionForNormalizedField(GraphQLSchema schema, |
| 147 | + String objectTypeName, |
| 148 | + NormalizedField normalizedField) { |
| 149 | + |
| 150 | + final List<Selection<?>> subSelections; |
| 151 | + if (normalizedField.getChildren().isEmpty()) { |
| 152 | + subSelections = emptyList(); |
| 153 | + } else { |
| 154 | + GraphQLFieldDefinition fieldDef = getFieldDefinition(schema, objectTypeName, normalizedField); |
| 155 | + GraphQLUnmodifiedType fieldOutputType = unwrapAll(fieldDef.getType()); |
| 156 | + |
| 157 | + subSelections = subSelectionsForNormalizedField( |
| 158 | + schema, |
| 159 | + fieldOutputType.getName(), |
| 160 | + normalizedField.getChildren() |
| 161 | + ); |
| 162 | + } |
| 163 | + |
| 164 | + SelectionSet selectionSet = selectionSetOrNullIfEmpty(subSelections); |
| 165 | +// List<Argument> arguments = createArguments(executableNormalizedField, variableAccumulator); |
| 166 | + List<Argument> arguments = normalizedField.getAstArguments(); |
| 167 | + |
| 168 | + |
| 169 | + Field.Builder builder = newField() |
| 170 | + .name(normalizedField.getFieldName()) |
| 171 | + .alias(normalizedField.getAlias()) |
| 172 | + .selectionSet(selectionSet) |
| 173 | + .arguments(arguments); |
| 174 | + return builder.build(); |
| 175 | + } |
| 176 | + |
| 177 | + @Nullable |
| 178 | + private static SelectionSet selectionSetOrNullIfEmpty(List<Selection<?>> selections) { |
| 179 | + return selections.isEmpty() ? null : newSelectionSet().selections(selections).build(); |
| 180 | + } |
| 181 | + |
| 182 | + private static SelectionSet selectionSet(List<Field> fields) { |
| 183 | + return newSelectionSet().selections(fields).build(); |
| 184 | + } |
| 185 | + |
| 186 | + |
| 187 | + @NotNull |
| 188 | + private static GraphQLFieldDefinition getFieldDefinition(GraphQLSchema schema, |
| 189 | + String parentType, |
| 190 | + NormalizedField nf) { |
| 191 | + return Introspection.getFieldDef(schema, (GraphQLCompositeType) schema.getType(parentType), nf.getName()); |
| 192 | + } |
| 193 | + |
| 194 | + |
| 195 | + @Nullable |
| 196 | + private static GraphQLObjectType getOperationType(@NotNull GraphQLSchema schema, |
| 197 | + @NotNull OperationDefinition.Operation operationKind) { |
| 198 | + switch (operationKind) { |
| 199 | + case QUERY: |
| 200 | + return schema.getQueryType(); |
| 201 | + case MUTATION: |
| 202 | + return schema.getMutationType(); |
| 203 | + case SUBSCRIPTION: |
| 204 | + return schema.getSubscriptionType(); |
| 205 | + } |
| 206 | + |
| 207 | + return Assert.assertShouldNeverHappen("Unknown operation kind " + operationKind); |
| 208 | + } |
| 209 | + |
| 210 | +} |
0 commit comments