Skip to content

Commit

Permalink
feat: support extensions parameter
Browse files Browse the repository at this point in the history
resolve #104
  • Loading branch information
aoudiamoncef committed May 25, 2021
1 parent 85fd2a8 commit 65a623f
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.leangen.graphql.spqr.spring.autoconfigure;

import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import io.leangen.graphql.spqr.spring.web.GraphQLController;
Expand Down Expand Up @@ -43,8 +44,8 @@ public GraphQLMvcExecutor defaultExecutor(MvcContextFactory contextFactory, Spqr
@ConditionalOnProperty(name = "graphql.spqr.http.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnMissingBean(GraphQLController.class)
@ConditionalOnBean(GraphQLSchema.class)
public DefaultGraphQLController graphQLController(GraphQL graphQL, GraphQLMvcExecutor executor) {
return new DefaultGraphQLController(graphQL, executor);
public DefaultGraphQLController graphQLController(GraphQL graphQL, GraphQLMvcExecutor executor, ObjectMapper objectMapper) {
return new DefaultGraphQLController(graphQL, executor, objectMapper);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.leangen.graphql.spqr.spring.autoconfigure;

import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import io.leangen.graphql.module.Module;
Expand Down Expand Up @@ -47,8 +48,8 @@ public GraphQLReactiveExecutor graphQLExecutor(ReactiveContextFactory contextFac
@ConditionalOnProperty(name = "graphql.spqr.http.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnMissingBean(GraphQLController.class)
@ConditionalOnBean(GraphQLSchema.class)
public DefaultGraphQLController graphQLController(GraphQL graphQL, GraphQLReactiveExecutor executor) {
return new DefaultGraphQLController(graphQL, executor);
public DefaultGraphQLController graphQLController(GraphQL graphQL, GraphQLReactiveExecutor executor, ObjectMapper objectMapper) {
return new DefaultGraphQLController(graphQL, executor,objectMapper);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
package io.leangen.graphql.spqr.spring.web;

import graphql.GraphQL;
import io.leangen.graphql.spqr.spring.web.dto.ExecutorParams;
import io.leangen.graphql.spqr.spring.web.dto.GraphQLRequest;
import io.leangen.graphql.spqr.spring.web.dto.TransportType;
import io.leangen.graphql.util.Utils;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import graphql.GraphQL;
import io.leangen.graphql.spqr.spring.web.dto.ExecutorParams;
import io.leangen.graphql.spqr.spring.web.dto.GraphQLRequest;
import io.leangen.graphql.spqr.spring.web.dto.TransportType;
import io.leangen.graphql.util.Utils;

@RestController
public abstract class GraphQLController<R> {

protected final GraphQL graphQL;
protected final GraphQLExecutor<R> executor;
protected final ObjectMapper objectMapper;

public GraphQLController(GraphQL graphQL, GraphQLExecutor<R> executor) {
public GraphQLController(GraphQL graphQL, GraphQLExecutor<R> executor, ObjectMapper objectMapper) {
this.graphQL = graphQL;
this.executor = executor;
this.objectMapper = objectMapper;
}

@PostMapping(
Expand Down Expand Up @@ -53,7 +59,8 @@ public Object jsonPost(GraphQLRequest requestBody, GraphQLRequest requestParams,
String query = Utils.isNotEmpty(requestParams.getQuery()) ? requestParams.getQuery() : requestBody.getQuery();
String operationName = Utils.isNotEmpty(requestParams.getOperationName()) ? requestParams.getOperationName() : requestBody.getOperationName();
Map<String, Object> variables = requestParams.getVariables().isEmpty() ? requestBody.getVariables() : requestParams.getVariables();
ExecutorParams<R> params = new ExecutorParams<>(new GraphQLRequest(id, query, operationName, variables), request, transportType);
Map<String, Object> extensions = requestParams.getExtensions().isEmpty() ? requestBody.getExtensions() : requestParams.getExtensions();
ExecutorParams<R> params = new ExecutorParams<>(new GraphQLRequest(id, query, operationName, variables, extensions), request, transportType);
return executor.execute(graphQL, params);
}

Expand All @@ -66,13 +73,12 @@ public Object executeGraphQLPost(@RequestBody String queryBody,
GraphQLRequest originalReq,
R request) {
String query = Utils.isNotEmpty(originalReq.getQuery()) ? originalReq.getQuery() : queryBody;
GraphQLRequest remappedReq = new GraphQLRequest(originalReq.getId(), query, originalReq.getOperationName(), originalReq.getVariables());
GraphQLRequest remappedReq = new GraphQLRequest(originalReq.getId(), query, originalReq.getOperationName(), originalReq.getVariables(), originalReq.getExtensions());
ExecutorParams<R> params = new ExecutorParams<>(remappedReq, request, TransportType.HTTP);
return executor.execute(graphQL, params);
}

@RequestMapping(
method = RequestMethod.POST,
@PostMapping(
value = "${graphql.spqr.http.endpoint:/graphql}",
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, "application/x-www-form-urlencoded;charset=UTF-8"},
produces = MediaType.APPLICATION_JSON_VALUE
Expand All @@ -88,7 +94,7 @@ public Object executeFormPost(@RequestParam Map<String, String> queryParams,
String id = Utils.isNotEmpty(idParam) ? idParam : graphQLRequest.getId();
String query = Utils.isNotEmpty(queryParam) ? queryParam : graphQLRequest.getQuery();
String operationName = Utils.isEmpty(operationNameParam) ? graphQLRequest.getOperationName() : operationNameParam;
ExecutorParams<R> params = new ExecutorParams<>(new GraphQLRequest(id, query, operationName, graphQLRequest.getVariables()), request, TransportType.HTTP);
ExecutorParams<R> params = new ExecutorParams<>(new GraphQLRequest(id, query, operationName, graphQLRequest.getVariables(), graphQLRequest.getExtensions()), request, TransportType.HTTP);

return executor.execute(graphQL, params);
}
Expand All @@ -98,20 +104,41 @@ public Object executeFormPost(@RequestParam Map<String, String> queryParams,
produces = MediaType.APPLICATION_JSON_VALUE,
headers = { "Connection!=Upgrade", "Connection!=keep-alive, Upgrade" }
)
public Object executeGet(GraphQLRequest graphQLRequest, R request) {
return get(graphQLRequest, request, TransportType.HTTP);
public Object executeGet(String id,
String query,
String operationName,
String variables,
String extensions,
R request) {
return get(new GraphQLRequest(id, query, operationName, parseAsMap(variables), parseAsMap(extensions)), request, TransportType.HTTP);
}

private Object get(GraphQLRequest graphQLRequest, R request, TransportType transportType) {
return executor.execute(graphQL, new ExecutorParams<>(graphQLRequest, request, transportType));
}

@GetMapping(
value = "${graphql.spqr.http.endpoint:/graphql}",
produces = MediaType.TEXT_EVENT_STREAM_VALUE,
headers = { "Connection!=Upgrade", "Connection!=keep-alive, Upgrade" }
)
public Object executeGetEventStream(GraphQLRequest graphQLRequest, R request) {
return get(graphQLRequest, request, TransportType.HTTP_EVENT_STREAM);
public Object executeGetEventStream(String id,
String query,
String operationName,
String variables,
String extensions,
R request) {
return get(new GraphQLRequest(id, query, operationName, parseAsMap(variables), parseAsMap(extensions)), request, TransportType.HTTP_EVENT_STREAM);
}

private Object get(GraphQLRequest graphQLRequest, R request, TransportType transportType) {
return executor.execute(graphQL, new ExecutorParams<>(graphQLRequest, request, transportType));
private Map<String, Object> parseAsMap(String str) {
if (str == null || str.trim().isEmpty()) {
return Collections.emptyMap();
}
try {
return objectMapper.readValue(str, new TypeReference<Map<String, Object>>() {});
} catch (IOException e) {
throw new IllegalArgumentException("failed to parse: " + str);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ public class GraphQLRequest {
private final String query;
private final String operationName;
private final Map<String, Object> variables;
private final Map<String, Object> extensions;

@JsonCreator
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public GraphQLRequest(@JsonProperty("id") String id,
@JsonProperty("query") String query,
@JsonProperty("operationName") String operationName,
@JsonProperty("variables") Map<String, Object> variables) {
@JsonProperty("variables") Map<String, Object> variables,
@JsonProperty("extensions") Map<String, Object> extensions) {
this.id = id;
this.query = query;
this.query = query == null && extensions != null ? "" : query;
this.operationName = operationName;
this.variables = variables != null ? variables : Collections.emptyMap();
this.extensions = extensions != null ? extensions : Collections.emptyMap();
}

public String getId() {
Expand All @@ -41,4 +44,7 @@ public String getOperationName() {
public Map<String, Object> getVariables() {
return variables;
}

public Map<String, Object> getExtensions() { return extensions; }

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.leangen.graphql.spqr.spring.web.mvc;

import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.GraphQL;
import io.leangen.graphql.spqr.spring.web.GraphQLController;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -12,7 +13,7 @@
public class DefaultGraphQLController extends GraphQLController<NativeWebRequest> {

@Autowired
public DefaultGraphQLController(GraphQL graphQL, GraphQLMvcExecutor executor) {
super(graphQL, executor);
public DefaultGraphQLController(GraphQL graphQL, GraphQLMvcExecutor executor, ObjectMapper objectMapper) {
super(graphQL, executor, objectMapper);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.leangen.graphql.spqr.spring.web.reactive;

import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.GraphQL;
import io.leangen.graphql.spqr.spring.web.GraphQLController;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -12,7 +13,7 @@
public class DefaultGraphQLController extends GraphQLController<ServerWebExchange> {

@Autowired
public DefaultGraphQLController(GraphQL graphQL, GraphQLReactiveExecutor executor) {
super(graphQL, executor);
public DefaultGraphQLController(GraphQL graphQL, GraphQLReactiveExecutor executor, ObjectMapper objectMapper) {
super(graphQL, executor, objectMapper);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package io.leangen.graphql.spqr.spring.web;

import io.leangen.graphql.spqr.spring.autoconfigure.BaseAutoConfiguration;
import io.leangen.graphql.spqr.spring.autoconfigure.MvcAutoConfiguration;
import io.leangen.graphql.spqr.spring.autoconfigure.SpringDataAutoConfiguration;
import io.leangen.graphql.spqr.spring.test.ResolverBuilder_TestConfig;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalToCompressingWhiteSpace;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -15,15 +21,10 @@
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalToCompressingWhiteSpace;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.leangen.graphql.spqr.spring.autoconfigure.BaseAutoConfiguration;
import io.leangen.graphql.spqr.spring.autoconfigure.MvcAutoConfiguration;
import io.leangen.graphql.spqr.spring.autoconfigure.SpringDataAutoConfiguration;
import io.leangen.graphql.spqr.spring.test.ResolverBuilder_TestConfig;

@RunWith(SpringRunner.class)
@WebMvcTest
Expand All @@ -48,12 +49,22 @@ public void defaultControllerTest_POST_applicationGraphql_noQueryParams() throws
.andExpect(content().string(containsString("Hello world")));
}

@Test
public void defaultControllerTest_POST_applicationJson_persistedQuery() throws Exception {
mockMvc.perform(
post("/"+apiContext)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"variables\":null,\"operationName\":null,\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"fcf31818e50ac3e818ca4bdbc433d6ab73176f0b9d5f9d5ad17e200cdab6fba4\"}}}"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Invalid Syntax : offending token '<EOF>' at line 1 column 1")));
}

@Test
public void defaultControllerTest_POST_applicationJson_noQueryParams() throws Exception {
mockMvc.perform(
post("/"+apiContext)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"query\":\"{greetingFromBeanSource_wiredAsComponent_byAnnotation}\",\"variables\":null,\"operationName\":null}"))
.content("{\"query\":\"{greetingFromBeanSource_wiredAsComponent_byAnnotation}\",\"variables\":null,\"operationName\":null,\"extensions\":null}"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello world")));
}
Expand All @@ -77,6 +88,15 @@ public void defaultControllerTest_GET() throws Exception {
.andExpect(content().string(containsString("Hello world")));
}

@Test
public void defaultControllerTest_GET_persistedQuery() throws Exception {
mockMvc.perform(
get("/"+apiContext)
.param("extensions", "{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"4b758938f2d00323147290e3b0d041e6a0952e2c694ab2c0ea7212ca08f337b3\"}}"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Invalid Syntax : offending token '<EOF>' at line 1 column 1")));
}

@Test
public void defaultControllerTest_POST_applicationGraphql_INVALID() throws Exception {
mockMvc.perform(
Expand All @@ -103,7 +123,7 @@ public void defaultControllerTest_POST_applicationJson_INVALID() throws Exceptio
mockMvc.perform(
post("/"+apiContext)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"query\":\"{INVALID_QUERY}\",\"variables\":null,\"operationName\":null}"))
.content("{\"query\":\"{INVALID_QUERY}\",\"variables\":null,\"operationName\":null,\"extensions\":null}"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("FieldUndefined: Field 'INVALID_QUERY'")));
}
Expand All @@ -114,7 +134,7 @@ public void defaultControllerTest_POST_applicationJson_overridingQueryParams() t
post("/"+apiContext)
.param("query","{greetingFromBeanSource_wiredAsComponent_byAnnotation}")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"query\":\"{INVALID_QUERY}\",\"variables\":null,\"operationName\":null}"))
.content("{\"query\":\"{INVALID_QUERY}\",\"variables\":null,\"operationName\":null,\"extensions\":null}"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello world")));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.springframework.web.reactive.function.BodyInserters;

import java.net.URI;
import java.util.Map;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -44,6 +45,34 @@ public void setUp() {
webTestClient = WebTestClient.bindToApplicationContext(context).build();
}

@Test
public void defaultControllerTest_GET_mono() {
webTestClient.get()
.uri(uriBuilder -> uriBuilder
.path("/" + apiContext)
.queryParam("query", "{query}")
.build("{greetingFromAnnotatedSourceReactive_mono}"))
.accept(MediaType.TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.expectBody(String.class)
.consumeWith(c -> assertThat("", c.getResponseBody(), containsString("Hello world !")));
}

@Test
public void defaultControllerTest_GET_persistedQuery_mono() {
webTestClient.get()
.uri(uriBuilder -> uriBuilder
.path("/" + apiContext)
.queryParam("extensions", "{persisted}")
.build("{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"4b758938f2d00323147290e3b0d041e6a0952e2c694ab2c0ea7212ca08f337b3\"}}"))
.accept(MediaType.TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.expectBody(String.class)
.consumeWith(c -> assertThat("", c.getResponseBody(), containsString("Invalid Syntax : offending token '<EOF>' at line 1 column 1\"")));
}

@Test
public void defaultControllerTest_POST_formUrlEncoded_mono() {
LinkedMultiValueMap<String, String> body = new LinkedMultiValueMap<>();
Expand Down

0 comments on commit 65a623f

Please sign in to comment.