json

package
v0.0.0-...-e0889ae Latest Latest
Warning

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

Go to latest
Published: Dec 29, 2019 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package json adds composition friendly JSON support to HTTP clients

A HTTP client request can be constructed using NewRequest which takes fluent functional args to specify the Body and Query parameters:

// queryArgs can be any type that can be serialized by
// github.com/google/go-querystring/query"
queryArgs := struct {
    Foo int
    Hoo string
}{ 42, "something" }

// body can be any type that can be serialied to JSON
body := struct {
    Hey string
}{ "Hey!" }

// make a HTTP request with these
req, err := json.NewRequest(
    "GET",
    url,
    json.Query(queryArgs),
    json.Body(body),
)

The response can then be sent using standard http.Client mechanisms.

This package also exposes a `Transport` type for parsing `application/json` content-types. The middleware pattern is built on top of the standard http.RoundTripper interface which allows other processing activity to be properly chained. For instance, it is possible to properly chain Retries (assuming that was also implemented as a http.RoundTripper):

// output can be any JSON decodable type
var output struct { ... }
client := &http.Client{
    Transport: json.Transport{
        Result: &output,
        // Transports can be chained!
        Transport: retry.Transport{
            // See github.com/tvastar/http/retry for details
            // of how to use the retry transport
            Transport: http.DefaultTransport,
        }
    },
}
res, err := client.Do(req)
// if there was no error, output will be filled in!
Example
package main

import (
	"bytes"
	gojson "encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"

	"github.com/tvastar/http/json"
)

func main() {
	// input and output can be strongly typed below
	makeCall := func(url string, query, input, output interface{}) (*http.Response, error) {
		req, err := json.NewRequest(
			"POST",
			url,
			json.Query(query),
			json.Body(input),
		)
		if err != nil {
			return nil, err
		}

		client := &http.Client{
			Transport: json.Transport{
				Result:    output,
				Transport: http.DefaultTransport,
			},
		}
		return client.Do(req)
	}

	// actual test

	server := simpleTestServer(func(query string, jsonBody interface{}) interface{} {
		return map[string]interface{}{
			"Query": query,
			"Body":  jsonBody,
		}
	})
	defer server.Close()

	input := map[string]interface{}{"hello": 42}
	var output interface{}

	res, err := makeCall(server.URL, sampleQuery(), input, &output)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	greeting, err := ioutil.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	// both of these should agree
	fmt.Printf("%s", greeting)
	fmt.Println(output)

}

func sampleQuery() interface{} {
	return struct {
		Foo  int  `url:"foo"`
		Heya bool `url:"heya"`
	}{42, true}
}

func simpleTestServer(fn func(query string, body interface{}) interface{}) *httptest.Server {

	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var body interface{}
		err := gojson.NewDecoder(r.Body).Decode(&body)
		if err != nil {
			panic(err)
		}

		var buf bytes.Buffer
		enc := gojson.NewEncoder(&buf)
		enc.SetEscapeHTML(false)
		err = enc.Encode(fn(r.URL.Query().Encode(), body))
		if err != nil {
			panic(err)
		}
		w.Header().Add("Content-Type", "application/json")
		_, err = w.Write(buf.Bytes())
		if err != nil {
			panic(err)
		}
	}))
}
Output:

{"Body":{"hello":42},"Query":"foo=42&heya=true"}
map[Body:map[hello:42] Query:foo=42&heya=true]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewRequest

func NewRequest(method, url string, options ...Option) (*http.Request, error)

NewRequest creates a new http Request with the provided options.

See Body and Query for interesting JSON options.

Example
package main

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/tvastar/http/json"
)

func main() {
	r, err := json.NewRequest(
		"POST",
		"http://localhost:99/boo?x=1",
		json.Query(sampleQuery()),
		json.Body(map[string]interface{}{"hello": 42}),
	)
	var buf bytes.Buffer
	err2 := r.Write(&buf)
	fmt.Println("Error:", err, err2)
	fmt.Println(strings.Replace(buf.String(), "\r", "", 100))

}

func sampleQuery() interface{} {
	return struct {
		Foo  int  `url:"foo"`
		Heya bool `url:"heya"`
	}{42, true}
}
Output:

Error: <nil> <nil>
POST /boo?foo=42&heya=true&x=1 HTTP/1.1
Host: localhost:99
User-Agent: Go-http-client/1.1
Content-Length: 13
Content-Type: application/json

{"hello":42}

Types

type Option

type Option func(req *http.Request) (*http.Request, error)

Option is an option to pass to NewRequest.

Custom options can either mutate the request or create a new request and return that

func Body

func Body(v interface{}) Option

Body updates the request to use the JSON encoding of the provided value. It also sets the Content-Type value to "application/json"

func Query

func Query(v interface{}) Option

Query updates the query with the provided args using standard URL encoding schemes. Existing query values are retained.

type Transport

type Transport struct {
	Result    interface{}       // where the result is stored
	Transport http.RoundTripper // the base transport
}

Transport implements a JSON-decoder HTTP transport.

This wraps another transport and only parses the response. If the response has the content type `application/json`, then the response body is decoded into `Result`. Note that `Result` must be a reference type (i.e. something that can be passed to json.Unmarshal).

func (Transport) RoundTrip

func (t Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements the http.RoundTripper interface

Jump to

Keyboard shortcuts

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