mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-06 13:26:28 +00:00
feat(clients): add Go, Rust, C# and Dart clients for ResolveSpec and FunctionSpec
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user