Skip to content

Commit 4e378cb

Browse files
committed
feat(gax): add getFirstHeader to HttpHeadersUtils
1 parent 0776d87 commit 4e378cb

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpHeadersUtils.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.google.api.client.util.FieldInfo;
3535
import com.google.api.core.InternalApi;
3636
import com.google.common.collect.ImmutableList;
37+
import com.google.common.collect.Iterables;
3738
import java.util.List;
3839
import java.util.Map;
3940
import org.jspecify.annotations.NullMarked;
@@ -52,6 +53,34 @@ public class HttpHeadersUtils {
5253
return null;
5354
}
5455

56+
/**
57+
* Retrieves the single string value of a header by case-insensitive name from a headers map.
58+
*
59+
* @param headers the map of header names to header values
60+
* @param name the case-insensitive header name to look up
61+
* @return the single string value of the header, or {@code null} if not found
62+
* @throws IllegalArgumentException if multiple values are present for the header
63+
*/
64+
public static @Nullable String getSingleHeader(Map<String, Object> headers, String name) {
65+
return headers.entrySet().stream()
66+
.filter(entry -> name.equalsIgnoreCase(entry.getKey()))
67+
.findFirst()
68+
.map(entry -> extractSingleString(entry.getValue()))
69+
.orElse(null);
70+
}
71+
72+
private static @Nullable String extractSingleString(@Nullable Object headerValue) {
73+
if (headerValue == null) {
74+
return null;
75+
}
76+
// HttpHeaders stores known headers as Lists
77+
if (headerValue instanceof Iterable) {
78+
Object onlyElement = Iterables.getOnlyElement((Iterable<?>) headerValue, null);
79+
return onlyElement != null ? onlyElement.toString() : null;
80+
}
81+
return headerValue.toString();
82+
}
83+
5584
public static HttpHeaders setHeaders(HttpHeaders headers, Map<String, String> headersMap) {
5685
for (Map.Entry<String, String> entry : headersMap.entrySet()) {
5786
setHeader(headers, entry.getKey(), entry.getValue());

sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpHeadersUtilsTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@
2929
*/
3030
package com.google.api.gax.httpjson;
3131

32+
import static com.google.common.truth.Truth.assertThat;
3233
import static org.junit.jupiter.api.Assertions.assertEquals;
3334
import static org.junit.jupiter.api.Assertions.assertNull;
35+
import static org.junit.jupiter.api.Assertions.assertThrows;
3436

3537
import com.google.api.client.http.HttpHeaders;
3638
import com.google.common.collect.ImmutableList;
3739
import com.google.common.collect.ImmutableMap;
40+
import java.util.Collections;
41+
import java.util.HashMap;
3842
import java.util.Map;
3943
import org.junit.jupiter.api.Test;
4044

@@ -130,4 +134,56 @@ void testGetUserAgentValue() {
130134
headersMap = ImmutableMap.of("Custom-Header", "CustomHeader", "accept", "Accept");
131135
assertNull(HttpHeadersUtils.getUserAgentValue(headersMap));
132136
}
137+
138+
@Test
139+
void getSingleHeader_success() {
140+
Map<String, Object> headers =
141+
ImmutableMap.of(
142+
"X-Goog-Upload-URL",
143+
"https://test.googleapis.com/upload",
144+
"Location",
145+
ImmutableList.of("https://redirect.url"),
146+
"Content-Length",
147+
1024L);
148+
149+
// Case-insensitive & exact lookup
150+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "X-Goog-Upload-URL"))
151+
.isEqualTo("https://test.googleapis.com/upload");
152+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "x-goog-upload-url"))
153+
.isEqualTo("https://test.googleapis.com/upload");
154+
155+
// Single-element iterable
156+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "location"))
157+
.isEqualTo("https://redirect.url");
158+
159+
// Non-string object conversion
160+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "content-length")).isEqualTo("1024");
161+
}
162+
163+
@Test
164+
void getSingleHeader_multipleElements_throwsIllegalArgumentException() {
165+
Map<String, Object> headers =
166+
ImmutableMap.of(
167+
"Location", ImmutableList.of("https://redirect.url", "https://secondary.url"));
168+
169+
assertThrows(
170+
IllegalArgumentException.class,
171+
() -> HttpHeadersUtils.getSingleHeader(headers, "location"));
172+
}
173+
174+
@Test
175+
void getSingleHeader_missingOrEmpty_returnsNull() {
176+
Map<String, Object> headers = new HashMap<>();
177+
headers.put("Existing-Header", "value");
178+
headers.put("Null-Value-Header", null);
179+
headers.put("Empty-List-Header", Collections.emptyList());
180+
headers.put("Null-First-Header", Collections.singletonList(null));
181+
headers.put(null, "some-value");
182+
183+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "non-existent-header")).isNull();
184+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "null-value-header")).isNull();
185+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "empty-list-header")).isNull();
186+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "null-first-header")).isNull();
187+
assertThat(HttpHeadersUtils.getSingleHeader(headers, "any-header")).isNull();
188+
}
133189
}

0 commit comments

Comments
 (0)