sling

package module
v1.4.2 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2024 License: MIT Imports: 8 Imported by: 811

README

Sling

GoDoc Workflow Sponsors Mastodon

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.

Features
  • Method Setters: Get/Post/Put/Patch/Delete/Head
  • Add or Set Request Headers
  • Base/Path: Extend a Sling for different endpoints
  • 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 set path, method, header, query, or body properties and create an http.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)
Path

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

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

Use Get, Post, Put, Patch, Delete, Head, Options, Trace, or Connect 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 for requests created by a Sling.

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

Define url tagged structs. Use QueryStruct to encode a struct as query parameters on requests.

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

Define JSON tagged structs. Use BodyJSON to JSON encode a struct as the Body on requests.

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

Define url tagged structs. Use BodyForm to form url encode a struct as the Body on requests.

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.

Plain Body

Use Body to set a plain io.Reader on requests created by a Sling.

body := strings.NewReader("raw body")
req, err := sling.New().Base("https://example.com").Body(body).Request()

Set a content type header, if desired (e.g. Set("Content-Type", "text/plain")).

Extend a Sling

Each Sling creates a standard http.Request (e.g. with some path and query params) each time Request() is called. You may wish to extend an existing Sling to minimize duplication (e.g. a common client or base url).

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(authClient)

// 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 would 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().

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

Modify a Request

Sling provides the raw http.Request so modifications can be made using standard net/http features. For example, in Go 1.7+ , add HTTP tracing to a request with a context:

req, err := sling.New().Get("https://example.com").QueryStruct(params).Request()
// handle error

trace := &httptrace.ClientTrace{
   DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
      fmt.Printf("DNS Info: %+v\n", dnsInfo)
   },
   GotConn: func(connInfo httptrace.GotConnInfo) {
      fmt.Printf("Got Conn: %+v\n", connInfo)
   },
}

req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
client.Do(req)
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.

const baseURL = "https://api.github.com/"

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
}

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

Contributing

See the Contributing Guide.

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 set path, method, header, query, or body properties and create an http.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)

Path

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

// creates a GET request to https://example.com/foo/bar
req, err := sling.New().Base("https://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 for requests created by a Sling.

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

QueryStruct

Define url parameter structs (https://godoc.org/github.com/google/go-querystring/query). Use QueryStruct to encode a struct as query parameters on requests.

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

Define JSON tagged structs (https://golang.org/pkg/encoding/json/). Use BodyJSON to JSON encode a struct as the Body on requests.

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

Define url tagged structs (https://godoc.org/github.com/google/go-querystring/query). Use BodyForm to form url encode a struct as the Body on requests.

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.

Plain Body

Use Body to set a plain io.Reader on requests created by a Sling.

body := strings.NewReader("raw body")
req, err := sling.New().Base("https://example.com").Body(body).Request()

Set a content type header, if desired (e.g. Set("Content-Type", "text/plain")).

Extend a Sling

Each Sling generates an http.Request (say with some path and query params) each time Request() is called, based on its state. When creating different 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(authClient)

// 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 would 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 BodyProvider

type BodyProvider interface {
	// ContentType returns the Content-Type of the body.
	ContentType() string
	// Body returns the io.Reader body.
	Body() (io.Reader, error)
}

BodyProvider provides Body content for http.Request attachment.

type Doer

type Doer interface {
	Do(req *http.Request) (*http.Response, error)
}

Doer executes http requests. It is implemented by *http.Client. You can wrap *http.Client with layers of Doers to form a stack of client-side middleware.

type ResponseDecoder added in v1.3.0

type ResponseDecoder interface {
	// Decode decodes the response into the value pointed to by v.
	Decode(resp *http.Response, v interface{}) error
}

ResponseDecoder decodes http responses into struct values.

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

func (s *Sling) Body(body io.Reader) *Sling

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 (*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 as the Body on new requests (see Request()). 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 (*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 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 (*Sling) BodyProvider

func (s *Sling) BodyProvider(body BodyProvider) *Sling

BodyProvider sets the Sling's body provider.

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) Connect added in v1.2.0

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

Connect sets the Sling method to CONNECT and sets the given pathURL.

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. If the status code of response is 204(no content) or the Content-Length is 0, decoding is skipped. Any error sending the request or decoding the response is returned.

func (*Sling) Doer

func (s *Sling) Doer(doer Doer) *Sling

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

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) Options added in v1.2.0

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

Options sets the Sling method to OPTIONS and sets the given pathURL.

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. If the status code of response is 204(no content) or the Content-Lenght is 0, decoding is skipped. 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) ResponseDecoder added in v1.3.0

func (s *Sling) ResponseDecoder(decoder ResponseDecoder) *Sling

ResponseDecoder sets the Sling's response decoder.

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.

func (*Sling) SetBasicAuth

func (s *Sling) SetBasicAuth(username, password string) *Sling

SetBasicAuth sets the Authorization header to use HTTP Basic Authentication with the provided username and password. With HTTP Basic Authentication the provided username and password are not encrypted.

func (*Sling) Trace added in v1.2.0

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

Trace sets the Sling method to TRACE and sets the given pathURL.

Directories

Path Synopsis
examples module

Jump to

Keyboard shortcuts

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