func CheckResponse(r *http.Response) error {
	switch r.StatusCode {
	case 200, 201, 202, 204, 304:
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		errorResponse.Body = data
		var raw interface{}
		if err := json.Unmarshal(data, &raw); err != nil {
			errorResponse.Message = "failed to parse unknown error format"
		} else {
			errorResponse.Message = parseError(raw)
		}
	}
	return errorResponse
}
CheckResponse checks the API response for errors, and returns them if present.
package gitlab
import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/google/go-querystring/query"
	"github.com/hashicorp/go-cleanhttp"
	retryablehttp "github.com/hashicorp/go-retryablehttp"
	"golang.org/x/oauth2"
	"golang.org/x/time/rate"
)
const (
	defaultBaseURL = "https://gitlab.com/"
	apiVersionPath = "api/v4/"
	userAgent      = "go-gitlab"
	headerRateLimit = "RateLimit-Limit"
	headerRateReset = "RateLimit-Reset"
)
type authType int
const (
	basicAuth authType = iota
	oAuthToken
	privateToken
)
type Client struct {
	client *retryablehttp.Client
	baseURL *url.URL
	disableRetries bool
	configureLimiterOnce sync.Once
	limiter RateLimiter
	authType authType
	username, password string
	token string
	tokenLock sync.RWMutex
	UserAgent string
	AccessRequests        *AccessRequestsService
	Applications          *ApplicationsService
	AuditEvents           *AuditEventsService
	Avatar                *AvatarRequestsService
	AwardEmoji            *AwardEmojiService
	Boards                *IssueBoardsService
	Branches              *BranchesService
	BroadcastMessage      *BroadcastMessagesService
	CIYMLTemplate         *CIYMLTemplatesService
	Commits               *CommitsService
	ContainerRegistry     *ContainerRegistryService
	CustomAttribute       *CustomAttributesService
	DeployKeys            *DeployKeysService
	DeployTokens          *DeployTokensService
	Deployments           *DeploymentsService
	Discussions           *DiscussionsService
	Environments          *EnvironmentsService
	EpicIssues            *EpicIssuesService
	Epics                 *EpicsService
	Events                *EventsService
	Features              *FeaturesService
	FreezePeriods         *FreezePeriodsService
	GitIgnoreTemplates    *GitIgnoreTemplatesService
	GroupBadges           *GroupBadgesService
	GroupCluster          *GroupClustersService
	GroupImportExport     *GroupImportExportService
	GroupIssueBoards      *GroupIssueBoardsService
	GroupLabels           *GroupLabelsService
	GroupMembers          *GroupMembersService
	GroupMilestones       *GroupMilestonesService
	GroupVariables        *GroupVariablesService
	GroupWikis            *GroupWikisService
	Groups                *GroupsService
	InstanceCluster       *InstanceClustersService
	InstanceVariables     *InstanceVariablesService
	Invites               *InvitesService
	IssueLinks            *IssueLinksService
	Issues                *IssuesService
	IssuesStatistics      *IssuesStatisticsService
	Jobs                  *JobsService
	Keys                  *KeysService
	Labels                *LabelsService
	License               *LicenseService
	LicenseTemplates      *LicenseTemplatesService
	ManagedLicenses       *ManagedLicensesService
	MergeRequestApprovals *MergeRequestApprovalsService
	MergeRequests         *MergeRequestsService
	Milestones            *MilestonesService
	Namespaces            *NamespacesService
	Notes                 *NotesService
	NotificationSettings  *NotificationSettingsService
	Packages              *PackagesService
	PagesDomains          *PagesDomainsService
	PipelineSchedules     *PipelineSchedulesService
	PipelineTriggers      *PipelineTriggersService
	Pipelines             *PipelinesService
	ProjectBadges         *ProjectBadgesService
	ProjectAccessTokens   *ProjectAccessTokensService
	ProjectCluster        *ProjectClustersService
	ProjectImportExport   *ProjectImportExportService
	ProjectMembers        *ProjectMembersService
	ProjectMirrors        *ProjectMirrorService
	ProjectSnippets       *ProjectSnippetsService
	ProjectVariables      *ProjectVariablesService
	Projects              *ProjectsService
	ProtectedBranches     *ProtectedBranchesService
	ProtectedEnvironments *ProtectedEnvironmentsService
	ProtectedTags         *ProtectedTagsService
	ReleaseLinks          *ReleaseLinksService
	Releases              *ReleasesService
	Repositories          *RepositoriesService
	RepositoryFiles       *RepositoryFilesService
	ResourceLabelEvents   *ResourceLabelEventsService
	ResourceStateEvents   *ResourceStateEventsService
	Runners               *RunnersService
	Search                *SearchService
	Services              *ServicesService
	Settings              *SettingsService
	Sidekiq               *SidekiqService
	Snippets              *SnippetsService
	SystemHooks           *SystemHooksService
	Tags                  *TagsService
	Todos                 *TodosService
	Users                 *UsersService
	Validate              *ValidateService
	Version               *VersionService
	Wikis                 *WikisService
}
type ListOptions struct {
	Page int `url:"page,omitempty" json:"page,omitempty"`
	PerPage int `url:"per_page,omitempty" json:"per_page,omitempty"`
}
type RateLimiter interface {
	Wait(context.Context) error
}
func NewClient(token string, options ...ClientOptionFunc) (*Client, error) {
	client, err := newClient(options...)
	if err != nil {
		return nil, err
	}
	client.authType = privateToken
	client.token = token
	return client, nil
}
func NewBasicAuthClient(username, password string, options ...ClientOptionFunc) (*Client, error) {
	client, err := newClient(options...)
	if err != nil {
		return nil, err
	}
	client.authType = basicAuth
	client.username = username
	client.password = password
	return client, nil
}
func NewOAuthClient(token string, options ...ClientOptionFunc) (*Client, error) {
	client, err := newClient(options...)
	if err != nil {
		return nil, err
	}
	client.authType = oAuthToken
	client.token = token
	return client, nil
}
func newClient(options ...ClientOptionFunc) (*Client, error) {
	c := &Client{UserAgent: userAgent}
	c.client = &retryablehttp.Client{
		Backoff:      c.retryHTTPBackoff,
		CheckRetry:   c.retryHTTPCheck,
		ErrorHandler: retryablehttp.PassthroughErrorHandler,
		HTTPClient:   cleanhttp.DefaultPooledClient(),
		RetryWaitMin: 100 * time.Millisecond,
		RetryWaitMax: 400 * time.Millisecond,
		RetryMax:     5,
	}
	c.setBaseURL(defaultBaseURL)
	for _, fn := range options {
		if fn == nil {
			continue
		}
		if err := fn(c); err != nil {
			return nil, err
		}
	}
	timeStats := &timeStatsService{client: c}
	c.AccessRequests = &AccessRequestsService{client: c}
	c.Applications = &ApplicationsService{client: c}
	c.AuditEvents = &AuditEventsService{client: c}
	c.Avatar = &AvatarRequestsService{client: c}
	c.AwardEmoji = &AwardEmojiService{client: c}
	c.Boards = &IssueBoardsService{client: c}
	c.Branches = &BranchesService{client: c}
	c.BroadcastMessage = &BroadcastMessagesService{client: c}
	c.CIYMLTemplate = &CIYMLTemplatesService{client: c}
	c.Commits = &CommitsService{client: c}
	c.ContainerRegistry = &ContainerRegistryService{client: c}
	c.CustomAttribute = &CustomAttributesService{client: c}
	c.DeployKeys = &DeployKeysService{client: c}
	c.DeployTokens = &DeployTokensService{client: c}
	c.Deployments = &DeploymentsService{client: c}
	c.Discussions = &DiscussionsService{client: c}
	c.Environments = &EnvironmentsService{client: c}
	c.EpicIssues = &EpicIssuesService{client: c}
	c.Epics = &EpicsService{client: c}
	c.Events = &EventsService{client: c}
	c.Features = &FeaturesService{client: c}
	c.FreezePeriods = &FreezePeriodsService{client: c}
	c.GitIgnoreTemplates = &GitIgnoreTemplatesService{client: c}
	c.GroupBadges = &GroupBadgesService{client: c}
	c.GroupCluster = &GroupClustersService{client: c}
	c.GroupImportExport = &GroupImportExportService{client: c}
	c.GroupIssueBoards = &GroupIssueBoardsService{client: c}
	c.GroupLabels = &GroupLabelsService{client: c}
	c.GroupMembers = &GroupMembersService{client: c}
	c.GroupMilestones = &GroupMilestonesService{client: c}
	c.GroupVariables = &GroupVariablesService{client: c}
	c.GroupWikis = &GroupWikisService{client: c}
	c.Groups = &GroupsService{client: c}
	c.InstanceCluster = &InstanceClustersService{client: c}
	c.InstanceVariables = &InstanceVariablesService{client: c}
	c.Invites = &InvitesService{client: c}
	c.IssueLinks = &IssueLinksService{client: c}
	c.Issues = &IssuesService{client: c, timeStats: timeStats}
	c.IssuesStatistics = &IssuesStatisticsService{client: c}
	c.Jobs = &JobsService{client: c}
	c.Keys = &KeysService{client: c}
	c.Labels = &LabelsService{client: c}
	c.License = &LicenseService{client: c}
	c.LicenseTemplates = &LicenseTemplatesService{client: c}
	c.ManagedLicenses = &ManagedLicensesService{client: c}
	c.MergeRequestApprovals = &MergeRequestApprovalsService{client: c}
	c.MergeRequests = &MergeRequestsService{client: c, timeStats: timeStats}
	c.Milestones = &MilestonesService{client: c}
	c.Namespaces = &NamespacesService{client: c}
	c.Notes = &NotesService{client: c}
	c.NotificationSettings = &NotificationSettingsService{client: c}
	c.Packages = &PackagesService{client: c}
	c.PagesDomains = &PagesDomainsService{client: c}
	c.PipelineSchedules = &PipelineSchedulesService{client: c}
	c.PipelineTriggers = &PipelineTriggersService{client: c}
	c.Pipelines = &PipelinesService{client: c}
	c.ProjectBadges = &ProjectBadgesService{client: c}
	c.ProjectAccessTokens = &ProjectAccessTokensService{client: c}
	c.ProjectCluster = &ProjectClustersService{client: c}
	c.ProjectImportExport = &ProjectImportExportService{client: c}
	c.ProjectMembers = &ProjectMembersService{client: c}
	c.ProjectMirrors = &ProjectMirrorService{client: c}
	c.ProjectSnippets = &ProjectSnippetsService{client: c}
	c.ProjectVariables = &ProjectVariablesService{client: c}
	c.Projects = &ProjectsService{client: c}
	c.ProtectedBranches = &ProtectedBranchesService{client: c}
	c.ProtectedEnvironments = &ProtectedEnvironmentsService{client: c}
	c.ProtectedTags = &ProtectedTagsService{client: c}
	c.ReleaseLinks = &ReleaseLinksService{client: c}
	c.Releases = &ReleasesService{client: c}
	c.Repositories = &RepositoriesService{client: c}
	c.RepositoryFiles = &RepositoryFilesService{client: c}
	c.ResourceLabelEvents = &ResourceLabelEventsService{client: c}
	c.ResourceStateEvents = &ResourceStateEventsService{client: c}
	c.Runners = &RunnersService{client: c}
	c.Search = &SearchService{client: c}
	c.Services = &ServicesService{client: c}
	c.Settings = &SettingsService{client: c}
	c.Sidekiq = &SidekiqService{client: c}
	c.Snippets = &SnippetsService{client: c}
	c.SystemHooks = &SystemHooksService{client: c}
	c.Tags = &TagsService{client: c}
	c.Todos = &TodosService{client: c}
	c.Users = &UsersService{client: c}
	c.Validate = &ValidateService{client: c}
	c.Version = &VersionService{client: c}
	c.Wikis = &WikisService{client: c}
	return c, nil
}
func (c *Client) retryHTTPCheck(ctx context.Context, resp *http.Response, err error) (bool, error) {
	if ctx.Err() != nil {
		return false, ctx.Err()
	}
	if err != nil {
		return false, err
	}
	if !c.disableRetries && (resp.StatusCode == 429 || resp.StatusCode >= 500) {
		return true, nil
	}
	return false, nil
}
func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
	if resp != nil && resp.StatusCode == 429 {
		return rateLimitBackoff(min, max, attemptNum, resp)
	}
	min = 700 * time.Millisecond
	max = 900 * time.Millisecond
	return retryablehttp.LinearJitterBackoff(min, max, attemptNum, resp)
}
func rateLimitBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
	jitter := time.Duration(rnd.Float64() * float64(max-min))
	if resp != nil {
		if v := resp.Header.Get(headerRateReset); v != "" {
			if reset, _ := strconv.ParseInt(v, 10, 64); reset > 0 {
				if wait := time.Until(time.Unix(reset, 0)); wait > min {
					min = wait
				}
			}
		}
	}
	return min + jitter
}
func (c *Client) configureLimiter(ctx context.Context) error {
	limit := rate.Inf
	burst := 0
	defer func() {
		c.limiter = rate.NewLimiter(limit, burst)
	}()
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL.String(), nil)
	if err != nil {
		return err
	}
	resp, err := c.client.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	resp.Body.Close()
	if v := resp.Header.Get(headerRateLimit); v != "" {
		if rateLimit, _ := strconv.ParseFloat(v, 64); rateLimit > 0 {
			rateLimit /= 60
			limit = rate.Limit(rateLimit * 0.66)
			burst = int(rateLimit * 0.33)
		}
	}
	return nil
}
func (c *Client) BaseURL() *url.URL {
	u := *c.baseURL
	return &u
}
func (c *Client) setBaseURL(urlStr string) error {
	if !strings.HasSuffix(urlStr, "/") {
		urlStr += "/"
	}
	baseURL, err := url.Parse(urlStr)
	if err != nil {
		return err
	}
	if !strings.HasSuffix(baseURL.Path, apiVersionPath) {
		baseURL.Path += apiVersionPath
	}
	c.baseURL = baseURL
	return nil
}
func (c *Client) NewRequest(method, path string, opt interface{}, options []RequestOptionFunc) (*retryablehttp.Request, error) {
	u := *c.baseURL
	unescaped, err := url.PathUnescape(path)
	if err != nil {
		return nil, err
	}
	u.RawPath = c.baseURL.Path + path
	u.Path = c.baseURL.Path + unescaped
	reqHeaders := make(http.Header)
	reqHeaders.Set("Accept", "application/json")
	if c.UserAgent != "" {
		reqHeaders.Set("User-Agent", c.UserAgent)
	}
	var body interface{}
	switch {
	case method == http.MethodPost || method == http.MethodPut:
		reqHeaders.Set("Content-Type", "application/json")
		if opt != nil {
			body, err = json.Marshal(opt)
			if err != nil {
				return nil, err
			}
		}
	case opt != nil:
		q, err := query.Values(opt)
		if err != nil {
			return nil, err
		}
		u.RawQuery = q.Encode()
	}
	req, err := retryablehttp.NewRequest(method, u.String(), body)
	if err != nil {
		return nil, err
	}
	for _, fn := range options {
		if fn == nil {
			continue
		}
		if err := fn(req); err != nil {
			return nil, err
		}
	}
	for k, v := range reqHeaders {
		req.Header[k] = v
	}
	return req, nil
}
type Response struct {
	*http.Response
	TotalItems   int
	TotalPages   int
	ItemsPerPage int
	CurrentPage  int
	NextPage     int
	PreviousPage int
}
func newResponse(r *http.Response) *Response {
	response := &Response{Response: r}
	response.populatePageValues()
	return response
}
const (
	xTotal      = "X-Total"
	xTotalPages = "X-Total-Pages"
	xPerPage    = "X-Per-Page"
	xPage       = "X-Page"
	xNextPage   = "X-Next-Page"
	xPrevPage   = "X-Prev-Page"
)
func (r *Response) populatePageValues() {
	if totalItems := r.Response.Header.Get(xTotal); totalItems != "" {
		r.TotalItems, _ = strconv.Atoi(totalItems)
	}
	if totalPages := r.Response.Header.Get(xTotalPages); totalPages != "" {
		r.TotalPages, _ = strconv.Atoi(totalPages)
	}
	if itemsPerPage := r.Response.Header.Get(xPerPage); itemsPerPage != "" {
		r.ItemsPerPage, _ = strconv.Atoi(itemsPerPage)
	}
	if currentPage := r.Response.Header.Get(xPage); currentPage != "" {
		r.CurrentPage, _ = strconv.Atoi(currentPage)
	}
	if nextPage := r.Response.Header.Get(xNextPage); nextPage != "" {
		r.NextPage, _ = strconv.Atoi(nextPage)
	}
	if previousPage := r.Response.Header.Get(xPrevPage); previousPage != "" {
		r.PreviousPage, _ = strconv.Atoi(previousPage)
	}
}
func (c *Client) Do(req *retryablehttp.Request, v interface{}) (*Response, error) {
	c.configureLimiterOnce.Do(func() { c.configureLimiter(req.Context()) })
	err := c.limiter.Wait(req.Context())
	if err != nil {
		return nil, err
	}
	var basicAuthToken string
	switch c.authType {
	case basicAuth:
		c.tokenLock.RLock()
		basicAuthToken = c.token
		c.tokenLock.RUnlock()
		if basicAuthToken == "" {
			basicAuthToken, err = c.requestOAuthToken(req.Context(), basicAuthToken)
			if err != nil {
				return nil, err
			}
		}
		req.Header.Set("Authorization", "Bearer "+basicAuthToken)
	case oAuthToken:
		req.Header.Set("Authorization", "Bearer "+c.token)
	case privateToken:
		req.Header.Set("PRIVATE-TOKEN", c.token)
	}
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == http.StatusUnauthorized && c.authType == basicAuth {
		resp.Body.Close()
		if _, err := c.requestOAuthToken(req.Context(), basicAuthToken); err != nil {
			return nil, err
		}
		return c.Do(req, v)
	}
	defer resp.Body.Close()
	response := newResponse(resp)
	err = CheckResponse(resp)
	if err != nil {
		return response, err
	}
	if v != nil {
		if w, ok := v.(io.Writer); ok {
			_, err = io.Copy(w, resp.Body)
		} else {
			err = json.NewDecoder(resp.Body).Decode(v)
		}
	}
	return response, err
}
func (c *Client) requestOAuthToken(ctx context.Context, token string) (string, error) {
	c.tokenLock.Lock()
	defer c.tokenLock.Unlock()
	if c.token != token {
		return c.token, nil
	}
	config := &oauth2.Config{
		Endpoint: oauth2.Endpoint{
			AuthURL:  strings.TrimSuffix(c.baseURL.String(), apiVersionPath) + "oauth/authorize",
			TokenURL: strings.TrimSuffix(c.baseURL.String(), apiVersionPath) + "oauth/token",
		},
	}
	ctx = context.WithValue(ctx, oauth2.HTTPClient, c.client.HTTPClient)
	t, err := config.PasswordCredentialsToken(ctx, c.username, c.password)
	if err != nil {
		return "", err
	}
	c.token = t.AccessToken
	return c.token, nil
}
func parseID(id interface{}) (string, error) {
	switch v := id.(type) {
	case int:
		return strconv.Itoa(v), nil
	case string:
		return v, nil
	default:
		return "", fmt.Errorf("invalid ID type %#v, the ID must be an int or a string", id)
	}
}
func pathEscape(s string) string {
	return strings.Replace(url.PathEscape(s), ".", "%2E", -1)
}
type ErrorResponse struct {
	Body     []byte
	Response *http.Response
	Message  string
}
func (e *ErrorResponse) Error() string {
	path, _ := url.QueryUnescape(e.Response.Request.URL.Path)
	u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, path)
	return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message)
}