Skip to content

Commit 65d79c6

Browse files
authored
feat: Add client API version support (#4246)
1 parent 7cafc9a commit 65d79c6

2 files changed

Lines changed: 196 additions & 31 deletions

File tree

github/github.go

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,8 @@ const (
4040
HeaderRequestID = "X-Github-Request-Id"
4141

4242
// https://docs.github.com/en/rest/about-the-rest-api/api-versions#about-api-versioning
43-
defaultAPIVersion = api20221128
44-
latestAPIVersion = api20260310
45-
api20221128 = "2022-11-28"
46-
api20260310 = "2026-03-10"
43+
api20221128 = "2022-11-28"
44+
api20260310 = "2026-03-10"
4745

4846
defaultBaseURL = "https://api.github.com/"
4947
defaultUserAgent = "go-github" + "/" + Version
@@ -178,6 +176,13 @@ type Client struct {
178176
// Base URL for uploading files.
179177
uploadURL *url.URL
180178

179+
// Default API version to set in the X-Github-Api-Version header.
180+
apiVersionDefault string
181+
// Minimum API version that the client can use.
182+
apiVersionMin string
183+
// Maximum API version that the client can use.
184+
apiVersionMax string
185+
181186
// User agent used when communicating with the GitHub API.
182187
userAgent string
183188

@@ -347,6 +352,8 @@ type clientOptions struct {
347352
httpClient *http.Client
348353
transport http.RoundTripper
349354
timeout *time.Duration
355+
apiVersionMin *string
356+
apiVersionMax *string
350357
userAgent *string
351358
envProxy bool
352359
token *string
@@ -558,7 +565,11 @@ func NewClient(opts ...ClientOptionsFunc) (*Client, error) {
558565
// newClient creates a new Client with the provided options. This is an internal
559566
// helper function that is called by [NewClient] and [Client.Clone].
560567
func newClient(opts clientOptions) (*Client, error) {
561-
c := &Client{}
568+
c := &Client{
569+
apiVersionDefault: api20221128,
570+
apiVersionMin: api20221128,
571+
apiVersionMax: api20260310,
572+
}
562573

563574
if opts.httpClient != nil {
564575
c.client = opts.httpClient
@@ -609,6 +620,14 @@ func newClient(opts clientOptions) (*Client, error) {
609620
CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },
610621
}
611622

623+
if opts.apiVersionMin != nil {
624+
c.apiVersionMin = *opts.apiVersionMin
625+
}
626+
627+
if opts.apiVersionMax != nil {
628+
c.apiVersionMax = *opts.apiVersionMax
629+
}
630+
612631
if opts.userAgent != nil {
613632
c.userAgent = *opts.userAgent
614633
} else {
@@ -718,6 +737,8 @@ func (c *Client) Clone(opts ...ClientOptionsFunc) (*Client, error) {
718737
}
719738

720739
o := clientOptions{
740+
apiVersionMin: &c.apiVersionMin,
741+
apiVersionMax: &c.apiVersionMax,
721742
userAgent: &c.userAgent,
722743
baseURL: Ptr(*c.baseURL),
723744
uploadURL: Ptr(*c.uploadURL),
@@ -814,7 +835,7 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body any
814835
if c.userAgent != "" {
815836
req.Header.Set("User-Agent", c.userAgent)
816837
}
817-
req.Header.Set(headerAPIVersion, defaultAPIVersion)
838+
req.Header.Set(headerAPIVersion, c.apiVersionDefault)
818839

819840
for _, opt := range opts {
820841
opt(req)
@@ -851,7 +872,7 @@ func (c *Client) NewFormRequest(ctx context.Context, urlStr string, body io.Read
851872
if c.userAgent != "" {
852873
req.Header.Set("User-Agent", c.userAgent)
853874
}
854-
req.Header.Set(headerAPIVersion, defaultAPIVersion)
875+
req.Header.Set(headerAPIVersion, c.apiVersionDefault)
855876

856877
for _, opt := range opts {
857878
opt(req)
@@ -922,7 +943,7 @@ func (c *Client) NewUploadRequest(ctx context.Context, urlStr string, reader io.
922943
req.Header.Set("Content-Type", mediaType)
923944
req.Header.Set("Accept", mediaTypeV3)
924945
req.Header.Set("User-Agent", c.userAgent)
925-
req.Header.Set(headerAPIVersion, defaultAPIVersion)
946+
req.Header.Set(headerAPIVersion, c.apiVersionDefault)
926947

927948
for _, opt := range opts {
928949
opt(req)
@@ -1143,6 +1164,28 @@ const (
11431164
// unexpectedly large error body.
11441165
const maxErrorBodySize = 1 * 1024 * 1024 // 1 MiB
11451166

1167+
// ErrUnsupportedAPIVersion is returned when the API version specified in the
1168+
// request is not supported by the client.
1169+
var ErrUnsupportedAPIVersion = errors.New("unsupported api version")
1170+
1171+
// checkRequestAPIVersionBeforeDo checks if the API version specified in the
1172+
// request is supported by the client before making the API call. If the
1173+
// version is not supported, it returns [ErrUnsupportedAPIVersion]. If the
1174+
// version is empty it returns nil.
1175+
func (c *Client) checkRequestAPIVersionBeforeDo(req *http.Request) error {
1176+
reqAPIVersion := req.Header.Get(headerAPIVersion)
1177+
1178+
if reqAPIVersion == "" {
1179+
return nil
1180+
}
1181+
1182+
if reqAPIVersion < c.apiVersionMin || reqAPIVersion > c.apiVersionMax {
1183+
return ErrUnsupportedAPIVersion
1184+
}
1185+
1186+
return nil
1187+
}
1188+
11461189
// bareDo sends an API request using `caller` http.Client passed in the parameters
11471190
// and lets you handle the api response. If an error or API Error occurs, the error
11481191
// will contain more information. Otherwise, you are supposed to read and close the
@@ -1151,6 +1194,10 @@ const maxErrorBodySize = 1 * 1024 * 1024 // 1 MiB
11511194
func (c *Client) bareDo(caller *http.Client, req *http.Request) (*Response, error) {
11521195
ctx := req.Context()
11531196

1197+
if err := c.checkRequestAPIVersionBeforeDo(req); err != nil {
1198+
return nil, err
1199+
}
1200+
11541201
rateLimitCategory := CoreCategory
11551202

11561203
if !c.disableRateLimitCheck {

0 commit comments

Comments
 (0)