From 464307b843c526fe37c654ced3e72fdffb1e887d Mon Sep 17 00:00:00 2001 From: Shubham Tiwari Date: Wed, 15 Nov 2023 15:52:02 +0530 Subject: [PATCH] feat: added data residency for eu and global regions --- base_interface.go | 34 ++++++++++++++++++++++++++++++++++ sendgrid.go | 26 ++++++++++++++++++++------ sendgrid_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/base_interface.go b/base_interface.go index ae826811..b4775770 100644 --- a/base_interface.go +++ b/base_interface.go @@ -5,6 +5,8 @@ import ( "compress/gzip" "context" "errors" + "fmt" + "log" "net/http" "strconv" "time" @@ -20,11 +22,17 @@ const ( rateLimitSleep = 1100 ) +var allowedRegionsHostMap = map[string]string{ + "eu": "https://api.eu.sendgrid.com", + "global": "https://api.sendgrid.com", +} + type options struct { Auth string Endpoint string Host string Subuser string + Region string } // Client is the Twilio SendGrid Go client @@ -49,12 +57,38 @@ func requestNew(options options) rest.Request { requestHeaders["On-Behalf-Of"] = options.Subuser } + host, err := setDataResidency(options) + if err == nil { + options.Host = host + } else { + fmt.Println(err) + log.Println(err) + } + return rest.Request{ BaseURL: options.baseURL(), Headers: requestHeaders, } } +func setDataResidency(options options) (string, error) { + currentHost := options.Host + defaultHost := allowedRegionsHostMap["global"] + if currentHost != defaultHost { // for testing, the hostname can be different + return currentHost, nil + } + region := options.Region + if region != "" { + regionalHost, isPresent := allowedRegionsHostMap[region] + if isPresent { + return regionalHost, nil + } else { + return defaultHost, errors.New("error: region can only be \"eu\" or \"global\"") + } + } + return defaultHost, nil +} + // Send sends an email through Twilio SendGrid func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) { return cl.SendWithContext(context.Background(), email) diff --git a/sendgrid.go b/sendgrid.go index 7192bec2..fc8a2945 100644 --- a/sendgrid.go +++ b/sendgrid.go @@ -10,18 +10,27 @@ type sendGridOptions struct { Endpoint string Host string Subuser string + Region string } // GetRequest // @return [Request] a default request object -func GetRequest(key, endpoint, host string) rest.Request { - return createSendGridRequest(sendGridOptions{key, endpoint, host, ""}) +func GetRequest(key, endpoint, host string, regionOptional ...string) rest.Request { + region := "" + if len(regionOptional) > 0 { + region = regionOptional[0] + } + return createSendGridRequest(sendGridOptions{key, endpoint, host, "", region}) } // GetRequestSubuser like GetRequest but with On-Behalf of Subuser // @return [Request] a default request object -func GetRequestSubuser(key, endpoint, host, subuser string) rest.Request { - return createSendGridRequest(sendGridOptions{key, endpoint, host, subuser}) +func GetRequestSubuser(key, endpoint, host, subuser string, regionOptional ...string) rest.Request { + region := "" + if len(regionOptional) > 0 { + region = regionOptional[0] + } + return createSendGridRequest(sendGridOptions{key, endpoint, host, subuser, region}) } // createSendGridRequest create Request @@ -32,6 +41,7 @@ func createSendGridRequest(sgOptions sendGridOptions) rest.Request { sgOptions.Endpoint, sgOptions.Host, sgOptions.Subuser, + sgOptions.Region, } if options.Host == "" { @@ -42,8 +52,12 @@ func createSendGridRequest(sgOptions sendGridOptions) rest.Request { } // NewSendClient constructs a new Twilio SendGrid client given an API key -func NewSendClient(key string) *Client { - request := GetRequest(key, "/v3/mail/send", "") +func NewSendClient(key string, regionOptional ...string) *Client { + region := "" + if len(regionOptional) > 0 { + region = regionOptional[0] + } + request := GetRequest(key, "/v3/mail/send", "", region) request.Method = "POST" return &Client{request} } diff --git a/sendgrid_test.go b/sendgrid_test.go index ada4dfe1..22d70511 100644 --- a/sendgrid_test.go +++ b/sendgrid_test.go @@ -1,9 +1,11 @@ package sendgrid import ( + "bytes" "context" "encoding/json" "fmt" + "log" "net/http" "net/http/httptest" "os" @@ -81,6 +83,41 @@ func TestGetRequestSubuser(t *testing.T) { ShouldHaveHeaders(&request, t) } +func Test_test_set_residency_eu(t *testing.T) { + request := GetRequest("API_KEY", "", "", "eu") + assert.Equal(t, "https://api.eu.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + +func Test_test_set_residency_global(t *testing.T) { + request := GetRequest("API_KEY", "", "https://api.sendgrid.com", "global") + assert.Equal(t, "https://api.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + +func Test_test_set_residency_override_host(t *testing.T) { + request := GetRequest("API_KEY", "", "https://api.sendgrid.com", "eu") + assert.Equal(t, "https://api.eu.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + +func Test_test_set_residency_default(t *testing.T) { + request := GetRequest("API_KEY", "", "") + assert.Equal(t, "https://api.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + +func Test_test_set_residency_incorrect_region(t *testing.T) { + var buffer bytes.Buffer + log.SetOutput(&buffer) + request := GetRequest("API_KEY", "", "", "foo") + + log.SetOutput(new(bytes.Buffer)) + capturedOutput := buffer.String() + + expectedErrorMessage := "error: region can only be \"eu\" or \"global\"" + if !strings.Contains(capturedOutput, expectedErrorMessage) { + t.Errorf("Expected error message '%s' not found on setting invalid region", expectedErrorMessage) + } + assert.Equal(t, "https://api.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + func getRequest(endpoint string) rest.Request { return GetRequest("SENDGRID_APIKEY", endpoint, "") }