github.com/google/go-github/v49@v49.1.0/github/github.go (about)

     1  // Copyright 2013 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  //go:generate go run gen-accessors.go
     7  //go:generate go run gen-stringify-test.go
     8  
     9  package github
    10  
    11  import (
    12  	"bytes"
    13  	"context"
    14  	"encoding/json"
    15  	"errors"
    16  	"fmt"
    17  	"io"
    18  	"net/http"
    19  	"net/url"
    20  	"reflect"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/google/go-querystring/query"
    27  )
    28  
    29  const (
    30  	Version = "v49.1.0"
    31  
    32  	defaultAPIVersion = "2022-11-28"
    33  	defaultBaseURL    = "https://api.github.com/"
    34  	defaultUserAgent  = "go-github" + "/" + Version
    35  	uploadBaseURL     = "https://uploads.github.com/"
    36  
    37  	headerAPIVersion    = "X-GitHub-Api-Version"
    38  	headerRateLimit     = "X-RateLimit-Limit"
    39  	headerRateRemaining = "X-RateLimit-Remaining"
    40  	headerRateReset     = "X-RateLimit-Reset"
    41  	headerOTP           = "X-GitHub-OTP"
    42  
    43  	headerTokenExpiration = "GitHub-Authentication-Token-Expiration"
    44  
    45  	mediaTypeV3                = "application/vnd.github.v3+json"
    46  	defaultMediaType           = "application/octet-stream"
    47  	mediaTypeV3SHA             = "application/vnd.github.v3.sha"
    48  	mediaTypeV3Diff            = "application/vnd.github.v3.diff"
    49  	mediaTypeV3Patch           = "application/vnd.github.v3.patch"
    50  	mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json"
    51  	mediaTypeIssueImportAPI    = "application/vnd.github.golden-comet-preview+json"
    52  
    53  	// Media Type values to access preview APIs
    54  	// These media types will be added to the API request as headers
    55  	// and used to enable particular features on GitHub API that are still in preview.
    56  	// After some time, specific media types will be promoted (to a "stable" state).
    57  	// From then on, the preview headers are not required anymore to activate the additional
    58  	// feature on GitHub.com's API. However, this API header might still be needed for users
    59  	// to run a GitHub Enterprise Server on-premise.
    60  	// It's not uncommon for GitHub Enterprise Server customers to run older versions which
    61  	// would probably rely on the preview headers for some time.
    62  	// While the header promotion is going out for GitHub.com, it may be some time before it
    63  	// even arrives in GitHub Enterprise Server.
    64  	// We keep those preview headers around to avoid breaking older GitHub Enterprise Server
    65  	// versions. Additionally, non-functional (preview) headers don't create any side effects
    66  	// on GitHub Cloud version.
    67  	//
    68  	// See https://github.com/google/go-github/pull/2125 for full context.
    69  
    70  	// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
    71  	mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
    72  
    73  	// https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/
    74  	mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"
    75  
    76  	// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
    77  	mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
    78  
    79  	// https://developer.github.com/changes/2018-10-16-deployments-environments-states-and-auto-inactive-updates/
    80  	mediaTypeExpandDeploymentStatusPreview = "application/vnd.github.flash-preview+json"
    81  
    82  	// https://developer.github.com/changes/2016-05-12-reactions-api-preview/
    83  	mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"
    84  
    85  	// https://developer.github.com/changes/2016-05-23-timeline-preview-api/
    86  	mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
    87  
    88  	// https://developer.github.com/changes/2016-09-14-projects-api/
    89  	mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
    90  
    91  	// https://developer.github.com/changes/2017-01-05-commit-search-api/
    92  	mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"
    93  
    94  	// https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/
    95  	mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json"
    96  
    97  	// https://developer.github.com/changes/2017-05-23-coc-api/
    98  	mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json"
    99  
   100  	// https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/
   101  	mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json"
   102  
   103  	// https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/
   104  	mediaTypeRequiredApprovingReviewsPreview = "application/vnd.github.luke-cage-preview+json"
   105  
   106  	// https://developer.github.com/changes/2018-05-07-new-checks-api-public-beta/
   107  	mediaTypeCheckRunsPreview = "application/vnd.github.antiope-preview+json"
   108  
   109  	// https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/
   110  	mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview"
   111  
   112  	// https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/
   113  	mediaTypeSignaturePreview = "application/vnd.github.zzzax-preview+json"
   114  
   115  	// https://developer.github.com/changes/2018-09-05-project-card-events/
   116  	mediaTypeProjectCardDetailsPreview = "application/vnd.github.starfox-preview+json"
   117  
   118  	// https://developer.github.com/changes/2018-12-18-interactions-preview/
   119  	mediaTypeInteractionRestrictionsPreview = "application/vnd.github.sombra-preview+json"
   120  
   121  	// https://developer.github.com/changes/2019-03-14-enabling-disabling-pages/
   122  	mediaTypeEnablePagesAPIPreview = "application/vnd.github.switcheroo-preview+json"
   123  
   124  	// https://developer.github.com/changes/2019-04-24-vulnerability-alerts/
   125  	mediaTypeRequiredVulnerabilityAlertsPreview = "application/vnd.github.dorian-preview+json"
   126  
   127  	// https://developer.github.com/changes/2019-06-04-automated-security-fixes/
   128  	mediaTypeRequiredAutomatedSecurityFixesPreview = "application/vnd.github.london-preview+json"
   129  
   130  	// https://developer.github.com/changes/2019-05-29-update-branch-api/
   131  	mediaTypeUpdatePullRequestBranchPreview = "application/vnd.github.lydian-preview+json"
   132  
   133  	// https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/
   134  	mediaTypeListPullsOrBranchesForCommitPreview = "application/vnd.github.groot-preview+json"
   135  
   136  	// https://docs.github.com/en/rest/previews/#repository-creation-permissions
   137  	mediaTypeMemberAllowedRepoCreationTypePreview = "application/vnd.github.surtur-preview+json"
   138  
   139  	// https://docs.github.com/en/rest/previews/#create-and-use-repository-templates
   140  	mediaTypeRepositoryTemplatePreview = "application/vnd.github.baptiste-preview+json"
   141  
   142  	// https://developer.github.com/changes/2019-10-03-multi-line-comments/
   143  	mediaTypeMultiLineCommentsPreview = "application/vnd.github.comfort-fade-preview+json"
   144  
   145  	// https://developer.github.com/changes/2019-11-05-deprecated-passwords-and-authorizations-api/
   146  	mediaTypeOAuthAppPreview = "application/vnd.github.doctor-strange-preview+json"
   147  
   148  	// https://developer.github.com/changes/2019-12-03-internal-visibility-changes/
   149  	mediaTypeRepositoryVisibilityPreview = "application/vnd.github.nebula-preview+json"
   150  
   151  	// https://developer.github.com/changes/2018-12-10-content-attachments-api/
   152  	mediaTypeContentAttachmentsPreview = "application/vnd.github.corsair-preview+json"
   153  )
   154  
   155  var errNonNilContext = errors.New("context must be non-nil")
   156  
   157  // A Client manages communication with the GitHub API.
   158  type Client struct {
   159  	clientMu sync.Mutex   // clientMu protects the client during calls that modify the CheckRedirect func.
   160  	client   *http.Client // HTTP client used to communicate with the API.
   161  
   162  	// Base URL for API requests. Defaults to the public GitHub API, but can be
   163  	// set to a domain endpoint to use with GitHub Enterprise. BaseURL should
   164  	// always be specified with a trailing slash.
   165  	BaseURL *url.URL
   166  
   167  	// Base URL for uploading files.
   168  	UploadURL *url.URL
   169  
   170  	// User agent used when communicating with the GitHub API.
   171  	UserAgent string
   172  
   173  	rateMu     sync.Mutex
   174  	rateLimits [categories]Rate // Rate limits for the client as determined by the most recent API calls.
   175  
   176  	common service // Reuse a single struct instead of allocating one for each service on the heap.
   177  
   178  	// Services used for talking to different parts of the GitHub API.
   179  	Actions        *ActionsService
   180  	Activity       *ActivityService
   181  	Admin          *AdminService
   182  	Apps           *AppsService
   183  	Authorizations *AuthorizationsService
   184  	Billing        *BillingService
   185  	Checks         *ChecksService
   186  	CodeScanning   *CodeScanningService
   187  	Dependabot     *DependabotService
   188  	Enterprise     *EnterpriseService
   189  	Gists          *GistsService
   190  	Git            *GitService
   191  	Gitignores     *GitignoresService
   192  	Interactions   *InteractionsService
   193  	IssueImport    *IssueImportService
   194  	Issues         *IssuesService
   195  	Licenses       *LicensesService
   196  	Marketplace    *MarketplaceService
   197  	Migrations     *MigrationService
   198  	Organizations  *OrganizationsService
   199  	Projects       *ProjectsService
   200  	PullRequests   *PullRequestsService
   201  	Reactions      *ReactionsService
   202  	Repositories   *RepositoriesService
   203  	SCIM           *SCIMService
   204  	Search         *SearchService
   205  	SecretScanning *SecretScanningService
   206  	Teams          *TeamsService
   207  	Users          *UsersService
   208  }
   209  
   210  type service struct {
   211  	client *Client
   212  }
   213  
   214  // Client returns the http.Client used by this GitHub client.
   215  func (c *Client) Client() *http.Client {
   216  	c.clientMu.Lock()
   217  	defer c.clientMu.Unlock()
   218  	clientCopy := *c.client
   219  	return &clientCopy
   220  }
   221  
   222  // ListOptions specifies the optional parameters to various List methods that
   223  // support offset pagination.
   224  type ListOptions struct {
   225  	// For paginated result sets, page of results to retrieve.
   226  	Page int `url:"page,omitempty"`
   227  
   228  	// For paginated result sets, the number of results to include per page.
   229  	PerPage int `url:"per_page,omitempty"`
   230  }
   231  
   232  // ListCursorOptions specifies the optional parameters to various List methods that
   233  // support cursor pagination.
   234  type ListCursorOptions struct {
   235  	// For paginated result sets, page of results to retrieve.
   236  	Page string `url:"page,omitempty"`
   237  
   238  	// For paginated result sets, the number of results to include per page.
   239  	PerPage int `url:"per_page,omitempty"`
   240  
   241  	// For paginated result sets, the number of results per page (max 100), starting from the first matching result.
   242  	// This parameter must not be used in combination with last.
   243  	First int `url:"first,omitempty"`
   244  
   245  	// For paginated result sets, the number of results per page (max 100), starting from the last matching result.
   246  	// This parameter must not be used in combination with first.
   247  	Last int `url:"last,omitempty"`
   248  
   249  	// A cursor, as given in the Link header. If specified, the query only searches for events after this cursor.
   250  	After string `url:"after,omitempty"`
   251  
   252  	// A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
   253  	Before string `url:"before,omitempty"`
   254  
   255  	// A cursor, as given in the Link header. If specified, the query continues the search using this cursor.
   256  	Cursor string `url:"cursor,omitempty"`
   257  }
   258  
   259  // UploadOptions specifies the parameters to methods that support uploads.
   260  type UploadOptions struct {
   261  	Name      string `url:"name,omitempty"`
   262  	Label     string `url:"label,omitempty"`
   263  	MediaType string `url:"-"`
   264  }
   265  
   266  // RawType represents type of raw format of a request instead of JSON.
   267  type RawType uint8
   268  
   269  const (
   270  	// Diff format.
   271  	Diff RawType = 1 + iota
   272  	// Patch format.
   273  	Patch
   274  )
   275  
   276  // RawOptions specifies parameters when user wants to get raw format of
   277  // a response instead of JSON.
   278  type RawOptions struct {
   279  	Type RawType
   280  }
   281  
   282  // addOptions adds the parameters in opts as URL query parameters to s. opts
   283  // must be a struct whose fields may contain "url" tags.
   284  func addOptions(s string, opts interface{}) (string, error) {
   285  	v := reflect.ValueOf(opts)
   286  	if v.Kind() == reflect.Ptr && v.IsNil() {
   287  		return s, nil
   288  	}
   289  
   290  	u, err := url.Parse(s)
   291  	if err != nil {
   292  		return s, err
   293  	}
   294  
   295  	qs, err := query.Values(opts)
   296  	if err != nil {
   297  		return s, err
   298  	}
   299  
   300  	u.RawQuery = qs.Encode()
   301  	return u.String(), nil
   302  }
   303  
   304  // NewClient returns a new GitHub API client. If a nil httpClient is
   305  // provided, a new http.Client will be used. To use API methods which require
   306  // authentication, provide an http.Client that will perform the authentication
   307  // for you (such as that provided by the golang.org/x/oauth2 library).
   308  func NewClient(httpClient *http.Client) *Client {
   309  	if httpClient == nil {
   310  		httpClient = &http.Client{}
   311  	}
   312  	baseURL, _ := url.Parse(defaultBaseURL)
   313  	uploadURL, _ := url.Parse(uploadBaseURL)
   314  
   315  	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: defaultUserAgent, UploadURL: uploadURL}
   316  	c.common.client = c
   317  	c.Actions = (*ActionsService)(&c.common)
   318  	c.Activity = (*ActivityService)(&c.common)
   319  	c.Admin = (*AdminService)(&c.common)
   320  	c.Apps = (*AppsService)(&c.common)
   321  	c.Authorizations = (*AuthorizationsService)(&c.common)
   322  	c.Billing = (*BillingService)(&c.common)
   323  	c.Checks = (*ChecksService)(&c.common)
   324  	c.CodeScanning = (*CodeScanningService)(&c.common)
   325  	c.Dependabot = (*DependabotService)(&c.common)
   326  	c.Enterprise = (*EnterpriseService)(&c.common)
   327  	c.Gists = (*GistsService)(&c.common)
   328  	c.Git = (*GitService)(&c.common)
   329  	c.Gitignores = (*GitignoresService)(&c.common)
   330  	c.Interactions = (*InteractionsService)(&c.common)
   331  	c.IssueImport = (*IssueImportService)(&c.common)
   332  	c.Issues = (*IssuesService)(&c.common)
   333  	c.Licenses = (*LicensesService)(&c.common)
   334  	c.Marketplace = &MarketplaceService{client: c}
   335  	c.Migrations = (*MigrationService)(&c.common)
   336  	c.Organizations = (*OrganizationsService)(&c.common)
   337  	c.Projects = (*ProjectsService)(&c.common)
   338  	c.PullRequests = (*PullRequestsService)(&c.common)
   339  	c.Reactions = (*ReactionsService)(&c.common)
   340  	c.Repositories = (*RepositoriesService)(&c.common)
   341  	c.SCIM = (*SCIMService)(&c.common)
   342  	c.Search = (*SearchService)(&c.common)
   343  	c.SecretScanning = (*SecretScanningService)(&c.common)
   344  	c.Teams = (*TeamsService)(&c.common)
   345  	c.Users = (*UsersService)(&c.common)
   346  	return c
   347  }
   348  
   349  // NewEnterpriseClient returns a new GitHub API client with provided
   350  // base URL and upload URL (often is your GitHub Enterprise hostname).
   351  // If the base URL does not have the suffix "/api/v3/", it will be added automatically.
   352  // If the upload URL does not have the suffix "/api/uploads", it will be added automatically.
   353  // If a nil httpClient is provided, a new http.Client will be used.
   354  //
   355  // Note that NewEnterpriseClient is a convenience helper only;
   356  // its behavior is equivalent to using NewClient, followed by setting
   357  // the BaseURL and UploadURL fields.
   358  //
   359  // Another important thing is that by default, the GitHub Enterprise URL format
   360  // should be http(s)://[hostname]/api/v3/ or you will always receive the 406 status code.
   361  // The upload URL format should be http(s)://[hostname]/api/uploads/.
   362  func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*Client, error) {
   363  	baseEndpoint, err := url.Parse(baseURL)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	if !strings.HasSuffix(baseEndpoint.Path, "/") {
   369  		baseEndpoint.Path += "/"
   370  	}
   371  	if !strings.HasSuffix(baseEndpoint.Path, "/api/v3/") &&
   372  		!strings.HasPrefix(baseEndpoint.Host, "api.") &&
   373  		!strings.Contains(baseEndpoint.Host, ".api.") {
   374  		baseEndpoint.Path += "api/v3/"
   375  	}
   376  
   377  	uploadEndpoint, err := url.Parse(uploadURL)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  
   382  	if !strings.HasSuffix(uploadEndpoint.Path, "/") {
   383  		uploadEndpoint.Path += "/"
   384  	}
   385  	if !strings.HasSuffix(uploadEndpoint.Path, "/api/uploads/") &&
   386  		!strings.HasPrefix(uploadEndpoint.Host, "api.") &&
   387  		!strings.Contains(uploadEndpoint.Host, ".api.") {
   388  		uploadEndpoint.Path += "api/uploads/"
   389  	}
   390  
   391  	c := NewClient(httpClient)
   392  	c.BaseURL = baseEndpoint
   393  	c.UploadURL = uploadEndpoint
   394  	return c, nil
   395  }
   396  
   397  // RequestOption represents an option that can modify an http.Request.
   398  type RequestOption func(req *http.Request)
   399  
   400  // WithVersion overrides the GitHub v3 API version for this individual request.
   401  // For more information, see:
   402  // https://github.blog/2022-11-28-to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/
   403  func WithVersion(version string) RequestOption {
   404  	return func(req *http.Request) {
   405  		req.Header.Set(headerAPIVersion, version)
   406  	}
   407  }
   408  
   409  // NewRequest creates an API request. A relative URL can be provided in urlStr,
   410  // in which case it is resolved relative to the BaseURL of the Client.
   411  // Relative URLs should always be specified without a preceding slash. If
   412  // specified, the value pointed to by body is JSON encoded and included as the
   413  // request body.
   414  func (c *Client) NewRequest(method, urlStr string, body interface{}, opts ...RequestOption) (*http.Request, error) {
   415  	if !strings.HasSuffix(c.BaseURL.Path, "/") {
   416  		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
   417  	}
   418  
   419  	u, err := c.BaseURL.Parse(urlStr)
   420  	if err != nil {
   421  		return nil, err
   422  	}
   423  
   424  	var buf io.ReadWriter
   425  	if body != nil {
   426  		buf = &bytes.Buffer{}
   427  		enc := json.NewEncoder(buf)
   428  		enc.SetEscapeHTML(false)
   429  		err := enc.Encode(body)
   430  		if err != nil {
   431  			return nil, err
   432  		}
   433  	}
   434  
   435  	req, err := http.NewRequest(method, u.String(), buf)
   436  	if err != nil {
   437  		return nil, err
   438  	}
   439  
   440  	if body != nil {
   441  		req.Header.Set("Content-Type", "application/json")
   442  	}
   443  	req.Header.Set("Accept", mediaTypeV3)
   444  	if c.UserAgent != "" {
   445  		req.Header.Set("User-Agent", c.UserAgent)
   446  	}
   447  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   448  
   449  	for _, opt := range opts {
   450  		opt(req)
   451  	}
   452  
   453  	return req, nil
   454  }
   455  
   456  // NewFormRequest creates an API request. A relative URL can be provided in urlStr,
   457  // in which case it is resolved relative to the BaseURL of the Client.
   458  // Relative URLs should always be specified without a preceding slash.
   459  // Body is sent with Content-Type: application/x-www-form-urlencoded.
   460  func (c *Client) NewFormRequest(urlStr string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
   461  	if !strings.HasSuffix(c.BaseURL.Path, "/") {
   462  		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
   463  	}
   464  
   465  	u, err := c.BaseURL.Parse(urlStr)
   466  	if err != nil {
   467  		return nil, err
   468  	}
   469  
   470  	req, err := http.NewRequest(http.MethodPost, u.String(), body)
   471  	if err != nil {
   472  		return nil, err
   473  	}
   474  
   475  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   476  	req.Header.Set("Accept", mediaTypeV3)
   477  	if c.UserAgent != "" {
   478  		req.Header.Set("User-Agent", c.UserAgent)
   479  	}
   480  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   481  
   482  	for _, opt := range opts {
   483  		opt(req)
   484  	}
   485  
   486  	return req, nil
   487  }
   488  
   489  // NewUploadRequest creates an upload request. A relative URL can be provided in
   490  // urlStr, in which case it is resolved relative to the UploadURL of the Client.
   491  // Relative URLs should always be specified without a preceding slash.
   492  func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string, opts ...RequestOption) (*http.Request, error) {
   493  	if !strings.HasSuffix(c.UploadURL.Path, "/") {
   494  		return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL)
   495  	}
   496  	u, err := c.UploadURL.Parse(urlStr)
   497  	if err != nil {
   498  		return nil, err
   499  	}
   500  
   501  	req, err := http.NewRequest("POST", u.String(), reader)
   502  	if err != nil {
   503  		return nil, err
   504  	}
   505  
   506  	req.ContentLength = size
   507  
   508  	if mediaType == "" {
   509  		mediaType = defaultMediaType
   510  	}
   511  	req.Header.Set("Content-Type", mediaType)
   512  	req.Header.Set("Accept", mediaTypeV3)
   513  	req.Header.Set("User-Agent", c.UserAgent)
   514  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   515  
   516  	for _, opt := range opts {
   517  		opt(req)
   518  	}
   519  
   520  	return req, nil
   521  }
   522  
   523  // Response is a GitHub API response. This wraps the standard http.Response
   524  // returned from GitHub and provides convenient access to things like
   525  // pagination links.
   526  type Response struct {
   527  	*http.Response
   528  
   529  	// These fields provide the page values for paginating through a set of
   530  	// results. Any or all of these may be set to the zero value for
   531  	// responses that are not part of a paginated set, or for which there
   532  	// are no additional pages.
   533  	//
   534  	// These fields support what is called "offset pagination" and should
   535  	// be used with the ListOptions struct.
   536  	NextPage  int
   537  	PrevPage  int
   538  	FirstPage int
   539  	LastPage  int
   540  
   541  	// Additionally, some APIs support "cursor pagination" instead of offset.
   542  	// This means that a token points directly to the next record which
   543  	// can lead to O(1) performance compared to O(n) performance provided
   544  	// by offset pagination.
   545  	//
   546  	// For APIs that support cursor pagination (such as
   547  	// TeamsService.ListIDPGroupsInOrganization), the following field
   548  	// will be populated to point to the next page.
   549  	//
   550  	// To use this token, set ListCursorOptions.Page to this value before
   551  	// calling the endpoint again.
   552  	NextPageToken string
   553  
   554  	// For APIs that support cursor pagination, such as RepositoriesService.ListHookDeliveries,
   555  	// the following field will be populated to point to the next page.
   556  	// Set ListCursorOptions.Cursor to this value when calling the endpoint again.
   557  	Cursor string
   558  
   559  	// For APIs that support before/after pagination, such as OrganizationsService.AuditLog.
   560  	Before string
   561  	After  string
   562  
   563  	// Explicitly specify the Rate type so Rate's String() receiver doesn't
   564  	// propagate to Response.
   565  	Rate Rate
   566  
   567  	// token's expiration date
   568  	TokenExpiration Timestamp
   569  }
   570  
   571  // newResponse creates a new Response for the provided http.Response.
   572  // r must not be nil.
   573  func newResponse(r *http.Response) *Response {
   574  	response := &Response{Response: r}
   575  	response.populatePageValues()
   576  	response.Rate = parseRate(r)
   577  	response.TokenExpiration = parseTokenExpiration(r)
   578  	return response
   579  }
   580  
   581  // populatePageValues parses the HTTP Link response headers and populates the
   582  // various pagination link values in the Response.
   583  func (r *Response) populatePageValues() {
   584  	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
   585  		for _, link := range strings.Split(links[0], ",") {
   586  			segments := strings.Split(strings.TrimSpace(link), ";")
   587  
   588  			// link must at least have href and rel
   589  			if len(segments) < 2 {
   590  				continue
   591  			}
   592  
   593  			// ensure href is properly formatted
   594  			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
   595  				continue
   596  			}
   597  
   598  			// try to pull out page parameter
   599  			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
   600  			if err != nil {
   601  				continue
   602  			}
   603  
   604  			q := url.Query()
   605  
   606  			if cursor := q.Get("cursor"); cursor != "" {
   607  				for _, segment := range segments[1:] {
   608  					switch strings.TrimSpace(segment) {
   609  					case `rel="next"`:
   610  						r.Cursor = cursor
   611  					}
   612  				}
   613  
   614  				continue
   615  			}
   616  
   617  			page := q.Get("page")
   618  			since := q.Get("since")
   619  			before := q.Get("before")
   620  			after := q.Get("after")
   621  
   622  			if page == "" && before == "" && after == "" && since == "" {
   623  				continue
   624  			}
   625  
   626  			if since != "" && page == "" {
   627  				page = since
   628  			}
   629  
   630  			for _, segment := range segments[1:] {
   631  				switch strings.TrimSpace(segment) {
   632  				case `rel="next"`:
   633  					if r.NextPage, err = strconv.Atoi(page); err != nil {
   634  						r.NextPageToken = page
   635  					}
   636  					r.After = after
   637  				case `rel="prev"`:
   638  					r.PrevPage, _ = strconv.Atoi(page)
   639  					r.Before = before
   640  				case `rel="first"`:
   641  					r.FirstPage, _ = strconv.Atoi(page)
   642  				case `rel="last"`:
   643  					r.LastPage, _ = strconv.Atoi(page)
   644  				}
   645  			}
   646  		}
   647  	}
   648  }
   649  
   650  // parseRate parses the rate related headers.
   651  func parseRate(r *http.Response) Rate {
   652  	var rate Rate
   653  	if limit := r.Header.Get(headerRateLimit); limit != "" {
   654  		rate.Limit, _ = strconv.Atoi(limit)
   655  	}
   656  	if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
   657  		rate.Remaining, _ = strconv.Atoi(remaining)
   658  	}
   659  	if reset := r.Header.Get(headerRateReset); reset != "" {
   660  		if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
   661  			rate.Reset = Timestamp{time.Unix(v, 0)}
   662  		}
   663  	}
   664  	return rate
   665  }
   666  
   667  // parseTokenExpiration parses the TokenExpiration related headers.
   668  func parseTokenExpiration(r *http.Response) Timestamp {
   669  	var exp Timestamp
   670  	if v := r.Header.Get(headerTokenExpiration); v != "" {
   671  		if t, err := time.Parse("2006-01-02 03:04:05 MST", v); err == nil {
   672  			exp = Timestamp{t.Local()}
   673  		}
   674  	}
   675  	return exp
   676  }
   677  
   678  type requestContext uint8
   679  
   680  const (
   681  	bypassRateLimitCheck requestContext = iota
   682  )
   683  
   684  // BareDo sends an API request and lets you handle the api response. If an error
   685  // or API Error occurs, the error will contain more information. Otherwise you
   686  // are supposed to read and close the response's Body. If rate limit is exceeded
   687  // and reset time is in the future, BareDo returns *RateLimitError immediately
   688  // without making a network API call.
   689  //
   690  // The provided ctx must be non-nil, if it is nil an error is returned. If it is
   691  // canceled or times out, ctx.Err() will be returned.
   692  func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, error) {
   693  	if ctx == nil {
   694  		return nil, errNonNilContext
   695  	}
   696  
   697  	req = withContext(ctx, req)
   698  
   699  	rateLimitCategory := category(req.URL.Path)
   700  
   701  	if bypass := ctx.Value(bypassRateLimitCheck); bypass == nil {
   702  		// If we've hit rate limit, don't make further requests before Reset time.
   703  		if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
   704  			return &Response{
   705  				Response: err.Response,
   706  				Rate:     err.Rate,
   707  			}, err
   708  		}
   709  	}
   710  
   711  	resp, err := c.client.Do(req)
   712  	if err != nil {
   713  		// If we got an error, and the context has been canceled,
   714  		// the context's error is probably more useful.
   715  		select {
   716  		case <-ctx.Done():
   717  			return nil, ctx.Err()
   718  		default:
   719  		}
   720  
   721  		// If the error type is *url.Error, sanitize its URL before returning.
   722  		if e, ok := err.(*url.Error); ok {
   723  			if url, err := url.Parse(e.URL); err == nil {
   724  				e.URL = sanitizeURL(url).String()
   725  				return nil, e
   726  			}
   727  		}
   728  
   729  		return nil, err
   730  	}
   731  
   732  	response := newResponse(resp)
   733  
   734  	// Don't update the rate limits if this was a cached response.
   735  	// X-From-Cache is set by https://github.com/gregjones/httpcache
   736  	if response.Header.Get("X-From-Cache") == "" {
   737  		c.rateMu.Lock()
   738  		c.rateLimits[rateLimitCategory] = response.Rate
   739  		c.rateMu.Unlock()
   740  	}
   741  
   742  	err = CheckResponse(resp)
   743  	if err != nil {
   744  		defer resp.Body.Close()
   745  		// Special case for AcceptedErrors. If an AcceptedError
   746  		// has been encountered, the response's payload will be
   747  		// added to the AcceptedError and returned.
   748  		//
   749  		// Issue #1022
   750  		aerr, ok := err.(*AcceptedError)
   751  		if ok {
   752  			b, readErr := io.ReadAll(resp.Body)
   753  			if readErr != nil {
   754  				return response, readErr
   755  			}
   756  
   757  			aerr.Raw = b
   758  			err = aerr
   759  		}
   760  	}
   761  	return response, err
   762  }
   763  
   764  // Do sends an API request and returns the API response. The API response is
   765  // JSON decoded and stored in the value pointed to by v, or returned as an
   766  // error if an API error has occurred. If v implements the io.Writer interface,
   767  // the raw response body will be written to v, without attempting to first
   768  // decode it. If v is nil, and no error hapens, the response is returned as is.
   769  // If rate limit is exceeded and reset time is in the future, Do returns
   770  // *RateLimitError immediately without making a network API call.
   771  //
   772  // The provided ctx must be non-nil, if it is nil an error is returned. If it
   773  // is canceled or times out, ctx.Err() will be returned.
   774  func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
   775  	resp, err := c.BareDo(ctx, req)
   776  	if err != nil {
   777  		return resp, err
   778  	}
   779  	defer resp.Body.Close()
   780  
   781  	switch v := v.(type) {
   782  	case nil:
   783  	case io.Writer:
   784  		_, err = io.Copy(v, resp.Body)
   785  	default:
   786  		decErr := json.NewDecoder(resp.Body).Decode(v)
   787  		if decErr == io.EOF {
   788  			decErr = nil // ignore EOF errors caused by empty response body
   789  		}
   790  		if decErr != nil {
   791  			err = decErr
   792  		}
   793  	}
   794  	return resp, err
   795  }
   796  
   797  // checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
   798  // current client state in order to quickly check if *RateLimitError can be immediately returned
   799  // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
   800  // Otherwise it returns nil, and Client.Do should proceed normally.
   801  func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
   802  	c.rateMu.Lock()
   803  	rate := c.rateLimits[rateLimitCategory]
   804  	c.rateMu.Unlock()
   805  	if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
   806  		// Create a fake response.
   807  		resp := &http.Response{
   808  			Status:     http.StatusText(http.StatusForbidden),
   809  			StatusCode: http.StatusForbidden,
   810  			Request:    req,
   811  			Header:     make(http.Header),
   812  			Body:       io.NopCloser(strings.NewReader("")),
   813  		}
   814  		return &RateLimitError{
   815  			Rate:     rate,
   816  			Response: resp,
   817  			Message:  fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
   818  		}
   819  	}
   820  
   821  	return nil
   822  }
   823  
   824  // compareHTTPResponse returns whether two http.Response objects are equal or not.
   825  // Currently, only StatusCode is checked. This function is used when implementing the
   826  // Is(error) bool interface for the custom error types in this package.
   827  func compareHTTPResponse(r1, r2 *http.Response) bool {
   828  	if r1 == nil && r2 == nil {
   829  		return true
   830  	}
   831  
   832  	if r1 != nil && r2 != nil {
   833  		return r1.StatusCode == r2.StatusCode
   834  	}
   835  	return false
   836  }
   837  
   838  /*
   839  An ErrorResponse reports one or more errors caused by an API request.
   840  
   841  GitHub API docs: https://docs.github.com/en/rest/#client-errors
   842  */
   843  type ErrorResponse struct {
   844  	Response *http.Response // HTTP response that caused this error
   845  	Message  string         `json:"message"` // error message
   846  	Errors   []Error        `json:"errors"`  // more detail on individual errors
   847  	// Block is only populated on certain types of errors such as code 451.
   848  	Block *ErrorBlock `json:"block,omitempty"`
   849  	// Most errors will also include a documentation_url field pointing
   850  	// to some content that might help you resolve the error, see
   851  	// https://docs.github.com/en/rest/#client-errors
   852  	DocumentationURL string `json:"documentation_url,omitempty"`
   853  }
   854  
   855  // ErrorBlock contains a further explanation for the reason of an error.
   856  // See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/
   857  // for more information.
   858  type ErrorBlock struct {
   859  	Reason    string     `json:"reason,omitempty"`
   860  	CreatedAt *Timestamp `json:"created_at,omitempty"`
   861  }
   862  
   863  func (r *ErrorResponse) Error() string {
   864  	return fmt.Sprintf("%v %v: %d %v %+v",
   865  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   866  		r.Response.StatusCode, r.Message, r.Errors)
   867  }
   868  
   869  // Is returns whether the provided error equals this error.
   870  func (r *ErrorResponse) Is(target error) bool {
   871  	v, ok := target.(*ErrorResponse)
   872  	if !ok {
   873  		return false
   874  	}
   875  
   876  	if r.Message != v.Message || (r.DocumentationURL != v.DocumentationURL) ||
   877  		!compareHTTPResponse(r.Response, v.Response) {
   878  		return false
   879  	}
   880  
   881  	// Compare Errors.
   882  	if len(r.Errors) != len(v.Errors) {
   883  		return false
   884  	}
   885  	for idx := range r.Errors {
   886  		if r.Errors[idx] != v.Errors[idx] {
   887  			return false
   888  		}
   889  	}
   890  
   891  	// Compare Block.
   892  	if (r.Block != nil && v.Block == nil) || (r.Block == nil && v.Block != nil) {
   893  		return false
   894  	}
   895  	if r.Block != nil && v.Block != nil {
   896  		if r.Block.Reason != v.Block.Reason {
   897  			return false
   898  		}
   899  		if (r.Block.CreatedAt != nil && v.Block.CreatedAt == nil) || (r.Block.CreatedAt ==
   900  			nil && v.Block.CreatedAt != nil) {
   901  			return false
   902  		}
   903  		if r.Block.CreatedAt != nil && v.Block.CreatedAt != nil {
   904  			if *(r.Block.CreatedAt) != *(v.Block.CreatedAt) {
   905  				return false
   906  			}
   907  		}
   908  	}
   909  
   910  	return true
   911  }
   912  
   913  // TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
   914  // that has two-factor authentication enabled. The request can be reattempted
   915  // by providing a one-time password in the request.
   916  type TwoFactorAuthError ErrorResponse
   917  
   918  func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
   919  
   920  // RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
   921  // remaining value of 0.
   922  type RateLimitError struct {
   923  	Rate     Rate           // Rate specifies last known rate limit for the client
   924  	Response *http.Response // HTTP response that caused this error
   925  	Message  string         `json:"message"` // error message
   926  }
   927  
   928  func (r *RateLimitError) Error() string {
   929  	return fmt.Sprintf("%v %v: %d %v %v",
   930  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   931  		r.Response.StatusCode, r.Message, formatRateReset(time.Until(r.Rate.Reset.Time)))
   932  }
   933  
   934  // Is returns whether the provided error equals this error.
   935  func (r *RateLimitError) Is(target error) bool {
   936  	v, ok := target.(*RateLimitError)
   937  	if !ok {
   938  		return false
   939  	}
   940  
   941  	return r.Rate == v.Rate &&
   942  		r.Message == v.Message &&
   943  		compareHTTPResponse(r.Response, v.Response)
   944  }
   945  
   946  // AcceptedError occurs when GitHub returns 202 Accepted response with an
   947  // empty body, which means a job was scheduled on the GitHub side to process
   948  // the information needed and cache it.
   949  // Technically, 202 Accepted is not a real error, it's just used to
   950  // indicate that results are not ready yet, but should be available soon.
   951  // The request can be repeated after some time.
   952  type AcceptedError struct {
   953  	// Raw contains the response body.
   954  	Raw []byte
   955  }
   956  
   957  func (*AcceptedError) Error() string {
   958  	return "job scheduled on GitHub side; try again later"
   959  }
   960  
   961  // Is returns whether the provided error equals this error.
   962  func (ae *AcceptedError) Is(target error) bool {
   963  	v, ok := target.(*AcceptedError)
   964  	if !ok {
   965  		return false
   966  	}
   967  	return bytes.Compare(ae.Raw, v.Raw) == 0
   968  }
   969  
   970  // AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
   971  // "documentation_url" field value equal to "https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits".
   972  type AbuseRateLimitError struct {
   973  	Response *http.Response // HTTP response that caused this error
   974  	Message  string         `json:"message"` // error message
   975  
   976  	// RetryAfter is provided with some abuse rate limit errors. If present,
   977  	// it is the amount of time that the client should wait before retrying.
   978  	// Otherwise, the client should try again later (after an unspecified amount of time).
   979  	RetryAfter *time.Duration
   980  }
   981  
   982  func (r *AbuseRateLimitError) Error() string {
   983  	return fmt.Sprintf("%v %v: %d %v",
   984  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   985  		r.Response.StatusCode, r.Message)
   986  }
   987  
   988  // Is returns whether the provided error equals this error.
   989  func (r *AbuseRateLimitError) Is(target error) bool {
   990  	v, ok := target.(*AbuseRateLimitError)
   991  	if !ok {
   992  		return false
   993  	}
   994  
   995  	return r.Message == v.Message &&
   996  		r.RetryAfter == v.RetryAfter &&
   997  		compareHTTPResponse(r.Response, v.Response)
   998  }
   999  
  1000  // sanitizeURL redacts the client_secret parameter from the URL which may be
  1001  // exposed to the user.
  1002  func sanitizeURL(uri *url.URL) *url.URL {
  1003  	if uri == nil {
  1004  		return nil
  1005  	}
  1006  	params := uri.Query()
  1007  	if len(params.Get("client_secret")) > 0 {
  1008  		params.Set("client_secret", "REDACTED")
  1009  		uri.RawQuery = params.Encode()
  1010  	}
  1011  	return uri
  1012  }
  1013  
  1014  /*
  1015  An Error reports more details on an individual error in an ErrorResponse.
  1016  These are the possible validation error codes:
  1017  
  1018  	missing:
  1019  	    resource does not exist
  1020  	missing_field:
  1021  	    a required field on a resource has not been set
  1022  	invalid:
  1023  	    the formatting of a field is invalid
  1024  	already_exists:
  1025  	    another resource has the same valid as this field
  1026  	custom:
  1027  	    some resources return this (e.g. github.User.CreateKey()), additional
  1028  	    information is set in the Message field of the Error
  1029  
  1030  GitHub error responses structure are often undocumented and inconsistent.
  1031  Sometimes error is just a simple string (Issue #540).
  1032  In such cases, Message represents an error message as a workaround.
  1033  
  1034  GitHub API docs: https://docs.github.com/en/rest/#client-errors
  1035  */
  1036  type Error struct {
  1037  	Resource string `json:"resource"` // resource on which the error occurred
  1038  	Field    string `json:"field"`    // field on which the error occurred
  1039  	Code     string `json:"code"`     // validation error code
  1040  	Message  string `json:"message"`  // Message describing the error. Errors with Code == "custom" will always have this set.
  1041  }
  1042  
  1043  func (e *Error) Error() string {
  1044  	return fmt.Sprintf("%v error caused by %v field on %v resource",
  1045  		e.Code, e.Field, e.Resource)
  1046  }
  1047  
  1048  func (e *Error) UnmarshalJSON(data []byte) error {
  1049  	type aliasError Error // avoid infinite recursion by using type alias.
  1050  	if err := json.Unmarshal(data, (*aliasError)(e)); err != nil {
  1051  		return json.Unmarshal(data, &e.Message) // data can be json string.
  1052  	}
  1053  	return nil
  1054  }
  1055  
  1056  // CheckResponse checks the API response for errors, and returns them if
  1057  // present. A response is considered an error if it has a status code outside
  1058  // the 200 range or equal to 202 Accepted.
  1059  // API error responses are expected to have response
  1060  // body, and a JSON response body that maps to ErrorResponse.
  1061  //
  1062  // The error type will be *RateLimitError for rate limit exceeded errors,
  1063  // *AcceptedError for 202 Accepted status codes,
  1064  // and *TwoFactorAuthError for two-factor authentication errors.
  1065  func CheckResponse(r *http.Response) error {
  1066  	if r.StatusCode == http.StatusAccepted {
  1067  		return &AcceptedError{}
  1068  	}
  1069  	if c := r.StatusCode; 200 <= c && c <= 299 {
  1070  		return nil
  1071  	}
  1072  
  1073  	errorResponse := &ErrorResponse{Response: r}
  1074  	data, err := io.ReadAll(r.Body)
  1075  	if err == nil && data != nil {
  1076  		json.Unmarshal(data, errorResponse)
  1077  	}
  1078  	// Re-populate error response body because GitHub error responses are often
  1079  	// undocumented and inconsistent.
  1080  	// Issue #1136, #540.
  1081  	r.Body = io.NopCloser(bytes.NewBuffer(data))
  1082  	switch {
  1083  	case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
  1084  		return (*TwoFactorAuthError)(errorResponse)
  1085  	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0":
  1086  		return &RateLimitError{
  1087  			Rate:     parseRate(r),
  1088  			Response: errorResponse.Response,
  1089  			Message:  errorResponse.Message,
  1090  		}
  1091  	case r.StatusCode == http.StatusForbidden &&
  1092  		(strings.HasSuffix(errorResponse.DocumentationURL, "#abuse-rate-limits") ||
  1093  			strings.HasSuffix(errorResponse.DocumentationURL, "#secondary-rate-limits")):
  1094  		abuseRateLimitError := &AbuseRateLimitError{
  1095  			Response: errorResponse.Response,
  1096  			Message:  errorResponse.Message,
  1097  		}
  1098  		if v := r.Header["Retry-After"]; len(v) > 0 {
  1099  			// According to GitHub support, the "Retry-After" header value will be
  1100  			// an integer which represents the number of seconds that one should
  1101  			// wait before resuming making requests.
  1102  			retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop.
  1103  			retryAfter := time.Duration(retryAfterSeconds) * time.Second
  1104  			abuseRateLimitError.RetryAfter = &retryAfter
  1105  		}
  1106  		return abuseRateLimitError
  1107  	default:
  1108  		return errorResponse
  1109  	}
  1110  }
  1111  
  1112  // parseBoolResponse determines the boolean result from a GitHub API response.
  1113  // Several GitHub API methods return boolean responses indicated by the HTTP
  1114  // status code in the response (true indicated by a 204, false indicated by a
  1115  // 404). This helper function will determine that result and hide the 404
  1116  // error if present. Any other error will be returned through as-is.
  1117  func parseBoolResponse(err error) (bool, error) {
  1118  	if err == nil {
  1119  		return true, nil
  1120  	}
  1121  
  1122  	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
  1123  		// Simply false. In this one case, we do not pass the error through.
  1124  		return false, nil
  1125  	}
  1126  
  1127  	// some other real error occurred
  1128  	return false, err
  1129  }
  1130  
  1131  // Rate represents the rate limit for the current client.
  1132  type Rate struct {
  1133  	// The number of requests per hour the client is currently limited to.
  1134  	Limit int `json:"limit"`
  1135  
  1136  	// The number of remaining requests the client can make this hour.
  1137  	Remaining int `json:"remaining"`
  1138  
  1139  	// The time at which the current rate limit will reset.
  1140  	Reset Timestamp `json:"reset"`
  1141  }
  1142  
  1143  func (r Rate) String() string {
  1144  	return Stringify(r)
  1145  }
  1146  
  1147  // RateLimits represents the rate limits for the current client.
  1148  type RateLimits struct {
  1149  	// The rate limit for non-search API requests. Unauthenticated
  1150  	// requests are limited to 60 per hour. Authenticated requests are
  1151  	// limited to 5,000 per hour.
  1152  	//
  1153  	// GitHub API docs: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting
  1154  	Core *Rate `json:"core"`
  1155  
  1156  	// The rate limit for search API requests. Unauthenticated requests
  1157  	// are limited to 10 requests per minutes. Authenticated requests are
  1158  	// limited to 30 per minute.
  1159  	//
  1160  	// GitHub API docs: https://docs.github.com/en/rest/search#rate-limit
  1161  	Search *Rate `json:"search"`
  1162  
  1163  	// GitHub API docs: https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit
  1164  	GraphQL *Rate `json:"graphql"`
  1165  
  1166  	// GitHub API dos: https://docs.github.com/en/rest/rate-limit
  1167  	IntegrationManifest *Rate `json:"integration_manifest"`
  1168  
  1169  	SourceImport              *Rate `json:"source_import"`
  1170  	CodeScanningUpload        *Rate `json:"code_scanning_upload"`
  1171  	ActionsRunnerRegistration *Rate `json:"actions_runner_registration"`
  1172  	SCIM                      *Rate `json:"scim"`
  1173  }
  1174  
  1175  func (r RateLimits) String() string {
  1176  	return Stringify(r)
  1177  }
  1178  
  1179  type rateLimitCategory uint8
  1180  
  1181  const (
  1182  	coreCategory rateLimitCategory = iota
  1183  	searchCategory
  1184  	graphqlCategory
  1185  	integrationManifestCategory
  1186  	sourceImportCategory
  1187  	codeScanningUploadCategory
  1188  	actionsRunnerRegistrationCategory
  1189  	scimCategory
  1190  
  1191  	categories // An array of this length will be able to contain all rate limit categories.
  1192  )
  1193  
  1194  // category returns the rate limit category of the endpoint, determined by Request.URL.Path.
  1195  func category(path string) rateLimitCategory {
  1196  	switch {
  1197  	default:
  1198  		return coreCategory
  1199  	case strings.HasPrefix(path, "/search/"):
  1200  		return searchCategory
  1201  	}
  1202  }
  1203  
  1204  // RateLimits returns the rate limits for the current client.
  1205  func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) {
  1206  	req, err := c.NewRequest("GET", "rate_limit", nil)
  1207  	if err != nil {
  1208  		return nil, nil, err
  1209  	}
  1210  
  1211  	response := new(struct {
  1212  		Resources *RateLimits `json:"resources"`
  1213  	})
  1214  
  1215  	// This resource is not subject to rate limits.
  1216  	ctx = context.WithValue(ctx, bypassRateLimitCheck, true)
  1217  	resp, err := c.Do(ctx, req, response)
  1218  	if err != nil {
  1219  		return nil, resp, err
  1220  	}
  1221  
  1222  	if response.Resources != nil {
  1223  		c.rateMu.Lock()
  1224  		if response.Resources.Core != nil {
  1225  			c.rateLimits[coreCategory] = *response.Resources.Core
  1226  		}
  1227  		if response.Resources.Search != nil {
  1228  			c.rateLimits[searchCategory] = *response.Resources.Search
  1229  		}
  1230  		if response.Resources.GraphQL != nil {
  1231  			c.rateLimits[graphqlCategory] = *response.Resources.GraphQL
  1232  		}
  1233  		if response.Resources.IntegrationManifest != nil {
  1234  			c.rateLimits[integrationManifestCategory] = *response.Resources.IntegrationManifest
  1235  		}
  1236  		if response.Resources.SourceImport != nil {
  1237  			c.rateLimits[sourceImportCategory] = *response.Resources.SourceImport
  1238  		}
  1239  		if response.Resources.CodeScanningUpload != nil {
  1240  			c.rateLimits[codeScanningUploadCategory] = *response.Resources.CodeScanningUpload
  1241  		}
  1242  		if response.Resources.ActionsRunnerRegistration != nil {
  1243  			c.rateLimits[actionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration
  1244  		}
  1245  		if response.Resources.SCIM != nil {
  1246  			c.rateLimits[scimCategory] = *response.Resources.SCIM
  1247  		}
  1248  		c.rateMu.Unlock()
  1249  	}
  1250  
  1251  	return response.Resources, resp, nil
  1252  }
  1253  
  1254  func setCredentialsAsHeaders(req *http.Request, id, secret string) *http.Request {
  1255  	// To set extra headers, we must make a copy of the Request so
  1256  	// that we don't modify the Request we were given. This is required by the
  1257  	// specification of http.RoundTripper.
  1258  	//
  1259  	// Since we are going to modify only req.Header here, we only need a deep copy
  1260  	// of req.Header.
  1261  	convertedRequest := new(http.Request)
  1262  	*convertedRequest = *req
  1263  	convertedRequest.Header = make(http.Header, len(req.Header))
  1264  
  1265  	for k, s := range req.Header {
  1266  		convertedRequest.Header[k] = append([]string(nil), s...)
  1267  	}
  1268  	convertedRequest.SetBasicAuth(id, secret)
  1269  	return convertedRequest
  1270  }
  1271  
  1272  /*
  1273  UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
  1274  that need to use a higher rate limit associated with your OAuth application.
  1275  
  1276  	t := &github.UnauthenticatedRateLimitedTransport{
  1277  		ClientID:     "your app's client ID",
  1278  		ClientSecret: "your app's client secret",
  1279  	}
  1280  	client := github.NewClient(t.Client())
  1281  
  1282  This will add the client id and secret as a base64-encoded string in the format
  1283  ClientID:ClientSecret and apply it as an "Authorization": "Basic" header.
  1284  
  1285  See https://docs.github.com/en/rest/#unauthenticated-rate-limited-requests for
  1286  more information.
  1287  */
  1288  type UnauthenticatedRateLimitedTransport struct {
  1289  	// ClientID is the GitHub OAuth client ID of the current application, which
  1290  	// can be found by selecting its entry in the list at
  1291  	// https://github.com/settings/applications.
  1292  	ClientID string
  1293  
  1294  	// ClientSecret is the GitHub OAuth client secret of the current
  1295  	// application.
  1296  	ClientSecret string
  1297  
  1298  	// Transport is the underlying HTTP transport to use when making requests.
  1299  	// It will default to http.DefaultTransport if nil.
  1300  	Transport http.RoundTripper
  1301  }
  1302  
  1303  // RoundTrip implements the RoundTripper interface.
  1304  func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1305  	if t.ClientID == "" {
  1306  		return nil, errors.New("t.ClientID is empty")
  1307  	}
  1308  	if t.ClientSecret == "" {
  1309  		return nil, errors.New("t.ClientSecret is empty")
  1310  	}
  1311  
  1312  	req2 := setCredentialsAsHeaders(req, t.ClientID, t.ClientSecret)
  1313  	// Make the HTTP request.
  1314  	return t.transport().RoundTrip(req2)
  1315  }
  1316  
  1317  // Client returns an *http.Client that makes requests which are subject to the
  1318  // rate limit of your OAuth application.
  1319  func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
  1320  	return &http.Client{Transport: t}
  1321  }
  1322  
  1323  func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
  1324  	if t.Transport != nil {
  1325  		return t.Transport
  1326  	}
  1327  	return http.DefaultTransport
  1328  }
  1329  
  1330  // BasicAuthTransport is an http.RoundTripper that authenticates all requests
  1331  // using HTTP Basic Authentication with the provided username and password. It
  1332  // additionally supports users who have two-factor authentication enabled on
  1333  // their GitHub account.
  1334  type BasicAuthTransport struct {
  1335  	Username string // GitHub username
  1336  	Password string // GitHub password
  1337  	OTP      string // one-time password for users with two-factor auth enabled
  1338  
  1339  	// Transport is the underlying HTTP transport to use when making requests.
  1340  	// It will default to http.DefaultTransport if nil.
  1341  	Transport http.RoundTripper
  1342  }
  1343  
  1344  // RoundTrip implements the RoundTripper interface.
  1345  func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1346  	req2 := setCredentialsAsHeaders(req, t.Username, t.Password)
  1347  	if t.OTP != "" {
  1348  		req2.Header.Set(headerOTP, t.OTP)
  1349  	}
  1350  	return t.transport().RoundTrip(req2)
  1351  }
  1352  
  1353  // Client returns an *http.Client that makes requests that are authenticated
  1354  // using HTTP Basic Authentication.
  1355  func (t *BasicAuthTransport) Client() *http.Client {
  1356  	return &http.Client{Transport: t}
  1357  }
  1358  
  1359  func (t *BasicAuthTransport) transport() http.RoundTripper {
  1360  	if t.Transport != nil {
  1361  		return t.Transport
  1362  	}
  1363  	return http.DefaultTransport
  1364  }
  1365  
  1366  // formatRateReset formats d to look like "[rate reset in 2s]" or
  1367  // "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]"
  1368  // for the negative cases.
  1369  func formatRateReset(d time.Duration) string {
  1370  	isNegative := d < 0
  1371  	if isNegative {
  1372  		d *= -1
  1373  	}
  1374  	secondsTotal := int(0.5 + d.Seconds())
  1375  	minutes := secondsTotal / 60
  1376  	seconds := secondsTotal - minutes*60
  1377  
  1378  	var timeString string
  1379  	if minutes > 0 {
  1380  		timeString = fmt.Sprintf("%dm%02ds", minutes, seconds)
  1381  	} else {
  1382  		timeString = fmt.Sprintf("%ds", seconds)
  1383  	}
  1384  
  1385  	if isNegative {
  1386  		return fmt.Sprintf("[rate limit was reset %v ago]", timeString)
  1387  	}
  1388  	return fmt.Sprintf("[rate reset in %v]", timeString)
  1389  }
  1390  
  1391  // When using roundTripWithOptionalFollowRedirect, note that it
  1392  // is the responsibility of the caller to close the response body.
  1393  func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u string, followRedirects bool, opts ...RequestOption) (*http.Response, error) {
  1394  	req, err := c.NewRequest("GET", u, nil, opts...)
  1395  	if err != nil {
  1396  		return nil, err
  1397  	}
  1398  
  1399  	var resp *http.Response
  1400  	// Use http.DefaultTransport if no custom Transport is configured
  1401  	req = withContext(ctx, req)
  1402  	if c.client.Transport == nil {
  1403  		resp, err = http.DefaultTransport.RoundTrip(req)
  1404  	} else {
  1405  		resp, err = c.client.Transport.RoundTrip(req)
  1406  	}
  1407  	if err != nil {
  1408  		return nil, err
  1409  	}
  1410  
  1411  	// If redirect response is returned, follow it
  1412  	if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
  1413  		resp.Body.Close()
  1414  		u = resp.Header.Get("Location")
  1415  		resp, err = c.roundTripWithOptionalFollowRedirect(ctx, u, false, opts...)
  1416  	}
  1417  	return resp, err
  1418  }
  1419  
  1420  // Bool is a helper routine that allocates a new bool value
  1421  // to store v and returns a pointer to it.
  1422  func Bool(v bool) *bool { return &v }
  1423  
  1424  // Int is a helper routine that allocates a new int value
  1425  // to store v and returns a pointer to it.
  1426  func Int(v int) *int { return &v }
  1427  
  1428  // Int64 is a helper routine that allocates a new int64 value
  1429  // to store v and returns a pointer to it.
  1430  func Int64(v int64) *int64 { return &v }
  1431  
  1432  // String is a helper routine that allocates a new string value
  1433  // to store v and returns a pointer to it.
  1434  func String(v string) *string { return &v }