func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) error {
	c.rateMu.Lock()
	rate := c.rateLimits[rateLimitCategory]
	c.rateMu.Unlock()
	if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
		resp := &http.Response{
			Status:     http.StatusText(http.StatusForbidden),
			StatusCode: http.StatusForbidden,
			Request:    req,
			Header:     make(http.Header),
			Body:       ioutil.NopCloser(strings.NewReader("")),
		}
		return &RateLimitError{
			Rate:     rate,
			Response: resp,
			Message:  fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
		}
	}
	return nil
}
checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
current client state in order to quickly check if *RateLimitError can be immediately returned
from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
Otherwise it returns nil, and Client.Do should proceed normally.
package github
import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/google/go-querystring/query"
)
const (
	libraryVersion = "3"
	defaultBaseURL = "https://api.github.com/"
	uploadBaseURL  = "https://uploads.github.com/"
	userAgent      = "go-github/" + libraryVersion
	headerRateLimit     = "X-RateLimit-Limit"
	headerRateRemaining = "X-RateLimit-Remaining"
	headerRateReset     = "X-RateLimit-Reset"
	headerOTP           = "X-GitHub-OTP"
	mediaTypeV3                = "application/vnd.github.v3+json"
	defaultMediaType           = "application/octet-stream"
	mediaTypeV3SHA             = "application/vnd.github.v3.sha"
	mediaTypeV3Diff            = "application/vnd.github.v3.diff"
	mediaTypeV3Patch           = "application/vnd.github.v3.patch"
	mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json"
	mediaTypeLicensesPreview = "application/vnd.github.drax-preview+json"
	mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
	mediaTypeProtectedBranchesPreview = "application/vnd.github.loki-preview+json"
	mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"
	mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
	mediaTypeImportPreview = "application/vnd.github.barred-rock-preview"
	mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"
	mediaTypeSquashPreview = "application/vnd.github.polaris-preview+json"
	mediaTypeGitSigningPreview = "application/vnd.github.cryptographer-preview+json"
	mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
	mediaTypeRepositoryInvitationsPreview = "application/vnd.github.swamp-thing-preview+json"
	mediaTypePagesPreview = "application/vnd.github.mister-fantastic-preview+json"
	mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
	mediaTypeIntegrationPreview = "application/vnd.github.machine-man-preview+json"
	mediaTypeOrgMembershipPreview = "application/vnd.github.korra-preview+json"
	mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"
	mediaTypePullRequestReviewsPreview = "application/vnd.github.black-cat-preview+json"
)
type Client struct {
	clientMu sync.Mutex    
	client   *http.Client  
	BaseURL *url.URL
	UploadURL *url.URL
	UserAgent string
	rateMu     sync.Mutex
	rateLimits [categories]Rate  
	common service  
	Activity       *ActivityService
	Admin          *AdminService
	Authorizations *AuthorizationsService
	Gists          *GistsService
	Git            *GitService
	Gitignores     *GitignoresService
	Integrations   *IntegrationsService
	Issues         *IssuesService
	Organizations  *OrganizationsService
	Projects       *ProjectsService
	PullRequests   *PullRequestsService
	Repositories   *RepositoriesService
	Search         *SearchService
	Users          *UsersService
	Licenses       *LicensesService
	Migrations     *MigrationService
	Reactions      *ReactionsService
}
type service struct {
	client *Client
}
type ListOptions struct {
	Page int `url:"page,omitempty"`
	PerPage int `url:"per_page,omitempty"`
}
type UploadOptions struct {
	Name string `url:"name,omitempty"`
}
type RawType uint8
const (
	Diff RawType = 1 + iota
	Patch
)
type RawOptions struct {
	Type RawType
}
func addOptions(s string, opt interface{}) (string, error) {
	v := reflect.ValueOf(opt)
	if v.Kind() == reflect.Ptr && v.IsNil() {
		return s, nil
	}
	u, err := url.Parse(s)
	if err != nil {
		return s, err
	}
	qs, err := query.Values(opt)
	if err != nil {
		return s, err
	}
	u.RawQuery = qs.Encode()
	return u.String(), nil
}
func NewClient(httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}
	baseURL, _ := url.Parse(defaultBaseURL)
	uploadURL, _ := url.Parse(uploadBaseURL)
	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL}
	c.common.client = c
	c.Activity = (*ActivityService)(&c.common)
	c.Admin = (*AdminService)(&c.common)
	c.Authorizations = (*AuthorizationsService)(&c.common)
	c.Gists = (*GistsService)(&c.common)
	c.Git = (*GitService)(&c.common)
	c.Gitignores = (*GitignoresService)(&c.common)
	c.Integrations = (*IntegrationsService)(&c.common)
	c.Issues = (*IssuesService)(&c.common)
	c.Licenses = (*LicensesService)(&c.common)
	c.Migrations = (*MigrationService)(&c.common)
	c.Organizations = (*OrganizationsService)(&c.common)
	c.Projects = (*ProjectsService)(&c.common)
	c.PullRequests = (*PullRequestsService)(&c.common)
	c.Reactions = (*ReactionsService)(&c.common)
	c.Repositories = (*RepositoriesService)(&c.common)
	c.Search = (*SearchService)(&c.common)
	c.Users = (*UsersService)(&c.common)
	return c
}
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
	rel, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}
	u := c.BaseURL.ResolveReference(rel)
	var buf io.ReadWriter
	if body != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return nil, err
		}
	}
	req, err := http.NewRequest(method, u.String(), buf)
	if err != nil {
		return nil, err
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set("Accept", mediaTypeV3)
	if c.UserAgent != "" {
		req.Header.Set("User-Agent", c.UserAgent)
	}
	return req, nil
}
func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
	rel, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}
	u := c.UploadURL.ResolveReference(rel)
	req, err := http.NewRequest("POST", u.String(), reader)
	if err != nil {
		return nil, err
	}
	req.ContentLength = size
	if mediaType == "" {
		mediaType = defaultMediaType
	}
	req.Header.Set("Content-Type", mediaType)
	req.Header.Set("Accept", mediaTypeV3)
	req.Header.Set("User-Agent", c.UserAgent)
	return req, nil
}
type Response struct {
	*http.Response
	NextPage  int
	PrevPage  int
	FirstPage int
	LastPage  int
	Rate
}
func newResponse(r *http.Response) *Response {
	response := &Response{Response: r}
	response.populatePageValues()
	response.Rate = parseRate(r)
	return response
}
func (r *Response) populatePageValues() {
	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
		for _, link := range strings.Split(links[0], ",") {
			segments := strings.Split(strings.TrimSpace(link), ";")
			if len(segments) < 2 {
				continue
			}
			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
				continue
			}
			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
			if err != nil {
				continue
			}
			page := url.Query().Get("page")
			if page == "" {
				continue
			}
			for _, segment := range segments[1:] {
				switch strings.TrimSpace(segment) {
				case `rel="next"`:
					r.NextPage, _ = strconv.Atoi(page)
				case `rel="prev"`:
					r.PrevPage, _ = strconv.Atoi(page)
				case `rel="first"`:
					r.FirstPage, _ = strconv.Atoi(page)
				case `rel="last"`:
					r.LastPage, _ = strconv.Atoi(page)
				}
			}
		}
	}
}
func parseRate(r *http.Response) Rate {
	var rate Rate
	if limit := r.Header.Get(headerRateLimit); limit != "" {
		rate.Limit, _ = strconv.Atoi(limit)
	}
	if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
		rate.Remaining, _ = strconv.Atoi(remaining)
	}
	if reset := r.Header.Get(headerRateReset); reset != "" {
		if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
			rate.Reset = Timestamp{time.Unix(v, 0)}
		}
	}
	return rate
}
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
	req = req.WithContext(ctx)
	rateLimitCategory := category(req.URL.Path)
	if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
		return nil, err
	}
	resp, err := c.client.Do(req)
	if err != nil {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
		}
		if e, ok := err.(*url.Error); ok {
			if url, err := url.Parse(e.URL); err == nil {
				e.URL = sanitizeURL(url).String()
				return nil, e
			}
		}
		return nil, err
	}
	defer func() {
		io.CopyN(ioutil.Discard, resp.Body, 512)
		resp.Body.Close()
	}()
	response := newResponse(resp)
	c.rateMu.Lock()
	c.rateLimits[rateLimitCategory] = response.Rate
	c.rateMu.Unlock()
	err = CheckResponse(resp)
	if err != nil {
		return response, err
	}
	if v != nil {
		if w, ok := v.(io.Writer); ok {
			io.Copy(w, resp.Body)
		} else {
			err = json.NewDecoder(resp.Body).Decode(v)
			if err == io.EOF {
				err = nil  
			}
		}
	}
	return response, err
}