sling

package
v0.0.0-...-ebb22de Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 23, 2015 License: MIT, MIT Imports: 7 Imported by: 0

README

Sling Build Status Coverage GoDoc

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 or the examples to learn how to compose a Sling into your API client.

Note: Sling v1.0 recently introduced some breaking changes. See changes.

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

Install

go get github.com/dghubble/sling

Documentation

Read GoDoc

Usage

Use a Sling to create an http.Request with a chained API for setting properties (path, method, queries, body, etc.).

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)
Path

Use Path to set or extend the URL for created Requests. Extension means the path will be resolved relative to the existing URL.

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

Use Get, Post, Put, Patch, Delete, or Head which are exactly the same as Path except they set the HTTP method too.

req, err := sling.New().Post("http://upload.com/gophers")
Headers

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

base := sling.New().Base(baseUrl).Set("User-Agent", "Gophergram API Client")
req, err := base.New().Get("gophergram/list").Request()
QueryStruct

Define url parameter structs and use QueryStruct to encode query parameters.

// Github Issue Parameters
type IssueParams struct {
    Filter    string `url:"filter,omitempty"`
    State     string `url:"state,omitempty"`
    Labels    string `url:"labels,omitempty"`
    Sort      string `url:"sort,omitempty"`
    Direction string `url:"direction,omitempty"`
    Since     string `url:"since,omitempty"`
}
githubBase := sling.New().Base("https://api.github.com/").Client(httpClient)
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)

params := &IssueParams{Sort: "updated", State: "open"}
req, err := githubBase.New().Get(path).QueryStruct(params).Request()
Body
Json Body

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

type IssueRequest struct {
    Title     string   `json:"title,omitempty"`
    Body      string   `json:"body,omitempty"`
    Assignee  string   `json:"assignee,omitempty"`
    Milestone int      `json:"milestone,omitempty"`
    Labels    []string `json:"labels,omitempty"`
}
githubBase := sling.New().Base("https://api.github.com/").Client(httpClient)
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)

body := &IssueRequest{
    Title: "Test title",
    Body:  "Some issue",
}
req, err := githubBase.New().Post(path).BodyJSON(body).Request()

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

Form Body

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

type StatusUpdateParams struct {
    Status             string   `url:"status,omitempty"`
    InReplyToStatusId  int64    `url:"in_reply_to_status_id,omitempty"`
    MediaIds           []int64  `url:"media_ids,omitempty,comma"`
}
tweetParams := &StatusUpdateParams{Status: "writing some Go"}
req, err := twitterBase.New().Post(path).BodyForm(tweetParams).Request()

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

Extend a Sling

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.

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 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.

// Github Issue (abbreviated)
type Issue struct {
    Title  string `json:"title"`
    Body   string `json:"body"`
}
issues := new([]Issue)
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.

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"`
}
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 lists repository issues.

type IssueService struct {
    sling *sling.Sling
}

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

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().Get(path).QueryStruct(params).Receive(issues, githubError)
    if err == nil {
        err = githubError
    }
    return *issues, resp, err
}

Projects using Sling

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

Motivation

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

This project borrows and abstracts those ideas into a Sling, an agnostic component any API client can use for creating and sending requests.

License

MIT License

Documentation

Overview

Package 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 the examples to learn how to compose a Sling into your API client.

Usage

Use a Sling to create an http.Request with a chained API for setting properties (path, method, queries, body, etc.).

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)

Path

Use Path to set or extend the URL for created Requests. Extension means the path will be resolved relative to the existing URL.

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

Use Get, Post, Put, Patch, Delete, or Head which are exactly the same as Path except they set the HTTP method too.

req, err := sling.New().Post("http://upload.com/gophers")

Headers

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

base := sling.New().Base(baseUrl).Set("User-Agent", "Gophergram API Client")
req, err := base.New().Get("gophergram/list").Request()

QueryStruct

Define url parameter structs (https://godoc.org/github.com/google/go-querystring/query) and use QueryStruct to encode query parameters.

// Github Issue Parameters
type IssueParams struct {
    Filter    string `url:"filter,omitempty"`
    State     string `url:"state,omitempty"`
    Labels    string `url:"labels,omitempty"`
    Sort      string `url:"sort,omitempty"`
    Direction string `url:"direction,omitempty"`
    Since     string `url:"since,omitempty"`
}

githubBase := sling.New().Base("https://api.github.com/").Client(httpClient)
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)

params := &IssueParams{Sort: "updated", State: "open"}
req, err := githubBase.New().Get(path).QueryStruct(params).Request()

Json Body

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

type IssueRequest struct {
    Title     string   `json:"title,omitempty"`
    Body      string   `json:"body,omitempty"`
    Assignee  string   `json:"assignee,omitempty"`
    Milestone int      `json:"milestone,omitempty"`
    Labels    []string `json:"labels,omitempty"`
}

githubBase := sling.New().Base("https://api.github.com/").Client(httpClient)
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)

body := &IssueRequest{
    Title: "Test title",
    Body:  "Some issue",
}
req, err := githubBase.New().Post(path).BodyJSON(body).Request()

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

Form Body

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

type StatusUpdateParams struct {
    Status             string   `url:"status,omitempty"`
    InReplyToStatusId  int64    `url:"in_reply_to_status_id,omitempty"`
    MediaIds           []int64  `url:"media_ids,omitempty,comma"`
}

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

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

Extend a Sling

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.

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 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.

// Github Issue (abbreviated)
type Issue struct {
    Title  string `json:"title"`
    Body   string `json:"body"`
}

issues := new([]Issue)
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.

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"`
}

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.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Sling

type Sling struct {
	// contains filtered or unexported fields
}

Sling is an HTTP Request builder and sender.

func New

func New() *Sling

New returns a new Sling with an http DefaultClient.

func (*Sling) Add

func (s *Sling) Add(key, value string) *Sling

Add adds the key, value pair in Headers, appending values for existing keys to the key's values. Header keys are canonicalized.

func (*Sling) Base

func (s *Sling) Base(rawURL string) *Sling

Base sets the rawURL. If you intend to extend the url with Path, baseUrl should be specified with a trailing slash.

func (*Sling) BodyForm

func (s *Sling) BodyForm(bodyForm interface{}) *Sling

BodyForm sets the Sling's bodyForm. The value pointed to by the bodyForm will be url encoded to set the Body on new requests. The bodyStruct argument should be a pointer to a url tagged struct. See https://godoc.org/github.com/google/go-querystring/query for details.

func (*Sling) BodyJSON

func (s *Sling) BodyJSON(bodyJSON interface{}) *Sling

BodyJSON sets the Sling's bodyJSON. The value pointed to by the bodyJSON will be JSON encoded to set 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 (*Sling) Client

func (s *Sling) Client(httpClient *http.Client) *Sling

Client sets the http Client used to do requests. If a nil client is given, the http.DefaultClient will be used.

func (*Sling) Delete

func (s *Sling) Delete(pathURL string) *Sling

Delete sets the Sling method to DELETE and sets the given pathURL.

func (*Sling) Do

func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*http.Response, error)

Do sends an HTTP request and returns the response. Success responses (2XX) are JSON decoded into the value pointed to by successV and other responses are JSON decoded into the value pointed to by failureV. Any error sending the request or decoding the response is returned.

func (*Sling) Get

func (s *Sling) Get(pathURL string) *Sling

Get sets the Sling method to GET and sets the given pathURL.

func (*Sling) Head

func (s *Sling) Head(pathURL string) *Sling

Head sets the Sling method to HEAD and sets the given pathURL.

func (*Sling) New

func (s *Sling) New() *Sling

New returns a copy of a Sling for creating a new Sling with properties from a parent Sling. For example,

parentSling := sling.New().Client(client).Base("https://api.io/")
fooSling := parentSling.New().Get("foo/")
barSling := parentSling.New().Get("bar/")

fooSling and barSling will both use the same client, but send requests to https://api.io/foo/ and https://api.io/bar/ respectively.

Note that query and body values are copied so if pointer values are used, mutating the original value will mutate the value within the child Sling.

func (*Sling) Patch

func (s *Sling) Patch(pathURL string) *Sling

Patch sets the Sling method to PATCH and sets the given pathURL.

func (*Sling) Path

func (s *Sling) Path(path string) *Sling

Path extends the rawURL with the given path by resolving the reference to an absolute URL. If parsing errors occur, the rawURL is left unmodified.

func (*Sling) Post

func (s *Sling) Post(pathURL string) *Sling

Post sets the Sling method to POST and sets the given pathURL.

func (*Sling) Put

func (s *Sling) Put(pathURL string) *Sling

Put sets the Sling method to PUT and sets the given pathURL.

func (*Sling) QueryStruct

func (s *Sling) QueryStruct(queryStruct interface{}) *Sling

QueryStruct appends the queryStruct to the Sling's queryStructs. The value pointed to by each queryStruct will be encoded as url query parameters on new requests (see Request()). The queryStruct argument should be a pointer to a url tagged struct. See https://godoc.org/github.com/google/go-querystring/query for details.

func (*Sling) Receive

func (s *Sling) Receive(successV, failureV interface{}) (*http.Response, error)

Receive creates a new HTTP request and returns the response. Success responses (2XX) are JSON decoded into the value pointed to by successV and other responses are JSON decoded into the value pointed to by failureV. Any error creating the request, sending it, or decoding the response is returned. Receive is shorthand for calling Request and Do.

func (*Sling) ReceiveSuccess

func (s *Sling) ReceiveSuccess(successV interface{}) (*http.Response, error)

ReceiveSuccess creates a new HTTP request and returns the response. Success responses (2XX) are JSON decoded into the value pointed to by successV. Any error creating the request, sending it, or decoding a 2XX response is returned.

func (*Sling) Request

func (s *Sling) Request() (*http.Request, error)

Request returns a new http.Request created with the Sling properties. Returns any errors parsing the rawURL, encoding query structs, encoding the body, or creating the http.Request.

func (*Sling) Set

func (s *Sling) Set(key, value string) *Sling

Set sets the key, value pair in Headers, replacing existing values associated with key. Header keys are canonicalized.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL