Skip to content

Commit 5a5a30d

Browse files
committed
Ability to combine multiple response projections #985
1 parent 02cd272 commit 5a5a30d

File tree

40 files changed

+761
-154
lines changed

40 files changed

+761
-154
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@
55
*/
66
public interface GraphQLParametrizedInput {
77

8+
GraphQLParametrizedInput deepCopy();
9+
810
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,18 @@ public boolean equals(Object obj) {
8686
public int hashCode() {
8787
return Objects.hash(name, alias, parameters, projection);
8888
}
89+
90+
public GraphQLResponseField deepCopy() {
91+
GraphQLResponseField deepCopy = new GraphQLResponseField(this.name);
92+
if (this.alias != null) {
93+
deepCopy.alias = this.alias;
94+
}
95+
if (this.parameters != null) {
96+
deepCopy.parameters = this.parameters.deepCopy();
97+
}
98+
if (this.projection != null) {
99+
deepCopy.projection = this.projection.deepCopy$();
100+
}
101+
return deepCopy;
102+
}
89103
}
Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,75 @@
11
package com.kobylynskyi.graphql.codegen.model.graphql;
22

3-
import java.util.ArrayList;
3+
import java.util.LinkedHashMap;
44
import java.util.List;
5+
import java.util.Map;
56
import java.util.StringJoiner;
67

78
/**
8-
* The implementation class should basically contain the fields of the particular type which
9-
* should be returned back to the client.
9+
* The implementation class should contain the fields of the particular type that should be returned to the client.
1010
*/
1111
public abstract class GraphQLResponseProjection {
1212

13-
protected final List<GraphQLResponseField> fields = new ArrayList<>();
13+
protected final Map<String, GraphQLResponseField> fields = new LinkedHashMap<>();
14+
15+
public GraphQLResponseProjection() {
16+
}
17+
18+
public GraphQLResponseProjection(GraphQLResponseProjection projection) {
19+
if (projection == null) {
20+
return;
21+
}
22+
projection.fields.values().forEach(this::add$);
23+
}
24+
25+
public GraphQLResponseProjection(List<? extends GraphQLResponseProjection> projections) {
26+
if (projections == null) {
27+
return;
28+
}
29+
for (GraphQLResponseProjection projection : projections) {
30+
if (projection == null) {
31+
continue;
32+
}
33+
projection.fields.values().forEach(this::add$);
34+
}
35+
}
36+
37+
public abstract GraphQLResponseProjection deepCopy$();
38+
39+
protected void add$(GraphQLResponseField responseField) {
40+
GraphQLResponseField existingResponseField = fields.get(responseField.getName());
41+
if (existingResponseField != null) {
42+
if (responseField.getAlias() != null) {
43+
existingResponseField.alias(responseField.getAlias());
44+
}
45+
if (responseField.getParameters() != null) {
46+
existingResponseField.parameters(responseField.getParameters().deepCopy());
47+
}
48+
if (responseField.getProjection() != null) {
49+
GraphQLResponseProjection projectionCopy = responseField.getProjection().deepCopy$();
50+
if (existingResponseField.getProjection() != null) {
51+
for (GraphQLResponseField field : projectionCopy.fields.values()) {
52+
existingResponseField.getProjection().add$(field);
53+
}
54+
} else {
55+
existingResponseField.projection(projectionCopy);
56+
}
57+
}
58+
} else {
59+
fields.put(responseField.getName(), responseField.deepCopy());
60+
}
61+
}
1462

1563
@Override
1664
public String toString() {
1765
if (fields.isEmpty()) {
1866
return "";
1967
}
2068
StringJoiner joiner = new StringJoiner(" ", "{ ", " }");
21-
fields.forEach(field -> joiner.add(field.toString()));
69+
for (GraphQLResponseField value : fields.values()) {
70+
joiner.add(value.toString());
71+
}
2272
return joiner.toString();
2373
}
74+
2475
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ public class ${className} implements GraphQLParametrizedInput {
7070

7171
</#list>
7272
</#if>
73+
@Override
74+
public ${className} deepCopy() {
75+
${className} parametrizedInput = new ${className}();
76+
<#if fields?has_content>
77+
<#list fields as field>
78+
parametrizedInput.${field.name}(this.${field.name});
79+
</#list>
80+
</#if>
81+
return parametrizedInput;
82+
}
83+
7384
<#if equalsAndHashCode>
7485
@Override
7586
public boolean equals(Object obj) {

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLResponseProjection;
88
import java.util.HashMap;
99
import java.util.Map;
1010
</#if>
11+
import java.util.List;
1112
<#if equalsAndHashCode>
1213
import java.util.Objects;
1314
</#if>
@@ -36,6 +37,14 @@ public class ${className} extends GraphQLResponseProjection {
3637

3738
public ${className}() {
3839
}
40+
41+
public ${className}(${className} projection) {
42+
super(projection);
43+
}
44+
45+
public ${className}(List<${className}> projections) {
46+
super(projections);
47+
}
3948
<#if fields?has_content && generateAllMethodInProjection>
4049

4150
public ${className} all$() {
@@ -76,7 +85,7 @@ public class ${className} extends GraphQLResponseProjection {
7685
}
7786

7887
public ${className} ${field.methodName}(String alias<#if field.type?has_content>, ${field.type} subProjection</#if>) {
79-
fields.add(new GraphQLResponseField("${field.name}").alias(alias)<#if field.type?has_content>.projection(subProjection)</#if>);
88+
add$(new GraphQLResponseField("${field.name}").alias(alias)<#if field.type?has_content>.projection(subProjection)</#if>);
8089
return this;
8190
}
8291

@@ -86,13 +95,18 @@ public class ${className} extends GraphQLResponseProjection {
8695
}
8796

8897
public ${className} ${field.methodName}(String alias, ${field.parametrizedInputClassName} input<#if field.type?has_content>, ${field.type} subProjection</#if>) {
89-
fields.add(new GraphQLResponseField("${field.name}").alias(alias).parameters(input)<#if field.type?has_content>.projection(subProjection)</#if>);
98+
add$(new GraphQLResponseField("${field.name}").alias(alias).parameters(input)<#if field.type?has_content>.projection(subProjection)</#if>);
9099
return this;
91100
}
92101

93102
</#if>
94103
</#list>
95104
</#if>
105+
@Override
106+
public ${className} deepCopy$() {
107+
return new ${className}(this);
108+
}
109+
96110
<#if equalsAndHashCode>
97111
@Override
98112
public boolean equals(Object obj) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
import java.io.File;
88
import java.io.FileNotFoundException;
99
import java.io.IOException;
10+
import java.nio.file.Files;
11+
import java.nio.file.OpenOption;
12+
import java.nio.file.Paths;
13+
import java.nio.file.StandardOpenOption;
1014
import java.time.ZonedDateTime;
1115
import java.util.Arrays;
1216

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.kobylynskyi.graphql.codegen.model.graphql;
2+
3+
import com.kobylynskyi.graphql.codegen.model.graphql.data.EventPropertyChildParametrizedInput;
4+
import com.kobylynskyi.graphql.codegen.model.graphql.data.EventPropertyResponseProjection;
5+
import com.kobylynskyi.graphql.codegen.model.graphql.data.EventResponseProjection;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.util.Arrays;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
12+
13+
class GraphQLResponseProjectionTest {
14+
15+
@Test
16+
public void deepCopy_empty() {
17+
EventResponseProjection original = new EventResponseProjection();
18+
19+
EventResponseProjection deepCopy = original.deepCopy$();
20+
21+
assertTrue(deepCopy.fields.isEmpty());
22+
}
23+
24+
@Test
25+
public void deepCopy() {
26+
EventResponseProjection original = new EventResponseProjection();
27+
original.id();
28+
original.status("state");
29+
original.properties(new EventPropertyResponseProjection()
30+
.intVal()
31+
.stringVal()
32+
.child(new EventPropertyChildParametrizedInput(1, 2),
33+
new EventPropertyResponseProjection()
34+
.booleanVal()));
35+
36+
EventResponseProjection deepCopy = original.deepCopy$();
37+
38+
assertEquals("{ id state : status properties { intVal stringVal child (first: 1, last: 2) { booleanVal } } }",
39+
original.toString());
40+
assertEquals("{ id state : status properties { intVal stringVal child (first: 1, last: 2) { booleanVal } } }",
41+
deepCopy.toString());
42+
43+
original.active();
44+
deepCopy.rating();
45+
46+
assertEquals("{ id state : status properties { intVal stringVal child (first: 1, last: 2) { booleanVal } } active }",
47+
original.toString());
48+
assertEquals("{ id state : status properties { intVal stringVal child (first: 1, last: 2) { booleanVal } } rating }",
49+
deepCopy.toString());
50+
}
51+
52+
@Test
53+
public void join() {
54+
EventResponseProjection projection1 = new EventResponseProjection();
55+
projection1.id();
56+
projection1.status("state");
57+
projection1.properties(new EventPropertyResponseProjection()
58+
.intVal()
59+
.stringVal()
60+
.child(new EventPropertyChildParametrizedInput(1, 2),
61+
new EventPropertyResponseProjection()
62+
.booleanVal()));
63+
64+
EventResponseProjection projection2 = new EventResponseProjection();
65+
projection2.id("uid");
66+
projection2.active();
67+
projection2.properties(new EventPropertyResponseProjection()
68+
.floatVal()
69+
.child(new EventPropertyChildParametrizedInput(3, 4),
70+
new EventPropertyResponseProjection()
71+
.intVal()));
72+
73+
EventResponseProjection projection12 = new EventResponseProjection(Arrays.asList(projection1, projection2));
74+
assertEquals("{ uid : id state : status properties { intVal stringVal child (first: 3, last: 4) { booleanVal intVal } floatVal } active }",
75+
projection12.toString());
76+
}
77+
78+
@Test
79+
public void join_safe() {
80+
EventResponseProjection projection1 = new EventResponseProjection();
81+
projection1.id(null);
82+
projection1.properties(null);
83+
84+
EventResponseProjection projection2 = new EventResponseProjection();
85+
projection2.properties(new EventPropertyResponseProjection()
86+
.child(null));
87+
88+
EventResponseProjection projection3 = new EventResponseProjection();
89+
projection2.properties(new EventPropertyResponseProjection()
90+
.child(null, null, new EventPropertyResponseProjection()
91+
.child(null)));
92+
93+
EventResponseProjection projection123 = new EventResponseProjection(Arrays.asList(projection1, projection2, projection3));
94+
assertEquals("{ id properties { child { child } } }",
95+
projection123.toString());
96+
}
97+
98+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,11 @@ public String toString() {
6262
return joiner.toString();
6363
}
6464

65+
@Override
66+
public EventPropertyChildParametrizedInput deepCopy() {
67+
EventPropertyChildParametrizedInput parametrizedInput = new EventPropertyChildParametrizedInput();
68+
parametrizedInput.first(this.first);
69+
parametrizedInput.last(this.last);
70+
return parametrizedInput;
71+
}
6572
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,11 @@ public String toString() {
6262
return joiner.toString();
6363
}
6464

65+
@Override
66+
public EventPropertyParentParametrizedInput deepCopy() {
67+
EventPropertyParentParametrizedInput parametrizedInput = new EventPropertyParentParametrizedInput();
68+
parametrizedInput.withStatus(this.withStatus);
69+
parametrizedInput.createdAfter(this.createdAfter);
70+
return parametrizedInput;
71+
}
6572
}

0 commit comments

Comments
 (0)