// Package rest is the shared base for okg's per-service HTTP
// clients (//okg/klee, //okg/who, etc.). It owns the
// host/api-key/transport plumbing and JSON marshaling; each
// domain-specific client embeds *Client and adds typed methods.
package rest
import "encoding/json"
import "fmt"
import "io"
import "net/http"
import "strings"
// Client carries the host, API key, and underlying http.Client
// that the JSON helpers below dispatch through.
type Client struct {
Host string
APIKey string
HTTP *http.Client
}
func NewClient(host, apiKey string) *Client {
return &Client{
Host: host,
APIKey: apiKey,
HTTP: &http.Client{},
}
}
// Do builds a request to c.Host+path with the standard headers
// (Accept, Authorization, Content-Type when a body is present)
// and runs it through c.HTTP.
func (c *Client) Do(
method, path string, body io.Reader,
) (*http.Response, error) {
req, err := http.NewRequest(method, c.Host+path, 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 issues a GET, returns an HTTP-status error on 4xx/5xx,
// and decodes a successful body into dst (or skips decode if
// dst is nil).
func (c *Client) GetJSON(
path string, dst interface{},
) error {
return c.exchange("GET", path, nil, dst)
}
// PostJSON issues a POST with payload (omitted if nil) and
// decodes the response into dst (or skips decode if dst is nil).
func (c *Client) PostJSON(
path string, payload, dst interface{},
) error {
return c.exchange("POST", path, payload, dst)
}
// PatchJSON issues a PATCH; payload is required.
func (c *Client) PatchJSON(
path string, payload, dst interface{},
) error {
return c.exchange("PATCH", path, payload, dst)
}
func (c *Client) exchange(
method, path string, payload, dst interface{},
) error {
var body io.Reader
if payload != nil {
data, err := json.Marshal(payload)
if err != nil {
return err
}
body = strings.NewReader(string(data))
}
resp, err := c.Do(method, 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, b)
}
if dst != nil {
return json.NewDecoder(resp.Body).Decode(dst)
}
return nil
}