ducky/subscriptions

Paddy 2015-10-04 Parent:8eb19bcbf17d

17:7eef47ecc01c Go to Latest

ducky/subscriptions/client/client.go

Document our client to make golint happy. Take care of all the documentation warnings in the client subpackage, which means golint now returns successfully.

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@17 20 // ErrNilClient is returned when a method is called on a nil Client.
paddy@17 21 ErrNilClient = errors.New("nil client wrapper")
paddy@17 22 // ErrNilHTTPClient is returned when a method is called on a Client
paddy@17 23 // with its http.Client not set.
paddy@7 24 ErrNilHTTPClient = errors.New("nil client")
paddy@7 25 )
paddy@7 26
paddy@17 27 // Client is a wrapper that bundles all the information necessary to interact
paddy@17 28 // with the subscriptions API.
paddy@7 29 type Client struct {
paddy@7 30 client *http.Client
paddy@7 31 address string
paddy@7 32 ID uuid.ID
paddy@7 33 }
paddy@7 34
paddy@17 35 // New returns a new Client. The passed address is the base address for the
paddy@17 36 // subscriptions API, as an absolute or relative URL. The passed ID is the
paddy@17 37 // OAuth2 client ID to use for the Client.
paddy@7 38 func New(address string, id uuid.ID) *Client {
paddy@7 39 address = strings.TrimRight(address, "/")
paddy@7 40 return &Client{
paddy@7 41 address: address,
paddy@7 42 client: &http.Client{},
paddy@7 43 ID: id,
paddy@7 44 }
paddy@7 45 }
paddy@7 46
paddy@7 47 func (c *Client) do(method, url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
paddy@7 48 if c == nil {
paddy@7 49 return api.Response{}, ErrNilClient
paddy@7 50 }
paddy@7 51 if c.client == nil {
paddy@7 52 return api.Response{}, ErrNilHTTPClient
paddy@7 53 }
paddy@7 54 var response api.Response
paddy@7 55 if !strings.HasPrefix(url, "http") {
paddy@7 56 url = strings.TrimLeft(url, "/")
paddy@7 57 url = "/" + url
paddy@7 58 url = c.address + url
paddy@7 59 }
paddy@7 60 var body io.Reader
paddy@7 61 if request != nil {
paddy@7 62 data, err := json.Marshal(request)
paddy@7 63 if err != nil {
paddy@7 64 return response, err
paddy@7 65 }
paddy@7 66 body = bytes.NewBuffer(data)
paddy@7 67 }
paddy@7 68 req, err := http.NewRequest(method, url, body)
paddy@7 69 if err != nil {
paddy@7 70 return response, err
paddy@7 71 }
paddy@7 72 req.Header.Set("Ducky-Scope", strings.Join(scopes, " "))
paddy@7 73 req.Header.Set("Ducky-Issuer", c.ID.String())
paddy@7 74 if subject != nil {
paddy@7 75 req.Header.Set("Ducky-Subject", subject.String())
paddy@7 76 }
paddy@7 77 req.Header.Set("Ducky-Expires", time.Now().Add(time.Hour).String())
paddy@7 78 req.Header.Set("Ducky-Issued-At", time.Now().String())
paddy@7 79 req.Header.Set("Ducky-Not-Before", time.Now().Add(-5*time.Minute).String())
paddy@7 80 resp, err := c.client.Do(req)
paddy@7 81 if err != nil {
paddy@7 82 return response, err
paddy@7 83 }
paddy@7 84 defer resp.Body.Close()
paddy@7 85 switch resp.Header.Get("Content-Type") {
paddy@7 86 case "application/json":
paddy@7 87 dec := json.NewDecoder(resp.Body)
paddy@7 88 err = dec.Decode(&response)
paddy@7 89 if err != nil {
paddy@7 90 return response, err
paddy@7 91 }
paddy@7 92 default:
paddy@7 93 dec := json.NewDecoder(resp.Body)
paddy@7 94 err = dec.Decode(&response)
paddy@7 95 if err != nil {
paddy@7 96 return response, err
paddy@7 97 }
paddy@7 98 }
paddy@9 99 if len(response.Errors) > 0 {
paddy@9 100 return response, httpErrors(response.Errors)
paddy@9 101 }
paddy@7 102 return response, nil
paddy@7 103 }
paddy@7 104
paddy@9 105 type httpErrors []commonAPI.RequestError
paddy@9 106
paddy@9 107 func (h httpErrors) Error() string {
paddy@9 108 return fmt.Sprintf("%+#v", h)
paddy@9 109 }
paddy@9 110
paddy@17 111 // Get returns the api Response object for the passed URL called
paddy@17 112 // over HTTP GET. The passed scope IDs will be applied to the request,
paddy@17 113 // and the request will be made on behalf of the subject (user)
paddy@17 114 // specified by the passed ID. If the zero value is passed for the ID,
paddy@17 115 // the request will be made without a subject. To request no scopes,
paddy@17 116 // pass an empty slice or nil.
paddy@7 117 func (c *Client) Get(url string, scopes []string, subject uuid.ID) (api.Response, error) {
paddy@7 118 return c.do("GET", url, nil, scopes, subject)
paddy@7 119 }
paddy@7 120
paddy@17 121 // Post returns the api Response object for the passed URL called
paddy@17 122 // over HTTP POST. The passed scope IDs will be applied to the request,
paddy@17 123 // and the request will be made on behalf of the subject (user)
paddy@17 124 // specified by the passed ID. If the zero value is passed for the ID,
paddy@17 125 // the request will be made without a subject. To request no scopes,
paddy@17 126 // pass an empty slice or nil.
paddy@7 127 func (c *Client) Post(url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
paddy@7 128 return c.do("POST", url, request, scopes, subject)
paddy@7 129 }
paddy@7 130
paddy@17 131 // Patch returns the api Response object for the passed URL called
paddy@17 132 // over HTTP PATCH. The passed scope IDs will be applied to the request,
paddy@17 133 // and the request will be made on behalf of the subject (user)
paddy@17 134 // specified by the passed ID. If the zero value is passed for the ID,
paddy@17 135 // the request will be made without a subject. To request no scopes,
paddy@17 136 // pass an empty slice or nil.
paddy@7 137 func (c *Client) Patch(url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
paddy@7 138 return c.do("PATCH", url, request, scopes, subject)
paddy@7 139 }