Configure JSON GraphQlModule in client support

The various GraphQL clients supported in this project automatically detect
JSON codecs for reading/writing GraphQL requests as JSON payloads.
If there is none detected, clients provide a default codec instance.

This commit configures automatically the `GraphQlModule` from gh-1174 in
default codecs and add integration tests for `FieldValue<T>` usage on the
client side.

This also improves the documentation around `FieldValue<T>` for both
server and client side support.

Closes gh-1190
This commit is contained in:
Brian Clozel
2025-04-15 11:44:05 +02:00
parent 1ec45c68f5
commit 70f16c1158
9 changed files with 271 additions and 18 deletions

View File

@@ -393,19 +393,36 @@ include-code::UseInterceptor[tag=register,indent=0]
[[client.argument-value]
== Argument Value
[[client.fieldvalue]]
== `FieldValue`
If you want to use `ArgumentValue` from a client or test, you can register the
`GraphQLModule` in Jackson which can serialize/deserialize the value depending on
the state.
By default, input types in GraphQL are nullable and optional, an input value (or any of its fields)
can be set to the `null` literal, or not provided at all. This distinction is useful for
partial updates with a mutation where the underlying data may also be, either set to
`null` or not changed at all accordingly.
For example:
Similar to the xref:controllers.adoc#controllers.schema-mapping.fieldvalue[`FieldValue<T> support in controllers`],
we can wrap an Input type with `FieldValue<T>` or use it at the level of class attributes on the client side.
Given a `ProjectInput` class like:
include-code::ProjectInput[indent=0]
We can use our client to send a mutation request:
include-code::FieldValueClient[tag=fieldvalue,indent=0]
For this to work, the client must use Jackson for JSON (de)serialization and must be configured
with the `org.springframework.graphql.client.json.GraphQlModule`.
This can be registered manually on the underlying HTTP client like so:
include-code::FieldValueClient[tag=createclient,indent=0]
This `GraphQlModule` can be globally registered in Spring Boot applications by contributing it as a bean:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
public class MyConfiguration {
public class GraphQlJsonConfiguration {
@Bean
public GraphQLModule graphQLModule() {

View File

@@ -403,7 +403,7 @@ specified in the annotation, or to the parameter name. For access to the full ar
map, please use xref:controllers.adoc#controllers.schema-mapping.arguments[`@Arguments`] instead.
[[controllers.schema-mapping.field-value]]
[[controllers.schema-mapping.fieldvalue]]
=== `FieldValue`
By default, input arguments in GraphQL are nullable and optional, which means an argument
@@ -426,13 +426,21 @@ For example:
@Controller
public class BookController {
@MutationMapping
public void addBook(FieldValue<BookInput> bookInput) {
if (!bookInput.isOmitted()) {
BookInput value = bookInput.value();
// ...
@QueryMapping
public List<Book> searchBook(@Argument String search, FieldValue<Genre> genre) {
if (!genre.isOmitted()) {
// genre has been set but might hold a "null" value
Genre genreValue = genre.value();
}
}
@MutationMapping
public void addBook(@Argument BookInput bookInput) {
FieldValue<String> genre = bookInput.genre();
genre.ifPresent(genre -> {
//...
});
}
}
----
@@ -440,6 +448,9 @@ For example:
method parameter, either initialized via a constructor argument or via a setter, including
as a field of an object nested at any level below the top level object.
This is also supported on the client side with a dedicated Jackson Module,
see the xref:client.adoc#client.fieldvalue[`FieldValue` support for clients] section.
[[controllers.schema-mapping.arguments]]
=== `@Arguments`

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.docs.client.fieldvalue;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.graphql.FieldValue;
import org.springframework.graphql.client.ClientGraphQlResponse;
import org.springframework.graphql.client.HttpGraphQlClient;
import org.springframework.graphql.client.json.GraphQlModule;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.reactive.function.client.WebClient;
public class FieldValueClient {
private final HttpGraphQlClient graphQlClient;
// tag::createclient[]
public FieldValueClient(HttpGraphQlClient graphQlClient) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
.modulesToInstall(new GraphQlModule())
.build();
Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON);
WebClient webClient = WebClient.builder()
.baseUrl("https://example.com/graphql")
.codecs((codecs) -> codecs.defaultCodecs().jackson2JsonEncoder(jsonEncoder))
.build();
this.graphQlClient = HttpGraphQlClient.create(webClient);
}
// end::createclient[]
// tag::fieldvalue[]
public void updateProject() {
ProjectInput projectInput = new ProjectInput("spring-graphql",
FieldValue.ofNullable("Spring for GraphQL"));
ClientGraphQlResponse response = this.graphQlClient.document("""
mutation updateProject($project: ProjectInput!) {
updateProject($project: $project) {
id
name
}
}
""")
.variables(Map.of("project", projectInput))
.executeSync();
}
// end::fieldvalue[]
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.docs.client.fieldvalue;
import org.springframework.graphql.FieldValue;
public record ProjectInput(String id, FieldValue<String> name) {
}