Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ Two endpoints so far, both accepting `addr` parameter.
}
],
"Address": {
"ACRateAccountKey": "12342478585",
"Address": "500 Queen Street, Auckland Central",
"Suggestion": "500 Queen Street, Auckland Central"
"ID": "12342478585",
"Address": "500 Queen Street, Auckland Central"
}
}

Expand Down
64 changes: 38 additions & 26 deletions addr.go
Original file line number Diff line number Diff line change
@@ -1,73 +1,85 @@
package aklapi

import (
"bytes"
"encoding/json"
"errors"
"log"
"net/http"
"strconv"
"time"
)

var (
// defined as a variable so it can be overridden in tests.
addrURI = `https://www.aucklandcouncil.govt.nz/_vti_bin/ACWeb/ACservices.svc/GetMatchingPropertyAddresses`
addrURI = `https://www.aucklandcouncil.govt.nz/nextapi/property`
)

// AddrRequest is the address request.
type AddrRequest struct {
ResultCount int `json:"ResultCount"`
SearchText string `json:"SearchText"`
RateKeyRequired bool `json:"RateKeyRequired"`
PageSize int
SearchText string
}

// AddrResponse is the address response.
type AddrResponse []Address

// AddrSuggestion is the address suggestion.
// Address is the address and its unique identifier (rate account key).
type Address struct {
ACRateAccountKey string `json:"ACRateAccountKey"`
Address string `json:"Address"`
Suggestion string `json:"Suggestion"`
ID string `json:"ID"`
Address string `json:"Address"`
}

// AddrResponse is the address response.
type AddrResponse struct {
Items []Address `json:"items"`
}

func (s Address) String() string {
return "<" + s.Address + " (" + s.ACRateAccountKey + ")>"
return "<" + s.Address + " (" + s.ID + ")>"
}

// AddressLookup is a convenience function to get addresses.
func AddressLookup(addr string) (AddrResponse, error) {
return MatchingPropertyAddresses(&AddrRequest{SearchText: addr, RateKeyRequired: false, ResultCount: 10})
func AddressLookup(addr string) (*AddrResponse, error) {
return MatchingPropertyAddresses(&AddrRequest{SearchText: addr, PageSize: 10})
}

// MatchingPropertyAddresses wrapper around the AKL Council API.
func MatchingPropertyAddresses(addrReq *AddrRequest) (AddrResponse, error) {
func MatchingPropertyAddresses(addrReq *AddrRequest) (*AddrResponse, error) {
cachedAr, ok := addrCache.Lookup(addrReq.SearchText)
if ok {
log.Printf("cached address result: %q", cachedAr)
return cachedAr, nil
}
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
if err := enc.Encode(addrReq); err != nil {

req, err := http.NewRequest("GET", addrURI, nil)
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Add("query", addrReq.SearchText)
if addrReq.PageSize > 0 {
q.Add("pageSize", strconv.Itoa(addrReq.PageSize))
}
req.URL.RawQuery = q.Encode()

start := time.Now()
resp, err := http.Post(addrURI, "application/json; charset=UTF-8", &buf)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
log.Printf("address call complete in %s", time.Since(start))

if resp.StatusCode != http.StatusOK {
return nil, errors.New("address API returned status code: " + strconv.Itoa(resp.StatusCode))
}

dec := json.NewDecoder(resp.Body)
ar := AddrResponse{}
if err := dec.Decode(&ar); err != nil {
var apiResp AddrResponse
if err := dec.Decode(&apiResp); err != nil {
return nil, err
}
addrCache.Add(addrReq.SearchText, ar)
return ar, nil

addrCache.Add(addrReq.SearchText, &apiResp)
return &apiResp, nil
}

func oneAddress(addr string) (*Address, error) {
Expand All @@ -76,8 +88,8 @@ func oneAddress(addr string) (*Address, error) {
return nil, err
}
// need exactly one address to continue
if len(resp) != 1 {
if len(resp.Items) != 1 {
return nil, errors.New("ambiguous or empty address results")
}
return &resp[0], nil
return &resp.Items[0], nil
}
6 changes: 3 additions & 3 deletions addr_cache.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package aklapi

type addrResponseCache map[string]AddrResponse
type addrResponseCache map[string]*AddrResponse

var addrCache = make(addrResponseCache)

func (c addrResponseCache) Lookup(searchText string) (resp AddrResponse, ok bool) {
func (c addrResponseCache) Lookup(searchText string) (resp *AddrResponse, ok bool) {
if NoCache {
return nil, false
}
resp, ok = c[searchText]
return
}

func (c addrResponseCache) Add(searchText string, ar AddrResponse) {
func (c addrResponseCache) Add(searchText string, ar *AddrResponse) {
c[searchText] = ar
}
16 changes: 8 additions & 8 deletions addr_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ func Test_addrResponseCache_Lookup(t *testing.T) {
NoCache bool // if true, set NoCache to true before running test
c addrResponseCache
args args
wantResp AddrResponse
wantResp *AddrResponse
wantOk bool
}{
{"not in cache",
false,
addrResponseCache{
"xxx": AddrResponse{*testAddr},
"xxx": &AddrResponse{Items: []Address{*testAddr}},
},
args{"yyy"},
nil,
Expand All @@ -34,16 +34,16 @@ func Test_addrResponseCache_Lookup(t *testing.T) {
{"cached",
false,
addrResponseCache{
testAddr.Address: AddrResponse{*testAddr},
testAddr.Address: &AddrResponse{Items: []Address{*testAddr}},
},
args{testAddr.Address},
AddrResponse{*testAddr},
&AddrResponse{Items: []Address{*testAddr}},
true,
},
{"cached, no cache mode",
true,
addrResponseCache{
testAddr.Address: AddrResponse{*testAddr},
testAddr.Address: &AddrResponse{Items: []Address{*testAddr}},
},
args{testAddr.Address},
nil,
Expand All @@ -67,7 +67,7 @@ func Test_addrResponseCache_Lookup(t *testing.T) {
func Test_addrResponseCache_Add(t *testing.T) {
type args struct {
searchText string
ar AddrResponse
ar *AddrResponse
}
tests := []struct {
name string
Expand All @@ -77,9 +77,9 @@ func Test_addrResponseCache_Add(t *testing.T) {
}{
{"add",
addrResponseCache{},
args{testAddr.Address, AddrResponse{*testAddr}},
args{testAddr.Address, &AddrResponse{Items: []Address{*testAddr}}},
addrResponseCache{
testAddr.Address: AddrResponse{*testAddr},
testAddr.Address: &AddrResponse{Items: []Address{*testAddr}},
},
},
}
Expand Down
37 changes: 22 additions & 15 deletions addr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import (
)

var testAddr = &Address{
ACRateAccountKey: "42",
Address: "Red Square",
Suggestion: "Red Square",
ID: "42",
Address: "Red Square",
}

func TestMatchingPropertyAddresses(t *testing.T) {
Expand All @@ -23,21 +22,29 @@ func TestMatchingPropertyAddresses(t *testing.T) {
name string
testSrv *httptest.Server
args args
want AddrResponse
want *AddrResponse
wantErr bool
}{
{"main branch",
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writeAddrJSON(w, AddrResponse{*testAddr})
writeAddrJSON(w, AddrResponse{Items: []Address{*testAddr}})
})),
args{&AddrRequest{
ResultCount: 1,
SearchText: "red sq",
RateKeyRequired: false,
PageSize: 1,
SearchText: "red sq",
}},
AddrResponse{*testAddr},
&AddrResponse{Items: []Address{*testAddr}},
false,
},
{"non-200 status code",
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
writeAddrJSON(w, AddrResponse{})
})),
args{&AddrRequest{}},
nil,
true,
},
{"post error",
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
Expand Down Expand Up @@ -81,15 +88,15 @@ func TestAddress(t *testing.T) {
name string
testSrv *httptest.Server
args args
want AddrResponse
want *AddrResponse
wantErr bool
}{
{"main branch",
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writeAddrJSON(w, AddrResponse{*testAddr})
writeAddrJSON(w, AddrResponse{Items: []Address{*testAddr}})
})),
args{"red square"},
AddrResponse{*testAddr},
&AddrResponse{Items: []Address{*testAddr}},
false,
},
}
Expand Down Expand Up @@ -124,23 +131,23 @@ func Test_oneAddress(t *testing.T) {
}{
{"one address",
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writeAddrJSON(w, AddrResponse{*testAddr})
writeAddrJSON(w, AddrResponse{Items: []Address{*testAddr}})
})),
args{"red square"},
testAddr,
false,
},
{"several address",
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writeAddrJSON(w, AddrResponse{*testAddr, *testAddr})
writeAddrJSON(w, AddrResponse{Items: []Address{*testAddr, *testAddr}})
})),
args{"red squarex"},
nil,
true,
},
{"no address",
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("[]"))
w.Write([]byte("{\"items\":[]}"))
})),
args{"red squarec"},
nil,
Expand Down
6 changes: 3 additions & 3 deletions rubbish.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func CollectionDayDetail(addr string) (*CollectionDayDetailResult, error) {
return nil, err
}
start := time.Now()
result, err := fetchandparse(address.ACRateAccountKey)
result, err := fetchandparse(address.ID)
if err != nil {
return nil, err
}
Expand All @@ -112,8 +112,8 @@ func CollectionDayDetail(addr string) (*CollectionDayDetailResult, error) {
}

// fetchandparse retrieves the data from the webpage and attempts to parse it.
func fetchandparse(ACRateAccountKey string) (*CollectionDayDetailResult, error) {
resp, err := http.Get(fmt.Sprintf(collectionDayURI, ACRateAccountKey))
func fetchandparse(addressID string) (*CollectionDayDetailResult, error) {
resp, err := http.Get(fmt.Sprintf(collectionDayURI, addressID))
if err != nil {
return nil, err
}
Expand Down
Loading