#1652 - Tighten contracts for parameter values in MethodLinkBuilderFactory.linkTo(…).

If a parameter array is given to MethodLinkBuilderFactory.linkTo(…) methods, we now verify its correct length in correspondence to the method given. If no parameters are given at all, we automatically create a parameter value array of the required length.
This commit is contained in:
Oliver Drotbohm
2021-09-24 16:19:18 +02:00
parent 3b94d4b862
commit f0a000a07e
4 changed files with 98 additions and 11 deletions

View File

@@ -30,7 +30,18 @@ public interface MethodLinkBuilderFactory<T extends LinkBuilder> extends LinkBui
/**
* Returns a {@link LinkBuilder} pointing to the URI mapped to the given {@link Method} and expanding this mapping
* using the given parameters.
* using {@literal null} values as parameters.
*
* @param method must not be {@literal null}.
* @return
* @since 1.4
*/
T linkTo(Method method);
/**
* Returns a {@link LinkBuilder} pointing to the URI mapped to the given {@link Method} and expanding this mapping
* using the given parameters. The number of parameter values has to match the length of the given method's expected
* parameters.
*
* @param method must not be {@literal null}.
* @param parameters
@@ -40,7 +51,19 @@ public interface MethodLinkBuilderFactory<T extends LinkBuilder> extends LinkBui
/**
* Returns a {@link LinkBuilder} pointing to the URI mapped to the given {@link Method} assuming it was invoked on an
* object of the given type.
* object of the given type expanding the mapping using {@literal null} values as parameters.
*
* @param type must not be {@literal null}.
* @param method must not be {@literal null}.
* @return
* @since 1.4
*/
T linkTo(Class<?> type, Method method);
/**
* Returns a {@link LinkBuilder} pointing to the URI mapped to the given {@link Method} assuming it was invoked on an
* object of the given type. The number of parameter values has to match the length of the given method's expected
* parameters.
*
* @param type must not be {@literal null}.
* @param method must not be {@literal null}.

View File

@@ -116,6 +116,16 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getComponents()).slash(uriComponents, true);
}
/*
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method)
*/
public static WebMvcLinkBuilder linkTo(Method method) {
Assert.notNull(method, "Method must not be null!");
return linkTo(method.getDeclaringClass(), method, new Object[method.getParameterTypes().length]);
}
/*
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
*/
@@ -127,6 +137,17 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
return linkTo(method.getDeclaringClass(), method, parameters);
}
/*
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Class<?>, Method)
*/
public static WebMvcLinkBuilder linkTo(Class<?> controller, Method method) {
Assert.notNull(controller, "Controller type must not be null!");
Assert.notNull(method, "Method must not be null!");
return linkTo(controller, method, new Object[method.getParameterTypes().length]);
}
/*
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Class<?>, Method, Object...)
*/
@@ -136,6 +157,12 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
Assert.notNull(method, "Method must not be null!");
Assert.notNull(parameters, "Parameters must not be null!");
int expected = method.getParameterTypes().length;
int given = parameters.length;
Assert.isTrue(expected == given,
() -> String.format("Incorrect number of parameter values given. Expected %s, got %s!", expected, given));
return linkTo(DummyInvocationUtils.getLastInvocationAware(controller, method, parameters));
}

View File

@@ -103,6 +103,33 @@ public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory<WebMvc
return WebMvcLinkBuilder.linkTo(controller, parameters);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.server.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method)
*/
@Override
public WebMvcLinkBuilder linkTo(Method method) {
return WebMvcLinkBuilder.linkTo(method);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
return WebMvcLinkBuilder.linkTo(method, parameters);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.server.MethodLinkBuilderFactory#linkTo(java.lang.Class, java.lang.reflect.Method)
*/
@Override
public WebMvcLinkBuilder linkTo(Class<?> type, Method method) {
return WebMvcLinkBuilder.linkTo(type, method);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Class, java.lang.reflect.Method, java.lang.Object[])
@@ -161,15 +188,6 @@ public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory<WebMvc
}, builderFactory, getConversionService());
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
return WebMvcLinkBuilder.linkTo(method, parameters);
}
private static Supplier<ConversionService> getConversionService() {
return () -> {

View File

@@ -30,6 +30,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.ConfigurableConversionService;
@@ -674,6 +675,24 @@ class WebMvcLinkBuilderUnitTest extends TestUtils {
assertThat(result.getHref()).endsWith("?param=first");
}
@Test // #1652
void buildsLinkWithoutParameterValuesGiven() throws Exception {
Method method = ControllerWithMethods.class.getDeclaredMethod("myMethod", Object.class);
Stream.<ThrowingCallable> of( //
() -> linkTo(method, new Object[0]), //
() -> linkTo(ControllerWithMethods.class, method, new Object[0]) //
) //
.map(assertThatIllegalArgumentException()::isThrownBy)
.forEach(it -> it.withMessageContaining("Expected 1, got 0"));
Stream.<ThrowingCallable> of( //
() -> linkTo(method), //
() -> linkTo(ControllerWithMethods.class, method) //
).forEach(assertThatNoException()::isThrownBy);
}
private static UriComponents toComponents(Link link) {
return UriComponentsBuilder.fromUriString(link.expand().getHref()).build();
}