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!: add Go modules and Go 1.19 compatibility #114

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
name: Validate title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v4
- uses: amannn/action-semantic-pull-request@v5
with:
types: chore docs fix feat test
env:
Expand Down
31 changes: 28 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:
go: [ '1.14', '1.15', '1.16', '1.17' ]
steps:
- name: Checkout rest
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Setup Go environment
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go }}

Expand All @@ -36,14 +36,39 @@ jobs:
- name: Run Tests
run: make test

test-v3:

Choose a reason for hiding this comment

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

Why are we running this in a separate job? Consider including the respective Go versions in the original test job. Keep in mind while performing these changes that Go 1.20 is out already.

Choose a reason for hiding this comment

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

Ditto to the reason I explained in go commands comment.

name: Build & Test
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
go: [ '1.18', '1.19', '1.20' ]
steps:
- name: Checkout rest
uses: actions/checkout@v3

- name: Setup Go environment
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go }}

- name: Set Go env vars
run: |
echo "GOPATH=$HOME" >> $GITHUB_ENV
echo "GOBIN=$HOME/bin" >> $GITHUB_ENV
echo "GO111MODULE=off" >> $GITHUB_ENV

- name: Run Tests
run: cd v3 && make test

deploy:
name: Deploy
if: success() && github.ref_type == 'tag'
needs: [ test ]
runs-on: ubuntu-latest
steps:
- name: Checkout rest
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Create GitHub Release
uses: sendgrid/dx-automator/actions/release@main
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ _testmain.go
.settings.json

temp.go
.vscode
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ We welcome direct contributions to the rest code base. Thank you!

##### Supported Versions #####

- Go version 1.14, 1.15 or 1.16
- Go version 1.14, 1.15, 1.16, 1.17, 1.18, 1.19, or 1.20

##### Initial setup: #####

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: test install

install:
go get -t -v ./...
go get -t -v

test: install
go test -race -cover -v ./...

Choose a reason for hiding this comment

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

Why are you removing the ./...?

Choose a reason for hiding this comment

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

I understand now, this change was intentional due to the subfolder, if we remove the subfolder altogether, we can revert this change.

go test -race -cover -v
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,24 @@ This library supports the following Go implementations:
* Go 1.15
* Go 1.16
* Go 1.17
* Go 1.18
* Go 1.19
* Go 1.20

## Install Package

If you are using Go 1.14 through 1.17:

```bash
go get github.com/sendgrid/rest
```

If you are using Go 1.18 or above:

```bash
go get github.com/sendgrid/rest/v3
```

## Setup Environment Variables

### Initial Setup
Expand Down Expand Up @@ -107,7 +118,7 @@ Your go files will be executed relative to the root of this directory. So in the
```go
package main

import "github.com/sendgrid/rest"
import "github.com/sendgrid/rest" // Or github.com/sendgrid/rest/v3 if using Go 1.18 or above
import "fmt"

func main() {
Expand Down Expand Up @@ -136,7 +147,7 @@ func main() {
```go
package main

import "github.com/sendgrid/rest"
import "github.com/sendgrid/rest" // Or github.com/sendgrid/rest/v3 if using Go 1.18 or above
import "fmt"

func main() {
Expand Down
41 changes: 22 additions & 19 deletions docker/example.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
package main

import "github.com/sendgrid/rest"
import "fmt"
import (
"fmt"

"github.com/sendgrid/rest"
)

func main() {
const host = "https://httpbin.org"
param := "get"
endpoint := "/" + param
baseURL := host + endpoint
method := rest.Get
request := rest.Request{
Method: method,
BaseURL: baseURL,
}
response, err := rest.Send(request)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(response.StatusCode)
fmt.Println(response.Body)
fmt.Println(response.Headers)
}
const host = "https://httpbin.org"
param := "get"
endpoint := "/" + param
baseURL := host + endpoint
method := rest.Get
request := rest.Request{
Method: method,
BaseURL: baseURL,
}
response, err := rest.Send(request)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(response.StatusCode)
fmt.Println(response.Body)
fmt.Println(response.Headers)
}
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/sendgrid/rest

go 1.14

require golang.org/x/net v0.0.0-20220708220712-1185a9018129
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
7 changes: 7 additions & 0 deletions v3/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: test install

install:
go get -t -v

test: install
go test -race -cover -v
5 changes: 5 additions & 0 deletions v3/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/sendgrid/rest/v3

Choose a reason for hiding this comment

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

I don't think it's a good idea to have a different folders. We can simply replace the version in the original go.mod file instead. Please revert these changes in the v3 folder.

Choose a reason for hiding this comment

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

Even though the https://go.dev/blog/v2-go-modules recommends having different folders, that's for projects that are actually introducing breaking changes, and this is not the case in this PR.

Copy link
Author

@zwartho zwartho Aug 7, 2023

Choose a reason for hiding this comment

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

@marcoshuck Thanks for your review. If you take a look at these failed jobs, breaking changes are seemingly apparent when referencing the io module. This SO question explains it nicely, but essentially the only Go changes made in this MR were replacing references to the deprecated ioutil module. I could not get tests for certain versions of Go passing without utilizing the v3 directory and splitting up the test job into two separate jobs. So in order to prevent breaking functionality for users of Go 1.14, 1.15, etc., I believe the need for the v3 directory and separate test jobs is necessary. If you have any suggestions to mitigate this issue in a different way, please let me know and I can accommodate.

Copy link

@marcoshuck marcoshuck Aug 9, 2023

Choose a reason for hiding this comment

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

That's a very a good point. We need to have this discussion with some of the members from the @sendgrid team, my 2c: We should replace ioutil with io package references or any other alternative that makes it work. Bumping to a new major version should allow us to deprecate previous Go versions.

Copy link
Author

Choose a reason for hiding this comment

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

@twilio-dx @SendGridDX Any suggested next steps?


go 1.18

require golang.org/x/net v0.13.0
2 changes: 2 additions & 0 deletions v3/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
161 changes: 161 additions & 0 deletions v3/rest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Package rest allows for quick and easy access any REST or REST-like API.
package rest

import (
"bytes"
"context"
"io"
"net/http"
"net/url"
)

// Version represents the current version of the rest library
const Version = "3.0.0"

// Method contains the supported HTTP verbs.
type Method string

// Supported HTTP verbs.
const (
Get Method = "GET"
Post Method = "POST"
Put Method = "PUT"
Patch Method = "PATCH"
Delete Method = "DELETE"
)

// Request holds the request to an API Call.
type Request struct {
Method Method
BaseURL string // e.g. https://api.sendgrid.com
Headers map[string]string
QueryParams map[string]string
Body []byte
}

// RestError is a struct for an error handling.
type RestError struct {
Response *Response
}

// Error is the implementation of the error interface.
func (e *RestError) Error() string {
return e.Response.Body
}

// DefaultClient is used if no custom HTTP client is defined
var DefaultClient = &Client{HTTPClient: &http.Client{}}

// Client allows modification of client headers, redirect policy
// and other settings
// See https://golang.org/pkg/net/http
type Client struct {
HTTPClient *http.Client
}

// Response holds the response from an API call.
type Response struct {
StatusCode int // e.g. 200
Body string // e.g. {"result: success"}
Headers map[string][]string // e.g. map[X-Ratelimit-Limit:[600]]
}

// AddQueryParameters adds query parameters to the URL.
func AddQueryParameters(baseURL string, queryParams map[string]string) string {
baseURL += "?"
params := url.Values{}
for key, value := range queryParams {
params.Add(key, value)
}
return baseURL + params.Encode()
}

// BuildRequestObject creates the HTTP request object.
func BuildRequestObject(request Request) (*http.Request, error) {
// Add any query parameters to the URL.
if len(request.QueryParams) != 0 {
request.BaseURL = AddQueryParameters(request.BaseURL, request.QueryParams)
}
req, err := http.NewRequest(string(request.Method), request.BaseURL, bytes.NewBuffer(request.Body))
if err != nil {
return req, err
}
for key, value := range request.Headers {
req.Header.Set(key, value)
}
_, exists := req.Header["Content-Type"]
if len(request.Body) > 0 && !exists {
req.Header.Set("Content-Type", "application/json")
}
return req, err
}

// MakeRequest makes the API call.
func MakeRequest(req *http.Request) (*http.Response, error) {
return DefaultClient.HTTPClient.Do(req)
}

// BuildResponse builds the response struct.
func BuildResponse(res *http.Response) (*Response, error) {
body, err := io.ReadAll(res.Body)
response := Response{
StatusCode: res.StatusCode,
Body: string(body),
Headers: res.Header,
}
res.Body.Close() // nolint
return &response, err
}

// Deprecated: API supports old implementation
func API(request Request) (*Response, error) {
return Send(request)
}

// Send uses the DefaultClient to send your request
func Send(request Request) (*Response, error) {
return SendWithContext(context.Background(), request)
}

// SendWithContext uses the DefaultClient to send your request with the provided context.
func SendWithContext(ctx context.Context, request Request) (*Response, error) {
return DefaultClient.SendWithContext(ctx, request)
}

// The following functions enable the ability to define a
// custom HTTP Client

// MakeRequest makes the API call.
func (c *Client) MakeRequest(req *http.Request) (*http.Response, error) {
return c.HTTPClient.Do(req)
}

// Deprecated: API supports old implementation
func (c *Client) API(request Request) (*Response, error) {
return c.Send(request)
}

// Send will build your request, make the request, and build your response.
func (c *Client) Send(request Request) (*Response, error) {
return c.SendWithContext(context.Background(), request)
}

// SendWithContext will build your request passing in the provided context, make the request, and build your response.
func (c *Client) SendWithContext(ctx context.Context, request Request) (*Response, error) {
// Build the HTTP request object.
req, err := BuildRequestObject(request)
if err != nil {
return nil, err
}
// Pass in the user provided context
req = req.WithContext(ctx)

// Build the HTTP client and make the request.
res, err := c.MakeRequest(req)
if err != nil {
return nil, err
}

// Build Response object.
return BuildResponse(res)
}
Loading