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

import com.kobylynskyi.graphql.codegen.mapper.*;
4 5
import com.kobylynskyi.graphql.codegen.model.DefaultMappingConfigValues;
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
6
import com.kobylynskyi.graphql.codegen.model.definitions.*;
A
Alberto Valiña 已提交
7
import com.kobylynskyi.graphql.codegen.supplier.MappingConfigSupplier;
8 9 10 11 12
import graphql.language.*;
import lombok.Getter;
import lombok.Setter;

import java.io.File;
13
import java.util.*;
14

15 16
import static java.util.stream.Collectors.toList;

17 18
/**
 * Generator of:
19 20 21
 * - 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
22 23
 *
 * @author kobylynskyi
A
Alberto Valiña 已提交
24
 * @author valinhadev
25 26 27
 */
@Getter
@Setter
28
public class GraphQLCodegen {
29 30 31 32 33

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

34
    public GraphQLCodegen(List<String> schemas, File outputDir, MappingConfig mappingConfig) {
A
Alberto Valiña 已提交
35 36 37
        this(schemas, outputDir, mappingConfig, null);
    }

38
    public GraphQLCodegen(List<String> schemas, File outputDir, MappingConfig mappingConfig, MappingConfigSupplier externalMappingConfigSupplier) {
39 40 41
        this.schemas = schemas;
        this.outputDir = outputDir;
        this.mappingConfig = mappingConfig;
A
Alberto Valiña 已提交
42 43 44 45 46 47 48 49
        this.mappingConfig.combine(externalMappingConfigSupplier != null ? externalMappingConfigSupplier.get() : null);
        initDefaultValues(mappingConfig);
    }

    private void initDefaultValues(MappingConfig mappingConfig) {
        if (mappingConfig.getModelValidationAnnotation() == null) {
            mappingConfig.setModelValidationAnnotation(DefaultMappingConfigValues.DEFAULT_VALIDATION_ANNOTATION);
        }
50 51 52
        if (mappingConfig.getGenerateBuilder() == null) {
            mappingConfig.setGenerateBuilder(DefaultMappingConfigValues.DEFAULT_BUILDER);
        }
A
Alberto Valiña 已提交
53 54 55
        if (mappingConfig.getGenerateEqualsAndHashCode() == null) {
            mappingConfig.setGenerateEqualsAndHashCode(DefaultMappingConfigValues.DEFAULT_EQUALS_AND_HASHCODE);
        }
56 57 58 59 60 61 62 63 64
        if (mappingConfig.getGenerateRequests() == null) {
            mappingConfig.setGenerateRequests(DefaultMappingConfigValues.DEFAULT_GENERATE_REQUESTS);
        }
        if (mappingConfig.getRequestSuffix() == null) {
            mappingConfig.setRequestSuffix(DefaultMappingConfigValues.DEFAULT_REQUEST_SUFFIX);
        }
        if (mappingConfig.getResponseProjectionSuffix() == null) {
            mappingConfig.setResponseProjectionSuffix(DefaultMappingConfigValues.DEFAULT_RESPONSE_PROJECTION_SUFFIX);
        }
A
Alberto Valiña 已提交
65 66 67 68 69 70
        if (mappingConfig.getGenerateToString() == null) {
            mappingConfig.setGenerateToString(DefaultMappingConfigValues.DEFAULT_TO_STRING);
        }
        if (mappingConfig.getGenerateApis() == null) {
            mappingConfig.setGenerateApis(DefaultMappingConfigValues.DEFAULT_GENERATE_APIS);
        }
71 72 73
        if (mappingConfig.getGenerateAsyncApi() == null) {
            mappingConfig.setGenerateAsyncApi(DefaultMappingConfigValues.DEFAULT_GENERATE_ASYNC_APIS);
        }
74 75 76
        if (mappingConfig.getGenerateParameterizedFieldsResolvers() == null) {
            mappingConfig.setGenerateParameterizedFieldsResolvers(DefaultMappingConfigValues.DEFAULT_GENERATE_PARAMETERIZED_FIELDS_RESOLVERS);
        }
77 78 79
        if (mappingConfig.getGenerateExtensionFieldsResolvers() == null) {
            mappingConfig.setGenerateExtensionFieldsResolvers(DefaultMappingConfigValues.DEFAULT_GENERATE_EXTENSION_FIELDS_RESOLVERS);
        }
80 81 82
        if (mappingConfig.getGenerateDataFetchingEnvironmentArgumentInApis() == null) {
            mappingConfig.setGenerateDataFetchingEnvironmentArgumentInApis(DefaultMappingConfigValues.DEFAULT_GENERATE_DATA_FETCHING_ENV);
        }
83 84 85 86
        if (mappingConfig.getGenerateRequests()) {
            // required for request serialization
            mappingConfig.setGenerateToString(true);
        }
87 88
    }

89
    public List<File> generate() throws Exception {
90
        GraphQLCodegenFileCreator.prepareOutputDir(outputDir);
91
        long startTime = System.currentTimeMillis();
92
        List<File> generatedFiles = Collections.emptyList();
93
        if (!schemas.isEmpty()) {
94 95
            ExtendedDocument document = GraphQLDocumentParser.getDocument(schemas);
            initCustomTypeMappings(document.getScalarDefinitions());
96
            generatedFiles = processDefinitions(document);
97
        }
98
        long elapsed = System.currentTimeMillis() - startTime;
99
        System.out.println(String.format("Finished processing %d schema(s) in %d ms", schemas.size(), elapsed));
100
        return generatedFiles;
101 102
    }

103 104
    private List<File> processDefinitions(ExtendedDocument document) {
        List<File> generatedFiles = new ArrayList<>();
105
        Set<String> typeNames = document.getTypeNames();
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
        for (ExtendedObjectTypeDefinition extendedObjectTypeDefinition : document.getTypeDefinitions()) {
            generatedFiles.addAll(generateType(extendedObjectTypeDefinition, document, typeNames));
        }
        for (ExtendedObjectTypeDefinition extendedObjectTypeDefinition : document.getTypeDefinitions()) {
            generateFieldResolver(extendedObjectTypeDefinition.getFieldDefinitions(), extendedObjectTypeDefinition.getName())
                    .ifPresent(generatedFiles::add);
        }
        for (ExtendedObjectTypeDefinition extendedObjectTypeDefinition : document.getOperationDefinitions()) {
            generatedFiles.addAll(generateOperation(extendedObjectTypeDefinition));
        }
        for (ExtendedInputObjectTypeDefinition extendedInputObjectTypeDefinition : document.getInputDefinitions()) {
            generatedFiles.add(generateInput(extendedInputObjectTypeDefinition));
        }
        for (ExtendedEnumTypeDefinition extendedEnumTypeDefinition : document.getEnumDefinitions()) {
            generatedFiles.add(generateEnum(extendedEnumTypeDefinition));
        }
        for (ExtendedUnionTypeDefinition extendedUnionTypeDefinition : document.getUnionDefinitions()) {
            generatedFiles.add(generateUnion(extendedUnionTypeDefinition));
        }
        for (ExtendedInterfaceTypeDefinition extendedInterfaceTypeDefinition : document.getInterfaceDefinitions()) {
            generatedFiles.add(generateInterface(extendedInterfaceTypeDefinition));
        }
        for (ExtendedInterfaceTypeDefinition definition : document.getInterfaceDefinitions()) {
            generateFieldResolver(definition.getFieldDefinitions(), definition.getName())
                    .ifPresent(generatedFiles::add);
        }
        System.out.println(String.format("Generated %d definition classes in folder %s",
                generatedFiles.size(), outputDir.getAbsolutePath()));
        return generatedFiles;
135 136
    }

137
    private File generateUnion(ExtendedUnionTypeDefinition definition) {
138
        Map<String, Object> dataModel = UnionDefinitionToDataModelMapper.map(mappingConfig, definition);
139
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.unionTemplate, dataModel, outputDir);
140 141
    }

142
    private File generateInterface(ExtendedInterfaceTypeDefinition definition) {
143
        Map<String, Object> dataModel = InterfaceDefinitionToDataModelMapper.map(mappingConfig, definition);
144
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.interfaceTemplate, dataModel, outputDir);
145 146
    }

147 148
    private List<File> generateOperation(ExtendedObjectTypeDefinition definition) {
        List<File> generatedFiles = new ArrayList<>();
A
Alberto Valiña 已提交
149
        if (Boolean.TRUE.equals(mappingConfig.getGenerateApis())) {
150
            for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
151
                Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeField(mappingConfig, operationDef, definition.getName());
152
                generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
153 154
            }
            // We need to generate a root object to workaround https://github.com/facebook/relay/issues/112
155
            Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeFields(mappingConfig, definition);
156
            generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
157
        }
158 159 160

        if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) {
            // generate request objects for graphql operations
161
            for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
162
                Map<String, Object> requestDataModel = FieldDefinitionToRequestDataModelMapper.map(mappingConfig, operationDef, definition.getName());
163
                generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.requestTemplate, requestDataModel, outputDir));
164 165
            }
        }
166
        return generatedFiles;
167 168
    }

169 170
    private List<File> generateType(ExtendedObjectTypeDefinition definition, ExtendedDocument document, Set<String> typeNames) {
        List<File> generatedFiles = new ArrayList<>();
171
        Map<String, Object> dataModel = TypeDefinitionToDataModelMapper.map(mappingConfig, definition, document);
172
        generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir));
173 174 175

        if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) {
            Map<String, Object> responseProjDataModel = TypeDefinitionToDataModelMapper.mapResponseProjection(mappingConfig, definition, document, typeNames);
176
            generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.responseProjectionTemplate, responseProjDataModel, outputDir));
177
        }
178
        return generatedFiles;
179 180
    }

181
    private Optional<File> generateFieldResolver(List<ExtendedFieldDefinition> fieldDefinitions, String definitionName) {
182
        List<ExtendedFieldDefinition> fieldDefsWithResolvers = fieldDefinitions.stream()
183
                .filter(fieldDef -> FieldDefinitionToParameterMapper.generateResolversForField(mappingConfig, fieldDef, definitionName))
184 185
                .collect(toList());
        if (!fieldDefsWithResolvers.isEmpty()) {
186
            Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapToTypeResolver(mappingConfig, fieldDefsWithResolvers, definitionName);
187
            return Optional.of(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
188
        }
189
        return Optional.empty();
190 191
    }

192
    private File generateInput(ExtendedInputObjectTypeDefinition definition) {
193
        Map<String, Object> dataModel = InputDefinitionToDataModelMapper.map(mappingConfig, definition);
194
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir);
195 196
    }

197
    private File generateEnum(ExtendedEnumTypeDefinition definition) {
198
        Map<String, Object> dataModel = EnumDefinitionToDataModelMapper.map(mappingConfig, definition);
199
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.enumTemplate, dataModel, outputDir);
200 201
    }

202 203 204 205 206
    private void initCustomTypeMappings(Collection<ExtendedScalarTypeDefinition> scalarTypeDefinitions) {
        for (ExtendedScalarTypeDefinition definition : scalarTypeDefinitions) {
            mappingConfig.putCustomTypeMappingIfAbsent(definition.getDefinition().getName(), "String");
            for (ScalarTypeExtensionDefinition extension : definition.getExtensions()) {
                mappingConfig.putCustomTypeMappingIfAbsent(extension.getName(), "String");
207 208
            }
        }
209 210 211 212 213
        mappingConfig.putCustomTypeMappingIfAbsent("ID", "String");
        mappingConfig.putCustomTypeMappingIfAbsent("String", "String");
        mappingConfig.putCustomTypeMappingIfAbsent("Int", "Integer");
        mappingConfig.putCustomTypeMappingIfAbsent("Float", "Double");
        mappingConfig.putCustomTypeMappingIfAbsent("Boolean", "Boolean");
214
    }
215

216
}