Skip to content

Commit 1ea0234

Browse files
authored
Support for async resolvers (#26)
* Support for async resolvers Solves issue: #24 * #24 Refactors and fixes * Renamed asyncApi to generateAsyncApi in MappingConfig * Changes for adding java.util.concurrent import when it comes to async Query or Mutation * Improved tests coverage
1 parent c11a2e6 commit 1ea0234

File tree

7 files changed

+136
-23
lines changed

7 files changed

+136
-23
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static Map<String, Object> map(MappingConfig mappingConfig, FieldDefiniti
3131
Map<String, Object> dataModel = new HashMap<>();
3232
String packageName = MapperUtils.getApiPackageName(mappingConfig);
3333
dataModel.put(PACKAGE, packageName);
34-
dataModel.put(IMPORTS, MapperUtils.getImports(mappingConfig, packageName));
34+
dataModel.put(IMPORTS, MapperUtils.getImportsForFieldDefinition(mappingConfig, packageName, objectTypeName));
3535
dataModel.put(CLASS_NAME, getClassName(fieldDefinition.getName(), objectTypeName));
3636
OperationDefinition operation = mapFieldDefinition(mappingConfig, fieldDefinition, objectTypeName);
3737
dataModel.put(OPERATIONS, Collections.singletonList(operation));
@@ -50,7 +50,7 @@ static OperationDefinition mapFieldDefinition(MappingConfig mappingConfig, Field
5050
OperationDefinition operation = new OperationDefinition();
5151
operation.setName(fieldDef.getName());
5252
String javaType = GraphqlTypeToJavaTypeMapper.getJavaType(mappingConfig, fieldDef.getType(), fieldDef.getName(), parentTypeName);
53-
operation.setType(GraphqlTypeToJavaTypeMapper.wrapIntoSubscriptionIfRequired(mappingConfig, javaType, parentTypeName));
53+
operation.setType(GraphqlTypeToJavaTypeMapper.wrapIntoAsyncIfRequired(mappingConfig, javaType, parentTypeName));
5454
operation.setAnnotations(GraphqlTypeToJavaTypeMapper.getAnnotations(mappingConfig, fieldDef.getType(), fieldDef.getName(), parentTypeName));
5555
operation.setParameters(InputValueDefinitionToParameterMapper.map(mappingConfig, fieldDef.getInputValueDefinitions(), fieldDef.getName()));
5656
return operation;

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,32 @@ private static String wrapIntoJavaCollection(String type) {
167167
return String.format("Collection<%s>", type);
168168
}
169169

170+
/**
171+
* Wrap java type into {@link java.util.concurrent.CompletableFuture}. E.g.: "String" becomes "CompletableFuture<String>"
172+
*
173+
* @param type Anything that will be wrapped into CompletableFuture<>
174+
* @return String wrapped into CompletableFuture<>
175+
*/
176+
private static String wrapIntoJavaCompletableFuture(String type) {
177+
return String.format("CompletableFuture<%s>", type);
178+
}
179+
180+
/**
181+
* Wraps type taking into account if an async api is needed, whether it is a Query, Mutation or Subscription
182+
*
183+
* @param mappingConfig Global mapping configuration
184+
* @param javaTypeName The type that will be wrapped into
185+
* @param parentTypeName Name of the parent type
186+
* @return Java type wrapped into the subscriptionReturnType
187+
*/
188+
static String wrapIntoAsyncIfRequired(MappingConfig mappingConfig, String javaTypeName, String parentTypeName) {
189+
if (MapperUtils.isAsyncQueryOrMutation(mappingConfig, parentTypeName)) {
190+
return wrapIntoJavaCompletableFuture(javaTypeName);
191+
}
192+
193+
return wrapIntoSubscriptionIfRequired(mappingConfig, javaTypeName, parentTypeName);
194+
}
195+
170196
/**
171197
* Wraps type into subscriptionReturnType (defined in the mapping configuration.
172198
* Example:

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.kobylynskyi.graphql.codegen.utils.Utils;
55
import graphql.language.Document;
66
import graphql.language.NamedNode;
7+
import graphql.language.OperationDefinition.Operation;
78
import graphql.language.UnionTypeDefinition;
89

910
import java.util.Arrays;
@@ -158,4 +159,37 @@ static Set<String> getImports(MappingConfig mappingConfig, String packageName) {
158159
imports.add("java.util");
159160
return imports;
160161
}
162+
163+
/**
164+
* Returns imports required for the particular case of field definitions (operations), taking into account async
165+
* operations, etc.
166+
*
167+
* @param mappingConfig
168+
* @param packageName
169+
* @param objectTypeName
170+
* @return
171+
*/
172+
static Set<String> getImportsForFieldDefinition(MappingConfig mappingConfig, String packageName, String objectTypeName) {
173+
final Set<String> imports = getImports(mappingConfig, packageName);
174+
175+
if (isAsyncQueryOrMutation(mappingConfig, objectTypeName)) {
176+
imports.add("java.util.concurrent");
177+
}
178+
179+
return imports;
180+
}
181+
182+
/**
183+
* Determines if the specified operation is an async query or mutation
184+
*
185+
* @param mappingConfig
186+
* @param objectTypeName
187+
* @return true if the given operation is an async query or mutation, false otherwise
188+
*/
189+
static boolean isAsyncQueryOrMutation(MappingConfig mappingConfig, String objectTypeName) {
190+
boolean isAsyncApi = mappingConfig.getGenerateAsyncApi() != null && mappingConfig.getGenerateAsyncApi().booleanValue();
191+
192+
return isAsyncApi && (Operation.QUERY.name().equalsIgnoreCase(objectTypeName) || Operation.MUTATION.name()
193+
.equalsIgnoreCase(objectTypeName));
194+
}
161195
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static Map<String, Object> map(MappingConfig mappingConfig, ObjectTypeDef
2929
Map<String, Object> dataModel = new HashMap<>();
3030
String packageName = MapperUtils.getApiPackageName(mappingConfig);
3131
dataModel.put(PACKAGE, packageName);
32-
dataModel.put(IMPORTS, MapperUtils.getImports(mappingConfig, packageName));
32+
dataModel.put(IMPORTS, MapperUtils.getImportsForFieldDefinition(mappingConfig, packageName, typeDefinition.getName()));
3333
dataModel.put(CLASS_NAME, Utils.capitalize(typeDefinition.getName()));
3434
List<Object> operations = typeDefinition.getFieldDefinitions().stream()
3535
.map(fieldDef ->

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package com.kobylynskyi.graphql.codegen.model;
22

33
import java.util.HashMap;
4-
import java.util.HashSet;
54
import java.util.Map;
6-
import java.util.Set;
75

86
import lombok.Data;
97

@@ -39,6 +37,7 @@ public class MappingConfig implements Combinable<MappingConfig> {
3937
private String subscriptionReturnType;
4038
private Boolean generateEqualsAndHashCode;
4139
private Boolean generateToString;
40+
private Boolean generateAsyncApi;
4241

4342

4443
/**
@@ -81,5 +80,6 @@ public void combine(MappingConfig source) {
8180
this.subscriptionReturnType = source.subscriptionReturnType != null ? source.subscriptionReturnType : this.subscriptionReturnType;
8281
this.generateEqualsAndHashCode = source.generateEqualsAndHashCode != null ? source.generateEqualsAndHashCode : this.generateEqualsAndHashCode;
8382
this.generateToString = source.generateToString != null ? source.generateToString : this.generateToString;
83+
this.generateAsyncApi = source.generateAsyncApi != null? source.generateAsyncApi : this.generateAsyncApi;
8484
}
8585
}

src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenTest.java

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
11
package com.kobylynskyi.graphql.codegen;
22

3-
import static java.util.stream.Collectors.toList;
4-
import static org.hamcrest.MatcherAssert.assertThat;
5-
import static org.junit.jupiter.api.Assertions.assertEquals;
6-
import static org.junit.jupiter.api.Assertions.assertNotEquals;
7-
import static org.junit.jupiter.api.Assertions.fail;
8-
9-
import java.io.File;
10-
import java.io.FileNotFoundException;
11-
import java.io.IOException;
12-
import java.nio.file.NoSuchFileException;
13-
import java.util.Arrays;
14-
import java.util.Collections;
15-
import java.util.HashMap;
16-
import java.util.List;
17-
import java.util.Objects;
18-
3+
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
4+
import com.kobylynskyi.graphql.codegen.utils.Utils;
5+
import org.hamcrest.Matchers;
196
import org.hamcrest.core.StringContains;
207
import org.hamcrest.core.StringStartsWith;
218
import org.junit.jupiter.api.AfterEach;
229
import org.junit.jupiter.api.Assertions;
2310
import org.junit.jupiter.api.BeforeEach;
2411
import org.junit.jupiter.api.Test;
2512

26-
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
27-
import com.kobylynskyi.graphql.codegen.utils.Utils;
13+
import java.io.File;
14+
import java.io.FileNotFoundException;
15+
import java.io.IOException;
16+
import java.nio.file.NoSuchFileException;
17+
import java.util.*;
18+
19+
import static java.util.stream.Collectors.toList;
20+
import static org.hamcrest.MatcherAssert.assertThat;
21+
import static org.junit.jupiter.api.Assertions.*;
2822

2923
class GraphqlCodegenTest {
3024

@@ -343,4 +337,59 @@ void generate_OnlyModel() throws Exception {
343337
assertEquals(Arrays.asList("Event.java", "EventProperty.java", "EventStatus.java"), generatedFileNames);
344338
}
345339

340+
@Test
341+
void generate_WithoutAsyncApis() throws Exception {
342+
mappingConfig.setGenerateAsyncApi(false);
343+
generator.generate();
344+
345+
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
346+
assertFileContainsElements(files, "VersionQuery.java", "String version()");
347+
}
348+
349+
@Test
350+
void generate_AsyncQueryApis() throws Exception {
351+
mappingConfig.setGenerateAsyncApi(true);
352+
generator.generate();
353+
354+
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
355+
356+
String importJavaUtilConcurrent = "import java.util.concurrent";
357+
358+
assertFileContainsElements(files, "VersionQuery.java", importJavaUtilConcurrent,
359+
"CompletableFuture<String> version()");
360+
361+
assertFileContainsElements(files, "EventsByCategoryAndStatusQuery.java", importJavaUtilConcurrent,
362+
"CompletableFuture<Collection<Event>> eventsByCategoryAndStatus(");
363+
364+
assertFileContainsElements(files, "EventByIdQuery.java", importJavaUtilConcurrent,
365+
"CompletableFuture<Event> eventById(");
366+
367+
}
368+
369+
@Test
370+
void generate_AsyncMutationApis() throws Exception {
371+
mappingConfig.setGenerateAsyncApi(true);
372+
generator.generate();
373+
374+
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
375+
376+
assertFileContainsElements(files, "CreateEventMutation.java",
377+
"import java.util.concurrent","CompletableFuture<Event> createEvent(");
378+
379+
}
380+
381+
private void assertFileContainsElements(File[] files, String fileName, String...elements)
382+
throws IOException {
383+
File file = getFile(files, fileName);
384+
385+
assertNotNull(file);
386+
387+
String fileContent = Utils.getFileContent(file.getPath());
388+
assertThat(fileContent, Matchers.stringContainsInOrder(elements));
389+
}
390+
391+
private File getFile(File[] files, String fileName) {
392+
return Arrays.stream(files).filter(f -> f.getName().equals(fileName)).findFirst().get();
393+
}
394+
346395
}

src/test/java/com/kobylynskyi/graphql/codegen/model/MappingConfigTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ void combineCustomWithDefault() {
6767
assertEquals("ModelValidationAnnotation", mappingConfig.getModelValidationAnnotation());
6868
assertEquals("PackageName", mappingConfig.getPackageName());
6969
assertEquals("SubscriptionsReturnType", mappingConfig.getSubscriptionReturnType());
70+
assertFalse(mappingConfig.getGenerateAsyncApi());
7071
}
7172

7273
@Test
@@ -88,6 +89,7 @@ void combineCustomWithCustom() {
8889
assertEquals("ModelValidationAnnotation2", mappingConfig.getModelValidationAnnotation());
8990
assertEquals("PackageName2", mappingConfig.getPackageName());
9091
assertEquals("SubscriptionsReturnType2", mappingConfig.getSubscriptionReturnType());
92+
assertTrue(mappingConfig.getGenerateAsyncApi());
9193
}
9294

9395
private static Map<String, String> hashMap(AbstractMap.SimpleEntry<String, String>... entries) {
@@ -109,6 +111,7 @@ private static MappingConfig buildMappingConfig() {
109111
config.setModelValidationAnnotation("ModelValidationAnnotation");
110112
config.setPackageName("PackageName");
111113
config.setSubscriptionReturnType("SubscriptionsReturnType");
114+
config.setGenerateAsyncApi(false);
112115
return config;
113116
}
114117

@@ -126,6 +129,7 @@ private static MappingConfig buildMappingConfig2() {
126129
config.setModelValidationAnnotation("ModelValidationAnnotation2");
127130
config.setPackageName("PackageName2");
128131
config.setSubscriptionReturnType("SubscriptionsReturnType2");
132+
config.setGenerateAsyncApi(true);
129133
return config;
130134
}
131135

0 commit comments

Comments
 (0)