From 5d29db9b4e5322f2e5cdfa2c6e3bbd25ddfb9d28 Mon Sep 17 00:00:00 2001 From: Mae Evans Date: Mon, 15 Jun 2026 14:47:49 -0600 Subject: [PATCH 1/4] Add US Autocomplete (V2) API client Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 1 + Makefile | 7 +- .../com/smartystreets/api/ClientBuilder.java | 6 + .../api/us_autocomplete/Client.java | 86 +++++++ .../api/us_autocomplete/Lookup.java | 213 ++++++++++++++++++ .../api/us_autocomplete/Result.java | 16 ++ .../api/us_autocomplete/Suggestion.java | 56 +++++ .../api/us_autocomplete_pro/Lookup.java | 2 +- .../api/us_autocomplete_pro/Suggestion.java | 2 +- .../java/examples/UsAutocompleteExample.java | 79 +++++++ .../api/us_autocomplete/ClientTest.java | 99 ++++++++ .../api/us_autocomplete/SuggestionTest.java | 27 +++ 12 files changed, 590 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/smartystreets/api/us_autocomplete/Client.java create mode 100644 src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java create mode 100644 src/main/java/com/smartystreets/api/us_autocomplete/Result.java create mode 100644 src/main/java/com/smartystreets/api/us_autocomplete/Suggestion.java create mode 100644 src/main/java/examples/UsAutocompleteExample.java create mode 100644 src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java create mode 100644 src/test/java/com/smartystreets/api/us_autocomplete/SuggestionTest.java diff --git a/CLAUDE.md b/CLAUDE.md index 0c890c0..26d9d6a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,6 +84,7 @@ Test doubles are in `src/test/java/com/smartystreets/api/mocks/`: - US Street Address (`us_street/`) - US ZIP Code (`us_zipcode/`) - US Reverse Geo (`us_reverse_geo/`) +- US Autocomplete (`us_autocomplete/`) - US Autocomplete Pro (`us_autocomplete_pro/`) - US Extract (`us_extract/`) - US Enrichment (`us_enrichment/`) diff --git a/Makefile b/Makefile index c9f99f8..ec45bfa 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,9 @@ international_street_api: international_postal_code_api: mvn exec:java $(EXEC_OPTS) -Dexec.mainClass="examples.InternationalPostalCodeExample" +us_autocomplete_api: + mvn exec:java $(EXEC_OPTS) -Dexec.mainClass="examples.UsAutocompleteExample" + us_autocomplete_pro_api: mvn exec:java $(EXEC_OPTS) -Dexec.mainClass="examples.UsAutocompleteProExample" @@ -65,6 +68,6 @@ us_street_iana_timezone_api: us_zipcode_api: mvn exec:java $(EXEC_OPTS) -Dexec.mainClass="examples.UsZipCodeSingleLookupExample" && mvn exec:java $(EXEC_OPTS) -Dexec.mainClass="examples.UsZipCodeMultipleLookupsExample" -examples: international_autocomplete_api international_street_api international_postal_code_api us_autocomplete_pro_api us_enrichment_api us_enrichment_business_api us_enrichment_etag_api us_extract_api us_reverse_geo_api us_street_api us_zipcode_api +examples: international_autocomplete_api international_street_api international_postal_code_api us_autocomplete_api us_autocomplete_pro_api us_enrichment_api us_enrichment_business_api us_enrichment_etag_api us_extract_api us_reverse_geo_api us_street_api us_zipcode_api -.PHONY: clean test integration-test compile publish examples international_autocomplete_api international_street_api international_postal_code_api us_autocomplete_pro_api us_enrichment_api us_enrichment_business_api us_enrichment_etag_api us_extract_api us_reverse_geo_api us_street_api us_street_iana_timezone_api us_zipcode_api \ No newline at end of file +.PHONY: clean test integration-test compile publish examples international_autocomplete_api international_street_api international_postal_code_api us_autocomplete_api us_autocomplete_pro_api us_enrichment_api us_enrichment_business_api us_enrichment_etag_api us_extract_api us_reverse_geo_api us_street_api us_street_iana_timezone_api us_zipcode_api \ No newline at end of file diff --git a/src/main/java/com/smartystreets/api/ClientBuilder.java b/src/main/java/com/smartystreets/api/ClientBuilder.java index 98bff31..6468ef1 100644 --- a/src/main/java/com/smartystreets/api/ClientBuilder.java +++ b/src/main/java/com/smartystreets/api/ClientBuilder.java @@ -18,6 +18,7 @@ public class ClientBuilder { private final static String INTERNATIONAL_STREET_API_URL = "https://international-street.api.smarty.com/verify"; private final static String INTERNATIONAL_AUTOCOMPLETE_API_URL = "https://international-autocomplete.api.smarty.com/v2/lookup"; private final static String US_AUTOCOMPLETE_API_PRO_URL = "https://us-autocomplete-pro.api.smarty.com/lookup"; + private final static String US_AUTOCOMPLETE_API_URL = "https://us-autocomplete.api.smarty.com/v2/lookup"; private final static String US_EXTRACT_API_URL = "https://us-extract.api.smarty.com/"; private final static String US_STREET_API_URL = "https://us-street.api.smarty.com/street-address"; private final static String US_ZIP_CODE_API_URL = "https://us-zipcode.api.smarty.com/lookup"; @@ -236,6 +237,11 @@ public com.smartystreets.api.us_autocomplete_pro.Client buildUsAutocompleteProAp return new com.smartystreets.api.us_autocomplete_pro.Client(this.buildSender(), this.serializer); } + public com.smartystreets.api.us_autocomplete.Client buildUsAutocompleteApiClient() { + this.ensureURLPrefixNotNull(US_AUTOCOMPLETE_API_URL); + return new com.smartystreets.api.us_autocomplete.Client(this.buildSender(), this.serializer); + } + public com.smartystreets.api.us_extract.Client buildUsExtractApiClient() { this.ensureURLPrefixNotNull(US_EXTRACT_API_URL); return new com.smartystreets.api.us_extract.Client(this.buildSender(), this.serializer); diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Client.java b/src/main/java/com/smartystreets/api/us_autocomplete/Client.java new file mode 100644 index 0000000..984ac59 --- /dev/null +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Client.java @@ -0,0 +1,86 @@ +package com.smartystreets.api.us_autocomplete; + + +import com.smartystreets.api.*; +import com.smartystreets.api.exceptions.SmartyException; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; + +/** + * This client sends lookups to the SmartyStreets US Autocomplete API,
+ * and attaches the results to the appropriate Lookup objects. + */ +public class Client implements Closeable { + private final Sender sender; + private final Serializer serializer; + + public Client(Sender sender, Serializer serializer) { + this.sender = sender; + this.serializer = serializer; + } + + public Suggestion[] send(Lookup lookup) throws SmartyException, IOException, InterruptedException { + if (lookup == null || lookup.getSearch() == null || lookup.getSearch().length() == 0) + throw new SmartyException("Send() must be passed a Lookup with the search field set."); + + Request request = this.buildRequest(lookup); + + Response response = this.sender.send(request); + + Result result = this.serializer.deserialize(response.getPayload(), Result.class); + Suggestion[] suggestions = result.getSuggestions(); + lookup.setResult(suggestions); + + return suggestions; + } + + private Request buildRequest(Lookup lookup) { + Request request = new Request(); + + request.putParameter("search", lookup.getSearch()); + request.putParameter("max_results", lookup.getMaxSuggestionsStringIfSet()); + request.putParameter("include_only_cities", this.buildString(lookup.getCityFilter())); + request.putParameter("include_only_states", this.buildString(lookup.getStateFilter())); + request.putParameter("include_only_zip_codes", this.buildString(lookup.getZipcodeFilter())); + request.putParameter("exclude_states", this.buildString(lookup.getExcludeStates())); + request.putParameter("prefer_cities", this.buildString(lookup.getPreferCity())); + request.putParameter("prefer_states", this.buildString(lookup.getPreferState())); + request.putParameter("prefer_zip_codes", this.buildString(lookup.getPreferZipcode())); + request.putParameter("prefer_ratio", lookup.getPreferRatioStringIfSet()); + if (lookup.getGeolocateType() != null) { + request.putParameter("prefer_geolocation", lookup.getGeolocateType().getName()); + } + request.putParameter("selected", lookup.getSelected()); + request.putParameter("exclude", lookup.getExclude()); + request.putParameter("source", lookup.getSource()); + + return request; + } + + private String buildString(List list) { + return buildStringFromList(list, ";"); + } + + private String buildStringFromList(List list, String separator) { + if (list.isEmpty()) + return null; + + String filterList = ""; + + for (String item : list) { + filterList += (item + separator); + } + + if (filterList.endsWith(separator)) + filterList = filterList.substring(0, filterList.length()-1); + + return filterList; + } + + @Override + public void close() throws IOException { + this.sender.close(); + } +} diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java b/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java new file mode 100644 index 0000000..528b103 --- /dev/null +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java @@ -0,0 +1,213 @@ +package com.smartystreets.api.us_autocomplete; + +import com.smartystreets.api.GeolocateType; + +import java.util.ArrayList; +import java.util.List; + +/** + * In addition to holding all of the input data for this lookup, this class also
+ * will contain the result of the lookup after it comes back from the API. + * @see "https://www.smarty.com/docs/apis/us-autocomplete-v2/reference#http-request-input-fields" + */ +public class Lookup { + final int PREFER_RATIO_DEFAULT = 100; + final int MAX_RESULTS_DEFAULT = 10; + + //region [ Fields ] + + private Suggestion[] result; + private String search; + private int maxResults; + private List cityFilter; + private List stateFilter; + private List zipcodeFilter; + private List excludeStates; + private List preferCity; + private List preferState; + private List preferZipcode; + private int preferRatio; + private GeolocateType preferGeolocation; + private String selected; + private String exclude; + private String source; + + //endregion + + //region [ Constructors ] + + /** + * If you use this constructor, don't forget to set the search. It is required. + */ + public Lookup() { + this.maxResults = this.MAX_RESULTS_DEFAULT; + this.preferGeolocation = GeolocateType.CITY; + this.cityFilter = new ArrayList<>(); + this.stateFilter = new ArrayList<>(); + this.zipcodeFilter = new ArrayList<>(); + this.excludeStates = new ArrayList<>(); + this.preferCity = new ArrayList<>(); + this.preferState = new ArrayList<>(); + this.preferZipcode = new ArrayList<>(); + this.preferRatio = this.PREFER_RATIO_DEFAULT; + } + + /** + * @param search The beginning of an address + */ + public Lookup(String search) { + this(); + this.search = search; + } + + //endregion + + //region [ Getters ] + + public Suggestion[] getResult() { + return this.result; + } + + public Suggestion getResult(int index) { + return this.result[index]; + } + + public String getSearch() { + return this.search; + } + + public String getSelected() { return this.selected; } + + public String getExclude() { return this.exclude; } + + public String getSource() { return this.source; } + + public List getCityFilter() { + return this.cityFilter; + } + + public List getStateFilter() { + return this.stateFilter; + } + + public List getZipcodeFilter() { + return this.zipcodeFilter; + } + + public List getExcludeStates() { + return this.excludeStates; + } + + public List getPreferCity() { + return this.preferCity; + } + + public List getPreferState() { return this.preferState; } + + public List getPreferZipcode() { return this.preferZipcode; } + + public int getPreferRatio() { + return this.preferRatio; + } + + String getPreferRatioStringIfSet() { + if (this.preferRatio == this.PREFER_RATIO_DEFAULT) + return null; + return Integer.toString(this.preferRatio); + } + + public GeolocateType getGeolocateType() { + return preferGeolocation; + } + + public int getMaxResults() { + return maxResults; + } + + String getMaxSuggestionsStringIfSet() { + if (this.maxResults == this.MAX_RESULTS_DEFAULT) + return null; + return Integer.toString(this.maxResults); + } + + //endregion + + //region [ Setters ] + + public void setResult(Suggestion[] result) { + this.result = result; + } + + public void setSearch(String search) { + this.search = search; + } + + public void setSelected(String selected) { this.selected = selected; } + + public void setExclude(String exclude) { this.exclude = exclude; } + + public void setSource(String source) { this.source = source; } + + public void setCityFilter(List cityFilter) { + this.cityFilter = cityFilter; + } + + public void setStateFilter(List stateFilter) { + this.stateFilter = stateFilter; + } + + public void setZipcodeFilter(List zipcodeFilter) { this.zipcodeFilter = zipcodeFilter; } + + public void setExcludeStates(List excludeStates) { this.excludeStates = excludeStates; } + + public void setPreferCity(List cities) { + this.preferCity = cities; + } + + public void setPreferState(List states) { this.preferState = states; } + + public void setPreferZipcode(List zipcodes) { this.preferZipcode = zipcodes; } + + /*** + * Sets the percentage of suggestions that are to be from preferred cities/states. + * @param preferRatio An integer value, range [0, 100]. Default is 100. + */ + public void setPreferRatio(int preferRatio) { + this.preferRatio = preferRatio; + } + + public void setGeolocateType(GeolocateType geolocateType) { + this.preferGeolocation = geolocateType; + } + + /*** + * Sets the maximum number of suggestions to return. + * @param maxResults A positive integer range [1, 10]. Default is 10. + * @throws IllegalArgumentException + */ + public void setMaxResults(int maxResults) throws IllegalArgumentException{ + if (maxResults > 0 && maxResults <= this.MAX_RESULTS_DEFAULT) { + this.maxResults = maxResults; + } else { + throw new IllegalArgumentException("Max results must be a positive integer no larger than 10."); + } + } + + public void addCityFilter(String city) { + this.cityFilter.add(city); + } + + public void addStateFilter(String stateAbbreviation) { + this.stateFilter.add(stateAbbreviation); + } + + public void addPreferCity(String city) { + this.preferCity.add(city); + } + + public void addPreferState(String state) { this.preferState.add(state); } + + public void addPreferZipcode(String zipcode) { this.preferZipcode.add(zipcode); } + + //endregion +} diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Result.java b/src/main/java/com/smartystreets/api/us_autocomplete/Result.java new file mode 100644 index 0000000..1f895a6 --- /dev/null +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Result.java @@ -0,0 +1,16 @@ +package com.smartystreets.api.us_autocomplete; + + +import java.io.Serializable; + +public class Result implements Serializable { + private Suggestion[] suggestions; + + public Suggestion[] getSuggestions() { + return this.suggestions; + } + + public Suggestion getSuggestion(int index) { + return this.suggestions[index]; + } +} diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Suggestion.java b/src/main/java/com/smartystreets/api/us_autocomplete/Suggestion.java new file mode 100644 index 0000000..32ce2ca --- /dev/null +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Suggestion.java @@ -0,0 +1,56 @@ +package com.smartystreets.api.us_autocomplete; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +/** + * @see "https://www.smarty.com/docs/apis/us-autocomplete-v2/reference#http-response-status" + */ +public class Suggestion implements Serializable { + //region [ Fields ] + + private String smartyKey; + private String entryId; + private String streetLine; + private String secondary; + private String city; + private String state; + private String zipcode; + private Integer entries; + + //endregion + + //region [ Getters ] + + @JsonProperty("smarty_key") + public String getSmartyKey() { + return smartyKey; + } + + @JsonProperty("entry_id") + public String getEntryId() { + return entryId; + } + + @JsonProperty("street_line") + public String getStreetLine() { + return streetLine; + } + + public String getSecondary() { return secondary; } + + public String getCity() { + return city; + } + + public String getState() { + return state; + } + + public String getZipcode() { return zipcode; } + + public Integer getEntries() { return entries; } + + //endregion +} diff --git a/src/main/java/com/smartystreets/api/us_autocomplete_pro/Lookup.java b/src/main/java/com/smartystreets/api/us_autocomplete_pro/Lookup.java index 62cd07d..ada6cca 100644 --- a/src/main/java/com/smartystreets/api/us_autocomplete_pro/Lookup.java +++ b/src/main/java/com/smartystreets/api/us_autocomplete_pro/Lookup.java @@ -165,7 +165,7 @@ public void setPreferCity(List cities) { /*** * Sets the percentage of suggestions that are to be from preferred cities/states. - * @param preferRatio An integer value, range [0, 100]. Default is 33. + * @param preferRatio An integer value, range [0, 100]. Default is 100. * @see "https://smartystreets.com/docs/cloud/us-autocomplete-pro-api#preference" */ public void setPreferRatio(int preferRatio) { diff --git a/src/main/java/com/smartystreets/api/us_autocomplete_pro/Suggestion.java b/src/main/java/com/smartystreets/api/us_autocomplete_pro/Suggestion.java index 763c4e2..ce76666 100644 --- a/src/main/java/com/smartystreets/api/us_autocomplete_pro/Suggestion.java +++ b/src/main/java/com/smartystreets/api/us_autocomplete_pro/Suggestion.java @@ -17,7 +17,7 @@ public class Suggestion implements Serializable { private String zipcode; private Integer entries; - //region [ Fields ] + //endregion //region [ Getters ] diff --git a/src/main/java/examples/UsAutocompleteExample.java b/src/main/java/examples/UsAutocompleteExample.java new file mode 100644 index 0000000..eb70b33 --- /dev/null +++ b/src/main/java/examples/UsAutocompleteExample.java @@ -0,0 +1,79 @@ +package examples; + +import com.smartystreets.api.BasicAuthCredentials; +import com.smartystreets.api.exceptions.SmartyException; +import com.smartystreets.api.us_autocomplete.*; +import com.smartystreets.api.ClientBuilder; + +import java.io.IOException; + +// This example is for US Autocomplete (V2). It has the same name as a previous product +// which has been deprecated since 2022, which we refer to as US Autocomplete Basic. +// If you are still using US Autocomplete Basic, this SDK will not work. +public class UsAutocompleteExample { + public static void main(String[] args) { + // We recommend storing your authentication credentials in environment variables. + // for client-side requests (browser/mobile), use this code: + // SharedCredentials credentials = new SharedCredentials(System.getenv("SMARTY_AUTH_WEB"), System.getenv("SMARTY_AUTH_REFERER")); + BasicAuthCredentials credentials = new BasicAuthCredentials(System.getenv("SMARTY_AUTH_ID"), System.getenv("SMARTY_AUTH_TOKEN")); + + try (Client client = new ClientBuilder(credentials).buildUsAutocompleteApiClient()) { + Lookup lookup = new Lookup("1042 W Center"); + lookup.setMaxResults(5); + + client.send(lookup); + + System.out.println("*** Result with no filter ***"); + System.out.println(); + printResult(lookup); + + // Documentation for input fields can be found at: + // https://www.smarty.com/docs/apis/us-autocomplete-v2/reference#http-request-input-fields + + lookup.addCityFilter("Denver,Aurora,CO"); + lookup.addCityFilter("Orem,UT"); + lookup.addPreferState("CO"); + lookup.setPreferRatio(33); + lookup.setSource("all"); + + client.send(lookup); // The client will also return the suggestions directly + + System.out.println(); + System.out.println("*** Result with some filters ***"); + printResult(lookup); + + // Expand the secondaries of a result that has an entry_id by passing it back as the selected address. + String entryId = null; + for (Suggestion suggestion : lookup.getResult()) { + if (suggestion.getEntryId() != null && suggestion.getEntryId().length() > 0) + entryId = suggestion.getEntryId(); + } + + if (entryId != null) { + lookup.setSelected(entryId); + client.send(lookup); + + System.out.println(); + System.out.println("*** Secondaries ***"); + printResult(lookup); + } + } catch (SmartyException | IOException | InterruptedException ex) { + System.out.println(ex.getMessage()); + ex.printStackTrace(); + } + } + + private static void printResult(Lookup lookup) { + for (Suggestion suggestion : lookup.getResult()) { + System.out.print(suggestion.getStreetLine()); + System.out.print(" "); + System.out.print(suggestion.getSecondary()); + System.out.print(" "); + System.out.print(suggestion.getCity()); + System.out.print(", "); + System.out.print(suggestion.getState()); + System.out.print(", "); + System.out.println(suggestion.getZipcode()); + } + } +} diff --git a/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java b/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java new file mode 100644 index 0000000..906500e --- /dev/null +++ b/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java @@ -0,0 +1,99 @@ +package com.smartystreets.api.us_autocomplete; + +import com.smartystreets.api.GeolocateType; +import com.smartystreets.api.Response; +import com.smartystreets.api.URLPrefixSender; +import com.smartystreets.api.mocks.FakeDeserializer; +import com.smartystreets.api.mocks.FakeSerializer; +import com.smartystreets.api.mocks.MockSender; +import com.smartystreets.api.mocks.RequestCapturingSender; +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class ClientTest { + //region [ Single Lookup ] + + @Test + public void testSendingSingleSearchOnlyLookup() throws Exception { + RequestCapturingSender capturingSender = new RequestCapturingSender(); + URLPrefixSender sender = new URLPrefixSender("http://localhost/", capturingSender); + FakeSerializer serializer = new FakeSerializer(new Result()); + Client client = new Client(sender, serializer); + + client.send(new Lookup("1")); + + assertEquals("http://localhost/?search=1&prefer_geolocation=city", capturingSender.getRequest().getUrl()); + } + + @Test + public void testSendingSingleFullyPopulatedLookup() throws Exception { + RequestCapturingSender capturingSender = new RequestCapturingSender(); + URLPrefixSender sender = new URLPrefixSender("http://localhost/", capturingSender); + FakeSerializer serializer = new FakeSerializer(new Result()); + Client client = new Client(sender, serializer); + String expectedURL = "http://localhost/?search=1&max_results=2&include_only_cities=3&include_only_states=4&prefer_ratio=60&prefer_geolocation=city&selected=5&exclude=6"; + Lookup lookup = new Lookup(); + lookup.setSearch("1"); + lookup.setMaxResults(2); + lookup.addCityFilter("3"); + lookup.addStateFilter("4"); + lookup.setPreferRatio(60); + lookup.setGeolocateType(GeolocateType.CITY); + lookup.setSelected("5"); + lookup.setExclude("6"); + + client.send(lookup); + + assertEquals(expectedURL, capturingSender.getRequest().getUrl()); + } + + @Test + public void testSendingExclude() throws Exception { + RequestCapturingSender capturingSender = new RequestCapturingSender(); + URLPrefixSender sender = new URLPrefixSender("http://localhost/", capturingSender); + FakeSerializer serializer = new FakeSerializer(new Result()); + Client client = new Client(sender, serializer); + Lookup lookup = new Lookup("1"); + lookup.setExclude("excludedAddress"); + + client.send(lookup); + + assertEquals("http://localhost/?search=1&prefer_geolocation=city&exclude=excludedAddress", capturingSender.getRequest().getUrl()); + } + + //endregion + + //region [ Response Handling ] + + @Test + public void testDeserializeCalledWithResponseBody() throws Exception { + Response response = new Response(0, "Hello, World!".getBytes()); + MockSender mockSender = new MockSender(response); + URLPrefixSender sender = new URLPrefixSender("http://localhost/", mockSender); + FakeDeserializer deserializer = new FakeDeserializer(new Result()); + Client client = new Client(sender, deserializer); + + client.send(new Lookup("1")); + + assertEquals(response.getPayload(), deserializer.getPayload()); + } + + @Test + public void testResultCorrectlyAssignedToCorrespondingLookup() throws Exception { + Lookup lookup = new Lookup("1"); + Result expectedResult = new Result(); + + MockSender mockSender = new MockSender(new Response(0, "{[]}".getBytes())); + URLPrefixSender sender = new URLPrefixSender("http://localhost/", mockSender); + FakeDeserializer deserializer = new FakeDeserializer(expectedResult); + Client client = new Client(sender, deserializer); + + client.send(lookup); + + assertArrayEquals(expectedResult.getSuggestions(), lookup.getResult()); + } + + //endregion +} diff --git a/src/test/java/com/smartystreets/api/us_autocomplete/SuggestionTest.java b/src/test/java/com/smartystreets/api/us_autocomplete/SuggestionTest.java new file mode 100644 index 0000000..bc6e6db --- /dev/null +++ b/src/test/java/com/smartystreets/api/us_autocomplete/SuggestionTest.java @@ -0,0 +1,27 @@ +package com.smartystreets.api.us_autocomplete; + + +import com.smartystreets.api.SmartySerializer; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class SuggestionTest { + private final SmartySerializer smartySerializer = new SmartySerializer(); + private static final String responsePayload = "{\"suggestions\":[{\"smarty_key\":\"1\",\"entry_id\":\"2\",\"street_line\":\"3\",\"city\":\"4\",\"state\":\"5\"}]}"; + + @Test + public void testAllFieldGetFilledInCorrectly() throws IOException { + Result result = smartySerializer.deserialize(responsePayload.getBytes(), Result.class); + + assertNotNull(result.getSuggestions()[0]); + assertEquals("1", result.getSuggestion(0).getSmartyKey()); + assertEquals("2", result.getSuggestion(0).getEntryId()); + assertEquals("3", result.getSuggestion(0).getStreetLine()); + assertEquals("4", result.getSuggestion(0).getCity()); + assertEquals("5", result.getSuggestion(0).getState()); + } +} From a725665dd8f667f9d5170f5643149f8416e18843 Mon Sep 17 00:00:00 2001 From: Andrea Wait Date: Thu, 18 Jun 2026 13:43:29 -0600 Subject: [PATCH 2/4] Refactored source to enum --- .../api/us_autocomplete/Client.java | 4 +- .../api/us_autocomplete/Lookup.java | 6 +-- .../api/us_autocomplete/Source.java | 15 +++++++ .../java/examples/UsAutocompleteExample.java | 2 +- .../api/us_autocomplete/ClientTest.java | 40 +++++++++++++++++++ 5 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/smartystreets/api/us_autocomplete/Source.java diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Client.java b/src/main/java/com/smartystreets/api/us_autocomplete/Client.java index 984ac59..9d9efe9 100644 --- a/src/main/java/com/smartystreets/api/us_autocomplete/Client.java +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Client.java @@ -54,7 +54,9 @@ private Request buildRequest(Lookup lookup) { } request.putParameter("selected", lookup.getSelected()); request.putParameter("exclude", lookup.getExclude()); - request.putParameter("source", lookup.getSource()); + if (lookup.getSource() != null) { + request.putParameter("source", lookup.getSource().getName()); + } return request; } diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java b/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java index 528b103..08a7a16 100644 --- a/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java @@ -30,7 +30,7 @@ public class Lookup { private GeolocateType preferGeolocation; private String selected; private String exclude; - private String source; + private Source source; //endregion @@ -80,7 +80,7 @@ public String getSearch() { public String getExclude() { return this.exclude; } - public String getSource() { return this.source; } + public Source getSource() { return this.source; } public List getCityFilter() { return this.cityFilter; @@ -146,7 +146,7 @@ public void setSearch(String search) { public void setExclude(String exclude) { this.exclude = exclude; } - public void setSource(String source) { this.source = source; } + public void setSource(Source source) { this.source = source; } public void setCityFilter(List cityFilter) { this.cityFilter = cityFilter; diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Source.java b/src/main/java/com/smartystreets/api/us_autocomplete/Source.java new file mode 100644 index 0000000..5035d32 --- /dev/null +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Source.java @@ -0,0 +1,15 @@ +package com.smartystreets.api.us_autocomplete; + +public enum Source { + ALL("all"), POSTAL("postal"); + + private final String name; + + Source(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/examples/UsAutocompleteExample.java b/src/main/java/examples/UsAutocompleteExample.java index eb70b33..315fae3 100644 --- a/src/main/java/examples/UsAutocompleteExample.java +++ b/src/main/java/examples/UsAutocompleteExample.java @@ -34,7 +34,7 @@ public static void main(String[] args) { lookup.addCityFilter("Orem,UT"); lookup.addPreferState("CO"); lookup.setPreferRatio(33); - lookup.setSource("all"); + lookup.setSource(Source.ALL); client.send(lookup); // The client will also return the suggestions directly diff --git a/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java b/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java index 906500e..32242fd 100644 --- a/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java +++ b/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java @@ -49,6 +49,46 @@ public void testSendingSingleFullyPopulatedLookup() throws Exception { assertEquals(expectedURL, capturingSender.getRequest().getUrl()); } + @Test + public void testSendingLookupWithSourceAll() throws Exception { + RequestCapturingSender capturingSender = new RequestCapturingSender(); + URLPrefixSender sender = new URLPrefixSender("http://localhost/", capturingSender); + FakeSerializer serializer = new FakeSerializer(new Result()); + Client client = new Client(sender, serializer); + Lookup lookup = new Lookup("1"); + lookup.setSource(Source.ALL); + + client.send(lookup); + + assertEquals("http://localhost/?search=1&prefer_geolocation=city&source=all", capturingSender.getRequest().getUrl()); + } + + @Test + public void testSendingLookupWithSourcePostal() throws Exception { + RequestCapturingSender capturingSender = new RequestCapturingSender(); + URLPrefixSender sender = new URLPrefixSender("http://localhost/", capturingSender); + FakeSerializer serializer = new FakeSerializer(new Result()); + Client client = new Client(sender, serializer); + Lookup lookup = new Lookup("1"); + lookup.setSource(Source.POSTAL); + + client.send(lookup); + + assertEquals("http://localhost/?search=1&prefer_geolocation=city&source=postal", capturingSender.getRequest().getUrl()); + } + + @Test + public void testSendingLookupWithNoSourceOmitsParameter() throws Exception { + RequestCapturingSender capturingSender = new RequestCapturingSender(); + URLPrefixSender sender = new URLPrefixSender("http://localhost/", capturingSender); + FakeSerializer serializer = new FakeSerializer(new Result()); + Client client = new Client(sender, serializer); + + client.send(new Lookup("1")); + + assertEquals("http://localhost/?search=1&prefer_geolocation=city", capturingSender.getRequest().getUrl()); + } + @Test public void testSendingExclude() throws Exception { RequestCapturingSender capturingSender = new RequestCapturingSender(); From bcb05de1188206325e12e28b05c7f78a0ea5b5bc Mon Sep 17 00:00:00 2001 From: Mae Evans Date: Mon, 22 Jun 2026 13:57:39 -0600 Subject: [PATCH 3/4] add source --- .../java/com/smartystreets/api/us_autocomplete/Suggestion.java | 3 +++ .../com/smartystreets/api/us_autocomplete/SuggestionTest.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Suggestion.java b/src/main/java/com/smartystreets/api/us_autocomplete/Suggestion.java index 32ce2ca..04d374c 100644 --- a/src/main/java/com/smartystreets/api/us_autocomplete/Suggestion.java +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Suggestion.java @@ -18,6 +18,7 @@ public class Suggestion implements Serializable { private String state; private String zipcode; private Integer entries; + private String source; //endregion @@ -52,5 +53,7 @@ public String getState() { public Integer getEntries() { return entries; } + public String getSource() { return source; } + //endregion } diff --git a/src/test/java/com/smartystreets/api/us_autocomplete/SuggestionTest.java b/src/test/java/com/smartystreets/api/us_autocomplete/SuggestionTest.java index bc6e6db..adebcf7 100644 --- a/src/test/java/com/smartystreets/api/us_autocomplete/SuggestionTest.java +++ b/src/test/java/com/smartystreets/api/us_autocomplete/SuggestionTest.java @@ -11,7 +11,7 @@ public class SuggestionTest { private final SmartySerializer smartySerializer = new SmartySerializer(); - private static final String responsePayload = "{\"suggestions\":[{\"smarty_key\":\"1\",\"entry_id\":\"2\",\"street_line\":\"3\",\"city\":\"4\",\"state\":\"5\"}]}"; + private static final String responsePayload = "{\"suggestions\":[{\"smarty_key\":\"1\",\"entry_id\":\"2\",\"street_line\":\"3\",\"city\":\"4\",\"state\":\"5\",\"source\":\"6\"}]}"; @Test public void testAllFieldGetFilledInCorrectly() throws IOException { @@ -23,5 +23,6 @@ public void testAllFieldGetFilledInCorrectly() throws IOException { assertEquals("3", result.getSuggestion(0).getStreetLine()); assertEquals("4", result.getSuggestion(0).getCity()); assertEquals("5", result.getSuggestion(0).getState()); + assertEquals("6", result.getSuggestion(0).getSource()); } } From fe800576b2054f4826f00f947279f273a91268e5 Mon Sep 17 00:00:00 2001 From: Andy Johnson Date: Tue, 23 Jun 2026 10:56:19 -0600 Subject: [PATCH 4/4] Change exclude to a list. --- .../com/smartystreets/api/us_autocomplete/Client.java | 6 +++++- .../com/smartystreets/api/us_autocomplete/Lookup.java | 9 ++++++--- .../smartystreets/api/us_autocomplete/ClientTest.java | 7 ++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Client.java b/src/main/java/com/smartystreets/api/us_autocomplete/Client.java index 9d9efe9..6b0dcff 100644 --- a/src/main/java/com/smartystreets/api/us_autocomplete/Client.java +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Client.java @@ -53,7 +53,7 @@ private Request buildRequest(Lookup lookup) { request.putParameter("prefer_geolocation", lookup.getGeolocateType().getName()); } request.putParameter("selected", lookup.getSelected()); - request.putParameter("exclude", lookup.getExclude()); + request.putParameter("exclude", this.buildString(lookup.getExclude(), ",")); if (lookup.getSource() != null) { request.putParameter("source", lookup.getSource().getName()); } @@ -65,6 +65,10 @@ private String buildString(List list) { return buildStringFromList(list, ";"); } + private String buildString(List list, String separator) { + return buildStringFromList(list, separator); + } + private String buildStringFromList(List list, String separator) { if (list.isEmpty()) return null; diff --git a/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java b/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java index 08a7a16..87d620c 100644 --- a/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java +++ b/src/main/java/com/smartystreets/api/us_autocomplete/Lookup.java @@ -29,7 +29,7 @@ public class Lookup { private int preferRatio; private GeolocateType preferGeolocation; private String selected; - private String exclude; + private List exclude; private Source source; //endregion @@ -49,6 +49,7 @@ public Lookup() { this.preferCity = new ArrayList<>(); this.preferState = new ArrayList<>(); this.preferZipcode = new ArrayList<>(); + this.exclude = new ArrayList<>(); this.preferRatio = this.PREFER_RATIO_DEFAULT; } @@ -78,7 +79,7 @@ public String getSearch() { public String getSelected() { return this.selected; } - public String getExclude() { return this.exclude; } + public List getExclude() { return this.exclude; } public Source getSource() { return this.source; } @@ -144,7 +145,9 @@ public void setSearch(String search) { public void setSelected(String selected) { this.selected = selected; } - public void setExclude(String exclude) { this.exclude = exclude; } + public void setExclude(List exclude) { this.exclude = exclude; } + + public void addExclude(String item) { this.exclude.add(item); } public void setSource(Source source) { this.source = source; } diff --git a/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java b/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java index 32242fd..16fb299 100644 --- a/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java +++ b/src/test/java/com/smartystreets/api/us_autocomplete/ClientTest.java @@ -8,6 +8,7 @@ import com.smartystreets.api.mocks.MockSender; import com.smartystreets.api.mocks.RequestCapturingSender; import org.junit.Test; +import java.util.List; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -42,7 +43,7 @@ public void testSendingSingleFullyPopulatedLookup() throws Exception { lookup.setPreferRatio(60); lookup.setGeolocateType(GeolocateType.CITY); lookup.setSelected("5"); - lookup.setExclude("6"); + lookup.addExclude("6"); client.send(lookup); @@ -96,11 +97,11 @@ public void testSendingExclude() throws Exception { FakeSerializer serializer = new FakeSerializer(new Result()); Client client = new Client(sender, serializer); Lookup lookup = new Lookup("1"); - lookup.setExclude("excludedAddress"); + lookup.setExclude(List.of("excludedAddress", "excludedAddress2")); client.send(lookup); - assertEquals("http://localhost/?search=1&prefer_geolocation=city&exclude=excludedAddress", capturingSender.getRequest().getUrl()); + assertEquals("http://localhost/?search=1&prefer_geolocation=city&exclude=excludedAddress%2CexcludedAddress2", capturingSender.getRequest().getUrl()); } //endregion