Skip to content

Commit d8073a9

Browse files
authored
Introduce codegen for client code - Part 1. #37 (#53)
* Introduce codegen for client code - Part 1. #37 * Remove redundant null-checks * Minor fixes in Request class generation + improve code coverage. #37 * Customizable suffix of ResponseProjection classes. #37 * Code coverage for MappingConfig #37 * Fix method names #37 #53
1 parent c1ee76d commit d8073a9

File tree

55 files changed

+1763
-143
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1763
-143
lines changed

src/main/java/com/kobylynskyi/graphql/codegen/FreeMarkerTemplatesRegistry.java

+4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ class FreeMarkerTemplatesRegistry {
1212
static Template typeTemplate;
1313
static Template enumTemplate;
1414
static Template unionTemplate;
15+
static Template requestTemplate;
1516
static Template interfaceTemplate;
1617
static Template operationsTemplate;
1718
static Template fieldsResolverTemplate;
19+
static Template responseProjectionTemplate;
1820

1921
static {
2022
Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
@@ -28,9 +30,11 @@ class FreeMarkerTemplatesRegistry {
2830
typeTemplate = configuration.getTemplate("templates/javaClassGraphqlType.ftl");
2931
enumTemplate = configuration.getTemplate("templates/javaClassGraphqlEnum.ftl");
3032
unionTemplate = configuration.getTemplate("templates/javaClassGraphqlUnion.ftl");
33+
requestTemplate = configuration.getTemplate("templates/javaClassGraphqlRequest.ftl");
3134
interfaceTemplate = configuration.getTemplate("templates/javaClassGraphqlInterface.ftl");
3235
operationsTemplate = configuration.getTemplate("templates/javaClassGraphqlOperations.ftl");
3336
fieldsResolverTemplate = configuration.getTemplate("templates/javaClassGraphqlFieldsResolver.ftl");
37+
responseProjectionTemplate = configuration.getTemplate("templates/javaClassGraphqlResponseProjection.ftl");
3438
} catch (IOException e) {
3539
throw new UnableToLoadFreeMarkerTemplateException(e);
3640
}

src/main/java/com/kobylynskyi/graphql/codegen/GraphqlCodegen.java

+81-37
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.kobylynskyi.graphql.codegen;
22

33
import com.kobylynskyi.graphql.codegen.mapper.*;
4-
import com.kobylynskyi.graphql.codegen.model.*;
4+
import com.kobylynskyi.graphql.codegen.model.DefaultMappingConfigValues;
5+
import com.kobylynskyi.graphql.codegen.model.DefinitionTypeDeterminer;
6+
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
7+
import com.kobylynskyi.graphql.codegen.model.UnsupportedGraphqlDefinitionException;
58
import com.kobylynskyi.graphql.codegen.supplier.MappingConfigSupplier;
9+
import com.kobylynskyi.graphql.codegen.utils.Utils;
610
import freemarker.template.TemplateException;
711
import graphql.language.*;
812
import lombok.Getter;
@@ -12,17 +16,16 @@
1216
import java.io.IOException;
1317
import java.util.List;
1418
import java.util.Map;
19+
import java.util.Set;
20+
import java.util.stream.Collectors;
1521

1622
import static java.util.stream.Collectors.toList;
1723

1824
/**
1925
* Generator of:
20-
* - Interface for each GraphQL query
21-
* - Interface for each GraphQL mutation
22-
* - Interface for each GraphQL subscription
23-
* - Class for each GraphQL data type
24-
* - Class for each GraphQL enum type
25-
* - Class for each GraphQL scalar type
26+
* - Interface for each GraphQL query, mutation, subscription, union and field resolvers
27+
* - POJO Class for each GraphQL type and input
28+
* - Enum Class for each GraphQL enum
2629
*
2730
* @author kobylynskyi
2831
* @author valinhadev
@@ -55,6 +58,15 @@ private void initDefaultValues(MappingConfig mappingConfig) {
5558
if (mappingConfig.getGenerateEqualsAndHashCode() == null) {
5659
mappingConfig.setGenerateEqualsAndHashCode(DefaultMappingConfigValues.DEFAULT_EQUALS_AND_HASHCODE);
5760
}
61+
if (mappingConfig.getGenerateRequests() == null) {
62+
mappingConfig.setGenerateRequests(DefaultMappingConfigValues.DEFAULT_GENERATE_REQUESTS);
63+
}
64+
if (mappingConfig.getRequestSuffix() == null) {
65+
mappingConfig.setRequestSuffix(DefaultMappingConfigValues.DEFAULT_REQUEST_SUFFIX);
66+
}
67+
if (mappingConfig.getResponseProjectionSuffix() == null) {
68+
mappingConfig.setResponseProjectionSuffix(DefaultMappingConfigValues.DEFAULT_RESPONSE_PROJECTION_SUFFIX);
69+
}
5870
if (mappingConfig.getGenerateToString() == null) {
5971
mappingConfig.setGenerateToString(DefaultMappingConfigValues.DEFAULT_TO_STRING);
6072
}
@@ -64,6 +76,10 @@ private void initDefaultValues(MappingConfig mappingConfig) {
6476
if (mappingConfig.getGenerateParameterizedFieldsResolvers() == null) {
6577
mappingConfig.setGenerateParameterizedFieldsResolvers(DefaultMappingConfigValues.DEFAULT_GENERATE_PARAMETERIZED_FIELDS_RESOLVERS);
6678
}
79+
if (mappingConfig.getGenerateRequests()) {
80+
// required for request serialization
81+
mappingConfig.setGenerateToString(true);
82+
}
6783
}
6884

6985

@@ -72,46 +88,48 @@ public void generate() throws Exception {
7288
long startTime = System.currentTimeMillis();
7389
if (!schemas.isEmpty()) {
7490
Document document = GraphqlDocumentParser.getDocument(schemas);
75-
addScalarsToCustomMappingConfig(document);
91+
initCustomTypeMappings(document);
7692
processDocument(document);
7793
}
7894
long elapsed = System.currentTimeMillis() - startTime;
79-
System.out.println(String.format("Finished processing %d schemas in %d ms", schemas.size(), elapsed));
95+
System.out.println(String.format("Finished processing %d schema(s) in %d ms", schemas.size(), elapsed));
8096
}
8197

8298
private void processDocument(Document document) throws IOException, TemplateException {
99+
Set<String> typeNames = getAllTypeNames(document);
83100
for (Definition<?> definition : document.getDefinitions()) {
84-
GraphqlDefinitionType definitionType;
85101
try {
86-
definitionType = DefinitionTypeDeterminer.determine(definition);
87-
} catch (UnsupportedGraphqlDefinitionException ex) {
88-
continue;
89-
}
90-
switch (definitionType) {
91-
case OPERATION:
92-
generateOperation((ObjectTypeDefinition) definition);
93-
break;
94-
case TYPE:
95-
generateType((ObjectTypeDefinition) definition, document);
96-
generateFieldResolvers((ObjectTypeDefinition) definition);
97-
break;
98-
case INTERFACE:
99-
generateInterface((InterfaceTypeDefinition) definition);
100-
break;
101-
case ENUM:
102-
generateEnum((EnumTypeDefinition) definition);
103-
break;
104-
case INPUT:
105-
generateInput((InputObjectTypeDefinition) definition);
106-
break;
107-
case UNION:
108-
generateUnion((UnionTypeDefinition) definition);
102+
processDefinition(document, definition, typeNames);
103+
} catch (UnsupportedGraphqlDefinitionException ignored) {
109104
}
110105
}
111106
System.out.println(String.format("Generated %d definitions in folder '%s'", document.getDefinitions().size(),
112107
outputDir.getAbsolutePath()));
113108
}
114109

110+
private void processDefinition(Document document, Definition<?> definition, Set<String> typeNames) throws IOException, TemplateException {
111+
switch (DefinitionTypeDeterminer.determine(definition)) {
112+
case OPERATION:
113+
generateOperation((ObjectTypeDefinition) definition);
114+
break;
115+
case TYPE:
116+
generateType((ObjectTypeDefinition) definition, document, typeNames);
117+
generateFieldResolvers((ObjectTypeDefinition) definition);
118+
break;
119+
case INTERFACE:
120+
generateInterface((InterfaceTypeDefinition) definition);
121+
break;
122+
case ENUM:
123+
generateEnum((EnumTypeDefinition) definition);
124+
break;
125+
case INPUT:
126+
generateInput((InputObjectTypeDefinition) definition);
127+
break;
128+
case UNION:
129+
generateUnion((UnionTypeDefinition) definition);
130+
}
131+
}
132+
115133
private void generateUnion(UnionTypeDefinition definition) throws IOException, TemplateException {
116134
Map<String, Object> dataModel = UnionDefinitionToDataModelMapper.map(mappingConfig, definition);
117135
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.unionTemplate, dataModel, outputDir);
@@ -124,19 +142,32 @@ private void generateInterface(InterfaceTypeDefinition definition) throws IOExce
124142

125143
private void generateOperation(ObjectTypeDefinition definition) throws IOException, TemplateException {
126144
if (Boolean.TRUE.equals(mappingConfig.getGenerateApis())) {
127-
for (FieldDefinition fieldDef : definition.getFieldDefinitions()) {
128-
Map<String, Object> dataModel = FieldDefinitionToDataModelMapper.map(mappingConfig, fieldDef, definition.getName());
145+
for (FieldDefinition operationDef : definition.getFieldDefinitions()) {
146+
Map<String, Object> dataModel = FieldDefinitionToDataModelMapper.map(mappingConfig, operationDef, definition.getName());
129147
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir);
130148
}
131149
// We need to generate a root object to workaround https://github.com/facebook/relay/issues/112
132150
Map<String, Object> dataModel = ObjectDefinitionToDataModelMapper.map(mappingConfig, definition);
133151
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir);
134152
}
153+
154+
if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) {
155+
// generate request objects for graphql operations
156+
for (FieldDefinition operationDef : definition.getFieldDefinitions()) {
157+
Map<String, Object> requestDataModel = FieldDefinitionToRequestDataModelMapper.map(mappingConfig, operationDef, definition.getName());
158+
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.requestTemplate, requestDataModel, outputDir);
159+
}
160+
}
135161
}
136162

137-
private void generateType(ObjectTypeDefinition definition, Document document) throws IOException, TemplateException {
163+
private void generateType(ObjectTypeDefinition definition, Document document, Set<String> typeNames) throws IOException, TemplateException {
138164
Map<String, Object> dataModel = TypeDefinitionToDataModelMapper.map(mappingConfig, definition, document);
139165
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir);
166+
167+
if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) {
168+
Map<String, Object> responseProjDataModel = TypeDefinitionToDataModelMapper.mapResponseProjection(mappingConfig, definition, document, typeNames);
169+
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.responseProjectionTemplate, responseProjDataModel, outputDir);
170+
}
140171
}
141172

142173
private void generateFieldResolvers(ObjectTypeDefinition definition) throws IOException, TemplateException {
@@ -154,17 +185,30 @@ private void generateInput(InputObjectTypeDefinition definition) throws IOExcept
154185
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir);
155186
}
156187

188+
private static Set<String> getAllTypeNames(Document document) {
189+
return document.getDefinitionsOfType(ObjectTypeDefinition.class)
190+
.stream()
191+
.filter(typeDef -> !Utils.isGraphqlOperation(typeDef.getName()))
192+
.map(ObjectTypeDefinition::getName)
193+
.collect(Collectors.toSet());
194+
}
195+
157196
private void generateEnum(EnumTypeDefinition definition) throws IOException, TemplateException {
158197
Map<String, Object> dataModel = EnumDefinitionToDataModelMapper.map(mappingConfig, definition);
159198
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.enumTemplate, dataModel, outputDir);
160199
}
161200

162-
private void addScalarsToCustomMappingConfig(Document document) {
201+
private void initCustomTypeMappings(Document document) {
163202
for (Definition<?> definition : document.getDefinitions()) {
164203
if (definition instanceof ScalarTypeDefinition) {
165204
String scalarName = ((ScalarTypeDefinition) definition).getName();
166205
mappingConfig.putCustomTypeMappingIfAbsent(scalarName, "String");
167206
}
168207
}
208+
mappingConfig.putCustomTypeMappingIfAbsent("ID", "String");
209+
mappingConfig.putCustomTypeMappingIfAbsent("String", "String");
210+
mappingConfig.putCustomTypeMappingIfAbsent("Int", "Integer");
211+
mappingConfig.putCustomTypeMappingIfAbsent("Float", "Double");
212+
mappingConfig.putCustomTypeMappingIfAbsent("Boolean", "Boolean");
169213
}
170214
}

src/main/java/com/kobylynskyi/graphql/codegen/mapper/EnumDefinitionToDataModelMapper.java

-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ public static Map<String, Object> map(MappingConfig mappingConfig, EnumTypeDefin
4343
* @return list of strings
4444
*/
4545
private static List<String> map(List<EnumValueDefinition> enumValueDefinitions) {
46-
if (enumValueDefinitions == null) {
47-
return Collections.emptyList();
48-
}
4946
return enumValueDefinitions.stream()
5047
.map(EnumValueDefinition::getName)
5148
.map(MapperUtils::capitalizeIfRestricted)

src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToParameterMapper.java

+69-10
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
44
import com.kobylynskyi.graphql.codegen.model.ParameterDefinition;
5+
import com.kobylynskyi.graphql.codegen.model.ProjectionParameterDefinition;
56
import com.kobylynskyi.graphql.codegen.utils.Utils;
67
import graphql.language.FieldDefinition;
78

8-
import java.util.Collections;
99
import java.util.List;
10-
import java.util.stream.Collectors;
10+
import java.util.Set;
11+
12+
import static com.kobylynskyi.graphql.codegen.mapper.GraphqlTypeToJavaTypeMapper.*;
13+
import static java.util.stream.Collectors.toList;
1114

1215
/**
1316
* Mapper from GraphQL's FieldDefinition to a Freemarker-understandable format
@@ -24,18 +27,74 @@ public class FieldDefinitionToParameterMapper {
2427
* @param parentTypeName Name of the parent GraphQL type
2528
* @return Freemarker data model of the GraphQL field definition
2629
*/
27-
public static List<ParameterDefinition> map(MappingConfig mappingConfig,
28-
List<FieldDefinition> fieldDefinitions,
29-
String parentTypeName) {
30-
if (fieldDefinitions == null) {
31-
return Collections.emptyList();
32-
}
30+
public static List<ParameterDefinition> mapFields(MappingConfig mappingConfig, List<FieldDefinition> fieldDefinitions,
31+
String parentTypeName) {
3332
return fieldDefinitions.stream()
3433
.filter(fieldDef -> !generateResolversForField(mappingConfig, fieldDef, parentTypeName))
35-
.map(fieldDef -> GraphqlTypeToJavaTypeMapper.map(mappingConfig, fieldDef, parentTypeName))
36-
.collect(Collectors.toList());
34+
.map(fieldDef -> mapField(mappingConfig, fieldDef, parentTypeName))
35+
.collect(toList());
36+
}
37+
38+
/**
39+
* Map field definition to a Freemarker-understandable data model type
40+
*
41+
* @param mappingConfig Global mapping configuration
42+
* @param fieldDefinitions List of GraphQL field definitions
43+
* @param parentTypeName Name of the parent GraphQL type
44+
* @param typeNames Names of all GraphQL types
45+
* @return Freemarker data model of the GraphQL field definition
46+
*/
47+
public static List<ProjectionParameterDefinition> mapProjectionFields(MappingConfig mappingConfig,
48+
List<FieldDefinition> fieldDefinitions,
49+
String parentTypeName, Set<String> typeNames) {
50+
return fieldDefinitions.stream()
51+
.map(fieldDef -> mapProjectionField(mappingConfig, fieldDef, parentTypeName, typeNames))
52+
.collect(toList());
3753
}
3854

55+
/**
56+
* Map GraphQL's FieldDefinition to a Freemarker-understandable format of parameter
57+
*
58+
* @param mappingConfig Global mapping configuration
59+
* @param fieldDef GraphQL field definition
60+
* @param parentTypeName Name of the parent type
61+
* @return Freemarker-understandable format of parameter (field)
62+
*/
63+
private static ParameterDefinition mapField(MappingConfig mappingConfig, FieldDefinition fieldDef, String parentTypeName) {
64+
ParameterDefinition parameter = new ParameterDefinition();
65+
parameter.setName(MapperUtils.capitalizeIfRestricted(fieldDef.getName()));
66+
parameter.setType(getJavaType(mappingConfig, fieldDef.getType(), fieldDef.getName(), parentTypeName));
67+
parameter.setAnnotations(getAnnotations(mappingConfig, fieldDef.getType(), fieldDef.getName(), parentTypeName, false));
68+
return parameter;
69+
}
70+
71+
/**
72+
* Map GraphQL's FieldDefinition to a Freemarker-understandable format of parameter
73+
*
74+
* @param mappingConfig Global mapping configuration
75+
* @param fieldDef GraphQL field definition
76+
* @param parentTypeName Name of the parent type
77+
* @param typeNames Names of all GraphQL types
78+
* @return Freemarker-understandable format of parameter (field)
79+
*/
80+
private static ProjectionParameterDefinition mapProjectionField(MappingConfig mappingConfig, FieldDefinition fieldDef, String parentTypeName, Set<String> typeNames) {
81+
ProjectionParameterDefinition parameter = new ProjectionParameterDefinition();
82+
parameter.setName(MapperUtils.capitalizeIfRestricted(fieldDef.getName()));
83+
String nestedType = getNestedTypeName(fieldDef.getType());
84+
if (typeNames.contains(nestedType)) {
85+
parameter.setType(nestedType + mappingConfig.getResponseProjectionSuffix());
86+
}
87+
return parameter;
88+
}
89+
90+
/**
91+
* Check whether FieldResolver should be generated for a given field.
92+
*
93+
* @param mappingConfig Global mapping configuration
94+
* @param fieldDef GraphQL field definition
95+
* @param parentTypeName Name of the parent type
96+
* @return <code>true</code> if FieldResolver will be generated for the field. <code>false</code> otherwise
97+
*/
3998
public static boolean generateResolversForField(MappingConfig mappingConfig,
4099
FieldDefinition fieldDef,
41100
String parentTypeName) {

0 commit comments

Comments
 (0)