Skip to content

Commit fda4235

Browse files
authored
Relay support #295 (#304)
1 parent da05961 commit fda4235

18 files changed

Lines changed: 405 additions & 14 deletions

File tree

docs/codegen-options.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
| `apiReturnType` | String | Empty | Return type for api methods (query/mutation). For example: `reactor.core.publisher.Mono`, etc. |
3939
| `apiReturnListType` | String | Empty | Return type for api methods (query/mutation) having list type. For example: `reactor.core.publisher.Flux`, etc. By default is empty, so `apiReturnType` will be used. |
4040
| `subscriptionReturnType` | String | Empty | Return type for subscription methods. For example: `org.reactivestreams.Publisher`, `io.reactivex.Observable`, etc. |
41+
| `relayConfig` | *See [RelayConfig](#option-relayconfig)* | `@connection(for: ...)` | *See [RelayConfig](#option-relayconfig)* |
4142
| `generateClient` | Boolean | False | Specifies whether client-side classes should be generated for each query, mutation and subscription. This includes: `Request` classes (contain input data), `ResponseProjection` classes for each type (contain response fields) and `Response` classes (contain response data). |
4243
| `requestSuffix` | String | Request | Sets the suffix for `Request` classes. |
4344
| `responseSuffix` | String | Response | Sets the suffix for `Response` classes. |
@@ -137,6 +138,27 @@ You can also use one of the formatters for directive argument value: `{{val?toSt
137138

138139

139140

141+
### Option `relayConfig`
142+
143+
Can be used to supply a custom configuration for Relay support.
144+
For reference see: https://www.graphql-java-kickstart.com/tools/relay/
145+
146+
| Key inside `relayConfig` | Data Type | Default value | Description |
147+
| ------------------------ | --------- | -------------------------- | ----------- |
148+
| `directiveName` | String | `connection` | Directive name used for marking a field. |
149+
| `directiveArgumentName` | String | `for` | Directive argument name that contains a GraphQL type name. |
150+
| `connectionType` | String | `graphql.relay.Connection` | Generic Connection type. |
151+
152+
For example, the following schema:
153+
```
154+
type Query { users(first: Int, after: String): UserConnection @connection(for: "User") }
155+
```
156+
will result in generating the interface with the following method:
157+
```
158+
graphql.relay.Connection<User> users(Integer first, String after) throws Exception;
159+
```
160+
161+
140162

141163
### External mapping configuration
142164

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode
7676
private Boolean useOptionalForNullableReturnTypes = MappingConfigConstants.DEFAULT_USE_OPTIONAL_FOR_NULLABLE_RETURN_TYPES;
7777
private Set<String> fieldsWithResolvers = new HashSet<>();
7878
private Set<String> fieldsWithoutResolvers = new HashSet<>();
79+
private RelayConfig relayConfig = new RelayConfig();
7980

8081

8182
private Boolean generateClient;
@@ -134,6 +135,7 @@ public void generate() throws Exception {
134135
mappingConfig.setQueryResolverParentInterface(getQueryResolverParentInterface());
135136
mappingConfig.setMutationResolverParentInterface(getMutationResolverParentInterface());
136137
mappingConfig.setSubscriptionResolverParentInterface(getSubscriptionResolverParentInterface());
138+
mappingConfig.setRelayConfig(relayConfig);
137139

138140
new GraphQLCodegen(getActualSchemaPaths(), graphqlQueryIntrospectionResultPath, outputDir, mappingConfig, buildJsonSupplier()).generate();
139141
}
@@ -618,6 +620,17 @@ public void setParametrizedInputSuffix(String parametrizedInputSuffix) {
618620
this.parametrizedInputSuffix = parametrizedInputSuffix;
619621
}
620622

623+
@Nested
624+
@Optional
625+
@Override
626+
public RelayConfig getRelayConfig() {
627+
return relayConfig;
628+
}
629+
630+
public void relayConfig(Action<? super RelayConfig> action) {
631+
action.execute(relayConfig);
632+
}
633+
621634
@Nested
622635
@Optional
623636
public ParentInterfacesConfig getParentInterfaces() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.github.kobylynskyi.graphql.codegen.gradle;
2+
3+
import org.gradle.api.tasks.Input;
4+
import org.gradle.api.tasks.Optional;
5+
6+
public class RelayConfig extends com.kobylynskyi.graphql.codegen.model.RelayConfig {
7+
8+
@Input
9+
@Optional
10+
@Override
11+
public String getDirectiveName() {
12+
return super.getDirectiveName();
13+
}
14+
15+
@Input
16+
@Optional
17+
@Override
18+
public String getDirectiveArgumentName() {
19+
return super.getDirectiveArgumentName();
20+
}
21+
22+
@Input
23+
@Optional
24+
@Override
25+
public String getConnectionType() {
26+
return super.getConnectionType();
27+
}
28+
29+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.kobylynskyi.graphql.codegen.model.GraphQLCodegenConfiguration;
88
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
99
import com.kobylynskyi.graphql.codegen.model.MappingConfigConstants;
10+
import com.kobylynskyi.graphql.codegen.model.RelayConfig;
1011
import com.kobylynskyi.graphql.codegen.supplier.JsonMappingConfigSupplier;
1112
import com.kobylynskyi.graphql.codegen.supplier.MappingConfigSupplier;
1213
import com.kobylynskyi.graphql.codegen.supplier.SchemaFinder;
@@ -154,6 +155,9 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo
154155
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_PARAMETRIZED_INPUT_SUFFIX)
155156
private String parametrizedInputSuffix;
156157

158+
@Parameter
159+
private RelayConfig relayConfig = new RelayConfig();
160+
157161
@Parameter
158162
private String jsonConfigurationFile;
159163

@@ -211,6 +215,7 @@ public void execute() throws MojoExecutionException {
211215
mappingConfig.setQueryResolverParentInterface(getQueryResolverParentInterface());
212216
mappingConfig.setMutationResolverParentInterface(getMutationResolverParentInterface());
213217
mappingConfig.setSubscriptionResolverParentInterface(getSubscriptionResolverParentInterface());
218+
mappingConfig.setRelayConfig(relayConfig);
214219

215220
MappingConfigSupplier mappingConfigSupplier = buildJsonSupplier(jsonConfigurationFile);
216221

@@ -513,6 +518,15 @@ public Boolean getGenerateDataFetchingEnvironmentArgumentInApis() {
513518
return generateDataFetchingEnvironmentArgumentInApis;
514519
}
515520

521+
@Override
522+
public RelayConfig getRelayConfig() {
523+
return relayConfig;
524+
}
525+
526+
public void setRelayConfig(RelayConfig relayConfig) {
527+
this.relayConfig = relayConfig;
528+
}
529+
516530
public void setGenerateDataFetchingEnvironmentArgumentInApis(boolean generateDataFetchingEnvironmentArgumentInApis) {
517531
this.generateDataFetchingEnvironmentArgumentInApis = generateDataFetchingEnvironmentArgumentInApis;
518532
}

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

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
import com.kobylynskyi.graphql.codegen.model.NamedDefinition;
55
import com.kobylynskyi.graphql.codegen.model.OperationDefinition;
66
import com.kobylynskyi.graphql.codegen.model.ParameterDefinition;
7+
import com.kobylynskyi.graphql.codegen.model.RelayConfig;
78
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedFieldDefinition;
89
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedObjectTypeDefinition;
910
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
1011
import com.kobylynskyi.graphql.codegen.utils.Utils;
12+
import graphql.language.Argument;
13+
import graphql.language.Directive;
14+
import graphql.language.StringValue;
1115
import graphql.language.TypeName;
1216

1317
import java.util.ArrayList;
@@ -131,23 +135,26 @@ private static List<OperationDefinition> mapToOperations(MappingContext mappingC
131135
* Builds a Freemarker-understandable structure representing an operation to resolve a field for a given parent type.
132136
*
133137
* @param mappingContext Global mapping context
134-
* @param resolvedField The GraphQL definition of the field that the method should resolve
138+
* @param fieldDef The GraphQL definition of the field that the method should resolve
135139
* @param parentTypeName Name of the parent type which the field belongs to
136140
* @return Freemarker-understandable format of operation
137141
*/
138-
private static OperationDefinition map(MappingContext mappingContext, ExtendedFieldDefinition resolvedField,
142+
private static OperationDefinition map(MappingContext mappingContext, ExtendedFieldDefinition fieldDef,
139143
String parentTypeName) {
140-
NamedDefinition javaType = GraphqlTypeToJavaTypeMapper.getJavaType(
141-
mappingContext, resolvedField.getType(), resolvedField.getName(), parentTypeName);
144+
String name = MapperUtils.capitalizeIfRestricted(fieldDef.getName());
145+
NamedDefinition javaType = GraphqlTypeToJavaTypeMapper.getJavaType(mappingContext, fieldDef.getType(), fieldDef.getName(), parentTypeName);
146+
String returnType = getReturnType(mappingContext, fieldDef, javaType, parentTypeName);
147+
List<String> annotations = GraphqlTypeToJavaTypeMapper.getAnnotations(mappingContext, fieldDef.getType(), fieldDef, parentTypeName, false);
148+
List<ParameterDefinition> parameters = getOperationParameters(mappingContext, fieldDef, parentTypeName);
149+
142150
OperationDefinition operation = new OperationDefinition();
143-
operation.setName(MapperUtils.capitalizeIfRestricted(resolvedField.getName()));
144-
operation.setOriginalName(resolvedField.getName());
145-
operation.setType(GraphqlTypeToJavaTypeMapper.wrapIntoReturnTypeIfRequired(mappingContext, javaType, parentTypeName));
146-
operation.setAnnotations(GraphqlTypeToJavaTypeMapper.getAnnotations(mappingContext,
147-
resolvedField.getType(), resolvedField, parentTypeName, false));
148-
operation.setParameters(getOperationParameters(mappingContext, resolvedField, parentTypeName));
149-
operation.setJavaDoc(resolvedField.getJavaDoc());
150-
operation.setDeprecated(resolvedField.isDeprecated());
151+
operation.setName(name);
152+
operation.setOriginalName(fieldDef.getName());
153+
operation.setType(returnType);
154+
operation.setAnnotations(annotations);
155+
operation.setParameters(parameters);
156+
operation.setJavaDoc(fieldDef.getJavaDoc());
157+
operation.setDeprecated(fieldDef.isDeprecated());
151158
return operation;
152159
}
153160

@@ -196,4 +203,24 @@ public static String getParentInterface(MappingContext mappingContext, String ty
196203
MapperUtils.getModelClassNameWithPrefixAndSuffix(mappingContext, typeName));
197204
}
198205

206+
private static String getReturnType(MappingContext mappingContext, ExtendedFieldDefinition fieldDef,
207+
NamedDefinition namedDefinition, String parentTypeName) {
208+
RelayConfig relayConfig = mappingContext.getRelayConfig();
209+
if (relayConfig != null && relayConfig.getDirectiveName() != null) {
210+
Directive connectionDirective = fieldDef.getDirective(relayConfig.getDirectiveName());
211+
if (connectionDirective != null) {
212+
Argument argument = connectionDirective.getArgument(relayConfig.getDirectiveArgumentName());
213+
// as of now supporting only string value of directive argument
214+
if (argument != null && argument.getValue() instanceof StringValue) {
215+
String graphqlTypeName = ((StringValue) argument.getValue()).getValue();
216+
String javaTypeName = GraphqlTypeToJavaTypeMapper.getJavaType(mappingContext,
217+
new TypeName(graphqlTypeName), graphqlTypeName, parentTypeName, false).getName();
218+
return GraphqlTypeToJavaTypeMapper.getGenericsString(relayConfig.getConnectionType(), javaTypeName);
219+
}
220+
}
221+
}
222+
return GraphqlTypeToJavaTypeMapper.wrapApiReturnTypeIfRequired(mappingContext, namedDefinition, parentTypeName);
223+
224+
}
225+
199226
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ private static String wrapSuperTypeIntoJavaList(String type) {
255255
* @param parentTypeName Name of the parent type
256256
* @return Java type wrapped into the subscriptionReturnType
257257
*/
258-
static String wrapIntoReturnTypeIfRequired(MappingContext mappingContext, NamedDefinition namedDefinition, String parentTypeName) {
258+
static String wrapApiReturnTypeIfRequired(MappingContext mappingContext, NamedDefinition namedDefinition, String parentTypeName) {
259259
String javaTypeName = namedDefinition.getName();
260260
if (parentTypeName.equalsIgnoreCase(GraphQLOperation.SUBSCRIPTION.name())) {
261261
if (Utils.isNotBlank(mappingContext.getSubscriptionReturnType())) {
@@ -281,7 +281,7 @@ static String wrapIntoReturnTypeIfRequired(MappingContext mappingContext, NamedD
281281
return javaTypeName;
282282
}
283283

284-
private static String getGenericsString(String genericType, String typeParameter) {
284+
static String getGenericsString(String genericType, String typeParameter) {
285285
return String.format("%s<%s>", genericType, typeParameter);
286286
}
287287

src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,13 @@ public interface GraphQLCodegenConfiguration {
235235
*/
236236
Boolean getGenerateDataFetchingEnvironmentArgumentInApis();
237237

238+
/**
239+
* Relay-related configurations.
240+
*
241+
* @return Relay-related configurations.
242+
*/
243+
RelayConfig getRelayConfig();
244+
238245
/**
239246
* Fields that require Resolvers should be defined here in format: TypeName, TypeName.fieldName, @directive
240247
* If just type is specified, then all fields of this type will have resolvers

src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class MappingConfig implements GraphQLCodegenConfiguration, Combinable<Ma
3838
private String apiReturnType;
3939
private String apiReturnListType;
4040
private String subscriptionReturnType;
41+
private RelayConfig relayConfig = new RelayConfig();
4142

4243
// various toggles
4344
private Boolean generateApis;
@@ -99,6 +100,7 @@ public void combine(MappingConfig source) {
99100
generateDataFetchingEnvironmentArgumentInApis = getValueOrDefaultToThis(source, GraphQLCodegenConfiguration::getGenerateDataFetchingEnvironmentArgumentInApis);
100101
generateModelsForRootTypes = getValueOrDefaultToThis(source, GraphQLCodegenConfiguration::getGenerateModelsForRootTypes);
101102
useOptionalForNullableReturnTypes = getValueOrDefaultToThis(source, GraphQLCodegenConfiguration::getUseOptionalForNullableReturnTypes);
103+
relayConfig = getValueOrDefaultToThis(source, GraphQLCodegenConfiguration::getRelayConfig);
102104
queryResolverParentInterface = getValueOrDefaultToThis(source, GraphQLCodegenConfiguration::getQueryResolverParentInterface);
103105
mutationResolverParentInterface = getValueOrDefaultToThis(source, GraphQLCodegenConfiguration::getMutationResolverParentInterface);
104106
subscriptionResolverParentInterface = getValueOrDefaultToThis(source, GraphQLCodegenConfiguration::getSubscriptionResolverParentInterface);
@@ -404,6 +406,15 @@ public void setGenerateDataFetchingEnvironmentArgumentInApis(Boolean generateDat
404406
this.generateDataFetchingEnvironmentArgumentInApis = generateDataFetchingEnvironmentArgumentInApis;
405407
}
406408

409+
@Override
410+
public RelayConfig getRelayConfig() {
411+
return relayConfig;
412+
}
413+
414+
public void setRelayConfig(RelayConfig relayConfig) {
415+
this.relayConfig = relayConfig;
416+
}
417+
407418
@Override
408419
public Boolean getGenerateModelsForRootTypes() {
409420
return generateModelsForRootTypes;

src/main/java/com/kobylynskyi/graphql/codegen/model/MappingContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ public Boolean getGenerateDataFetchingEnvironmentArgumentInApis() {
166166
return config.getGenerateDataFetchingEnvironmentArgumentInApis();
167167
}
168168

169+
@Override
170+
public RelayConfig getRelayConfig() {
171+
return config.getRelayConfig();
172+
}
173+
169174
@Override
170175
public Boolean getUseOptionalForNullableReturnTypes() {
171176
return config.getUseOptionalForNullableReturnTypes();
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.kobylynskyi.graphql.codegen.model;
2+
3+
import java.io.Serializable;
4+
5+
public class RelayConfig implements Serializable {
6+
7+
// Increment this when the serialization output changes
8+
private static final long serialVersionUID = 937465092341L;
9+
10+
private String directiveName = "connection";
11+
private String directiveArgumentName = "for";
12+
private String connectionType = "graphql.relay.Connection";
13+
14+
public String getDirectiveName() {
15+
return directiveName;
16+
}
17+
18+
public void setDirectiveName(String directiveName) {
19+
this.directiveName = directiveName;
20+
}
21+
22+
public String getDirectiveArgumentName() {
23+
return directiveArgumentName;
24+
}
25+
26+
public void setDirectiveArgumentName(String directiveArgumentName) {
27+
this.directiveArgumentName = directiveArgumentName;
28+
}
29+
30+
public String getConnectionType() {
31+
return connectionType;
32+
}
33+
34+
public void setConnectionType(String connectionType) {
35+
this.connectionType = connectionType;
36+
}
37+
}

0 commit comments

Comments
 (0)