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
8package github
9
10import (
11	"bytes"
12	"context"
13	"encoding/json"
14	"errors"
15	"fmt"
16	"io"
17	"io/ioutil"
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
29const (
30	defaultBaseURL = "https://api.github.com/"
31	uploadBaseURL  = "https://uploads.github.com/"
32	userAgent      = "go-github"
33
34	headerRateLimit     = "X-RateLimit-Limit"
35	headerRateRemaining = "X-RateLimit-Remaining"
36	headerRateReset     = "X-RateLimit-Reset"
37	headerOTP           = "X-GitHub-OTP"
38
39	mediaTypeV3                = "application/vnd.github.v3+json"
40	defaultMediaType           = "application/octet-stream"
41	mediaTypeV3SHA             = "application/vnd.github.v3.sha"
42	mediaTypeV3Diff            = "application/vnd.github.v3.diff"
43	mediaTypeV3Patch           = "application/vnd.github.v3.patch"
44	mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json"
45
46	// Media Type values to access preview APIs
47
48	// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
49	mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
50
51	// https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/
52	mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"
53
54	// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
55	mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
56
57	// https://developer.github.com/changes/2016-02-19-source-import-preview-api/
58	mediaTypeImportPreview = "application/vnd.github.barred-rock-preview"
59
60	// https://developer.github.com/changes/2016-05-12-reactions-api-preview/
61	mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"
62
63	// https://developer.github.com/changes/2016-04-04-git-signing-api-preview/
64	mediaTypeGitSigningPreview = "application/vnd.github.cryptographer-preview+json"
65
66	// https://developer.github.com/changes/2016-05-23-timeline-preview-api/
67	mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
68
69	// https://developer.github.com/changes/2016-06-14-repository-invitations/
70	mediaTypeRepositoryInvitationsPreview = "application/vnd.github.swamp-thing-preview+json"
71
72	// https://developer.github.com/changes/2016-07-06-github-pages-preiew-api/
73	mediaTypePagesPreview = "application/vnd.github.mister-fantastic-preview+json"
74
75	// https://developer.github.com/changes/2016-09-14-projects-api/
76	mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
77
78	// https://developer.github.com/changes/2016-09-14-Integrations-Early-Access/
79	mediaTypeIntegrationPreview = "application/vnd.github.machine-man-preview+json"
80
81	// https://developer.github.com/changes/2017-01-05-commit-search-api/
82	mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"
83
84	// https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/
85	mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json"
86
87	// https://developer.github.com/changes/2017-02-09-community-health/
88	mediaTypeRepositoryCommunityHealthMetricsPreview = "application/vnd.github.black-panther-preview+json"
89
90	// https://developer.github.com/changes/2017-05-23-coc-api/
91	mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json"
92
93	// https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/
94	mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json"
95
96	// https://developer.github.com/changes/2017-08-30-preview-nested-teams/
97	mediaTypeNestedTeamsPreview = "application/vnd.github.hellcat-preview+json"
98
99	// https://developer.github.com/changes/2017-11-09-repository-transfer-api-preview/
100	mediaTypeRepositoryTransferPreview = "application/vnd.github.nightshade-preview+json"
101
102	// https://developer.github.com/changes/2018-01-25-organization-invitation-api-preview/
103	mediaTypeOrganizationInvitationPreview = "application/vnd.github.dazzler-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-02-22-label-description-search-preview/
109	mediaTypeLabelDescriptionSearchPreview = "application/vnd.github.symmetra-preview+json"
110
111	// https://developer.github.com/changes/2018-02-07-team-discussions-api/
112	mediaTypeTeamDiscussionsPreview = "application/vnd.github.echo-preview+json"
113
114	// https://developer.github.com/changes/2018-03-21-hovercard-api-preview/
115	mediaTypeHovercardPreview = "application/vnd.github.hagar-preview+json"
116
117	// https://developer.github.com/changes/2018-01-10-lock-reason-api-preview/
118	mediaTypeLockReasonPreview = "application/vnd.github.sailor-v-preview+json"
119
120	// https://developer.github.com/changes/2018-05-07-new-checks-api-public-beta/
121	mediaTypeCheckRunsPreview = "application/vnd.github.antiope-preview+json"
122
123	// https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/
124	mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview"
125)
126
127// A Client manages communication with the GitHub API.
128type Client struct {
129	clientMu sync.Mutex   // clientMu protects the client during calls that modify the CheckRedirect func.
130	client   *http.Client // HTTP client used to communicate with the API.
131
132	// Base URL for API requests. Defaults to the public GitHub API, but can be
133	// set to a domain endpoint to use with GitHub Enterprise. BaseURL should
134	// always be specified with a trailing slash.
135	BaseURL *url.URL
136
137	// Base URL for uploading files.
138	UploadURL *url.URL
139
140	// User agent used when communicating with the GitHub API.
141	UserAgent string
142
143	rateMu     sync.Mutex
144	rateLimits [categories]Rate // Rate limits for the client as determined by the most recent API calls.
145
146	common service // Reuse a single struct instead of allocating one for each service on the heap.
147
148	// Services used for talking to different parts of the GitHub API.
149	Activity       *ActivityService
150	Admin          *AdminService
151	Apps           *AppsService
152	Authorizations *AuthorizationsService
153	Checks         *ChecksService
154	Gists          *GistsService
155	Git            *GitService
156	Gitignores     *GitignoresService
157	Issues         *IssuesService
158	Licenses       *LicensesService
159	Marketplace    *MarketplaceService
160	Migrations     *MigrationService
161	Organizations  *OrganizationsService
162	Projects       *ProjectsService
163	PullRequests   *PullRequestsService
164	Reactions      *ReactionsService
165	Repositories   *RepositoriesService
166	Search         *SearchService
167	Teams          *TeamsService
168	Users          *UsersService
169}
170
171type service struct {
172	client *Client
173}
174
175// ListOptions specifies the optional parameters to various List methods that
176// support pagination.
177type ListOptions struct {
178	// For paginated result sets, page of results to retrieve.
179	Page int `url:"page,omitempty"`
180
181	// For paginated result sets, the number of results to include per page.
182	PerPage int `url:"per_page,omitempty"`
183}
184
185// UploadOptions specifies the parameters to methods that support uploads.
186type UploadOptions struct {
187	Name string `url:"name,omitempty"`
188}
189
190// RawType represents type of raw format of a request instead of JSON.
191type RawType uint8
192
193const (
194	// Diff format.
195	Diff RawType = 1 + iota
196	// Patch format.
197	Patch
198)
199
200// RawOptions specifies parameters when user wants to get raw format of
201// a response instead of JSON.
202type RawOptions struct {
203	Type RawType
204}
205
206// addOptions adds the parameters in opt as URL query parameters to s. opt
207// must be a struct whose fields may contain "url" tags.
208func addOptions(s string, opt interface{}) (string, error) {
209	v := reflect.ValueOf(opt)
210	if v.Kind() == reflect.Ptr && v.IsNil() {
211		return s, nil
212	}
213
214	u, err := url.Parse(s)
215	if err != nil {
216		return s, err
217	}
218
219	qs, err := query.Values(opt)
220	if err != nil {
221		return s, err
222	}
223
224	u.RawQuery = qs.Encode()
225	return u.String(), nil
226}
227
228// NewClient returns a new GitHub API client. If a nil httpClient is
229// provided, http.DefaultClient will be used. To use API methods which require
230// authentication, provide an http.Client that will perform the authentication
231// for you (such as that provided by the golang.org/x/oauth2 library).
232func NewClient(httpClient *http.Client) *Client {
233	if httpClient == nil {
234		httpClient = http.DefaultClient
235	}
236	baseURL, _ := url.Parse(defaultBaseURL)
237	uploadURL, _ := url.Parse(uploadBaseURL)
238
239	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL}
240	c.common.client = c
241	c.Activity = (*ActivityService)(&c.common)
242	c.Admin = (*AdminService)(&c.common)
243	c.Apps = (*AppsService)(&c.common)
244	c.Authorizations = (*AuthorizationsService)(&c.common)
245	c.Checks = (*ChecksService)(&c.common)
246	c.Gists = (*GistsService)(&c.common)
247	c.Git = (*GitService)(&c.common)
248	c.Gitignores = (*GitignoresService)(&c.common)
249	c.Issues = (*IssuesService)(&c.common)
250	c.Licenses = (*LicensesService)(&c.common)
251	c.Marketplace = &MarketplaceService{client: c}
252	c.Migrations = (*MigrationService)(&c.common)
253	c.Organizations = (*OrganizationsService)(&c.common)
254	c.Projects = (*ProjectsService)(&c.common)
255	c.PullRequests = (*PullRequestsService)(&c.common)
256	c.Reactions = (*ReactionsService)(&c.common)
257	c.Repositories = (*RepositoriesService)(&c.common)
258	c.Search = (*SearchService)(&c.common)
259	c.Teams = (*TeamsService)(&c.common)
260	c.Users = (*UsersService)(&c.common)
261	return c
262}
263
264// NewEnterpriseClient returns a new GitHub API client with provided
265// base URL and upload URL (often the same URL).
266// If either URL does not have a trailing slash, one is added automatically.
267// If a nil httpClient is provided, http.DefaultClient will be used.
268//
269// Note that NewEnterpriseClient is a convenience helper only;
270// its behavior is equivalent to using NewClient, followed by setting
271// the BaseURL and UploadURL fields.
272func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*Client, error) {
273	baseEndpoint, err := url.Parse(baseURL)
274	if err != nil {
275		return nil, err
276	}
277	if !strings.HasSuffix(baseEndpoint.Path, "/") {
278		baseEndpoint.Path += "/"
279	}
280
281	uploadEndpoint, err := url.Parse(uploadURL)
282	if err != nil {
283		return nil, err
284	}
285	if !strings.HasSuffix(uploadEndpoint.Path, "/") {
286		uploadEndpoint.Path += "/"
287	}
288
289	c := NewClient(httpClient)
290	c.BaseURL = baseEndpoint
291	c.UploadURL = uploadEndpoint
292	return c, nil
293}
294
295// NewRequest creates an API request. A relative URL can be provided in urlStr,
296// in which case it is resolved relative to the BaseURL of the Client.
297// Relative URLs should always be specified without a preceding slash. If
298// specified, the value pointed to by body is JSON encoded and included as the
299// request body.
300func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
301	if !strings.HasSuffix(c.BaseURL.Path, "/") {
302		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
303	}
304	u, err := c.BaseURL.Parse(urlStr)
305	if err != nil {
306		return nil, err
307	}
308
309	var buf io.ReadWriter
310	if body != nil {
311		buf = new(bytes.Buffer)
312		enc := json.NewEncoder(buf)
313		enc.SetEscapeHTML(false)
314		err := enc.Encode(body)
315		if err != nil {
316			return nil, err
317		}
318	}
319
320	req, err := http.NewRequest(method, u.String(), buf)
321	if err != nil {
322		return nil, err
323	}
324
325	if body != nil {
326		req.Header.Set("Content-Type", "application/json")
327	}
328	req.Header.Set("Accept", mediaTypeV3)
329	if c.UserAgent != "" {
330		req.Header.Set("User-Agent", c.UserAgent)
331	}
332	return req, nil
333}
334
335// NewUploadRequest creates an upload request. A relative URL can be provided in
336// urlStr, in which case it is resolved relative to the UploadURL of the Client.
337// Relative URLs should always be specified without a preceding slash.
338func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
339	if !strings.HasSuffix(c.UploadURL.Path, "/") {
340		return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL)
341	}
342	u, err := c.UploadURL.Parse(urlStr)
343	if err != nil {
344		return nil, err
345	}
346
347	req, err := http.NewRequest("POST", u.String(), reader)
348	if err != nil {
349		return nil, err
350	}
351	req.ContentLength = size
352
353	if mediaType == "" {
354		mediaType = defaultMediaType
355	}
356	req.Header.Set("Content-Type", mediaType)
357	req.Header.Set("Accept", mediaTypeV3)
358	req.Header.Set("User-Agent", c.UserAgent)
359	return req, nil
360}
361
362// Response is a GitHub API response. This wraps the standard http.Response
363// returned from GitHub and provides convenient access to things like
364// pagination links.
365type Response struct {
366	*http.Response
367
368	// These fields provide the page values for paginating through a set of
369	// results. Any or all of these may be set to the zero value for
370	// responses that are not part of a paginated set, or for which there
371	// are no additional pages.
372
373	NextPage  int
374	PrevPage  int
375	FirstPage int
376	LastPage  int
377
378	Rate
379}
380
381// newResponse creates a new Response for the provided http.Response.
382// r must not be nil.
383func newResponse(r *http.Response) *Response {
384	response := &Response{Response: r}
385	response.populatePageValues()
386	response.Rate = parseRate(r)
387	return response
388}
389
390// populatePageValues parses the HTTP Link response headers and populates the
391// various pagination link values in the Response.
392func (r *Response) populatePageValues() {
393	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
394		for _, link := range strings.Split(links[0], ",") {
395			segments := strings.Split(strings.TrimSpace(link), ";")
396
397			// link must at least have href and rel
398			if len(segments) < 2 {
399				continue
400			}
401
402			// ensure href is properly formatted
403			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
404				continue
405			}
406
407			// try to pull out page parameter
408			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
409			if err != nil {
410				continue
411			}
412			page := url.Query().Get("page")
413			if page == "" {
414				continue
415			}
416
417			for _, segment := range segments[1:] {
418				switch strings.TrimSpace(segment) {
419				case `rel="next"`:
420					r.NextPage, _ = strconv.Atoi(page)
421				case `rel="prev"`:
422					r.PrevPage, _ = strconv.Atoi(page)
423				case `rel="first"`:
424					r.FirstPage, _ = strconv.Atoi(page)
425				case `rel="last"`:
426					r.LastPage, _ = strconv.Atoi(page)
427				}
428
429			}
430		}
431	}
432}
433
434// parseRate parses the rate related headers.
435func parseRate(r *http.Response) Rate {
436	var rate Rate
437	if limit := r.Header.Get(headerRateLimit); limit != "" {
438		rate.Limit, _ = strconv.Atoi(limit)
439	}
440	if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
441		rate.Remaining, _ = strconv.Atoi(remaining)
442	}
443	if reset := r.Header.Get(headerRateReset); reset != "" {
444		if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
445			rate.Reset = Timestamp{time.Unix(v, 0)}
446		}
447	}
448	return rate
449}
450
451// Do sends an API request and returns the API response. The API response is
452// JSON decoded and stored in the value pointed to by v, or returned as an
453// error if an API error has occurred. If v implements the io.Writer
454// interface, the raw response body will be written to v, without attempting to
455// first decode it. If rate limit is exceeded and reset time is in the future,
456// Do returns *RateLimitError immediately without making a network API call.
457//
458// The provided ctx must be non-nil. If it is canceled or times out,
459// ctx.Err() will be returned.
460func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
461	req = withContext(ctx, req)
462
463	rateLimitCategory := category(req.URL.Path)
464
465	// If we've hit rate limit, don't make further requests before Reset time.
466	if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
467		return &Response{
468			Response: err.Response,
469			Rate:     err.Rate,
470		}, err
471	}
472
473	resp, err := c.client.Do(req)
474	if err != nil {
475		// If we got an error, and the context has been canceled,
476		// the context's error is probably more useful.
477		select {
478		case <-ctx.Done():
479			return nil, ctx.Err()
480		default:
481		}
482
483		// If the error type is *url.Error, sanitize its URL before returning.
484		if e, ok := err.(*url.Error); ok {
485			if url, err := url.Parse(e.URL); err == nil {
486				e.URL = sanitizeURL(url).String()
487				return nil, e
488			}
489		}
490
491		return nil, err
492	}
493	defer resp.Body.Close()
494
495	response := newResponse(resp)
496
497	c.rateMu.Lock()
498	c.rateLimits[rateLimitCategory] = response.Rate
499	c.rateMu.Unlock()
500
501	err = CheckResponse(resp)
502	if err != nil {
503		// Even though there was an error, we still return the response
504		// in case the caller wants to inspect it further.
505		// However, if the error is AcceptedError, decode it below before
506		// returning from this function and closing the response body.
507		if _, ok := err.(*AcceptedError); !ok {
508			return response, err
509		}
510	}
511
512	if v != nil {
513		if w, ok := v.(io.Writer); ok {
514			io.Copy(w, resp.Body)
515		} else {
516			decErr := json.NewDecoder(resp.Body).Decode(v)
517			if decErr == io.EOF {
518				decErr = nil // ignore EOF errors caused by empty response body
519			}
520			if decErr != nil {
521				err = decErr
522			}
523		}
524	}
525
526	return response, err
527}
528
529// checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
530// current client state in order to quickly check if *RateLimitError can be immediately returned
531// from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
532// Otherwise it returns nil, and Client.Do should proceed normally.
533func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
534	c.rateMu.Lock()
535	rate := c.rateLimits[rateLimitCategory]
536	c.rateMu.Unlock()
537	if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
538		// Create a fake response.
539		resp := &http.Response{
540			Status:     http.StatusText(http.StatusForbidden),
541			StatusCode: http.StatusForbidden,
542			Request:    req,
543			Header:     make(http.Header),
544			Body:       ioutil.NopCloser(strings.NewReader("")),
545		}
546		return &RateLimitError{
547			Rate:     rate,
548			Response: resp,
549			Message:  fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
550		}
551	}
552
553	return nil
554}
555
556/*
557An ErrorResponse reports one or more errors caused by an API request.
558
559GitHub API docs: https://developer.github.com/v3/#client-errors
560*/
561type ErrorResponse struct {
562	Response *http.Response // HTTP response that caused this error
563	Message  string         `json:"message"` // error message
564	Errors   []Error        `json:"errors"`  // more detail on individual errors
565	// Block is only populated on certain types of errors such as code 451.
566	// See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/
567	// for more information.
568	Block *struct {
569		Reason    string     `json:"reason,omitempty"`
570		CreatedAt *Timestamp `json:"created_at,omitempty"`
571	} `json:"block,omitempty"`
572	// Most errors will also include a documentation_url field pointing
573	// to some content that might help you resolve the error, see
574	// https://developer.github.com/v3/#client-errors
575	DocumentationURL string `json:"documentation_url,omitempty"`
576}
577
578func (r *ErrorResponse) Error() string {
579	return fmt.Sprintf("%v %v: %d %v %+v",
580		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
581		r.Response.StatusCode, r.Message, r.Errors)
582}
583
584// TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
585// that has two-factor authentication enabled. The request can be reattempted
586// by providing a one-time password in the request.
587type TwoFactorAuthError ErrorResponse
588
589func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
590
591// RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
592// remaining value of 0, and error message starts with "API rate limit exceeded for ".
593type RateLimitError struct {
594	Rate     Rate           // Rate specifies last known rate limit for the client
595	Response *http.Response // HTTP response that caused this error
596	Message  string         `json:"message"` // error message
597}
598
599func (r *RateLimitError) Error() string {
600	return fmt.Sprintf("%v %v: %d %v %v",
601		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
602		r.Response.StatusCode, r.Message, formatRateReset(r.Rate.Reset.Time.Sub(time.Now())))
603}
604
605// AcceptedError occurs when GitHub returns 202 Accepted response with an
606// empty body, which means a job was scheduled on the GitHub side to process
607// the information needed and cache it.
608// Technically, 202 Accepted is not a real error, it's just used to
609// indicate that results are not ready yet, but should be available soon.
610// The request can be repeated after some time.
611type AcceptedError struct{}
612
613func (*AcceptedError) Error() string {
614	return "job scheduled on GitHub side; try again later"
615}
616
617// AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
618// "documentation_url" field value equal to "https://developer.github.com/v3/#abuse-rate-limits".
619type AbuseRateLimitError struct {
620	Response *http.Response // HTTP response that caused this error
621	Message  string         `json:"message"` // error message
622
623	// RetryAfter is provided with some abuse rate limit errors. If present,
624	// it is the amount of time that the client should wait before retrying.
625	// Otherwise, the client should try again later (after an unspecified amount of time).
626	RetryAfter *time.Duration
627}
628
629func (r *AbuseRateLimitError) Error() string {
630	return fmt.Sprintf("%v %v: %d %v",
631		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
632		r.Response.StatusCode, r.Message)
633}
634
635// sanitizeURL redacts the client_secret parameter from the URL which may be
636// exposed to the user.
637func sanitizeURL(uri *url.URL) *url.URL {
638	if uri == nil {
639		return nil
640	}
641	params := uri.Query()
642	if len(params.Get("client_secret")) > 0 {
643		params.Set("client_secret", "REDACTED")
644		uri.RawQuery = params.Encode()
645	}
646	return uri
647}
648
649/*
650An Error reports more details on an individual error in an ErrorResponse.
651These are the possible validation error codes:
652
653    missing:
654        resource does not exist
655    missing_field:
656        a required field on a resource has not been set
657    invalid:
658        the formatting of a field is invalid
659    already_exists:
660        another resource has the same valid as this field
661    custom:
662        some resources return this (e.g. github.User.CreateKey()), additional
663        information is set in the Message field of the Error
664
665GitHub API docs: https://developer.github.com/v3/#client-errors
666*/
667type Error struct {
668	Resource string `json:"resource"` // resource on which the error occurred
669	Field    string `json:"field"`    // field on which the error occurred
670	Code     string `json:"code"`     // validation error code
671	Message  string `json:"message"`  // Message describing the error. Errors with Code == "custom" will always have this set.
672}
673
674func (e *Error) Error() string {
675	return fmt.Sprintf("%v error caused by %v field on %v resource",
676		e.Code, e.Field, e.Resource)
677}
678
679// CheckResponse checks the API response for errors, and returns them if
680// present. A response is considered an error if it has a status code outside
681// the 200 range or equal to 202 Accepted.
682// API error responses are expected to have either no response
683// body, or a JSON response body that maps to ErrorResponse. Any other
684// response body will be silently ignored.
685//
686// The error type will be *RateLimitError for rate limit exceeded errors,
687// *AcceptedError for 202 Accepted status codes,
688// and *TwoFactorAuthError for two-factor authentication errors.
689func CheckResponse(r *http.Response) error {
690	if r.StatusCode == http.StatusAccepted {
691		return &AcceptedError{}
692	}
693	if c := r.StatusCode; 200 <= c && c <= 299 {
694		return nil
695	}
696	errorResponse := &ErrorResponse{Response: r}
697	data, err := ioutil.ReadAll(r.Body)
698	if err == nil && data != nil {
699		json.Unmarshal(data, errorResponse)
700	}
701	switch {
702	case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
703		return (*TwoFactorAuthError)(errorResponse)
704	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0" && strings.HasPrefix(errorResponse.Message, "API rate limit exceeded for "):
705		return &RateLimitError{
706			Rate:     parseRate(r),
707			Response: errorResponse.Response,
708			Message:  errorResponse.Message,
709		}
710	case r.StatusCode == http.StatusForbidden && strings.HasSuffix(errorResponse.DocumentationURL, "/v3/#abuse-rate-limits"):
711		abuseRateLimitError := &AbuseRateLimitError{
712			Response: errorResponse.Response,
713			Message:  errorResponse.Message,
714		}
715		if v := r.Header["Retry-After"]; len(v) > 0 {
716			// According to GitHub support, the "Retry-After" header value will be
717			// an integer which represents the number of seconds that one should
718			// wait before resuming making requests.
719			retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop.
720			retryAfter := time.Duration(retryAfterSeconds) * time.Second
721			abuseRateLimitError.RetryAfter = &retryAfter
722		}
723		return abuseRateLimitError
724	default:
725		return errorResponse
726	}
727}
728
729// parseBoolResponse determines the boolean result from a GitHub API response.
730// Several GitHub API methods return boolean responses indicated by the HTTP
731// status code in the response (true indicated by a 204, false indicated by a
732// 404). This helper function will determine that result and hide the 404
733// error if present. Any other error will be returned through as-is.
734func parseBoolResponse(err error) (bool, error) {
735	if err == nil {
736		return true, nil
737	}
738
739	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
740		// Simply false. In this one case, we do not pass the error through.
741		return false, nil
742	}
743
744	// some other real error occurred
745	return false, err
746}
747
748// Rate represents the rate limit for the current client.
749type Rate struct {
750	// The number of requests per hour the client is currently limited to.
751	Limit int `json:"limit"`
752
753	// The number of remaining requests the client can make this hour.
754	Remaining int `json:"remaining"`
755
756	// The time at which the current rate limit will reset.
757	Reset Timestamp `json:"reset"`
758}
759
760func (r Rate) String() string {
761	return Stringify(r)
762}
763
764// RateLimits represents the rate limits for the current client.
765type RateLimits struct {
766	// The rate limit for non-search API requests. Unauthenticated
767	// requests are limited to 60 per hour. Authenticated requests are
768	// limited to 5,000 per hour.
769	//
770	// GitHub API docs: https://developer.github.com/v3/#rate-limiting
771	Core *Rate `json:"core"`
772
773	// The rate limit for search API requests. Unauthenticated requests
774	// are limited to 10 requests per minutes. Authenticated requests are
775	// limited to 30 per minute.
776	//
777	// GitHub API docs: https://developer.github.com/v3/search/#rate-limit
778	Search *Rate `json:"search"`
779}
780
781func (r RateLimits) String() string {
782	return Stringify(r)
783}
784
785type rateLimitCategory uint8
786
787const (
788	coreCategory rateLimitCategory = iota
789	searchCategory
790
791	categories // An array of this length will be able to contain all rate limit categories.
792)
793
794// category returns the rate limit category of the endpoint, determined by Request.URL.Path.
795func category(path string) rateLimitCategory {
796	switch {
797	default:
798		return coreCategory
799	case strings.HasPrefix(path, "/search/"):
800		return searchCategory
801	}
802}
803
804// RateLimits returns the rate limits for the current client.
805func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) {
806	req, err := c.NewRequest("GET", "rate_limit", nil)
807	if err != nil {
808		return nil, nil, err
809	}
810
811	response := new(struct {
812		Resources *RateLimits `json:"resources"`
813	})
814	resp, err := c.Do(ctx, req, response)
815	if err != nil {
816		return nil, nil, err
817	}
818
819	if response.Resources != nil {
820		c.rateMu.Lock()
821		if response.Resources.Core != nil {
822			c.rateLimits[coreCategory] = *response.Resources.Core
823		}
824		if response.Resources.Search != nil {
825			c.rateLimits[searchCategory] = *response.Resources.Search
826		}
827		c.rateMu.Unlock()
828	}
829
830	return response.Resources, resp, nil
831}
832
833/*
834UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
835that need to use a higher rate limit associated with your OAuth application.
836
837	t := &github.UnauthenticatedRateLimitedTransport{
838		ClientID:     "your app's client ID",
839		ClientSecret: "your app's client secret",
840	}
841	client := github.NewClient(t.Client())
842
843This will append the querystring params client_id=xxx&client_secret=yyy to all
844requests.
845
846See https://developer.github.com/v3/#unauthenticated-rate-limited-requests for
847more information.
848*/
849type UnauthenticatedRateLimitedTransport struct {
850	// ClientID is the GitHub OAuth client ID of the current application, which
851	// can be found by selecting its entry in the list at
852	// https://github.com/settings/applications.
853	ClientID string
854
855	// ClientSecret is the GitHub OAuth client secret of the current
856	// application.
857	ClientSecret string
858
859	// Transport is the underlying HTTP transport to use when making requests.
860	// It will default to http.DefaultTransport if nil.
861	Transport http.RoundTripper
862}
863
864// RoundTrip implements the RoundTripper interface.
865func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
866	if t.ClientID == "" {
867		return nil, errors.New("t.ClientID is empty")
868	}
869	if t.ClientSecret == "" {
870		return nil, errors.New("t.ClientSecret is empty")
871	}
872
873	// To set extra querystring params, we must make a copy of the Request so
874	// that we don't modify the Request we were given. This is required by the
875	// specification of http.RoundTripper.
876	//
877	// Since we are going to modify only req.URL here, we only need a deep copy
878	// of req.URL.
879	req2 := new(http.Request)
880	*req2 = *req
881	req2.URL = new(url.URL)
882	*req2.URL = *req.URL
883
884	q := req2.URL.Query()
885	q.Set("client_id", t.ClientID)
886	q.Set("client_secret", t.ClientSecret)
887	req2.URL.RawQuery = q.Encode()
888
889	// Make the HTTP request.
890	return t.transport().RoundTrip(req2)
891}
892
893// Client returns an *http.Client that makes requests which are subject to the
894// rate limit of your OAuth application.
895func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
896	return &http.Client{Transport: t}
897}
898
899func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
900	if t.Transport != nil {
901		return t.Transport
902	}
903	return http.DefaultTransport
904}
905
906// BasicAuthTransport is an http.RoundTripper that authenticates all requests
907// using HTTP Basic Authentication with the provided username and password. It
908// additionally supports users who have two-factor authentication enabled on
909// their GitHub account.
910type BasicAuthTransport struct {
911	Username string // GitHub username
912	Password string // GitHub password
913	OTP      string // one-time password for users with two-factor auth enabled
914
915	// Transport is the underlying HTTP transport to use when making requests.
916	// It will default to http.DefaultTransport if nil.
917	Transport http.RoundTripper
918}
919
920// RoundTrip implements the RoundTripper interface.
921func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
922	// To set extra headers, we must make a copy of the Request so
923	// that we don't modify the Request we were given. This is required by the
924	// specification of http.RoundTripper.
925	//
926	// Since we are going to modify only req.Header here, we only need a deep copy
927	// of req.Header.
928	req2 := new(http.Request)
929	*req2 = *req
930	req2.Header = make(http.Header, len(req.Header))
931	for k, s := range req.Header {
932		req2.Header[k] = append([]string(nil), s...)
933	}
934
935	req2.SetBasicAuth(t.Username, t.Password)
936	if t.OTP != "" {
937		req2.Header.Set(headerOTP, t.OTP)
938	}
939	return t.transport().RoundTrip(req2)
940}
941
942// Client returns an *http.Client that makes requests that are authenticated
943// using HTTP Basic Authentication.
944func (t *BasicAuthTransport) Client() *http.Client {
945	return &http.Client{Transport: t}
946}
947
948func (t *BasicAuthTransport) transport() http.RoundTripper {
949	if t.Transport != nil {
950		return t.Transport
951	}
952	return http.DefaultTransport
953}
954
955// formatRateReset formats d to look like "[rate reset in 2s]" or
956// "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]"
957// for the negative cases.
958func formatRateReset(d time.Duration) string {
959	isNegative := d < 0
960	if isNegative {
961		d *= -1
962	}
963	secondsTotal := int(0.5 + d.Seconds())
964	minutes := secondsTotal / 60
965	seconds := secondsTotal - minutes*60
966
967	var timeString string
968	if minutes > 0 {
969		timeString = fmt.Sprintf("%dm%02ds", minutes, seconds)
970	} else {
971		timeString = fmt.Sprintf("%ds", seconds)
972	}
973
974	if isNegative {
975		return fmt.Sprintf("[rate limit was reset %v ago]", timeString)
976	}
977	return fmt.Sprintf("[rate reset in %v]", timeString)
978}
979
980// Bool is a helper routine that allocates a new bool value
981// to store v and returns a pointer to it.
982func Bool(v bool) *bool { return &v }
983
984// Int is a helper routine that allocates a new int value
985// to store v and returns a pointer to it.
986func Int(v int) *int { return &v }
987
988// Int64 is a helper routine that allocates a new int64 value
989// to store v and returns a pointer to it.
990func Int64(v int64) *int64 { return &v }
991
992// String is a helper routine that allocates a new string value
993// to store v and returns a pointer to it.
994func String(v string) *string { return &v }
995