From 431e3dae2d34ebc06a92c3b380d8fba56111ece4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Fri, 13 Jan 2023 16:59:17 +0100 Subject: [PATCH 1/5] Deprecate HttpStatus 103 CHECKPOINT in favor of new EARLY_HINTS This commit takes rfc8297 into account and introduces a newer code 103 HttpStatus value which uses `Early Hints` as the more correct reason phrase, deprecating the outdated `CHECKPOINT` enum value for 103. Additionally: - `HttpStatus.valueOf(103)` will return the new enum value - `HttpStatusCode#isSameCodeAs(HttpStatusCode)` is introduced to ease comparison of deprecated enums vs their newer counterparts (or any instance of a more generic `HttpStatusCode`) by comparing the integer `value()` - `HttpStatusTests` covers the new deprecation as well as the three previously deprecated codes, including a check with the above new method to ensure they have comparable integer values Supersedes and Closes gh-27960 --- .../org/springframework/http/HttpStatus.java | 7 +++ .../springframework/http/HttpStatusCode.java | 10 +++++ .../springframework/http/HttpStatusTests.java | 43 +++++++++++++++++-- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/HttpStatus.java b/spring-web/src/main/java/org/springframework/http/HttpStatus.java index 7d878f936d8..205462e8cd4 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpStatus.java +++ b/spring-web/src/main/java/org/springframework/http/HttpStatus.java @@ -50,11 +50,18 @@ public enum HttpStatus implements HttpStatusCode { * @see WebDAV */ PROCESSING(102, Series.INFORMATIONAL, "Processing"), + /** + * {code 103 Early Hints}. + * @see An HTTP Status Code for Indicating Hints + */ + EARLY_HINTS(103, Series.INFORMATIONAL, "Early Hints"), /** * {@code 103 Checkpoint}. * @see A proposal for supporting * resumable POST/PUT HTTP requests in HTTP/1.0 + * @deprecated in favor of {@link #EARLY_HINTS} which will be returned from {@code HttpStatus.valueOf(103)} */ + @Deprecated CHECKPOINT(103, Series.INFORMATIONAL, "Checkpoint"), // 2xx Success diff --git a/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java b/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java index 380ba2aa57c..c4f7ebfa24f 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java +++ b/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java @@ -76,6 +76,16 @@ public sealed interface HttpStatusCode extends Serializable permits DefaultHttpS */ boolean isError(); + /** + * Whether this {@code HttpStatusCode} shares the same integer {@link #value() value} as the other status code. + *

Useful for functional comparisons that take deprecated aliases into account + * (e.g. in place of {@link HttpStatus#equals(Object) HttpStatus enum equality}). + * @param other the other {@code HttpStatusCode} to compare + * @return true if the two {@code HttpStatusCode} share the same integer {@code value()}, false otherwise + */ + default boolean isSameCodeAs(HttpStatusCode other) { + return value() == other.value(); + } /** * Return an {@code HttpStatusCode} object for the given integer value. diff --git a/spring-web/src/test/java/org/springframework/http/HttpStatusTests.java b/spring-web/src/test/java/org/springframework/http/HttpStatusTests.java index cec1f9ad960..473c6ab0ce2 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpStatusTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpStatusTests.java @@ -17,12 +17,19 @@ package org.springframework.http; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; /** * @author Arjen Poutsma @@ -37,7 +44,7 @@ void createStatusCodes() { statusCodes.put(100, "CONTINUE"); statusCodes.put(101, "SWITCHING_PROTOCOLS"); statusCodes.put(102, "PROCESSING"); - statusCodes.put(103, "CHECKPOINT"); + statusCodes.put(103, "EARLY_HINTS"); statusCodes.put(200, "OK"); statusCodes.put(201, "CREATED"); @@ -120,8 +127,7 @@ void fromMapToEnum() { void fromEnumToMap() { for (HttpStatus status : HttpStatus.values()) { int code = status.value(); - // The following status codes have more than one corresponding HttpStatus enum constant. - if (code == 302 || code == 413 || code == 414) { + if (DEPRECATED_CODES.contains(status)) { continue; } assertThat(statusCodes).as("Map has no value for [" + code + "]").containsKey(code); @@ -138,4 +144,35 @@ void allStatusSeriesShouldMatchExpectations() { } } + @ParameterizedTest(name = "[{index}] code {0}") + @MethodSource("codesWithAliases") + void codeWithDeprecatedAlias(int code, HttpStatus expected, HttpStatus outdated) { + HttpStatus resolved = HttpStatus.valueOf(code); + assertThat(resolved) + .as("HttpStatus.valueOf(" + code + ")") + .isSameAs(expected) + .isNotEqualTo(outdated); + assertThat(outdated.isSameCodeAs(resolved)) + .as("outdated isSameCodeAs(resolved)") + .isTrue(); + assertThat(outdated.value()) + .as("outdated value()") + .isEqualTo(resolved.value()); + } + + private static final Set DEPRECATED_CODES = codesWithAliases() + .stream() + .map(args -> (HttpStatus) args.get()[2]) + .collect(Collectors.toUnmodifiableSet()); + + @SuppressWarnings("deprecation") + static List codesWithAliases() { + return List.of( + arguments(103, HttpStatus.EARLY_HINTS, HttpStatus.CHECKPOINT), + arguments(302, HttpStatus.FOUND, HttpStatus.MOVED_TEMPORARILY), + arguments(413, HttpStatus.PAYLOAD_TOO_LARGE, HttpStatus.REQUEST_ENTITY_TOO_LARGE), + arguments(414, HttpStatus.URI_TOO_LONG, HttpStatus.REQUEST_URI_TOO_LONG) + ); + } + } From 3296b1d3e1a7dff2b2b3eb3ee7ad304a638de414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Fri, 13 Jan 2023 17:47:07 +0100 Subject: [PATCH 2/5] Add MockMVC matcher and kotlin DSL for isEarlyHints --- .../test/web/servlet/result/StatusResultMatchers.java | 10 ++++++++++ .../test/web/servlet/result/StatusResultMatchersDsl.kt | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java index 9c2b01e942f..8215e26098b 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java @@ -145,8 +145,18 @@ public ResultMatcher isProcessing() { /** * Assert the response status code is {@code HttpStatus.CHECKPOINT} (103). + * @see #isEarlyHints() + * @deprecated in favor of {@link #isEarlyHints()} */ + @Deprecated public ResultMatcher isCheckpoint() { + return isEarlyHints(); + } + + /** + * Assert the response status code is {@code HttpStatus.EARLY_HINTS} (103). + */ + public ResultMatcher isEarlyHints() { return matcher(HttpStatus.valueOf(103)); } diff --git a/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt b/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt index ef5b7748290..5870e8c59da 100644 --- a/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt +++ b/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt @@ -115,12 +115,20 @@ class StatusResultMatchersDsl internal constructor (private val actions: ResultA } /** - * @see StatusResultMatchers.isCheckpoint + * @see isEarlyHints */ + @Deprecated("use isEarlyHints() instead", replaceWith= ReplaceWith("isEarlyHints()")) fun isCheckpoint() { actions.andExpect(matchers.isCheckpoint()) } + /** + * @see StatusResultMatchers.isEarlyHints + */ + fun isEarlyHints() { + actions.andExpect(matchers.isEarlyHints()) + } + /** * @see StatusResultMatchers.isOk */ From e90989854b6ee9b3d1024d054deb1197ed5035cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Fri, 13 Jan 2023 17:52:07 +0100 Subject: [PATCH 3/5] Avoid using the deprecated matcher in kotlin DSL isCheckpoint --- .../test/web/servlet/result/StatusResultMatchersDsl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt b/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt index 5870e8c59da..11c9d875000 100644 --- a/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt +++ b/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt @@ -119,7 +119,7 @@ class StatusResultMatchersDsl internal constructor (private val actions: ResultA */ @Deprecated("use isEarlyHints() instead", replaceWith= ReplaceWith("isEarlyHints()")) fun isCheckpoint() { - actions.andExpect(matchers.isCheckpoint()) + isEarlyHints() } /** From 034dacd2ba89700e6936855db58f4899b2030412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Fri, 13 Jan 2023 19:54:56 +0100 Subject: [PATCH 4/5] Add since info to Deprecated, use Suppress at usage sites --- .../test/web/servlet/result/StatusResultMatchers.java | 2 +- .../test/web/servlet/result/StatusResultMatchersDsl.kt | 3 ++- .../src/main/java/org/springframework/http/HttpStatus.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java index 8215e26098b..ee1a58163ab 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java @@ -148,7 +148,7 @@ public ResultMatcher isProcessing() { * @see #isEarlyHints() * @deprecated in favor of {@link #isEarlyHints()} */ - @Deprecated + @Deprecated(since = "6.0") public ResultMatcher isCheckpoint() { return isEarlyHints(); } diff --git a/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt b/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt index 11c9d875000..dc05aba4d11 100644 --- a/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt +++ b/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/StatusResultMatchersDsl.kt @@ -119,7 +119,8 @@ class StatusResultMatchersDsl internal constructor (private val actions: ResultA */ @Deprecated("use isEarlyHints() instead", replaceWith= ReplaceWith("isEarlyHints()")) fun isCheckpoint() { - isEarlyHints() + @Suppress("DEPRECATION") + actions.andExpect(matchers.isCheckpoint()) } /** diff --git a/spring-web/src/main/java/org/springframework/http/HttpStatus.java b/spring-web/src/main/java/org/springframework/http/HttpStatus.java index 205462e8cd4..38d33b184ab 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpStatus.java +++ b/spring-web/src/main/java/org/springframework/http/HttpStatus.java @@ -61,7 +61,7 @@ public enum HttpStatus implements HttpStatusCode { * resumable POST/PUT HTTP requests in HTTP/1.0 * @deprecated in favor of {@link #EARLY_HINTS} which will be returned from {@code HttpStatus.valueOf(103)} */ - @Deprecated + @Deprecated(since = "6.0") CHECKPOINT(103, Series.INFORMATIONAL, "Checkpoint"), // 2xx Success From 11324584f5260767129353761c3cb0c4e36dda9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Mon, 16 Jan 2023 11:06:41 +0100 Subject: [PATCH 5/5] Polish javadoc --- .../main/java/org/springframework/http/HttpStatusCode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java b/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java index c4f7ebfa24f..0b16b6ca2a9 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java +++ b/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java @@ -78,8 +78,8 @@ public sealed interface HttpStatusCode extends Serializable permits DefaultHttpS /** * Whether this {@code HttpStatusCode} shares the same integer {@link #value() value} as the other status code. - *

Useful for functional comparisons that take deprecated aliases into account - * (e.g. in place of {@link HttpStatus#equals(Object) HttpStatus enum equality}). + *

Useful for comparisons that take deprecated aliases into account or compare arbitrary implementations + * of {@code HttpStatusCode} (e.g. in place of {@link HttpStatus#equals(Object) HttpStatus enum equality}). * @param other the other {@code HttpStatusCode} to compare * @return true if the two {@code HttpStatusCode} share the same integer {@code value()}, false otherwise */