issues

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

client.go (3748B)


      1 package main
      2 
      3 import (
      4 	"bytes"
      5 	"encoding/json"
      6 	"fmt"
      7 	"io"
      8 	"log"
      9 	"net/http"
     10 	"net/url"
     11 	"path"
     12 	"strconv"
     13 	"time"
     14 )
     15 
     16 const GitlabHosted string = "https://gitlab.com/api/v4"
     17 
     18 type gError struct {
     19 	Message any
     20 }
     21 
     22 func (e gError) Error() string {
     23 	switch v := e.Message.(type) {
     24 	case string:
     25 		return v
     26 	}
     27 	return "unknown"
     28 }
     29 
     30 type Issue struct {
     31 	ID      int       `json:"iid"`
     32 	Title   string    `json:"title"`
     33 	Created time.Time `json:"created_at"`
     34 	Updated time.Time `json:"updated_at"`
     35 	Closed  time.Time `json:"closed_at"`
     36 	State   string    `json:"state"`
     37 	Author  struct {
     38 		ID       int    `json:"id"`
     39 		Username string `json:"username"`
     40 	}
     41 	Description string   `json:"description"`
     42 	Labels      []string `json:"labels"`
     43 	URL         string   `json:"web_url"`
     44 }
     45 
     46 type Client struct {
     47 	*http.Client
     48 	BaseURL string
     49 	Token   string
     50 }
     51 
     52 func (c *Client) Issues(project string, search map[string]string) ([]Issue, error) {
     53 	p := path.Join("projects", url.PathEscape(project), "issues")
     54 	resp, err := c.get(p)
     55 	if err != nil {
     56 		return nil, err
     57 	}
     58 	defer resp.Body.Close()
     59 	var issues []Issue
     60 	if err := json.NewDecoder(resp.Body).Decode(&issues); err != nil {
     61 		return nil, fmt.Errorf("decode response: %w", err)
     62 	}
     63 	return issues, nil
     64 }
     65 
     66 func (c *Client) Issue(project string, id int) (*Issue, error) {
     67 	p := path.Join("projects", url.PathEscape(project), "issues", strconv.Itoa(id))
     68 	resp, err := c.get(p)
     69 	if err != nil {
     70 		return nil, err
     71 	}
     72 	var issue Issue
     73 	if err := json.NewDecoder(resp.Body).Decode(&issue); err != nil {
     74 		return nil, fmt.Errorf("decode response: %w", err)
     75 	}
     76 	return &issue, nil
     77 }
     78 
     79 func (c *Client) Create(project, title, desc string) (*Issue, error) {
     80 	m := map[string]string{
     81 		"title":       title,
     82 		"description": desc,
     83 	}
     84 	b, err := json.Marshal(m)
     85 	if err != nil {
     86 		return nil, err
     87 	}
     88 	p := path.Join("projects", url.PathEscape(project), "issues")
     89 	resp, err := c.post(p, "application/json", bytes.NewReader(b))
     90 	if err != nil {
     91 		return nil, err
     92 	}
     93 	var issue Issue
     94 	if err := json.NewDecoder(resp.Body).Decode(&issue); err != nil {
     95 		return nil, fmt.Errorf("decode created issue: %w", err)
     96 	}
     97 	return &issue, nil
     98 }
     99 
    100 func (c *Client) get(path string) (*http.Response, error) {
    101 	if c.BaseURL == "" {
    102 		c.BaseURL = GitlabHosted
    103 	}
    104 	u := fmt.Sprintf("%s/%s", c.BaseURL, path)
    105 	req, err := http.NewRequest(http.MethodGet, u, nil)
    106 	if err != nil {
    107 		return nil, err
    108 	}
    109 	return c.do(req)
    110 }
    111 
    112 func (c *Client) post(path, contentType string, body io.Reader) (*http.Response, error) {
    113 	if c.BaseURL == "" {
    114 		c.BaseURL = GitlabHosted
    115 	}
    116 	u := fmt.Sprintf("%s/%s", c.BaseURL, path)
    117 	req, err := http.NewRequest(http.MethodPost, u, body)
    118 	if err != nil {
    119 		return nil, err
    120 	}
    121 	req.Header.Set("Content-Type", contentType)
    122 	resp, err := c.do(req)
    123 	if err != nil {
    124 		return nil, err
    125 	}
    126 	if resp.StatusCode != http.StatusCreated {
    127 		return nil, fmt.Errorf("unexpected status %s", resp.Status)
    128 	}
    129 	return resp, nil
    130 }
    131 
    132 func (c *Client) do(req *http.Request) (*http.Response, error) {
    133 	if c.Client == nil {
    134 		c.Client = http.DefaultClient
    135 	}
    136 	if c.BaseURL == "" {
    137 		c.BaseURL = "https://gitlab.com/api/v4"
    138 	}
    139 
    140 	if c.Token != "" {
    141 		req.Header.Set("Authorization", "Bearer "+c.Token)
    142 	}
    143 
    144 	req.Header.Set("Accept", "application/json")
    145 	if *debug {
    146 		log.Println(req.Method, req.URL)
    147 	}
    148 	resp, err := c.Do(req)
    149 	if err != nil {
    150 		return nil, err
    151 	}
    152 	if resp.StatusCode >= http.StatusBadRequest {
    153 		var e gError
    154 		if err := json.NewDecoder(resp.Body).Decode(&e); err != nil {
    155 			resp.Body.Close()
    156 			return nil, fmt.Errorf("%s %s: %s: decode error message: %w", req.Method, req.URL, resp.Status, err)
    157 		}
    158 		resp.Body.Close()
    159 		return nil, fmt.Errorf("%s %s: %s: %w", req.Method, req.URL, resp.Status, e)
    160 	}
    161 	return resp, err
    162 }