ducky/subscriptions
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.
| 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 } |