ducky/subscriptions

Paddy 2015-07-18 Parent:9e138933e4ce Child:7eef47ecc01c

9:8eb19bcbf17d Go to Latest

ducky/subscriptions/client/client.go

Return errors from responses in client. When the client makes a request, non-200 responses _are not_ considered errors. So we need to check the response.Errors property, and if it has errors, _then_ we consider the request to have an error. To make this happen, we created an httpErrors type that fulfills the error interface and just wraps the response Errors property. Then callers can type-cast it and interrogate it.

History
paddy@7 1 package client
paddy@7 2
paddy@7 3 import (
paddy@7 4 "bytes"
paddy@7 5 "encoding/json"
paddy@7 6 "errors"
paddy@9 7 "fmt"
paddy@7 8 "io"
paddy@7 9 "net/http"
paddy@7 10 "strings"
paddy@7 11 "time"
paddy@7 12
paddy@9 13 commonAPI "code.secondbit.org/api.hg"
paddy@7 14 "code.secondbit.org/uuid.hg"
paddy@7 15
paddy@7 16 "code.secondbit.org/ducky/subscriptions.hg/api"
paddy@7 17 )
paddy@7 18
paddy@7 19 var (
paddy@7 20 ErrNilClient = errors.New("nil client wrapper")
paddy@7 21 ErrNilHTTPClient = errors.New("nil client")
paddy@7 22 )
paddy@7 23
paddy@7 24 type Client struct {
paddy@7 25 client *http.Client
paddy@7 26 address string
paddy@7 27 ID uuid.ID
paddy@7 28 }
paddy@7 29
paddy@7 30 func New(address string, id uuid.ID) *Client {
paddy@7 31 address = strings.TrimRight(address, "/")
paddy@7 32 return &Client{
paddy@7 33 address: address,
paddy@7 34 client: &http.Client{},
paddy@7 35 ID: id,
paddy@7 36 }
paddy@7 37 }
paddy@7 38
paddy@7 39 func (c *Client) do(method, url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
paddy@7 40 if c == nil {
paddy@7 41 return api.Response{}, ErrNilClient
paddy@7 42 }
paddy@7 43 if c.client == nil {
paddy@7 44 return api.Response{}, ErrNilHTTPClient
paddy@7 45 }
paddy@7 46 var response api.Response
paddy@7 47 if !strings.HasPrefix(url, "http") {
paddy@7 48 url = strings.TrimLeft(url, "/")
paddy@7 49 url = "/" + url
paddy@7 50 url = c.address + url
paddy@7 51 }
paddy@7 52 var body io.Reader
paddy@7 53 if request != nil {
paddy@7 54 data, err := json.Marshal(request)
paddy@7 55 if err != nil {
paddy@7 56 return response, err
paddy@7 57 }
paddy@7 58 body = bytes.NewBuffer(data)
paddy@7 59 }
paddy@7 60 req, err := http.NewRequest(method, url, body)
paddy@7 61 if err != nil {
paddy@7 62 return response, err
paddy@7 63 }
paddy@7 64 req.Header.Set("Ducky-Scope", strings.Join(scopes, " "))
paddy@7 65 req.Header.Set("Ducky-Issuer", c.ID.String())
paddy@7 66 if subject != nil {
paddy@7 67 req.Header.Set("Ducky-Subject", subject.String())
paddy@7 68 }
paddy@7 69 req.Header.Set("Ducky-Expires", time.Now().Add(time.Hour).String())
paddy@7 70 req.Header.Set("Ducky-Issued-At", time.Now().String())
paddy@7 71 req.Header.Set("Ducky-Not-Before", time.Now().Add(-5*time.Minute).String())
paddy@7 72 resp, err := c.client.Do(req)
paddy@7 73 if err != nil {
paddy@7 74 return response, err
paddy@7 75 }
paddy@7 76 defer resp.Body.Close()
paddy@7 77 switch resp.Header.Get("Content-Type") {
paddy@7 78 case "application/json":
paddy@7 79 dec := json.NewDecoder(resp.Body)
paddy@7 80 err = dec.Decode(&response)
paddy@7 81 if err != nil {
paddy@7 82 return response, err
paddy@7 83 }
paddy@7 84 default:
paddy@7 85 dec := json.NewDecoder(resp.Body)
paddy@7 86 err = dec.Decode(&response)
paddy@7 87 if err != nil {
paddy@7 88 return response, err
paddy@7 89 }
paddy@7 90 }
paddy@9 91 if len(response.Errors) > 0 {
paddy@9 92 return response, httpErrors(response.Errors)
paddy@9 93 }
paddy@7 94 return response, nil
paddy@7 95 }
paddy@7 96
paddy@9 97 type httpErrors []commonAPI.RequestError
paddy@9 98
paddy@9 99 func (h httpErrors) Error() string {
paddy@9 100 return fmt.Sprintf("%+#v", h)
paddy@9 101 }
paddy@9 102
paddy@7 103 func (c *Client) Get(url string, scopes []string, subject uuid.ID) (api.Response, error) {
paddy@7 104 return c.do("GET", url, nil, scopes, subject)
paddy@7 105 }
paddy@7 106
paddy@7 107 func (c *Client) Post(url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
paddy@7 108 return c.do("POST", url, request, scopes, subject)
paddy@7 109 }
paddy@7 110
paddy@7 111 func (c *Client) Patch(url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
paddy@7 112 return c.do("PATCH", url, request, scopes, subject)
paddy@7 113 }