Skip to content

Commit 8d9d0de

Browse files
authored
Support {TYPE} placeholder in apiReturnType and apiReturnListType #1167 (#1200)
1 parent a3e7551 commit 8d9d0de

File tree

7 files changed

+408
-6
lines changed

7 files changed

+408
-6
lines changed

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.kobylynskyi.graphql.codegen.mapper.DataModelMapper;
44
import com.kobylynskyi.graphql.codegen.mapper.GraphQLTypeMapper;
5+
import com.kobylynskyi.graphql.codegen.model.MappingConfigConstants;
56
import com.kobylynskyi.graphql.codegen.model.MappingContext;
67
import com.kobylynskyi.graphql.codegen.model.NamedDefinition;
78
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
@@ -10,6 +11,8 @@
1011
import java.util.HashSet;
1112
import java.util.Map;
1213
import java.util.Set;
14+
import java.util.regex.Matcher;
15+
import java.util.regex.Pattern;
1316

1417
import static java.util.Arrays.asList;
1518

@@ -19,6 +22,7 @@
1922
public class JavaGraphQLTypeMapper extends GraphQLTypeMapper {
2023

2124
public static final String JAVA_UTIL_LIST = "java.util.List";
25+
public static final Pattern JAVA_UTIL_LIST_ELEMENT_REGEX = Pattern.compile("java\\.util\\.List<(.+)>");
2226
private static final String JAVA_UTIL_OPTIONAL = "java.util.Optional";
2327
private static final Set<String> JAVA_PRIMITIVE_TYPES = new HashSet<>(asList(
2428
"byte", "short", "int", "long", "float", "double", "char", "boolean"));
@@ -65,11 +69,28 @@ public String wrapApiReturnTypeIfRequired(MappingContext mappingContext,
6569
if (computedTypeName.startsWith(JAVA_UTIL_LIST) &&
6670
Utils.isNotBlank(mappingContext.getApiReturnListType())) {
6771
// in case it is query/mutation, return type is list and apiReturnListType is set
68-
return computedTypeName.replace(JAVA_UTIL_LIST, mappingContext.getApiReturnListType());
72+
if (mappingContext.getApiReturnListType().contains(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER)) {
73+
Matcher matcher = JAVA_UTIL_LIST_ELEMENT_REGEX.matcher(computedTypeName);
74+
if (matcher.find()) {
75+
String listElement = matcher.group(1);
76+
return mappingContext.getApiReturnListType().replace(
77+
MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER,
78+
listElement);
79+
} else {
80+
throw new IllegalStateException();
81+
}
82+
} else {
83+
return computedTypeName.replace(JAVA_UTIL_LIST, mappingContext.getApiReturnListType());
84+
}
6985
}
7086
if (Utils.isNotBlank(mappingContext.getApiReturnType())) {
7187
// in case it is query/mutation and apiReturnType is set
72-
return getGenericsString(mappingContext, mappingContext.getApiReturnType(), computedTypeName);
88+
if (mappingContext.getApiReturnType().contains(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER)) {
89+
return mappingContext.getApiReturnType()
90+
.replace(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER, computedTypeName);
91+
} else {
92+
return getGenericsString(mappingContext, mappingContext.getApiReturnType(), computedTypeName);
93+
}
7394
}
7495
return getTypeConsideringPrimitive(mappingContext, namedDefinition, computedTypeName);
7596
}

src/main/java/com/kobylynskyi/graphql/codegen/kotlin/KotlinGraphQLTypeMapper.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.kobylynskyi.graphql.codegen.mapper.DataModelMapper;
44
import com.kobylynskyi.graphql.codegen.mapper.GraphQLTypeMapper;
5+
import com.kobylynskyi.graphql.codegen.model.MappingConfigConstants;
56
import com.kobylynskyi.graphql.codegen.model.MappingContext;
67
import com.kobylynskyi.graphql.codegen.model.NamedDefinition;
78
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedFieldDefinition;
@@ -10,6 +11,8 @@
1011

1112
import java.util.HashSet;
1213
import java.util.Set;
14+
import java.util.regex.Matcher;
15+
import java.util.regex.Pattern;
1316

1417
import static java.util.Arrays.asList;
1518

@@ -22,6 +25,7 @@
2225
public class KotlinGraphQLTypeMapper extends GraphQLTypeMapper {
2326

2427
private static final String KOTLIN_UTIL_LIST = "List";
28+
public static final Pattern KOTLIN_UTIL_LIST_ELEMENT_REGEX = Pattern.compile("List<(.+)>");
2529
private static final String KOTLIN_UTIL_NULLABLE = "?";
2630
// Char Boolean are not primitive type, but non null equivalent jvm primitive types.
2731
private static final Set<String> KOTLIN_PRIMITIVE_TYPES = new HashSet<>(
@@ -93,11 +97,35 @@ public String wrapApiReturnTypeIfRequired(MappingContext mappingContext,
9397
if (computedTypeName.startsWith(KOTLIN_UTIL_LIST) &&
9498
Utils.isNotBlank(mappingContext.getApiReturnListType())) {
9599
// in case it is query/mutation, return type is list and apiReturnListType is set
96-
return computedTypeName.replace(KOTLIN_UTIL_LIST, mappingContext.getApiReturnListType());
100+
if (mappingContext.getApiReturnListType().contains(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER)) {
101+
boolean isNullable = computedTypeName.endsWith(KOTLIN_UTIL_NULLABLE);
102+
103+
Matcher matcher = KOTLIN_UTIL_LIST_ELEMENT_REGEX.matcher(computedTypeName);
104+
if (matcher.find()) {
105+
String listElement = matcher.group(1);
106+
computedTypeName = mappingContext.getApiReturnListType()
107+
.replace(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER, listElement);
108+
109+
if (isNullable) {
110+
return computedTypeName + "?";
111+
} else {
112+
return computedTypeName;
113+
}
114+
} else {
115+
throw new IllegalStateException();
116+
}
117+
} else {
118+
return computedTypeName.replace(KOTLIN_UTIL_LIST, mappingContext.getApiReturnListType());
119+
}
97120
}
98121
if (Utils.isNotBlank(mappingContext.getApiReturnType())) {
99122
// in case it is query/mutation and apiReturnType is set
100-
return getGenericsString(mappingContext, mappingContext.getApiReturnType(), computedTypeName);
123+
if (mappingContext.getApiReturnType().contains(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER)) {
124+
return mappingContext.getApiReturnType()
125+
.replace(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER, computedTypeName);
126+
} else {
127+
return getGenericsString(mappingContext, mappingContext.getApiReturnType(), computedTypeName);
128+
}
101129
}
102130
return getTypeConsideringPrimitive(mappingContext, namedDefinition, computedTypeName);
103131
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class MappingConfigConstants {
99
public static final String DEFAULT_VALIDATION_ANNOTATION = "javax.validation.constraints.NotNull";
1010
public static final String PARENT_INTERFACE_TYPE_PLACEHOLDER = "{{TYPE}}";
1111
public static final String TYPE_NAME_PLACEHOLDER = "{{TYPE_NAME}}";
12+
public static final String API_RETURN_NAME_PLACEHOLDER = "{{TYPE}}";
1213

1314
public static final boolean DEFAULT_GENERATE_APIS = true;
1415
public static final String DEFAULT_GENERATE_APIS_STRING = "true";

src/main/java/com/kobylynskyi/graphql/codegen/scala/ScalaGraphQLTypeMapper.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package com.kobylynskyi.graphql.codegen.scala;
22

33
import com.kobylynskyi.graphql.codegen.mapper.GraphQLTypeMapper;
4+
import com.kobylynskyi.graphql.codegen.model.MappingConfigConstants;
45
import com.kobylynskyi.graphql.codegen.model.MappingContext;
56
import com.kobylynskyi.graphql.codegen.model.NamedDefinition;
67
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
78
import com.kobylynskyi.graphql.codegen.utils.Utils;
89

910
import java.util.HashSet;
1011
import java.util.Set;
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
1114

1215
import static com.kobylynskyi.graphql.codegen.java.JavaGraphQLTypeMapper.JAVA_UTIL_LIST;
1316
import static java.util.Arrays.asList;
@@ -18,6 +21,7 @@
1821
public class ScalaGraphQLTypeMapper extends GraphQLTypeMapper {
1922

2023
private static final String SCALA_UTIL_LIST = "scala.Seq";
24+
private static final Pattern SCALA_UTIL_LIST_ELEMENT_REGEX = Pattern.compile("scala\\.Seq\\[(.+)]");
2125
private static final String SCALA_UTIL_OPTIONAL = "scala.Option";
2226
private static final Set<String> SCALA_PRIMITIVE_TYPES = new HashSet<>(asList(
2327
"Byte", "Short", "Int", "Long", "Float", "Double", "Char", "Boolean"));
@@ -72,11 +76,28 @@ public String wrapApiReturnTypeIfRequired(MappingContext mappingContext,
7276
if (computedTypeName.startsWith(SCALA_UTIL_LIST) &&
7377
Utils.isNotBlank(mappingContext.getApiReturnListType())) {
7478
// in case it is query/mutation, return type is list and apiReturnListType is set
75-
return computedTypeName.replace(SCALA_UTIL_LIST, mappingContext.getApiReturnListType());
79+
if (mappingContext.getApiReturnListType().contains(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER)) {
80+
Matcher matcher = SCALA_UTIL_LIST_ELEMENT_REGEX.matcher(computedTypeName);
81+
if (matcher.find()) {
82+
String listElement = matcher.group(1);
83+
return mappingContext.getApiReturnListType().replace(
84+
MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER,
85+
listElement);
86+
} else {
87+
throw new IllegalStateException();
88+
}
89+
} else {
90+
return computedTypeName.replace(SCALA_UTIL_LIST, mappingContext.getApiReturnListType());
91+
}
7692
}
7793
if (Utils.isNotBlank(mappingContext.getApiReturnType())) {
7894
// in case it is query/mutation and apiReturnType is set
79-
return getGenericsString(mappingContext, mappingContext.getApiReturnType(), computedTypeName);
95+
if (mappingContext.getApiReturnType().contains(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER)) {
96+
return mappingContext.getApiReturnType()
97+
.replace(MappingConfigConstants.API_RETURN_NAME_PLACEHOLDER, computedTypeName);
98+
} else {
99+
return getGenericsString(mappingContext, mappingContext.getApiReturnType(), computedTypeName);
100+
}
80101
}
81102
return getTypeConsideringPrimitive(mappingContext, namedDefinition, computedTypeName);
82103
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.kobylynskyi.graphql.codegen;
2+
3+
import com.kobylynskyi.graphql.codegen.java.JavaGraphQLCodegen;
4+
import com.kobylynskyi.graphql.codegen.model.GeneratedLanguage;
5+
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
6+
import com.kobylynskyi.graphql.codegen.utils.Utils;
7+
import org.junit.jupiter.api.AfterEach;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
11+
import java.io.File;
12+
import java.io.IOException;
13+
import java.util.Objects;
14+
15+
import static com.kobylynskyi.graphql.codegen.TestUtils.assertFileContainsElements;
16+
import static java.util.Collections.singletonList;
17+
18+
class GraphQLCodegenApiReturnTypeTest {
19+
20+
private final File outputBuildDir = new File("build/generated");
21+
private final File outputJavaClassesDir = new File("build/generated/com/kobylynskyi/graphql/test1");
22+
23+
private MappingConfig mappingConfig;
24+
25+
@BeforeEach
26+
void init() {
27+
mappingConfig = new MappingConfig();
28+
mappingConfig.setPackageName("com.kobylynskyi.graphql.test1");
29+
mappingConfig.setGeneratedLanguage(GeneratedLanguage.JAVA);
30+
}
31+
32+
@AfterEach
33+
void cleanup() {
34+
Utils.deleteDir(outputBuildDir);
35+
}
36+
37+
@Test
38+
void generate_ApiReturnType_WithPlaceHolder() throws Exception {
39+
mappingConfig.setApiReturnType(
40+
"java.util.concurrent.CompletionStage<graphql.execution.DataFetcherResult<{{TYPE}}>>"
41+
);
42+
43+
generate("src/test/resources/schemas/test.graphqls");
44+
45+
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
46+
47+
String requireChildText = getChildText(
48+
"java.util.concurrent.CompletionStage<graphql.execution.DataFetcherResult" +
49+
"<java.util.List<EventProperty>>>"
50+
);
51+
assertFileContainsElements(
52+
files,
53+
"EventPropertyResolver.java",
54+
requireChildText
55+
);
56+
57+
String requireParentText = getParentText(
58+
"java.util.concurrent.CompletionStage<graphql.execution.DataFetcherResult<Event>>"
59+
);
60+
assertFileContainsElements(files, "EventPropertyResolver.java",
61+
requireParentText
62+
);
63+
}
64+
65+
@Test
66+
void generate_ApiReturnType_And_ApiReturnListType_WithPlaceHolder() throws Exception {
67+
mappingConfig.setApiReturnType(
68+
"java.util.concurrent.CompletionStage<graphql.execution.DataFetcherResult<{{TYPE}}>>"
69+
);
70+
mappingConfig.setApiReturnListType(
71+
"reactor.core.publisher.Mono<graphql.execution.DataFetcherResult<{{TYPE}}>>"
72+
);
73+
74+
generate("src/test/resources/schemas/test.graphqls");
75+
76+
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
77+
78+
assertFileContainsElements(
79+
files,
80+
"EventPropertyResolver.java",
81+
getChildText(
82+
"reactor.core.publisher.Mono<graphql.execution.DataFetcherResult<EventProperty>>"
83+
)
84+
);
85+
86+
assertFileContainsElements(
87+
files,
88+
"EventPropertyResolver.java",
89+
getParentText(
90+
"java.util.concurrent.CompletionStage<graphql.execution.DataFetcherResult<Event>>"
91+
)
92+
);
93+
}
94+
95+
private String getChildText(String returnType) {
96+
return returnType + " child(EventProperty eventProperty, Integer first, Integer last) throws Exception;";
97+
}
98+
99+
private String getParentText(String returnType) {
100+
return returnType +
101+
" parent(EventProperty eventProperty, EventStatus withStatus, String createdAfter) throws Exception;";
102+
}
103+
104+
private void generate(String path) throws IOException {
105+
new JavaGraphQLCodegen(singletonList(path), outputBuildDir, mappingConfig,
106+
TestUtils.getStaticGeneratedInfo(mappingConfig)).generate();
107+
}
108+
109+
}

0 commit comments

Comments
 (0)