Skip to content

Commit

Permalink
Support new GitHub v3 API versioning
Browse files Browse the repository at this point in the history
Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>
  • Loading branch information
gmlewis committed Nov 29, 2022
1 parent 18cd63d commit 3cd13ca
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 10 deletions.
52 changes: 42 additions & 10 deletions github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import (
)

const (
Version = "v48.0.0"
Version = "v48.2.0"

defaultBaseURL = "https://api.github.com/"
defaultUserAgent = "go-github" + "/" + Version
uploadBaseURL = "https://uploads.github.com/"
defaultAPIVersion = "2022-11-28"
defaultBaseURL = "https://api.github.com/"
defaultUserAgent = "go-github" + "/" + Version
uploadBaseURL = "https://uploads.github.com/"

headerAPIVersion = "X-GitHub-Api-Version"
headerRateLimit = "X-RateLimit-Limit"
headerRateRemaining = "X-RateLimit-Remaining"
headerRateReset = "X-RateLimit-Reset"
Expand Down Expand Up @@ -392,12 +394,24 @@ func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*C
return c, nil
}

// RequestOption represents an option that can modify an http.Request.
type RequestOption func(req *http.Request)

// WithVersion modifies the GitHub v3 API version for this individual request.
// For more information, see:
// https://github.blog/2022-11-28-to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/
func WithVersion(version string) RequestOption {
return func(req *http.Request) {
req.Header.Set(headerAPIVersion, version)
}
}

// NewRequest creates an API request. A relative URL can be provided in urlStr,
// in which case it is resolved relative to the BaseURL of the Client.
// Relative URLs should always be specified without a preceding slash. If
// specified, the value pointed to by body is JSON encoded and included as the
// request body.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
func (c *Client) NewRequest(method, urlStr string, body interface{}, opts ...RequestOption) (*http.Request, error) {
if !strings.HasSuffix(c.BaseURL.Path, "/") {
return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
}
Expand Down Expand Up @@ -430,14 +444,20 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
req.Header.Set(headerAPIVersion, defaultAPIVersion)

for _, opt := range opts {
opt(req)
}

return req, nil
}

// NewFormRequest creates an API request. A relative URL can be provided in urlStr,
// in which case it is resolved relative to the BaseURL of the Client.
// Relative URLs should always be specified without a preceding slash.
// Body is sent with Content-Type: application/x-www-form-urlencoded.
func (c *Client) NewFormRequest(urlStr string, body io.Reader) (*http.Request, error) {
func (c *Client) NewFormRequest(urlStr string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
if !strings.HasSuffix(c.BaseURL.Path, "/") {
return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
}
Expand All @@ -457,13 +477,19 @@ func (c *Client) NewFormRequest(urlStr string, body io.Reader) (*http.Request, e
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
req.Header.Set(headerAPIVersion, defaultAPIVersion)

for _, opt := range opts {
opt(req)
}

return req, nil
}

// NewUploadRequest creates an upload request. A relative URL can be provided in
// urlStr, in which case it is resolved relative to the UploadURL of the Client.
// Relative URLs should always be specified without a preceding slash.
func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string, opts ...RequestOption) (*http.Request, error) {
if !strings.HasSuffix(c.UploadURL.Path, "/") {
return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL)
}
Expand All @@ -485,6 +511,12 @@ func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, m
req.Header.Set("Content-Type", mediaType)
req.Header.Set("Accept", mediaTypeV3)
req.Header.Set("User-Agent", c.UserAgent)
req.Header.Set(headerAPIVersion, defaultAPIVersion)

for _, opt := range opts {
opt(req)
}

return req, nil
}

Expand Down Expand Up @@ -1358,8 +1390,8 @@ func formatRateReset(d time.Duration) string {

// When using roundTripWithOptionalFollowRedirect, note that it
// is the responsibility of the caller to close the response body.
func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u string, followRedirects bool) (*http.Response, error) {
req, err := c.NewRequest("GET", u, nil)
func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u string, followRedirects bool, opts ...RequestOption) (*http.Response, error) {
req, err := c.NewRequest("GET", u, nil, opts...)
if err != nil {
return nil, err
}
Expand All @@ -1380,7 +1412,7 @@ func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u stri
if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
resp.Body.Close()
u = resp.Header.Get("Location")
resp, err = c.roundTripWithOptionalFollowRedirect(ctx, u, false)
resp, err = c.roundTripWithOptionalFollowRedirect(ctx, u, false, opts...)
}
return resp, err
}
Expand Down
11 changes: 11 additions & 0 deletions github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,17 @@ func TestNewRequest(t *testing.T) {
if !strings.Contains(userAgent, Version) {
t.Errorf("NewRequest() User-Agent should contain %v, found %v", Version, userAgent)
}

apiVersion := req.Header.Get(headerAPIVersion)
if got, want := apiVersion, defaultAPIVersion; got != want {
t.Errorf("NewRequest() %v header is %v, want %v", headerAPIVersion, got, want)
}

req, _ = c.NewRequest("GET", inURL, inBody, WithVersion("2022-11-29"))
apiVersion = req.Header.Get(headerAPIVersion)
if got, want := apiVersion, "2022-11-29"; got != want {
t.Errorf("NewRequest() %v header is %v, want %v", headerAPIVersion, got, want)
}
}

func TestNewRequest_invalidJSON(t *testing.T) {
Expand Down

0 comments on commit 3cd13ca

Please sign in to comment.