From 0a47cb00129dd5fbffdf061a2e0ea5f49e19fe1d Mon Sep 17 00:00:00 2001 From: whowes Date: Tue, 18 Aug 2026 17:26:48 +0000 Subject: [PATCH] feat(gax): add getFirstHeader to HttpHeadersUtils --- .../api/gax/httpjson/HttpHeadersUtils.java | 29 ++++++++++ .../gax/httpjson/HttpHeadersUtilsTest.java | 56 +++++++++++++++++++ 2 files changed, 85 insertions(+) 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..e44e674473b9 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,34 @@ public class HttpHeadersUtils { return null; } + /** + * Retrieves the single 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 single string value of the header, or {@code null} if not found + * @throws IllegalArgumentException if multiple values are present for the header + */ + public static @Nullable String getSingleHeader(Map headers, String name) { + for (Map.Entry entry : headers.entrySet()) { + if (name.equalsIgnoreCase(entry.getKey())) { + return extractSingleString(entry.getValue()); + } + } + return null; + } + + private static @Nullable String extractSingleString(@Nullable Object headerValue) { + if (headerValue == null) { + return null; + } + if (headerValue instanceof Iterable) { + Object onlyElement = Iterables.getOnlyElement((Iterable) headerValue, null); + return onlyElement != null ? onlyElement.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..84538acb59a4 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,16 @@ */ 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 static org.junit.jupiter.api.Assertions.assertThrows; 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.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -130,4 +134,56 @@ void testGetUserAgentValue() { headersMap = ImmutableMap.of("Custom-Header", "CustomHeader", "accept", "Accept"); assertNull(HttpHeadersUtils.getUserAgentValue(headersMap)); } + + @Test + void getSingleHeader_success() { + Map headers = + ImmutableMap.of( + "X-Goog-Upload-URL", + "https://test.googleapis.com/upload", + "Location", + ImmutableList.of("https://redirect.url"), + "Content-Length", + 1024L); + + // Case-insensitive & exact lookup + assertThat(HttpHeadersUtils.getSingleHeader(headers, "X-Goog-Upload-URL")) + .isEqualTo("https://test.googleapis.com/upload"); + assertThat(HttpHeadersUtils.getSingleHeader(headers, "x-goog-upload-url")) + .isEqualTo("https://test.googleapis.com/upload"); + + // Single-element iterable + assertThat(HttpHeadersUtils.getSingleHeader(headers, "location")) + .isEqualTo("https://redirect.url"); + + // Non-string object conversion + assertThat(HttpHeadersUtils.getSingleHeader(headers, "content-length")).isEqualTo("1024"); + } + + @Test + void getSingleHeader_multipleElements_throwsIllegalArgumentException() { + Map headers = + ImmutableMap.of( + "Location", ImmutableList.of("https://redirect.url", "https://secondary.url")); + + assertThrows( + IllegalArgumentException.class, + () -> HttpHeadersUtils.getSingleHeader(headers, "location")); + } + + @Test + void getSingleHeader_missingOrEmpty_returnsNull() { + Map headers = new HashMap<>(); + headers.put("Existing-Header", "value"); + headers.put("Null-Value-Header", null); + headers.put("Empty-List-Header", Collections.emptyList()); + headers.put("Null-First-Header", Collections.singletonList(null)); + headers.put(null, "some-value"); + + assertThat(HttpHeadersUtils.getSingleHeader(headers, "non-existent-header")).isNull(); + assertThat(HttpHeadersUtils.getSingleHeader(headers, "null-value-header")).isNull(); + assertThat(HttpHeadersUtils.getSingleHeader(headers, "empty-list-header")).isNull(); + assertThat(HttpHeadersUtils.getSingleHeader(headers, "null-first-header")).isNull(); + assertThat(HttpHeadersUtils.getSingleHeader(headers, "any-header")).isNull(); + } }