Revert ArgumentValue->FieldValue changes
See gh-1187 See gh-1190 See gh-1174
This commit is contained in:
@@ -393,47 +393,6 @@ include-code::UseInterceptor[tag=register,indent=0]
|
||||
|
||||
|
||||
|
||||
[[client.fieldvalue]]
|
||||
== `FieldValue`
|
||||
|
||||
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.
|
||||
|
||||
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 GraphQlJsonConfiguration {
|
||||
|
||||
@Bean
|
||||
public GraphQLModule graphQLModule() {
|
||||
return new GraphQLModule();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[client.dgsgraphqlclient]]
|
||||
== DGS Codegen
|
||||
|
||||
|
||||
@@ -143,11 +143,11 @@ See xref:controllers.adoc#controllers.schema-mapping.argument[`@Argument`].
|
||||
|
||||
See xref:controllers.adoc#controllers.schema-mapping.argument[`@Argument`].
|
||||
|
||||
| `FieldValue`
|
||||
| `ArgumentValue`
|
||||
| For access to a named field argument bound to a higher-level, typed Object along
|
||||
with a flag to indicate if the input argument was omitted vs set to `null`.
|
||||
|
||||
See xref:controllers.adoc#controllers.schema-mapping.field-value[`FieldValue`].
|
||||
See xref:controllers.adoc#controllers.schema-mapping.argument-value[`ArgumentValue`].
|
||||
|
||||
| `@Arguments`
|
||||
| For access to all field arguments bound to a higher-level, typed Object.
|
||||
@@ -403,8 +403,8 @@ 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.fieldvalue]]
|
||||
=== `FieldValue`
|
||||
[[controllers.schema-mapping.argument-value]]
|
||||
=== `ArgumentValue`
|
||||
|
||||
By default, input arguments in GraphQL are nullable and optional, which means an argument
|
||||
can be set to the `null` literal, or not provided at all. This distinction is useful for
|
||||
@@ -414,7 +414,7 @@ there is no way to make such a distinction, because you would get `null` or an e
|
||||
`Optional` in both cases.
|
||||
|
||||
If you want to know not whether a value was not provided at all, you can declare an
|
||||
`FieldValue` method parameter, which is a simple container for the resulting value,
|
||||
`ArgumentValue` method parameter, which is a simple container for the resulting value,
|
||||
along with a flag to indicate whether the input argument was omitted altogether. You
|
||||
can use this instead of `@Argument`, in which case the argument name is determined from
|
||||
the method parameter name, or together with `@Argument` to specify the argument name.
|
||||
@@ -426,31 +426,20 @@ For example:
|
||||
@Controller
|
||||
public class BookController {
|
||||
|
||||
@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 -> {
|
||||
//...
|
||||
});
|
||||
public void addBook(ArgumentValue<BookInput> bookInput) {
|
||||
if (!bookInput.isOmitted()) {
|
||||
BookInput value = bookInput.value();
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
`FieldValue` is also supported as a field within the object structure of an `@Argument`
|
||||
`ArgumentValue` is also supported as a field within the object structure of an `@Argument`
|
||||
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`
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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[]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Simple container for GraphQL field values that indicates if the field
|
||||
* value is present, provided but set to {@literal "null"} or omitted altogether.
|
||||
*
|
||||
* <p>In the case of GraphQL mutations, clients send an Input Object with several fields;
|
||||
* the server must understand the difference between a field being absent
|
||||
* (the existing value should be left as-is) and a field set to {@literal "null"}
|
||||
* (the existing value must be set to {@literal "null"}). {@code FieldValue<T>}
|
||||
* helps to make this distinction.
|
||||
*
|
||||
* <p>Supported in one of the following places:
|
||||
* <ul>
|
||||
* <li>On a controller method parameter, either instead of
|
||||
* {@link org.springframework.graphql.data.method.annotation.Argument @Argument}
|
||||
* in which case the argument name is determined from the method parameter name,
|
||||
* or together with {@code @Argument} to specify the argument name.
|
||||
* <li>As a field within the object structure of an {@code @Argument} method
|
||||
* parameter, either initialized via a constructor argument or a setter,
|
||||
* including as a field of an object nested at any level below the top level
|
||||
* object.
|
||||
* </ul>
|
||||
*
|
||||
* @param <T> the type of value contained
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
* @since 1.4.0
|
||||
* @see <a href="http://spec.graphql.org/October2021/#sec-Input-Objects">Input Object</a>
|
||||
* @see <a href="http://spec.graphql.org/October2021/#sec-Non-Null.Nullable-vs-Optional">Nullable vs Optional</a>
|
||||
*/
|
||||
public final class FieldValue<T> {
|
||||
|
||||
private static final FieldValue<?> EMPTY = new FieldValue<>(null, false);
|
||||
|
||||
private static final FieldValue<?> OMITTED = new FieldValue<>(null, true);
|
||||
|
||||
|
||||
@Nullable
|
||||
private final T value;
|
||||
|
||||
private final boolean omitted;
|
||||
|
||||
|
||||
private FieldValue(@Nullable T value, boolean omitted) {
|
||||
this.value = value;
|
||||
this.omitted = omitted;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return {@code true} if a non-null value is present, and {@code false} otherwise.
|
||||
*/
|
||||
public boolean isPresent() {
|
||||
return (this.value != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the input value was present in the input but the value was {@code null},
|
||||
* and {@code false} otherwise.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return !this.omitted && this.value == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the input value was omitted altogether from the
|
||||
* input, and {@code false} if it was provided, but possibly set to the
|
||||
* {@literal "null"} literal.
|
||||
*/
|
||||
public boolean isOmitted() {
|
||||
return this.omitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contained value, or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
public T value() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contained value as a nullable {@link Optional}.
|
||||
*/
|
||||
public Optional<T> asOptional() {
|
||||
return Optional.ofNullable(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a value is present, performs the given action with the value, otherwise does nothing.
|
||||
* @param action the action to be performed, if a value is present
|
||||
*/
|
||||
public void ifPresent(Consumer<? super T> action) {
|
||||
Assert.notNull(action, "Action is required");
|
||||
if (this.value != null) {
|
||||
action.accept(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// This covers EMPTY and OMITTED constant
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof FieldValue<?> otherValue)) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.value, otherValue.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(this.value);
|
||||
result = 31 * result + Boolean.hashCode(this.omitted);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.isOmitted()) {
|
||||
return "FieldValue{omitted}";
|
||||
}
|
||||
return "FieldValue{value=" + this.value + "'}'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method for an argument value that was provided, even if
|
||||
* it was set to {@literal "null}.
|
||||
* @param <T> the type of value
|
||||
* @param value the value to hold in the instance
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> FieldValue<T> ofNullable(@Nullable T value) {
|
||||
return (value != null) ? new FieldValue<>(value, false) : (FieldValue<T>) EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method for an argument value that was omitted.
|
||||
* @param <T> the type of value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> FieldValue<T> omitted() {
|
||||
return (FieldValue<T>) OMITTED;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -23,22 +23,16 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.graphql.MediaTypes;
|
||||
import org.springframework.graphql.client.GraphQlClientInterceptor.Chain;
|
||||
import org.springframework.graphql.client.GraphQlClientInterceptor.SubscriptionChain;
|
||||
import org.springframework.graphql.client.json.GraphQlModule;
|
||||
import org.springframework.graphql.support.CachingDocumentSource;
|
||||
import org.springframework.graphql.support.DocumentSource;
|
||||
import org.springframework.graphql.support.ResourceDocumentSource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -241,15 +235,12 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
|
||||
|
||||
protected static class DefaultJackson2Codecs {
|
||||
|
||||
private static final ObjectMapper JSON_MAPPER = Jackson2ObjectMapperBuilder.json()
|
||||
.modulesToInstall(new GraphQlModule()).build();
|
||||
|
||||
static Encoder<?> encoder() {
|
||||
return new Jackson2JsonEncoder(JSON_MAPPER, MediaType.APPLICATION_JSON);
|
||||
return new Jackson2JsonEncoder();
|
||||
}
|
||||
|
||||
static Decoder<?> decoder() {
|
||||
return new Jackson2JsonDecoder(JSON_MAPPER, MediaType.APPLICATION_JSON, MediaTypes.APPLICATION_GRAPHQL_RESPONSE);
|
||||
return new Jackson2JsonDecoder();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
@@ -31,12 +30,10 @@ import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.graphql.GraphQlResponse;
|
||||
import org.springframework.graphql.client.SyncGraphQlClientInterceptor.Chain;
|
||||
import org.springframework.graphql.client.json.GraphQlModule;
|
||||
import org.springframework.graphql.support.CachingDocumentSource;
|
||||
import org.springframework.graphql.support.DocumentSource;
|
||||
import org.springframework.graphql.support.ResourceDocumentSource;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -194,9 +191,7 @@ public abstract class AbstractGraphQlClientSyncBuilder<B extends AbstractGraphQl
|
||||
private static final class DefaultJacksonConverter {
|
||||
|
||||
static HttpMessageConverter<Object> initialize() {
|
||||
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
|
||||
.modulesToInstall(new GraphQlModule()).build();
|
||||
return new MappingJackson2HttpMessageConverter(objectMapper);
|
||||
return new MappingJackson2HttpMessageConverter();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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.client.json;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
|
||||
import com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ReferenceTypeSerializer} that deserializes JSON values as {@link FieldValue}:
|
||||
* <ul>
|
||||
* <li>a {@link FieldValue#isEmpty() non empty FieldValue} when the JSON key is present and its value is not {@literal null}.
|
||||
* <li>an {@link FieldValue#isEmpty() empty FieldValue} when the JSON key is present and its value is {@literal null}.
|
||||
* <li>an {@link FieldValue#isOmitted() ommitted FieldValue} when the JSON key is not present.
|
||||
* </ul>
|
||||
* @author James Bodkin
|
||||
*/
|
||||
class FieldValueDeserializer extends ReferenceTypeDeserializer<FieldValue<?>> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
FieldValueDeserializer(final JavaType fullType, @Nullable final ValueInstantiator vi,
|
||||
final TypeDeserializer typeDeser, final JsonDeserializer<?> deser) {
|
||||
|
||||
super(fullType, vi, typeDeser, deser);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReferenceTypeDeserializer<FieldValue<?>> withResolved(final TypeDeserializer typeDeser,
|
||||
final JsonDeserializer<?> valueDeser) {
|
||||
|
||||
return new FieldValueDeserializer(_fullType, _valueInstantiator, typeDeser, valueDeser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldValue<? extends Serializable> getNullValue(final DeserializationContext ctxt) throws JsonMappingException {
|
||||
return FieldValue.ofNullable((Serializable) _valueDeserializer.getNullValue(ctxt));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getEmptyValue(final DeserializationContext ctxt) throws JsonMappingException {
|
||||
return getNullValue(ctxt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAbsentValue(final DeserializationContext ctxt) {
|
||||
return FieldValue.omitted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldValue<?> referenceValue(final Object contents) {
|
||||
return FieldValue.ofNullable((Serializable) contents);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getReferenced(final FieldValue<?> value) {
|
||||
return value.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldValue<?> updateReference(final FieldValue<?> value, final Object contents) {
|
||||
return FieldValue.ofNullable((Serializable) contents);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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.client.json;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer;
|
||||
import com.fasterxml.jackson.databind.type.ReferenceType;
|
||||
import com.fasterxml.jackson.databind.util.NameTransformer;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ReferenceTypeSerializer} that serializes {@link FieldValue} values as:
|
||||
* <ul>
|
||||
* <li>the embedded value if it is present and not {@literal null}.
|
||||
* <li>{@literal null} if the embedded value is present and {@literal null}.
|
||||
* <li>an empty value if the embedded value is not present.
|
||||
* </ul>
|
||||
* @author James Bodkin
|
||||
*/
|
||||
class FieldValueSerializer extends ReferenceTypeSerializer<FieldValue<?>> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
FieldValueSerializer(final ReferenceType fullType, final boolean staticTyping,
|
||||
@Nullable final TypeSerializer vts, final JsonSerializer<Object> ser) {
|
||||
|
||||
super(fullType, staticTyping, vts, ser);
|
||||
}
|
||||
|
||||
protected FieldValueSerializer(final FieldValueSerializer base, final BeanProperty property,
|
||||
final TypeSerializer vts, final JsonSerializer<?> valueSer,
|
||||
final NameTransformer unwrapper, final Object suppressableValue,
|
||||
final boolean suppressNulls) {
|
||||
|
||||
super(base, property, vts, valueSer, unwrapper, suppressableValue, suppressNulls);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReferenceTypeSerializer<FieldValue<?>> withResolved(final BeanProperty prop, final TypeSerializer vts,
|
||||
final JsonSerializer<?> valueSer, final NameTransformer unwrapper) {
|
||||
|
||||
return new FieldValueSerializer(this, prop, vts, valueSer, unwrapper, _suppressableValue, _suppressNulls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReferenceTypeSerializer<FieldValue<?>> withContentInclusion(final Object suppressableValue, final boolean suppressNulls) {
|
||||
return new FieldValueSerializer(this, _property, _valueTypeSerializer,
|
||||
_valueSerializer, _unwrapper, suppressableValue, suppressNulls);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean _isValuePresent(final FieldValue<?> value) {
|
||||
return !value.isOmitted();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Object _getReferenced(final FieldValue<?> value) {
|
||||
return value.value();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Object _getReferencedIfPresent(final FieldValue<?> value) {
|
||||
return value.value();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.client.json;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.type.ReferenceType;
|
||||
import com.fasterxml.jackson.databind.type.TypeBindings;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import com.fasterxml.jackson.databind.type.TypeModifier;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
|
||||
/**
|
||||
* {@link TypeModifier} that upgrades {@link FieldValue} types to {@link ReferenceType}.
|
||||
*
|
||||
* @author James Bodkin
|
||||
*/
|
||||
class FieldValueTypeModifier extends TypeModifier {
|
||||
|
||||
@Override
|
||||
public JavaType modifyType(final JavaType type, final Type jdkType, final TypeBindings context, final TypeFactory typeFactory) {
|
||||
Class<?> raw = type.getRawClass();
|
||||
if (!type.isReferenceType() && !type.isContainerType() && raw == FieldValue.class) {
|
||||
JavaType refType = type.containedTypeOrUnknown(0);
|
||||
return ReferenceType.upgradeFrom(type, refType);
|
||||
}
|
||||
else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* 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.client.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.Deserializers;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
|
||||
import com.fasterxml.jackson.databind.type.ReferenceType;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
class GraphQlDeserializers extends Deserializers.Base {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JsonDeserializer<?> findReferenceDeserializer(final ReferenceType refType, final DeserializationConfig config,
|
||||
final BeanDescription beanDesc, final TypeDeserializer contentTypeDeserializer,
|
||||
final JsonDeserializer<?> contentDeserializer) {
|
||||
|
||||
if (refType.hasRawClass(FieldValue.class)) {
|
||||
return new FieldValueDeserializer(refType, null, contentTypeDeserializer, contentDeserializer);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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.client.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.type.ReferenceType;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
|
||||
/**
|
||||
* {@link Module Jackson module} for JSON support in GraphQL clients.
|
||||
* <p>This module ships with the following features:
|
||||
* <ul>
|
||||
* <li>Manage {@link FieldValue} types as {@link ReferenceType}, similar to {@link java.util.Optional}
|
||||
* <li>Serializing and Deserializing values contained in {@link FieldValue} reference types
|
||||
* </ul>
|
||||
* @author James Bodkin
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class GraphQlModule extends Module {
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return GraphQlModule.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version version() {
|
||||
return Version.unknownVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupModule(final SetupContext context) {
|
||||
context.addSerializers(new GraphQlSerializers());
|
||||
context.addDeserializers(new GraphQlDeserializers());
|
||||
context.addTypeModifier(new FieldValueTypeModifier());
|
||||
|
||||
context.configOverride(FieldValue.class)
|
||||
.setInclude(JsonInclude.Value.empty().withValueInclusion(JsonInclude.Include.NON_ABSENT));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.client.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.Serializers;
|
||||
import com.fasterxml.jackson.databind.type.ReferenceType;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
class GraphQlSerializers extends Serializers.Base {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JsonSerializer<?> findReferenceSerializer(final SerializationConfig config, final ReferenceType refType,
|
||||
final BeanDescription beanDesc, @Nullable final TypeSerializer contentTypeSerializer,
|
||||
final JsonSerializer<Object> contentValueSerializer) {
|
||||
|
||||
Class<?> raw = refType.getRawClass();
|
||||
if (FieldValue.class.isAssignableFrom(raw)) {
|
||||
boolean staticTyping = contentTypeSerializer == null && config.isEnabled(MapperFeature.USE_STATIC_TYPING);
|
||||
return new FieldValueSerializer(refType, staticTyping, contentTypeSerializer, contentValueSerializer);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package contains JSON support for GraphQL clients.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.graphql.client.json;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
@@ -18,8 +18,10 @@ package org.springframework.graphql.data;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
@@ -44,9 +46,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.1.0
|
||||
* @see <a href="http://spec.graphql.org/October2021/#sec-Non-Null.Nullable-vs-Optional">Nullable vs Optional</a>
|
||||
* @deprecated since 1.4.0 in favor of {@link org.springframework.graphql.FieldValue}.
|
||||
*/
|
||||
@Deprecated(since = "1.4.0", forRemoval = true)
|
||||
public final class ArgumentValue<T> {
|
||||
|
||||
private static final ArgumentValue<?> EMPTY = new ArgumentValue<>(null, false);
|
||||
@@ -73,6 +73,15 @@ public final class ArgumentValue<T> {
|
||||
return (this.value != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the input value was present in the input but the value was {@code null},
|
||||
* and {@code false} otherwise.
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return !this.omitted && this.value == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the input value was omitted altogether from the
|
||||
* input, and {@code false} if it was provided, but possibly set to the
|
||||
@@ -97,6 +106,18 @@ public final class ArgumentValue<T> {
|
||||
return Optional.ofNullable(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a value is present, performs the given action with the value, otherwise does nothing.
|
||||
* @param action the action to be performed, if a value is present
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public void ifPresent(Consumer<? super T> action) {
|
||||
Assert.notNull(action, "Action is required");
|
||||
if (this.value != null) {
|
||||
action.accept(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// This covers EMPTY and OMITTED constant
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -68,15 +67,13 @@ import org.springframework.validation.FieldError;
|
||||
*
|
||||
* <p>The binder supports {@link Optional} as a wrapper around any Object or
|
||||
* scalar value in the target Object structure. In addition, it also supports
|
||||
* {@link org.springframework.graphql.FieldValue} as a wrapper that indicates
|
||||
* whether a given input argument was omitted rather than set to the
|
||||
* {@literal "null"} literal.
|
||||
* {@link ArgumentValue} as a wrapper that indicates whether a given input
|
||||
* argument was omitted rather than set to the {@literal "null"} literal.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class GraphQlArgumentBinder {
|
||||
|
||||
@Nullable
|
||||
@@ -118,7 +115,7 @@ public class GraphQlArgumentBinder {
|
||||
* @param name the name of an argument, or {@code null} to use the full map
|
||||
* @param targetType the type of Object to create
|
||||
* @return the created Object, possibly wrapped in {@link Optional} or in
|
||||
* {@link org.springframework.graphql.FieldValue}, or {@code null} if there is no value
|
||||
* {@link ArgumentValue}, or {@code null} if there is no value
|
||||
* @throws BindException containing one or more accumulated errors from
|
||||
* matching and/or converting arguments to the target Object
|
||||
*/
|
||||
@@ -178,9 +175,8 @@ public class GraphQlArgumentBinder {
|
||||
|
||||
boolean isOptional = (targetClass == Optional.class);
|
||||
boolean isArgumentValue = (targetClass == ArgumentValue.class);
|
||||
boolean isFieldValue = (targetClass == FieldValue.class);
|
||||
|
||||
if (isOptional || isArgumentValue || isFieldValue) {
|
||||
if (isOptional || isArgumentValue) {
|
||||
targetType = targetType.getNested(2);
|
||||
targetClass = targetType.resolve();
|
||||
}
|
||||
@@ -206,9 +202,6 @@ public class GraphQlArgumentBinder {
|
||||
else if (isArgumentValue) {
|
||||
value = (isOmitted ? ArgumentValue.omitted() : ArgumentValue.ofNullable(value));
|
||||
}
|
||||
else if (isFieldValue) {
|
||||
value = (isOmitted ? FieldValue.omitted() : FieldValue.ofNullable(value));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.GraphQlArgumentBinder;
|
||||
import org.springframework.graphql.data.method.HandlerMethod;
|
||||
@@ -489,12 +488,10 @@ public class AnnotatedControllerConfigurer
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
public Map<String, ResolvableType> getArguments() {
|
||||
|
||||
Predicate<MethodParameter> argumentPredicate = (p) ->
|
||||
(p.getParameterAnnotation(Argument.class) != null || p.getParameterType() == ArgumentValue.class ||
|
||||
p.getParameterType() == FieldValue.class);
|
||||
(p.getParameterAnnotation(Argument.class) != null || p.getParameterType() == ArgumentValue.class);
|
||||
|
||||
return Arrays.stream(this.mappingInfo.getHandlerMethod().getMethodParameters())
|
||||
.filter(argumentPredicate)
|
||||
|
||||
@@ -20,7 +20,6 @@ import graphql.schema.DataFetchingEnvironment;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.GraphQlArgumentBinder;
|
||||
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
|
||||
@@ -38,13 +37,12 @@ import org.springframework.validation.BindException;
|
||||
* parameter type.
|
||||
*
|
||||
* <p>This resolver also supports wrapping the target object with
|
||||
* {@link FieldValue} if the application
|
||||
* wants to differentiate between an input argument that was set to
|
||||
* {@code null} vs not provided at all.
|
||||
* When this wrapper type is used, the annotation is optional,
|
||||
* and the name of the argument is derived from the method parameter name.
|
||||
* {@link ArgumentValue} if the application wants to differentiate between an
|
||||
* input argument that was set to {@code null} vs not provided at all. When
|
||||
* this wrapper type is used, the annotation is optional, and the name of the
|
||||
* argument is derived from the method parameter name.
|
||||
*
|
||||
* <p>An {@link FieldValue} can also be nested within the object structure
|
||||
* <p>An {@link ArgumentValue} can also be nested within the object structure
|
||||
* of an {@link Argument @Argument}-annotated method parameter.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
@@ -52,7 +50,6 @@ import org.springframework.validation.BindException;
|
||||
* @since 1.0.0
|
||||
* @see org.springframework.graphql.data.method.annotation.support.ArgumentsMethodArgumentResolver
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final GraphQlArgumentBinder argumentBinder;
|
||||
@@ -75,8 +72,7 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return (parameter.getParameterAnnotation(Argument.class) != null ||
|
||||
parameter.getParameterType() == ArgumentValue.class ||
|
||||
parameter.getParameterType() == FieldValue.class);
|
||||
parameter.getParameterType() == ArgumentValue.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,10 +103,9 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso
|
||||
return argument.name();
|
||||
}
|
||||
}
|
||||
else if (parameter.getParameterType() != ArgumentValue.class &&
|
||||
parameter.getParameterType() != FieldValue.class) {
|
||||
else if (parameter.getParameterType() != ArgumentValue.class) {
|
||||
throw new IllegalStateException(
|
||||
"Expected either @Argument or a method parameter of type FieldValue");
|
||||
"Expected either @Argument or a method parameter of type ArgumentValue");
|
||||
}
|
||||
|
||||
String parameterName = parameter.getParameterName();
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.springframework.graphql.data.ArgumentValue;
|
||||
* @since 1.2.2
|
||||
*/
|
||||
@UnwrapByDefault
|
||||
@SuppressWarnings("removal")
|
||||
public final class ArgumentValueValueExtractor implements ValueExtractor<ArgumentValue<@ExtractedValue ?>> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-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.data.method.annotation.support;
|
||||
|
||||
import jakarta.validation.valueextraction.ExtractedValue;
|
||||
import jakarta.validation.valueextraction.UnwrapByDefault;
|
||||
import jakarta.validation.valueextraction.ValueExtractor;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
|
||||
/**
|
||||
* {@link ValueExtractor} that enables {@code @Valid} with {@link FieldValue},
|
||||
* and helps to extract the value from it.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@UnwrapByDefault
|
||||
public final class FieldValueValueExtractor implements ValueExtractor<FieldValue<@ExtractedValue ?>> {
|
||||
|
||||
@Override
|
||||
public void extractValues(FieldValue<?> fieldValue, ValueReceiver receiver) {
|
||||
if (!fieldValue.isOmitted()) {
|
||||
receiver.value(null, fieldValue.value());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.web.ProjectedPayload;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.graphql.data.method.annotation.Argument;
|
||||
@@ -98,16 +97,13 @@ public class ProjectedPayloadMethodArgumentResolver implements HandlerMethodArgu
|
||||
return (type.isInterface() && AnnotatedElementUtils.findMergedAnnotation(type, ProjectedPayload.class) != null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static Class<?> getTargetType(MethodParameter parameter) {
|
||||
Class<?> type = parameter.getParameterType();
|
||||
return (type.equals(Optional.class) || type.equals(ArgumentValue.class) ||
|
||||
type.equals(FieldValue.class)) ?
|
||||
return (type.equals(Optional.class) || type.equals(ArgumentValue.class)) ?
|
||||
parameter.nested().getNestedParameterType() : parameter.getParameterType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) throws Exception {
|
||||
|
||||
String name = (parameter.hasParameterAnnotation(Argument.class) ?
|
||||
@@ -116,9 +112,8 @@ public class ProjectedPayloadMethodArgumentResolver implements HandlerMethodArgu
|
||||
Class<?> targetType = parameter.getParameterType();
|
||||
boolean isOptional = (targetType == Optional.class);
|
||||
boolean isArgumentValue = (targetType == ArgumentValue.class);
|
||||
boolean isFieldValue = (targetType == FieldValue.class);
|
||||
|
||||
if (isOptional || isArgumentValue || isFieldValue) {
|
||||
if (isOptional || isArgumentValue) {
|
||||
targetType = parameter.nested().getNestedParameterType();
|
||||
}
|
||||
|
||||
@@ -133,10 +128,6 @@ public class ProjectedPayloadMethodArgumentResolver implements HandlerMethodArgu
|
||||
return (name != null && arguments.containsKey(name)) ?
|
||||
ArgumentValue.ofNullable(value) : ArgumentValue.omitted();
|
||||
}
|
||||
else if (isFieldValue) {
|
||||
return (name != null && arguments.containsKey(name)) ?
|
||||
FieldValue.ofNullable(value) : FieldValue.omitted();
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.projection.TargetAware;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.graphql.data.method.HandlerMethodArgumentResolverComposite;
|
||||
@@ -246,11 +245,9 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
public void apply(RuntimeHints runtimeHints) {
|
||||
Type parameterType = this.methodParameter.getGenericParameterType();
|
||||
if (ArgumentValue.class.isAssignableFrom(this.methodParameter.getParameterType()) ||
|
||||
FieldValue.class.isAssignableFrom(this.methodParameter.getParameterType())) {
|
||||
if (ArgumentValue.class.isAssignableFrom(this.methodParameter.getParameterType())) {
|
||||
parameterType = this.methodParameter.nested().getNestedGenericParameterType();
|
||||
}
|
||||
bindingRegistrar.registerReflectionHints(runtimeHints.reflection(), parameterType);
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
org.springframework.graphql.data.method.annotation.support.FieldValueValueExtractor
|
||||
org.springframework.graphql.data.method.annotation.support.ArgumentValueValueExtractor
|
||||
@@ -31,7 +31,6 @@ import graphql.validation.ValidationErrorType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.support.DefaultGraphQlRequest;
|
||||
|
||||
@@ -59,45 +58,6 @@ class GraphQlClientTests extends GraphQlClientTestSupport {
|
||||
assertThat(movieCharacter).isEqualTo(MovieCharacter.create("Luke Skywalker"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void retrieveEntityFieldValuePresent() {
|
||||
|
||||
String document = "mockRequest1";
|
||||
getGraphQlService().setDataAsJson(document, "{\"current\": {\"id\":\"spring-graphql\", \"name\":\"Spring for GraphQL\"}}");
|
||||
|
||||
Project currentProject = graphQlClient().document(document)
|
||||
.retrieve("current").toEntity(Project.class)
|
||||
.block(TIMEOUT);
|
||||
|
||||
assertThat(currentProject).isEqualTo(new Project("spring-graphql", FieldValue.ofNullable("Spring for GraphQL")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void retrieveEntityOmittedField() {
|
||||
|
||||
String document = "mockRequest1";
|
||||
getGraphQlService().setDataAsJson(document, "{\"current\": {\"id\":\"spring-graphql\"}}");
|
||||
|
||||
Project currentProject = graphQlClient().document(document)
|
||||
.retrieve("current").toEntity(Project.class)
|
||||
.block(TIMEOUT);
|
||||
|
||||
assertThat(currentProject).isEqualTo(new Project("spring-graphql", FieldValue.omitted()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void retrieveEntityNullField() {
|
||||
|
||||
String document = "mockRequest1";
|
||||
getGraphQlService().setDataAsJson(document, "{\"current\": {\"id\":\"spring-graphql\", \"name\": null}}");
|
||||
|
||||
Project currentProject = graphQlClient().document(document)
|
||||
.retrieve("current").toEntity(Project.class)
|
||||
.block(TIMEOUT);
|
||||
|
||||
assertThat(currentProject).isEqualTo(new Project("spring-graphql", FieldValue.ofNullable(null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void retrieveEntityList() {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2025 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -17,28 +17,16 @@
|
||||
package org.springframework.graphql.client;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.graphql.Book;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.MediaTypes;
|
||||
import org.springframework.graphql.MockWebServerExtension;
|
||||
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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -51,49 +39,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ExtendWith(MockWebServerExtension.class)
|
||||
class HttpGraphQlTransportIntegrationTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("fieldValues")
|
||||
void shouldSerializeFieldValues(ProjectInput projectInput, String variable, MockWebServer server) throws Exception {
|
||||
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(new GraphQlModule()).build();
|
||||
Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON);
|
||||
WebClient webClient = WebClient.builder()
|
||||
.baseUrl(server.url("/graphql").toString())
|
||||
.codecs(codecs -> codecs.defaultCodecs().jackson2JsonEncoder(jsonEncoder))
|
||||
.build();
|
||||
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
|
||||
|
||||
server.enqueue(new MockResponse().addHeader("Content-Type", MediaTypes.APPLICATION_GRAPHQL_RESPONSE)
|
||||
.setBody("""
|
||||
{
|
||||
"data": {
|
||||
"createProject": {
|
||||
"id": "spring-graphql"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
graphQlClient.document("""
|
||||
mutation createProject($project: ProjectInput!) {
|
||||
createProject($project: $project) {
|
||||
id
|
||||
}
|
||||
}
|
||||
""")
|
||||
.variables(Map.of("project", projectInput))
|
||||
.executeSync();
|
||||
|
||||
assertThat(server.takeRequest().getBody().readUtf8()).contains("\"variables\":{\"project\":" + variable + "}");
|
||||
}
|
||||
|
||||
static Stream<Arguments> fieldValues() {
|
||||
return Stream.of(
|
||||
Arguments.arguments(new ProjectInput("spring-graphql", FieldValue.omitted()), "{\"id\":\"spring-graphql\"}"),
|
||||
Arguments.arguments(new ProjectInput("spring-graphql", FieldValue.ofNullable(null)), "{\"id\":\"spring-graphql\",\"name\":null}"),
|
||||
Arguments.arguments(new ProjectInput("spring-graphql", FieldValue.ofNullable("Spring for GraphQL")), "{\"id\":\"spring-graphql\",\"name\":\"Spring for GraphQL\"}")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void shouldStreamSubscriptionResultsOverSse(MockWebServer server) {
|
||||
WebClient webClient = WebClient.create(server.url("/graphql").toString());
|
||||
@@ -150,8 +95,4 @@ class HttpGraphQlTransportIntegrationTests {
|
||||
}
|
||||
|
||||
|
||||
public record ProjectInput(String id, FieldValue<String> name) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* 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.client;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
|
||||
public record Project(String id, FieldValue<String> name) {
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* 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.client.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.graphql.FieldValue;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link org.springframework.graphql.FieldValue} support in {@link GraphQlModule}.
|
||||
*/
|
||||
class FieldValueJsonTests {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new GraphQlModule());
|
||||
|
||||
@Nested
|
||||
class DeserializationTests {
|
||||
|
||||
@Test
|
||||
void valueIsOmittedWhenJsonKeyMissing() throws Exception {
|
||||
Library library = objectMapper.readValue("{}", Library.class);
|
||||
|
||||
assertThat(library.name()).isNotNull()
|
||||
.satisfies(name -> assertThat(name.isOmitted()).isTrue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueIsEmptyWhenJsonValueIsNull() throws Exception {
|
||||
Library library = objectMapper.readValue("{\"name\":null}", Library.class);
|
||||
|
||||
assertThat(library.name()).isNotNull()
|
||||
.satisfies(name -> assertThat(name.isEmpty()).isTrue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueIsPresentWhenJsonValueIsNotNull() throws Exception {
|
||||
Library library = objectMapper.readValue("{\"name\":\"The Library\"}", Library.class);
|
||||
|
||||
assertThat(library.name()).isNotNull()
|
||||
.satisfies(
|
||||
name -> assertThat(name.isPresent()).isTrue(),
|
||||
name -> assertThat(name.value()).isEqualTo("The Library")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SerializationTests {
|
||||
|
||||
@Test
|
||||
void skipJsonAttributeWhenValueOmitted() throws Exception {
|
||||
Library library = new Library(FieldValue.omitted());
|
||||
|
||||
assertThat(objectMapper.writeValueAsString(library)).isEqualTo("{}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullJsonValueWhenValueIsNull() throws Exception {
|
||||
Library library = new Library(FieldValue.ofNullable(null));
|
||||
|
||||
assertThat(objectMapper.writeValueAsString(library)).isEqualTo("{\"name\":null}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyJsonValueWhenValueIsEmpty() throws Exception {
|
||||
Library library = new Library(FieldValue.ofNullable(""));
|
||||
|
||||
assertThat(objectMapper.writeValueAsString(library)).isEqualTo("{\"name\":\"\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonValueWhenValueIsPresent() throws Exception {
|
||||
Library library = new Library(FieldValue.ofNullable("The Library"));
|
||||
|
||||
assertThat(objectMapper.writeValueAsString(library)).isEqualTo("{\"name\":\"The Library\"}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public record Library(FieldValue<String> name) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql;
|
||||
package org.springframework.graphql.data;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -23,14 +23,14 @@ import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link FieldValue}.
|
||||
* Tests for {@link ArgumentValue}.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class FieldValueTests {
|
||||
class ArgumentValueTests {
|
||||
|
||||
@Test
|
||||
void existingValueShouldBePresent() {
|
||||
FieldValue<String> message = FieldValue.ofNullable("hello");
|
||||
ArgumentValue<String> message = ArgumentValue.ofNullable("hello");
|
||||
assertThat(message.isOmitted()).isFalse();
|
||||
assertThat(message.isPresent()).isTrue();
|
||||
assertThat(message.isEmpty()).isFalse();
|
||||
@@ -38,7 +38,7 @@ class FieldValueTests {
|
||||
|
||||
@Test
|
||||
void nullValueShouldBePresent() {
|
||||
FieldValue<String> message = FieldValue.ofNullable(null);
|
||||
ArgumentValue<String> message = ArgumentValue.ofNullable(null);
|
||||
assertThat(message.isOmitted()).isFalse();
|
||||
assertThat(message.isPresent()).isFalse();
|
||||
assertThat(message.isEmpty()).isTrue();
|
||||
@@ -46,7 +46,7 @@ class FieldValueTests {
|
||||
|
||||
@Test
|
||||
void noValueShouldBeOmitted() {
|
||||
FieldValue<String> message = FieldValue.omitted();
|
||||
ArgumentValue<String> message = ArgumentValue.omitted();
|
||||
assertThat(message.isOmitted()).isTrue();
|
||||
assertThat(message.isPresent()).isFalse();
|
||||
assertThat(message.isEmpty()).isFalse();
|
||||
@@ -54,29 +54,29 @@ class FieldValueTests {
|
||||
|
||||
@Test
|
||||
void asOptionalShouldMapOmitted() {
|
||||
assertThat(FieldValue.omitted().asOptional()).isEmpty();
|
||||
assertThat(FieldValue.ofNullable(null).asOptional()).isEmpty();
|
||||
assertThat(FieldValue.ofNullable("hello").asOptional()).isPresent();
|
||||
assertThat(ArgumentValue.omitted().asOptional()).isEmpty();
|
||||
assertThat(ArgumentValue.ofNullable(null).asOptional()).isEmpty();
|
||||
assertThat(ArgumentValue.ofNullable("hello").asOptional()).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifPresentShouldExecuteWhenValue() {
|
||||
AtomicBoolean called = new AtomicBoolean();
|
||||
FieldValue.ofNullable("hello").ifPresent(value -> called.set(true));
|
||||
ArgumentValue.ofNullable("hello").ifPresent(value -> called.set(true));
|
||||
assertThat(called.get()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifPresentShouldSkipWhenNull() {
|
||||
AtomicBoolean called = new AtomicBoolean();
|
||||
FieldValue.ofNullable(null).ifPresent(value -> called.set(true));
|
||||
ArgumentValue.ofNullable(null).ifPresent(value -> called.set(true));
|
||||
assertThat(called.get()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifPresentShouldSkipWhenOmitted() {
|
||||
AtomicBoolean called = new AtomicBoolean();
|
||||
FieldValue.omitted().ifPresent(value -> called.set(true));
|
||||
ArgumentValue.omitted().ifPresent(value -> called.set(true));
|
||||
assertThat(called.get()).isFalse();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2025 the original author or authors.
|
||||
* Copyright 2020-2023 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.
|
||||
@@ -38,7 +38,6 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.graphql.Book;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.method.annotation.Argument;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.validation.BindException;
|
||||
@@ -54,7 +53,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
* @author Brian Clozel
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
class GraphQlArgumentBinderTests {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
@@ -250,30 +248,6 @@ class GraphQlArgumentBinderTests {
|
||||
assertThat(itemBean.getName().isPresent()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryConstructorWithOptionalFieldBeanArgument() throws Exception {
|
||||
|
||||
ResolvableType targetType =
|
||||
ResolvableType.forClass(PrimaryConstructorOptionalFieldItemBean.class);
|
||||
|
||||
Object result = bind(
|
||||
"{\"item\":{\"name\":\"Item name\",\"age\":\"30\"},\"name\":\"Hello\"}", targetType);
|
||||
|
||||
assertThat(result).isInstanceOf(PrimaryConstructorOptionalFieldItemBean.class).isNotNull();
|
||||
PrimaryConstructorOptionalFieldItemBean itemBean = (PrimaryConstructorOptionalFieldItemBean) result;
|
||||
|
||||
assertThat(itemBean.getItem().value().getName()).isEqualTo("Item name");
|
||||
assertThat(itemBean.getItem().value().getAge()).isEqualTo(30);
|
||||
assertThat(itemBean.getName().value()).isEqualTo("Hello");
|
||||
|
||||
result = bind("{\"key\":{}}", targetType);
|
||||
itemBean = (PrimaryConstructorOptionalFieldItemBean) result;
|
||||
|
||||
assertThat(itemBean).isNotNull();
|
||||
assertThat(itemBean.getItem().isOmitted()).isTrue();
|
||||
assertThat(itemBean.getName().isPresent()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryConstructorWithNestedBeanList() throws Exception {
|
||||
|
||||
@@ -596,26 +570,6 @@ class GraphQlArgumentBinderTests {
|
||||
}
|
||||
}
|
||||
|
||||
static class PrimaryConstructorOptionalFieldItemBean {
|
||||
|
||||
private final FieldValue<String> name;
|
||||
|
||||
private final FieldValue<Item> item;
|
||||
|
||||
public PrimaryConstructorOptionalFieldItemBean(FieldValue<String> name, FieldValue<Item> item) {
|
||||
this.name = name;
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public FieldValue<String> getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public FieldValue<Item> getItem() {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class NoPrimaryConstructorBean {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2025 the original author or authors.
|
||||
* Copyright 2020-2023 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.
|
||||
@@ -25,7 +25,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.graphql.Book;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.GraphQlArgumentBinder;
|
||||
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
|
||||
@@ -54,9 +53,6 @@ class ArgumentMethodArgumentResolverTests extends ArgumentResolverTestSupport {
|
||||
param = methodParam(BookController.class, "addBook", ArgumentValue.class);
|
||||
assertThat(this.resolver.supportsParameter(param)).isTrue();
|
||||
|
||||
param = methodParam(BookController.class, "addBook", FieldValue.class);
|
||||
assertThat(this.resolver.supportsParameter(param)).isTrue();
|
||||
|
||||
param = methodParam(BookController.class, "rawArgumentValue", Map.class);
|
||||
assertThat(this.resolver.supportsParameter(param)).isTrue();
|
||||
|
||||
@@ -85,7 +81,7 @@ class ArgumentMethodArgumentResolverTests extends ArgumentResolverTestSupport {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveJavaBeanArgumentWithArgumentWrapper() throws Exception {
|
||||
void shouldResolveJavaBeanArgumentWithWrapper() throws Exception {
|
||||
Object result = this.resolver.resolveArgument(
|
||||
methodParam(BookController.class, "addBook", ArgumentValue.class),
|
||||
environment("{\"bookInput\": { \"name\": \"test name\", \"authorId\": 42} }"));
|
||||
@@ -98,20 +94,6 @@ class ArgumentMethodArgumentResolverTests extends ArgumentResolverTestSupport {
|
||||
.hasFieldOrPropertyWithValue("authorId", 42L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveJavaBeanArgumentWithFieldWrapper() throws Exception {
|
||||
Object result = this.resolver.resolveArgument(
|
||||
methodParam(BookController.class, "addBook", FieldValue.class),
|
||||
environment("{\"bookInput\": { \"name\": \"test name\", \"authorId\": 42} }"));
|
||||
|
||||
assertThat(result)
|
||||
.isNotNull()
|
||||
.isInstanceOf(FieldValue.class)
|
||||
.extracting(value -> ((FieldValue<?>) value).value())
|
||||
.hasFieldOrPropertyWithValue("name", "test name")
|
||||
.hasFieldOrPropertyWithValue("authorId", 42L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveListOfJavaBeansArgument() throws Exception {
|
||||
Object result = this.resolver.resolveArgument(
|
||||
@@ -170,11 +152,6 @@ class ArgumentMethodArgumentResolverTests extends ArgumentResolverTestSupport {
|
||||
return null;
|
||||
}
|
||||
|
||||
@MutationMapping
|
||||
public Book addBook(FieldValue<BookInput> bookInput) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@MutationMapping
|
||||
public List<Book> addBooks(@Argument List<Book> books) {
|
||||
return null;
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.graphql.Book;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.GraphQlArgumentBinder;
|
||||
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.graphql.data.method.annotation.Arguments;
|
||||
@@ -109,15 +109,15 @@ class ArgumentsMethodArgumentResolverTests extends ArgumentResolverTestSupport {
|
||||
@SuppressWarnings({"NotNullFieldNotInitialized", "unused"})
|
||||
static class BookInput {
|
||||
|
||||
FieldValue<String> name;
|
||||
ArgumentValue<String> name;
|
||||
|
||||
Long authorId;
|
||||
|
||||
public FieldValue<String> getName() {
|
||||
public ArgumentValue<String> getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(FieldValue<String> name) {
|
||||
public void setName(ArgumentValue<String> name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.web.ProjectedPayload;
|
||||
import org.springframework.graphql.Book;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.method.annotation.Argument;
|
||||
import org.springframework.graphql.data.method.annotation.QueryMapping;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -54,7 +54,7 @@ class ProjectedPayloadMethodArgumentResolverTests extends ArgumentResolverTestSu
|
||||
testSupports("projection", BookProjection.class, true);
|
||||
testSupports("optionalProjection", Optional.class, true);
|
||||
testSupports("optionalString", Optional.class, false);
|
||||
testSupports("fieldValueProjection", FieldValue.class, true);
|
||||
testSupports("argumentValueProjection", ArgumentValue.class, true);
|
||||
}
|
||||
|
||||
void testSupports(String methodName, Class<?> methodParamType, boolean supported) {
|
||||
@@ -86,39 +86,39 @@ class ProjectedPayloadMethodArgumentResolverTests extends ArgumentResolverTestSu
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldValuePresent() throws Exception {
|
||||
void argumentValuePresent() throws Exception {
|
||||
|
||||
Object result = this.resolver.resolveArgument(
|
||||
methodParam(BookController.class, "fieldValueProjection", FieldValue.class),
|
||||
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
|
||||
environment("{ \"where\" : { \"author\" : \"Orwell\" }}"));
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(FieldValue.class);
|
||||
BookProjection book = ((FieldValue<BookProjection>) result).value();
|
||||
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
|
||||
BookProjection book = ((ArgumentValue<BookProjection>) result).value();
|
||||
assertThat(book.getAuthor()).isEqualTo("Orwell");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldValueSetToNull() throws Exception {
|
||||
void argumentValueSetToNull() throws Exception {
|
||||
|
||||
Object result = this.resolver.resolveArgument(
|
||||
methodParam(BookController.class, "fieldValueProjection", FieldValue.class),
|
||||
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
|
||||
environment("{ \"where\" : null}"));
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(FieldValue.class);
|
||||
FieldValue<BookProjection> value = ((FieldValue<BookProjection>) result);
|
||||
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
|
||||
ArgumentValue<BookProjection> value = ((ArgumentValue<BookProjection>) result);
|
||||
assertThat(value.isPresent()).isFalse();
|
||||
assertThat(value.isOmitted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldValueIsOmitted() throws Exception {
|
||||
void argumentValueIsOmitted() throws Exception {
|
||||
|
||||
Object result = this.resolver.resolveArgument(
|
||||
methodParam(BookController.class, "fieldValueProjection", FieldValue.class),
|
||||
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
|
||||
environment("{}"));
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(FieldValue.class);
|
||||
FieldValue<BookProjection> value = ((FieldValue<BookProjection>) result);
|
||||
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
|
||||
ArgumentValue<BookProjection> value = ((ArgumentValue<BookProjection>) result);
|
||||
assertThat(value.isPresent()).isFalse();
|
||||
assertThat(value.isOmitted()).isTrue();
|
||||
}
|
||||
@@ -153,7 +153,7 @@ class ProjectedPayloadMethodArgumentResolverTests extends ArgumentResolverTestSu
|
||||
}
|
||||
|
||||
@QueryMapping
|
||||
public List<Book> fieldValueProjection(@Argument(name = "where") FieldValue<BookProjection> projection) {
|
||||
public List<Book> argumentValueProjection(@Argument(name = "where") ArgumentValue<BookProjection> projection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ import org.springframework.data.projection.TargetAware;
|
||||
import org.springframework.data.web.ProjectedPayload;
|
||||
import org.springframework.graphql.Author;
|
||||
import org.springframework.graphql.Book;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.federation.EntityMapping;
|
||||
import org.springframework.graphql.data.method.annotation.Argument;
|
||||
import org.springframework.graphql.data.method.annotation.BatchMapping;
|
||||
@@ -120,12 +120,12 @@ class SchemaMappingBeanFactoryInitializationAotProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerBindingReflectionOnFieldValue() {
|
||||
processBeanClasses(FieldValueController.class);
|
||||
assertThatIntrospectionOnMethodsHintRegisteredForType(FieldValueController.class);
|
||||
assertThatInvocationHintRegisteredForMethods(FieldValueController.class, "addBook");
|
||||
void registerBindingReflectionOnArgumentValue() {
|
||||
processBeanClasses(ArgumentValueController.class);
|
||||
assertThatIntrospectionOnMethodsHintRegisteredForType(ArgumentValueController.class);
|
||||
assertThatInvocationHintRegisteredForMethods(ArgumentValueController.class, "addBook");
|
||||
assertThatHintsForJavaBeanBindingRegisteredForTypes(Book.class, BookInput.class);
|
||||
assertThatHintsAreNotRegisteredForTypes(FieldValue.class);
|
||||
assertThatHintsAreNotRegisteredForTypes(ArgumentValue.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,9 +173,9 @@ class SchemaMappingBeanFactoryInitializationAotProcessorTests {
|
||||
}
|
||||
|
||||
@Controller
|
||||
static class FieldValueController {
|
||||
static class ArgumentValueController {
|
||||
@MutationMapping
|
||||
public Book addBook(FieldValue<BookInput> bookInput) {
|
||||
public Book addBook(ArgumentValue<BookInput> bookInput) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.assertj.core.api.ThrowableAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.graphql.FieldValue;
|
||||
import org.springframework.graphql.data.ArgumentValue;
|
||||
import org.springframework.graphql.data.method.HandlerMethod;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@@ -71,20 +71,20 @@ class ValidationHelperTests {
|
||||
BiConsumer<Object, Object[]> validator2 = validateFunction(MyBean.class, "myValidatedParameterMethod");
|
||||
assertViolation(() -> validator2.accept(bean, new Object[] {new ConstrainedInput(100)}), "integerValue");
|
||||
|
||||
BiConsumer<Object, Object[]> validator3 = validateFunction(MyBean.class, "myValidFieldValue");
|
||||
assertViolation(() -> validator3.accept(bean, new Object[] {FieldValue.ofNullable("")}), "myValidFieldValue.arg0");
|
||||
BiConsumer<Object, Object[]> validator3 = validateFunction(MyBean.class, "myValidArgumentValue");
|
||||
assertViolation(() -> validator3.accept(bean, new Object[] {ArgumentValue.ofNullable("")}), "myValidArgumentValue.arg0");
|
||||
|
||||
// Validate that an explicit null value is validated.
|
||||
assertViolation(() -> validator3.accept(bean, new Object[] {FieldValue.ofNullable(null)}), "myValidFieldValue.arg0");
|
||||
assertViolation(() -> validator3.accept(bean, new Object[] {ArgumentValue.ofNullable(null)}), "myValidArgumentValue.arg0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRaiseValidationErrorForOmittedFieldValue() {
|
||||
void shouldNotRaiseValidationErrorForOmittedArgumentValue() {
|
||||
MyBean bean = new MyBean();
|
||||
|
||||
// Validate that an omitted value is allowed.
|
||||
BiConsumer<Object, Object[]> validator3 = validateFunction(MyBean.class, "myValidFieldValue");
|
||||
validator3.accept(bean, new Object[] {FieldValue.omitted()});
|
||||
BiConsumer<Object, Object[]> validator3 = validateFunction(MyBean.class, "myValidArgumentValue");
|
||||
validator3.accept(bean, new Object[] {ArgumentValue.omitted()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,7 +173,7 @@ class ValidationHelperTests {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object myValidFieldValue(@Valid FieldValue<@NotBlank String> arg0) {
|
||||
public Object myValidArgumentValue(@Valid ArgumentValue<@NotBlank String> arg0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user