Skip to content

Commit

Permalink
feat: Add Go SDK custom header support and README (#1288)
Browse files Browse the repository at this point in the history
Following our node sdk's design, allow developers to set custom headers on Go SDK requests. Request specific custom headers will override headers defined in the sdk's settings/config. This will allow the Go SDK caller to set Accept headers and other custom headers. Also add README for Go SDK.

Also fixes issue stated in #1075. (Not the main one, but other one brought up by Go SDK users)
  • Loading branch information
jeremytchang authored Apr 5, 2023
1 parent f719466 commit 308b419
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 66 deletions.
118 changes: 118 additions & 0 deletions go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Go Looker SDK

The Go Looker SDK provides a convenient way to call your Looker instance's [Looker API](https://developers.looker.com/api/overview). The Go Looker SDK supports at least Go version 1.17.6 and 1.16.13 (subject to change). This SDK is community supported with contributions and discussion from the Looker developer community. We strive for functional parity with our original javascript/typescript SDK. Thanks for using our Go Looker SDK!

## Basic Usage

Example code snippet below for basic SDK setup and usage. Also `git clone` this sdk-codegen repo and run the [example code](go/example/main.go).

```go
import (
"fmt"
"github.com/looker-open-source/sdk-codegen/go/rtl"
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
)

func main() {
// Get settings from either looker.ini file OR environment:
// looker.ini file
cfg, err := rtl.NewSettingsFromFile("path/to/looker.ini", nil)
// environment
cfg, err := rtl.NewSettingsFromEnv()

// Create new auth session with sdk settings.
// The auth session will fetch/refresh the access
// token from your Looker instance's `login` endpoint.
session := rtl.NewAuthSession(cfg)

// Create new instance of the Go Looker SDK
sdk := v4.NewLookerSDK(session)

// Call the Looker API e.g. get your user's name
me, err := sdk.Me("", nil)
fmt.Printf("Your name is %s %s\n", *(me.first), *(me.last))
}
```

## Advanced usage

### Custom headers

You can set custom headers on Looker Go SDK's requests. They can either be applied to all outgoing requests or per outgoing request.

#### Custom headers for all requests

Follow the example code snippet below if you want all outgoing requests to have the same custom headers.

```go
func main() {
cfg, err := rtl.NewSettingsFromFile("path/to/looker.ini", nil)

// Set the Headers option in the settings/config
cfg.Headers = map[string]string{
"HeaderName1": "HeaderValue1",
"HeaderName2": "HeaderValue2",
}

session := rtl.NewAuthSession(cfg)
sdk := v4.NewLookerSDK(session)
}
```

#### Custom headers per request

Follow the example code snippet below if you want each outgoing request to have different custom headers. **These headers will overwrite any custom headers set in the SDK's settings as outlined in the previous [All Requests section](#custom-headers-for-all-requests).**

```go
func main() {
cfg, err := rtl.NewSettingsFromFile("path/to/looker.ini", nil)
session := rtl.NewAuthSession(cfg)
sdk := v4.NewLookerSDK(session)

// Set the headers in the options passed into the SDK method
sdk.Me("", &ApiSettings{Headings: map[string]string{
"HeaderName1": "HeaderValue1",
"HeaderName2": "HeaderValue2",
}})
}
```

### Timeout

You can set a custom timeout (in seconds) on Looker Go SDK's requests. The timeout defaults to 120 seconds. A timeout can either be applied to all outgoing requests or per outgoing request.

#### Timeout for all requests

Set `timeout` in your sdk's looker.ini file then call `NewSettingsFromFile()`.
```YAML
[Looker]
timeout=60
```

OR

Set `LOOKERSDK_TIMEOUT` environment variable then call `NewSettingsFromEnv()`.
```bash
LOOKERSDK_TIMEOUT=60
```

#### Timeout per request

Follow the example code snippet below if you want each outgoing request to have a different timeout. **The timeout will overwrite the timeout set in the SDK's settings as outlined in the previous [All Requests section](#timeout-for-all-requests).**

```go
import "context"

func main() {
cfg, err := rtl.NewSettingsFromFile("path/to/looker.ini", nil)
session := rtl.NewAuthSession(cfg)
sdk := v4.NewLookerSDK(session)

// Set the timeout in the options passed into the SDK method
me, err := sdk.Me("", &ApiSettings{Timeout: 60})

if errors.Is(err, context.DeadlineExceeded) {
// Timeout exceeded
}
}
```
13 changes: 12 additions & 1 deletion go/rtl/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func (s *AuthSession) Do(result interface{}, method, ver, path string, reqPars m
}

// set headers
req.Header.Add("Content-Type", contentTypeHeader)
req.Header.Set("Content-Type", contentTypeHeader)

if s.Config.AgentTag != "" {
req.Header.Set("User-Agent", s.Config.AgentTag)
Expand All @@ -151,6 +151,17 @@ func (s *AuthSession) Do(result interface{}, method, ver, path string, reqPars m
req.Header.Set("User-Agent", options.AgentTag)
}

if s.Config.Headers != nil {
for key, value := range s.Config.Headers {
req.Header.Set(key, value)
}
}
if options != nil && options.Headers != nil {
for key, value := range options.Headers {
req.Header.Set(key, value)
}
}

// set query params
setQuery(req.URL, reqPars)

Expand Down
190 changes: 125 additions & 65 deletions go/rtl/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,70 +125,6 @@ func TestAuthSession_Do_Authorization(t *testing.T) {
})
}

func TestAuthSession_Do_UserAgent(t *testing.T) {
const path = "/someMethod"
const apiVersion = "/4.0"

t.Run("Do() sets User-Agent header with AuthSession config's AgentTag", func(t *testing.T) {
mux := http.NewServeMux()
setupApi40Login(mux, foreverValidTestToken, http.StatusOK)
server := httptest.NewServer(mux)
defer server.Close()

mux.HandleFunc("/api"+apiVersion+path, func(w http.ResponseWriter, r *http.Request) {
userAgentHeader := r.Header.Get("User-Agent")
expectedHeader := "some-agent-tag"
if userAgentHeader != expectedHeader {
t.Errorf("User-Agent header not correct. got=%v want=%v", userAgentHeader, expectedHeader)
}
})

session := NewAuthSession(ApiSettings{
BaseUrl: server.URL,
ApiVersion: apiVersion,
AgentTag: "some-agent-tag",
})

var r string
err := session.Do(&r, "GET", apiVersion, path, nil, nil, nil)

if err != nil {
t.Errorf("Do() call failed: %v", err)
}
})

t.Run("Do() sets User-Agent header with Do's option's AgentTag, which will overwrite AuthSession config", func(t *testing.T) {
mux := http.NewServeMux()
setupApi40Login(mux, foreverValidTestToken, http.StatusOK)
server := httptest.NewServer(mux)
defer server.Close()

mux.HandleFunc("/api"+apiVersion+path, func(w http.ResponseWriter, r *http.Request) {
userAgentHeader := r.Header.Get("User-Agent")
expectedHeader := "new-agent-tag"
if userAgentHeader != expectedHeader {
t.Errorf("User-Agent header not correct. got=%v want=%v", userAgentHeader, expectedHeader)
}
})

session := NewAuthSession(ApiSettings{
BaseUrl: server.URL,
ApiVersion: apiVersion,
AgentTag: "some-agent-tag",
})

var r string
options := ApiSettings{
AgentTag: "new-agent-tag",
}
err := session.Do(&r, "GET", apiVersion, path, nil, nil, &options)

if err != nil {
t.Errorf("Do() call failed: %v", err)
}
})
}

func TestAuthSession_Do_Parse(t *testing.T) {
type stringStruct struct {
Field *string `json:"field"`
Expand Down Expand Up @@ -454,10 +390,75 @@ func TestAuthSession_Do_Parse(t *testing.T) {
})
}

func TestAuthSession_Do_Content_Type(t *testing.T) {
func TestAuthSession_Do_Headers(t *testing.T) {
const path = "/someMethod"
const apiVersion = "/4.0"

t.Run("Do() sets custom headers if Headers is set in the AuthSession's api settings", func(t *testing.T) {
mux := http.NewServeMux()
setupApi40Login(mux, foreverValidTestToken, http.StatusOK)
server := httptest.NewServer(mux)
defer server.Close()

mux.HandleFunc("/api"+apiVersion+path, func(w http.ResponseWriter, r *http.Request) {
headerValue1 := r.Header.Get("Key1")
headerValue2 := r.Header.Get("Key2")

expectedHeaderValue1 := "Value1"
expectedHeaderValue2 := "Value2"
if headerValue1 != expectedHeaderValue1 || headerValue2 != expectedHeaderValue2 {
t.Errorf("Custom headers not set correctly. got=%v and %v want=%v and %v", headerValue1, headerValue2, expectedHeaderValue1, expectedHeaderValue2)
}
})

s := NewAuthSession(ApiSettings{
BaseUrl: server.URL,
ApiVersion: apiVersion,
Headers: map[string]string{"Key1":"Value1","Key2":"Value2"},
})

var r string
err := s.Do(&r, "GET", apiVersion, path, nil, nil, nil)

if err != nil {
t.Errorf("Do() call failed: %v", err)
}
})

t.Run("Do()'s options.Headers will overwrite the Headers in the AuthSession's api settings", func(t *testing.T) {
mux := http.NewServeMux()
setupApi40Login(mux, foreverValidTestToken, http.StatusOK)
server := httptest.NewServer(mux)
defer server.Close()

mux.HandleFunc("/api"+apiVersion+path, func(w http.ResponseWriter, r *http.Request) {
headerValue1 := r.Header.Get("Key1")
headerValue2 := r.Header.Get("Key2")

expectedHeaderValue1 := "Value1"
expectedHeaderValue2 := "OverwriteValue2"
if headerValue1 != expectedHeaderValue1 || headerValue2 != expectedHeaderValue2 {
t.Errorf("Custom headers not set correctly. got=%v and %v want=%v and %v", headerValue1, headerValue2, expectedHeaderValue1, expectedHeaderValue2)
}
})

s := NewAuthSession(ApiSettings{
BaseUrl: server.URL,
ApiVersion: apiVersion,
Headers: map[string]string{"Key1":"Value1","Key2":"Value2"},
})

options := ApiSettings{
Headers: map[string]string{"Key1":"Value1","Key2":"OverwriteValue2"},
}
var r string
err := s.Do(&r, "GET", apiVersion, path, nil, nil, &options)

if err != nil {
t.Errorf("Do() call failed: %v", err)
}
})

t.Run("Do() sets Content-Type header to 'application/json' if body is json", func(t *testing.T) {
mux := http.NewServeMux()
setupApi40Login(mux, foreverValidTestToken, http.StatusOK)
Expand Down Expand Up @@ -517,6 +518,65 @@ func TestAuthSession_Do_Content_Type(t *testing.T) {
t.Errorf("Do() call failed: %v", err)
}
})

t.Run("Do() sets User-Agent header with AuthSession config's AgentTag", func(t *testing.T) {
mux := http.NewServeMux()
setupApi40Login(mux, foreverValidTestToken, http.StatusOK)
server := httptest.NewServer(mux)
defer server.Close()

mux.HandleFunc("/api"+apiVersion+path, func(w http.ResponseWriter, r *http.Request) {
userAgentHeader := r.Header.Get("User-Agent")
expectedHeader := "some-agent-tag"
if userAgentHeader != expectedHeader {
t.Errorf("User-Agent header not correct. got=%v want=%v", userAgentHeader, expectedHeader)
}
})

session := NewAuthSession(ApiSettings{
BaseUrl: server.URL,
ApiVersion: apiVersion,
AgentTag: "some-agent-tag",
})

var r string
err := session.Do(&r, "GET", apiVersion, path, nil, nil, nil)

if err != nil {
t.Errorf("Do() call failed: %v", err)
}
})

t.Run("Do() sets User-Agent header with Do's option's AgentTag, which will overwrite AuthSession config", func(t *testing.T) {
mux := http.NewServeMux()
setupApi40Login(mux, foreverValidTestToken, http.StatusOK)
server := httptest.NewServer(mux)
defer server.Close()

mux.HandleFunc("/api"+apiVersion+path, func(w http.ResponseWriter, r *http.Request) {
userAgentHeader := r.Header.Get("User-Agent")
expectedHeader := "new-agent-tag"
if userAgentHeader != expectedHeader {
t.Errorf("User-Agent header not correct. got=%v want=%v", userAgentHeader, expectedHeader)
}
})

session := NewAuthSession(ApiSettings{
BaseUrl: server.URL,
ApiVersion: apiVersion,
AgentTag: "some-agent-tag",
})

var r string
options := ApiSettings{
AgentTag: "new-agent-tag",
}
err := session.Do(&r, "GET", apiVersion, path, nil, nil, &options)

if err != nil {
t.Errorf("Do() call failed: %v", err)
}
})
}

func TestAuthSession_Do_Timeout(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions go/rtl/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type ApiSettings struct {
ClientId string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
ApiVersion string `ini:"api_version"`
Headers map[string]string
}

var defaultSettings ApiSettings = ApiSettings{
Expand Down

0 comments on commit 308b419

Please sign in to comment.