diff --git a/src/main/java/org/springframework/data/web/OffsetScrollPositionArgumentResolver.java b/src/main/java/org/springframework/data/web/OffsetScrollPositionArgumentResolver.java index 51338b0ef..25a7fb540 100644 --- a/src/main/java/org/springframework/data/web/OffsetScrollPositionArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/OffsetScrollPositionArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 the original author or authors. + * Copyright 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. @@ -17,7 +17,6 @@ package org.springframework.data.web; import org.springframework.core.MethodParameter; import org.springframework.data.domain.OffsetScrollPosition; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -27,27 +26,31 @@ import org.springframework.web.method.support.ModelAndViewContainer; /** * Argument resolver to extract a {@link OffsetScrollPosition} object from a {@link NativeWebRequest} for a particular - * {@link MethodParameter}. A {@link OffsetScrollPositionArgumentResolver} can either resolve {@link OffsetScrollPosition} itself or wrap another - * {@link OffsetScrollPositionArgumentResolver} to post-process {@link OffsetScrollPosition}. + * {@link MethodParameter}. A {@link OffsetScrollPositionArgumentResolver} can either resolve + * {@link OffsetScrollPosition} itself or wrap another {@link OffsetScrollPositionArgumentResolver} to post-process + * {@link OffsetScrollPosition}. * - * @since 3.2 * @author Yanming Zhou - * @see HandlerMethodArgumentResolver + * @author Mark Paluch + * @since 3.2 + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver */ public interface OffsetScrollPositionArgumentResolver extends HandlerMethodArgumentResolver { /** - * Resolves a {@link OffsetScrollPosition} method parameter into an argument value from a given request. + * Resolves a {@link OffsetScrollPosition} method parameter into an argument value from a given request. Supports also + * wrapped arguments in {@link java.util.Optional}. * * @param parameter the method parameter to resolve. This parameter must have previously been passed to * {@link #supportsParameter} which must have returned {@code true}. * @param mavContainer the ModelAndViewContainer for the current request * @param webRequest the current request * @param binderFactory a factory for creating {@link WebDataBinder} instances - * @return the resolved argument value + * @return the resolved argument value or {@literal null} if the value cannot be resolved. The returned value + * considers {@link MethodParameter#isOptional() Optional} wrapping by returing either the value wrapped + * within Optional or Optional.empty(). */ - @NonNull @Override - OffsetScrollPosition resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory); } diff --git a/src/main/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolver.java index 8dea7d95d..da572a833 100644 --- a/src/main/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 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. @@ -15,6 +15,8 @@ */ package org.springframework.data.web; +import java.util.Arrays; + import org.springframework.core.MethodParameter; import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.lang.Nullable; @@ -23,27 +25,28 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import java.util.Arrays; - /** - * {@link HandlerMethodArgumentResolver} to automatically create {@link OffsetScrollPosition} instances from request parameters. + * {@link HandlerMethodArgumentResolver} to automatically create {@link OffsetScrollPosition} instances from request + * parameters. * - * @since 3.2 * @author Yanming Zhou + * @since 3.2 */ public class OffsetScrollPositionHandlerMethodArgumentResolver extends OffsetScrollPositionHandlerMethodArgumentResolverSupport implements OffsetScrollPositionArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { - return OffsetScrollPosition.class.equals(parameter.getParameterType()); + return OffsetScrollPosition.class.equals(parameter.nestedIfOptional().getNestedParameterType()); } @Override - public OffsetScrollPosition resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { - String[] offsetParameter = webRequest.getParameterValues(getOffsetParameter(parameter)); - return parseParameterIntoOffsetScrollPosition(offsetParameter != null ? Arrays.asList(offsetParameter) : null); + String[] offsetParameter = webRequest.getParameterValues(getOffsetParameter(parameter.nestedIfOptional())); + return adaptArgumentIfNecessary( + parseParameterIntoOffsetScrollPosition(offsetParameter != null ? Arrays.asList(offsetParameter) : null), + parameter); } } diff --git a/src/main/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolverSupport.java b/src/main/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolverSupport.java index a21cf263d..2abfab8d0 100644 --- a/src/main/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolverSupport.java +++ b/src/main/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolverSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 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. @@ -15,22 +15,23 @@ */ package org.springframework.data.web; +import java.util.List; +import java.util.Optional; + import org.springframework.core.MethodParameter; import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.data.domain.ScrollPosition; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import java.util.List; -import java.util.Objects; - /** - * Base class providing methods for handler method argument resolvers to create {@link OffsetScrollPosition} instances from request - * parameters. + * Base class providing methods for handler method argument resolvers to create {@link OffsetScrollPosition} instances + * from request parameters. * - * @since 3.2 * @author Yanming Zhou + * @since 3.2 * @see OffsetScrollPositionHandlerMethodArgumentResolver * @see ReactiveOffsetScrollPositionHandlerMethodArgumentResolver */ @@ -71,7 +72,7 @@ public abstract class OffsetScrollPositionHandlerMethodArgumentResolverSupport { * @param parameter can be {@literal null}. * @return the offset parameter */ - protected String getOffsetParameter(@Nullable MethodParameter parameter) { + protected String getOffsetParameter(MethodParameter parameter) { StringBuilder builder = new StringBuilder(); @@ -89,19 +90,39 @@ public abstract class OffsetScrollPositionHandlerMethodArgumentResolverSupport { * Parses the given source into a {@link OffsetScrollPosition} instance. * * @param source could be {@literal null} or empty. - * @return parsed OffsetScrollPosition + * @return parsed OffsetScrollPosition or {@literal null} if it cannot be constructed. */ + @Nullable OffsetScrollPosition parseParameterIntoOffsetScrollPosition(@Nullable List source) { + // No parameter or Single empty parameter, e.g "offset=" - if (source == null || source.size() == 1 && !StringUtils.hasText(source.get(0))) { - return ScrollPosition.offset(); + if (CollectionUtils.isEmpty(source) || (source.size() == 1 && !StringUtils.hasText(source.get(0)))) { + return null; } + try { long offset = Long.parseLong(source.get(0)); return ScrollPosition.offset(offset); } catch (NumberFormatException ex) { - return ScrollPosition.offset(); + return null; } } + /** + * Adapt the given argument against the method parameter, if necessary. + * + * @param arg the resolved argument. + * @param parameter the method parameter descriptor. + * @return the adapted argument, or the original resolved argument as-is. + */ + @Nullable + Object adaptArgumentIfNecessary(@Nullable Object arg, MethodParameter parameter) { + + if (parameter.getParameterType() == Optional.class) { + return arg == null ? Optional.empty() : Optional.of(arg); + } + + return arg; + } + } diff --git a/src/main/java/org/springframework/data/web/ReactiveOffsetScrollPositionHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/ReactiveOffsetScrollPositionHandlerMethodArgumentResolver.java index 690775cfb..fa0efce72 100644 --- a/src/main/java/org/springframework/data/web/ReactiveOffsetScrollPositionHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/ReactiveOffsetScrollPositionHandlerMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 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. @@ -15,37 +15,36 @@ */ package org.springframework.data.web; +import java.util.List; + import org.springframework.core.MethodParameter; import org.springframework.data.domain.OffsetScrollPosition; -import org.springframework.lang.NonNull; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver; import org.springframework.web.server.ServerWebExchange; -import java.util.List; - /** - * Reactive {@link HandlerMethodArgumentResolver} to create {@link OffsetScrollPosition} instances from query string parameters. + * Reactive {@link HandlerMethodArgumentResolver} to create {@link OffsetScrollPosition} instances from query string + * parameters. * - * @since 3.2 * @author Yanming Zhou + * @since 3.2 */ -public class ReactiveOffsetScrollPositionHandlerMethodArgumentResolver extends OffsetScrollPositionHandlerMethodArgumentResolverSupport - implements SyncHandlerMethodArgumentResolver { +public class ReactiveOffsetScrollPositionHandlerMethodArgumentResolver + extends OffsetScrollPositionHandlerMethodArgumentResolverSupport implements SyncHandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { - return OffsetScrollPosition.class.equals(parameter.getParameterType()); + return OffsetScrollPosition.class.equals(parameter.nestedIfOptional().getNestedParameterType()); } - @NonNull @Override - public OffsetScrollPosition resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext, + public Object resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { List offsetParameter = exchange.getRequest().getQueryParams().get(getOffsetParameter(parameter)); - return parseParameterIntoOffsetScrollPosition(offsetParameter); + return adaptArgumentIfNecessary(parseParameterIntoOffsetScrollPosition(offsetParameter), parameter); } } diff --git a/src/test/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolverUnitTests.java index 8a95bfbee..4042852f2 100755 --- a/src/test/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/OffsetScrollPositionHandlerMethodArgumentResolverUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 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. @@ -15,8 +15,12 @@ */ package org.springframework.data.web; +import static org.assertj.core.api.Assertions.*; + import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.BeforeAll; + +import java.util.Optional; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.MethodParameter; @@ -28,40 +32,30 @@ import org.springframework.util.StringUtils; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; -import static org.assertj.core.api.Assertions.assertThat; - /** * Unit tests for {@link OffsetScrollPositionHandlerMethodArgumentResolver}. * - * @since 3.2 * @author Yanming Zhou + * @author Mark Paluch */ class OffsetScrollPositionHandlerMethodArgumentResolverUnitTests { - static MethodParameter PARAMETER; + static final MethodParameter PARAMETER = getParameterOfMethod("supportedMethod"); + static final MethodParameter OPTIONAL_PARAMETER = getOptionalParameterOfMethod("supportedMethodWithOptional"); - @BeforeAll - static void setUp() throws Exception { - PARAMETER = getParameterOfMethod("supportedMethod"); - } + final OffsetScrollPositionHandlerMethodArgumentResolver sut = new OffsetScrollPositionHandlerMethodArgumentResolver(); - @Test + @Test // GH-2856 void supportsSortParameter() { - - var resolver = new OffsetScrollPositionHandlerMethodArgumentResolver(); - - assertThat(resolver.supportsParameter(PARAMETER)).isTrue(); + assertThat(sut.supportsParameter(PARAMETER)).isTrue(); } - @Test - void fallbackToDefaultOffset() { - - var parameter = TestUtils.getParameterOfMethod(Controller.class, "unsupportedMethod", String.class); - var position = resolveOffset(new MockHttpServletRequest(), parameter); - assertThat(position).isEqualTo(ScrollPosition.offset()); + @Test // GH-2856 + void supportsOptionalOffsetScrollPositionParameter() { + assertThat(sut.supportsParameter(OPTIONAL_PARAMETER)).isTrue(); } - @Test + @Test // GH-2856 void discoversOffsetFromRequest() { var reference = ScrollPosition.offset(5); @@ -69,7 +63,35 @@ class OffsetScrollPositionHandlerMethodArgumentResolverUnitTests { assertSupportedAndResolvedTo(getRequestWithOffset(reference, null), PARAMETER, reference); } - @Test + @Test // GH-2856 + void returnsNullIfNotSpecified() { + + var request = new MockHttpServletRequest(); + var position = resolveOffset(request, PARAMETER); + + assertThat(position).isNull(); + } + + @Test // GH-2856 + void returnsOptionalParameterFromRequest() { + + var request = new MockHttpServletRequest(); + request.addParameter("offset", "5"); + var position = resolveOffset(request, OPTIONAL_PARAMETER); + + assertThat(position).isEqualTo(Optional.of(ScrollPosition.offset(5))); + } + + @Test // GH-2856 + void returnsEmptyOptionalIfNotSpecified() { + + var request = new MockHttpServletRequest(); + var position = resolveOffset(request, OPTIONAL_PARAMETER); + + assertThat(position).isEqualTo(Optional.empty()); + } + + @Test // GH-2856 void discoversOffsetFromRequestWithMultipleParams() { var request = new MockHttpServletRequest(); @@ -79,43 +101,43 @@ class OffsetScrollPositionHandlerMethodArgumentResolverUnitTests { assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset(5)); } - @Test + @Test // GH-2856 void discoversQualifiedOffsetFromRequest() { var parameter = getParameterOfMethod("qualifiedOffset"); var reference = ScrollPosition.offset(5); - assertSupportedAndResolvedTo(getRequestWithOffset(reference, "qual"), parameter, reference); + assertSupportedAndResolvedTo(getRequestWithOffset(reference, "hello"), parameter, reference); } - @Test - void returnsDefaultForOffsetParamSetToNothing() { + @Test // GH-2856 + void returnsNullForOffsetParamSetToNothing() { var request = new MockHttpServletRequest(); request.addParameter("offset", (String) null); - assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset()); + assertThat(resolveOffset(request, PARAMETER)).isNull(); } - @Test - void returnsDefaultForEmptyOffsetParam() { + @Test // GH-2856 + void returnsNullForEmptyOffsetParam() { var request = new MockHttpServletRequest(); request.addParameter("offset", ""); - assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset()); + assertThat(resolveOffset(request, PARAMETER)).isNull(); } - @Test - void returnsDefaultForOffsetParamIsInvalidProperty() { + @Test // GH-2856 + void returnsNullForOffsetParamIsInvalidProperty() { var request = new MockHttpServletRequest(); request.addParameter("offset", "invalid_number"); - assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset()); + assertThat(resolveOffset(request, PARAMETER)).isNull(); } - @Test + @Test // GH-2856 void emptyQualifierIsUsedInParameterLookup() { var parameter = getParameterOfMethod("emptyQualifier"); @@ -124,7 +146,7 @@ class OffsetScrollPositionHandlerMethodArgumentResolverUnitTests { assertSupportedAndResolvedTo(getRequestWithOffset(reference, ""), parameter, reference); } - @Test + @Test // GH-2856 void mergedQualifierIsUsedInParameterLookup() { var parameter = getParameterOfMethod("mergedQualifier"); @@ -133,30 +155,28 @@ class OffsetScrollPositionHandlerMethodArgumentResolverUnitTests { assertSupportedAndResolvedTo(getRequestWithOffset(reference, "merged"), parameter, reference); } - private static OffsetScrollPosition resolveOffset(HttpServletRequest request, MethodParameter parameter) { - - var resolver = new OffsetScrollPositionHandlerMethodArgumentResolver(); - return resolver.resolveArgument(parameter, null, new ServletWebRequest(request), null); + @Nullable + private Object resolveOffset(HttpServletRequest request, MethodParameter parameter) { + return sut.resolveArgument(parameter, null, new ServletWebRequest(request), null); } - private static void assertSupportedAndResolvedTo(NativeWebRequest request, MethodParameter parameter, OffsetScrollPosition position) { + private void assertSupportedAndResolvedTo(NativeWebRequest request, MethodParameter parameter, + OffsetScrollPosition position) { - var resolver = new OffsetScrollPositionHandlerMethodArgumentResolver(); - assertThat(resolver.supportsParameter(parameter)).isTrue(); - - try { - assertThat(resolver.resolveArgument(parameter, null, request, null)).isEqualTo(position); - } catch (Exception e) { - throw new RuntimeException(e); - } + assertThat(sut.supportsParameter(parameter)).isTrue(); + assertThat(sut.resolveArgument(parameter, null, request, null)).isEqualTo(position); } - private static MethodParameter getParameterOfMethod(String name) { return TestUtils.getParameterOfMethod(Controller.class, name, OffsetScrollPosition.class); } - private static NativeWebRequest getRequestWithOffset(@Nullable OffsetScrollPosition position, @Nullable String qualifier) { + private static MethodParameter getOptionalParameterOfMethod(String name) { + return TestUtils.getParameterOfMethod(Controller.class, name, Optional.class); + } + + private static NativeWebRequest getRequestWithOffset(@Nullable OffsetScrollPosition position, + @Nullable String qualifier) { var request = new MockHttpServletRequest(); @@ -174,9 +194,11 @@ class OffsetScrollPositionHandlerMethodArgumentResolverUnitTests { void supportedMethod(OffsetScrollPosition offset); + void supportedMethodWithOptional(Optional offset); + void unsupportedMethod(String string); - void qualifiedOffset(@Qualifier("qual") OffsetScrollPosition offset); + void qualifiedOffset(@Qualifier("hello") OffsetScrollPosition offset); void emptyQualifier(@Qualifier OffsetScrollPosition offset); diff --git a/src/test/java/org/springframework/data/web/ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests.java index 094d9aa70..55ba5bbf1 100755 --- a/src/test/java/org/springframework/data/web/ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 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. @@ -15,7 +15,10 @@ */ package org.springframework.data.web; -import org.junit.jupiter.api.BeforeAll; +import static org.assertj.core.api.Assertions.*; + +import java.util.Optional; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.MethodParameter; @@ -26,41 +29,40 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.util.StringUtils; -import static org.assertj.core.api.Assertions.assertThat; - /** * Unit tests for {@link ReactiveOffsetScrollPositionHandlerMethodArgumentResolver}. * - * @since 3.2 * @author Yanming Zhou + * @author Mark Paluch */ class ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests { - static MethodParameter PARAMETER; + static final MethodParameter PARAMETER = getParameterOfMethod("supportedMethod"); + static final MethodParameter OPTIONAL_PARAMETER = getOptionalParameterOfMethod("supportedMethodWithOptional"); - @BeforeAll - static void setUp() throws Exception { - PARAMETER = getParameterOfMethod("supportedMethod"); + final ReactiveOffsetScrollPositionHandlerMethodArgumentResolver sut = new ReactiveOffsetScrollPositionHandlerMethodArgumentResolver(); + + @Test // GH-2856 + void supportsOffsetScrollPositionParameter() { + assertThat(sut.supportsParameter(PARAMETER)).isTrue(); } - @Test - void supportsSortParameter() { - - var resolver = new ReactiveOffsetScrollPositionHandlerMethodArgumentResolver(); - - assertThat(resolver.supportsParameter(PARAMETER)).isTrue(); + @Test // GH-2856 + void supportsOptionalOffsetScrollPositionParameter() { + assertThat(sut.supportsParameter(OPTIONAL_PARAMETER)).isTrue(); } - @Test - void fallbackToDefaultOffset() { + @Test // GH-2856 + void fallbackToNullOffset() { var parameter = TestUtils.getParameterOfMethod(Controller.class, "unsupportedMethod", String.class); var request = MockServerHttpRequest.get("/foo").build(); var position = resolveOffset(request, parameter); - assertThat(position).isEqualTo(ScrollPosition.offset()); + + assertThat(position).isNull(); } - @Test + @Test // GH-2856 void discoversOffsetFromRequest() { var reference = ScrollPosition.offset(5); @@ -68,7 +70,34 @@ class ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests { assertSupportedAndResolvedTo(getRequestWithOffset(reference, null), PARAMETER, reference); } - @Test + @Test // GH-2856 + void returnsNullIfNotSpecified() { + + var request = MockServerHttpRequest.get("/foo").build(); + var position = resolveOffset(request, PARAMETER); + + assertThat(position).isNull(); + } + + @Test // GH-2856 + void returnsOptionalParameterFromRequest() { + + var request = MockServerHttpRequest.get("/foo?offset=5").build(); + var position = resolveOffset(request, OPTIONAL_PARAMETER); + + assertThat(position).isEqualTo(Optional.of(ScrollPosition.offset(5))); + } + + @Test // GH-2856 + void returnsEmptyOptionalIfNotSpecified() { + + var request = MockServerHttpRequest.get("/foo").build(); + var position = resolveOffset(request, OPTIONAL_PARAMETER); + + assertThat(position).isEqualTo(Optional.empty()); + } + + @Test // GH-2856 void discoversOffsetFromRequestWithMultipleParams() { var request = MockServerHttpRequest.get("/foo?offset=5&offset=6").build(); @@ -76,40 +105,32 @@ class ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests { assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset(5)); } - @Test + @Test // GH-2856 void discoversQualifiedOffsetFromRequest() { var parameter = getParameterOfMethod("qualifiedOffset"); var reference = ScrollPosition.offset(5); - assertSupportedAndResolvedTo(getRequestWithOffset(reference, "qual"), parameter, reference); + assertSupportedAndResolvedTo(getRequestWithOffset(reference, "hello"), parameter, reference); } - @Test - void returnsDefaultForOffsetParamSetToNothing() { - - var request = MockServerHttpRequest.get("/foo").build(); - - assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset()); - } - - @Test - void returnsDefaultForEmptyOffsetParam() { + @Test // GH-2856 + void returnsNullForEmptyOffsetParam() { var request = MockServerHttpRequest.get("/foo?offset=").build(); - assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset()); + assertThat(resolveOffset(request, PARAMETER)).isNull(); } - @Test - void returnsDefaultForOffsetParamIsInvalidProperty() { + @Test // GH-2856 + void returnsNullForOffsetParamIsInvalidProperty() { var request = MockServerHttpRequest.get("/foo?offset=invalid_number").build(); - assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset()); + assertThat(resolveOffset(request, PARAMETER)).isNull(); } - @Test + @Test // GH-2856 void emptyQualifierIsUsedInParameterLookup() { var parameter = getParameterOfMethod("emptyQualifier"); @@ -118,7 +139,7 @@ class ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests { assertSupportedAndResolvedTo(getRequestWithOffset(reference, ""), parameter, reference); } - @Test + @Test // GH-2856 void mergedQualifierIsUsedInParameterLookup() { var parameter = getParameterOfMethod("mergedQualifier"); @@ -127,47 +148,51 @@ class ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests { assertSupportedAndResolvedTo(getRequestWithOffset(reference, "merged"), parameter, reference); } - private static OffsetScrollPosition resolveOffset(MockServerHttpRequest request, MethodParameter parameter) { + @Nullable + private static Object resolveOffset(MockServerHttpRequest request, MethodParameter parameter) { var resolver = new ReactiveOffsetScrollPositionHandlerMethodArgumentResolver(); return resolver.resolveArgumentValue(parameter, null, MockServerWebExchange.from(request)); } - private static void assertSupportedAndResolvedTo(MockServerHttpRequest request, MethodParameter parameter, OffsetScrollPosition position) { + private void assertSupportedAndResolvedTo(MockServerHttpRequest request, MethodParameter parameter, + OffsetScrollPosition position) { - var resolver = new ReactiveOffsetScrollPositionHandlerMethodArgumentResolver(); - assertThat(resolver.supportsParameter(parameter)).isTrue(); + assertThat(sut.supportsParameter(parameter)).isTrue(); - try { - var resolved = resolveOffset(request, parameter); - assertThat(resolved).isEqualTo(position); - } catch (Exception e) { - throw new RuntimeException(e); - } + var resolved = resolveOffset(request, parameter); + assertThat(resolved).isEqualTo(position); } - private static MethodParameter getParameterOfMethod(String name) { return TestUtils.getParameterOfMethod(Controller.class, name, OffsetScrollPosition.class); } - private static MockServerHttpRequest getRequestWithOffset(@Nullable OffsetScrollPosition position, @Nullable String qualifier) { + private static MethodParameter getOptionalParameterOfMethod(String name) { + return TestUtils.getParameterOfMethod(Controller.class, name, Optional.class); + } + + private static MockServerHttpRequest getRequestWithOffset(@Nullable OffsetScrollPosition position, + @Nullable String qualifier) { if (position == null) { return TestUtils.getWebfluxRequest(); } String parameterName = StringUtils.hasLength(qualifier) ? qualifier + "_offset" : "offset"; - return MockServerHttpRequest.get(String.format("foo?%s=%s", parameterName, String.valueOf(position.getOffset()))).build(); + return MockServerHttpRequest.get(String.format("foo?%s=%s", parameterName, String.valueOf(position.getOffset()))) + .build(); } interface Controller { void supportedMethod(OffsetScrollPosition offset); + void supportedMethodWithOptional(Optional offset); + void unsupportedMethod(String string); - void qualifiedOffset(@Qualifier("qual") OffsetScrollPosition offset); + void qualifiedOffset(@Qualifier("hello") OffsetScrollPosition offset); void emptyQualifier(@Qualifier OffsetScrollPosition offset);