Skip to content

Commit

Permalink
Support intl autocomplete v2
Browse files Browse the repository at this point in the history
  • Loading branch information
XanSmarty committed Oct 31, 2023
1 parent 13013d5 commit 127c428
Show file tree
Hide file tree
Showing 15 changed files with 558 additions and 142 deletions.
43 changes: 43 additions & 0 deletions examples/international-autocomplete-api-deprecated/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"context"
"fmt"
international_autocomplete_deprecated "github.com/smartystreets/smartystreets-go-sdk/international-autocomplete-api-deprecated"
"github.com/smartystreets/smartystreets-go-sdk/wireup"
"log"
"os"
)

func main() {
log.SetFlags(log.Ltime | log.Llongfile)

client := wireup.BuildInternationalAutocompleteAPIDeprecatedClient(
wireup.WebsiteKeyCredential(os.Getenv("SMARTY_AUTH_WEB"), os.Getenv("SMARTY_AUTH_REFERER")),
//wireup.SecretKeyCredential(os.Getenv("SMARTY_AUTH_ID"), os.Getenv("SMARTY_AUTH_TOKEN")),
// The appropriate license values to be used for your subscriptions
// can be found on the Subscriptions page the account dashboard.
// https://www.smartystreets.com/docs/cloud/licensing
wireup.WithLicenses("international-autocomplete-cloud"),
)

// Documentation for input fields can be found at:
// https://smartystreets.com/docs/cloud/us-autocomplete-api#http-request-input-fields

lookup := &international_autocomplete_deprecated.Lookup{
Country: "FRA",
Search: "Louis",
Locality: "Paris",
}

if err := client.SendLookupWithContext(context.Background(), lookup); err != nil {
log.Fatal("Error sending batch:", err)
}

fmt.Printf("Results for input: [%s]\n", lookup.Search)
for s, candidate := range lookup.Result.Candidates {
fmt.Printf("#%d: %#v\n", s, candidate)
}

log.Println("OK")
}
6 changes: 5 additions & 1 deletion examples/international-autocomplete-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ func main() {
log.Fatal("Error sending batch:", err)
}

fmt.Printf("Results for input: [%s]\n", lookup.Search)
if len(lookup.Search) > 0 {
fmt.Printf("Results for input: [%s]\n", lookup.Search)
} else {
fmt.Printf("Results for input: [%s]\n", lookup.AddressID)
}
for s, candidate := range lookup.Result.Candidates {
fmt.Printf("#%d: %#v\n", s, candidate)
}
Expand Down
11 changes: 11 additions & 0 deletions international-autocomplete-api-deprecated/candidate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package international_autocomplete_api

type Candidate struct {
Street string `json:"street"`
Locality string `json:"locality"`
AdministrativeArea string `json:"administrative_area"`
SuperAdministrativeArea string `json:"super_administrative_area"`
SubAdministrativeArea string `json:"sub_administrative_area"`
PostalCode string `json:"postal_code"`
CountryIso3 string `json:"country_iso3"`
}
55 changes: 55 additions & 0 deletions international-autocomplete-api-deprecated/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package international_autocomplete_api

import (
"context"
"encoding/json"
"net/http"

sdk "github.com/smartystreets/smartystreets-go-sdk"
)

type Client struct {
sender sdk.RequestSender
}

// NewClient creates a client with the provided sender.
func NewClient(sender sdk.RequestSender) *Client {
return &Client{sender: sender}
}

func (c *Client) SendLookup(lookup *Lookup) error {
return c.SendLookupWithContext(context.Background(), lookup)
}

func (c *Client) SendLookupWithContext(ctx context.Context, lookup *Lookup) error {
if lookup == nil || len(lookup.Search) == 0 {
return nil
}

request := buildRequest(lookup)
request = request.WithContext(ctx)
response, err := c.sender.Send(request)
if err != nil {
return err
} else {
return deserializeResponse(response, lookup)
}
}

func deserializeResponse(response []byte, lookup *Lookup) error {
err := json.Unmarshal(response, &lookup.Result)
if err != nil {
return err
}
return nil
}

func buildRequest(lookup *Lookup) *http.Request {
request, _ := http.NewRequest("GET", suggestURL, nil) // We control the method and the URL. This is safe.
query := request.URL.Query()
lookup.populate(query)
request.URL.RawQuery = query.Encode()
return request
}

const suggestURL = "/lookup" // Remaining parts will be completed later by the sdk.BaseURLClient.
140 changes: 140 additions & 0 deletions international-autocomplete-api-deprecated/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package international_autocomplete_api

import (
"context"
"errors"
"net/http"
"testing"

"github.com/smarty/assertions/should"
"github.com/smarty/gunit"
)

func TestClientFixture(t *testing.T) {
gunit.Run(new(ClientFixture), t)
}

type ClientFixture struct {
*gunit.Fixture

sender *FakeSender
client *Client

input *Lookup
}

func (f *ClientFixture) Setup() {
f.sender = &FakeSender{}
f.client = NewClient(f.sender)
f.input = new(Lookup)
}

func (f *ClientFixture) TestAddressLookupSerializedAndSentWithContext__ResponseSuggestionsIncorporatedIntoLookup() {
f.sender.response = `{
"candidates": [
{
"street": "1",
"locality": "2",
"administrative_area": "3",
"super_administrative_area": "4",
"sub_administrative_area": "5",
"postal_code": "6",
"country_iso3": "7"
},
{
"street": "8",
"locality": "9",
"administrative_area": "10",
"super_administrative_area": "11",
"sub_administrative_area": "12",
"postal_code": "13",
"country_iso3": "14"
}
]
}`
f.input.Search = "42"

ctx := context.WithValue(context.Background(), "key", "value")
err := f.client.SendLookupWithContext(ctx, f.input)

f.So(err, should.BeNil)
f.So(f.sender.request, should.NotBeNil)
f.So(f.sender.request.Method, should.Equal, "GET")
f.So(f.sender.request.URL.Path, should.Equal, suggestURL)
f.So(f.sender.request.URL.Query().Get("search"), should.Equal, "42")
f.So(f.sender.request.URL.String(), should.Equal, suggestURL+"?distance=5&max_results=5&search=42")
f.So(f.sender.request.Context(), should.Resemble, ctx)

f.So(f.input.Result, should.Resemble, &Result{Candidates: []*Candidate{
{
Street: "1",
Locality: "2",
AdministrativeArea: "3",
SuperAdministrativeArea: "4",
SubAdministrativeArea: "5",
PostalCode: "6",
CountryIso3: "7",
},
{
Street: "8",
Locality: "9",
AdministrativeArea: "10",
SuperAdministrativeArea: "11",
SubAdministrativeArea: "12",
PostalCode: "13",
CountryIso3: "14",
},
}})
}
func (f *ClientFixture) TestNilLookupNOP() {
err := f.client.SendLookup(nil)
f.So(err, should.BeNil)
f.So(f.sender.request, should.BeNil)
}

func (f *ClientFixture) TestEmptyLookup_NOP() {
err := f.client.SendLookup(new(Lookup))
f.So(err, should.BeNil)
f.So(f.sender.request, should.BeNil)
}

func (f *ClientFixture) TestSenderErrorPreventsDeserialization() {
f.sender.err = errors.New("GOPHERS!")
f.sender.response = `{"candidates":[
{"text": "1"},
{"text": "2"},
{"text": "3"}
]}` // would be deserialized if not for the err (above)
f.input.Search = "HI"

err := f.client.SendLookup(f.input)

f.So(err, should.NotBeNil)
f.So(f.input.Result, should.BeNil)
}

func (f *ClientFixture) TestDeserializationErrorPreventsDeserialization() {
f.sender.response = `I can't haz JSON`
f.input.Search = "HI"

err := f.client.SendLookup(f.input)

f.So(err, should.NotBeNil)
f.So(f.input.Result, should.BeNil)
}

//////////////////////////////////////////////////////////////////

type FakeSender struct {
callCount int
request *http.Request

response string
err error
}

func (f *FakeSender) Send(request *http.Request) ([]byte, error) {
f.callCount++
f.request = request
return []byte(f.response), f.err
}
105 changes: 105 additions & 0 deletions international-autocomplete-api-deprecated/lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package international_autocomplete_api

import (
"math"
"net/url"
"strconv"
)

const (
maxResultsDefault = 5
distanceDefault = 5
)

type Lookup struct {
Country string
Search string
MaxResults int
Distance int
Geolocation InternationalGeolocateType
AdministrativeArea string
Locality string
PostalCode string
Latitude float64
Longitude float64
Result *Result
}

func (l Lookup) populate(query url.Values) {
l.populateCountry(query)
l.populateSearch(query)
l.populateMaxResults(query)
l.populateDistance(query)
l.populateGeolocation(query)
l.populateAdministrativeArea(query)
l.populateLocality(query)
l.populatePostalCode(query)
l.populateLatitude(query)
l.populateLongitude(query)
}
func (l Lookup) populateCountry(query url.Values) {
if len(l.Country) > 0 {
query.Set("country", l.Country)
}
}
func (l Lookup) populateSearch(query url.Values) {
if len(l.Search) > 0 {
query.Set("search", l.Search)
}
}
func (l Lookup) populateMaxResults(query url.Values) {
maxResults := l.MaxResults
if maxResults < 1 {
maxResults = maxResultsDefault
}
query.Set("max_results", strconv.Itoa(maxResults))
}
func (l Lookup) populateDistance(query url.Values) {
distance := l.Distance
if distance < 1 {
distance = distanceDefault
}
query.Set("distance", strconv.Itoa(distance))
}
func (l Lookup) populateGeolocation(query url.Values) {
if l.Geolocation != None {
query.Set("geolocation", string(l.Geolocation))
} else {
query.Del("geolocation")
}
}
func (l Lookup) populateAdministrativeArea(query url.Values) {
if len(l.AdministrativeArea) > 0 {
query.Set("include_only_administrative_area", l.AdministrativeArea)
}
}
func (l Lookup) populateLocality(query url.Values) {
if len(l.Locality) > 0 {
query.Set("include_only_locality", l.Locality)
}
}
func (l Lookup) populatePostalCode(query url.Values) {
if len(l.PostalCode) > 0 {
query.Set("include_only_postal_code", l.PostalCode)
}
}
func (l Lookup) populateLatitude(query url.Values) {
if math.Floor(l.Latitude) != 0 {
query.Set("latitude", strconv.FormatFloat(l.Latitude, 'f', 8, 64))
}
}
func (l Lookup) populateLongitude(query url.Values) {
if math.Floor(l.Longitude) != 0 {
query.Set("longitude", strconv.FormatFloat(l.Longitude, 'f', 8, 64))
}
}

type InternationalGeolocateType string

const (
AdminArea = InternationalGeolocateType("adminarea")
Locality = InternationalGeolocateType("locality")
PostalCode = InternationalGeolocateType("postalcode")
Geocodes = InternationalGeolocateType("geocodes")
None = InternationalGeolocateType("")
)
Loading

0 comments on commit 127c428

Please sign in to comment.