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