Skip to content

Commit

Permalink
Us enrichment api (#33)
Browse files Browse the repository at this point in the history
* Initial code for us-enrichmemt-api.

* Implement V1 enrichment sdk

implemented full enrichment sdk functionality.

---------

Co-authored-by: Bryan Amundson <[email protected]>
Co-authored-by: Michael Whatcott <[email protected]>
  • Loading branch information
3 people authored Oct 26, 2023
1 parent a3b88da commit 4222c10
Show file tree
Hide file tree
Showing 8 changed files with 809 additions and 1 deletion.
40 changes: 40 additions & 0 deletions examples/us-enrichment-api/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"fmt"
"github.com/smartystreets/smartystreets-go-sdk/wireup"
"log"
"os"
)

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

client := wireup.BuildUSEnrichmentAPIClient(
//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.smarty.com/docs/cloud/licensing
wireup.WithLicenses("us-property-data-principal-cloud"),
// wireup.DebugHTTPOutput(), // uncomment this line to see detailed HTTP request/response information.
)

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

smartyKey := "1682393594"

err, results := client.SendPropertyPrincipalLookup(smartyKey)

if err != nil {
log.Fatal("Error sending lookup:", err)
}

fmt.Printf("Results for input: (%s, %s)\n", smartyKey, "principal")
for s, response := range results {
fmt.Printf("#%d: %+v\n", s, response)
}

log.Println("OK")
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/smartystreets/smartystreets-go-sdk

go 1.13
go 1.18

require (
github.com/smarty/assertions v1.15.1
Expand Down
78 changes: 78 additions & 0 deletions us-enrichment-api/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package us_enrichment

import (
"context"
"net/http"
"strings"

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

type Client struct {
sender sdk.RequestSender
}

func NewClient(sender sdk.RequestSender) *Client {
return &Client{sender: sender}
}

func (c *Client) SendPropertyFinancialLookup(smartyKey string) (error, []*FinancialResponse) {
l := &financialLookup{
SmartyKey: smartyKey,
}
err := c.sendLookup(l)

return err, l.Response
}

func (c *Client) SendPropertyPrincipalLookup(smartyKey string) (error, []*PrincipalResponse) {
l := &principalLookup{
SmartyKey: smartyKey,
}
err := c.sendLookup(l)

return err, l.Response
}

func (c *Client) sendLookup(lookup enrichmentLookup) error {
return c.sendLookupWithContext(context.Background(), lookup)
}

func (c *Client) sendLookupWithContext(ctx context.Context, lookup enrichmentLookup) error {
if lookup == nil {
return nil
}
if len(lookup.GetSmartyKey()) == 0 {
return nil
}

request := buildRequest(lookup)
request = request.WithContext(ctx)

response, err := c.sender.Send(request)
if err != nil {
return err
}

return lookup.UnmarshalResponse(response)
}

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

func buildLookupURL(lookup enrichmentLookup) string {
newLookupURL := strings.Replace(lookupURL, lookupURLSmartyKey, lookup.GetSmartyKey(), 1)
newLookupURL = strings.Replace(newLookupURL, lookupURLDataSet, lookup.GetDataSet(), 1)
return strings.Replace(newLookupURL, lookupURLDataSubSet, lookup.GetDataSubset(), 1)
}

const (
lookupURLSmartyKey = ":smartykey"
lookupURLDataSet = ":dataset"
lookupURLDataSubSet = ":datasubset"
lookupURL = "/lookup/" + lookupURLSmartyKey + "/" + lookupURLDataSet + "/" + lookupURLDataSubSet // Remaining parts will be completed later by the sdk.BaseURLClient.
)
114 changes: 114 additions & 0 deletions us-enrichment-api/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package us_enrichment

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 enrichmentLookup
}

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

func (f *ClientFixture) TestLookupSerializedAndSentWithContext__ResponseSuggestionsIncorporatedIntoLookup() {
smartyKey := "123"
f.sender.response = validFinancialResponse
f.input = &financialLookup{
SmartyKey: smartyKey,
}

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, "/lookup/"+smartyKey+"/"+f.input.GetDataSet()+"/"+f.input.GetDataSubset())
f.So(f.sender.request.Context(), should.Resemble, ctx)

response := f.input.(*financialLookup).Response

f.So(response, should.Resemble, []*FinancialResponse{
{
SmartyKey: "123",
DataSetName: "property",
DataSubsetName: "financial",
Attributes: FinancialAttributes{
AssessedImprovementPercent: "Assessed_Improvement_Percent",
VeteranTaxExemption: "Veteran_Tax_Exemption",
WidowTaxExemption: "Widow_Tax_Exemption",
},
},
})
}

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(principalLookup))
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 = validPrincipalResponse // would be deserialized if not for the err (above)
f.input = &principalLookup{SmartyKey: "12345"}

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

f.So(err, should.NotBeNil)
f.So(f.input.(*principalLookup).Response, should.BeEmpty)
}

func (f *ClientFixture) TestDeserializationErrorPreventsDeserialization() {
f.sender.response = `I can't haz JSON`
f.input = &principalLookup{SmartyKey: "12345"}

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

f.So(err, should.NotBeNil)
f.So(f.input.(*principalLookup).Response, should.BeEmpty)
}

var validFinancialResponse = `[{"smarty_key":"123","data_set_name":"property","data_subset_name":"financial","attributes":{"assessed_improvement_percent":"Assessed_Improvement_Percent","veteran_tax_exemption":"Veteran_Tax_Exemption","widow_tax_exemption":"Widow_Tax_Exemption"}}]`
var validPrincipalResponse = `[{"smarty_key":"123","data_set_name":"property","data_subset_name":"principal","attributes":{"1st_floor_sqft":"1st_Floor_Sqft",lender_name_2":"Lender_Name_2","lender_seller_carry_back":"Lender_Seller_Carry_Back","year_built":"Year_Built","zoning":"Zoning"}}]`

/**************************************************************************/

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
}
61 changes: 61 additions & 0 deletions us-enrichment-api/lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package us_enrichment

import (
"encoding/json"
)

type enrichmentLookup interface {
GetSmartyKey() string
GetDataSet() string
GetDataSubset() string

UnmarshalResponse([]byte) error
}

type financialLookup struct {
SmartyKey string
Response []*FinancialResponse
}

func (f *financialLookup) GetSmartyKey() string {
return f.SmartyKey
}

func (f *financialLookup) GetDataSet() string {
return propertyDataSet
}

func (f *financialLookup) GetDataSubset() string {
return financialDataSubset
}

func (f *financialLookup) UnmarshalResponse(bytes []byte) error {
return json.Unmarshal(bytes, &f.Response)
}

type principalLookup struct {
SmartyKey string
Response []*PrincipalResponse
}

func (p *principalLookup) GetSmartyKey() string {
return p.SmartyKey
}

func (p *principalLookup) GetDataSet() string {
return propertyDataSet
}

func (p *principalLookup) GetDataSubset() string {
return principalDataSubset
}

func (p *principalLookup) UnmarshalResponse(bytes []byte) error {
return json.Unmarshal(bytes, &p.Response)
}

const (
financialDataSubset = "financial"
principalDataSubset = "principal"
propertyDataSet = "property"
)
Loading

0 comments on commit 4222c10

Please sign in to comment.