Skip to content

Commit 80ae459

Browse files
joffrey-bionJoffrey Bion
andauthored
Unify data and template of field resolvers and operations (#66)
Conceptually, GraphQL operations (queries/mutations/subscriptions) are a special case of field resolvers that happen to be on well known root types. Technically, this means that everything about field resolvers is the same as these root types resolvers, apart from the fact that the former get an instance of the resolved type as first param. Therefore, all the configuration that applies to field resolvers also applies to resolvers of the fields that are part of these root types, like CompletableFuture return type, or DataFetcherEnvironment parameter. Resolves: #64 Resolves: #61 Resolves: #63 Co-authored-by: Joffrey Bion <[email protected]>
1 parent 39e0cd8 commit 80ae459

File tree

16 files changed

+201
-250
lines changed

16 files changed

+201
-250
lines changed

plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphqlCodegenGradleTask.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class GraphqlCodegenGradleTask extends DefaultTask {
4646
private Boolean generateToString = false;
4747
private Boolean generateAsyncApi = false;
4848
private Boolean generateParameterizedFieldsResolvers = true;
49+
private Boolean generateDataFetchingEnvironmentArgumentInApis = false;
4950
private Set<String> fieldsWithResolvers = new HashSet<>();
5051
private Boolean generateRequests;
5152
private String requestSuffix;
@@ -70,6 +71,7 @@ public void generate() throws Exception {
7071
mappingConfig.setGenerateToString(generateToString);
7172
mappingConfig.setGenerateAsyncApi(generateAsyncApi);
7273
mappingConfig.setGenerateParameterizedFieldsResolvers(generateParameterizedFieldsResolvers);
74+
mappingConfig.setGenerateDataFetchingEnvironmentArgumentInApis(generateDataFetchingEnvironmentArgumentInApis);
7375
mappingConfig.setFieldsWithResolvers(fieldsWithResolvers);
7476
mappingConfig.setGenerateRequests(generateRequests);
7577
mappingConfig.setRequestSuffix(requestSuffix);
@@ -298,6 +300,16 @@ public void setGenerateParameterizedFieldsResolvers(Boolean generateParameterize
298300
this.generateParameterizedFieldsResolvers = generateParameterizedFieldsResolvers;
299301
}
300302

303+
@Input
304+
@Optional
305+
public Boolean getGenerateDataFetchingEnvironmentArgumentInApis() {
306+
return generateDataFetchingEnvironmentArgumentInApis;
307+
}
308+
309+
public void setGenerateDataFetchingEnvironmentArgumentInApis(Boolean generateDataFetchingEnvironmentArgumentInApis) {
310+
this.generateDataFetchingEnvironmentArgumentInApis = generateDataFetchingEnvironmentArgumentInApis;
311+
}
312+
301313
@Input
302314
@Optional
303315
public Set<String> getFieldsWithResolvers() {

plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphqlCodegenMojo.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ public class GraphqlCodegenMojo extends AbstractMojo {
7676
@Parameter(defaultValue = "true")
7777
private boolean generateParameterizedFieldsResolvers;
7878

79+
@Parameter(defaultValue = "false")
80+
private boolean generateDataFetchingEnvironmentArgumentInApis;
81+
7982
@Parameter
8083
private Set<String> fieldsWithResolvers = new HashSet<>();
8184

@@ -117,6 +120,7 @@ public void execute() throws MojoExecutionException {
117120
mappingConfig.setSubscriptionReturnType(subscriptionReturnType);
118121
mappingConfig.setGenerateAsyncApi(generateAsyncApi);
119122
mappingConfig.setGenerateParameterizedFieldsResolvers(generateParameterizedFieldsResolvers);
123+
mappingConfig.setGenerateDataFetchingEnvironmentArgumentInApis(generateDataFetchingEnvironmentArgumentInApis);
120124
mappingConfig.setFieldsWithResolvers(fieldsWithResolvers != null ? fieldsWithResolvers : new HashSet<>());
121125
mappingConfig.setGenerateRequests(generateRequests);
122126
mappingConfig.setRequestSuffix(requestSuffix);
@@ -322,6 +326,14 @@ public void setGenerateParameterizedFieldsResolvers(boolean generateParameterize
322326
this.generateParameterizedFieldsResolvers = generateParameterizedFieldsResolvers;
323327
}
324328

329+
public boolean isGenerateDataFetchingEnvironmentArgumentInApis() {
330+
return generateDataFetchingEnvironmentArgumentInApis;
331+
}
332+
333+
public void setGenerateDataFetchingEnvironmentArgumentInApis(boolean generateDataFetchingEnvironmentArgumentInApis) {
334+
this.generateDataFetchingEnvironmentArgumentInApis = generateDataFetchingEnvironmentArgumentInApis;
335+
}
336+
325337
public Set<String> getFieldsWithResolvers() {
326338
return fieldsWithResolvers;
327339
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class FreeMarkerTemplatesRegistry {
1515
static Template requestTemplate;
1616
static Template interfaceTemplate;
1717
static Template operationsTemplate;
18-
static Template fieldsResolverTemplate;
1918
static Template responseProjectionTemplate;
2019

2120
static {
@@ -33,7 +32,6 @@ class FreeMarkerTemplatesRegistry {
3332
requestTemplate = configuration.getTemplate("templates/javaClassGraphqlRequest.ftl");
3433
interfaceTemplate = configuration.getTemplate("templates/javaClassGraphqlInterface.ftl");
3534
operationsTemplate = configuration.getTemplate("templates/javaClassGraphqlOperations.ftl");
36-
fieldsResolverTemplate = configuration.getTemplate("templates/javaClassGraphqlFieldsResolver.ftl");
3735
responseProjectionTemplate = configuration.getTemplate("templates/javaClassGraphqlResponseProjection.ftl");
3836
} catch (IOException e) {
3937
throw new UnableToLoadFreeMarkerTemplateException(e);

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ private void initDefaultValues(MappingConfig mappingConfig) {
7979
if (mappingConfig.getGenerateParameterizedFieldsResolvers() == null) {
8080
mappingConfig.setGenerateParameterizedFieldsResolvers(DefaultMappingConfigValues.DEFAULT_GENERATE_PARAMETERIZED_FIELDS_RESOLVERS);
8181
}
82+
if (mappingConfig.getGenerateDataFetchingEnvironmentArgumentInApis() == null) {
83+
mappingConfig.setGenerateDataFetchingEnvironmentArgumentInApis(DefaultMappingConfigValues.DEFAULT_GENERATE_DATA_FETCHING_ENV);
84+
}
8285
if (mappingConfig.getGenerateRequests()) {
8386
// required for request serialization
8487
mappingConfig.setGenerateToString(true);
@@ -146,11 +149,11 @@ private void generateInterface(InterfaceTypeDefinition definition) throws IOExce
146149
private void generateOperation(ObjectTypeDefinition definition) throws IOException, TemplateException {
147150
if (Boolean.TRUE.equals(mappingConfig.getGenerateApis())) {
148151
for (FieldDefinition operationDef : definition.getFieldDefinitions()) {
149-
Map<String, Object> dataModel = FieldDefinitionToDataModelMapper.map(mappingConfig, operationDef, definition.getName());
152+
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeField(mappingConfig, operationDef, definition.getName());
150153
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir);
151154
}
152155
// We need to generate a root object to workaround https://github.com/facebook/relay/issues/112
153-
Map<String, Object> dataModel = ObjectDefinitionToDataModelMapper.map(mappingConfig, definition);
156+
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeFields(mappingConfig, definition);
154157
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir);
155158
}
156159

@@ -178,8 +181,8 @@ private void generateFieldResolvers(ObjectTypeDefinition definition) throws IOEx
178181
.filter(fieldDef -> FieldDefinitionToParameterMapper.generateResolversForField(mappingConfig, fieldDef, definition.getName()))
179182
.collect(toList());
180183
if (!fieldDefsWithResolvers.isEmpty()) {
181-
Map<String, Object> dataModel = FieldResolverDefinitionToDataModelMapper.map(mappingConfig, fieldDefsWithResolvers, definition.getName());
182-
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.fieldsResolverTemplate, dataModel, outputDir);
184+
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapToTypeResolver(mappingConfig, fieldDefsWithResolvers, definition.getName());
185+
GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir);
183186
}
184187
}
185188

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

Lines changed: 0 additions & 68 deletions
This file was deleted.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.kobylynskyi.graphql.codegen.mapper;
2+
3+
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
4+
import com.kobylynskyi.graphql.codegen.model.OperationDefinition;
5+
import com.kobylynskyi.graphql.codegen.model.ParameterDefinition;
6+
import com.kobylynskyi.graphql.codegen.utils.Utils;
7+
import graphql.language.FieldDefinition;
8+
import graphql.language.ObjectTypeDefinition;
9+
import graphql.language.TypeName;
10+
11+
import java.util.ArrayList;
12+
import java.util.Collections;
13+
import java.util.HashMap;
14+
import java.util.List;
15+
import java.util.Map;
16+
import java.util.Set;
17+
import java.util.stream.Collectors;
18+
19+
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.*;
20+
import static java.util.Collections.emptyList;
21+
22+
/**
23+
* Map field definitions to a Freemarker data model representing a resolver for these fields.
24+
*/
25+
public class FieldDefinitionsToResolverDataModelMapper {
26+
27+
/**
28+
* Map field definition to a Freemarker data model
29+
*
30+
* @param mappingConfig Global mapping configuration
31+
* @param fieldDefs GraphQL field definitions that require resolvers
32+
* @param parentTypeName Name of the type for which Resolver will be generated
33+
* @return Freemarker data model of the GraphQL parametrized field
34+
*/
35+
public static Map<String, Object> mapToTypeResolver(MappingConfig mappingConfig, List<FieldDefinition> fieldDefs,
36+
String parentTypeName) {
37+
// Example: PersonResolver
38+
String className = parentTypeName + "Resolver";
39+
return mapToResolverModel(mappingConfig, parentTypeName, className, fieldDefs);
40+
}
41+
42+
/**
43+
* Map field definition to a Freemarker data model
44+
*
45+
* @param mappingConfig Global mapping configuration
46+
* @param fieldDefinition GraphQL field definition
47+
* @param rootTypeName Object type (e.g.: "Query", "Mutation" or "Subscription")
48+
* @return Freemarker data model of the GraphQL field
49+
*/
50+
public static Map<String, Object> mapRootTypeField(MappingConfig mappingConfig, FieldDefinition fieldDefinition,
51+
String rootTypeName) {
52+
// Examples: VersionQuery, CreateEventMutation (rootTypeName is "Query" or the likes)
53+
String className = Utils.capitalize(fieldDefinition.getName()) + rootTypeName;
54+
List<FieldDefinition> fieldDefs = Collections.singletonList(fieldDefinition);
55+
return mapToResolverModel(mappingConfig, rootTypeName, className, fieldDefs);
56+
}
57+
58+
/**
59+
* Map a root object type definition to a Freemarker data model for a resolver with all its fields.
60+
*
61+
* @param mappingConfig Global mapping configuration
62+
* @param rootTypeDefinition GraphQL object definition of a root type like Query
63+
* @return Freemarker data model of the GraphQL object
64+
*/
65+
public static Map<String, Object> mapRootTypeFields(MappingConfig mappingConfig, ObjectTypeDefinition rootTypeDefinition) {
66+
String parentTypeName = rootTypeDefinition.getName();
67+
String className = Utils.capitalize(parentTypeName);
68+
// For root types like "Query", we create resolvers for all fields
69+
List<FieldDefinition> fieldDefinitions = rootTypeDefinition.getFieldDefinitions();
70+
return mapToResolverModel(mappingConfig, parentTypeName, className, fieldDefinitions);
71+
}
72+
73+
private static Map<String, Object> mapToResolverModel(MappingConfig mappingConfig, String parentTypeName, String className,
74+
List<FieldDefinition> fieldDefinitions) {
75+
String packageName = MapperUtils.getApiPackageName(mappingConfig);
76+
Set<String> imports = MapperUtils.getImportsForFieldResolvers(mappingConfig, packageName, parentTypeName);
77+
List<OperationDefinition> operations = mapToOperations(mappingConfig, fieldDefinitions, parentTypeName);
78+
79+
Map<String, Object> dataModel = new HashMap<>();
80+
dataModel.put(PACKAGE, packageName);
81+
dataModel.put(IMPORTS, imports);
82+
dataModel.put(CLASS_NAME, className);
83+
dataModel.put(OPERATIONS, operations);
84+
return dataModel;
85+
}
86+
87+
/**
88+
* Builds a list of Freemarker-understandable structures representing operations to resolve the given fields
89+
* for a given parent type.
90+
*
91+
* @param mappingConfig Global mapping configuration
92+
* @param fieldDefinitions The GraphQL definition of the fields that the methods should resolve
93+
* @param parentTypeName Name of the parent type which the field belongs to
94+
* @return Freemarker-understandable format of operations
95+
*/
96+
private static List<OperationDefinition> mapToOperations(MappingConfig mappingConfig, List<FieldDefinition> fieldDefinitions, String parentTypeName) {
97+
return fieldDefinitions.stream()
98+
.map(fieldDef -> map(mappingConfig, fieldDef, parentTypeName))
99+
.collect(Collectors.toList());
100+
}
101+
102+
/**
103+
* Builds a Freemarker-understandable structure representing an operation to resolve a field for a given parent type.
104+
*
105+
* @param mappingConfig Global mapping configuration
106+
* @param resolvedField The GraphQL definition of the field that the method should resolve
107+
* @param parentTypeName Name of the parent type which the field belongs to
108+
* @return Freemarker-understandable format of operation
109+
*/
110+
private static OperationDefinition map(MappingConfig mappingConfig, FieldDefinition resolvedField, String parentTypeName) {
111+
String javaType = GraphqlTypeToJavaTypeMapper.getJavaType(mappingConfig, resolvedField.getType(), resolvedField.getName(), parentTypeName);
112+
OperationDefinition operation = new OperationDefinition();
113+
operation.setName(resolvedField.getName());
114+
operation.setType(GraphqlTypeToJavaTypeMapper.wrapIntoAsyncIfRequired(mappingConfig, javaType, parentTypeName));
115+
operation.setAnnotations(GraphqlTypeToJavaTypeMapper.getAnnotations(mappingConfig, resolvedField.getType(), resolvedField.getName(), parentTypeName));
116+
operation.setParameters(getOperationParameters(mappingConfig, resolvedField, parentTypeName));
117+
return operation;
118+
}
119+
120+
private static List<ParameterDefinition> getOperationParameters(MappingConfig mappingConfig, FieldDefinition resolvedField, String parentTypeName) {
121+
List<ParameterDefinition> parameters = new ArrayList<>();
122+
123+
// 1. First parameter is the parent object for which we are resolving fields (unless it's the root Query)
124+
if (!Utils.isGraphqlOperation(parentTypeName)) {
125+
String parentObjectParamType = GraphqlTypeToJavaTypeMapper.getJavaType(mappingConfig, new TypeName(parentTypeName));
126+
String parentObjectParamName = MapperUtils.capitalizeIfRestricted(Utils.uncapitalize(parentObjectParamType));
127+
parameters.add(new ParameterDefinition(parentObjectParamType, parentObjectParamName, null, emptyList()));
128+
}
129+
130+
// 2. Next parameters are input values
131+
parameters.addAll(InputValueDefinitionToParameterMapper.map(mappingConfig, resolvedField.getInputValueDefinitions(), resolvedField.getName()));
132+
133+
// 3. Last parameter (optional) is the DataFetchingEnvironment
134+
if (mappingConfig.getGenerateDataFetchingEnvironmentArgumentInApis()) {
135+
parameters.add(ParameterDefinition.DATA_FETCHING_ENVIRONMENT);
136+
}
137+
return parameters;
138+
}
139+
}

0 commit comments

Comments
 (0)