diff --git a/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpHeadersUtils.java b/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpHeadersUtils.java index 66f89451225f..9c58a12fd41d 100644 --- a/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpHeadersUtils.java +++ b/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpHeadersUtils.java @@ -34,6 +34,7 @@ import com.google.api.client.util.FieldInfo; import com.google.api.core.InternalApi; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import java.util.List; import java.util.Map; import org.jspecify.annotations.NullMarked; @@ -52,6 +53,33 @@ public class HttpHeadersUtils { return null; } + /** + * Retrieves the first string value of a header by case-insensitive name from a headers map. + * + * @param headers the map of header names to header values + * @param name the case-insensitive header name to look up + * @return the first string value of the header, or {@code null} if not found + */ + public static @Nullable String getFirstHeader(Map headers, String name) { + for (Map.Entry entry : headers.entrySet()) { + if (entry.getKey().equalsIgnoreCase(name)) { + return extractFirstString(entry.getValue()); + } + } + return null; + } + + private static @Nullable String extractFirstString(@Nullable Object headerValue) { + if (headerValue == null) { + return null; + } + if (headerValue instanceof Iterable) { + Object firstElement = Iterables.getFirst((Iterable) headerValue, null); + return firstElement != null ? firstElement.toString() : null; + } + return headerValue.toString(); + } + public static HttpHeaders setHeaders(HttpHeaders headers, Map headersMap) { for (Map.Entry entry : headersMap.entrySet()) { setHeader(headers, entry.getKey(), entry.getValue()); diff --git a/sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpHeadersUtilsTest.java b/sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpHeadersUtilsTest.java index 247f2b29072f..d0f0afbca05f 100644 --- a/sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpHeadersUtilsTest.java +++ b/sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpHeadersUtilsTest.java @@ -29,12 +29,14 @@ */ package com.google.api.gax.httpjson; +import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import com.google.api.client.http.HttpHeaders; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import java.util.Collections; import java.util.Map; import org.junit.jupiter.api.Test; @@ -130,4 +132,68 @@ void testGetUserAgentValue() { headersMap = ImmutableMap.of("Custom-Header", "CustomHeader", "accept", "Accept"); assertNull(HttpHeadersUtils.getUserAgentValue(headersMap)); } + + @Test + void getFirstHeader_exactMatch_returnsValue() { + Map headers = + ImmutableMap.of("X-Goog-Upload-URL", "https://test.googleapis.com/upload"); + + assertThat(HttpHeadersUtils.getFirstHeader(headers, "X-Goog-Upload-URL")) + .isEqualTo("https://test.googleapis.com/upload"); + } + + @Test + void getFirstHeader_caseInsensitiveMatch_returnsValue() { + Map headers = + ImmutableMap.of("X-Goog-Upload-URL", "https://test.googleapis.com/upload"); + + assertThat(HttpHeadersUtils.getFirstHeader(headers, "x-goog-upload-url")) + .isEqualTo("https://test.googleapis.com/upload"); + } + + @Test + void getFirstHeader_iterableValue_returnsFirstElement() { + Map headers = + ImmutableMap.of( + "Location", ImmutableList.of("https://redirect.url", "https://secondary.url")); + + assertThat(HttpHeadersUtils.getFirstHeader(headers, "location")) + .isEqualTo("https://redirect.url"); + } + + @Test + void getFirstHeader_nonStringValue_convertsToString() { + Map headers = ImmutableMap.of("Content-Length", 1024L); + + assertThat(HttpHeadersUtils.getFirstHeader(headers, "content-length")).isEqualTo("1024"); + } + + @Test + void getFirstHeader_emptyIterable_returnsNull() { + Map headers = ImmutableMap.of("Empty-List-Header", ImmutableList.of()); + + assertThat(HttpHeadersUtils.getFirstHeader(headers, "empty-list-header")).isNull(); + } + + @Test + void getFirstHeader_nullFirstElementInIterable_returnsNull() { + Map headers = + Collections.singletonMap("Null-First-Header", Collections.singletonList(null)); + + assertThat(HttpHeadersUtils.getFirstHeader(headers, "null-first-header")).isNull(); + } + + @Test + void getFirstHeader_nullValueInMap_returnsNull() { + Map headers = Collections.singletonMap("Null-Value-Header", null); + + assertThat(HttpHeadersUtils.getFirstHeader(headers, "null-value-header")).isNull(); + } + + @Test + void getFirstHeader_headerNotFound_returnsNull() { + Map headers = ImmutableMap.of("Existing-Header", "value"); + + assertThat(HttpHeadersUtils.getFirstHeader(headers, "non-existent-header")).isNull(); + } }