x

Programs, configuration and documentation that don't fit anywhere else
Log | Files | Refs | README | LICENSE

bitbucket.go (2678B)


      1 package main
      2 
      3 import (
      4 	"encoding/json"
      5 	"errors"
      6 	"fmt"
      7 	"net/http"
      8 	"net/url"
      9 	"path"
     10 	"time"
     11 )
     12 
     13 // https://developer.atlassian.com/cloud/bitbucket/rest/
     14 
     15 const apiRoot string = "https://api.bitbucket.org/2.0/"
     16 
     17 var errNotExist = errors.New("no such repository")
     18 
     19 type Repository struct {
     20 	Name        string
     21 	Description string
     22 }
     23 
     24 type PullRequest struct {
     25 	ID          int
     26 	Title       string
     27 	Description string
     28 	Summary     struct {
     29 		Raw  string
     30 		HTML string
     31 	}
     32 	Created time.Time `json:"created_on"`
     33 	Updated time.Time `json:"updated_on"`
     34 	Author  struct {
     35 		Type        string
     36 		DisplayName string `json:"display_name"`
     37 	}
     38 	Links struct {
     39 		Self struct {
     40 			HRef string
     41 		}
     42 		HTML struct {
     43 			HRef string
     44 		}
     45 	}
     46 	State string
     47 }
     48 
     49 type Client struct {
     50 	Username, Password string
     51 	*http.Client
     52 }
     53 
     54 func (c *Client) Do(req *http.Request) (*http.Response, error) {
     55 	req.Header.Set("Accept", "application/json")
     56 	req.SetBasicAuth(c.Username, c.Password)
     57 	return c.Client.Do(req)
     58 }
     59 
     60 func (c *Client) PullRequests(workspace, repo string) ([]PullRequest, error) {
     61 	u, err := url.Parse(apiRoot + path.Join("repositories", workspace, repo, "pullrequests"))
     62 	if err != nil {
     63 		return nil, err
     64 	}
     65 	q := u.Query()
     66 	q.Add("state", "OPEN")
     67 	q.Add("state", "MERGED")
     68 	q.Add("state", "DECLINED")
     69 	u.RawQuery = q.Encode()
     70 	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
     71 	if err != nil {
     72 		return nil, err
     73 	}
     74 	resp, err := c.Do(req)
     75 	if err != nil {
     76 		return nil, err
     77 	}
     78 	defer resp.Body.Close()
     79 	if resp.StatusCode == http.StatusNotFound {
     80 		return nil, errNotExist
     81 	} else if resp.StatusCode != http.StatusOK {
     82 		return nil, fmt.Errorf("non-ok response status: %s", resp.Status)
     83 	}
     84 	v := struct {
     85 		Values []PullRequest
     86 	}{}
     87 	if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
     88 		return nil, err
     89 	}
     90 	return v.Values, nil
     91 }
     92 
     93 func (c *Client) Repositories(workspace string) ([]Repository, error) {
     94 	var repos []Repository
     95 	type results struct {
     96 		Next   string
     97 		Values []Repository
     98 	}
     99 	next := apiRoot + path.Join("repositories", workspace)
    100 	for next != "" {
    101 		req, err := http.NewRequest(http.MethodGet, next, nil)
    102 		if err != nil {
    103 			return repos, err
    104 		}
    105 		resp, err := c.Do(req)
    106 		if err != nil {
    107 			return repos, err
    108 		}
    109 		if resp.StatusCode == http.StatusNotFound {
    110 			return repos, fmt.Errorf("no such workspace")
    111 		} else if resp.StatusCode > 399 {
    112 			return repos, fmt.Errorf("non-ok status %s", resp.Status)
    113 		}
    114 		var v results
    115 		if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
    116 			return repos, fmt.Errorf("decode repositories: %w", err)
    117 		}
    118 		resp.Body.Close()
    119 		next = v.Next
    120 		repos = append(repos, v.Values...)
    121 	}
    122 	return repos, nil
    123 }