Skip to content

Commit

Permalink
feat: added json path features to GraphQLResponse; resolves graphql-j…
Browse files Browse the repository at this point in the history
  • Loading branch information
bsara committed Oct 26, 2023
1 parent 5708496 commit 48f744f
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 0 deletions.
1 change: 1 addition & 0 deletions graphql-webclient/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dependencies {
api "org.springframework.boot:spring-boot-starter-webflux"
implementation "com.jayway.jsonpath:json-path:2.8.0"

testImplementation "org.springframework.boot:spring-boot-starter-webflux"
testImplementation "org.springframework.boot:spring-boot-starter-test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.JsonPathException;
import com.jayway.jsonpath.ReadContext;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
Expand All @@ -20,6 +26,8 @@ public class GraphQLResponse {
private final List<GraphQLError> errors;
private final ObjectMapper objectMapper;

private ReadContext readContext;

GraphQLResponse(String rawResponse, ObjectMapper objectMapper) {
this.objectMapper = objectMapper;

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

public <T> T getAt(String path, Class<T> type) throws GraphQLResponseReadException {
try {
return getReadContext().read(path, type);
} catch (JsonPathException e) {
throw new GraphQLResponseReadException("Failed to read part of GraphQL response.", e);
}
}

public <T> T getFirst(Class<T> type) {
return getFirstDataEntry().map(it -> objectMapper.convertValue(it, type)).orElse(null);
}
Expand All @@ -76,6 +92,10 @@ public <T> List<T> getList(String fieldName, Class<T> type) {
return emptyList();
}

public <T> List<T> getListAt(String path, Class<T> itemType) throws GraphQLResponseReadException {
return objectMapper.convertValue(getAt(path), constructListType(itemType));
}

@SuppressWarnings("unchecked")
public <T> List<T> getFirstList(Class<T> type) {
return getFirstDataEntry()
Expand All @@ -84,10 +104,28 @@ public <T> List<T> getFirstList(Class<T> type) {
.orElseGet(Collections::emptyList);
}

public <T> T getAt(String path) throws GraphQLResponseReadException {
try {
return getReadContext().read(path);
} catch (JsonPathException e) {
throw new GraphQLResponseReadException("Failed to read part of GraphQL response.", e);
}
}

public void validateNoErrors() {
if (!errors.isEmpty()) {
throw new GraphQLErrorsException(errors);
}
}

public ReadContext getReadContext() {
if (readContext == null) {
Configuration.builder()
.mappingProvider(new JacksonMappingProvider(objectMapper))
.build();
readContext = JsonPath.parse(data.toString());
}
return readContext;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package graphql.kickstart.spring.webclient.boot;

import lombok.experimental.StandardException;

@StandardException
public class GraphQLResponseReadException extends RuntimeException {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graphql.kickstart.spring.webclient.boot;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -12,7 +13,11 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.PathNotFoundException;

import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Test;

class GraphQLResponseTest {
Expand Down Expand Up @@ -95,4 +100,83 @@ void getList_dataFieldExists_returnsList() {
assertEquals("value", values.get(0));
}

@Test
void getAt_dataFieldExists_returnsValue() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": \"value\" } } }");
String value = response.getAt("field.innerField", String.class);
assertEquals("value", value);
}

@Test
void getAt_noDataFieldExists_throwsException() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { } } }");
GraphQLResponseReadException ex = assertThrows(GraphQLResponseReadException.class, () -> response.getAt("field.innerField", String.class));
assertInstanceOf(PathNotFoundException.class, ex.getCause());
}

@Test
void getAt_dataIsNull_returnsNull() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": null } } }");
assertNull(response.getAt("field.innerField", String.class));
}

@Test
void getListAt_dataFieldExists_returnsList() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": [\"value\"] } } }");
List<String> values = response.getListAt("field.innerField", String.class);
assertEquals(1, values.size());
assertEquals("value", values.get(0));
}

@Test
void getListAt_dataIsNull_returnsNull() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": null } } }");
assertNull(response.getListAt("field.innerField", String.class));
}

@Test
void getListAt_noDataFieldExists_throwsException() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { } } }");
GraphQLResponseReadException ex = assertThrows(GraphQLResponseReadException.class, () -> response.getListAt("field.innerField", String.class));
assertInstanceOf(PathNotFoundException.class, ex.getCause());
}

@Test
void getAtAutoCast_dataFieldExists_returnsMap() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": { \"blah\": \"value\" } } } }");
Map<String, String> value = response.getAt("field.innerField");
assertEquals(1, value.size());
assertEquals("value", value.get("blah"));
}

@Test
void getAtAutoCast_dataFieldExists_returnsString() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": \"value\" } } }");
String value = response.getAt("field.innerField");
assertEquals("value", value);
}

@Test
void getAtAutoCast_dataFieldExists_returnsInt() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": 42 } } }");
Integer value = response.getAt("field.innerField");
assertEquals(42, value);
}

@Test
void getAtAutoCast_dataFieldExists_returnsDouble() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": 42.5 } } }");
Double value = response.getAt("field.innerField");
assertEquals(42.5, value);
}

@Test
void getAtAutoCast_dataFieldExists_returnsList() {
GraphQLResponse response = constructResponse("{ \"data\": { \"field\": { \"innerField\": [ \"value\", 42 ] } } }");
List<Object> values = response.getAt("field.innerField");
assertEquals(2, values.size());
assertEquals("value", values.get(0));
assertEquals(42, values.get(1));
}

}

0 comments on commit 48f744f

Please sign in to comment.