Allow Enum types to be injected as method parameter for Source

Prior to this commit, enum types would be rejected as possible source
arguments for schema mapping methods.

This is a valid use case and this commit removes this limitation.

Closes gh-1059
This commit is contained in:
Brian Clozel
2024-10-14 18:22:59 +02:00
parent 3a603e7a94
commit 2650eb5863
2 changed files with 157 additions and 3 deletions

View File

@@ -55,7 +55,6 @@ public class SourceMethodArgumentResolver implements HandlerMethodArgumentResolv
// Same as BeanUtils.isSimpleValueType except for CharSequence and Number
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
@@ -69,12 +68,12 @@ public class SourceMethodArgumentResolver implements HandlerMethodArgumentResolv
Object source = environment.getSource();
if (source == null) {
throw new IllegalStateException(formatArgumentError(parameter,
" was not recognized by any resolver and there is no source/parent either. " +
"was not recognized by any resolver and there is no source/parent either. " +
"Please, refer to the documentation for the full list of supported parameters."));
}
if (!parameter.getParameterType().isInstance(source)) {
throw new IllegalStateException(formatArgumentError(parameter,
" does not match the source Object type '" + source.getClass() + "'."));
"does not match the source Object type '" + source.getClass() + "'."));
}
return source;
}

View File

@@ -0,0 +1,155 @@
/*
* 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.
* 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 java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;
import graphql.schema.DataFetchingEnvironmentImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.core.MethodParameter;
import org.springframework.graphql.Author;
import org.springframework.graphql.Book;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link SourceMethodArgumentResolver}.
* @author Brian Clozel
*/
class SourceMethodArgumentResolverTests extends ArgumentResolverTestSupport {
private final HandlerMethodArgumentResolver resolver = new SourceMethodArgumentResolver();
@ParameterizedTest
@MethodSource("excludedTypes")
void excludedSourceTypes(Class<?> argumentType) {
MethodParameter param = methodParam(BookController.class, "notSupported", argumentType);
assertThat(this.resolver.supportsParameter(param)).isFalse();
}
static Stream<Class<?>> excludedTypes() {
return Stream.of(List.class, String[].class, Integer.class, Date.class, Instant.class, URI.class, URL.class, Locale.class, Class.class);
}
@Test
void supportedSourceTypes() {
MethodParameter param = methodParam(BookController.class, "author", Book.class);
assertThat(this.resolver.supportsParameter(param)).isTrue();
param = methodParam(BookController.class, "name", Format.class);
assertThat(this.resolver.supportsParameter(param)).isTrue();
}
@Test
void bindArgumentWhenPresent() throws Exception {
Book aBook = BookSource.getBook(1L);
Object result = this.resolver.resolveArgument(
methodParam(BookController.class, "author", Book.class),
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().source(aBook).build());
assertThat(result).isNotNull().isInstanceOf(Book.class).isEqualTo(aBook);
}
@Test
void bindArgumentWhenUnavailable() throws Exception {
MethodParameter methodParameter = methodParam(BookController.class, "author", Book.class);
assertThatThrownBy(() -> this.resolver.resolveArgument(
methodParameter,
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().build())).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Parameter [0] in %s: was not recognized by any resolver and there is no source/parent either.", methodParameter.getMethod());
}
@Test
void bindArgumentWhenWrongType() throws Exception {
MethodParameter methodParameter = methodParam(BookController.class, "author", Book.class);
assertThatThrownBy(() -> this.resolver.resolveArgument(
methodParameter,
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().source(2L).build())).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Parameter [0] in %s: does not match the source Object type 'class java.lang.Long'.", methodParameter.getMethod());
}
@SuppressWarnings({"ConstantConditions", "unused"})
@Controller
static class BookController {
public void notSupported(Integer integer) {
}
public void notSupported(Date date) {
}
public void notSupported(Instant instant) {
}
public void notSupported(URI uri) {
}
public void notSupported(URL url) {
}
public void notSupported(Locale locale) {
}
public void notSupported(Class<?> klass) {
}
public void notSupported(List<String> list) {
}
public void notSupported(String[] array) {
}
@SchemaMapping
public Author author(Book book) {
return null;
}
@SchemaMapping
public String name(Format format) {
return format.name();
}
}
enum Format {
PAPERBACK, EBOOK;
}
}