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 67dadc3..1b4d740 100644 --- a/us-enrichment-api/client.go +++ b/us-enrichment-api/client.go @@ -36,6 +36,17 @@ func (c *Client) SendPropertyPrincipal(lookup *Lookup) (error, []*PrincipalRespo return err, propertyLookup.Response } +func (c *Client) SendUniversalLookup(lookup *Lookup, dataSet, dataSubset string) (error, []byte) { + g := &universalLookup{ + Lookup: lookup, + DataSet: dataSet, + DataSubset: dataSubset, + } + + err := c.sendLookup(g) + return err, g.Response +} + func (c *Client) sendLookup(lookup enrichmentLookup) error { return c.sendLookupWithContext(context.Background(), lookup) } @@ -81,15 +92,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/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 a7fdd71..264166d 100644 --- a/us-enrichment-api/lookup.go +++ b/us-enrichment-api/lookup.go @@ -1,6 +1,7 @@ package us_enrichment import ( + bytesPackage "bytes" "encoding/json" "net/http" "net/url" @@ -23,6 +24,45 @@ type enrichmentLookup interface { populate(query url.Values) } +type universalLookup struct { + Lookup *Lookup + DataSet string + DataSubset string + Response []byte +} + +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 { + + eTagAttribute := []byte(`"eTag": "` + etag[0] + `",`) + insertLocation := bytesPackage.IndexByte(bytes, '{') + 1 + + if insertLocation > 0 && insertLocation < len(bytes) { + var modifiedResponse bytesPackage.Buffer + modifiedResponse.Write(bytes[:insertLocation]) + modifiedResponse.Write(eTagAttribute) + modifiedResponse.Write(bytes[insertLocation:]) + g.Response = modifiedResponse.Bytes() + } + + } + } + + return nil + +} +func (g *universalLookup) populate(query url.Values) { + g.Lookup.populateInclude(query) + g.Lookup.populateExclude(query) +} + //////////////////////////////////////////////////////////////////////////////////////// type financialLookup struct {