Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support extensions parameter #105

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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