Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added data residency for eu and global regions #469

Merged
merged 7 commits into from
Nov 21, 2023
50 changes: 50 additions & 0 deletions base_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"errors"
"net/http"
"net/url"
"strconv"
"time"

Expand All @@ -20,6 +21,11 @@ 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
Expand Down Expand Up @@ -55,6 +61,50 @@ func requestNew(options options) rest.Request {
}
}

// extractEndpoint extracts the endpoint from a baseURL
func extractEndpoint(link string) (string, error) {
parsedURL, err := url.Parse(link)
if err != nil {
return "", err
}

return parsedURL.Path, nil
}

// SetHost changes the baseURL of the request with the host passed
/*
* This allows support for global and eu regions only. This set will likely expand in the future.
* Global should be the default
* Global region means the message should be sent through:
* HTTP: api.sendgrid.com
* EU region means the message should be sent through:
* HTTP: api.eu.sendgrid.com
*/
// @return [Request] the modified request object
func SetHost(request rest.Request, host string) (rest.Request, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public functions should be documented

endpoint, err := extractEndpoint(request.BaseURL)
if err != nil {
return request, err
}

request.BaseURL = host + endpoint
return request, nil
}

// SetDataResidency modifies the host as per the region
// @return [Request] the modified request object
func SetDataResidency(request rest.Request, region string) (rest.Request, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add comments to document what these function does, especially how it manipulates the host field

regionalHost, present := allowedRegionsHostMap[region]
if !present {
return request, errors.New("error: region can only be \"eu\" or \"global\"")
}
request, err := SetHost(request, regionalHost)
if err != nil {
return request, err
}
return request, nil
}

// Send sends an email through Twilio SendGrid
func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) {
return cl.SendWithContext(context.Background(), email)
Expand Down
100 changes: 100 additions & 0 deletions examples/dataresidency/setRegion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"fmt"
"log"
"os"

"github.com/sendgrid/rest"
"github.com/sendgrid/sendgrid-go/helpers/mail"

"github.com/sendgrid/sendgrid-go"
)

var SAMPLE_EMAIL = "[email protected]"

// SetDataResidency : Set region for sendgrid.
func SetDataResidencyGlobal() {
message := buildHelloEmail()
request, err := buildSendgridObj("global")
if err != nil {
log.Println(err)
} else {
request.Body = mail.GetRequestBody(message)
response, err := sendgrid.API(request)
if err != nil {
log.Println(err)
} else {
fmt.Println(response.StatusCode)
fmt.Println(response.Body)
fmt.Println(response.Headers)
}
}
}

func SetDataResidencyEu() {
message := buildHelloEmail()
request, err := buildSendgridObj("eu")
if err != nil {
log.Println(err)
} else {
request.Body = mail.GetRequestBody(message)
response, err := sendgrid.API(request)
if err != nil {
log.Println(err)
} else {
fmt.Println(response.StatusCode)
fmt.Println(response.Body)
fmt.Println(response.Headers)
}
}
}

func SetDataResidencyDefault() {
message := buildHelloEmail()
request := sendgrid.GetRequest(os.Getenv("SENDGRID_API_KEY"), "/v3/mail/send", "")
request.Method = "POST"
request.Body = mail.GetRequestBody(message)
response, err := sendgrid.API(request)
if err != nil {
log.Println(err)
} else {
fmt.Println(response.StatusCode)
fmt.Println(response.Body)
fmt.Println(response.Headers)
}
}

func buildHelloEmail() *mail.SGMailV3 {
// Note that when you use this constructor an initial personalization object
// is created for you. It can be accessed via
// mail.personalization.get(0) as it is a List object

from := mail.NewEmail("test_user", SAMPLE_EMAIL)
subject := "Sending with Twilio SendGrid is Fun"
to := mail.NewEmail("test_user", SAMPLE_EMAIL)
plainTextContent := "and easy to do anywhere, even with Go"
htmlContent := "<strong>and easy to do anywhere, even with Go</strong>"
message := mail.NewSingleEmail(from, subject, to, plainTextContent, htmlContent)
email := mail.NewEmail("test_user", SAMPLE_EMAIL)

p := mail.NewPersonalization()
p.AddTos(email)
message.AddPersonalizations(p)

return message
}

func buildSendgridObj(region string) (rest.Request, error) {
request := sendgrid.GetRequest(os.Getenv("SENDGRID_API_KEY"), "/v3/mail/send", "")
request.Method = "POST"
request, err := sendgrid.SetDataResidency(request, region)
if err != nil {
return request, err
}
return request, nil
}

func main() {
// add your function calls here
}
47 changes: 47 additions & 0 deletions sendgrid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,53 @@ func TestGetRequestSubuser(t *testing.T) {
ShouldHaveHeaders(&request, t)
}

func TestSetDataResidencyEU(t *testing.T) {
request := GetRequest("API_KEY", "", "")
request, err := SetDataResidency(request, "eu")
assert.Nil(t, err)
assert.Equal(t, "https://api.eu.sendgrid.com", request.BaseURL, "Host not correct as per the region")
}

func TestSetDataResidencyGlobal(t *testing.T) {
request := GetRequest("API_KEY", "", "https://api.sendgrid.com")
request, err := SetDataResidency(request, "global")
assert.Nil(t, err)
assert.Equal(t, "https://api.sendgrid.com", request.BaseURL, "Host not correct as per the region")
}

func TestSetDataResidencyOverrideHost(t *testing.T) {
request := GetRequest("API_KEY", "", "https://test.api.com")
request, err := SetDataResidency(request, "eu")
assert.Nil(t, err)
assert.Equal(t, "https://api.eu.sendgrid.com", request.BaseURL, "Host not correct as per the region")
}

func TestSetDataResidencyOverrideDataResidency(t *testing.T) {
request := GetRequest("API_KEY", "", "")
request, err := SetDataResidency(request, "eu")
assert.Nil(t, err)
request, err = SetHost(request, "https://test.api.com")
assert.Nil(t, err)
assert.Equal(t, "https://test.api.com", request.BaseURL, "Host not correct as per the region")
}

func TestSetDataResidencyIncorrectRegion(t *testing.T) {
request := GetRequest("API_KEY", "", "")
_, err := SetDataResidency(request, "foo")
assert.NotNil(t, err, "error: region can only be \"eu\" or \"global\"")
}

func TestSetDataResidencyNullRegion(t *testing.T) {
request := GetRequest("API_KEY", "", "")
_, err := SetDataResidency(request, "")
assert.NotNil(t, err, "error: region can only be \"eu\" or \"global\"")
}

func TestSetDataResidencyDefaultRegion(t *testing.T) {
request := GetRequest("API_KEY", "", "")
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, "")
}
Expand Down
Loading