GraphQLCodegen.java 15.0 KB
Newer Older
1 2
package com.kobylynskyi.graphql.codegen;

3 4 5 6 7 8 9 10
import com.kobylynskyi.graphql.codegen.mapper.EnumDefinitionToDataModelMapper;
import com.kobylynskyi.graphql.codegen.mapper.FieldDefinitionToParameterMapper;
import com.kobylynskyi.graphql.codegen.mapper.FieldDefinitionsToResolverDataModelMapper;
import com.kobylynskyi.graphql.codegen.mapper.InputDefinitionToDataModelMapper;
import com.kobylynskyi.graphql.codegen.mapper.InterfaceDefinitionToDataModelMapper;
import com.kobylynskyi.graphql.codegen.mapper.RequestResponseDefinitionToDataModelMapper;
import com.kobylynskyi.graphql.codegen.mapper.TypeDefinitionToDataModelMapper;
import com.kobylynskyi.graphql.codegen.mapper.UnionDefinitionToDataModelMapper;
11
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
12
import com.kobylynskyi.graphql.codegen.model.MappingConfigConstants;
13
import com.kobylynskyi.graphql.codegen.model.MappingContext;
14 15 16 17 18 19 20 21
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedDocument;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedEnumTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedFieldDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedInputObjectTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedInterfaceTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedObjectTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedScalarTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedUnionTypeDefinition;
A
Alberto Valiña 已提交
22
import com.kobylynskyi.graphql.codegen.supplier.MappingConfigSupplier;
23
import com.kobylynskyi.graphql.codegen.utils.Utils;
24 25
import graphql.language.FieldDefinition;
import graphql.language.ScalarTypeExtensionDefinition;
26 27 28 29
import lombok.Getter;
import lombok.Setter;

import java.io.File;
30 31 32 33 34 35
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
36

37 38
import static java.util.stream.Collectors.toList;

39 40
/**
 * Generator of:
41 42 43
 * - Interface for each GraphQL query, mutation, subscription, union and field resolvers
 * - POJO Class for each GraphQL type and input
 * - Enum Class for each GraphQL enum
44 45
 *
 * @author kobylynskyi
A
Alberto Valiña 已提交
46
 * @author valinhadev
47 48 49
 */
@Getter
@Setter
50
public class GraphQLCodegen {
51 52 53 54 55

    private List<String> schemas;
    private File outputDir;
    private MappingConfig mappingConfig;

56
    public GraphQLCodegen(List<String> schemas, File outputDir, MappingConfig mappingConfig) {
A
Alberto Valiña 已提交
57 58 59
        this(schemas, outputDir, mappingConfig, null);
    }

60
    public GraphQLCodegen(List<String> schemas, File outputDir, MappingConfig mappingConfig, MappingConfigSupplier externalMappingConfigSupplier) {
61 62 63
        this.schemas = schemas;
        this.outputDir = outputDir;
        this.mappingConfig = mappingConfig;
A
Alberto Valiña 已提交
64 65 66 67 68 69
        this.mappingConfig.combine(externalMappingConfigSupplier != null ? externalMappingConfigSupplier.get() : null);
        initDefaultValues(mappingConfig);
    }

    private void initDefaultValues(MappingConfig mappingConfig) {
        if (mappingConfig.getModelValidationAnnotation() == null) {
70
            mappingConfig.setModelValidationAnnotation(MappingConfigConstants.DEFAULT_VALIDATION_ANNOTATION);
A
Alberto Valiña 已提交
71
        }
72
        if (mappingConfig.getGenerateBuilder() == null) {
73
            mappingConfig.setGenerateBuilder(MappingConfigConstants.DEFAULT_BUILDER);
74
        }
A
Alberto Valiña 已提交
75
        if (mappingConfig.getGenerateEqualsAndHashCode() == null) {
76
            mappingConfig.setGenerateEqualsAndHashCode(MappingConfigConstants.DEFAULT_EQUALS_AND_HASHCODE);
A
Alberto Valiña 已提交
77
        }
78 79
        if (mappingConfig.getGenerateClient() == null) {
            mappingConfig.setGenerateClient(MappingConfigConstants.DEFAULT_GENERATE_CLIENT);
80 81
        }
        if (mappingConfig.getRequestSuffix() == null) {
82
            mappingConfig.setRequestSuffix(MappingConfigConstants.DEFAULT_REQUEST_SUFFIX);
83
        }
84 85 86
        if (mappingConfig.getResponseSuffix() == null) {
            mappingConfig.setResponseSuffix(MappingConfigConstants.DEFAULT_RESPONSE_SUFFIX);
        }
87
        if (mappingConfig.getResponseProjectionSuffix() == null) {
88
            mappingConfig.setResponseProjectionSuffix(MappingConfigConstants.DEFAULT_RESPONSE_PROJECTION_SUFFIX);
89
        }
90 91 92
        if (mappingConfig.getParametrizedInputSuffix() == null) {
            mappingConfig.setParametrizedInputSuffix(MappingConfigConstants.DEFAULT_PARAMETRIZED_INPUT_SUFIX);
        }
A
Alberto Valiña 已提交
93
        if (mappingConfig.getGenerateToString() == null) {
94
            mappingConfig.setGenerateToString(MappingConfigConstants.DEFAULT_TO_STRING);
A
Alberto Valiña 已提交
95 96
        }
        if (mappingConfig.getGenerateApis() == null) {
97
            mappingConfig.setGenerateApis(MappingConfigConstants.DEFAULT_GENERATE_APIS);
A
Alberto Valiña 已提交
98
        }
99
        if (mappingConfig.getGenerateAsyncApi() == null) {
100
            mappingConfig.setGenerateAsyncApi(MappingConfigConstants.DEFAULT_GENERATE_ASYNC_APIS);
101
        }
102
        if (mappingConfig.getGenerateParameterizedFieldsResolvers() == null) {
103
            mappingConfig.setGenerateParameterizedFieldsResolvers(MappingConfigConstants.DEFAULT_GENERATE_PARAMETERIZED_FIELDS_RESOLVERS);
104
        }
105
        if (mappingConfig.getGenerateExtensionFieldsResolvers() == null) {
106
            mappingConfig.setGenerateExtensionFieldsResolvers(MappingConfigConstants.DEFAULT_GENERATE_EXTENSION_FIELDS_RESOLVERS);
107
        }
108
        if (mappingConfig.getGenerateDataFetchingEnvironmentArgumentInApis() == null) {
109
            mappingConfig.setGenerateDataFetchingEnvironmentArgumentInApis(MappingConfigConstants.DEFAULT_GENERATE_DATA_FETCHING_ENV);
110
        }
111
        if (mappingConfig.getGenerateClient()) {
112 113 114
            // required for request serialization
            mappingConfig.setGenerateToString(true);
        }
115 116
    }

117
    public List<File> generate() throws Exception {
118
        GraphQLCodegenFileCreator.prepareOutputDir(outputDir);
119
        long startTime = System.currentTimeMillis();
120
        List<File> generatedFiles = Collections.emptyList();
121
        if (!schemas.isEmpty()) {
122 123
            ExtendedDocument document = GraphQLDocumentParser.getDocument(schemas);
            initCustomTypeMappings(document.getScalarDefinitions());
124
            generatedFiles = processDefinitions(document);
125
        }
126
        long elapsed = System.currentTimeMillis() - startTime;
127
        System.out.println(String.format("Finished processing %d schema(s) in %d ms", schemas.size(), elapsed));
128
        return generatedFiles;
129 130
    }

131
    private List<File> processDefinitions(ExtendedDocument document) {
132 133 134
        MappingContext context = new MappingContext(mappingConfig, document,
                document.getTypeNames(), document.getInterfaceNames());

135 136
        List<File> generatedFiles = new ArrayList<>();
        for (ExtendedObjectTypeDefinition extendedObjectTypeDefinition : document.getTypeDefinitions()) {
137
            generatedFiles.addAll(generateType(context, extendedObjectTypeDefinition));
138 139
        }
        for (ExtendedObjectTypeDefinition extendedObjectTypeDefinition : document.getTypeDefinitions()) {
140
            generateFieldResolver(context, extendedObjectTypeDefinition.getFieldDefinitions(), extendedObjectTypeDefinition.getName())
141 142 143
                    .ifPresent(generatedFiles::add);
        }
        for (ExtendedObjectTypeDefinition extendedObjectTypeDefinition : document.getOperationDefinitions()) {
144
            generatedFiles.addAll(generateOperation(context, extendedObjectTypeDefinition));
145 146
        }
        for (ExtendedInputObjectTypeDefinition extendedInputObjectTypeDefinition : document.getInputDefinitions()) {
147
            generatedFiles.add(generateInput(context, extendedInputObjectTypeDefinition));
148 149
        }
        for (ExtendedEnumTypeDefinition extendedEnumTypeDefinition : document.getEnumDefinitions()) {
150
            generatedFiles.add(generateEnum(context, extendedEnumTypeDefinition));
151 152
        }
        for (ExtendedUnionTypeDefinition extendedUnionTypeDefinition : document.getUnionDefinitions()) {
153
            generatedFiles.add(generateUnion(context, extendedUnionTypeDefinition));
154 155
        }
        for (ExtendedInterfaceTypeDefinition extendedInterfaceTypeDefinition : document.getInterfaceDefinitions()) {
156
            generatedFiles.add(generateInterface(context, extendedInterfaceTypeDefinition));
157 158
        }
        for (ExtendedInterfaceTypeDefinition definition : document.getInterfaceDefinitions()) {
159
            generateFieldResolver(context, definition.getFieldDefinitions(), definition.getName())
160 161 162 163 164
                    .ifPresent(generatedFiles::add);
        }
        System.out.println(String.format("Generated %d definition classes in folder %s",
                generatedFiles.size(), outputDir.getAbsolutePath()));
        return generatedFiles;
165 166
    }

167 168
    private File generateUnion(MappingContext mappingContext, ExtendedUnionTypeDefinition definition) {
        Map<String, Object> dataModel = UnionDefinitionToDataModelMapper.map(mappingContext, definition);
169
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.unionTemplate, dataModel, outputDir);
170 171
    }

172 173
    private File generateInterface(MappingContext mappingContext, ExtendedInterfaceTypeDefinition definition) {
        Map<String, Object> dataModel = InterfaceDefinitionToDataModelMapper.map(mappingContext, definition);
174
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.interfaceTemplate, dataModel, outputDir);
175 176
    }

177
    private List<File> generateOperation(MappingContext mappingContext, ExtendedObjectTypeDefinition definition) {
178
        List<File> generatedFiles = new ArrayList<>();
179
        List<String> fieldNames = definition.getFieldDefinitions().stream().map(FieldDefinition::getName).collect(toList());
180
        if (mappingConfig.getGenerateApis()) {
181
            for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
182
                Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeField(mappingContext, operationDef, definition.getName(), fieldNames);
183
                generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
184 185
            }
            // We need to generate a root object to workaround https://github.com/facebook/relay/issues/112
186
            Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeFields(mappingContext, definition);
187
            generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
188
        }
189

190
        if (mappingConfig.getGenerateClient()) {
191
            // generate request objects for graphql operations
192
            for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
193
                Map<String, Object> requestDataModel = RequestResponseDefinitionToDataModelMapper.mapRequest(mappingContext, operationDef, definition.getName(), fieldNames);
194
                generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.requestTemplate, requestDataModel, outputDir));
195 196 197

                Map<String, Object> responseDataModel = RequestResponseDefinitionToDataModelMapper.mapResponse(mappingContext, operationDef, definition.getName(), fieldNames);
                generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.responseTemplate, responseDataModel, outputDir));
198 199
            }
        }
200
        return generatedFiles;
201 202
    }

203
    private List<File> generateType(MappingContext mappingContext, ExtendedObjectTypeDefinition definition) {
204
        List<File> generatedFiles = new ArrayList<>();
205
        Map<String, Object> dataModel = TypeDefinitionToDataModelMapper.map(mappingContext, definition);
206
        generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir));
207

208 209
        if (mappingConfig.getGenerateClient()) {
            Map<String, Object> responseProjDataModel = RequestResponseDefinitionToDataModelMapper.mapResponseProjection(mappingContext, definition);
210
            generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.responseProjectionTemplate, responseProjDataModel, outputDir));
211 212 213

            for (ExtendedFieldDefinition fieldDefinition : definition.getFieldDefinitions()) {
                if (!Utils.isEmpty(fieldDefinition.getInputValueDefinitions())) {
214
                    Map<String, Object> fieldProjDataModel = RequestResponseDefinitionToDataModelMapper.mapParametrizedInput(mappingContext, fieldDefinition, definition);
215 216 217
                    generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.parametrizedInputTemplate, fieldProjDataModel, outputDir));
                }
            }
218
        }
219
        return generatedFiles;
220 221
    }

222
    private Optional<File> generateFieldResolver(MappingContext mappingContext, List<ExtendedFieldDefinition> fieldDefinitions, String definitionName) {
223
        List<ExtendedFieldDefinition> fieldDefsWithResolvers = fieldDefinitions.stream()
224
                .filter(fieldDef -> FieldDefinitionToParameterMapper.generateResolversForField(mappingContext, fieldDef, definitionName))
225 226
                .collect(toList());
        if (!fieldDefsWithResolvers.isEmpty()) {
227
            Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapToTypeResolver(mappingContext, fieldDefsWithResolvers, definitionName);
228
            return Optional.of(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
229
        }
230
        return Optional.empty();
231 232
    }

233 234
    private File generateInput(MappingContext mappingContext, ExtendedInputObjectTypeDefinition definition) {
        Map<String, Object> dataModel = InputDefinitionToDataModelMapper.map(mappingContext, definition);
235
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir);
236 237
    }

238 239
    private File generateEnum(MappingContext mappingContext, ExtendedEnumTypeDefinition definition) {
        Map<String, Object> dataModel = EnumDefinitionToDataModelMapper.map(mappingContext, definition);
240
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.enumTemplate, dataModel, outputDir);
241 242
    }

243 244
    private void initCustomTypeMappings(Collection<ExtendedScalarTypeDefinition> scalarTypeDefinitions) {
        for (ExtendedScalarTypeDefinition definition : scalarTypeDefinitions) {
245 246 247
            if (definition.getDefinition() != null) {
                mappingConfig.putCustomTypeMappingIfAbsent(definition.getDefinition().getName(), "String");
            }
248 249
            for (ScalarTypeExtensionDefinition extension : definition.getExtensions()) {
                mappingConfig.putCustomTypeMappingIfAbsent(extension.getName(), "String");
250 251
            }
        }
252 253 254 255 256
        mappingConfig.putCustomTypeMappingIfAbsent("ID", "String");
        mappingConfig.putCustomTypeMappingIfAbsent("String", "String");
        mappingConfig.putCustomTypeMappingIfAbsent("Int", "Integer");
        mappingConfig.putCustomTypeMappingIfAbsent("Float", "Double");
        mappingConfig.putCustomTypeMappingIfAbsent("Boolean", "Boolean");
257
    }
258

259
}