mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 13:28:25 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1141 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1141 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
//
 | 
						|
// Copyright 2017, Sander van Harmelen
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
//
 | 
						|
 | 
						|
// Package gitlab implements a GitLab API client.
 | 
						|
package gitlab
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"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"
 | 
						|
)
 | 
						|
 | 
						|
// authType represents an authentication type within GitLab.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
type authType int
 | 
						|
 | 
						|
// List of available authentication types.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
const (
 | 
						|
	basicAuth authType = iota
 | 
						|
	oAuthToken
 | 
						|
	privateToken
 | 
						|
)
 | 
						|
 | 
						|
// AccessLevelValue represents a permission level within GitLab.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html
 | 
						|
type AccessLevelValue int
 | 
						|
 | 
						|
// List of available access levels
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html
 | 
						|
const (
 | 
						|
	NoPermissions         AccessLevelValue = 0
 | 
						|
	GuestPermissions      AccessLevelValue = 10
 | 
						|
	ReporterPermissions   AccessLevelValue = 20
 | 
						|
	DeveloperPermissions  AccessLevelValue = 30
 | 
						|
	MaintainerPermissions AccessLevelValue = 40
 | 
						|
	OwnerPermissions      AccessLevelValue = 50
 | 
						|
 | 
						|
	// These are deprecated and should be removed in a future version
 | 
						|
	MasterPermissions AccessLevelValue = 40
 | 
						|
	OwnerPermission   AccessLevelValue = 50
 | 
						|
)
 | 
						|
 | 
						|
// BuildStateValue represents a GitLab build state.
 | 
						|
type BuildStateValue string
 | 
						|
 | 
						|
// These constants represent all valid build states.
 | 
						|
const (
 | 
						|
	Pending  BuildStateValue = "pending"
 | 
						|
	Created  BuildStateValue = "created"
 | 
						|
	Running  BuildStateValue = "running"
 | 
						|
	Success  BuildStateValue = "success"
 | 
						|
	Failed   BuildStateValue = "failed"
 | 
						|
	Canceled BuildStateValue = "canceled"
 | 
						|
	Skipped  BuildStateValue = "skipped"
 | 
						|
	Manual   BuildStateValue = "manual"
 | 
						|
)
 | 
						|
 | 
						|
// DeploymentStatusValue represents a Gitlab deployment status.
 | 
						|
type DeploymentStatusValue string
 | 
						|
 | 
						|
// These constants represent all valid deployment statuses.
 | 
						|
const (
 | 
						|
	DeploymentStatusCreated  DeploymentStatusValue = "created"
 | 
						|
	DeploymentStatusRunning  DeploymentStatusValue = "running"
 | 
						|
	DeploymentStatusSuccess  DeploymentStatusValue = "success"
 | 
						|
	DeploymentStatusFailed   DeploymentStatusValue = "failed"
 | 
						|
	DeploymentStatusCanceled DeploymentStatusValue = "canceled"
 | 
						|
)
 | 
						|
 | 
						|
// ISOTime represents an ISO 8601 formatted date
 | 
						|
type ISOTime time.Time
 | 
						|
 | 
						|
// ISO 8601 date format
 | 
						|
const iso8601 = "2006-01-02"
 | 
						|
 | 
						|
// MarshalJSON implements the json.Marshaler interface
 | 
						|
func (t ISOTime) MarshalJSON() ([]byte, error) {
 | 
						|
	if y := time.Time(t).Year(); y < 0 || y >= 10000 {
 | 
						|
		// ISO 8901 uses 4 digits for the years
 | 
						|
		return nil, errors.New("json: ISOTime year outside of range [0,9999]")
 | 
						|
	}
 | 
						|
 | 
						|
	b := make([]byte, 0, len(iso8601)+2)
 | 
						|
	b = append(b, '"')
 | 
						|
	b = time.Time(t).AppendFormat(b, iso8601)
 | 
						|
	b = append(b, '"')
 | 
						|
 | 
						|
	return b, nil
 | 
						|
}
 | 
						|
 | 
						|
// UnmarshalJSON implements the json.Unmarshaler interface
 | 
						|
func (t *ISOTime) UnmarshalJSON(data []byte) error {
 | 
						|
	// Ignore null, like in the main JSON package
 | 
						|
	if string(data) == "null" {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	isotime, err := time.Parse(`"`+iso8601+`"`, string(data))
 | 
						|
	*t = ISOTime(isotime)
 | 
						|
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// EncodeValues implements the query.Encoder interface
 | 
						|
func (t *ISOTime) EncodeValues(key string, v *url.Values) error {
 | 
						|
	if t == nil || (time.Time(*t)).IsZero() {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	v.Add(key, t.String())
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// String implements the Stringer interface
 | 
						|
func (t ISOTime) String() string {
 | 
						|
	return time.Time(t).Format(iso8601)
 | 
						|
}
 | 
						|
 | 
						|
// NotificationLevelValue represents a notification level.
 | 
						|
type NotificationLevelValue int
 | 
						|
 | 
						|
// String implements the fmt.Stringer interface.
 | 
						|
func (l NotificationLevelValue) String() string {
 | 
						|
	return notificationLevelNames[l]
 | 
						|
}
 | 
						|
 | 
						|
// MarshalJSON implements the json.Marshaler interface.
 | 
						|
func (l NotificationLevelValue) MarshalJSON() ([]byte, error) {
 | 
						|
	return json.Marshal(l.String())
 | 
						|
}
 | 
						|
 | 
						|
// UnmarshalJSON implements the json.Unmarshaler interface.
 | 
						|
func (l *NotificationLevelValue) UnmarshalJSON(data []byte) error {
 | 
						|
	var raw interface{}
 | 
						|
	if err := json.Unmarshal(data, &raw); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	switch raw := raw.(type) {
 | 
						|
	case float64:
 | 
						|
		*l = NotificationLevelValue(raw)
 | 
						|
	case string:
 | 
						|
		*l = notificationLevelTypes[raw]
 | 
						|
	case nil:
 | 
						|
		// No action needed.
 | 
						|
	default:
 | 
						|
		return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// List of valid notification levels.
 | 
						|
const (
 | 
						|
	DisabledNotificationLevel NotificationLevelValue = iota
 | 
						|
	ParticipatingNotificationLevel
 | 
						|
	WatchNotificationLevel
 | 
						|
	GlobalNotificationLevel
 | 
						|
	MentionNotificationLevel
 | 
						|
	CustomNotificationLevel
 | 
						|
)
 | 
						|
 | 
						|
var notificationLevelNames = [...]string{
 | 
						|
	"disabled",
 | 
						|
	"participating",
 | 
						|
	"watch",
 | 
						|
	"global",
 | 
						|
	"mention",
 | 
						|
	"custom",
 | 
						|
}
 | 
						|
 | 
						|
var notificationLevelTypes = map[string]NotificationLevelValue{
 | 
						|
	"disabled":      DisabledNotificationLevel,
 | 
						|
	"participating": ParticipatingNotificationLevel,
 | 
						|
	"watch":         WatchNotificationLevel,
 | 
						|
	"global":        GlobalNotificationLevel,
 | 
						|
	"mention":       MentionNotificationLevel,
 | 
						|
	"custom":        CustomNotificationLevel,
 | 
						|
}
 | 
						|
 | 
						|
// VisibilityValue represents a visibility level within GitLab.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
type VisibilityValue string
 | 
						|
 | 
						|
// List of available visibility levels.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
const (
 | 
						|
	PrivateVisibility  VisibilityValue = "private"
 | 
						|
	InternalVisibility VisibilityValue = "internal"
 | 
						|
	PublicVisibility   VisibilityValue = "public"
 | 
						|
)
 | 
						|
 | 
						|
// ProjectCreationLevelValue represents a project creation level within GitLab.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
type ProjectCreationLevelValue string
 | 
						|
 | 
						|
// List of available project creation levels.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
const (
 | 
						|
	NoOneProjectCreation      ProjectCreationLevelValue = "noone"
 | 
						|
	MaintainerProjectCreation ProjectCreationLevelValue = "maintainer"
 | 
						|
	DeveloperProjectCreation  ProjectCreationLevelValue = "developer"
 | 
						|
)
 | 
						|
 | 
						|
// SubGroupCreationLevelValue represents a sub group creation level within GitLab.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
type SubGroupCreationLevelValue string
 | 
						|
 | 
						|
// List of available sub group creation levels.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
const (
 | 
						|
	OwnerSubGroupCreationLevelValue      SubGroupCreationLevelValue = "owner"
 | 
						|
	MaintainerSubGroupCreationLevelValue SubGroupCreationLevelValue = "maintainer"
 | 
						|
)
 | 
						|
 | 
						|
// VariableTypeValue represents a variable type within GitLab.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
type VariableTypeValue string
 | 
						|
 | 
						|
// List of available variable types.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/
 | 
						|
const (
 | 
						|
	EnvVariableType  VariableTypeValue = "env_var"
 | 
						|
	FileVariableType VariableTypeValue = "file"
 | 
						|
)
 | 
						|
 | 
						|
// MergeMethodValue represents a project merge type within GitLab.
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method
 | 
						|
type MergeMethodValue string
 | 
						|
 | 
						|
// List of available merge type
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method
 | 
						|
const (
 | 
						|
	NoFastForwardMerge MergeMethodValue = "merge"
 | 
						|
	FastForwardMerge   MergeMethodValue = "ff"
 | 
						|
	RebaseMerge        MergeMethodValue = "rebase_merge"
 | 
						|
)
 | 
						|
 | 
						|
// EventTypeValue represents actions type for contribution events
 | 
						|
type EventTypeValue string
 | 
						|
 | 
						|
// List of available action type
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#action-types
 | 
						|
const (
 | 
						|
	CreatedEventType   EventTypeValue = "created"
 | 
						|
	UpdatedEventType   EventTypeValue = "updated"
 | 
						|
	ClosedEventType    EventTypeValue = "closed"
 | 
						|
	ReopenedEventType  EventTypeValue = "reopened"
 | 
						|
	PushedEventType    EventTypeValue = "pushed"
 | 
						|
	CommentedEventType EventTypeValue = "commented"
 | 
						|
	MergedEventType    EventTypeValue = "merged"
 | 
						|
	JoinedEventType    EventTypeValue = "joined"
 | 
						|
	LeftEventType      EventTypeValue = "left"
 | 
						|
	DestroyedEventType EventTypeValue = "destroyed"
 | 
						|
	ExpiredEventType   EventTypeValue = "expired"
 | 
						|
)
 | 
						|
 | 
						|
// EventTargetTypeValue represents actions type value for contribution events
 | 
						|
type EventTargetTypeValue string
 | 
						|
 | 
						|
// List of available action type
 | 
						|
//
 | 
						|
// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#target-types
 | 
						|
const (
 | 
						|
	IssueEventTargetType        EventTargetTypeValue = "issue"
 | 
						|
	MilestoneEventTargetType    EventTargetTypeValue = "milestone"
 | 
						|
	MergeRequestEventTargetType EventTargetTypeValue = "merge_request"
 | 
						|
	NoteEventTargetType         EventTargetTypeValue = "note"
 | 
						|
	ProjectEventTargetType      EventTargetTypeValue = "project"
 | 
						|
	SnippetEventTargetType      EventTargetTypeValue = "snippet"
 | 
						|
	UserEventTargetType         EventTargetTypeValue = "user"
 | 
						|
)
 | 
						|
 | 
						|
// A Client manages communication with the GitLab API.
 | 
						|
type Client struct {
 | 
						|
	// HTTP client used to communicate with the API.
 | 
						|
	client *retryablehttp.Client
 | 
						|
 | 
						|
	// Base URL for API requests. Defaults to the public GitLab API, but can be
 | 
						|
	// set to a domain endpoint to use with a self hosted GitLab server. baseURL
 | 
						|
	// should always be specified with a trailing slash.
 | 
						|
	baseURL *url.URL
 | 
						|
 | 
						|
	// disableRetries is used to disable the default retry logic.
 | 
						|
	disableRetries bool
 | 
						|
 | 
						|
	// configLimiter is used to make sure the limiter is configured exactly
 | 
						|
	// once and block all other calls until the initial (one) call is done.
 | 
						|
	configureLimiterOnce sync.Once
 | 
						|
 | 
						|
	// Limiter is used to limit API calls and prevent 429 responses.
 | 
						|
	limiter *rate.Limiter
 | 
						|
 | 
						|
	// Token type used to make authenticated API calls.
 | 
						|
	authType authType
 | 
						|
 | 
						|
	// Username and password used for basix authentication.
 | 
						|
	username, password string
 | 
						|
 | 
						|
	// Token used to make authenticated API calls.
 | 
						|
	token string
 | 
						|
 | 
						|
	// User agent used when communicating with the GitLab API.
 | 
						|
	UserAgent string
 | 
						|
 | 
						|
	// Services used for talking to different parts of the GitLab API.
 | 
						|
	AccessRequests        *AccessRequestsService
 | 
						|
	Applications          *ApplicationsService
 | 
						|
	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
 | 
						|
	Epics                 *EpicsService
 | 
						|
	Events                *EventsService
 | 
						|
	Features              *FeaturesService
 | 
						|
	GitIgnoreTemplates    *GitIgnoreTemplatesService
 | 
						|
	GroupBadges           *GroupBadgesService
 | 
						|
	GroupCluster          *GroupClustersService
 | 
						|
	GroupIssueBoards      *GroupIssueBoardsService
 | 
						|
	GroupLabels           *GroupLabelsService
 | 
						|
	GroupMembers          *GroupMembersService
 | 
						|
	GroupMilestones       *GroupMilestonesService
 | 
						|
	GroupVariables        *GroupVariablesService
 | 
						|
	Groups                *GroupsService
 | 
						|
	IssueLinks            *IssueLinksService
 | 
						|
	Issues                *IssuesService
 | 
						|
	Jobs                  *JobsService
 | 
						|
	Keys                  *KeysService
 | 
						|
	Labels                *LabelsService
 | 
						|
	License               *LicenseService
 | 
						|
	LicenseTemplates      *LicenseTemplatesService
 | 
						|
	MergeRequestApprovals *MergeRequestApprovalsService
 | 
						|
	MergeRequests         *MergeRequestsService
 | 
						|
	Milestones            *MilestonesService
 | 
						|
	Namespaces            *NamespacesService
 | 
						|
	Notes                 *NotesService
 | 
						|
	NotificationSettings  *NotificationSettingsService
 | 
						|
	PagesDomains          *PagesDomainsService
 | 
						|
	PipelineSchedules     *PipelineSchedulesService
 | 
						|
	PipelineTriggers      *PipelineTriggersService
 | 
						|
	Pipelines             *PipelinesService
 | 
						|
	ProjectBadges         *ProjectBadgesService
 | 
						|
	ProjectCluster        *ProjectClustersService
 | 
						|
	ProjectImportExport   *ProjectImportExportService
 | 
						|
	ProjectMembers        *ProjectMembersService
 | 
						|
	ProjectSnippets       *ProjectSnippetsService
 | 
						|
	ProjectVariables      *ProjectVariablesService
 | 
						|
	Projects              *ProjectsService
 | 
						|
	ProtectedBranches     *ProtectedBranchesService
 | 
						|
	ProtectedTags         *ProtectedTagsService
 | 
						|
	ReleaseLinks          *ReleaseLinksService
 | 
						|
	Releases              *ReleasesService
 | 
						|
	Repositories          *RepositoriesService
 | 
						|
	RepositoryFiles       *RepositoryFilesService
 | 
						|
	ResourceLabelEvents   *ResourceLabelEventsService
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
// ListOptions specifies the optional parameters to various List methods that
 | 
						|
// support pagination.
 | 
						|
type ListOptions struct {
 | 
						|
	// For paginated result sets, page of results to retrieve.
 | 
						|
	Page int `url:"page,omitempty" json:"page,omitempty"`
 | 
						|
 | 
						|
	// For paginated result sets, the number of results to include per page.
 | 
						|
	PerPage int `url:"per_page,omitempty" json:"per_page,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// NewClient returns a new GitLab API client. To use API methods which require
 | 
						|
// authentication, provide a valid private or personal token.
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
// NewBasicAuthClient returns a new GitLab API client. To use API methods which
 | 
						|
// require authentication, provide a valid username and password.
 | 
						|
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
 | 
						|
 | 
						|
	err = client.requestOAuthToken(context.Background())
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return client, nil
 | 
						|
}
 | 
						|
 | 
						|
// NewOAuthClient returns a new GitLab API client. To use API methods which
 | 
						|
// require authentication, provide a valid oauth token.
 | 
						|
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 (c *Client) requestOAuthToken(ctx context.Context) error {
 | 
						|
	config := &oauth2.Config{
 | 
						|
		Endpoint: oauth2.Endpoint{
 | 
						|
			AuthURL:  fmt.Sprintf("%s://%s/oauth/authorize", c.BaseURL().Scheme, c.BaseURL().Host),
 | 
						|
			TokenURL: fmt.Sprintf("%s://%s/oauth/token", c.BaseURL().Scheme, c.BaseURL().Host),
 | 
						|
		},
 | 
						|
	}
 | 
						|
	ctx = context.WithValue(ctx, oauth2.HTTPClient, c.client)
 | 
						|
	t, err := config.PasswordCredentialsToken(ctx, c.username, c.password)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	c.token = t.AccessToken
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func newClient(options ...ClientOptionFunc) (*Client, error) {
 | 
						|
	c := &Client{UserAgent: userAgent}
 | 
						|
 | 
						|
	// Configure the HTTP client.
 | 
						|
	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,
 | 
						|
	}
 | 
						|
 | 
						|
	// Set the default base URL.
 | 
						|
	c.setBaseURL(defaultBaseURL)
 | 
						|
 | 
						|
	// Apply any given client options.
 | 
						|
	for _, fn := range options {
 | 
						|
		if fn == nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if err := fn(c); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Create the internal timeStats service.
 | 
						|
	timeStats := &timeStatsService{client: c}
 | 
						|
 | 
						|
	// Create all the public services.
 | 
						|
	c.AccessRequests = &AccessRequestsService{client: c}
 | 
						|
	c.Applications = &ApplicationsService{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.Epics = &EpicsService{client: c}
 | 
						|
	c.Events = &EventsService{client: c}
 | 
						|
	c.Features = &FeaturesService{client: c}
 | 
						|
	c.GitIgnoreTemplates = &GitIgnoreTemplatesService{client: c}
 | 
						|
	c.GroupBadges = &GroupBadgesService{client: c}
 | 
						|
	c.GroupCluster = &GroupClustersService{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.Groups = &GroupsService{client: c}
 | 
						|
	c.IssueLinks = &IssueLinksService{client: c}
 | 
						|
	c.Issues = &IssuesService{client: c, timeStats: timeStats}
 | 
						|
	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.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.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.ProjectCluster = &ProjectClustersService{client: c}
 | 
						|
	c.ProjectImportExport = &ProjectImportExportService{client: c}
 | 
						|
	c.ProjectMembers = &ProjectMembersService{client: c}
 | 
						|
	c.ProjectSnippets = &ProjectSnippetsService{client: c}
 | 
						|
	c.ProjectVariables = &ProjectVariablesService{client: c}
 | 
						|
	c.Projects = &ProjectsService{client: c}
 | 
						|
	c.ProtectedBranches = &ProtectedBranchesService{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.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
 | 
						|
}
 | 
						|
 | 
						|
// retryHTTPCheck provides a callback for Client.CheckRetry which
 | 
						|
// will retry both rate limit (429) and server (>= 500) errors.
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
// retryHTTPBackoff provides a generic callback for Client.Backoff which
 | 
						|
// will pass through all calls based on the status code of the response.
 | 
						|
func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
 | 
						|
	// Use the rate limit backoff function when we are rate limited.
 | 
						|
	if resp != nil && resp.StatusCode == 429 {
 | 
						|
		return rateLimitBackoff(min, max, attemptNum, resp)
 | 
						|
	}
 | 
						|
 | 
						|
	// Set custom duration's when we experience a service interruption.
 | 
						|
	min = 700 * time.Millisecond
 | 
						|
	max = 900 * time.Millisecond
 | 
						|
 | 
						|
	return retryablehttp.LinearJitterBackoff(min, max, attemptNum, resp)
 | 
						|
}
 | 
						|
 | 
						|
// rateLimitBackoff provides a callback for Client.Backoff which will use the
 | 
						|
// RateLimit-Reset header to determine the time to wait. We add some jitter
 | 
						|
// to prevent a thundering herd.
 | 
						|
//
 | 
						|
// min and max are mainly used for bounding the jitter that will be added to
 | 
						|
// the reset time retrieved from the headers. But if the final wait time is
 | 
						|
// less then min, min will be used instead.
 | 
						|
func rateLimitBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
 | 
						|
	// rnd is used to generate pseudo-random numbers.
 | 
						|
	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
 | 
						|
 | 
						|
	// First create some jitter bounded by the min and max durations.
 | 
						|
	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 {
 | 
						|
				// Only update min if the given time to wait is longer.
 | 
						|
				if wait := time.Until(time.Unix(reset, 0)); wait > min {
 | 
						|
					min = wait
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return min + jitter
 | 
						|
}
 | 
						|
 | 
						|
// configureLimiter configures the rate limiter.
 | 
						|
func (c *Client) configureLimiter() error {
 | 
						|
	// Set default values for when rate limiting is disabled.
 | 
						|
	limit := rate.Inf
 | 
						|
	burst := 0
 | 
						|
 | 
						|
	defer func() {
 | 
						|
		// Create a new limiter using the calculated values.
 | 
						|
		c.limiter = rate.NewLimiter(limit, burst)
 | 
						|
	}()
 | 
						|
 | 
						|
	// Create a new request.
 | 
						|
	req, err := http.NewRequest("GET", c.baseURL.String(), nil)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// Make a single request to retrieve the rate limit headers.
 | 
						|
	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 {
 | 
						|
			// The rate limit is based on requests per minute, so for our limiter to
 | 
						|
			// work correctly we devide the limit by 60 to get the limit per second.
 | 
						|
			rateLimit /= 60
 | 
						|
			// Configure the limit and burst using a split of 2/3 for the limit and
 | 
						|
			// 1/3 for the burst. This enables clients to burst 1/3 of the allowed
 | 
						|
			// calls before the limiter kicks in. The remaining calls will then be
 | 
						|
			// spread out evenly using intervals of time.Second / limit which should
 | 
						|
			// prevent hitting the rate limit.
 | 
						|
			limit = rate.Limit(rateLimit * 0.66)
 | 
						|
			burst = int(rateLimit * 0.33)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// BaseURL return a copy of the baseURL.
 | 
						|
func (c *Client) BaseURL() *url.URL {
 | 
						|
	u := *c.baseURL
 | 
						|
	return &u
 | 
						|
}
 | 
						|
 | 
						|
// setBaseURL sets the base URL for API requests to a custom endpoint.
 | 
						|
func (c *Client) setBaseURL(urlStr string) error {
 | 
						|
	// Make sure the given URL end with a slash
 | 
						|
	if !strings.HasSuffix(urlStr, "/") {
 | 
						|
		urlStr += "/"
 | 
						|
	}
 | 
						|
 | 
						|
	baseURL, err := url.Parse(urlStr)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if !strings.HasSuffix(baseURL.Path, apiVersionPath) {
 | 
						|
		baseURL.Path += apiVersionPath
 | 
						|
	}
 | 
						|
 | 
						|
	// Update the base URL of the client.
 | 
						|
	c.baseURL = baseURL
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// NewRequest creates an API request. A relative URL path can be provided in
 | 
						|
// path, in which case it is resolved relative to the base URL of the Client.
 | 
						|
// Relative URL paths should always be specified without a preceding slash. If
 | 
						|
// specified, the value pointed to by body is JSON encoded and included as the
 | 
						|
// request body.
 | 
						|
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
 | 
						|
	}
 | 
						|
 | 
						|
	// Set the encoded path data
 | 
						|
	u.RawPath = c.baseURL.Path + path
 | 
						|
	u.Path = c.baseURL.Path + unescaped
 | 
						|
 | 
						|
	// Create a request specific headers map.
 | 
						|
	reqHeaders := make(http.Header)
 | 
						|
	reqHeaders.Set("Accept", "application/json")
 | 
						|
 | 
						|
	switch c.authType {
 | 
						|
	case basicAuth, oAuthToken:
 | 
						|
		reqHeaders.Set("Authorization", "Bearer "+c.token)
 | 
						|
	case privateToken:
 | 
						|
		reqHeaders.Set("PRIVATE-TOKEN", c.token)
 | 
						|
	}
 | 
						|
 | 
						|
	if c.UserAgent != "" {
 | 
						|
		reqHeaders.Set("User-Agent", c.UserAgent)
 | 
						|
	}
 | 
						|
 | 
						|
	var body interface{}
 | 
						|
	switch {
 | 
						|
	case method == "POST" || method == "PUT":
 | 
						|
		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
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Set the request specific headers.
 | 
						|
	for k, v := range reqHeaders {
 | 
						|
		req.Header[k] = v
 | 
						|
	}
 | 
						|
 | 
						|
	return req, nil
 | 
						|
}
 | 
						|
 | 
						|
// Response is a GitLab API response. This wraps the standard http.Response
 | 
						|
// returned from GitLab and provides convenient access to things like
 | 
						|
// pagination links.
 | 
						|
type Response struct {
 | 
						|
	*http.Response
 | 
						|
 | 
						|
	// These fields provide the page values for paginating through a set of
 | 
						|
	// results. Any or all of these may be set to the zero value for
 | 
						|
	// responses that are not part of a paginated set, or for which there
 | 
						|
	// are no additional pages.
 | 
						|
	TotalItems   int
 | 
						|
	TotalPages   int
 | 
						|
	ItemsPerPage int
 | 
						|
	CurrentPage  int
 | 
						|
	NextPage     int
 | 
						|
	PreviousPage int
 | 
						|
}
 | 
						|
 | 
						|
// newResponse creates a new Response for the provided http.Response.
 | 
						|
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"
 | 
						|
)
 | 
						|
 | 
						|
// populatePageValues parses the HTTP Link response headers and populates the
 | 
						|
// various pagination link values in the Response.
 | 
						|
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)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Do sends an API request and returns the API response. The API response is
 | 
						|
// JSON decoded and stored in the value pointed to by v, or returned as an
 | 
						|
// error if an API error has occurred. If v implements the io.Writer
 | 
						|
// interface, the raw response body will be written to v, without attempting to
 | 
						|
// first decode it.
 | 
						|
func (c *Client) Do(req *retryablehttp.Request, v interface{}) (*Response, error) {
 | 
						|
	// If not yet configured, try to configure the rate limiter. Fail
 | 
						|
	// silently as the limiter will be disabled in case of an error.
 | 
						|
	c.configureLimiterOnce.Do(func() { c.configureLimiter() })
 | 
						|
 | 
						|
	// Wait will block until the limiter can obtain a new token.
 | 
						|
	if err := c.limiter.Wait(req.Context()); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err := c.client.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	if resp.StatusCode == http.StatusUnauthorized && c.authType == basicAuth {
 | 
						|
		err = c.requestOAuthToken(req.Context())
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return c.Do(req, v)
 | 
						|
	}
 | 
						|
 | 
						|
	response := newResponse(resp)
 | 
						|
 | 
						|
	err = CheckResponse(resp)
 | 
						|
	if err != nil {
 | 
						|
		// Even though there was an error, we still return the response
 | 
						|
		// in case the caller wants to inspect it further.
 | 
						|
		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
 | 
						|
}
 | 
						|
 | 
						|
// Helper function to accept and format both the project ID or name as project
 | 
						|
// identifier for all API calls.
 | 
						|
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)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Helper function to escape a project identifier.
 | 
						|
func pathEscape(s string) string {
 | 
						|
	return strings.Replace(url.PathEscape(s), ".", "%2E", -1)
 | 
						|
}
 | 
						|
 | 
						|
// An ErrorResponse reports one or more errors caused by an API request.
 | 
						|
//
 | 
						|
// GitLab API docs:
 | 
						|
// https://docs.gitlab.com/ce/api/README.html#data-validation-and-error-reporting
 | 
						|
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)
 | 
						|
}
 | 
						|
 | 
						|
// CheckResponse checks the API response for errors, and returns them if present.
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
// Format:
 | 
						|
// {
 | 
						|
//     "message": {
 | 
						|
//         "<property-name>": [
 | 
						|
//             "<error-message>",
 | 
						|
//             "<error-message>",
 | 
						|
//             ...
 | 
						|
//         ],
 | 
						|
//         "<embed-entity>": {
 | 
						|
//             "<property-name>": [
 | 
						|
//                 "<error-message>",
 | 
						|
//                 "<error-message>",
 | 
						|
//                 ...
 | 
						|
//             ],
 | 
						|
//         }
 | 
						|
//     },
 | 
						|
//     "error": "<error-message>"
 | 
						|
// }
 | 
						|
func parseError(raw interface{}) string {
 | 
						|
	switch raw := raw.(type) {
 | 
						|
	case string:
 | 
						|
		return raw
 | 
						|
 | 
						|
	case []interface{}:
 | 
						|
		var errs []string
 | 
						|
		for _, v := range raw {
 | 
						|
			errs = append(errs, parseError(v))
 | 
						|
		}
 | 
						|
		return fmt.Sprintf("[%s]", strings.Join(errs, ", "))
 | 
						|
 | 
						|
	case map[string]interface{}:
 | 
						|
		var errs []string
 | 
						|
		for k, v := range raw {
 | 
						|
			errs = append(errs, fmt.Sprintf("{%s: %s}", k, parseError(v)))
 | 
						|
		}
 | 
						|
		sort.Strings(errs)
 | 
						|
		return strings.Join(errs, ", ")
 | 
						|
 | 
						|
	default:
 | 
						|
		return fmt.Sprintf("failed to parse unexpected error type: %T", raw)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Bool is a helper routine that allocates a new bool value
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func Bool(v bool) *bool {
 | 
						|
	p := new(bool)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// Int is a helper routine that allocates a new int32 value
 | 
						|
// to store v and returns a pointer to it, but unlike Int32
 | 
						|
// its argument value is an int.
 | 
						|
func Int(v int) *int {
 | 
						|
	p := new(int)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// String is a helper routine that allocates a new string value
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func String(v string) *string {
 | 
						|
	p := new(string)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// Time is a helper routine that allocates a new time.Time value
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func Time(v time.Time) *time.Time {
 | 
						|
	p := new(time.Time)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// AccessLevel is a helper routine that allocates a new AccessLevelValue
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func AccessLevel(v AccessLevelValue) *AccessLevelValue {
 | 
						|
	p := new(AccessLevelValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// BuildState is a helper routine that allocates a new BuildStateValue
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func BuildState(v BuildStateValue) *BuildStateValue {
 | 
						|
	p := new(BuildStateValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// DeploymentStatus is a helper routine that allocates a new
 | 
						|
// DeploymentStatusValue to store v and returns a pointer to it.
 | 
						|
func DeploymentStatus(v DeploymentStatusValue) *DeploymentStatusValue {
 | 
						|
	p := new(DeploymentStatusValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// NotificationLevel is a helper routine that allocates a new NotificationLevelValue
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue {
 | 
						|
	p := new(NotificationLevelValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// VariableType is a helper routine that allocates a new VariableTypeValue
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func VariableType(v VariableTypeValue) *VariableTypeValue {
 | 
						|
	p := new(VariableTypeValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// Visibility is a helper routine that allocates a new VisibilityValue
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func Visibility(v VisibilityValue) *VisibilityValue {
 | 
						|
	p := new(VisibilityValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// ProjectCreationLevel is a helper routine that allocates a new ProjectCreationLevelValue
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func ProjectCreationLevel(v ProjectCreationLevelValue) *ProjectCreationLevelValue {
 | 
						|
	p := new(ProjectCreationLevelValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// SubGroupCreationLevel is a helper routine that allocates a new SubGroupCreationLevelValue
 | 
						|
// to store v and returns a pointer to it.
 | 
						|
func SubGroupCreationLevel(v SubGroupCreationLevelValue) *SubGroupCreationLevelValue {
 | 
						|
	p := new(SubGroupCreationLevelValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// MergeMethod is a helper routine that allocates a new MergeMethod
 | 
						|
// to sotre v and returns a pointer to it.
 | 
						|
func MergeMethod(v MergeMethodValue) *MergeMethodValue {
 | 
						|
	p := new(MergeMethodValue)
 | 
						|
	*p = v
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// BoolValue is a boolean value with advanced json unmarshaling features.
 | 
						|
type BoolValue bool
 | 
						|
 | 
						|
// UnmarshalJSON allows 1 and 0 to be considered as boolean values
 | 
						|
// Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/50122
 | 
						|
func (t *BoolValue) UnmarshalJSON(b []byte) error {
 | 
						|
	switch string(b) {
 | 
						|
	case `"1"`:
 | 
						|
		*t = true
 | 
						|
		return nil
 | 
						|
	case `"0"`:
 | 
						|
		*t = false
 | 
						|
		return nil
 | 
						|
	default:
 | 
						|
		var v bool
 | 
						|
		err := json.Unmarshal(b, &v)
 | 
						|
		*t = BoolValue(v)
 | 
						|
		return err
 | 
						|
	}
 | 
						|
}
 |