All Projects → pojozhang → sugar

pojozhang / sugar

Licence: MIT license
Declarative HTTP client for Golang

Programming Languages

go
31211 projects - #10 most used programming language

Projects that are alternatives of or similar to sugar

theon
Declarative library to build Web API clients & SDKs for the browser and node.js
Stars: ✭ 50 (+100%)
Mutual labels:  declarative, http-client
suru-plus-dark
Suru++ 25 Dark — A full dark cyberpunk, elegant, futuristic and Papirus-like third-party icons theme
Stars: ✭ 55 (+120%)
Mutual labels:  elegant
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
Stars: ✭ 179,407 (+717528%)
Mutual labels:  declarative
Rump
REST client for Java that allows for easy configuration and default values. Allows for quick request construction and a huge range of modifications by using response/request interceptors, adjusting default values related to HTTP requests and creating custom instances for when you need multiple API connection setups.
Stars: ✭ 55 (+120%)
Mutual labels:  http-client
nxt init
Define initializers and attribute readers for your arguments with a single line of code
Stars: ✭ 21 (-16%)
Mutual labels:  sugar
http
ponylang HTTP client library 🐴 🕸️
Stars: ✭ 38 (+52%)
Mutual labels:  http-client
Plotly.py
The interactive graphing library for Python (includes Plotly Express) ✨
Stars: ✭ 10,701 (+42704%)
Mutual labels:  declarative
vuo
A realtime visual programming language for interactive media.
Stars: ✭ 103 (+312%)
Mutual labels:  declarative
pawn-requests
pawn-requests provides an API for interacting with HTTP(S) JSON APIs.
Stars: ✭ 56 (+124%)
Mutual labels:  http-client
typesense-dart
Dart client for Typesense
Stars: ✭ 50 (+100%)
Mutual labels:  http-client
react-is-scrolling
Simply detect if users are scrolling in your components in a declarative API
Stars: ✭ 17 (-32%)
Mutual labels:  declarative
flyteidl
Specification of the IR for Flyte workflows and tasks. Also Interfaces for all backend services. https://docs.flyte.org/projects/flyteidl/en/stable/
Stars: ✭ 27 (+8%)
Mutual labels:  declarative
bs-declaredom
Strongly typed declarative markup for the DOM and CSS
Stars: ✭ 66 (+164%)
Mutual labels:  declarative
cpphttpstack
c++ api for http client & server
Stars: ✭ 30 (+20%)
Mutual labels:  http-client
hackernews
📰 HackerNews API
Stars: ✭ 40 (+60%)
Mutual labels:  http-client
Layerjs
layerJS: Javascript UI composition framework
Stars: ✭ 1,825 (+7200%)
Mutual labels:  declarative
sparrowql
Declarative MongoDB aggregations.
Stars: ✭ 28 (+12%)
Mutual labels:  declarative
dclareForMPS
Adding declarative, reactive and incremental rules to MPS
Stars: ✭ 21 (-16%)
Mutual labels:  declarative
react-nonav
Experimental React Native declarative navigation
Stars: ✭ 58 (+132%)
Mutual labels:  declarative
simplehttp
HTTP client for Elixir without dependencies
Stars: ✭ 16 (-36%)
Mutual labels:  http-client

GitHub (pre-)release Go codecov Go Report Card go GoDoc license

Sugar is a DECLARATIVE http client providing elegant APIs for Golang.

🇨🇳 中文文档

🌈 Features

  • Elegant APIs
  • Plugins
  • Chained invocations
  • Highly extensible

🚀 Download

go get -add github.com/pojozhang/sugar

📙 Usage

Firstly you need to import the package.

import . "github.com/pojozhang/sugar"

And now you are ready to easily send any request to any corner on this blue planet.

Request

Plain Text

// POST /books HTTP/1.1
// Host: api.example.com
// Content-Type: text/plain
Post(ctx, "http://api.example.com/books", "bookA")

Path

// GET /books/123 HTTP/1.1
// Host: api.example.com
Get(ctx, "http://api.example.com/books/:id", Path{"id": 123})
Get(ctx, "http://api.example.com/books/:id", P{"id": 123})

Query

// GET /books?name=bookA HTTP/1.1
// Host: api.example.com
Get(ctx, "http://api.example.com/books", Query{"name": "bookA"})
Get(ctx, "http://api.example.com/books", Q{"name": "bookA"})

// list
// GET /books?name=bookA&name=bookB HTTP/1.1
// Host: api.example.com
Get(ctx, "http://api.example.com/books", Query{"name": List{"bookA", "bookB"}})
Get(ctx, "http://api.example.com/books", Q{"name": L{"bookA", "bookB"}})

Cookie

// GET /books HTTP/1.1
// Host: api.example.com
// Cookie: name=bookA
Get(ctx, "http://api.example.com/books", Cookie{"name": "bookA"})
Get(ctx, "http://api.example.com/books", C{"name": "bookA"})

Header

// GET /books HTTP/1.1
// Host: api.example.com
// Name: bookA
Get(ctx, "http://api.example.com/books", Header{"name": "bookA"})
Get(ctx, "http://api.example.com/books", H{"name": "bookA"})

Json

// POST /books HTTP/1.1
// Host: api.example.com
// Content-Type: application/json;charset=UTF-8
// {"name":"bookA"}
Post(ctx, "http://api.example.com/books", Json{`{"name":"bookA"}`})
Post(ctx, "http://api.example.com/books", J{`{"name":"bookA"}`})

// map
Post(ctx, "http://api.example.com/books", Json{Map{"name": "bookA"}})
Post(ctx, "http://api.example.com/books", J{M{"name": "bookA"}})

// list
Post(ctx, "http://api.example.com/books", Json{List{Map{"name": "bookA"}}})
Post(ctx, "http://api.example.com/books", J{L{M{"name": "bookA"}}})

Xml

// POST /books HTTP/1.1
// Host: api.example.com
// Authorization: Basic dXNlcjpwYXNzd29yZA==
// Content-Type: application/xml; charset=UTF-8
// <book name="bookA"></book>
Post(ctx, "http://api.example.com/books", Xml{`<book name="bookA"></book>`})
Post(ctx, "http://api.example.com/books", X{`<book name="bookA"></book>`})

Form

// POST /books HTTP/1.1
// Host: api.example.com
// Content-Type: application/x-www-form-urlencoded
Post(ctx, "http://api.example.com/books", Form{"name": "bookA"})
Post(ctx, "http://api.example.com/books", F{"name": "bookA"})

// list
Post(ctx, "http://api.example.com/books", Form{"name": List{"bookA", "bookB"}})
Post(ctx, "http://api.example.com/books", F{"name": L{"bookA", "bookB"}})

Basic Auth

// DELETE /books HTTP/1.1
// Host: api.example.com
// Authorization: Basic dXNlcjpwYXNzd29yZA==
Delete(ctx, "http://api.example.com/books", User{"user", "password"})
Delete(ctx, "http://api.example.com/books", U{"user", "password"})

Multipart

// POST /books HTTP/1.1
// Host: api.example.com
// Content-Type: multipart/form-data; boundary=19b8acc2469f1914a24fc6e0152aac72f1f92b6f5104b57477262816ab0f
//
// --19b8acc2469f1914a24fc6e0152aac72f1f92b6f5104b57477262816ab0f
// Content-Disposition: form-data; name="name"
//
// bookA
// --19b8acc2469f1914a24fc6e0152aac72f1f92b6f5104b57477262816ab0f
// Content-Disposition: form-data; name="file"; filename="text"
// Content-Type: application/octet-stream
//
// hello sugar!
// --19b8acc2469f1914a24fc6e0152aac72f1f92b6f5104b57477262816ab0f--
f, _ := os.Open("text")
Post(ctx, "http://api.example.com/books", MultiPart{"name": "bookA", "file": f})
Post(ctx, "http://api.example.com/books", MP{"name": "bookA", "file": f})

Mix

Due to Sugar's flexible design, different types of parameters can be freely combined.

Patch(ctx, "http://api.example.com/books/:id", Path{"id": 123}, Json{`{"name":"bookA"}`}, User{"user", "password"})

Apply

You can use Apply() to preset some values which will be attached to every following request. Call Reset() to clean preset values.

Apply(User{"user", "password"})
Get(ctx, "http://api.example.com/books")
Get(ctx, "http://api.example.com/books")
Reset()
Get(ctx, "http://api.example.com/books")
Get(ctx, "http://api.example.com/books", User{"user", "password"})
Get(ctx, "http://api.example.com/books", User{"user", "password"})
Get(ctx, "http://api.example.com/books")

The latter is equal to the former.

Response

A request API always returns a value of type *Response which also provides some nice APIs.

Raw

Raw() returns a value of type *http.Response and an error which is similar to standard go API.

resp, err := Post(ctx, "http://api.example.com/books", "bookA").Raw()
...

ReadBytes

ReadBytes() is another syntax sugar to read bytes from response body. Notice that this method will close body after reading.

bytes, resp, err := Get(ctx, "http://api.example.com/books").ReadBytes()
...

Read

Read() reads different types of response via decoder API. The following two examples read response body as plain text/JSON according to different content types.

// plain text
var text = new(string)
resp, err := Get(ctx, "http://api.example.com/text").Read(text)

// json
var books []book
resp, err := Get(ctx, "http://api.example.com/json").Read(&books)

Download files

You can also use Read() to download files.

f,_ := os.Create("tmp.png")
defer f.Close()
resp, err := Get(ctx, "http://api.example.com/logo.png").Read(f)

🔌 Extension

There are three major components in Sugar: Encoder, Decoder and Plugin.

  • An encoder is used to encode your parameters and assemble requests.
  • A decoder is used to decode the data from response body.
  • A plugin is designed to work as an interceptor.

Encoder

You can register your custom encoder which should implement Encoder interface.

type MyEncoder struct {
}

func (r *MyEncoder) Encode(context *RequestContext, chain *EncoderChain) error {
    myParams, ok := context.Param.(MyParam)
    if !ok {
	return chain.Next()
    }
    ...
    req := context.Request
    ...
    return nil
}

Encoders.Add(&MyEncoder{})

Get(ctx, "http://api.example.com/books", MyParam{})

Decoder

You can implement Decoder interface so that you can convert a response body to a specific struct. It is very convenient to get converted value via func (r *Response) Read(v interface{}) (*http.Response, error) API.

type MyDecoder struct {
}

func (d *MyDecoder) Decode(context *ResponseContext, chain *DecoderChain) error {
    // decode data from body if a content type named `my-content-type` is set in header
    for _, contentType := range context.Response.Header[ContentType] {
	if strings.Contains(strings.ToLower(contentType), "my-content-type") {
	    body, err := ioutil.ReadAll(context.Response.Body)
	    if err != nil {
		return err
	    }
	    json.Unmarshal(body, context.Out)
	    ...
	    return nil
	}
    }
    return chain.Next()
}

Decoders.Add(&MyDecoder{})

Plugin

Plugin is a new feature since V2. You can do anything as you like before the request is sent or after the response is received.

// Implementation of builtin Logger plugin
Use(func(c *Context) error {
    b, _ := httputil.DumpRequest(c.Request, true)
    log.Println(string(b))
    defer func() {
        if c.Response != nil {
	    b, _ := httputil.DumpResponse(c.Response, true)
	    log.Println(string(b))
	}
    }()
    return c.Next()
})

Logger

You can use Logger plugin to log any request you send or any response you get.

Use(Logger)

Retryer

You can use Retryer plugin to retry a request when the server returns 500 or when you get a net error.

Use(Retryer(3, time.Second, 1, time.Second))

Custom error handling

Sometimes you may get an custom API error when a request is invalid. The following example shows you how to handle this situation via a plugin:

// your error struct
type apiError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

func (e apiError) Error() string {
	return e.Message
}

Use(func(c *Context) error {
		if err := c.Next(); err != nil {
			return err
		}

		if c.Response != nil && c.Response.StatusCode >= http.StatusBadRequest {
			defer func() { c.Response.Body.Close() }()
			body, err := ioutil.ReadAll(c.Response.Body)
			if err != nil {
				return err
			}

			e := apiError{}
			if err = json.Unmarshal(body, &e); err != nil {
				return err
			}
			return e
		}

		return nil
	})

// send request
_, err := client.Get(ctx, "some url").Read(&json{})
// type switch
switch e := err.(type) {
	case apiError:
		// your code
	}
// your code
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].