Repository: algolia/algoliasearch-client-go
Pull request #738 (2023-07-04 14:55:44)

fix(transport): allow to override existing header with different case
| Q                 | A
| ----------------- | ----------
| Bug fix?          | yes
| New feature?      | no
| BC breaks?        | no     
| Need Doc update   | no


## Describe your change

Allow to override existing header with different case instead of appending values

## What problem is this fixing?

The current behaviour append header values but the search engine doesn't support multivalued headers. It leads to undefined behaviour based on the header ordering.



Patch:
diff --git a/algolia/transport/transport.go b/algolia/transport/transport.go
index 6992f962..ec2587cf 100644
--- a/algolia/transport/transport.go
+++ b/algolia/transport/transport.go
@@ -26,7 +26,7 @@ const version = "3.30.0"
 type Transport struct {
 	requester     Requester
 	retryStrategy *RetryStrategy
-	headers       map[string]string
+	headers       http.Header
 	compression   compression.Compression
 }
 
@@ -54,16 +54,19 @@ func New(
 		userAgents = append([]string{extraUserAgent}, userAgents...)
 	}
 
-	headers := map[string]string{
+	headers := http.Header{}
+	for k, v := range map[string]string{
 		"Connection":               "Keep-Alive",
 		"Content-Type":             "application/json; charset=utf-8",
 		"User-Agent":               strings.Join(userAgents, ";"),
 		"X-Algolia-Application-Id": appID,
 		"X-Algolia-API-Key":        apiKey,
+	} {
+		headers.Set(k, v)
 	}
 
 	for k, v := range defaultHeaders {
-		headers[k] = v
+		headers.Set(k, v)
 	}
 
 	return &Transport{
@@ -191,14 +194,11 @@ func (t *Transport) request(req *http.Request) (io.ReadCloser, int, error) {
 	return res.Body, res.StatusCode, nil
 }
 
-func mergeHeaders(defaultHeaders, extraHeaders map[string]string) map[string]string {
-	headers := make(map[string]string)
+func mergeHeaders(defaultHeaders http.Header, extraHeaders map[string]string) http.Header {
+	headers := defaultHeaders.Clone()
 
-	for key, value := range defaultHeaders {
-		headers[key] = value
-	}
 	for key, value := range extraHeaders {
-		headers[key] = value
+		headers.Set(key, value)
 	}
 
 	return headers
@@ -263,7 +263,7 @@ func buildRequest(
 	host string,
 	path string,
 	body interface{},
-	headers map[string]string,
+	headers http.Header,
 	urlParams map[string]string,
 ) (req *http.Request, err error) {
 	urlStr := "https://" + host + path
@@ -280,9 +280,7 @@ func buildRequest(
 	}
 
 	// Add headers
-	for k, v := range headers {
-		req.Header.Add(k, v)
-	}
+	req.Header = headers
 
 	// Add Content-Encoding header, if needed
 	if isCompressionEnabled {


Test patch:
diff --git a/algolia/transport/transport_test.go b/algolia/transport/transport_test.go
index 01924fc3..a519c581 100644
--- a/algolia/transport/transport_test.go
+++ b/algolia/transport/transport_test.go
@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"runtime"
+	"strings"
 	"testing"
 	"time"
 
@@ -153,3 +155,42 @@ func TestOnNetworkErrorWithNilBody(t *testing.T) {
 	require.Len(t, noMoreHostToTryErr.IntermediateNetworkErrors(), 1)
 	require.Equal(t, noMoreHostToTryErr.IntermediateNetworkErrors()[0].Error(), "cannot perform request:\n\terror=oh no\n\tmethod=GET\n\turl=https:")
 }
+
+type MockRequester struct {
+	validator func(req *http.Request)
+}
+
+func (m *MockRequester) Request(req *http.Request) (*http.Response, error) {
+	m.validator(req)
+	return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader("\"\""))}, nil
+}
+
+func TestShouldSupportCaseSensitiveHeader(t *testing.T) {
+	hosts := []*StatefulHost{NewStatefulHost("", call.IsReadWrite)}
+	requester := &MockRequester{validator: func(req *http.Request) {
+		require.Equal(t, "/1/test", req.URL.Path)
+		require.Equal(t, http.Header{
+			"Connection":               []string{"Keep-Alive"},
+			"Content-Type":             []string{"application/json; charset=utf-8"},
+			"User-Agent":               []string{fmt.Sprintf("Algolia for Go (%s);Go (%s)", version, runtime.Version())},
+			"X-Algolia-Api-Key":        []string{"newKey"},
+			"X-Algolia-Application-Id": []string{"appID"},
+		}, req.Header)
+	}}
+
+	transporter := New(
+		hosts,
+		requester,
+		"appID",
+		"wrong key",
+		time.Second,
+		time.Second,
+		map[string]string{"x-algolia-api-keY": "still wrong key"},
+		"",
+		compression.None,
+	)
+	opts := []interface{}{opt.ExtraHeaders(map[string]string{"x-algolia-api-key": "newKey"})}
+	var res string
+	err := transporter.Request(&res, http.MethodGet, "/1/test", nil, call.Read, opts...)
+	require.NoError(t, err)
+}