You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-scm/scm/driver/bitbucket/bitbucket.go

199 lines
5.1 KiB
Go

// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package bitbucket implements a Bitbucket Cloud client.
package bitbucket
import (
"bytes"
"context"
"encoding/json"
"io"
"mime/multipart"
"net/url"
"strings"
"git.awesome-for.me/liuzhiguo/go-scm/scm"
)
// New returns a new Bitbucket API client.
func New(uri string) (*scm.Client, error) {
base, err := url.Parse(uri)
if err != nil {
return nil, err
}
if !strings.HasSuffix(base.Path, "/") {
base.Path = base.Path + "/"
}
client := &wrapper{new(scm.Client)}
client.BaseURL = base
// initialize services
client.Driver = scm.DriverBitbucket
client.Linker = &linker{"https://bitbucket.org/"}
client.Contents = &contentService{client}
client.Git = &gitService{client}
client.Issues = &issueService{client}
client.Milestones = &milestoneService{client}
client.Organizations = &organizationService{client}
client.PullRequests = &pullService{client}
client.Repositories = &repositoryService{client}
client.Releases = &releaseService{client}
client.Reviews = &reviewService{client}
client.Users = &userService{client}
client.Webhooks = &webhookService{client}
return client.Client, nil
}
// NewDefault returns a new Bitbucket API client using the
// default api.bitbucket.org address.
func NewDefault() *scm.Client {
client, _ := New("https://api.bitbucket.org")
return client
}
// wraper wraps the Client to provide high level helper functions
// for making http requests and unmarshaling the response.
type wrapper struct {
*scm.Client
}
// do wraps the Client.Do function by creating the Request and
// unmarshalling the response.
func (c *wrapper) do(ctx context.Context, method, path string, in, out interface{}) (*scm.Response, error) {
req := &scm.Request{
Method: method,
Path: path,
}
// if we are posting or putting data, we need to
// write it to the body of the request.
if in != nil {
// create or update content
switch content := in.(type) {
case *contentCreateUpdate:
// add the content to the multipart
myReader := strings.NewReader(string(content.Content))
var b bytes.Buffer
w := multipart.NewWriter(&b)
var fw io.Writer
fw, _ = w.CreateFormFile(content.Files, "")
_, _ = io.Copy(fw, myReader)
// add the other fields
if content.Message != "" {
_ = w.WriteField("message", content.Message)
}
if content.Branch != "" {
_ = w.WriteField("branch", content.Branch)
}
if content.Sha != "" {
_ = w.WriteField("parents", content.Sha)
}
if content.Author != "" {
_ = w.WriteField("author", content.Author)
}
w.Close()
// write the multipart response to the body
req.Body = &b
// write the content type that contains the length of the multipart
req.Header = map[string][]string{
"Content-Type": {w.FormDataContentType()},
}
case *contentDelete:
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
fw, err := writer.CreateFormField("files")
_, err = io.Copy(fw, strings.NewReader(content.File))
if err != nil {
return nil, err
}
fw, err = writer.CreateFormField("message")
_, err = io.Copy(fw, strings.NewReader(content.Message))
if err != nil {
return nil, err
}
fw, err = writer.CreateFormField("author")
_, err = io.Copy(fw, strings.NewReader(content.Author))
if err != nil {
return nil, err
}
if content.Branch != "" {
fw, err = writer.CreateFormField("branch")
_, err = io.Copy(fw, strings.NewReader(content.Branch))
if err != nil {
return nil, err
}
}
writer.Close()
req.Body = bytes.NewReader(body.Bytes())
req.Header = map[string][]string{
"Content-Type": {writer.FormDataContentType()},
}
default:
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(in)
req.Header = map[string][]string{
"Content-Type": {"application/json"},
}
req.Body = buf
}
}
// execute the http request
res, err := c.Client.Do(ctx, req)
if err != nil {
return nil, err
}
defer res.Body.Close()
// if an error is encountered, unmarshal and return the
// error response.
if res.Status == 401 {
return res, scm.ErrNotAuthorized
} else if res.Status > 300 {
err := new(Error)
json.NewDecoder(res.Body).Decode(err)
return res, err
}
if out == nil {
return res, nil
}
// if raw output is expected, copy to the provided
// buffer and exit.
if w, ok := out.(io.Writer); ok {
io.Copy(w, res.Body)
return res, nil
}
// if a json response is expected, parse and return
// the json response.
return res, json.NewDecoder(res.Body).Decode(out)
}
// pagination represents Bitbucket pagination properties
// embedded in list responses.
type pagination struct {
PageLen int `json:"pagelen"`
Page int `json:"page"`
Size int `json:"size"`
Next string `json:"next"`
}
// link represents Bitbucket link properties embedded in responses.
type link struct {
Href string `json:"href"`
}
// Error represents a Bitbucket error.
type Error struct {
Type string `json:"type"`
Data struct {
Message string `json:"message"`
} `json:"error"`
}
func (e *Error) Error() string {
return e.Data.Message
}