Skip to content

Commit

Permalink
Add initial Sling which decodes Response Body JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
dghubble committed Apr 2, 2015
0 parents commit 3e7592f
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 0 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

# Sling

Sling is a Go REST client library for building and firing requests.

Sling generalizes ideas from the google/go-github API to make it easy to build Go clients in the same style.

4 changes: 4 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
Sling is a Go REST client library for building and firing API requests.
*/
package sling
41 changes: 41 additions & 0 deletions sling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package sling

import (
"encoding/json"
"net/http"
)

type Sling struct {
// http Client for doing requests
httpClient *http.Client
}

// New returns a new Sling.
func New(httpClient *http.Client) *Sling {
if httpClient == nil {
httpClient = http.DefaultClient
}
return &Sling{httpClient: httpClient}
}

// Fire sends the HTTP request and decodes the response into the value pointed
// to by 'success'. It wraps http.Client.Do, but handles closing the Response
// Body. The Response and any error making the request are returned.
//
// Note that non-2xx StatusCodes are valid responses, not errors.
func (s *Sling) Fire(req *http.Request, value interface{}) (*http.Response, error) {
resp, err := s.httpClient.Do(req)
if err != nil {
return resp, err
}
// when err is nil, resp contains a non-nil resp.Body which must be closed
defer resp.Body.Close()
err = decodeResponse(resp, value)
return resp, err
}

// decodeResponse decodes Response Body encoded as JSON into the value pointed
// to by v. Caller should call resp.Body.Close().
func decodeResponse(resp *http.Response, v interface{}) error {
return json.NewDecoder(resp.Body).Decode(v)
}
77 changes: 77 additions & 0 deletions sling_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package sling

import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)

func TestNew(t *testing.T) {
developerClient := &http.Client{}
cases := []struct {
input *http.Client
expected *http.Client
}{
{nil, http.DefaultClient},
{developerClient, developerClient},
}
for _, c := range cases {
sling := New(c.input)
if sling.httpClient != c.expected {
t.Errorf("expected %v, got %v", c.expected, sling.httpClient)
}
}
}

// mockServer returns an httptest.Server which always returns the given body.
// The caller must close the test server.
func mockServer(body string) (*http.Client, *httptest.Server) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, body)
}))
transport := &http.Transport{
Proxy: func(req *http.Request) (*url.URL, error) {
return url.Parse(server.URL)
},
}
client := &http.Client{Transport: transport}
return client, server
}

type FakeModel struct {
Text string `json:"text"`
FavoriteCount int64 `json:"favorite_count"`
}

func TestFire(t *testing.T) {
expectedText := "Some text"
var expectedFavoriteCount int64 = 24
client, server := mockServer(`{"text": "Some text", "favorite_count": 24}`)
defer server.Close()

sling := New(client)
req, _ := http.NewRequest("GET", server.URL, nil)
var model FakeModel
resp, err := sling.Fire(req, &model)

if err != nil {
t.Errorf("expected nil, got %v", err)
}
if resp.StatusCode != 200 {
t.Errorf("expected %d, got %d", 200, resp.StatusCode)
}
expectedReadError := "http: read on closed response body"
if _, err = ioutil.ReadAll(resp.Body); err == nil || err.Error() != expectedReadError {
t.Errorf("expected %s, got %v", expectedReadError, err)
}
if model.Text != expectedText {
t.Errorf("expected %s, got %s", expectedText, model.Text)
}
if model.FavoriteCount != expectedFavoriteCount {
t.Errorf("expected %d, got %d", expectedFavoriteCount, model.FavoriteCount)
}
}

0 comments on commit 3e7592f

Please sign in to comment.