Giter Club home page Giter Club logo

rsling's Introduction

GoDoc Build Status Go Report Card

Overview

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

rslings store HTTP Request properties to simplify sending requests and decoding responses. Check usage or the examples to learn how to compose a rsling into your API client.

Fair Notice: rsling started its life as a normal fork of the already popular and awesome sling. However overtime I noticed for some reason PRs like this and this etc. were not being entertained at all for unknown reasons hence I decided to make this repo into a hard/detached fork and go my separate way so that exciting new features and improvements can be added without hinderance. Having said that I would like to give a big thanks to the contributors and owners of the sling repository because without them this package could not have existed.

Differences from sling:

  • Easier to perform context aware requests, see
  • Ability to provide separate response decoders for success and failure cases, see
  • Ability to pass on response data as it is for data streaming purposes or deferred decoding, see
  • Much more intuitive way of extending URL paths using the Extend API, see

Features

  • Method Setters: Get/Post/Put/Patch/Delete/Head
  • Add or Set Request Headers
  • Base/Path: Extend a rsling for different endpoints
  • Encode structs into URL query parameters
  • Encode a form or JSON into the Request Body
  • Receive JSON success or failure responses
  • Control a request's lifetime via context

Install

go get github.com/rsjethani/rsling

Documentation

Read GoDoc

Usage

Use a rsling 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 := rsling.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 := rsling.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 := rsling.New().Post("http://upload.com/gophers")

Headers

Add or Set headers for requests created by a rsling.

s := rsling.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 := rsling.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 := rsling.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 rsling.

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

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

Extend a rsling

Each rsling 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 rsling to minimize duplication (e.g. a common client or base url).

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

const twitterApi = "https://api.twitter.com/1.1/"
base := rsling.New().Base(twitterApi).Client(authClient)

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

// statuses/update.json rsling
tweetPostrsling := base.New().Post("statuses/update.json").BodyForm(params)
req, err := tweetPostrsling.Request()

Without the calls to base.New(), tweetShowrsling and tweetPostrsling would reference the base rsling 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 rsling, 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

rsling 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 := rsling.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 {
    rsling *rsling.rsling
}

func NewIssueService(httpClient *http.Client) *IssueService {
    return &IssueService{
        rsling: rsling.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.rsling.New().Get(path).QueryStruct(params).Receive(issues, githubError)
    if err == nil {
        err = githubError
    }
    return *issues, resp, err
}

Controlling lifetime via context

All the above functionality of a rsling can be made context aware.

Getting a context aware request:

ctx, cancel := context.WithTimeout(context.Background(),10*time.Second)
req, err := rsling.New().Get("https://example.com").RequestWithContext(ctx)

Receiving in a context aware manner

success := &struct{}{}
failure := &struct{}{}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
resp, err := rsling.New().Path("https://example.com").Get("/foo").ReceiveWithContext(ctx,success,failure)

After making the request you can first check whether request completed in time before proceeding with the response:

if errors.Is(err, context.DeadlineExceeded) {
    // Take action accordingly
}

For more details about effectively using context please see: https://go.dev/blog/context

Example APIs using rsling

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 rsling, an agnostic component any API client can use for creating and sending requests.

Contributing

See the Contributing Guide.

License

MIT License

rsling's People

Contributors

dghubble avatar rsjethani avatar dependabot[bot] avatar aarono avatar dihedron avatar andyjeffries avatar perigrin avatar jesustinoco avatar ae6rt avatar miku avatar maxbeutel avatar mirceamironenco avatar olix0r avatar rliu054 avatar bigflood avatar buddhamagnet avatar jawher avatar wangyuehong avatar seipan avatar

Stargazers

 avatar  avatar Ravi Shekhar Jethani avatar

rsling's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.