Skip to content

Commit 5184822

Browse files
authored
Merge pull request #138 from bsara/response-json-path
feat: added json path features to GraphQLResponse
2 parents ba1b2f1 + 21b1741 commit 5184822

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

graphql-webclient/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
dependencies {
22
api "org.springframework.boot:spring-boot-starter-webflux"
3+
implementation "com.jayway.jsonpath:json-path:2.8.0"
34

45
testImplementation "org.springframework.boot:spring-boot-starter-webflux"
56
testImplementation "org.springframework.boot:spring-boot-starter-test"

graphql-webclient/src/main/java/graphql/kickstart/spring/webclient/boot/GraphQLResponse.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
import com.fasterxml.jackson.databind.JavaType;
77
import com.fasterxml.jackson.databind.JsonNode;
88
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.jayway.jsonpath.Configuration;
10+
import com.jayway.jsonpath.JsonPath;
11+
import com.jayway.jsonpath.JsonPathException;
12+
import com.jayway.jsonpath.ReadContext;
13+
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
14+
915
import java.util.Collections;
1016
import java.util.List;
1117
import java.util.Optional;
@@ -20,6 +26,8 @@ public class GraphQLResponse {
2026
private final List<GraphQLError> errors;
2127
private final ObjectMapper objectMapper;
2228

29+
private ReadContext readContext;
30+
2331
GraphQLResponse(String rawResponse, ObjectMapper objectMapper) {
2432
this.objectMapper = objectMapper;
2533

@@ -58,6 +66,14 @@ public <T> T get(String fieldName, Class<T> type) {
5866
return null;
5967
}
6068

69+
public <T> T getAt(String path, Class<T> type) throws GraphQLResponseReadException {
70+
try {
71+
return getReadContext().read(path, type);
72+
} catch (JsonPathException e) {
73+
throw new GraphQLResponseReadException("Failed to read part of GraphQL response.", e);
74+
}
75+
}
76+
6177
public <T> T getFirst(Class<T> type) {
6278
return getFirstDataEntry().map(it -> objectMapper.convertValue(it, type)).orElse(null);
6379
}
@@ -76,6 +92,10 @@ public <T> List<T> getList(String fieldName, Class<T> type) {
7692
return emptyList();
7793
}
7894

95+
public <T> List<T> getListAt(String path, Class<T> itemType) throws GraphQLResponseReadException {
96+
return objectMapper.convertValue(getAt(path), constructListType(itemType));
97+
}
98+
7999
@SuppressWarnings("unchecked")
80100
public <T> List<T> getFirstList(Class<T> type) {
81101
return getFirstDataEntry()
@@ -84,10 +104,28 @@ public <T> List<T> getFirstList(Class<T> type) {
84104
.orElseGet(Collections::emptyList);
85105
}
86106

107+
public <T> T getAt(String path) throws GraphQLResponseReadException {
108+
try {
109+
return getReadContext().read(path);
110+
} catch (JsonPathException e) {
111+
throw new GraphQLResponseReadException("Failed to read part of GraphQL response.", e);
112+
}
113+
}
114+
87115
public void validateNoErrors() {
88116
if (!errors.isEmpty()) {
89117
throw new GraphQLErrorsException(errors);
90118
}
91119
}
92120

121+
public ReadContext getReadContext() {
122+
if (readContext == null) {
123+
Configuration.builder()
124+
.mappingProvider(new JacksonMappingProvider(objectMapper))
125+
.build();
126+
readContext = JsonPath.parse(data.toString());
127+
}
128+
return readContext;
129+
}
130+
93131
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package graphql.kickstart.spring.webclient.boot;
2+
3+
import lombok.experimental.StandardException;
4+
5+
@StandardException
6+
public class GraphQLResponseReadException extends RuntimeException {
7+
}

graphql-webclient/src/test/java/graphql/kickstart/spring/webclient/boot/GraphQLResponseTest.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.kickstart.spring.webclient.boot;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
45
import static org.junit.jupiter.api.Assertions.assertNull;
56
import static org.junit.jupiter.api.Assertions.assertThrows;
67
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -12,7 +13,11 @@
1213

1314
import com.fasterxml.jackson.databind.JsonNode;
1415
import com.fasterxml.jackson.databind.ObjectMapper;
16+
import com.jayway.jsonpath.PathNotFoundException;
17+
1518
import java.util.List;
19+
import java.util.Map;
20+
1621
import org.junit.jupiter.api.Test;
1722

1823
class GraphQLResponseTest {
@@ -95,4 +100,83 @@ void getList_dataFieldExists_returnsList() {
95100
assertEquals("value", values.get(0));
96101
}
97102

103+
@Test
104+
void getAt_dataFieldExists_returnsValue() {
105+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": \"value\" } } }");
106+
String value = response.getAt("field.innerField", String.class);
107+
assertEquals("value", value);
108+
}
109+
110+
@Test
111+
void getAt_noDataFieldExists_throwsException() {
112+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { } } }");
113+
GraphQLResponseReadException ex = assertThrows(GraphQLResponseReadException.class, () -> response.getAt("field.innerField", String.class));
114+
assertInstanceOf(PathNotFoundException.class, ex.getCause());
115+
}
116+
117+
@Test
118+
void getAt_dataIsNull_returnsNull() {
119+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": null } } }");
120+
assertNull(response.getAt("field.innerField", String.class));
121+
}
122+
123+
@Test
124+
void getListAt_dataFieldExists_returnsList() {
125+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": [\"value\"] } } }");
126+
List<String> values = response.getListAt("field.innerField", String.class);
127+
assertEquals(1, values.size());
128+
assertEquals("value", values.get(0));
129+
}
130+
131+
@Test
132+
void getListAt_dataIsNull_returnsNull() {
133+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": null } } }");
134+
assertNull(response.getListAt("field.innerField", String.class));
135+
}
136+
137+
@Test
138+
void getListAt_noDataFieldExists_throwsException() {
139+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { } } }");
140+
GraphQLResponseReadException ex = assertThrows(GraphQLResponseReadException.class, () -> response.getListAt("field.innerField", String.class));
141+
assertInstanceOf(PathNotFoundException.class, ex.getCause());
142+
}
143+
144+
@Test
145+
void getAtAutoCast_dataFieldExists_returnsMap() {
146+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": { \"blah\": \"value\" } } } }");
147+
Map<String, String> value = response.getAt("field.innerField");
148+
assertEquals(1, value.size());
149+
assertEquals("value", value.get("blah"));
150+
}
151+
152+
@Test
153+
void getAtAutoCast_dataFieldExists_returnsString() {
154+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": \"value\" } } }");
155+
String value = response.getAt("field.innerField");
156+
assertEquals("value", value);
157+
}
158+
159+
@Test
160+
void getAtAutoCast_dataFieldExists_returnsInt() {
161+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": 42 } } }");
162+
Integer value = response.getAt("field.innerField");
163+
assertEquals(42, value);
164+
}
165+
166+
@Test
167+
void getAtAutoCast_dataFieldExists_returnsDouble() {
168+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": 42.5 } } }");
169+
Double value = response.getAt("field.innerField");
170+
assertEquals(42.5, value);
171+
}
172+
173+
@Test
174+
void getAtAutoCast_dataFieldExists_returnsList() {
175+
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": [ \"value\", 42 ] } } }");
176+
List<Object> values = response.getAt("field.innerField");
177+
assertEquals(2, values.size());
178+
assertEquals("value", values.get(0));
179+
assertEquals(42, values.get(1));
180+
}
181+
98182
}

0 commit comments

Comments
 (0)