Skip to content

Commit

Permalink
Reword body handling to use providers
Browse files Browse the repository at this point in the history
Incidentally, also add support for multipart and file uploading.
  • Loading branch information
jawher committed Oct 4, 2016
1 parent 52e88a7 commit 2d354f9
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 111 deletions.
100 changes: 100 additions & 0 deletions body_providers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package sling

import (
"bytes"
"encoding/json"
"io"
"strings"

goquery "github.com/google/go-querystring/query"
)

// BodyProvider creates request bodies. Slings comes with a couple of providers (form, json, ...)
// and you can also implement yours to handle custom body types.
type BodyProvider interface {
// ContentType controls the request's content type header
ContentType() string

// Body creates the io.Reader that will be used as the request's body
Body() (io.Reader, error)
}

// ReaderBody creates a BodyProvider from an io.Reader
func ReaderBody(body io.Reader) BodyProvider {
return readerBodyProvider{reader: body}
}

// JSONBody creates a BodyProvider that encodes the provided value as JSON.
// The bodyJSON argument should be a pointer to a JSON tagged struct. See
// https://golang.org/pkg/encoding/json/#MarshalIndent for details.
func JSONBody(bodyJSON interface{}) BodyProvider {
if bodyJSON == nil {
return nil
}

return jsonBodyProvider{payload: bodyJSON}
}

// FormBody creates a BodyProvider that encodes the provided value as URL form encoded body.
// The bodyForm argument should be a pointer to a url tagged struct. See
// https://godoc.org/github.com/google/go-querystring/query for details.
func FormBody(bodyForm interface{}) BodyProvider {
if bodyForm == nil {
return nil
}

return formBodyProvider{payload: bodyForm}
}

// Implementations

// JSON

type jsonBodyProvider struct {
payload interface{}
}

func (p jsonBodyProvider) ContentType() string {
return jsonContentType
}

func (p jsonBodyProvider) Body() (io.Reader, error) {
buf := &bytes.Buffer{}
err := json.NewEncoder(buf).Encode(p.payload)
if err != nil {
return nil, err
}

return buf, nil
}

// Form
type formBodyProvider struct {
payload interface{}
}

func (p formBodyProvider) ContentType() string {
return formContentType
}

func (p formBodyProvider) Body() (io.Reader, error) {
values, err := goquery.Values(p.payload)
if err != nil {
return nil, err
}
return strings.NewReader(values.Encode()), nil
}

// Reader

type readerBodyProvider struct {
reader io.Reader
}

func (p readerBodyProvider) ContentType() string {
return ""
}

func (p readerBodyProvider) Body() (io.Reader, error) {
return p.reader, nil
}
107 changes: 32 additions & 75 deletions sling.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package sling

import (
"bytes"
"encoding/base64"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
Expand Down Expand Up @@ -38,12 +36,8 @@ type Sling struct {
header http.Header
// url tagged query structs
queryStructs []interface{}
// json tagged body struct
bodyJSON interface{}
// url tagged body struct (form)
bodyForm interface{}
// simply assigned body
body io.ReadCloser
// body provider
bodyProvider BodyProvider
}

// New returns a new Sling with an http DefaultClient.
Expand Down Expand Up @@ -80,9 +74,7 @@ func (s *Sling) New() *Sling {
rawURL: s.rawURL,
header: headerCopy,
queryStructs: append([]interface{}{}, s.queryStructs...),
bodyJSON: s.bodyJSON,
bodyForm: s.bodyForm,
body: s.body,
bodyProvider: s.bodyProvider,
}
}

Expand Down Expand Up @@ -211,43 +203,47 @@ func (s *Sling) QueryStruct(queryStruct interface{}) *Sling {

// Body

// BodyProvider sets the Sling's body provider.
func (s *Sling) BodyProvider(body BodyProvider) *Sling {
if body == nil {
return s
}

s.bodyProvider = body

typ := body.ContentType()
if typ != "" {
s.Set(contentType, typ)
}

return s
}

// BodyJSON sets the Sling's bodyJSON. The value pointed to by the bodyJSON
// will be JSON encoded as the Body on new requests (see Request()).
// The bodyJSON argument should be a pointer to a JSON tagged struct. See
// https://golang.org/pkg/encoding/json/#MarshalIndent for details.
func (s *Sling) BodyJSON(bodyJSON interface{}) *Sling {
if bodyJSON != nil {
s.bodyJSON = bodyJSON
s.Set(contentType, jsonContentType)
}
return s
return s.BodyProvider(JSONBody(bodyJSON))
}

// BodyForm sets the Sling's bodyForm. The value pointed to by the bodyForm
// will be url encoded as the Body on new requests (see Request()).
// The bodyStruct argument should be a pointer to a url tagged struct. See
// The bodyForm argument should be a pointer to a url tagged struct. See
// https://godoc.org/github.com/google/go-querystring/query for details.
func (s *Sling) BodyForm(bodyForm interface{}) *Sling {
if bodyForm != nil {
s.bodyForm = bodyForm
s.Set(contentType, formContentType)
}
return s
return s.BodyProvider(FormBody(bodyForm))
}

// Body sets the Sling's body. The body value will be set as the Body on new
// requests (see Request()).
// If the provided body is also an io.Closer, the request Body will be closed
// by http.Client methods.
func (s *Sling) Body(body io.Reader) *Sling {
rc, ok := body.(io.ReadCloser)
if !ok && body != nil {
rc = ioutil.NopCloser(body)
}
if rc != nil {
s.body = rc
if body == nil {
return s
}
return s
return s.BodyProvider(ReaderBody(body))
}

// Requests
Expand All @@ -264,9 +260,13 @@ func (s *Sling) Request() (*http.Request, error) {
if err != nil {
return nil, err
}
body, err := s.getRequestBody()
if err != nil {
return nil, err
var body io.Reader

if s.bodyProvider != nil {
body, err = s.bodyProvider.Body()
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(s.method, reqURL.String(), body)
if err != nil {
Expand Down Expand Up @@ -301,49 +301,6 @@ func addQueryStructs(reqURL *url.URL, queryStructs []interface{}) error {
return nil
}

// getRequestBody returns the io.Reader which should be used as the body
// of new Requests.
func (s *Sling) getRequestBody() (body io.Reader, err error) {
if s.bodyJSON != nil && s.header.Get(contentType) == jsonContentType {
body, err = encodeBodyJSON(s.bodyJSON)
if err != nil {
return nil, err
}
} else if s.bodyForm != nil && s.header.Get(contentType) == formContentType {
body, err = encodeBodyForm(s.bodyForm)
if err != nil {
return nil, err
}
} else if s.body != nil {
body = s.body
}
return body, nil
}

// encodeBodyJSON JSON encodes the value pointed to by bodyJSON into an
// io.Reader, typically for use as a Request Body.
func encodeBodyJSON(bodyJSON interface{}) (io.Reader, error) {
var buf = new(bytes.Buffer)
if bodyJSON != nil {
buf = &bytes.Buffer{}
err := json.NewEncoder(buf).Encode(bodyJSON)
if err != nil {
return nil, err
}
}
return buf, nil
}

// encodeBodyForm url encodes the value pointed to by bodyForm into an
// io.Reader, typically for use as a Request Body.
func encodeBodyForm(bodyForm interface{}) (io.Reader, error) {
values, err := goquery.Values(bodyForm)
if err != nil {
return nil, err
}
return strings.NewReader(values.Encode()), nil
}

// addHeaders adds the key, value pairs from the given http.Header to the
// request. Values for existing keys are appended to the keys values.
func addHeaders(req *http.Request, header http.Header) {
Expand Down
Loading

0 comments on commit 2d354f9

Please sign in to comment.