From 39edcdcd191a1215683d8f119b5617b4f77767bd Mon Sep 17 00:00:00 2001 From: Jacob McKenzie Date: Mon, 19 Feb 2024 08:47:58 -0700 Subject: [PATCH 1/4] Added a generic send lookup method to the us-enrichment go sdk --- us-enrichment-api/client.go | 32 ++++++++++++++++++++++++++------ us-enrichment-api/lookup.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/us-enrichment-api/client.go b/us-enrichment-api/client.go index 67dadc3..5b33f6f 100644 --- a/us-enrichment-api/client.go +++ b/us-enrichment-api/client.go @@ -2,6 +2,7 @@ package us_enrichment import ( "context" + "encoding/json" "net/http" "strings" @@ -36,6 +37,18 @@ func (c *Client) SendPropertyPrincipal(lookup *Lookup) (error, []*PrincipalRespo return err, propertyLookup.Response } +func (c *Client) SendGenericLookup(lookup *Lookup, dataSet, dataSubset string) (error, []byte) { + propertyLookup := &genericLookup{ + Lookup: lookup, + DataSet: dataSet, + DataSubset: dataSubset, + } + + err := c.sendLookup(propertyLookup) + jsonData, _ := json.Marshal(propertyLookup.Response) + return err, jsonData +} + func (c *Client) sendLookup(lookup enrichmentLookup) error { return c.sendLookupWithContext(context.Background(), lookup) } @@ -81,15 +94,22 @@ func buildRequest(lookup enrichmentLookup) *http.Request { } func buildLookupURL(lookup enrichmentLookup) string { - newLookupURL := strings.Replace(lookupURL, lookupURLSmartyKey, lookup.getSmartyKey(), 1) + var newLookupURL string + if len(lookup.getDataSubset()) == 0 { + newLookupURL = strings.Replace(lookupURLWithoutSubSet, lookupURLSmartyKey, lookup.getSmartyKey(), 1) + } else { + newLookupURL = strings.Replace(lookupURLWithSubSet, 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. - lookupETagHeader = "Etag" + lookupURLSmartyKey = ":smartykey" + lookupURLDataSet = ":dataset" + lookupURLDataSubSet = ":datasubset" + lookupURLWithSubSet = "/lookup/" + lookupURLSmartyKey + "/" + lookupURLDataSet + "/" + lookupURLDataSubSet // Remaining parts will be completed later by the sdk.BaseURLClient. + lookupURLWithoutSubSet = "/lookup/" + lookupURLSmartyKey + "/" + lookupURLDataSet // Remaining parts will be completed later by the sdk.BaseURLClient. + lookupETagHeader = "Etag" ) diff --git a/us-enrichment-api/lookup.go b/us-enrichment-api/lookup.go index a7fdd71..bc60f8b 100644 --- a/us-enrichment-api/lookup.go +++ b/us-enrichment-api/lookup.go @@ -23,6 +23,39 @@ type enrichmentLookup interface { populate(query url.Values) } +type genericLookup struct { + Lookup *Lookup + DataSet string + DataSubset string + Response []map[string]interface{} +} + +func (g *genericLookup) getSmartyKey() string { return g.Lookup.SmartyKey } +func (g *genericLookup) getDataSet() string { return g.DataSet } +func (g *genericLookup) getDataSubset() string { return g.DataSubset } +func (g *genericLookup) getLookup() *Lookup { return g.Lookup } +func (g *genericLookup) getResponse() interface{} { return g.Response } +func (g *genericLookup) unmarshalResponse(bytes []byte, headers http.Header) error { + if err := json.Unmarshal(bytes, &g.Response); err != nil { + return err + } + + if headers != nil { + if etag, found := headers[lookupETagHeader]; found { + if len(etag) > 0 && len(g.Response) > 0 { + g.Response[0]["eTag"] = etag[0] + } + } + } + + return nil + +} +func (g *genericLookup) populate(query url.Values) { + g.Lookup.populateInclude(query) + g.Lookup.populateExclude(query) +} + //////////////////////////////////////////////////////////////////////////////////////// type financialLookup struct { From ef333269dd19b5e8a6e81eaf63aadda8090d7d42 Mon Sep 17 00:00:00 2001 From: Jacob McKenzie Date: Mon, 19 Feb 2024 11:21:12 -0700 Subject: [PATCH 2/4] Modified the generic lookup to eliminate marshaling and unmarshaling --- us-enrichment-api/client.go | 8 +++----- us-enrichment-api/lookup.go | 25 ++++++++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/us-enrichment-api/client.go b/us-enrichment-api/client.go index 5b33f6f..1f8df01 100644 --- a/us-enrichment-api/client.go +++ b/us-enrichment-api/client.go @@ -2,7 +2,6 @@ package us_enrichment import ( "context" - "encoding/json" "net/http" "strings" @@ -38,15 +37,14 @@ func (c *Client) SendPropertyPrincipal(lookup *Lookup) (error, []*PrincipalRespo } func (c *Client) SendGenericLookup(lookup *Lookup, dataSet, dataSubset string) (error, []byte) { - propertyLookup := &genericLookup{ + g := &genericLookup{ Lookup: lookup, DataSet: dataSet, DataSubset: dataSubset, } - err := c.sendLookup(propertyLookup) - jsonData, _ := json.Marshal(propertyLookup.Response) - return err, jsonData + err := c.sendLookup(g) + return err, g.Response } func (c *Client) sendLookup(lookup enrichmentLookup) error { diff --git a/us-enrichment-api/lookup.go b/us-enrichment-api/lookup.go index bc60f8b..cbbd58f 100644 --- a/us-enrichment-api/lookup.go +++ b/us-enrichment-api/lookup.go @@ -1,6 +1,7 @@ package us_enrichment import ( + "bytes" "encoding/json" "net/http" "net/url" @@ -27,7 +28,7 @@ type genericLookup struct { Lookup *Lookup DataSet string DataSubset string - Response []map[string]interface{} + Response []byte } func (g *genericLookup) getSmartyKey() string { return g.Lookup.SmartyKey } @@ -35,16 +36,22 @@ func (g *genericLookup) getDataSet() string { return g.DataSet } func (g *genericLookup) getDataSubset() string { return g.DataSubset } func (g *genericLookup) getLookup() *Lookup { return g.Lookup } func (g *genericLookup) getResponse() interface{} { return g.Response } -func (g *genericLookup) unmarshalResponse(bytes []byte, headers http.Header) error { - if err := json.Unmarshal(bytes, &g.Response); err != nil { - return err - } - +func (g *genericLookup) unmarshalResponse(bytesResponse []byte, headers http.Header) error { + g.Response = bytesResponse if headers != nil { - if etag, found := headers[lookupETagHeader]; found { - if len(etag) > 0 && len(g.Response) > 0 { - g.Response[0]["eTag"] = etag[0] + if etag, found := headers[lookupETagHeader]; found && len(etag) > 0 { + + eTagAttribute := []byte(`"eTag": "` + etag[0] + `",`) + insertLocation := bytes.IndexByte(bytesResponse, '{') + 1 + + if insertLocation > 0 && insertLocation < len(bytesResponse) { + var modifiedResponse bytes.Buffer + modifiedResponse.Write(bytesResponse[:insertLocation]) + modifiedResponse.Write(eTagAttribute) + modifiedResponse.Write(bytesResponse[insertLocation:]) + g.Response = modifiedResponse.Bytes() } + } } From b6dc573cabacf13a1a3387426d3ad8aaec14c95c Mon Sep 17 00:00:00 2001 From: Jacob McKenzie Date: Wed, 21 Feb 2024 08:16:46 -0700 Subject: [PATCH 3/4] Added tests for generic unMarshalResponse as well as renamed some member variables of said function for consistency --- us-enrichment-api/client_test.go | 23 +++++++++++++++++++++++ us-enrichment-api/lookup.go | 16 ++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/us-enrichment-api/client_test.go b/us-enrichment-api/client_test.go index 58548a4..99056c5 100644 --- a/us-enrichment-api/client_test.go +++ b/us-enrichment-api/client_test.go @@ -93,6 +93,29 @@ func (f *ClientFixture) TestDeserializationErrorPreventsDeserialization() { f.So(f.input.(*principalLookup).Response, should.BeEmpty) } +func (f *ClientFixture) TestGenericLookupUnmarshallingWithEtag() { + lookup := genericLookup{ + Response: []byte(validFinancialResponse), + } + httpHeaders := http.Header{"Etag": []string{"ABCDEFG"}} + + _ = lookup.unmarshalResponse(lookup.Response, httpHeaders) + + f.So(lookup.Response, should.Equal, []byte(`[{"eTag": "ABCDEFG","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"}}]`)) +} + +func (f *ClientFixture) TestGenericLookupUnmarshallingWithNoEtag() { + lookup := genericLookup{ + Response: []byte(validPrincipalResponse), + } + + httpHeaders := http.Header{"NotAnEtag": []string{"ABC"}} + + _ = lookup.unmarshalResponse(lookup.Response, httpHeaders) + + f.So(lookup.Response, should.Equal, []byte(validPrincipalResponse)) +} + 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"}}]` diff --git a/us-enrichment-api/lookup.go b/us-enrichment-api/lookup.go index cbbd58f..d9c1474 100644 --- a/us-enrichment-api/lookup.go +++ b/us-enrichment-api/lookup.go @@ -1,7 +1,7 @@ package us_enrichment import ( - "bytes" + bytesPackage "bytes" "encoding/json" "net/http" "net/url" @@ -36,19 +36,19 @@ func (g *genericLookup) getDataSet() string { return g.DataSet } func (g *genericLookup) getDataSubset() string { return g.DataSubset } func (g *genericLookup) getLookup() *Lookup { return g.Lookup } func (g *genericLookup) getResponse() interface{} { return g.Response } -func (g *genericLookup) unmarshalResponse(bytesResponse []byte, headers http.Header) error { - g.Response = bytesResponse +func (g *genericLookup) unmarshalResponse(bytes []byte, headers http.Header) error { + g.Response = bytes if headers != nil { if etag, found := headers[lookupETagHeader]; found && len(etag) > 0 { eTagAttribute := []byte(`"eTag": "` + etag[0] + `",`) - insertLocation := bytes.IndexByte(bytesResponse, '{') + 1 + insertLocation := bytesPackage.IndexByte(bytes, '{') + 1 - if insertLocation > 0 && insertLocation < len(bytesResponse) { - var modifiedResponse bytes.Buffer - modifiedResponse.Write(bytesResponse[:insertLocation]) + if insertLocation > 0 && insertLocation < len(bytes) { + var modifiedResponse bytesPackage.Buffer + modifiedResponse.Write(bytes[:insertLocation]) modifiedResponse.Write(eTagAttribute) - modifiedResponse.Write(bytesResponse[insertLocation:]) + modifiedResponse.Write(bytes[insertLocation:]) g.Response = modifiedResponse.Bytes() } From 875d69e9bd9f817d2b7630ca8c69b038fde83bf4 Mon Sep 17 00:00:00 2001 From: Jacob McKenzie Date: Mon, 26 Feb 2024 13:31:58 -0700 Subject: [PATCH 4/4] Renamed generic to universal, added tests, and created examples --- examples/us-enrichment-api/main.go | 2 + examples/us-enrichment-api/universal/main.go | 53 ++++++++++++++++++++ us-enrichment-api/client.go | 4 +- us-enrichment-api/lookup.go | 16 +++--- 4 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 examples/us-enrichment-api/universal/main.go diff --git a/examples/us-enrichment-api/main.go b/examples/us-enrichment-api/main.go index 24f5990..6cad381 100644 --- a/examples/us-enrichment-api/main.go +++ b/examples/us-enrichment-api/main.go @@ -51,5 +51,7 @@ func main() { fmt.Printf("#%d: %+v\n", s, response) } + //TODO: Add a test for the "genericLookup" feature + log.Println("OK") } diff --git a/examples/us-enrichment-api/universal/main.go b/examples/us-enrichment-api/universal/main.go new file mode 100644 index 0000000..0381b40 --- /dev/null +++ b/examples/us-enrichment-api/universal/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" + + us_enrichment "github.com/smartystreets/smartystreets-go-sdk/us-enrichment-api" + "github.com/smartystreets/smartystreets-go-sdk/wireup" +) + +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://www.smarty.com/docs/cloud/us-address-enrichment-api#http-request-input-fields + + smartyKey := "1682393594" + + lookup := us_enrichment.Lookup{ + SmartyKey: smartyKey, + Include: "group_structural,sale_date", // optional: only include these attributes in the returned data + Exclude: "", // optional: exclude attributes from the returned data + ETag: "", // optional: check if the record has been updated + } + + err, results := client.SendUniversalLookup(&lookup, "property", "principal") //for datasets with no subsets, enter the empty string, "", for the dataSubset field + + if err != nil { + // If ETag was supplied in the lookup, this status will be returned if the ETag value for the record is current + if client.IsHTTPErrorCode(err, http.StatusNotModified) { + log.Printf("Record has not been modified since the last request") + return + } + log.Fatal("Error sending lookup:", err) + } + + fmt.Printf("Results for input: (%s, %s)\n", smartyKey, "principal") + fmt.Println(string(results)) + + log.Println("OK") +} diff --git a/us-enrichment-api/client.go b/us-enrichment-api/client.go index 1f8df01..1b4d740 100644 --- a/us-enrichment-api/client.go +++ b/us-enrichment-api/client.go @@ -36,8 +36,8 @@ func (c *Client) SendPropertyPrincipal(lookup *Lookup) (error, []*PrincipalRespo return err, propertyLookup.Response } -func (c *Client) SendGenericLookup(lookup *Lookup, dataSet, dataSubset string) (error, []byte) { - g := &genericLookup{ +func (c *Client) SendUniversalLookup(lookup *Lookup, dataSet, dataSubset string) (error, []byte) { + g := &universalLookup{ Lookup: lookup, DataSet: dataSet, DataSubset: dataSubset, diff --git a/us-enrichment-api/lookup.go b/us-enrichment-api/lookup.go index d9c1474..264166d 100644 --- a/us-enrichment-api/lookup.go +++ b/us-enrichment-api/lookup.go @@ -24,19 +24,19 @@ type enrichmentLookup interface { populate(query url.Values) } -type genericLookup struct { +type universalLookup struct { Lookup *Lookup DataSet string DataSubset string Response []byte } -func (g *genericLookup) getSmartyKey() string { return g.Lookup.SmartyKey } -func (g *genericLookup) getDataSet() string { return g.DataSet } -func (g *genericLookup) getDataSubset() string { return g.DataSubset } -func (g *genericLookup) getLookup() *Lookup { return g.Lookup } -func (g *genericLookup) getResponse() interface{} { return g.Response } -func (g *genericLookup) unmarshalResponse(bytes []byte, headers http.Header) error { +func (g *universalLookup) getSmartyKey() string { return g.Lookup.SmartyKey } +func (g *universalLookup) getDataSet() string { return g.DataSet } +func (g *universalLookup) getDataSubset() string { return g.DataSubset } +func (g *universalLookup) getLookup() *Lookup { return g.Lookup } +func (g *universalLookup) getResponse() interface{} { return g.Response } +func (g *universalLookup) unmarshalResponse(bytes []byte, headers http.Header) error { g.Response = bytes if headers != nil { if etag, found := headers[lookupETagHeader]; found && len(etag) > 0 { @@ -58,7 +58,7 @@ func (g *genericLookup) unmarshalResponse(bytes []byte, headers http.Header) err return nil } -func (g *genericLookup) populate(query url.Values) { +func (g *universalLookup) populate(query url.Values) { g.Lookup.populateInclude(query) g.Lookup.populateExclude(query) }