Skip to content

Commit 1a5bbd9

Browse files
committed
Kotlin/Scala: combining multiple projections #985
1 parent 7799ed2 commit 1a5bbd9

21 files changed

+314
-52
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ public int hashCode() {
8787
return Objects.hash(name, alias, parameters, projection);
8888
}
8989

90+
/**
91+
* Returns a clone of the instance, having a deep copy of the parameters and projection.
92+
*
93+
* @return a clone (deep copy)
94+
*/
9095
public GraphQLResponseField deepCopy() {
9196
GraphQLResponseField deepCopy = new GraphQLResponseField(this.name);
9297
if (this.alias != null) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
*/
1212
public abstract class GraphQLResponseProjection {
1313

14+
/**
15+
* Contains all response projection fields, where:
16+
* <key> is the name+alias pair (where alias is nullable)
17+
* <value> is GraphQLResponseField which represents the response projection field
18+
*/
1419
protected final Map<Pair<String, String>, GraphQLResponseField> fields = new LinkedHashMap<>();
1520

1621
public GraphQLResponseProjection() {
@@ -35,8 +40,10 @@ public GraphQLResponseProjection(List<? extends GraphQLResponseProjection> proje
3540
}
3641
}
3742

43+
@SuppressWarnings({"MethodNameCheck"})
3844
public abstract GraphQLResponseProjection deepCopy$();
3945

46+
@SuppressWarnings({"MethodNameCheck"})
4047
protected void add$(GraphQLResponseField responseField) {
4148
Pair<String, String> nameAndAlias = new Pair<>(responseField.getName(), responseField.getAlias());
4249
GraphQLResponseField existingResponseField = fields.get(nameAndAlias);

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

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

3+
import java.util.Objects;
4+
5+
/**
6+
* Class that represents a key-value pair.
7+
*
8+
* @param <K> key
9+
* @param <V> value
10+
*/
311
public class Pair<K, V> {
412

513
private final K key;
@@ -30,13 +38,12 @@ public int hashCode() {
3038

3139
@Override
3240
public boolean equals(Object o) {
33-
if (this == o) return true;
41+
if (this == o) {
42+
return true;
43+
}
3444
if (o instanceof Pair) {
35-
Pair pair = (Pair) o;
36-
if (key != null ? !key.equals(pair.key) : pair.key != null) {
37-
return false;
38-
}
39-
return value != null ? value.equals(pair.value) : pair.value == null;
45+
Pair<?, ?> pair = (Pair<?, ?>) o;
46+
return Objects.equals(key, pair.key) && Objects.equals(value, pair.value);
4047
}
4148
return false;
4249
}

src/main/resources/templates/kotlin-lang/parametrized_input.ftl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ data class ${className}(
3838
</#if>
3939
) : GraphQLParametrizedInput {
4040

41+
override fun deepCopy(): ${className} {
42+
val parametrizedInput = ${className}()
43+
<#if fields?has_content>
44+
<#list fields as field>
45+
parametrizedInput.${field.name}(this.${field.name})
46+
</#list>
47+
</#if>
48+
return parametrizedInput
49+
}
50+
4151
override fun toString(): String {
4252
val joiner = StringJoiner(", ", "( ", " )")
4353
<#list fields as field>

src/main/resources/templates/kotlin-lang/response_projection.ftl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ import java.util.Objects
2424
<#list annotations as annotation>
2525
@${annotation}
2626
</#list>
27-
open class ${className} : GraphQLResponseProjection() {
27+
open class ${className} : GraphQLResponseProjection {
28+
29+
constructor(): super()
30+
31+
constructor(projection: ${className}): super(projection)
32+
33+
constructor(projections: List<${className}>): super(projections)
2834

2935
<#if fields?has_content && generateAllMethodInProjection>
3036
private val projectionDepthOnFields: MutableMap<String, Int> by lazy { mutableMapOf<String, Int>() }
@@ -63,21 +69,23 @@ open class ${className} : GraphQLResponseProjection() {
6369
fun ${field.methodName}(<#if field.type?has_content>subProjection: ${field.type}</#if>): ${className} = ${field.methodName}(<#if field.parametrizedInputClassName?has_content></#if>null<#if field.type?has_content>, subProjection</#if>)
6470

6571
fun ${field.methodName}(alias: String?<#if field.type?has_content>, subProjection: ${field.type}</#if>): ${className} {
66-
fields.add(GraphQLResponseField("${field.name}").alias(alias)<#if field.type?has_content>.projection(subProjection)</#if>)
72+
`add$`(GraphQLResponseField("${field.name}").alias(alias)<#if field.type?has_content>.projection(subProjection)</#if>)
6773
return this
6874
}
6975

7076
<#if field.parametrizedInputClassName?has_content>
7177
fun ${field.methodName}(input: ${field.parametrizedInputClassName}<#if field.type?has_content>, subProjection: ${field.type}</#if>): ${className} = ${field.methodName}(null, input<#if field.type?has_content>, subProjection</#if>)
7278

7379
fun ${field.methodName}(alias: String?, input: ${field.parametrizedInputClassName}<#if field.type?has_content>, subProjection: ${field.type}</#if>): ${className} {
74-
fields.add(GraphQLResponseField("${field.name}").alias(alias).parameters(input)<#if field.type?has_content>.projection(subProjection)</#if>)
80+
`add$`(GraphQLResponseField("${field.name}").alias(alias).parameters(input)<#if field.type?has_content>.projection(subProjection)</#if>)
7581
return this
7682
}
7783

7884
</#if>
7985
</#list>
8086
</#if>
87+
override fun `deepCopy$`(): ${className} = ${className}(this)
88+
8189
<#if equalsAndHashCode>
8290
override fun equals(other: Any?): Boolean {
8391
if (this === other) {

src/main/resources/templates/scala-lang/parametrized_input.ftl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ case class ${className}(
5454
</#if>
5555
) extends GraphQLParametrizedInput {
5656

57+
override def deepCopy(): ${className} {
58+
val parametrizedInput = ${className}()
59+
<#if fields?has_content>
60+
<#list fields as field>
61+
parametrizedInput.${field.name}(this.${field.name})
62+
</#list>
63+
</#if>
64+
parametrizedInput
65+
}
66+
5767
override def toString(): String = {<#--There is no Option[Seq[T]], Format is not supported in the generated code, so it is very difficult to write template for this format.-->
5868
<#if fields?has_content>
5969
scala.Seq(<#list fields as field><#assign getMethod = ".get"><#assign asJava = ".asJava">

src/main/resources/templates/scala-lang/response_projection.ftl

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import java.util.Objects
1010
<#if fields?has_content && generateAllMethodInProjection>
1111
import scala.collection.mutable.HashMap
1212
</#if>
13+
import scala.collection.JavaConverters._
1314

1415
<#if javaDoc?has_content>
1516
/**
@@ -27,7 +28,29 @@ import scala.collection.mutable.HashMap
2728
<#list annotations as annotation>
2829
@${annotation}
2930
</#list>
30-
class ${className} extends GraphQLResponseProjection {
31+
class ${className}() extends GraphQLResponseProjection() {
32+
33+
def this(projection: ${className}) = {
34+
this()
35+
if (projection != null) {
36+
for (field <- projection.fields.values) {
37+
add$(field)
38+
}
39+
}
40+
}
41+
42+
def this(projections: scala.Seq[${className}]) = {
43+
this()
44+
if (projections != null) {
45+
for (projection <- projections) {
46+
if (projection != null) {
47+
for (field <- projection.fields.values) {
48+
add$(field)
49+
}
50+
}
51+
}
52+
}
53+
}
3154

3255
<#if fields?has_content && generateAllMethodInProjection>
3356
private final lazy val projectionDepthOnFields = new HashMap[String, Int]
@@ -85,6 +108,8 @@ class ${className} extends GraphQLResponseProjection {
85108
</#if>
86109
</#list>
87110
</#if>
111+
override def deepCopy$(): ${className} = ${className}(this)
112+
88113
<#if equalsAndHashCode>
89114
override def equals(obj: Any): Boolean = {
90115
if (this == obj) {

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ public void deepCopy() {
6767
original.active();
6868
deepCopy.rating();
6969

70-
assertEquals("{ id state : status properties { intVal stringVal child (first: 1, last: 2) { booleanVal } } active }",
70+
assertEquals(
71+
"{ id state : status properties { intVal stringVal child (first: 1, last: 2) { booleanVal } } active }",
7172
original.toString());
72-
assertEquals("{ id state : status properties { intVal stringVal child (first: 1, last: 2) { booleanVal } } rating }",
73+
assertEquals(
74+
"{ id state : status properties { intVal stringVal child (first: 1, last: 2) { booleanVal } } rating }",
7375
deepCopy.toString());
7476
}
7577

@@ -97,7 +99,9 @@ public void join() {
9799
.intVal()));
98100

99101
EventResponseProjection projection12 = new EventResponseProjection(asList(projection1, projection2));
100-
assertEquals("{ id state : status rating properties { intVal stringVal child12 : child (first: 1, last: 2) { booleanVal } floatVal child34 : child (first: 3, last: 4) { intVal } } uid : id status active }",
102+
assertEquals("{ id state : status rating " +
103+
"properties { intVal stringVal child12 : child (first: 1, last: 2) { booleanVal } " +
104+
"floatVal child34 : child (first: 3, last: 4) { intVal } } uid : id status active }",
101105
projection12.toString());
102106
}
103107

@@ -196,7 +200,8 @@ public void join_null_values() {
196200
.child(null, null, new EventPropertyResponseProjection()
197201
.child(null)));
198202

199-
EventResponseProjection projection123 = new EventResponseProjection(asList(projection1, projection2, projection3));
203+
EventResponseProjection projection123 = new EventResponseProjection(
204+
asList(projection1, projection2, projection3));
200205
assertEquals("{ id properties { child { child } } }",
201206
projection123.toString());
202207
}

src/test/java/com/kobylynskyi/graphql/codegen/model/graphql/data/UpdateNodeUnionResponseProjection.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public UpdateNodeUnionResponseProjection onOrganization(OrganizationResponseProj
3434
return onOrganization(null, subProjection);
3535
}
3636

37-
public UpdateNodeUnionResponseProjection onOrganization(String alias, OrganizationResponseProjection subProjection) {
37+
public UpdateNodeUnionResponseProjection onOrganization(String alias,
38+
OrganizationResponseProjection subProjection) {
3839
add$(new GraphQLResponseField("...on Organization").alias(alias).projection(subProjection));
3940
return this;
4041
}

src/test/resources/expected-classes/kt/SearchResultItemConnectionResponseProjection.kt.txt

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import java.util.Objects
1111
value = ["com.kobylynskyi.graphql.codegen.GraphQLCodegen"],
1212
date = "2020-12-31T23:59:59-0500"
1313
)
14-
open class SearchResultItemConnectionResponseProjection : GraphQLResponseProjection() {
14+
open class SearchResultItemConnectionResponseProjection : GraphQLResponseProjection {
15+
16+
constructor(): super()
17+
18+
constructor(projection: SearchResultItemConnectionResponseProjection): super(projection)
19+
20+
constructor(projections: List<SearchResultItemConnectionResponseProjection>): super(projections)
1521

1622
private val projectionDepthOnFields: MutableMap<String, Int> by lazy { mutableMapOf<String, Int>() }
1723

@@ -42,66 +48,68 @@ open class SearchResultItemConnectionResponseProjection : GraphQLResponseProject
4248
fun codeCount(): SearchResultItemConnectionResponseProjection = codeCount(null)
4349

4450
fun codeCount(alias: String?): SearchResultItemConnectionResponseProjection {
45-
fields.add(GraphQLResponseField("codeCount").alias(alias))
51+
`add$`(GraphQLResponseField("codeCount").alias(alias))
4652
return this
4753
}
4854

4955
fun edges(subProjection: SearchResultItemEdgeResponseProjection): SearchResultItemConnectionResponseProjection = edges(null, subProjection)
5056

5157
fun edges(alias: String?, subProjection: SearchResultItemEdgeResponseProjection): SearchResultItemConnectionResponseProjection {
52-
fields.add(GraphQLResponseField("edges").alias(alias).projection(subProjection))
58+
`add$`(GraphQLResponseField("edges").alias(alias).projection(subProjection))
5359
return this
5460
}
5561

5662
fun issueCount(): SearchResultItemConnectionResponseProjection = issueCount(null)
5763

5864
fun issueCount(alias: String?): SearchResultItemConnectionResponseProjection {
59-
fields.add(GraphQLResponseField("issueCount").alias(alias))
65+
`add$`(GraphQLResponseField("issueCount").alias(alias))
6066
return this
6167
}
6268

6369
fun nodes(subProjection: SearchResultItemResponseProjection): SearchResultItemConnectionResponseProjection = nodes(null, subProjection)
6470

6571
fun nodes(alias: String?, subProjection: SearchResultItemResponseProjection): SearchResultItemConnectionResponseProjection {
66-
fields.add(GraphQLResponseField("nodes").alias(alias).projection(subProjection))
72+
`add$`(GraphQLResponseField("nodes").alias(alias).projection(subProjection))
6773
return this
6874
}
6975

7076
fun pageInfo(subProjection: PageInfoResponseProjection): SearchResultItemConnectionResponseProjection = pageInfo(null, subProjection)
7177

7278
fun pageInfo(alias: String?, subProjection: PageInfoResponseProjection): SearchResultItemConnectionResponseProjection {
73-
fields.add(GraphQLResponseField("pageInfo").alias(alias).projection(subProjection))
79+
`add$`(GraphQLResponseField("pageInfo").alias(alias).projection(subProjection))
7480
return this
7581
}
7682

7783
fun repositoryCount(): SearchResultItemConnectionResponseProjection = repositoryCount(null)
7884

7985
fun repositoryCount(alias: String?): SearchResultItemConnectionResponseProjection {
80-
fields.add(GraphQLResponseField("repositoryCount").alias(alias))
86+
`add$`(GraphQLResponseField("repositoryCount").alias(alias))
8187
return this
8288
}
8389

8490
fun userCount(): SearchResultItemConnectionResponseProjection = userCount(null)
8591

8692
fun userCount(alias: String?): SearchResultItemConnectionResponseProjection {
87-
fields.add(GraphQLResponseField("userCount").alias(alias))
93+
`add$`(GraphQLResponseField("userCount").alias(alias))
8894
return this
8995
}
9096

9197
fun wikiCount(): SearchResultItemConnectionResponseProjection = wikiCount(null)
9298

9399
fun wikiCount(alias: String?): SearchResultItemConnectionResponseProjection {
94-
fields.add(GraphQLResponseField("wikiCount").alias(alias))
100+
`add$`(GraphQLResponseField("wikiCount").alias(alias))
95101
return this
96102
}
97103

98104
fun typename(): SearchResultItemConnectionResponseProjection = typename(null)
99105

100106
fun typename(alias: String?): SearchResultItemConnectionResponseProjection {
101-
fields.add(GraphQLResponseField("__typename").alias(alias))
107+
`add$`(GraphQLResponseField("__typename").alias(alias))
102108
return this
103109
}
104110

111+
override fun `deepCopy$`(): SearchResultItemConnectionResponseProjection = SearchResultItemConnectionResponseProjection(this)
112+
105113
override fun equals(other: Any?): Boolean {
106114
if (this === other) {
107115
return true
@@ -115,4 +123,4 @@ open class SearchResultItemConnectionResponseProjection : GraphQLResponseProject
115123

116124
override fun hashCode(): Int = Objects.hash(fields)
117125

118-
}
126+
}

0 commit comments

Comments
 (0)