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 }