ducky/subscriptions
ducky/subscriptions/client/client.go
Make api subpackage golint-passing. Add comments to all the exported functions, methods, and variables in the api subpackage, to make golint happy. Also, make the individual endpoints in the api subpackage unexported, as there's no real use case for exporting them. The handlers depend on the placeholders in the endpoint, so we need them to be controlled in unison, which means it's probably a bad idea to declare the route outside of the API package. And the only reason to expose the Handler is so people can declare custom endpoints.
| 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 } |