Files
ResolveSpec/clients/resolvespec-go/client.go
T

122 lines
2.8 KiB
Go

package resolvespec
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
// APIError is the server error object.
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
Details any `json:"details,omitempty"`
Detail string `json:"detail,omitempty"` // server-side reason (funcspec / restheadspec)
SQL string `json:"sql,omitempty"`
}
// Error is returned on a non-2xx response or an unsuccessful result.
type Error struct {
StatusCode int
APIError
}
func (e *Error) Error() string {
if e.Message != "" {
return e.Message
}
return fmt.Sprintf("http %d", e.StatusCode)
}
type config struct {
baseURL string
token string
headers http.Header
http *http.Client
}
// Option configures a client.
type Option func(*config)
func WithToken(token string) Option { return func(c *config) { c.token = token } }
func WithHTTPClient(h *http.Client) Option { return func(c *config) { c.http = h } }
func WithHeader(name, value string) Option {
return func(c *config) { c.headers.Set(name, value) }
}
func newConfig(baseURL string, opts []Option) config {
c := config{baseURL: strings.TrimRight(baseURL, "/"), headers: http.Header{}, http: &http.Client{Timeout: 30 * time.Second}}
for _, o := range opts {
o(&c)
}
return c
}
// headers: Content-Type < custom headers < bearer token.
func (c *config) newRequest(ctx context.Context, method, u string, body []byte) (*http.Request, error) {
var r io.Reader
if body != nil {
r = bytes.NewReader(body)
}
req, err := http.NewRequestWithContext(ctx, method, u, r)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
for k, vs := range c.headers {
req.Header[k] = append([]string(nil), vs...)
}
if c.token != "" {
req.Header.Set("Authorization", "Bearer "+c.token)
}
return req, nil
}
func (c *config) do(req *http.Request) (*http.Response, []byte, error) {
resp, err := c.http.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
return resp, b, err
}
func errorFrom(status int, body []byte) *Error {
e := &Error{StatusCode: status}
var env struct {
Error *APIError `json:"error"`
}
if json.Unmarshal(body, &env) == nil && env.Error != nil {
e.APIError = *env.Error
}
if e.Message == "" {
text := ""
if !json.Valid(body) {
text = strings.TrimSpace(string(body))
if len(text) > 200 {
text = text[:200]
}
}
if text == "" {
text = fmt.Sprintf("%s (%d)", http.StatusText(status), status)
}
e.Message = text
}
return e
}
func buildURL(base, schema, entity string, id string) string {
u := base + "/" + url.PathEscape(schema) + "/" + url.PathEscape(entity)
if id != "" {
u += "/" + url.PathEscape(id)
}
return u
}