GraphQLCodegen.java 12.5 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.MappingContext;
7
import com.kobylynskyi.graphql.codegen.model.definitions.*;
A
Alberto Valiña 已提交
8
import com.kobylynskyi.graphql.codegen.supplier.MappingConfigSupplier;
9 10
import graphql.language.FieldDefinition;
import graphql.language.ScalarTypeExtensionDefinition;
11 12 13 14
import lombok.Getter;
import lombok.Setter;

import java.io.File;
15
import java.util.*;
16

17 18
import static java.util.stream.Collectors.toList;

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

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

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

40
    public GraphQLCodegen(List<String> schemas, File outputDir, MappingConfig mappingConfig, MappingConfigSupplier externalMappingConfigSupplier) {
41 42 43
        this.schemas = schemas;
        this.outputDir = outputDir;
        this.mappingConfig = mappingConfig;
A
Alberto Valiña 已提交
44 45 46 47 48 49 50 51
        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);
        }
52 53 54
        if (mappingConfig.getGenerateBuilder() == null) {
            mappingConfig.setGenerateBuilder(DefaultMappingConfigValues.DEFAULT_BUILDER);
        }
A
Alberto Valiña 已提交
55 56 57
        if (mappingConfig.getGenerateEqualsAndHashCode() == null) {
            mappingConfig.setGenerateEqualsAndHashCode(DefaultMappingConfigValues.DEFAULT_EQUALS_AND_HASHCODE);
        }
58 59 60 61 62 63 64 65 66
        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 已提交
67 68 69 70 71 72
        if (mappingConfig.getGenerateToString() == null) {
            mappingConfig.setGenerateToString(DefaultMappingConfigValues.DEFAULT_TO_STRING);
        }
        if (mappingConfig.getGenerateApis() == null) {
            mappingConfig.setGenerateApis(DefaultMappingConfigValues.DEFAULT_GENERATE_APIS);
        }
73 74 75
        if (mappingConfig.getGenerateAsyncApi() == null) {
            mappingConfig.setGenerateAsyncApi(DefaultMappingConfigValues.DEFAULT_GENERATE_ASYNC_APIS);
        }
76 77 78
        if (mappingConfig.getGenerateParameterizedFieldsResolvers() == null) {
            mappingConfig.setGenerateParameterizedFieldsResolvers(DefaultMappingConfigValues.DEFAULT_GENERATE_PARAMETERIZED_FIELDS_RESOLVERS);
        }
79 80 81
        if (mappingConfig.getGenerateExtensionFieldsResolvers() == null) {
            mappingConfig.setGenerateExtensionFieldsResolvers(DefaultMappingConfigValues.DEFAULT_GENERATE_EXTENSION_FIELDS_RESOLVERS);
        }
82 83 84
        if (mappingConfig.getGenerateDataFetchingEnvironmentArgumentInApis() == null) {
            mappingConfig.setGenerateDataFetchingEnvironmentArgumentInApis(DefaultMappingConfigValues.DEFAULT_GENERATE_DATA_FETCHING_ENV);
        }
85 86 87 88
        if (mappingConfig.getGenerateRequests()) {
            // required for request serialization
            mappingConfig.setGenerateToString(true);
        }
89 90
    }

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

105
    private List<File> processDefinitions(ExtendedDocument document) {
106 107 108
        MappingContext context = new MappingContext(mappingConfig, document,
                document.getTypeNames(), document.getInterfaceNames());

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

141 142
    private File generateUnion(MappingContext mappingContext, ExtendedUnionTypeDefinition definition) {
        Map<String, Object> dataModel = UnionDefinitionToDataModelMapper.map(mappingContext, definition);
143
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.unionTemplate, dataModel, outputDir);
144 145
    }

146 147
    private File generateInterface(MappingContext mappingContext, ExtendedInterfaceTypeDefinition definition) {
        Map<String, Object> dataModel = InterfaceDefinitionToDataModelMapper.map(mappingContext, definition);
148
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.interfaceTemplate, dataModel, outputDir);
149 150
    }

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

        if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) {
            // generate request objects for graphql operations
166
            for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
167
                Map<String, Object> requestDataModel = FieldDefinitionToRequestDataModelMapper.map(mappingContext, operationDef, definition.getName(), fieldNames);
168
                generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.requestTemplate, requestDataModel, outputDir));
169 170
            }
        }
171
        return generatedFiles;
172 173
    }

174
    private List<File> generateType(MappingContext mappingContext, ExtendedObjectTypeDefinition definition) {
175
        List<File> generatedFiles = new ArrayList<>();
176
        Map<String, Object> dataModel = TypeDefinitionToDataModelMapper.map(mappingContext, definition);
177
        generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir));
178 179

        if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) {
180
            Map<String, Object> responseProjDataModel = TypeDefinitionToDataModelMapper.mapResponseProjection(mappingContext, definition);
181
            generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.responseProjectionTemplate, responseProjDataModel, outputDir));
182
        }
183
        return generatedFiles;
184 185
    }

186
    private Optional<File> generateFieldResolver(MappingContext mappingContext, List<ExtendedFieldDefinition> fieldDefinitions, String definitionName) {
187
        List<ExtendedFieldDefinition> fieldDefsWithResolvers = fieldDefinitions.stream()
188
                .filter(fieldDef -> FieldDefinitionToParameterMapper.generateResolversForField(mappingContext, fieldDef, definitionName))
189 190
                .collect(toList());
        if (!fieldDefsWithResolvers.isEmpty()) {
191
            Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapToTypeResolver(mappingContext, fieldDefsWithResolvers, definitionName);
192
            return Optional.of(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
193
        }
194
        return Optional.empty();
195 196
    }

197 198
    private File generateInput(MappingContext mappingContext, ExtendedInputObjectTypeDefinition definition) {
        Map<String, Object> dataModel = InputDefinitionToDataModelMapper.map(mappingContext, definition);
199
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir);
200 201
    }

202 203
    private File generateEnum(MappingContext mappingContext, ExtendedEnumTypeDefinition definition) {
        Map<String, Object> dataModel = EnumDefinitionToDataModelMapper.map(mappingContext, definition);
204
        return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.enumTemplate, dataModel, outputDir);
205 206
    }

207 208 209 210 211
    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");
212 213
            }
        }
214 215 216 217 218
        mappingConfig.putCustomTypeMappingIfAbsent("ID", "String");
        mappingConfig.putCustomTypeMappingIfAbsent("String", "String");
        mappingConfig.putCustomTypeMappingIfAbsent("Int", "Integer");
        mappingConfig.putCustomTypeMappingIfAbsent("Float", "Double");
        mappingConfig.putCustomTypeMappingIfAbsent("Boolean", "Boolean");
219
    }
220

221
}