Skip to content

Commit

Permalink
Merge pull request #7 from dghubble/dev
Browse files Browse the repository at this point in the history
Proposed v1.0
  • Loading branch information
dghubble committed May 23, 2015
2 parents 7bcba7e + a2c3718 commit 9024207
Show file tree
Hide file tree
Showing 7 changed files with 795 additions and 306 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Expand Up @@ -6,8 +6,10 @@ go:
- tip
before_install:
- go get golang.org/x/tools/cmd/vet
- go get github.com/golang/lint/golint
install:
- go get -v .
script:
- go test -v .
- go vet ./...
- go vet ./...
- golint ./...
48 changes: 48 additions & 0 deletions CHANGES.md
@@ -0,0 +1,48 @@
# Sling Changelog

Notable changes between releases.

## v1.0.0 (2015-05-23)

* Added support for receiving and decoding error JSON structs
* Renamed Sling `JsonBody` setter to `BodyJSON` (breaking)
* Renamed Sling `BodyStruct` setter to `BodyForm` (breaking)
* Renamed Sling fields `httpClient`, `method`, `rawURL`, and `header` to be internal (breaking)
* Changed `Do` and `Receive` to skip response JSON decoding if "application/json" Content-Type is missing
* Changed `Sling.Receive(v interface{})` to `Sling.Receive(successV, failureV interface{})` (breaking)
* Previously `Receive` attempted to decode the response Body in all cases
* Updated `Receive` will decode the response Body into successV for 2XX responses or decode the Body into failureV for other status codes. Pass a nil `successV` or `failureV` to skip JSON decoding into that value.
* To upgrade, pass nil for the `failureV` argument or consider defining a JSON tagged struct appropriate for the API endpoint. (e.g. `s.Receive(&issue, nil)`, `s.Receive(&issue, &githubError)`)
* To retain the old behavior, duplicate the first argument (e.g. s.Receive(&tweet, &tweet))
* Changed `Sling.Do(http.Request, v interface{})` to `Sling.Do(http.Request, successV, failureV interface{})` (breaking)
* See the changelog entry about `Receive`, the upgrade path is the same.
* Removed HEAD, GET, POST, PUT, PATCH, DELETE constants, no reason to export them (breaking)

## v0.4.0 (2015-04-26)

* Improved golint compliance
* Fixed typos and test printouts

## v0.3.0 (2015-04-21)

* Added BodyStruct method for setting a url encoded form body on the Request
* Added Add and Set methods for adding or setting Request Headers
* Added JsonBody method for setting JSON Request Body
* Improved examples and documentation

## v0.2.0 (2015-04-05)

* Added http.Client setter
* Added Sling.New() method to return a copy of a Sling
* Added Base setter and Path extension support
* Added method setters (Get, Post, Put, Patch, Delete, Head)
* Added support for encoding URL Query parameters
* Added example tiny Github API
* Changed v0.1.0 method signatures and names (breaking)
* Removed Go 1.0 support

## v0.1.0 (2015-04-01)

* Support decoding JSON responses.


135 changes: 90 additions & 45 deletions README.md
Expand Up @@ -2,18 +2,20 @@
# Sling [![Build Status](https://travis-ci.org/dghubble/sling.png)](https://travis-ci.org/dghubble/sling) [![Coverage](http://gocover.io/_badge/github.com/dghubble/sling)](http://gocover.io/github.com/dghubble/sling) [![GoDoc](http://godoc.org/github.com/dghubble/sling?status.png)](http://godoc.org/github.com/dghubble/sling)
<img align="right" src="https://s3.amazonaws.com/dghubble/small-gopher-with-sling.png">

Sling is a Go REST client library for creating and sending API requests.
Sling is a Go HTTP client library for creating and sending API requests.

Slings store http Request properties to simplify sending requests and decoding responses. Check [usage](#usage) or the [examples](examples) to learn how to compose a Sling into your API client.
Slings store HTTP Request properties to simplify sending requests and decoding responses. Check [usage](#usage) or the [examples](examples) to learn how to compose a Sling into your API client.

Note: Sling **v1.0** recently introduced some breaking changes. See [changes](CHANGES.md).

### Features

* Base/Path - path extend a Sling for different endpoints
* Method Setters: Get/Post/Put/Patch/Delete/Head
* Add and Set Request Headers
* Encode url structs into URL query parameters
* Encode url structs or json into the Request Body
* Decode received JSON success responses
* Encode structs into URL query parameters
* Encode a form or JSON into the Request Body
* Receive JSON success or failure responses

## Install

Expand All @@ -25,34 +27,36 @@ Read [GoDoc](https://godoc.org/github.com/dghubble/sling)

## Usage

Use a simple Sling to set request properties (`Path`, `QueryParams`, etc.) and then create a new `http.Request` by calling `Request()`.
Use a Sling to create an `http.Request` with a chained API for setting properties (path, method, queries, body, etc.).

```go
req, err := sling.New().Get("https://example.com").Request()
type Params struct {
Count int `url:"count,omitempty"`
}
params := &Params{Count: 5}

req, err := sling.New().Get("https://example.com").QueryStruct(params).Request()
client.Do(req)
```

Slings are much more powerful though. Use them to create REST clients which wrap complex API endpoints. Copy a base Sling with `New()` to avoid repeating common configuration.
### Path

```go
const twitterApi = "https://api.twitter.com/1.1/"
base := sling.New().Base(twitterApi).Client(httpAuthClient)
Use `Path` to set or extend the URL for created Requests. Extension means the path will be resolved relative to the existing URL.

users := base.New().Path("users/")
statuses := base.New().Path("statuses/")
```go
// sends a GET request to http://example.com/foo/bar
req, err := sling.New().Base("http://example.com/").Path("foo/").Path("bar").Request()
```

Choose an http method, set query parameters, and send the request.
Use `Get`, `Post`, `Put`, `Patch`, `Delete`, or `Head` which are exactly the same as `Path` except they set the HTTP method too.

```go
statuses.New().Get("show.json").QueryStruct(params).Receive(tweet)
req, err := sling.New().Post("http://upload.com/gophers")
```

The sections below provide more details about setting headers, query parameters, body data, and decoding a typed response after sending.

### Headers

`Add` or `Set` headers which should be applied to all Requests created by a Sling.
`Add` or `Set` headers which should be applied to the Requests created by a Sling.

```go
base := sling.New().Base(baseUrl).Set("User-Agent", "Gophergram API Client")
Expand Down Expand Up @@ -85,9 +89,9 @@ req, err := githubBase.New().Get(path).QueryStruct(params).Request()

### Body

#### JsonBody
#### Json Body

Make a Sling include JSON in the Body of its Requests using `JsonBody`.
Make a Sling include JSON in the Body of its Requests using `BodyJSON`.

```go
type IssueRequest struct {
Expand All @@ -107,14 +111,14 @@ body := &IssueRequest{
Title: "Test title",
Body: "Some issue",
}
req, err := githubBase.New().Post(path).JsonBody(body).Request()
req, err := githubBase.New().Post(path).BodyJSON(body).Request()
```

Requests will include an `application/json` Content-Type header.

#### BodyStruct
#### Form Body

Make a Sling include a url-tagged struct as a url-encoded form in the Body of its Requests using `BodyStruct`.
Make a Sling include a url-tagged struct as a url-encoded form in the Body of its Requests using `BodyForm`.

```go
type StatusUpdateParams struct {
Expand All @@ -126,33 +130,81 @@ type StatusUpdateParams struct {

```go
tweetParams := &StatusUpdateParams{Status: "writing some Go"}
req, err := twitterBase.New().Post(path).BodyStruct(tweetParams).Request()
req, err := twitterBase.New().Post(path).BodyForm(tweetParams).Request()
```

Requests will include an `application/x-www-form-urlencoded` Content-Type header.

### Extend a

Each distinct Sling generates an `http.Request` (say with some path and query
params) each time `Request()` is called, based on its state. When creating
different kinds of requests using distinct Slings, you may wish to extend
an existing Sling to minimize duplication (e.g. a common client).

Each Sling instance provides a `New()` method which creates an independent copy, so setting properties on the child won't mutate the parent Sling.

```go
const twitterApi = "https://api.twitter.com/1.1/"
base := sling.New().Base(twitterApi).Client(httpAuthClient)

// statuses/show.json Sling
tweetShowSling := base.New().Get("statuses/show.json").QueryStruct(params)
req, err := tweetShowSling.Request()

// statuses/update.json Sling
tweetPostSling := base.New().Post("statuses/update.json").BodyForm(params)
req, err := tweetPostSling.Request()
```

Without the calls to `base.New()`, tweetShowSling and tweetPostSling reference
the base Sling and POST to
"https://api.twitter.com/1.1/statuses/show.json/statuses/update.json", which
is undesired.

Recap: If you wish to extend a Sling, create a new child copy with `New()`.

### Receive

Define expected value structs. Use `Receive(v interface{})` to send a new Request that will automatically decode the response into the value.
Define a JSON struct to decode a type from 2XX success responses. Use `ReceiveSuccess(successV interface{})` to send a new Request and decode the response body into `successV` if it succeeds.

```go
// Github Issue (abbreviated)
type Issue struct {
Id int `json:"id"`
Url string `json:"url"`
Number int `json:"number"`
State string `json:"state"`
Title string `json:"title"`
Body string `json:"body"`
}
```

```go
issues := new([]Issue)
resp, err := githubBase.New().Get(path).QueryStruct(params).Receive(issues)
resp, err := githubBase.New().Get(path).QueryStruct(params).ReceiveSuccess(issues)
fmt.Println(issues, resp, err)
```

Most APIs return failure responses with JSON error details. To decode these, define success and failure JSON structs. Use `Receive(successV, failureV interface{})` to send a new Request that will automatically decode the response into the `successV` for 2XX responses or into `failureV` for non-2XX responses.

```go
type GithubError struct {
Message string `json:"message"`
Errors []struct {
Resource string `json:"resource"`
Field string `json:"field"`
Code string `json:"code"`
} `json:"errors"`
DocumentationURL string `json:"documentation_url"`
}
```

```go
issues := new([]Issue)
githubError := new(GithubError)
resp, err := githubBase.New().Get(path).QueryStruct(params).Receive(issues, githubError)
fmt.Println(issues, githubError, resp, err)
```

Pass a nil `successV` or `failureV` argument to skip JSON decoding into that value.

### Build an API

APIs typically define an endpoint (also called a service) for each type of resource. For example, here is a tiny Github IssueService which [creates](https://developer.github.com/v3/issues/#create-an-issue) and [lists](https://developer.github.com/v3/issues/#list-issues-for-a-repository) repository issues.
Expand All @@ -164,21 +216,18 @@ type IssueService struct {

func NewIssueService(httpClient *http.Client) *IssueService {
return &IssueService{
sling: sling.New().Client(httpClient).Base(baseUrl),
sling: sling.New().Client(httpClient).Base(baseURL),
}
}

func (s *IssueService) Create(owner, repo string, issueBody *IssueRequest) (*Issue, *http.Response, error) {
issue := new(Issue)
func (s *IssueService) ListByRepo(owner, repo string, params *IssueListParams) ([]Issue, *http.Response, error) {
issues := new([]Issue)
githubError := new(GithubError)
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)
resp, err := s.sling.New().Post(path).JsonBody(issueBody).Receive(issue)
return issue, resp, err
}

func (srvc IssueService) List(owner, repo string, params *IssueParams) ([]Issue, *http.Response, error) {
var issues []Issue
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)
resp, err := srvc.sling.New().Get(path).QueryStruct(params).Receive(&issues)
resp, err := s.sling.New().Get(path).QueryStruct(params).Receive(issues, githubError)
if err == nil {
err = githubError
}
return *issues, resp, err
}
```
Expand All @@ -189,10 +238,6 @@ func (srvc IssueService) List(owner, repo string, params *IssueParams) ([]Issue,

Create a Pull Request to add a link to your own API.

## Roadmap

* Receive custom error structs

## Motivation

Many client libraries follow the lead of [google/go-github](https://github.com/google/go-github) (our inspiration!), but do so by reimplementing logic common to all clients.
Expand Down

0 comments on commit 9024207

Please sign in to comment.