Repository: googlemaps/google-maps-services-go
Pull request #248 (2020-11-02 14:01:36)

feat: add ability to pass the experience id within the context
In scenario when singleton SDK client used to make calls for different customers, setting the [experienceId](https://developers.google.com/maps/documentation/one_per_ride/managing-trips#go_example_2) explicitly for every new call leads to the race conditions between the different requests.

I propose this PR to add ability passing the experienceId using context.

googlebot: 
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed (or fixed any issues), please reply here with `@googlebot I signed it!` and we'll verify it.

----

#### What to do if you already signed the CLA

##### Individual signers

*   It's possible we don't have your GitHub username or you're using a different email address on your commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

##### Corporate signers

*   Your company has a Point of Contact who decides which employees are authorized to participate. Ask your POC to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the Google project maintainer to [go/cla#troubleshoot](http://go/cla#troubleshoot) ([Public version](https://opensource.google/docs/cla/#troubleshoot)).
*   The email used to register you as an authorized contributor must be the email used for the Git commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
*   The email used to register you as an authorized contributor must also be [attached to your GitHub account](https://github.com/settings/emails).
		

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgooglemaps%2Fgoogle-maps-services-go%2Fpull%2F248) for more info**.

<!-- need_sender_cla -->
google-cla[bot]: 
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed (or fixed any issues), please reply here with `@googlebot I signed it!` and we'll verify it.

----

#### What to do if you already signed the CLA

##### Individual signers

*   It's possible we don't have your GitHub username or you're using a different email address on your commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

##### Corporate signers

*   Your company has a Point of Contact who decides which employees are authorized to participate. Ask your POC to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the Google project maintainer to [go/cla#troubleshoot](http://go/cla#troubleshoot) ([Public version](https://opensource.google/docs/cla/#troubleshoot)).
*   The email used to register you as an authorized contributor must be the email used for the Git commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
*   The email used to register you as an authorized contributor must also be [attached to your GitHub account](https://github.com/settings/emails).
		

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgooglemaps%2Fgoogle-maps-services-go%2Fpull%2F248) for more info**.

<!-- need_sender_cla -->
jpoehnelt: LGTM, just need to sign CLA https://github.com/googlemaps/google-maps-services-go/pull/248#issuecomment-720489974
feodor-shi: @googlebot I signed it!
jpoehnelt: It's showing as not signed for the email associated with the commit and github account. 

f****.s*****@c****m.com




feodor-shi: > It's showing as not signed for the email associated with the commit and github account.
> 
> f****.s*****@c****m.com

hmm in the current agreement I put our team group e**-t**@c****m.com and my primary github account's email what you posted "f****.s*****@c****m.com" is the member of that group. Is there any hint what I can verify from my side, why is not visible on your side?
googlebot: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgooglemaps%2Fgoogle-maps-services-go%2Fpull%2F248) for more info**.

<!-- ok -->
jpoehnelt: I forced a rescan and it found it.

Patch:
diff --git a/client.go b/client.go
index b19091e..9164f60 100644
--- a/client.go
+++ b/client.go
@@ -50,8 +50,14 @@ type ClientOption func(*Client) error
 
 var defaultRequestsPerSecond = 50
 
+type contextKey string
+func (c contextKey) String() string {
+	return "maps " + string(c)
+}
+
 const (
 	ExperienceIdHeaderName = "X-GOOG-MAPS-EXPERIENCE-ID"
+	contextExperienceId    = contextKey("EXP-IDS")
 )
 
 // NewClient constructs a new Client which can make requests to the Google Maps
@@ -118,6 +124,23 @@ func WithAPIKeyAndSignature(apiKey, signature string) ClientOption {
 	}
 }
 
+// ExperienceIdContext injects the experienceIds in the context, from where they will be pull out
+// in the post/get handlers. Useful if a customer uses one client instance per different experiences calls
+func ExperienceIdContext(ctx context.Context, experienceIds ...string) context.Context {
+	if ctx != nil {
+		return context.WithValue(ctx, contextExperienceId, experienceIds)
+	}
+	return ctx
+}
+
+// ExperienceIdFromContext returns experienceIds from context if presented
+func ExperienceIdFromContext(ctx context.Context) []string {
+	if experiencesId := ctx.Value(contextExperienceId); experiencesId != nil {
+		return experiencesId.([]string)
+	}
+	return nil
+}
+
 // WithBaseURL configures a Maps API client with a custom base url
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
@@ -205,7 +228,7 @@ func (c *Client) get(ctx context.Context, config *apiConfig, apiReq apiRequest)
 		return nil, err
 	}
 
-	c.setExperienceIdHeader(req)
+	c.setExperienceIdHeader(ctx, req)
 
 	q, err := c.generateAuthQuery(config.path, apiReq.params(), config.acceptsClientID, config.acceptsSignature)
 	if err != nil {
@@ -235,7 +258,7 @@ func (c *Client) post(ctx context.Context, config *apiConfig, apiReq interface{}
 	}
 	req.Header.Set("Content-Type", "application/json")
 
-	c.setExperienceIdHeader(req)
+	c.setExperienceIdHeader(ctx, req)
 
 	q, err := c.generateAuthQuery(config.path, url.Values{}, config.acceptsClientID, config.acceptsSignature)
 	if err != nil {
@@ -294,9 +317,18 @@ func (c *Client) clearExperienceId() {
 	c.experienceId = nil
 }
 
-func (c *Client) setExperienceIdHeader(req *http.Request) {
+func (c *Client) setExperienceIdHeader(ctx context.Context, req *http.Request) {
+	var ids []string
 	if len(c.experienceId) > 0 {
-		req.Header.Set(ExperienceIdHeaderName, strings.Join(c.experienceId, ","))
+		ids = append(ids, c.experienceId...)
+	}
+	if experiencesId := ExperienceIdFromContext(ctx); experiencesId != nil {
+		for _, v := range experiencesId {
+			ids = append(ids, v)
+		}
+	}
+	if len(ids) != 0 {
+		req.Header.Set(ExperienceIdHeaderName, strings.Join(ids, ","))
 	}
 }
 


Test patch:
diff --git a/client_test.go b/client_test.go
index 30c9a8d..98f99a0 100644
--- a/client_test.go
+++ b/client_test.go
@@ -15,6 +15,7 @@
 package maps
 
 import (
+	"context"
 	"net/http"
 	"strings"
 	"testing"
@@ -69,20 +70,45 @@ func TestClientSetExperienceIdHeader(t *testing.T) {
 	// slice has two elements
 	c.experienceId = ids
 	req, _ := http.NewRequest("GET", "/", nil)
-	c.setExperienceIdHeader(req)
-	assert.Equal(t, req.Header.Get(ExperienceIdHeaderName), strings.Join(ids, ","))
+	c.setExperienceIdHeader(context.Background(), req)
+	assert.Equal(t, strings.Join(ids, ","), req.Header.Get(ExperienceIdHeaderName))
 
 	// slice is nil
 	c.experienceId = nil
 	req, _ = http.NewRequest("GET", "/", nil)
-	c.setExperienceIdHeader(req)
-	assert.Equal(t, req.Header.Get(ExperienceIdHeaderName), "")
+	c.setExperienceIdHeader(context.Background(), req)
+	assert.Equal(t, "", req.Header.Get(ExperienceIdHeaderName))
 
 	// slice is empty
 	c.experienceId = []string{}
 	req, _ = http.NewRequest("GET", "/", nil)
-	c.setExperienceIdHeader(req)
-	assert.Equal(t, req.Header.Get(ExperienceIdHeaderName), "")
+	c.setExperienceIdHeader(context.Background(), req)
+	assert.Equal(t, "", req.Header.Get(ExperienceIdHeaderName))
+
+	var ctx context.Context
+	// context has one element
+	c.experienceId = []string{}
+	ctx = context.Background()
+	ctx = ExperienceIdContext(ctx, "foo")
+	req, _ = http.NewRequest("GET", "/", nil)
+	c.setExperienceIdHeader(ctx, req)
+	assert.Equal(t, "foo", req.Header.Get(ExperienceIdHeaderName))
+
+	// context has two elements
+	c.experienceId = []string{}
+	ctx = context.Background()
+	ctx = ExperienceIdContext(ctx, ids...)
+	req, _ = http.NewRequest("GET", "/", nil)
+	c.setExperienceIdHeader(ctx, req)
+	assert.Equal(t, strings.Join(ids, ","), req.Header.Get(ExperienceIdHeaderName))
+
+	// context has two elements and client has two elements
+	c.experienceId = ids
+	ctx = context.Background()
+	ctx = ExperienceIdContext(ctx, ids...)
+	req, _ = http.NewRequest("GET", "/", nil)
+	c.setExperienceIdHeader(ctx, req)
+	assert.Equal(t, strings.Join(ids, ",") + "," + strings.Join(ids, ","), req.Header.Get(ExperienceIdHeaderName))
 }
 
 func TestClientExperienceIdSample(t *testing.T) {