From 8ec49c9d855efb187f61ac7a245ab7fb3519706b Mon Sep 17 00:00:00 2001 From: mudaafi Date: Wed, 1 Jul 2026 05:50:09 +0800 Subject: [PATCH 1/4] expose headers --- .../io/constructor/client/ConstructorIO.java | 145 +++++++++++------- .../client/models/AutocompleteResponse.java | 13 ++ .../client/models/SearchResponse.java | 14 ++ ...structorIOAutocompleteUrlEncodingTest.java | 41 ++++- .../ConstructorIOSearchUrlEncodingTest.java | 32 ++++ 5 files changed, 181 insertions(+), 64 deletions(-) diff --git a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java index 22f9c68c..5a6848c6 100644 --- a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java +++ b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java @@ -926,8 +926,11 @@ public String retrieveVariationsAsJson(VariationsRequest req) throws Constructor public AutocompleteResponse autocomplete(AutocompleteRequest req, UserInfo userInfo) throws ConstructorException { try { - String json = autocompleteAsJSON(req, userInfo); - return createAutocompleteResponse(json); + Request request = createAutocompleteRequest(req, userInfo); + Response response = clientWithRetry.newCall(request).execute(); + Map> headers = response.headers().toMultimap(); + String json = getResponseBody(response); + return createAutocompleteResponse(json, headers); } catch (Exception exception) { throw new ConstructorException(exception); } @@ -948,74 +951,83 @@ public AutocompleteResponse autocomplete(AutocompleteRequest req, UserInfo userI public String autocompleteAsJSON(AutocompleteRequest req, UserInfo userInfo) throws ConstructorException { try { - List paths = Arrays.asList("autocomplete", req.getQuery()); - HttpUrl url = (userInfo == null) ? this.makeUrl(paths) : this.makeUrl(paths, userInfo); + Request request = createAutocompleteRequest(req, userInfo); + Response response = clientWithRetry.newCall(request).execute(); + return getResponseBody(response); + } catch (Exception exception) { + throw new ConstructorException(exception); + } + } - for (Map.Entry entry : req.getResultsPerSection().entrySet()) { - String section = entry.getKey(); - String count = String.valueOf(entry.getValue()); - url = url.newBuilder().addQueryParameter("num_results_" + section, count).build(); - } + /** + * Creates an autocomplete OkHttp request + * + * @param req the autocomplete request + * @param userInfo optional information about the user + * @return an autocomplete OkHttp request + * @throws ConstructorException + */ + protected Request createAutocompleteRequest(AutocompleteRequest req, UserInfo userInfo) + throws ConstructorException, UnsupportedEncodingException { + List paths = Arrays.asList("autocomplete", req.getQuery()); + HttpUrl url = (userInfo == null) ? this.makeUrl(paths) : this.makeUrl(paths, userInfo); - for (String hiddenField : req.getHiddenFields()) { + for (Map.Entry entry : req.getResultsPerSection().entrySet()) { + String section = entry.getKey(); + String count = String.valueOf(entry.getValue()); + url = url.newBuilder().addQueryParameter("num_results_" + section, count).build(); + } + + for (String hiddenField : req.getHiddenFields()) { + url = + url.newBuilder() + .addQueryParameter("fmt_options[hidden_fields]", hiddenField) + .build(); + } + + for (String filterName : req.getFilters().keySet()) { + for (String facetValue : req.getFilters().get(filterName)) { url = url.newBuilder() - .addQueryParameter("fmt_options[hidden_fields]", hiddenField) + .addQueryParameter("filters[" + filterName + "]", facetValue) .build(); } + } - for (String filterName : req.getFilters().keySet()) { - for (String facetValue : req.getFilters().get(filterName)) { + for (String sectionName : req.getFiltersPerSection().keySet()) { + for (String filterName : req.getFiltersPerSection().get(sectionName).keySet()) { + for (String facetValue : + req.getFiltersPerSection().get(sectionName).get(filterName)) { url = url.newBuilder() - .addQueryParameter("filters[" + filterName + "]", facetValue) + .addQueryParameter( + "filters" + + "[" + + sectionName + + "]" + + "[" + + filterName + + "]", + facetValue) .build(); } } + } - for (String sectionName : req.getFiltersPerSection().keySet()) { - for (String filterName : req.getFiltersPerSection().get(sectionName).keySet()) { - for (String facetValue : - req.getFiltersPerSection().get(sectionName).get(filterName)) { - url = - url.newBuilder() - .addQueryParameter( - "filters" - + "[" - + sectionName - + "]" - + "[" - + filterName - + "]", - facetValue) - .build(); - } - } - } - - if (req.getVariationsMap() != null) { - String variationsMapJson = new Gson().toJson(req.getVariationsMap()); - url = - url.newBuilder() - .addQueryParameter("variations_map", variationsMapJson) - .build(); - } - - if (req.getPreFilterExpression() != null) { - url = - url.newBuilder() - .addQueryParameter( - "pre_filter_expression", req.getPreFilterExpression()) - .build(); - } - - Request request = this.makeUserRequestBuilder(userInfo).url(url).get().build(); + if (req.getVariationsMap() != null) { + String variationsMapJson = new Gson().toJson(req.getVariationsMap()); + url = url.newBuilder().addQueryParameter("variations_map", variationsMapJson).build(); + } - Response response = clientWithRetry.newCall(request).execute(); - return getResponseBody(response); - } catch (Exception exception) { - throw new ConstructorException(exception); + if (req.getPreFilterExpression() != null) { + url = + url.newBuilder() + .addQueryParameter( + "pre_filter_expression", req.getPreFilterExpression()) + .build(); } + + return this.makeUserRequestBuilder(userInfo).url(url).get().build(); } /** @@ -1171,8 +1183,9 @@ public SearchResponse search(SearchRequest req, UserInfo userInfo) throws Constr try { Request request = createSearchRequest(req, userInfo); Response response = clientWithRetry.newCall(request).execute(); + Map> headers = response.headers().toMultimap(); String json = getResponseBody(response); - return createSearchResponse(json); + return createSearchResponse(json, headers); } catch (Exception exception) { throw new ConstructorException(exception); } @@ -2200,6 +2213,11 @@ protected String getVersion() { * to do it in a Gson Type Adapter. */ protected static AutocompleteResponse createAutocompleteResponse(String string) { + return createAutocompleteResponse(string, null); + } + + protected static AutocompleteResponse createAutocompleteResponse( + String string, Map> headers) { JSONObject json = new JSONObject(string); JSONObject sections = json.getJSONObject("sections"); for (Object sectionKey : sections.keySet()) { @@ -2208,7 +2226,11 @@ protected static AutocompleteResponse createAutocompleteResponse(String string) moveMetadataOutOfResultData(results); } String transformed = json.toString(); - return new Gson().fromJson(transformed, AutocompleteResponse.class); + AutocompleteResponse autocompleteResponse = + new Gson().fromJson(transformed, AutocompleteResponse.class); + autocompleteResponse.setHeaders(headers); + + return autocompleteResponse; } /** @@ -2217,6 +2239,11 @@ protected static AutocompleteResponse createAutocompleteResponse(String string) * in a Gson Type Adapter. */ protected static SearchResponse createSearchResponse(String string) { + return createSearchResponse(string, null); + } + + protected static SearchResponse createSearchResponse( + String string, Map> headers) { JSONObject json = new JSONObject(string); JSONObject response = json.getJSONObject("response"); JSONArray results; @@ -2227,7 +2254,9 @@ protected static SearchResponse createSearchResponse(String string) { } String transformed = json.toString(); - return new Gson().fromJson(transformed, SearchResponse.class); + SearchResponse searchResponse = new Gson().fromJson(transformed, SearchResponse.class); + searchResponse.setHeaders(headers); + return searchResponse; } /** diff --git a/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java index 412ec04a..1ca422c6 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java @@ -48,4 +48,17 @@ public void setResultId(String resultId) { public void setRequest(Map request) { this.request = request; } + + private transient Map> headers; + + /** + * @return the HTTP response headers, or null if not available + */ + public Map> getHeaders() { + return headers; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } } diff --git a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java index d040717c..ff1c7fc0 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java @@ -1,6 +1,7 @@ package io.constructor.client.models; import com.google.gson.annotations.SerializedName; +import java.util.List; import java.util.Map; /** Constructor.io Search Response ... uses Gson/Reflection to load data in */ @@ -47,4 +48,17 @@ public void setResponse(SearchResponseInner response) { public void setRequest(Map request) { this.request = request; } + + private transient Map> headers; + + /** + * @return the HTTP response headers, or null if not available + */ + public Map> getHeaders() { + return headers; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } } diff --git a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOAutocompleteUrlEncodingTest.java b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOAutocompleteUrlEncodingTest.java index 0c35c735..999d3614 100644 --- a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOAutocompleteUrlEncodingTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOAutocompleteUrlEncodingTest.java @@ -1,7 +1,11 @@ package io.constructor.client; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import io.constructor.client.models.AutocompleteResponse; +import java.util.List; +import java.util.Map; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -41,8 +45,7 @@ public void AutocompleteWithPlusShouldBeEncodedInUrl() throws Exception { constructor.autocomplete(request, null); RecordedRequest recordedRequest = mockServer.takeRequest(); - String expectedPath = - String.format("/autocomplete/r%%2Bco?key=%s&c=ciojava-7.5.0", apiKey); + String expectedPath = String.format("/autocomplete/r%%2Bco?key=%s&c=ciojava-7.5.0", apiKey); String actualPath = recordedRequest.getPath(); assertEquals("recorded request is encoded correctly", actualPath, expectedPath); } @@ -59,8 +62,7 @@ public void AutocompleteWithSpaceShouldBeEncodedInUrl() throws Exception { constructor.autocomplete(request, null); RecordedRequest recordedRequest = mockServer.takeRequest(); - String expectedPath = - String.format("/autocomplete/r%%20co?key=%s&c=ciojava-7.5.0", apiKey); + String expectedPath = String.format("/autocomplete/r%%20co?key=%s&c=ciojava-7.5.0", apiKey); String actualPath = recordedRequest.getPath(); assertEquals("recorded request is encoded correctly", actualPath, expectedPath); } @@ -77,8 +79,7 @@ public void AutocompleteWithSlashShouldBeEncodedInUrl() throws Exception { constructor.autocomplete(request, null); RecordedRequest recordedRequest = mockServer.takeRequest(); - String expectedPath = - String.format("/autocomplete/r%%2Fco?key=%s&c=ciojava-7.5.0", apiKey); + String expectedPath = String.format("/autocomplete/r%%2Fco?key=%s&c=ciojava-7.5.0", apiKey); String actualPath = recordedRequest.getPath(); assertEquals("recorded request is encoded correctly", actualPath, expectedPath); } @@ -99,4 +100,32 @@ public void AutocompleteWithSingleQuoteShouldBeAllowedInUrl() throws Exception { String actualPath = recordedRequest.getPath(); assertEquals("recorded request is encoded correctly", actualPath, expectedPath); } + + @Test + public void AutocompleteShouldReturnRateLimitHeaders() throws Exception { + String string = Utils.getTestResource("response.autocomplete.peanut.json"); + MockResponse mockResponse = + new MockResponse() + .setResponseCode(200) + .setBody(string) + .addHeader("X-RateLimit-Limit", "100") + .addHeader("X-RateLimit-Remaining", "99") + .addHeader("X-RateLimit-Reset", "1620000000"); + mockServer.enqueue(mockResponse); + + ConstructorIO constructor = + new ConstructorIO("", apiKey, false, "127.0.0.1", mockServer.getPort()); + AutocompleteRequest request = new AutocompleteRequest("peanut"); + AutocompleteResponse response = constructor.autocomplete(request, null); + + mockServer.takeRequest(); + + Map> headers = response.getHeaders(); + assertNotNull("headers should not be null", headers); + assertEquals("rate limit header", "100", headers.get("x-ratelimit-limit").get(0)); + assertEquals( + "rate limit remaining header", "99", headers.get("x-ratelimit-remaining").get(0)); + assertEquals( + "rate limit reset header", "1620000000", headers.get("x-ratelimit-reset").get(0)); + } } diff --git a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOSearchUrlEncodingTest.java b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOSearchUrlEncodingTest.java index 9c058e5c..9500f450 100644 --- a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOSearchUrlEncodingTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOSearchUrlEncodingTest.java @@ -1,7 +1,11 @@ package io.constructor.client; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import io.constructor.client.models.SearchResponse; +import java.util.List; +import java.util.Map; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -108,4 +112,32 @@ public void SearchWithSingleQuoteShouldBeAllowedInUrl() throws Exception { String actualPath = recordedRequest.getPath(); assertEquals("recorded request is encoded correctly", actualPath, expectedPath); } + + @Test + public void SearchShouldReturnRateLimitHeaders() throws Exception { + String string = Utils.getTestResource("response.search.peanut.json"); + MockResponse mockResponse = + new MockResponse() + .setResponseCode(200) + .setBody(string) + .addHeader("X-RateLimit-Limit", "100") + .addHeader("X-RateLimit-Remaining", "99") + .addHeader("X-RateLimit-Reset", "1620000000"); + mockServer.enqueue(mockResponse); + + ConstructorIO constructor = + new ConstructorIO("", apiKey, false, "127.0.0.1", mockServer.getPort()); + SearchRequest request = new SearchRequest("peanut"); + SearchResponse response = constructor.search(request, null); + + mockServer.takeRequest(); + + Map> headers = response.getHeaders(); + assertNotNull("headers should not be null", headers); + assertEquals("rate limit header", "100", headers.get("x-ratelimit-limit").get(0)); + assertEquals( + "rate limit remaining header", "99", headers.get("x-ratelimit-remaining").get(0)); + assertEquals( + "rate limit reset header", "1620000000", headers.get("x-ratelimit-reset").get(0)); + } } From 81b2e930239281f79b2026dd6ba8a1b67ea0f7fc Mon Sep 17 00:00:00 2001 From: mudaafi Date: Wed, 1 Jul 2026 06:17:41 +0800 Subject: [PATCH 2/4] address comments --- .../io/constructor/client/ConstructorIO.java | 97 ++++++++++--------- .../client/models/AutocompleteResponse.java | 21 ++-- .../client/models/SearchResponse.java | 21 ++-- 3 files changed, 74 insertions(+), 65 deletions(-) diff --git a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java index 5a6848c6..f27489e3 100644 --- a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java +++ b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java @@ -968,66 +968,73 @@ public String autocompleteAsJSON(AutocompleteRequest req, UserInfo userInfo) * @throws ConstructorException */ protected Request createAutocompleteRequest(AutocompleteRequest req, UserInfo userInfo) - throws ConstructorException, UnsupportedEncodingException { - List paths = Arrays.asList("autocomplete", req.getQuery()); - HttpUrl url = (userInfo == null) ? this.makeUrl(paths) : this.makeUrl(paths, userInfo); - - for (Map.Entry entry : req.getResultsPerSection().entrySet()) { - String section = entry.getKey(); - String count = String.valueOf(entry.getValue()); - url = url.newBuilder().addQueryParameter("num_results_" + section, count).build(); - } + throws ConstructorException { + try { + List paths = Arrays.asList("autocomplete", req.getQuery()); + HttpUrl url = (userInfo == null) ? this.makeUrl(paths) : this.makeUrl(paths, userInfo); - for (String hiddenField : req.getHiddenFields()) { - url = - url.newBuilder() - .addQueryParameter("fmt_options[hidden_fields]", hiddenField) - .build(); - } + for (Map.Entry entry : req.getResultsPerSection().entrySet()) { + String section = entry.getKey(); + String count = String.valueOf(entry.getValue()); + url = url.newBuilder().addQueryParameter("num_results_" + section, count).build(); + } - for (String filterName : req.getFilters().keySet()) { - for (String facetValue : req.getFilters().get(filterName)) { + for (String hiddenField : req.getHiddenFields()) { url = url.newBuilder() - .addQueryParameter("filters[" + filterName + "]", facetValue) + .addQueryParameter("fmt_options[hidden_fields]", hiddenField) .build(); } - } - for (String sectionName : req.getFiltersPerSection().keySet()) { - for (String filterName : req.getFiltersPerSection().get(sectionName).keySet()) { - for (String facetValue : - req.getFiltersPerSection().get(sectionName).get(filterName)) { + for (String filterName : req.getFilters().keySet()) { + for (String facetValue : req.getFilters().get(filterName)) { url = url.newBuilder() - .addQueryParameter( - "filters" - + "[" - + sectionName - + "]" - + "[" - + filterName - + "]", - facetValue) + .addQueryParameter("filters[" + filterName + "]", facetValue) .build(); } } - } - if (req.getVariationsMap() != null) { - String variationsMapJson = new Gson().toJson(req.getVariationsMap()); - url = url.newBuilder().addQueryParameter("variations_map", variationsMapJson).build(); - } + for (String sectionName : req.getFiltersPerSection().keySet()) { + for (String filterName : req.getFiltersPerSection().get(sectionName).keySet()) { + for (String facetValue : + req.getFiltersPerSection().get(sectionName).get(filterName)) { + url = + url.newBuilder() + .addQueryParameter( + "filters" + + "[" + + sectionName + + "]" + + "[" + + filterName + + "]", + facetValue) + .build(); + } + } + } - if (req.getPreFilterExpression() != null) { - url = - url.newBuilder() - .addQueryParameter( - "pre_filter_expression", req.getPreFilterExpression()) - .build(); - } + if (req.getVariationsMap() != null) { + String variationsMapJson = new Gson().toJson(req.getVariationsMap()); + url = + url.newBuilder() + .addQueryParameter("variations_map", variationsMapJson) + .build(); + } - return this.makeUserRequestBuilder(userInfo).url(url).get().build(); + if (req.getPreFilterExpression() != null) { + url = + url.newBuilder() + .addQueryParameter( + "pre_filter_expression", req.getPreFilterExpression()) + .build(); + } + + return this.makeUserRequestBuilder(userInfo).url(url).get().build(); + } catch (Exception exception) { + throw new ConstructorException(exception); + } } /** diff --git a/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java index 1ca422c6..7214c50b 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java @@ -1,6 +1,7 @@ package io.constructor.client.models; import com.google.gson.annotations.SerializedName; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -16,6 +17,8 @@ public class AutocompleteResponse { @SerializedName("request") private Map request; + private transient Map> headers = Collections.emptyMap(); + /** * @return the resultId */ @@ -37,6 +40,13 @@ public Map getRequest() { return request; } + /** + * @return the HTTP response headers + */ + public Map> getHeaders() { + return headers; + } + public void setSections(Map> sections) { this.sections = sections; } @@ -49,16 +59,7 @@ public void setRequest(Map request) { this.request = request; } - private transient Map> headers; - - /** - * @return the HTTP response headers, or null if not available - */ - public Map> getHeaders() { - return headers; - } - public void setHeaders(Map> headers) { - this.headers = headers; + this.headers = (headers != null) ? headers : Collections.emptyMap(); } } diff --git a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java index ff1c7fc0..ab2c4251 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java @@ -1,6 +1,7 @@ package io.constructor.client.models; import com.google.gson.annotations.SerializedName; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -16,6 +17,8 @@ public class SearchResponse { @SerializedName("request") private Map request; + private transient Map> headers = Collections.emptyMap(); + /** * @return the resultId */ @@ -37,6 +40,13 @@ public Map getRequest() { return request; } + /** + * @return the HTTP response headers + */ + public Map> getHeaders() { + return headers; + } + public void setResultId(String resultId) { this.resultId = resultId; } @@ -49,16 +59,7 @@ public void setRequest(Map request) { this.request = request; } - private transient Map> headers; - - /** - * @return the HTTP response headers, or null if not available - */ - public Map> getHeaders() { - return headers; - } - public void setHeaders(Map> headers) { - this.headers = headers; + this.headers = (headers != null) ? headers : Collections.emptyMap(); } } From b8ff557e8c2edeff1aa5be9a0988c7483ae98ba9 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Wed, 1 Jul 2026 06:31:05 +0800 Subject: [PATCH 3/4] declare types --- .../io/constructor/client/models/AutocompleteResponse.java | 5 +++-- .../java/io/constructor/client/models/SearchResponse.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java index 7214c50b..3e9dfc24 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java @@ -17,7 +17,8 @@ public class AutocompleteResponse { @SerializedName("request") private Map request; - private transient Map> headers = Collections.emptyMap(); + private transient Map> headers = + Collections.>emptyMap(); /** * @return the resultId @@ -60,6 +61,6 @@ public void setRequest(Map request) { } public void setHeaders(Map> headers) { - this.headers = (headers != null) ? headers : Collections.emptyMap(); + this.headers = (headers != null) ? headers : Collections.>emptyMap(); } } diff --git a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java index ab2c4251..baf7c5fe 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java @@ -17,7 +17,8 @@ public class SearchResponse { @SerializedName("request") private Map request; - private transient Map> headers = Collections.emptyMap(); + private transient Map> headers = + Collections.>emptyMap(); /** * @return the resultId @@ -60,6 +61,6 @@ public void setRequest(Map request) { } public void setHeaders(Map> headers) { - this.headers = (headers != null) ? headers : Collections.emptyMap(); + this.headers = (headers != null) ? headers : Collections.>emptyMap(); } } From 4e4499325700edaddb0bb24b0683a43a5c4228a8 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Fri, 3 Jul 2026 01:20:14 +0800 Subject: [PATCH 4/4] address comments --- .../io/constructor/client/ConstructorIO.java | 4 ++- .../client/models/AutocompleteResponse.java | 17 +----------- .../client/models/ResponseHeaders.java | 26 +++++++++++++++++++ .../client/models/SearchResponse.java | 18 +------------ 4 files changed, 31 insertions(+), 34 deletions(-) create mode 100644 constructorio-client/src/main/java/io/constructor/client/models/ResponseHeaders.java diff --git a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java index f27489e3..9dd7eaff 100644 --- a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java +++ b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java @@ -1227,8 +1227,10 @@ public void onFailure(Call call, IOException e) { public void onResponse(Call call, final Response response) throws IOException { try { + Map> headers = + response.headers().toMultimap(); String json = getResponseBody(response); - SearchResponse res = createSearchResponse(json); + SearchResponse res = createSearchResponse(json, headers); c.onResponse(res); } catch (Exception e) { c.onFailure(new ConstructorException(e)); diff --git a/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java index 3e9dfc24..1a9b78bd 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/AutocompleteResponse.java @@ -1,12 +1,11 @@ package io.constructor.client.models; import com.google.gson.annotations.SerializedName; -import java.util.Collections; import java.util.List; import java.util.Map; /** Constructor.io Autocomplete Response ... uses Gson/Reflection to load data in */ -public class AutocompleteResponse { +public class AutocompleteResponse extends ResponseHeaders { @SerializedName("sections") private Map> sections; @@ -17,9 +16,6 @@ public class AutocompleteResponse { @SerializedName("request") private Map request; - private transient Map> headers = - Collections.>emptyMap(); - /** * @return the resultId */ @@ -41,13 +37,6 @@ public Map getRequest() { return request; } - /** - * @return the HTTP response headers - */ - public Map> getHeaders() { - return headers; - } - public void setSections(Map> sections) { this.sections = sections; } @@ -59,8 +48,4 @@ public void setResultId(String resultId) { public void setRequest(Map request) { this.request = request; } - - public void setHeaders(Map> headers) { - this.headers = (headers != null) ? headers : Collections.>emptyMap(); - } } diff --git a/constructorio-client/src/main/java/io/constructor/client/models/ResponseHeaders.java b/constructorio-client/src/main/java/io/constructor/client/models/ResponseHeaders.java new file mode 100644 index 00000000..a1323296 --- /dev/null +++ b/constructorio-client/src/main/java/io/constructor/client/models/ResponseHeaders.java @@ -0,0 +1,26 @@ +package io.constructor.client.models; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** Base class that provides access to HTTP response headers. */ +public abstract class ResponseHeaders { + + private transient Map> headers = + Collections.>emptyMap(); + + /** + * @return the HTTP response headers + */ + public Map> getHeaders() { + return headers; + } + + /** + * @param headers the HTTP response headers to set. If null, defaults to an empty map. + */ + public void setHeaders(Map> headers) { + this.headers = (headers != null) ? headers : Collections.>emptyMap(); + } +} diff --git a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java index baf7c5fe..0e0355f7 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponse.java @@ -1,12 +1,10 @@ package io.constructor.client.models; import com.google.gson.annotations.SerializedName; -import java.util.Collections; -import java.util.List; import java.util.Map; /** Constructor.io Search Response ... uses Gson/Reflection to load data in */ -public class SearchResponse { +public class SearchResponse extends ResponseHeaders { @SerializedName("result_id") private String resultId; @@ -17,9 +15,6 @@ public class SearchResponse { @SerializedName("request") private Map request; - private transient Map> headers = - Collections.>emptyMap(); - /** * @return the resultId */ @@ -41,13 +36,6 @@ public Map getRequest() { return request; } - /** - * @return the HTTP response headers - */ - public Map> getHeaders() { - return headers; - } - public void setResultId(String resultId) { this.resultId = resultId; } @@ -59,8 +47,4 @@ public void setResponse(SearchResponseInner response) { public void setRequest(Map request) { this.request = request; } - - public void setHeaders(Map> headers) { - this.headers = (headers != null) ? headers : Collections.>emptyMap(); - } }