http.go (4912B)
1 package jira 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "net/url" 10 "os" 11 "path" 12 "strings" 13 ) 14 15 type Client struct { 16 *http.Client 17 Debug bool 18 Username, Password string 19 APIRoot *url.URL 20 } 21 22 type errorResponse struct { 23 Errors map[string]string 24 } 25 26 func (resp errorResponse) Error() string { 27 errs := make([]string, len(resp.Errors)) 28 var i int 29 for k, v := range resp.Errors { 30 errs[i] = fmt.Sprintf("%s: %s", k, v) 31 i++ 32 } 33 return strings.Join(errs, "; ") 34 } 35 36 func (c *Client) Projects() ([]Project, error) { 37 u := *c.APIRoot 38 u.Path = path.Join(u.Path, "project") 39 var p []Project 40 err := c.getJSON(u.String(), &p) 41 return p, err 42 } 43 44 func (c *Client) Project(name string) (*Project, error) { 45 u := fmt.Sprintf("%s/project/%s", c.APIRoot, name) 46 var p Project 47 err := c.getJSON(u, &p) 48 return &p, err 49 } 50 51 func (c *Client) Issues(project string) ([]Issue, error) { 52 q := fmt.Sprintf("project = %q", project) 53 return c.SearchIssues(q) 54 } 55 56 func (c *Client) SearchIssues(query string) ([]Issue, error) { 57 u := *c.APIRoot 58 u.Path = path.Join(u.Path, "search") 59 q := make(url.Values) 60 q.Add("jql", query) 61 u.RawQuery = q.Encode() 62 resp, err := c.get(u.String()) 63 if err != nil { 64 return nil, err 65 } 66 if resp.StatusCode == http.StatusBadRequest { 67 return nil, fmt.Errorf("bad query") 68 } 69 if resp.StatusCode != http.StatusOK { 70 return nil, fmt.Errorf("non-ok status: %s", resp.Status) 71 } 72 t := struct { 73 Issues []Issue 74 }{} 75 if err := json.NewDecoder(resp.Body).Decode(&t); err != nil { 76 return nil, fmt.Errorf("decode issues: %w", err) 77 } 78 return t.Issues, nil 79 } 80 81 func (c *Client) CheckIssue(name string) (bool, error) { 82 u := *c.APIRoot 83 u.Path = path.Join(u.Path, "issue", name) 84 req, err := http.NewRequest(http.MethodHead, u.String(), nil) 85 if err != nil { 86 return false, err 87 } 88 resp, err := c.do(req) 89 if err != nil { 90 return false, err 91 } 92 if resp.StatusCode == http.StatusOK { 93 return true, nil 94 } 95 return false, nil 96 } 97 98 func (c *Client) Issue(name string) (*Issue, error) { 99 u := *c.APIRoot 100 u.Path = path.Join(u.Path, "issue", name) 101 var is Issue 102 err := c.getJSON(u.String(), &is) 103 return &is, err 104 } 105 106 func (c *Client) checkComment(ikey, id string) (bool, error) { 107 u := *c.APIRoot 108 u.Path = path.Join(u.Path, "issue", ikey, "comment", id) 109 req, err := http.NewRequest(http.MethodHead, u.String(), nil) 110 if err != nil { 111 return false, err 112 } 113 resp, err := c.do(req) 114 if err != nil { 115 return false, err 116 } 117 if resp.StatusCode == http.StatusOK { 118 return true, nil 119 } 120 return false, nil 121 } 122 123 func (c *Client) Comment(ikey, id string) (*Comment, error) { 124 u := *c.APIRoot 125 u.Path = path.Join(u.Path, "issue", ikey, "comment", id) 126 var com Comment 127 err := c.getJSON(u.String(), &com) 128 return &com, err 129 } 130 131 func (c *Client) CreateIssue(issue Issue) error { 132 u := *c.APIRoot 133 u.Path = path.Join(u.Path, "issue") 134 resp, err := c.postJSON(u.String(), &issue) 135 if err != nil { 136 return fmt.Errorf("post json: %w", err) 137 } 138 defer resp.Body.Close() 139 if resp.StatusCode >= http.StatusBadRequest { 140 var eresp errorResponse 141 if err := json.NewDecoder(resp.Body).Decode(&eresp); err != nil { 142 return fmt.Errorf("status %s: decode error response: %w", resp.Status, err) 143 } 144 return eresp 145 } 146 return nil 147 } 148 149 func (c *Client) PostComment(issueKey string, body io.Reader) error { 150 b, err := io.ReadAll(body) 151 if err != nil { 152 return fmt.Errorf("read body: %w", err) 153 } 154 cm := Comment{Body: string(b)} 155 u := fmt.Sprintf("%s/issue/%s/comment", c.APIRoot, issueKey) 156 resp, err := c.postJSON(u, &cm) 157 if err != nil { 158 return fmt.Errorf("post json: %w", err) 159 } 160 if resp.StatusCode >= http.StatusBadRequest { 161 return fmt.Errorf("non-ok status: %s", resp.Status) 162 } 163 return nil 164 } 165 166 func (c *Client) getJSON(url string, v any) error { 167 resp, err := c.get(url) 168 if err != nil { 169 return err 170 } 171 defer resp.Body.Close() 172 dec := json.NewDecoder(resp.Body) 173 if resp.StatusCode != http.StatusOK { 174 var eresp errorResponse 175 if err := dec.Decode(&eresp); err != nil { 176 return fmt.Errorf("status %s: decode error response: %w", resp.Status, err) 177 } 178 return eresp 179 } 180 return dec.Decode(v) 181 } 182 183 func (c *Client) get(url string) (*http.Response, error) { 184 req, err := http.NewRequest(http.MethodGet, url, nil) 185 if err != nil { 186 return nil, err 187 } 188 return c.do(req) 189 } 190 191 func (c *Client) postJSON(url string, v any) (*http.Response, error) { 192 body, err := json.Marshal(v) 193 if err != nil { 194 return nil, err 195 } 196 req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) 197 if err != nil { 198 return nil, err 199 } 200 req.Header.Set("Content-Type", "application/json") 201 return c.do(req) 202 } 203 204 func (c *Client) do(req *http.Request) (*http.Response, error) { 205 if c.Debug { 206 fmt.Fprintln(os.Stderr, req.Method, req.URL) 207 } 208 209 if c.Client == nil { 210 c.Client = http.DefaultClient 211 } 212 213 if c.Username != "" && c.Password != "" { 214 req.SetBasicAuth(c.Username, c.Password) 215 } 216 return c.Do(req) 217 }