issues

Github, Jira, Gitlab issues from Plan 9's acme
Log | Files | Refs | README | LICENSE

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 }