From e9e71cbbff70f167c06c1e1d9c995158dd799e84 Mon Sep 17 00:00:00 2001 From: Phil Adams Date: Tue, 13 Oct 2020 14:16:20 -0500 Subject: [PATCH] feat: support use of Context with RequestBuilder Fixes arf/planning-sdk-squad#2230 Fixes https://github.com/IBM/go-sdk-core/issues/77 This commit adds support for setting a context.Context instance on the RequestBuilder struct. This Context instance will then be associated with the http.Request instance constructed by the RequestBuilder.Build() method. BREAKING CHANGE: Minimum supported Go version is now 1.13. The minimum version of Go supported by this library has been changed to Go version 1.13. To use this and future versions of the Go core library, please make sure that you are using Go version 1.13 or higher. --- .bumpversion.cfg | 8 +- .travis.yml | 8 +- CONTRIBUTING.md | 8 +- Makefile | 2 +- README.md | 12 +-- {v4 => v5}/core/authenticator.go | 0 {v4 => v5}/core/authenticator_factory.go | 0 {v4 => v5}/core/authenticator_factory_test.go | 0 {v4 => v5}/core/base_service.go | 0 {v4 => v5}/core/base_service_test.go | 0 {v4 => v5}/core/basic_authenticator.go | 0 {v4 => v5}/core/basic_authenticator_test.go | 0 {v4 => v5}/core/bearer_token_authenticator.go | 0 .../core/bearer_token_authenticator_test.go | 0 {v4 => v5}/core/config_utils.go | 0 {v4 => v5}/core/config_utils_test.go | 0 {v4 => v5}/core/constants.go | 0 {v4 => v5}/core/cp4d_authenticator.go | 0 {v4 => v5}/core/cp4d_authenticator_test.go | 0 {v4 => v5}/core/detailed_response.go | 0 {v4 => v5}/core/detailed_response_test.go | 0 {v4 => v5}/core/doc.go | 0 {v4 => v5}/core/gzip.go | 0 {v4 => v5}/core/gzip_test.go | 0 {v4 => v5}/core/iam_authenticator.go | 0 {v4 => v5}/core/iam_authenticator_test.go | 0 {v4 => v5}/core/marshal_nulls_test.go | 0 {v4 => v5}/core/noauth_authenticator.go | 0 {v4 => v5}/core/noauth_authenticator_test.go | 0 {v4 => v5}/core/request_builder.go | 45 +++++++---- {v4 => v5}/core/request_builder_test.go | 80 +++++++++++++++++++ {v4 => v5}/core/unmarshal.go | 0 {v4 => v5}/core/unmarshal_test.go | 0 {v4 => v5}/core/unmarshal_v2.go | 0 {v4 => v5}/core/unmarshal_v2_models_test.go | 0 .../core/unmarshal_v2_primitives_test.go | 0 {v4 => v5}/core/utils.go | 0 {v4 => v5}/core/utils_test.go | 0 {v4 => v5}/core/version.go | 0 {v4 => v5}/go.mod | 4 +- {v4 => v5}/go.sum | 0 {v4 => v5}/resources/ibm-credentials.env | 0 {v4 => v5}/resources/my-credentials.env | 0 {v4 => v5}/resources/test_file.txt | 0 {v4 => v5}/resources/vcap_services.json | 0 45 files changed, 123 insertions(+), 44 deletions(-) rename {v4 => v5}/core/authenticator.go (100%) rename {v4 => v5}/core/authenticator_factory.go (100%) rename {v4 => v5}/core/authenticator_factory_test.go (100%) rename {v4 => v5}/core/base_service.go (100%) rename {v4 => v5}/core/base_service_test.go (100%) rename {v4 => v5}/core/basic_authenticator.go (100%) rename {v4 => v5}/core/basic_authenticator_test.go (100%) rename {v4 => v5}/core/bearer_token_authenticator.go (100%) rename {v4 => v5}/core/bearer_token_authenticator_test.go (100%) rename {v4 => v5}/core/config_utils.go (100%) rename {v4 => v5}/core/config_utils_test.go (100%) rename {v4 => v5}/core/constants.go (100%) rename {v4 => v5}/core/cp4d_authenticator.go (100%) rename {v4 => v5}/core/cp4d_authenticator_test.go (100%) rename {v4 => v5}/core/detailed_response.go (100%) rename {v4 => v5}/core/detailed_response_test.go (100%) rename {v4 => v5}/core/doc.go (100%) rename {v4 => v5}/core/gzip.go (100%) rename {v4 => v5}/core/gzip_test.go (100%) rename {v4 => v5}/core/iam_authenticator.go (100%) rename {v4 => v5}/core/iam_authenticator_test.go (100%) rename {v4 => v5}/core/marshal_nulls_test.go (100%) rename {v4 => v5}/core/noauth_authenticator.go (100%) rename {v4 => v5}/core/noauth_authenticator_test.go (100%) rename {v4 => v5}/core/request_builder.go (91%) rename {v4 => v5}/core/request_builder_test.go (89%) rename {v4 => v5}/core/unmarshal.go (100%) rename {v4 => v5}/core/unmarshal_test.go (100%) rename {v4 => v5}/core/unmarshal_v2.go (100%) rename {v4 => v5}/core/unmarshal_v2_models_test.go (100%) rename {v4 => v5}/core/unmarshal_v2_primitives_test.go (100%) rename {v4 => v5}/core/utils.go (100%) rename {v4 => v5}/core/utils_test.go (100%) rename {v4 => v5}/core/version.go (100%) rename {v4 => v5}/go.mod (88%) rename {v4 => v5}/go.sum (100%) rename {v4 => v5}/resources/ibm-credentials.env (100%) rename {v4 => v5}/resources/my-credentials.env (100%) rename {v4 => v5}/resources/test_file.txt (100%) rename {v4 => v5}/resources/vcap_services.json (100%) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 1b8f1df..ca4b737 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,16 +3,10 @@ current_version = 4.6.1 commit = True message = Update version numbers from {current_version} -> {new_version} [skip ci] -[bumpversion:file:v4/core/version.go] +[bumpversion:file:v5/core/version.go] search = __VERSION__ = "{current_version}" replace = __VERSION__ = "{new_version}" -[bumpversion:file:v4/go.mod] -parse = (?P\d+) -serialize = {major} -search = module github.com/IBM/go-sdk-core/v{current_version} -replace = module github.com/IBM/go-sdk-core/v{new_version} - [bumpversion:file:Makefile] parse = (?P\d+) serialize = {major} diff --git a/.travis.yml b/.travis.yml index 58e275f..9826835 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,15 +3,11 @@ language: go dist: xenial go: -- 1.12.x +- 1.13.x notifications: email: false -env: - global: - - GO111MODULE=on - before_install: - nvm install 12 - npm install -g npm@6.x @@ -19,7 +15,7 @@ before_install: - sudo apt-get install python install: - - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.21.0 + - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.31.0 script: - make all diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 613a70d..b6324fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,9 +30,9 @@ If you want to contribute to the repository, here's a quick guide: 4. Install the `golangci-lint` tool: ```sh - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.21.0 + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.31.0 ``` - * Note: As of this writing, the 1.21.0 version of `golangci-lint` is being used by this project. + * Note: As of this writing, the 1.31.0 version of `golangci-lint` is being used by this project. Please check the `curl` command found in the `.travis.yml` file to see the version of this tool that is currently being used at the time you are planning to commit changes. This will ensure that you are using the same version of the linter as the Travis build automation, which will ensure that you are using the same set of linter checks @@ -42,12 +42,12 @@ If you want to contribute to the repository, here's a quick guide: 6. Test your changes: ```sh - go test ./... + make test ``` 7. Check your code for lint issues ```sh - golangci-lint run + make lint ``` 8. Commit your changes: diff --git a/Makefile b/Makefile index 21da3d6..e2ade3d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile to build go-sdk-core library -VDIR=v4 +VDIR=v5 all: build test lint tidy diff --git a/README.md b/README.md index dfdf2e8..a5d6847 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ go get -u github.com/IBM/go-sdk-core/... ``` ## Prerequisites -- Go version 1.12 or newer +- Go version 1.13 or newer ## Authentication The go-sdk-core project supports the following types of authentication: @@ -32,15 +32,9 @@ Before opening a new issue, please search for similar issues. It's possible that ## Tests -Run all test suites: +To build, test and lint-check the project: ```bash -go test ./... -``` - -Get code coverage for each test suite: -```bash -go test -coverprofile=coverage.out ./... -go tool cover -html=coverage.out +make all ``` ## Contributing diff --git a/v4/core/authenticator.go b/v5/core/authenticator.go similarity index 100% rename from v4/core/authenticator.go rename to v5/core/authenticator.go diff --git a/v4/core/authenticator_factory.go b/v5/core/authenticator_factory.go similarity index 100% rename from v4/core/authenticator_factory.go rename to v5/core/authenticator_factory.go diff --git a/v4/core/authenticator_factory_test.go b/v5/core/authenticator_factory_test.go similarity index 100% rename from v4/core/authenticator_factory_test.go rename to v5/core/authenticator_factory_test.go diff --git a/v4/core/base_service.go b/v5/core/base_service.go similarity index 100% rename from v4/core/base_service.go rename to v5/core/base_service.go diff --git a/v4/core/base_service_test.go b/v5/core/base_service_test.go similarity index 100% rename from v4/core/base_service_test.go rename to v5/core/base_service_test.go diff --git a/v4/core/basic_authenticator.go b/v5/core/basic_authenticator.go similarity index 100% rename from v4/core/basic_authenticator.go rename to v5/core/basic_authenticator.go diff --git a/v4/core/basic_authenticator_test.go b/v5/core/basic_authenticator_test.go similarity index 100% rename from v4/core/basic_authenticator_test.go rename to v5/core/basic_authenticator_test.go diff --git a/v4/core/bearer_token_authenticator.go b/v5/core/bearer_token_authenticator.go similarity index 100% rename from v4/core/bearer_token_authenticator.go rename to v5/core/bearer_token_authenticator.go diff --git a/v4/core/bearer_token_authenticator_test.go b/v5/core/bearer_token_authenticator_test.go similarity index 100% rename from v4/core/bearer_token_authenticator_test.go rename to v5/core/bearer_token_authenticator_test.go diff --git a/v4/core/config_utils.go b/v5/core/config_utils.go similarity index 100% rename from v4/core/config_utils.go rename to v5/core/config_utils.go diff --git a/v4/core/config_utils_test.go b/v5/core/config_utils_test.go similarity index 100% rename from v4/core/config_utils_test.go rename to v5/core/config_utils_test.go diff --git a/v4/core/constants.go b/v5/core/constants.go similarity index 100% rename from v4/core/constants.go rename to v5/core/constants.go diff --git a/v4/core/cp4d_authenticator.go b/v5/core/cp4d_authenticator.go similarity index 100% rename from v4/core/cp4d_authenticator.go rename to v5/core/cp4d_authenticator.go diff --git a/v4/core/cp4d_authenticator_test.go b/v5/core/cp4d_authenticator_test.go similarity index 100% rename from v4/core/cp4d_authenticator_test.go rename to v5/core/cp4d_authenticator_test.go diff --git a/v4/core/detailed_response.go b/v5/core/detailed_response.go similarity index 100% rename from v4/core/detailed_response.go rename to v5/core/detailed_response.go diff --git a/v4/core/detailed_response_test.go b/v5/core/detailed_response_test.go similarity index 100% rename from v4/core/detailed_response_test.go rename to v5/core/detailed_response_test.go diff --git a/v4/core/doc.go b/v5/core/doc.go similarity index 100% rename from v4/core/doc.go rename to v5/core/doc.go diff --git a/v4/core/gzip.go b/v5/core/gzip.go similarity index 100% rename from v4/core/gzip.go rename to v5/core/gzip.go diff --git a/v4/core/gzip_test.go b/v5/core/gzip_test.go similarity index 100% rename from v4/core/gzip_test.go rename to v5/core/gzip_test.go diff --git a/v4/core/iam_authenticator.go b/v5/core/iam_authenticator.go similarity index 100% rename from v4/core/iam_authenticator.go rename to v5/core/iam_authenticator.go diff --git a/v4/core/iam_authenticator_test.go b/v5/core/iam_authenticator_test.go similarity index 100% rename from v4/core/iam_authenticator_test.go rename to v5/core/iam_authenticator_test.go diff --git a/v4/core/marshal_nulls_test.go b/v5/core/marshal_nulls_test.go similarity index 100% rename from v4/core/marshal_nulls_test.go rename to v5/core/marshal_nulls_test.go diff --git a/v4/core/noauth_authenticator.go b/v5/core/noauth_authenticator.go similarity index 100% rename from v4/core/noauth_authenticator.go rename to v5/core/noauth_authenticator.go diff --git a/v4/core/noauth_authenticator_test.go b/v5/core/noauth_authenticator_test.go similarity index 100% rename from v4/core/noauth_authenticator_test.go rename to v5/core/noauth_authenticator_test.go diff --git a/v4/core/request_builder.go b/v5/core/request_builder.go similarity index 91% rename from v4/core/request_builder.go rename to v5/core/request_builder.go index ee19777..dbed7ac 100644 --- a/v4/core/request_builder.go +++ b/v5/core/request_builder.go @@ -16,6 +16,7 @@ package core import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -76,6 +77,13 @@ type RequestBuilder struct { // the "Content-Encoding" header will be added to the request with the // value "gzip". EnableGzipCompression bool + + // RequestContext can be used by users of RequestBuilder to provide an + // optional context.Context instance to be associated with the http.Request + // instance that will be built by the Build() method. + // If specified, then http.NewRequestWithContext() is used to create the + // http.Request instance. If nil, then http.NewRequest() is used instead. + RequestContext *context.Context } // NewRequestBuilder initiates a new request. @@ -275,7 +283,7 @@ func (requestBuilder *RequestBuilder) SetBodyContentForMultipart(contentType str } // Build builds an HTTP Request object from this RequestBuilder instance. -func (requestBuilder *RequestBuilder) Build() (*http.Request, error) { +func (requestBuilder *RequestBuilder) Build() (req *http.Request, err error) { // Create multipart form data if len(requestBuilder.Form) > 0 { // handle both application/x-www-form-urlencoded or multipart/form-data @@ -287,29 +295,31 @@ func (requestBuilder *RequestBuilder) Build() (*http.Request, error) { data.Add(fieldName, v.contents.(string)) } } - _, err := requestBuilder.SetBodyContentString(data.Encode()) + _, err = requestBuilder.SetBodyContentString(data.Encode()) if err != nil { - return nil, err + return } } else { formWriter := requestBuilder.createMultipartWriter() for fieldName, l := range requestBuilder.Form { for _, v := range l { - dataPartWriter, err := createFormFile(formWriter, fieldName, v.fileName, v.contentType) + var dataPartWriter io.Writer + dataPartWriter, err = createFormFile(formWriter, fieldName, v.fileName, v.contentType) if err != nil { - return nil, err + return } - if err = requestBuilder.SetBodyContentForMultipart(v.contentType, - v.contents, dataPartWriter); err != nil { - return nil, err + + err = requestBuilder.SetBodyContentForMultipart(v.contentType, v.contents, dataPartWriter) + if err != nil { + return } } } requestBuilder.AddHeader("Content-Type", formWriter.FormDataContentType()) - err := formWriter.Close() + err = formWriter.Close() if err != nil { - return nil, err + return } } } @@ -318,18 +328,23 @@ func (requestBuilder *RequestBuilder) Build() (*http.Request, error) { // and add the "Content-Encoding: gzip" request header. if !IsNil(requestBuilder.Body) && requestBuilder.EnableGzipCompression && !SliceContains(requestBuilder.Header[CONTENT_ENCODING], "gzip") { - newBody, err := NewGzipCompressionReader(requestBuilder.Body) + var newBody io.Reader + newBody, err = NewGzipCompressionReader(requestBuilder.Body) if err != nil { - return nil, err + return } requestBuilder.Body = newBody requestBuilder.Header.Add(CONTENT_ENCODING, "gzip") } // Create the request - req, err := http.NewRequest(requestBuilder.Method, requestBuilder.URL.String(), requestBuilder.Body) + if requestBuilder.RequestContext != nil { + req, err = http.NewRequestWithContext(*requestBuilder.RequestContext, requestBuilder.Method, requestBuilder.URL.String(), requestBuilder.Body) + } else { + req, err = http.NewRequest(requestBuilder.Method, requestBuilder.URL.String(), requestBuilder.Body) + } if err != nil { - return nil, err + return } // Headers @@ -345,7 +360,7 @@ func (requestBuilder *RequestBuilder) Build() (*http.Request, error) { // Encode query req.URL.RawQuery = query.Encode() - return req, nil + return } // SetBodyContent sets the body content from one of three different sources. diff --git a/v4/core/request_builder_test.go b/v5/core/request_builder_test.go similarity index 89% rename from v4/core/request_builder_test.go rename to v5/core/request_builder_test.go index 1cf65c0..7298b24 100644 --- a/v4/core/request_builder_test.go +++ b/v5/core/request_builder_test.go @@ -16,9 +16,14 @@ package core import ( "bytes" + "context" + "fmt" "io" + "net/http" + "net/http/httptest" "os" "testing" + "time" assert "github.com/stretchr/testify/assert" ) @@ -593,3 +598,78 @@ func TestBuild(t *testing.T) { assert.Equal(t, wantURL, request.URL.String()) assert.Equal(t, "Application/json", request.Header["Content-Type"][0]) } + +func TestRequestWithContext(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"name": "wonder woman"}`) + })) + defer server.Close() + + ctx, cancelFunc := context.WithTimeout(context.Background(), 3*time.Second) + defer cancelFunc() + + builder := NewRequestBuilder("GET") + builder.RequestContext = &ctx + _, err := builder.ConstructHTTPURL(server.URL, nil, nil) + assert.Nil(t, err) + req, _ := builder.Build() + + authenticator, _ := NewNoAuthAuthenticator() + + options := &ServiceOptions{ + URL: server.URL, + Authenticator: authenticator, + } + service, err := NewBaseService(options) + assert.Nil(t, err) + + var foo *Foo + detailedResponse, err := service.Request(req, &foo) + assert.Nil(t, err) + assert.NotNil(t, detailedResponse) + assert.Equal(t, http.StatusOK, detailedResponse.StatusCode) + assert.Equal(t, "application/json", detailedResponse.Headers.Get("Content-Type")) + + result, ok := detailedResponse.Result.(*Foo) + assert.Equal(t, true, ok) + assert.NotNil(t, result) + assert.NotNil(t, foo) + assert.Equal(t, "wonder woman", *(result.Name)) +} + +func TestRequestWithContextTimeout(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json") + w.WriteHeader(http.StatusOK) + time.Sleep(3 * time.Second) + fmt.Fprint(w, `{"name": "wonder woman"}`) + })) + defer server.Close() + + ctx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelFunc() + + builder := NewRequestBuilder("GET") + builder.RequestContext = &ctx + _, err := builder.ConstructHTTPURL(server.URL, nil, nil) + assert.Nil(t, err) + req, _ := builder.Build() + + authenticator, _ := NewNoAuthAuthenticator() + + options := &ServiceOptions{ + URL: server.URL, + Authenticator: authenticator, + } + service, err := NewBaseService(options) + assert.Nil(t, err) + + var foo *Foo + detailedResponse, err := service.Request(req, &foo) + assert.NotNil(t, err) + t.Logf("Expected error: %s\n", err.Error()) + assert.Contains(t, err.Error(), "context deadline exceeded") + assert.Nil(t, detailedResponse) +} diff --git a/v4/core/unmarshal.go b/v5/core/unmarshal.go similarity index 100% rename from v4/core/unmarshal.go rename to v5/core/unmarshal.go diff --git a/v4/core/unmarshal_test.go b/v5/core/unmarshal_test.go similarity index 100% rename from v4/core/unmarshal_test.go rename to v5/core/unmarshal_test.go diff --git a/v4/core/unmarshal_v2.go b/v5/core/unmarshal_v2.go similarity index 100% rename from v4/core/unmarshal_v2.go rename to v5/core/unmarshal_v2.go diff --git a/v4/core/unmarshal_v2_models_test.go b/v5/core/unmarshal_v2_models_test.go similarity index 100% rename from v4/core/unmarshal_v2_models_test.go rename to v5/core/unmarshal_v2_models_test.go diff --git a/v4/core/unmarshal_v2_primitives_test.go b/v5/core/unmarshal_v2_primitives_test.go similarity index 100% rename from v4/core/unmarshal_v2_primitives_test.go rename to v5/core/unmarshal_v2_primitives_test.go diff --git a/v4/core/utils.go b/v5/core/utils.go similarity index 100% rename from v4/core/utils.go rename to v5/core/utils.go diff --git a/v4/core/utils_test.go b/v5/core/utils_test.go similarity index 100% rename from v4/core/utils_test.go rename to v5/core/utils_test.go diff --git a/v4/core/version.go b/v5/core/version.go similarity index 100% rename from v4/core/version.go rename to v5/core/version.go diff --git a/v4/go.mod b/v5/go.mod similarity index 88% rename from v4/go.mod rename to v5/go.mod index 5ead507..e666dd6 100644 --- a/v4/go.mod +++ b/v5/go.mod @@ -1,6 +1,6 @@ -module github.com/IBM/go-sdk-core/v4 +module github.com/IBM/go-sdk-core/v5 -go 1.12 +go 1.13 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible diff --git a/v4/go.sum b/v5/go.sum similarity index 100% rename from v4/go.sum rename to v5/go.sum diff --git a/v4/resources/ibm-credentials.env b/v5/resources/ibm-credentials.env similarity index 100% rename from v4/resources/ibm-credentials.env rename to v5/resources/ibm-credentials.env diff --git a/v4/resources/my-credentials.env b/v5/resources/my-credentials.env similarity index 100% rename from v4/resources/my-credentials.env rename to v5/resources/my-credentials.env diff --git a/v4/resources/test_file.txt b/v5/resources/test_file.txt similarity index 100% rename from v4/resources/test_file.txt rename to v5/resources/test_file.txt diff --git a/v4/resources/vcap_services.json b/v5/resources/vcap_services.json similarity index 100% rename from v4/resources/vcap_services.json rename to v5/resources/vcap_services.json