code.oscarkilo.com/okg/client.go

.gitignore
README.md
auth.go
client.go
config.go
go.mod
main.go
okg_test.go
pr.go
repo.go
package main

import "encoding/json"
import "fmt"
import "io"
import "net/http"
import "os"
import "os/exec"
import "regexp"
import "strings"

var repoRegex = regexp.MustCompile(
  `code\.oscarkilo\.com/([a-z][-a-z0-9]*)\.git`)

// detectRepo parses the git remote URL for the klee repo name.
func detectRepo() (string, error) {
  cmd := exec.Command("git", "remote", "get-url", "origin")
  out, err := cmd.Output()
  if err != nil {
    return "", fmt.Errorf(
      "not a git repo or no remote 'origin': %v", err)
  }
  url := strings.TrimSpace(string(out))
  m := repoRegex.FindStringSubmatch(url)
  if m == nil {
    return "", fmt.Errorf(
      "remote URL %q is not a klee repo", url)
  }
  return m[1], nil
}

// resolveRepo returns the repo name from --repo flag,
// OKG_REPO env var, or git remote detection.
func resolveRepo(flagRepo string) (string, error) {
  if flagRepo != "" {
    return flagRepo, nil
  }
  if v := os.Getenv("OKG_REPO"); v != "" {
    return v, nil
  }
  return detectRepo()
}

type Client struct {
  Host   string
  ApiKey string
  http   *http.Client
}

func newClient(cfg *Config) *Client {
  return &Client{
    Host:   cfg.Host,
    ApiKey: cfg.ApiKey,
    http:   &http.Client{},
  }
}

// do performs an HTTP request and returns the response.
func (c *Client) do(
  method, path string, body io.Reader,
) (*http.Response, error) {
  url := c.Host + path
  req, err := http.NewRequest(method, url, body)
  if err != nil {
    return nil, err
  }
  req.Header.Set("Accept", "application/json")
  if c.ApiKey != "" {
    req.Header.Set(
      "Authorization", "Bearer "+c.ApiKey)
  }
  if body != nil {
    req.Header.Set("Content-Type", "application/json")
  }
  return c.http.Do(req)
}

// getJSON performs a GET and decodes JSON into dst.
func (c *Client) getJSON(path string, dst interface{}) error {
  resp, err := c.do("GET", path, nil)
  if err != nil {
    return err
  }
  defer resp.Body.Close()
  if resp.StatusCode >= 400 {
    body, _ := io.ReadAll(resp.Body)
    return fmt.Errorf(
      "HTTP %d: %s", resp.StatusCode, string(body))
  }
  return json.NewDecoder(resp.Body).Decode(dst)
}

// postJSON performs a POST with a JSON body,
// decodes the response into dst.
func (c *Client) postJSON(
  path string, payload interface{}, dst interface{},
) error {
  body, err := jsonBody(payload)
  if err != nil {
    return err
  }
  resp, err := c.do("POST", path, body)
  if err != nil {
    return err
  }
  defer resp.Body.Close()
  if resp.StatusCode >= 400 {
    b, _ := io.ReadAll(resp.Body)
    return fmt.Errorf(
      "HTTP %d: %s", resp.StatusCode, string(b))
  }
  if dst != nil {
    return json.NewDecoder(resp.Body).Decode(dst)
  }
  return nil
}

// patchJSON performs a PATCH with a JSON body,
// decodes the response into dst.
func (c *Client) patchJSON(
  path string, payload interface{}, dst interface{},
) error {
  body, err := jsonBody(payload)
  if err != nil {
    return err
  }
  resp, err := c.do("PATCH", path, body)
  if err != nil {
    return err
  }
  defer resp.Body.Close()
  if resp.StatusCode >= 400 {
    b, _ := io.ReadAll(resp.Body)
    return fmt.Errorf(
      "HTTP %d: %s", resp.StatusCode, string(b))
  }
  if dst != nil {
    return json.NewDecoder(resp.Body).Decode(dst)
  }
  return nil
}

func jsonBody(v interface{}) (io.Reader, error) {
  data, err := json.Marshal(v)
  if err != nil {
    return nil, err
  }
  return strings.NewReader(string(data)), nil
}